| // 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.common.collect.ImmutableMap; |
| 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.parseAbsolute(label, ImmutableMap.of()); |
| } 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)); |
| } |
| } |