| // Copyright 2022 The Bazel Authors. All rights reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| package com.google.devtools.build.lib.query2.cquery; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| import static java.nio.charset.StandardCharsets.UTF_8; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.eventbus.EventBus; |
| import com.google.devtools.build.lib.analysis.OutputGroupInfo; |
| import com.google.devtools.build.lib.analysis.OutputGroupInfo.ValidationMode; |
| import com.google.devtools.build.lib.analysis.TopLevelArtifactContext; |
| import com.google.devtools.build.lib.events.Event; |
| import com.google.devtools.build.lib.events.Reporter; |
| import com.google.devtools.build.lib.query2.PostAnalysisQueryEnvironment; |
| import com.google.devtools.build.lib.query2.engine.QueryExpression; |
| import com.google.devtools.build.lib.query2.engine.QueryParser; |
| import java.io.ByteArrayOutputStream; |
| import java.io.PrintStream; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.stream.Collectors; |
| import org.junit.Before; |
| import org.junit.Test; |
| |
| /** Tests cquery's {@link --output=files} format. */ |
| public class FilesOutputFormatterCallbackTest extends ConfiguredTargetQueryTest { |
| |
| private CqueryOptions options; |
| private Reporter reporter; |
| private final List<Event> events = new ArrayList<>(); |
| |
| @Before |
| public final void defineSimpleRule() throws Exception { |
| writeFile( |
| "defs/rules.bzl", |
| "def _r_impl(ctx):", |
| " default_file = ctx.actions.declare_file(ctx.attr.name + '_default_file')", |
| " output_group_only = ctx.actions.declare_file(ctx.attr.name + '_output_group_only')", |
| " runfile = ctx.actions.declare_file(ctx.attr.name + '_runfile')", |
| " executable_only = ctx.actions.declare_file(ctx.attr.name + '_executable')", |
| " files = [default_file, output_group_only, runfile, executable_only]", |
| " ctx.actions.run_shell(", |
| " outputs = files,", |
| " command = '\\n'.join(['touch %s' % file.path for file in files]),", |
| " )", |
| " return [", |
| " DefaultInfo(", |
| " executable = executable_only,", |
| " files = depset(", |
| " direct = [", |
| " default_file,", |
| " ctx.file._implicit_source_dep,", |
| " ctx.file.explicit_source_dep,", |
| " ],", |
| " transitive = [info[DefaultInfo].files for info in ctx.attr.deps]", |
| " ),", |
| " runfiles = ctx.runfiles([runfile]),", |
| " ),", |
| " OutputGroupInfo(", |
| " foobar = [output_group_only, ctx.file.explicit_source_dep],", |
| " ),", |
| " ]", |
| "r = rule(", |
| " implementation = _r_impl,", |
| " executable = True,", |
| " attrs = {", |
| " 'deps': attr.label_list(),", |
| " '_implicit_source_dep': attr.label(default = 'rules.bzl', allow_single_file =" |
| + " True),", |
| " 'explicit_source_dep': attr.label(allow_single_file = True),", |
| " },", |
| ")"); |
| writeFile("defs/BUILD", "exports_files(['rules.bzl'])"); |
| writeFile( |
| "pkg/BUILD", |
| "load('//defs:rules.bzl', 'r')", |
| "r(", |
| " name = 'main',", |
| " explicit_source_dep = 'BUILD',", |
| ")", |
| "r(", |
| " name = 'other',", |
| " deps = [':main'],", |
| " explicit_source_dep = 'BUILD',", |
| ")"); |
| } |
| |
| @Before |
| public final void setUpCqueryOptions() { |
| this.options = new CqueryOptions(); |
| this.reporter = new Reporter(new EventBus(), events::add); |
| } |
| |
| private List<String> getOutput(String queryExpression, List<String> outputGroups) |
| throws Exception { |
| QueryExpression expression = QueryParser.parse(queryExpression, getDefaultFunctions()); |
| Set<String> targetPatternSet = new LinkedHashSet<>(); |
| expression.collectTargetPatterns(targetPatternSet); |
| PostAnalysisQueryEnvironment<KeyedConfiguredTarget> env = |
| ((ConfiguredTargetQueryHelper) helper).getPostAnalysisQueryEnvironment(targetPatternSet); |
| |
| ByteArrayOutputStream output = new ByteArrayOutputStream(); |
| FilesOutputFormatterCallback callback = |
| new FilesOutputFormatterCallback( |
| reporter, |
| options, |
| new PrintStream(output), |
| getHelper().getSkyframeExecutor(), |
| env.getAccessor(), |
| // Based on BuildRequest#getTopLevelArtifactContext. |
| new TopLevelArtifactContext( |
| false, |
| false, |
| false, |
| OutputGroupInfo.determineOutputGroups(outputGroups, ValidationMode.OFF, false))); |
| env.evaluateQuery(expression, callback); |
| return Arrays.asList(output.toString(UTF_8).split(System.lineSeparator())); |
| } |
| |
| @Test |
| public void basicQuery_defaultOutputGroup() throws Exception { |
| List<String> output = getOutput("//pkg:all", ImmutableList.of()); |
| var sourceAndGeneratedFiles = |
| output.stream() |
| .collect(Collectors.<String>partitioningBy(path -> path.matches("^[^/]*-out/.*"))); |
| assertThat(sourceAndGeneratedFiles.get(false)).containsExactly("pkg/BUILD", "defs/rules.bzl"); |
| assertContainsExactlyWithBinDirPrefix( |
| sourceAndGeneratedFiles.get(true), "pkg/main_default_file", "pkg/other_default_file"); |
| } |
| |
| @Test |
| public void basicQuery_defaultAndCustomOutputGroup() throws Exception { |
| List<String> output = getOutput("//pkg:main", ImmutableList.of("+foobar")); |
| var sourceAndGeneratedFiles = |
| output.stream() |
| .collect(Collectors.<String>partitioningBy(path -> path.matches("^[^/]*-out/.*"))); |
| assertThat(sourceAndGeneratedFiles.get(false)).containsExactly("pkg/BUILD", "defs/rules.bzl"); |
| assertContainsExactlyWithBinDirPrefix( |
| sourceAndGeneratedFiles.get(true), "pkg/main_default_file", "pkg/main_output_group_only"); |
| } |
| |
| @Test |
| public void basicQuery_customOutputGroupOnly() throws Exception { |
| List<String> output = getOutput("//pkg:other", ImmutableList.of("foobar")); |
| var sourceAndGeneratedFiles = |
| output.stream() |
| .collect(Collectors.<String>partitioningBy(path -> path.matches("^[^/]*-out/.*"))); |
| assertThat(sourceAndGeneratedFiles.get(false)).containsExactly("pkg/BUILD"); |
| assertContainsExactlyWithBinDirPrefix( |
| sourceAndGeneratedFiles.get(true), "pkg/other_output_group_only"); |
| } |
| |
| private void assertContainsExactlyWithBinDirPrefix( |
| List<String> output, String... binDirRelativePaths) { |
| if (binDirRelativePaths.length == 0) { |
| assertThat(output).isEmpty(); |
| return; |
| } |
| |
| // Extract the configuration-dependent bin dir from the first output. |
| assertThat(output).isNotEmpty(); |
| String firstPath = output.get(0); |
| String binDir = firstPath.substring(0, firstPath.indexOf("bin/") + "bin/".length()); |
| |
| assertThat(output) |
| .containsExactly( |
| Arrays.stream(binDirRelativePaths) |
| .map(binDirRelativePath -> binDir + binDirRelativePath) |
| .toArray()); |
| } |
| } |