| // Copyright 2021 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.packages; | |
| import static com.google.common.collect.ImmutableList.toImmutableList; | |
| import static com.google.common.truth.Truth.assertThat; | |
| import static org.junit.Assert.assertThrows; | |
| import static org.junit.Assert.fail; | |
| import com.google.common.collect.ImmutableList; | |
| import com.google.devtools.build.lib.analysis.util.BuildViewTestCase; | |
| import com.google.devtools.build.lib.cmdline.Label; | |
| import com.google.devtools.build.lib.skyframe.ConfiguredTargetAndData; | |
| import com.google.devtools.build.lib.vfs.ModifiedFileSet; | |
| import com.google.devtools.build.lib.vfs.PathFragment; | |
| import com.google.devtools.build.lib.vfs.Root; | |
| import java.io.IOException; | |
| import java.util.List; | |
| import java.util.stream.Collectors; | |
| import org.junit.Test; | |
| import org.junit.runner.RunWith; | |
| import org.junit.runners.JUnit4; | |
| /** Tests for {@code native.subpackages} function. */ | |
| @RunWith(JUnit4.class) | |
| public class NativeSubpackagesTest extends BuildViewTestCase { | |
| private static final String ALL_SUBDIRS = "**"; | |
| @Test | |
| public void subpackages_simple_subDir() throws Exception { | |
| makeSubpackageFileGroup("test/starlark/BUILD", ALL_SUBDIRS, null, null); | |
| makeFilesSubPackage("test/starlark/sub"); | |
| assertAttrLabelList( | |
| "//test/starlark:files", "srcs", ImmutableList.of("//test/starlark/sub:files")); | |
| } | |
| @Test | |
| public void subpackages_simple_include() throws Exception { | |
| makeSubpackageFileGroup("test/starlark/BUILD", "sub1", null, null); | |
| makeFilesSubPackage("test/starlark/sub"); | |
| makeFilesSubPackage("test/starlark/sub1"); | |
| makeFilesSubPackage("test/starlark/sub2"); | |
| assertAttrLabelList( | |
| "//test/starlark:files", "srcs", ImmutableList.of("//test/starlark/sub1:files")); | |
| } | |
| @Test | |
| public void subpackages_simple_exclude() throws Exception { | |
| makeSubpackageFileGroup("test/starlark/BUILD", ALL_SUBDIRS, "['sub2/**']", null); | |
| makeFilesSubPackage("test/starlark/sub"); | |
| makeFilesSubPackage("test/starlark/sub1"); | |
| makeFilesSubPackage("test/starlark/sub2"); | |
| makeFilesSubPackage("test/starlark/sub3"); | |
| assertAttrLabelList( | |
| "//test/starlark:files", | |
| "srcs", | |
| ImmutableList.of( | |
| "//test/starlark/sub:files", | |
| "//test/starlark/sub1:files", | |
| "//test/starlark/sub3:files")); | |
| } | |
| @Test | |
| public void subpackages_simple_empty_allow() throws Exception { | |
| makeSubpackageFileGroup("test/starlark/BUILD", ALL_SUBDIRS, null, true); | |
| assertAttrLabelList("//test/starlark:files", "srcs", ImmutableList.of()); | |
| } | |
| @Test | |
| public void subpackages_simple_empty_disallow() throws Exception { | |
| makeSubpackageFileGroup("test/starlark/BUILD", ALL_SUBDIRS, null, null); | |
| // force evaluation | |
| AssertionError e = | |
| assertThrows(AssertionError.class, () -> getConfiguredTarget("//test/starlark:files")); | |
| assertThat(e).hasMessageThat().contains("subpackages pattern '**' didn't match anything"); | |
| } | |
| @Test | |
| public void subpackages_deeplyNested_withSubdirs() throws Exception { | |
| makeSubpackageFileGroup("test/starlark/BUILD", ALL_SUBDIRS, null, true); | |
| // Setup a dir with 2 subdirs, 1 a package one not | |
| makeFilesSubPackage("test/starlark/sub"); | |
| // Should be blocked by 'sub' | |
| makeFilesSubPackage("test/starlark/sub/sub2"); | |
| makeFilesSubPackage("test/starlark/sub3"); | |
| makeFilesSubPackage("test/starlark/not_sub/sub_is_pkg/eventually"); | |
| scratch.file("test/starlark/not_sub/file1.txt"); | |
| scratch.file("test/starlark/not_sub/double_not_sub/file.txt"); | |
| assertAttrLabelList( | |
| "//test/starlark:files", | |
| "srcs", | |
| ImmutableList.of( | |
| "//test/starlark/sub:files", | |
| "//test/starlark/sub3:files", | |
| "//test/starlark/not_sub/sub_is_pkg/eventually:files")); | |
| } | |
| @Test | |
| public void subpackages_incremental_addSubPkg() throws Exception { | |
| makeSubpackageFileGroup("test/starlark/BUILD", ALL_SUBDIRS, null, null); | |
| // Setup a two subdirs one shallow and one deep | |
| makeFilesSubPackage("test/starlark/sub"); | |
| makeFilesSubPackage("test/starlark/deep/1/2/3"); | |
| assertAttrLabelList( | |
| "//test/starlark:files", | |
| "srcs", | |
| ImmutableList.of("//test/starlark/sub:files", "//test/starlark/deep/1/2/3:files")); | |
| // Add a 2nd shallow and 2nd deep mid | |
| makeFilesSubPackage("test/starlark/sub2"); | |
| // Poke Skyframe by invalidating the dirent and files that changed. | |
| invalidateSkyFrameFiles( | |
| "test/starlark/sub2", "test/starlark/sub2/BUILD", "test/starlark/sub2/file.txt"); | |
| // We should now be aware of the new one via Skyframe invalidation. | |
| assertAttrLabelList( | |
| "//test/starlark:files", | |
| "srcs", | |
| ImmutableList.of( | |
| "//test/starlark/sub:files", | |
| "//test/starlark/sub2:files", | |
| "//test/starlark/deep/1/2/3:files")); | |
| } | |
| @Test | |
| public void subpackages_incremental_delSubPkg() throws Exception { | |
| makeSubpackageFileGroup("test/starlark/BUILD", ALL_SUBDIRS, null, null); | |
| // Setup a single subdir | |
| makeFilesSubPackage("test/starlark/sub"); | |
| makeFilesSubPackage("test/starlark/sub2"); | |
| assertAttrLabelList( | |
| "//test/starlark:files", | |
| "srcs", | |
| ImmutableList.of("//test/starlark/sub:files", "//test/starlark/sub2:files")); | |
| scratch.deleteFile("test/starlark/sub2/BUILD"); | |
| scratch.deleteFile("test/starlark/sub2/file.txt"); | |
| invalidateSkyFrameFiles("test/starlark/sub2/BUILD", "test/starlark/sub2/file.txt"); | |
| // We should now be aware of the new one. | |
| assertAttrLabelList( | |
| "//test/starlark:files", "srcs", ImmutableList.of("//test/starlark/sub:files")); | |
| } | |
| @Test | |
| public void subpackages_incremental_convertSubDirToPkg() throws Exception { | |
| makeSubpackageFileGroup("test/starlark/BUILD", ALL_SUBDIRS, null, null); | |
| // Setup both immediate and deeply nested sub-dirs with BUILD files. | |
| makeFilesSubPackage("test/starlark/sub"); | |
| scratch.file("test/starlark/sub2/file2.txt"); | |
| // Initially we have a subdir with 'sub/BUILD' and sub2/file2.txt" | |
| assertAttrLabelList( | |
| "//test/starlark:files", "srcs", ImmutableList.of("//test/starlark/sub:files")); | |
| // Then we add a BUILD file to sub2 making it a package Skyframe should pick | |
| // that up once invalidated. | |
| makeFilesSubPackage("test/starlark/sub2"); | |
| // Poke Skyframe by invalidating the dirent and files that changed. | |
| invalidateSkyFrameFiles("test/starlark/sub2/BUILD", "test/starlark/sub2/file.txt"); | |
| assertAttrLabelList( | |
| "//test/starlark:files", | |
| "srcs", | |
| ImmutableList.of("//test/starlark/sub:files", "//test/starlark/sub2:files")); | |
| } | |
| @Test | |
| public void invalidPositionalParams() throws Exception { | |
| scratch.file("foo/subdir/BUILD"); | |
| scratch.file("foo/BUILD", "[sh_library(name = p) for p in subpackages(['subdir'])]"); | |
| AssertionError e = | |
| assertThrows(AssertionError.class, () -> getConfiguredTargetAndData("//foo:subdir")); | |
| assertThat(e).hasMessageThat().contains("got unexpected positional argument"); | |
| } | |
| @Test | |
| public void invalidMissingInclude() throws Exception { | |
| scratch.file("foo/subdir/BUILD"); | |
| scratch.file("foo/BUILD", "[sh_library(name = p) for p in subpackages()]"); | |
| AssertionError e = | |
| assertThrows(AssertionError.class, () -> getConfiguredTargetAndData("//foo:subdir")); | |
| assertThat(e).hasMessageThat().contains("missing 1 required named argument: include"); | |
| } | |
| @Test | |
| public void validNoWildCardInclude() throws Exception { | |
| makeSubpackageFileGroup( | |
| "test/starlark/BUILD", /*include=*/ ImmutableList.of("sub", "sub2/deep"), null, null); | |
| makeFilesSubPackage("test/starlark/sub"); | |
| makeFilesSubPackage("test/starlark/sub2/deep"); | |
| assertAttrLabelList( | |
| "//test/starlark:files", | |
| "srcs", | |
| ImmutableList.of("//test/starlark/sub:files", "//test/starlark/sub2/deep:files")); | |
| } | |
| @Test | |
| public void includeValidMatchSubdir() throws Exception { | |
| scratch.file("foo/subdir/BUILD"); | |
| scratch.file("foo/BUILD", "[sh_library(name = p) for p in subpackages(include = ['subdir'])]"); | |
| getConfiguredTargetAndData("//foo:subdir"); | |
| } | |
| @Test | |
| public void includeValidSubMatchSubdir() throws Exception { | |
| makeFilesSubPackage("test/starlark/subdir/sub/deeper"); | |
| makeFilesSubPackage("test/starlark/subdir/sub2/deeper"); | |
| makeFilesSubPackage("test/starlark/subdir/sub3/deeper"); | |
| makeSubpackageFileGroup("test/starlark/BUILD", "subdir/*/deeper", null, null); | |
| assertAttrLabelList( | |
| "//test/starlark:files", | |
| "srcs", | |
| ImmutableList.of( | |
| "//test/starlark/subdir/sub/deeper:files", | |
| "//test/starlark/subdir/sub2/deeper:files", | |
| "//test/starlark/subdir/sub3/deeper:files")); | |
| } | |
| /** | |
| * Constructs a BUILD file with a single filegroup target whose srcs attribute is the list of all | |
| * //p:files, where //p is a subpackage returned by a call to native.subpackages. | |
| */ | |
| private void makeSubpackageFileGroup( | |
| String buildPath, ImmutableList<String> include, String exclude, Boolean allowEmpty) | |
| throws IOException { | |
| StringBuilder subpackages = new StringBuilder(); | |
| subpackages.append("subpackages(include = ["); | |
| subpackages.append(include.stream().map(i -> "'" + i + "'").collect(Collectors.joining(", "))); | |
| subpackages.append("]"); | |
| if (exclude != null) { | |
| subpackages.append(", exclude = "); | |
| subpackages.append(exclude); | |
| } | |
| if (allowEmpty != null) { | |
| subpackages.append(", allow_empty = "); | |
| subpackages.append(allowEmpty ? "True" : "False"); | |
| } | |
| subpackages.append(")"); | |
| scratch.file( | |
| buildPath, | |
| "filegroup(", | |
| " name = 'files',", | |
| " srcs = [", | |
| " '//%s/%s:files' % (package_name(), s) for s in " + subpackages, | |
| " ],", | |
| ")"); | |
| } | |
| private void makeSubpackageFileGroup( | |
| String buildPath, String include, String exclude, Boolean allowEmpty) throws IOException { | |
| makeSubpackageFileGroup(buildPath, ImmutableList.of(include), exclude, allowEmpty); | |
| } | |
| /** | |
| * Creates a BUILD file and single file at the given packagePath, the BUILD file will contain a | |
| * single filegroup called 'files' which contains the created file. | |
| */ | |
| private void makeFilesSubPackage(String packagePath) throws IOException { | |
| scratch.file(packagePath + "/file.txt"); | |
| scratch.file( | |
| packagePath + "/BUILD", "filegroup(", " name = 'files',", " srcs = glob(['*']),", ")"); | |
| } | |
| private void assertAttrLabelList(String target, String attrName, List<String> expectedLabels) | |
| throws Exception { | |
| ConfiguredTargetAndData cfgTarget = getConfiguredTargetAndData(target); | |
| assertThat(cfgTarget).isNotNull(); | |
| ImmutableList<Label> labels = | |
| expectedLabels.stream().map(this::makeLabel).collect(toImmutableList()); | |
| ConfiguredAttributeMapper configuredAttributeMapper = | |
| getMapperFromConfiguredTargetAndTarget(cfgTarget); | |
| assertThat(configuredAttributeMapper.get(attrName, BuildType.LABEL_LIST)) | |
| .containsExactlyElementsIn(labels); | |
| } | |
| private Label makeLabel(String label) { | |
| try { | |
| return Label.parseCanonical(label); | |
| } catch (Exception e) { | |
| fail("Unable to construct Label from " + label); | |
| return null; | |
| } | |
| } | |
| private void invalidateSkyFrameFiles(String... files) throws Exception { | |
| ModifiedFileSet.Builder builder = ModifiedFileSet.builder(); | |
| for (String f : files) { | |
| builder.modify(PathFragment.create(f)); | |
| } | |
| getSkyframeExecutor() | |
| .invalidateFilesUnderPathForTesting( | |
| reporter, builder.build(), Root.fromPath(rootDirectory)); | |
| } | |
| } |