blob: f9d1e9e73bf98d472289383809265f6dc6d822d5 [file] [log] [blame]
// Copyright 2018 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.analysis;
import static com.google.common.truth.Truth.assertThat;
import static com.google.devtools.build.lib.actions.CompletionContext.FAILED_COMPLETION_CTX;
import static com.google.devtools.build.lib.analysis.TargetCompleteEvent.newFileFromArtifact;
import static java.nio.charset.StandardCharsets.ISO_8859_1;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.actions.ActionInputMap;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.Artifact.SpecialArtifact;
import com.google.devtools.build.lib.actions.Artifact.TreeFileArtifact;
import com.google.devtools.build.lib.actions.ArtifactPathResolver;
import com.google.devtools.build.lib.actions.CompletionContext;
import com.google.devtools.build.lib.actions.EventReportingArtifacts.ReportedArtifacts;
import com.google.devtools.build.lib.actions.FileArtifactValue;
import com.google.devtools.build.lib.analysis.TopLevelArtifactHelper.ArtifactsToBuild;
import com.google.devtools.build.lib.analysis.config.BuildConfigurationValue;
import com.google.devtools.build.lib.analysis.util.AnalysisTestCase;
import com.google.devtools.build.lib.buildeventstream.BuildEvent.LocalFile;
import com.google.devtools.build.lib.buildeventstream.BuildEvent.LocalFile.LocalFileType;
import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.File;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.skyframe.ConfiguredTargetAndData;
import com.google.devtools.build.lib.skyframe.TreeArtifactValue;
import com.google.devtools.build.lib.testutil.TestConstants;
import com.google.devtools.build.lib.util.OS;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.util.ArrayList;
import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Tests for {@link TargetCompleteEvent}. */
@RunWith(JUnit4.class)
public class TargetCompleteEventTest extends AnalysisTestCase {
@Test
public void testReferencedSourceFile() throws Exception {
scratch.file("BUILD", "filegroup(name = 'files', srcs = ['file'])");
scratch.file("file", "content does not matter");
ConfiguredTargetAndData ctAndData = getCtAndData("//:files");
ArtifactsToBuild artifactsToBuild = getArtifactsToBuild(ctAndData);
Artifact artifact = Iterables.getOnlyElement(artifactsToBuild.getAllArtifacts().toList());
FileArtifactValue metadata =
FileArtifactValue.createForNormalFile(new byte[] {1, 2, 3}, null, 10);
CompletionContext completionContext =
getCompletionContext(ImmutableMap.of(artifact, metadata), ImmutableMap.of());
TargetCompleteEvent event =
TargetCompleteEvent.successfulBuild(
ctAndData,
completionContext,
artifactsToBuild.getAllArtifactsByOutputGroup(),
/* announceTargetSummary= */ false);
assertThat(event.referencedLocalFiles())
.containsExactly(
new LocalFile(artifact.getPath(), LocalFileType.OUTPUT_FILE, artifact, metadata));
}
@Test
public void testReferencedSourceDirectory() throws Exception {
scratch.file("BUILD", "filegroup(name = 'files', srcs = ['dir'])");
scratch.file("dir/file", "content does not matter");
ConfiguredTargetAndData ctAndData = getCtAndData("//:files");
ArtifactsToBuild artifactsToBuild = getArtifactsToBuild(ctAndData);
Artifact artifact = Iterables.getOnlyElement(artifactsToBuild.getAllArtifacts().toList());
FileArtifactValue metadata = FileArtifactValue.createForDirectoryWithMtime(0);
CompletionContext completionContext =
getCompletionContext(ImmutableMap.of(artifact, metadata), ImmutableMap.of());
TargetCompleteEvent event =
TargetCompleteEvent.successfulBuild(
ctAndData,
completionContext,
artifactsToBuild.getAllArtifactsByOutputGroup(),
/* announceTargetSummary= */ false);
assertThat(event.referencedLocalFiles())
.containsExactly(
new LocalFile(artifact.getPath(), LocalFileType.OUTPUT_DIRECTORY, artifact, metadata));
}
@Test
public void testReferencedTreeArtifact() throws Exception {
scratch.file(
"defs.bzl",
"def _impl(ctx):",
" d = ctx.actions.declare_directory(ctx.label.name)",
" ctx.actions.run_shell(outputs = [d], command = 'does not matter')",
" return DefaultInfo(files = depset([d]))",
"dir = rule(_impl)");
scratch.file(
"BUILD",
"load(':defs.bzl', 'dir')",
"dir(name = 'dir')",
"filegroup(name = 'files', srcs = ['dir'])");
ConfiguredTargetAndData ctAndData = getCtAndData("//:files");
ArtifactsToBuild artifactsToBuild = getArtifactsToBuild(ctAndData);
SpecialArtifact tree =
(SpecialArtifact) Iterables.getOnlyElement(artifactsToBuild.getAllArtifacts().toList());
TreeFileArtifact fileChild =
TreeFileArtifact.createTreeOutput(tree, PathFragment.create("dir/file.txt"));
FileArtifactValue fileMetadata =
FileArtifactValue.createForNormalFile(new byte[] {1, 2, 3}, null, 10);
// A TreeFileArtifact can be a directory, when materialized by a symlink.
// See https://github.com/bazelbuild/bazel/issues/20418.
TreeFileArtifact dirChild = TreeFileArtifact.createTreeOutput(tree, PathFragment.create("sym"));
FileArtifactValue dirMetadata = FileArtifactValue.createForDirectoryWithMtime(123456789);
TreeArtifactValue metadata =
TreeArtifactValue.newBuilder(tree)
.putChild(fileChild, fileMetadata)
.putChild(dirChild, dirMetadata)
.build();
CompletionContext completionContext =
getCompletionContext(ImmutableMap.of(), ImmutableMap.of(tree, metadata));
TargetCompleteEvent event =
TargetCompleteEvent.successfulBuild(
ctAndData,
completionContext,
artifactsToBuild.getAllArtifactsByOutputGroup(),
/* announceTargetSummary= */ false);
assertThat(event.referencedLocalFiles())
.containsExactly(
new LocalFile(fileChild.getPath(), LocalFileType.OUTPUT_FILE, fileChild, fileMetadata),
new LocalFile(
dirChild.getPath(), LocalFileType.OUTPUT_DIRECTORY, dirChild, dirMetadata));
}
@Test
public void testReferencedUnresolvedSymlink() throws Exception {
scratch.file(
"defs.bzl",
"def _impl(ctx):",
" s = ctx.actions.declare_symlink(ctx.label.name)",
" ctx.actions.symlink(output = s, target_path = 'does not matter')",
" return DefaultInfo(files = depset([s]))",
"sym = rule(_impl)");
scratch.file(
"BUILD",
"load(':defs.bzl', 'sym')",
"sym(name = 'sym')",
"filegroup(name = 'files', srcs = ['sym'])");
ConfiguredTargetAndData ctAndData = getCtAndData("//:files");
ArtifactsToBuild artifactsToBuild = getArtifactsToBuild(ctAndData);
Artifact artifact = Iterables.getOnlyElement(artifactsToBuild.getAllArtifacts().toList());
artifact.getPath().getParentDirectory().createDirectoryAndParents();
artifact.getPath().createSymbolicLink(fileSystem.getPath("/some/path"));
FileArtifactValue metadata = FileArtifactValue.createForUnresolvedSymlink(artifact.getPath());
CompletionContext completionContext =
getCompletionContext(ImmutableMap.of(artifact, metadata), ImmutableMap.of());
TargetCompleteEvent event =
TargetCompleteEvent.successfulBuild(
ctAndData,
completionContext,
artifactsToBuild.getAllArtifactsByOutputGroup(),
/* announceTargetSummary= */ false);
assertThat(event.referencedLocalFiles())
.containsExactly(
new LocalFile(artifact.getPath(), LocalFileType.OUTPUT_SYMLINK, artifact, metadata));
}
/** Regression test for b/165671166. */
@Test
public void testFileProtoFromArtifactReencodesAsUtf8() throws Exception {
if (OS.getCurrent() == OS.WINDOWS) {
// Windows filesystems return paths with wide characters and don't suffer from the current
// workaround where arbitrary bytes are represented to Java as Latin-1.
return;
}
scratch.file("sh/BUILD", "filegroup(name = 'globby', srcs = glob(['dir/*']))");
// Bytes are UTF-8 encoding of: sh/dir/圖片
byte[] filenameBytes = {
0x73, 0x68, 0x2f, 0x64, 0x69, 0x72, 0x2f, -27, -100, -106, -25, -119, -121
};
String utf8InLatin1FileName = new String(filenameBytes, ISO_8859_1);
scratch.file(utf8InLatin1FileName, "content does not matter");
ConfiguredTargetAndData ctAndData = getCtAndData("//sh:globby");
ArtifactsToBuild artifactsToBuild = getArtifactsToBuild(ctAndData);
TargetCompleteEvent event =
TargetCompleteEvent.successfulBuild(
ctAndData,
FAILED_COMPLETION_CTX,
artifactsToBuild.getAllArtifactsByOutputGroup(),
/*announceTargetSummary=*/ false);
ArrayList<File> fileProtos = new ArrayList<>();
ReportedArtifacts reportedArtifacts = event.reportedArtifacts();
for (NestedSet<Artifact> artifactSet : reportedArtifacts.artifacts) {
for (Artifact a : artifactSet.toListInterruptibly()) {
fileProtos.add(
newFileFromArtifact(
/* name= */ null,
a,
PathFragment.EMPTY_FRAGMENT,
FAILED_COMPLETION_CTX,
/* uri= */ null));
}
}
// Bytes are the same but the encoding is actually UTF-8 as required of a protobuf string.
String utf8FileName = new String(filenameBytes, UTF_8);
assertThat(fileProtos).hasSize(1);
assertThat(fileProtos.get(0).getName()).isEqualTo(utf8FileName);
}
private ConfiguredTargetAndData getCtAndData(String target) throws Exception {
AnalysisResult result = update(target);
ConfiguredTarget ct = Iterables.getOnlyElement(result.getTargetsToBuild());
TargetAndConfiguration tac = Iterables.getOnlyElement(result.getTopLevelTargetsWithConfigs());
var configuredTargetConfiguration =
(BuildConfigurationValue)
skyframeExecutor.getEvaluator().getExistingValue(ct.getConfigurationKey());
return new ConfiguredTargetAndData(ct, tac.getTarget(), configuredTargetConfiguration, null);
}
private ArtifactsToBuild getArtifactsToBuild(ConfiguredTargetAndData ctAndData) {
TopLevelArtifactContext context =
new TopLevelArtifactContext(false, false, false, OutputGroupInfo.DEFAULT_GROUPS);
return TopLevelArtifactHelper.getAllArtifactsToBuild(ctAndData.getConfiguredTarget(), context);
}
private CompletionContext getCompletionContext(
Map<Artifact, FileArtifactValue> metadata,
Map<SpecialArtifact, TreeArtifactValue> treeMetadata) {
ImmutableMap.Builder<Artifact, ImmutableCollection<? extends Artifact>> expandedArtifacts =
ImmutableMap.builder();
ActionInputMap inputMap = new ActionInputMap(0);
for (Map.Entry<Artifact, FileArtifactValue> entry : metadata.entrySet()) {
expandedArtifacts.put(entry.getKey(), ImmutableList.of(entry.getKey()));
inputMap.put(entry.getKey(), entry.getValue(), /* depOwner= */ null);
}
for (Map.Entry<SpecialArtifact, TreeArtifactValue> entry : treeMetadata.entrySet()) {
expandedArtifacts.put(entry.getKey(), entry.getValue().getChildren());
inputMap.putTreeArtifact(entry.getKey(), entry.getValue(), /* depOwner= */ null);
}
return new CompletionContext(
directories.getExecRoot(TestConstants.WORKSPACE_NAME),
expandedArtifacts.buildOrThrow(),
/* expandedFilesets= */ ImmutableMap.of(),
ArtifactPathResolver.IDENTITY,
inputMap,
/* expandFilesets= */ false,
/* fullyResolveFilesetLinks= */ false);
}
}