blob: 3bc1e7b12e783b887df9594423b3df8e52c49c53 [file] [log] [blame]
// Copyright 2023 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.rules.starlarkdocextract;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
import static org.junit.Assert.assertThrows;
import com.google.devtools.build.lib.actions.Action;
import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.actions.BinaryFileWriteAction;
import com.google.devtools.build.lib.analysis.actions.FileWriteAction;
import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
import com.google.devtools.build.lib.bazel.bzlmod.BzlmodTestUtil;
import com.google.devtools.build.lib.bazel.repository.starlark.StarlarkRepositoryModule;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.starlarkbuildapi.repository.RepositoryBootstrap;
import com.google.devtools.build.lib.testutil.TestRuleClassProvider;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.AspectInfo;
import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.AttributeInfo;
import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.AttributeType;
import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.FunctionParamInfo;
import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.ModuleExtensionInfo;
import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.ModuleExtensionTagClassInfo;
import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.ModuleInfo;
import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.OriginKey;
import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.ProviderInfo;
import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.ProviderNameGroup;
import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.RepositoryRuleInfo;
import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.RuleInfo;
import com.google.devtools.build.skydoc.rendering.proto.StardocOutputProtos.StarlarkFunctionInfo;
import com.google.protobuf.ExtensionRegistry;
import com.google.protobuf.TextFormat;
import java.util.NoSuchElementException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public final class StarlarkDocExtractTest extends BuildViewTestCase {
@Override
protected ConfiguredRuleClassProvider createRuleClassProvider() {
ConfiguredRuleClassProvider.Builder builder = new ConfiguredRuleClassProvider.Builder();
TestRuleClassProvider.addStandardRules(builder);
// Ensure repository_rule is supported.
builder.addStarlarkBootstrap(new RepositoryBootstrap(new StarlarkRepositoryModule()));
return builder.build();
}
private static ModuleInfo protoFromBinaryFileWriteAction(Action action) throws Exception {
assertThat(action).isInstanceOf(BinaryFileWriteAction.class);
return ModuleInfo.parseFrom(
((BinaryFileWriteAction) action).getSource().openStream(),
ExtensionRegistry.getEmptyRegistry());
}
private static ModuleInfo protoFromTextFileWriteAction(Action action) throws Exception {
assertThat(action).isInstanceOf(FileWriteAction.class);
return TextFormat.parse(
((FileWriteAction) action).getFileContents(),
ExtensionRegistry.getEmptyRegistry(),
ModuleInfo.class);
}
private ModuleInfo protoFromConfiguredTarget(String targetName) throws Exception {
ConfiguredTarget target = getConfiguredTarget(targetName);
Label targetLabel = Label.parseCanonicalUnchecked(targetName);
String outputName = targetLabel.toPathFragment().getPathString() + ".binaryproto";
if (!targetLabel.getRepository().isMain()) {
outputName =
String.format("external/%s/%s", targetLabel.getRepository().getName(), outputName);
}
return protoFromBinaryFileWriteAction(getGeneratingAction(target, outputName));
}
@Before
public void setUpBzlLibrary() throws Exception {
// TODO(https://github.com/bazelbuild/bazel/issues/18599): get rid of this when we bundle
// bzl_library with Bazel.
scratch.file(
"bzl_library.bzl",
"def _bzl_library_impl(ctx):",
" deps_files = [x.files for x in ctx.attr.deps]",
" all_files = depset(ctx.files.srcs, order = 'postorder', transitive = deps_files)",
" return DefaultInfo(files = all_files)",
"",
"bzl_library = rule(",
" implementation = _bzl_library_impl,",
" attrs = {",
" 'srcs': attr.label_list(allow_files = ['.bzl', '.scl']),",
" 'deps': attr.label_list(),",
" }",
")");
}
@Test
public void basicFunctionality() throws Exception {
scratch.file(
"foo.bzl", //
"'''Module doc string'''",
"True");
scratch.file(
"BUILD", //
"starlark_doc_extract(",
" name = 'extract',",
" src = 'foo.bzl',",
")");
ConfiguredTarget target = getConfiguredTarget("//:extract");
ModuleInfo moduleInfo =
protoFromBinaryFileWriteAction(getGeneratingAction(target, "extract.binaryproto"));
assertThat(moduleInfo.getModuleDocstring()).isEqualTo("Module doc string");
assertThat(moduleInfo.getFile()).isEqualTo("//:foo.bzl");
}
@Test
public void textprotoOut() throws Exception {
scratch.file(
"foo.bzl", //
"'''Module doc string'''",
"True");
scratch.file(
"BUILD", //
"starlark_doc_extract(",
" name = 'extract',",
" src = 'foo.bzl',",
")");
ConfiguredTarget ruleTarget = getConfiguredTarget("//:extract");
// Verify that we do not generate textproto output unless explicitly requested.
assertThrows(
NoSuchElementException.class, () -> getGeneratingAction(ruleTarget, "extract.textproto"));
ConfiguredTarget textprotoOutputTarget = getConfiguredTarget("//:extract.textproto");
ModuleInfo moduleInfo =
protoFromTextFileWriteAction(
getGeneratingAction(textprotoOutputTarget, "extract.textproto"));
assertThat(moduleInfo.getModuleDocstring()).isEqualTo("Module doc string");
}
@Test
public void sclDialect() throws Exception {
setBuildLanguageOptions("--experimental_enable_scl_dialect");
scratch.file(
"foo.scl", //
"def f():",
" '''This is my function'''",
" pass");
scratch.file(
"bar.scl", //
"'''My scl module string'''",
"load('//:foo.scl', 'f')",
"bar_f = f");
scratch.file(
"BUILD", //
"load('bzl_library.bzl', 'bzl_library')",
"bzl_library(",
" name = 'foo_scl',",
" srcs = ['foo.scl'],",
")",
"starlark_doc_extract(",
" name = 'extract',",
" src = 'bar.scl',",
" deps = ['foo_scl'],",
")");
ModuleInfo moduleInfo = protoFromConfiguredTarget("//:extract");
assertThat(moduleInfo.getModuleDocstring()).isEqualTo("My scl module string");
assertThat(moduleInfo.getFile()).isEqualTo("//:bar.scl");
assertThat(moduleInfo.getFuncInfo(0).getDocString()).isEqualTo("This is my function");
}
@Test
public void sourceWithSyntaxError_fails() throws Exception {
scratch.file(
"error.bzl", //
"!!!");
scratch.file(
"error_loader.bzl", //
"'''This is my module'''",
"load('error.bzl', 'x')");
scratch.file(
"BUILD", //
"load('bzl_library.bzl', 'bzl_library')",
"bzl_library(",
" name = 'error_bzl',",
" srcs = ['error.bzl'],",
")",
"starlark_doc_extract(",
" name = 'error_doc',",
" src = 'error.bzl',",
")",
"starlark_doc_extract(",
" name = 'error_loader_doc',",
" src = 'error_loader.bzl',",
" deps = ['error_bzl'],",
")");
AssertionError errorDocFailure =
assertThrows(AssertionError.class, () -> getConfiguredTarget("//:error_doc"));
assertThat(errorDocFailure).hasMessageThat().contains("invalid character: '!'");
AssertionError errorLoaderDocFailure =
assertThrows(AssertionError.class, () -> getConfiguredTarget("//:error_loader_doc"));
assertThat(errorLoaderDocFailure).hasMessageThat().contains("invalid character: '!'");
}
@Test
public void symbolNames() throws Exception {
scratch.file(
"foo.bzl", //
"def func1():",
" pass",
"def func2():",
" pass",
"def _hidden():",
" pass");
scratch.file(
"BUILD", //
"starlark_doc_extract(",
" name = 'extract_some',",
" src = 'foo.bzl',",
" symbol_names = ['func1'],",
")",
"starlark_doc_extract(",
" name = 'extract_all',",
" src = 'foo.bzl',",
")");
ModuleInfo dumpSome = protoFromConfiguredTarget("//:extract_some");
assertThat(dumpSome.getFuncInfoList().stream().map(StarlarkFunctionInfo::getFunctionName))
.containsExactly("func1");
ModuleInfo dumpAll = protoFromConfiguredTarget("//:extract_all");
assertThat(dumpAll.getFuncInfoList().stream().map(StarlarkFunctionInfo::getFunctionName))
.containsExactly("func1", "func2");
}
@Test
public void originKey() throws Exception {
scratch.file(
"origin.bzl", //
"def my_macro():",
" pass",
"MyInfo = provider()",
"MyOtherInfo = provider()",
"my_rule = rule(",
" implementation = lambda ctx: None,",
" attrs = {'a': attr.label(providers = [MyInfo, MyOtherInfo])},",
" provides = [MyInfo, MyOtherInfo],",
")",
"my_aspect = aspect(implementation = lambda target, ctx: None)");
scratch.file(
"renamer.bzl", //
"load(':origin.bzl', 'my_macro', 'MyInfo', 'MyOtherInfo', 'my_rule', 'my_aspect')",
"namespace = struct(",
" renamed_macro = my_macro,",
" RenamedInfo = MyInfo,",
" renamed_rule = my_rule,",
" renamed_aspect = my_aspect,",
")",
"other_namespace = struct(",
" RenamedOtherInfo = MyOtherInfo,",
")");
scratch.file(
"BUILD", //
"load('bzl_library.bzl', 'bzl_library')",
"bzl_library(",
" name = 'origin_bzl',",
" srcs = ['origin.bzl'],",
")",
"starlark_doc_extract(",
" name = 'extract_renamed',",
" src = 'renamer.bzl',",
" deps = ['origin_bzl'],",
" symbol_names = ['namespace'],",
")");
ModuleInfo moduleInfo = protoFromConfiguredTarget("//:extract_renamed");
assertThat(moduleInfo.getFuncInfoList().stream().map(StarlarkFunctionInfo::getFunctionName))
.containsExactly("namespace.renamed_macro");
assertThat(moduleInfo.getFuncInfoList().stream().map(StarlarkFunctionInfo::getOriginKey))
.containsExactly(
OriginKey.newBuilder().setName("my_macro").setFile("//:origin.bzl").build());
assertThat(moduleInfo.getProviderInfoList().stream().map(ProviderInfo::getProviderName))
.containsExactly("namespace.RenamedInfo");
assertThat(moduleInfo.getProviderInfoList().stream().map(ProviderInfo::getOriginKey))
.containsExactly(OriginKey.newBuilder().setName("MyInfo").setFile("//:origin.bzl").build());
assertThat(moduleInfo.getRuleInfoList().stream().map(RuleInfo::getRuleName))
.containsExactly("namespace.renamed_rule");
assertThat(moduleInfo.getRuleInfoList().stream().map(RuleInfo::getOriginKey))
.containsExactly(
OriginKey.newBuilder().setName("my_rule").setFile("//:origin.bzl").build());
assertThat(moduleInfo.getRuleInfo(0).getAttributeList())
.containsExactly(
ModuleInfoExtractor.IMPLICIT_NAME_ATTRIBUTE_INFO,
AttributeInfo.newBuilder()
.setName("a")
.setType(AttributeType.LABEL)
.setDefaultValue("None")
.addProviderNameGroup(
ProviderNameGroup.newBuilder()
.addProviderName("namespace.RenamedInfo")
.addProviderName("other_namespace.RenamedOtherInfo")
.addOriginKey(
OriginKey.newBuilder().setName("MyInfo").setFile("//:origin.bzl"))
.addOriginKey(
OriginKey.newBuilder().setName("MyOtherInfo").setFile("//:origin.bzl")))
.build());
assertThat(moduleInfo.getRuleInfo(0).getAdvertisedProviders())
.isEqualTo(
ProviderNameGroup.newBuilder()
.addProviderName("namespace.RenamedInfo")
.addProviderName("other_namespace.RenamedOtherInfo")
.addOriginKey(OriginKey.newBuilder().setName("MyInfo").setFile("//:origin.bzl"))
.addOriginKey(
OriginKey.newBuilder().setName("MyOtherInfo").setFile("//:origin.bzl"))
.build());
assertThat(moduleInfo.getAspectInfoList().stream().map(AspectInfo::getAspectName))
.containsExactly("namespace.renamed_aspect");
assertThat(moduleInfo.getAspectInfoList().stream().map(AspectInfo::getOriginKey))
.containsExactly(
OriginKey.newBuilder().setName("my_aspect").setFile("//:origin.bzl").build());
}
private static AttributeInfo getFirstRuleFirstAttr(ModuleInfo moduleInfo) {
// Attribute 0 is the implicit `name` attribute
return moduleInfo.getRuleInfo(0).getAttribute(1);
}
@Test
public void originKeyFileAndModuleInfoFileLabels_forBzlFileInBzlmodModule_areDisplayForm()
throws Exception {
scratch.overwriteFile("MODULE.bazel", "bazel_dep(name='origin_repo', version='0.1')");
registry.addModule(
BzlmodTestUtil.createModuleKey("origin_repo", "0.1"),
"module(name='origin_repo', version='0.1')");
Path originRepoPath = moduleRoot.getRelative("origin_repo~0.1");
scratch.file(originRepoPath.getRelative("WORKSPACE").getPathString());
scratch.file(
originRepoPath.getRelative("BUILD").getPathString(), //
"exports_files(['origin.bzl'])");
scratch.file(
originRepoPath.getRelative("origin.bzl").getPathString(), //
"def my_macro():",
" pass",
"MyInfo = provider()",
"my_rule = rule(",
" implementation = lambda ctx: None,",
" attrs = {'a': attr.label(providers = [MyInfo])},",
" provides = [MyInfo],",
")",
"my_aspect = aspect(implementation = lambda target, ctx: None)");
scratch.file(
"renamer.bzl", //
"load('@origin_repo//:origin.bzl', 'my_macro', 'MyInfo', 'my_rule', 'my_aspect')",
"namespace = struct(",
" renamed_macro = my_macro,",
" RenamedInfo = MyInfo,",
" renamed_rule = my_rule,",
" renamed_aspect = my_aspect,",
")");
scratch.file(
"BUILD", //
"load('bzl_library.bzl', 'bzl_library')",
"bzl_library(",
" name = 'origin_bzl',",
" srcs = ['@origin_repo//:origin.bzl'],",
")",
"starlark_doc_extract(",
" name = 'extract_origin',",
" src = '@origin_repo//:origin.bzl',",
")",
"starlark_doc_extract(",
" name = 'extract_renamed',",
" src = 'renamer.bzl',",
" deps = ['origin_bzl'],",
")");
invalidatePackages();
// verify that ModuleInfo.name for a .bzl file in another bzlmod module is in display form, i.e.
// "@origin_repo//:origin.bzl" as opposed to "@@origin_repo~0.1//:origin.bzl"
ModuleInfo originModuleInfo = protoFromConfiguredTarget("//:extract_origin");
assertThat(originModuleInfo.getFile()).isEqualTo("@origin_repo//:origin.bzl");
// verify that OriginKey.name for entities defined in a .bzl file in another bzlmod module is in
// display form, i.e. "@origin_repo//:origin.bzl" as opposed to "@@origin_repo~0.1//:origin.bzl"
ModuleInfo renamedModuleInfo = protoFromConfiguredTarget("//:extract_renamed");
assertThat(renamedModuleInfo.getFile()).isEqualTo("//:renamer.bzl");
assertThat(renamedModuleInfo.getFuncInfo(0).getOriginKey().getFile())
.isEqualTo("@origin_repo//:origin.bzl");
assertThat(renamedModuleInfo.getProviderInfo(0).getOriginKey().getFile())
.isEqualTo("@origin_repo//:origin.bzl");
assertThat(renamedModuleInfo.getAspectInfo(0).getOriginKey().getFile())
.isEqualTo("@origin_repo//:origin.bzl");
assertThat(renamedModuleInfo.getRuleInfo(0).getOriginKey().getFile())
.isEqualTo("@origin_repo//:origin.bzl");
assertThat(
getFirstRuleFirstAttr(renamedModuleInfo)
.getProviderNameGroup(0)
.getOriginKey(0)
.getFile())
.isEqualTo("@origin_repo//:origin.bzl");
assertThat(renamedModuleInfo.getRuleInfo(0).getAdvertisedProviders().getOriginKey(0).getFile())
.isEqualTo("@origin_repo//:origin.bzl");
}
@Test
public void exportNestedFunctionsAndLambdas() throws Exception {
scratch.file(
"origin.bzl", //
"def return_nested():",
" def nested(x):",
" '''My nested function'''",
" pass",
" return nested",
"",
"def return_lambda():",
" return lambda y: y");
scratch.file(
"exporter.bzl", //
"load(':origin.bzl', 'return_nested', 'return_lambda')",
"exported_nested = return_nested()",
"exported_lambda = return_lambda()");
scratch.file(
"BUILD", //
"load('bzl_library.bzl', 'bzl_library')",
"bzl_library(",
" name = 'origin_bzl',",
" srcs = ['origin.bzl'],",
")",
"starlark_doc_extract(",
" name = 'extract_exporter',",
" src = 'exporter.bzl',",
" deps = ['origin_bzl'],",
")");
ModuleInfo moduleInfo = protoFromConfiguredTarget("//:extract_exporter");
assertThat(moduleInfo.getFuncInfoList())
.containsExactly(
StarlarkFunctionInfo.newBuilder()
.setFunctionName("exported_nested")
.setDocString("My nested function")
.addParameter(FunctionParamInfo.newBuilder().setName("x").setMandatory(true))
.setOriginKey(
// OriginKey.name for nested functions is explicitly unset
OriginKey.newBuilder().setFile("//:origin.bzl"))
.build(),
StarlarkFunctionInfo.newBuilder()
.setFunctionName("exported_lambda")
.addParameter(FunctionParamInfo.newBuilder().setName("y").setMandatory(true))
.setOriginKey(
// OriginKey.name for lambdas is explicitly unset
OriginKey.newBuilder().setFile("//:origin.bzl"))
.build());
}
@Test
public void missingBzlLibraryDeps_fails() throws Exception {
scratch.file(
"dep.bzl", //
"load('//:forgotten_dep_of_dep.bzl', 'g')",
"def f(): pass");
scratch.file(
"forgotten_dep_of_dep.bzl", //
"def g(): pass");
scratch.file(
"forgotten_dep.bzl", //
"load('//:forgotten_dep_of_forgotten_dep.bzl', 'j')",
"def h(): pass");
scratch.file(
"forgotten_dep2.bzl", //
"def i(): pass");
scratch.file(
"forgotten_dep_of_forgotten_dep.bzl", //
"def j(): pass");
scratch.file(
"foo.bzl", //
"load('//:dep.bzl', 'f')",
"load('//:forgotten_dep.bzl', 'h')",
"load('//:forgotten_dep2.bzl', 'i')");
scratch.file(
"BUILD", //
"load('bzl_library.bzl', 'bzl_library')",
"bzl_library(",
" name = 'dep_bzl',",
" srcs = ['dep.bzl'],",
")",
"starlark_doc_extract(",
" name = 'extract',",
" src = 'foo.bzl',", // Note that src does not need to be part of deps
" deps = ['dep_bzl']",
")");
AssertionError e = assertThrows(AssertionError.class, () -> getConfiguredTarget("//:extract"));
assertThat(e)
.hasMessageThat()
.contains(
"missing bzl_library targets for Starlark module(s) //:forgotten_dep_of_dep.bzl,"
+ " //:forgotten_dep.bzl, //:forgotten_dep2.bzl");
// We do not want to log transitive deps of already missing deps in the error message - it would
// be hard to read and unnecessary, since a valid bzl_library target should bring in its
// transitive deps.
assertThat(e).hasMessageThat().doesNotContain("forgotten_dep_of_forgotten_dep.bzl");
}
@Test
public void depsWithDerivedFiles_onUnknownLoads_failsAndPrintsDerivedFiles() throws Exception {
scratch.file("BUILD");
scratch.file(
"pkg/source_file_masked_by_rule_name.bzl", //
"def f(): pass");
scratch.file(
"pkg/source_file_masked_by_rule_output_name.bzl", //
"def g(): pass");
scratch.file(
"pkg/foo.bzl", //
"load('//pkg:source_file_masked_by_rule_name.bzl', 'f')",
"load('//pkg:source_file_masked_by_rule_output_name.bzl', 'g')");
scratch.file(
"pkg/BUILD", //
"load('//:bzl_library.bzl', 'bzl_library')",
"genrule(",
" name = 'source_file_masked_by_rule_name.bzl',",
" outs = ['some_output.bzl'],",
" cmd = 'touch $@'",
")",
"genrule(",
" name = 'source_file_masked_by_rule_output_name_bzl_generator',",
" outs = ['source_file_masked_by_rule_output_name.bzl'],",
" cmd = 'touch $@'",
")",
"genrule(",
" name = 'some_rule',",
" outs = ['ordinary_generated_file.bzl'],",
" cmd = 'touch $@'",
")",
"bzl_library(",
" name = 'deps_bzl',",
" srcs = [",
" 'source_file_masked_by_rule_name.bzl',",
" 'source_file_masked_by_rule_output_name.bzl',",
" 'ordinary_generated_file.bzl',",
" ],",
")",
"starlark_doc_extract(",
" name = 'extract',",
" src = 'foo.bzl',",
" deps = ['deps_bzl']",
")");
AssertionError error =
assertThrows(AssertionError.class, () -> getConfiguredTarget("//pkg:extract"));
assertThat(error)
.hasMessageThat()
.contains(
"missing bzl_library targets for Starlark module(s)"
+ " //pkg:source_file_masked_by_rule_name.bzl,"
+ " //pkg:source_file_masked_by_rule_output_name.bzl\n"
+ "Note the following are generated file(s) and cannot be loaded in Starlark:"
+ " pkg/some_output.bzl (generated by rule"
+ " //pkg:source_file_masked_by_rule_name.bzl),"
+ " pkg/source_file_masked_by_rule_output_name.bzl (generated by rule"
+ " //pkg:source_file_masked_by_rule_output_name_bzl_generator),"
+ " pkg/ordinary_generated_file.bzl (generated by rule //pkg:some_rule)");
}
@Test
public void depsWithDerivedFiles_onNoUnknownLoads_succeeds() throws Exception {
scratch.file("BUILD");
scratch.file(
"util.bzl",
"def _impl(ctx):",
" out = ctx.actions.declare_file(ctx.attr.out)",
" ctx.actions.run_shell(command = 'touch $1', arguments = [out.path], outputs = [out])",
" return DefaultInfo(files = depset([out]), runfiles = ctx.runfiles([out]))",
"generate_out_without_declaring_it_as_a_target = rule(",
" attrs = {'out': attr.string()},",
" implementation = _impl,",
")");
scratch.file(
"pkg/source_dep.bzl", //
"def f(): pass");
scratch.file(
"pkg/foo.bzl", //
"load('//pkg:source_dep.bzl', 'f')");
scratch.file(
"pkg/BUILD", //
"load('//:bzl_library.bzl', 'bzl_library')",
"load('//:util.bzl', 'generate_out_without_declaring_it_as_a_target')",
"genrule(",
" name = 'some_rule',",
" outs = ['declared_derived_dep.bzl'],",
" cmd = 'touch $@'",
")",
// //pkg:generate_source_dep_without_declaring_it_as_a_target masks the source_dep.bzl
// source artifact with a non-target, derived artifact having the same root-relative path.
"generate_out_without_declaring_it_as_a_target(",
" name = 'generate_source_dep_without_declaring_it_as_a_target',",
" out = 'source_dep.bzl'",
")",
"bzl_library(",
" name = 'deps_bzl',",
" srcs = [",
" 'declared_derived_dep.bzl',",
" 'source_dep.bzl',",
" 'generate_source_dep_without_declaring_it_as_a_target',",
" ],",
")",
"starlark_doc_extract(",
" name = 'extract',",
" src = 'foo.bzl',",
" deps = ['deps_bzl']",
")");
getConfiguredTarget("//pkg:extract");
assertNoEvents();
}
@Test
public void srcDerivedFile_fails() throws Exception {
scratch.file("BUILD");
scratch.file("pkg/source_file_masked_by_rule_name.bzl");
scratch.file("pkg/source_file_masked_by_rule_output_name.bzl");
scratch.file(
"pkg/BUILD", //
"genrule(",
" name = 'source_file_masked_by_rule_name.bzl',",
" outs = ['some_output.bzl'],",
" cmd = 'touch $@'",
")",
"genrule(",
" name = 'source_file_masked_by_rule_output_name_bzl_generator',",
" outs = ['source_file_masked_by_rule_output_name.bzl'],",
" cmd = 'touch $@'",
")",
"genrule(",
" name = 'some_rule',",
" outs = ['ordinary_generated_file.bzl'],",
" cmd = 'touch $@'",
")",
"starlark_doc_extract(",
" name = 'source_file_masked_by_rule_name_doc',",
" src = 'source_file_masked_by_rule_name.bzl',",
")",
"starlark_doc_extract(",
" name = 'source_file_masked_by_rule_output_name_doc',",
" src = 'source_file_masked_by_rule_output_name.bzl',",
")",
"starlark_doc_extract(",
" name = 'ordinary_generated_file_doc',",
" src = 'ordinary_generated_file.bzl',",
")");
AssertionError maskedByRuleError =
assertThrows(
AssertionError.class,
() -> getConfiguredTarget("//pkg:source_file_masked_by_rule_name_doc"));
assertThat(maskedByRuleError)
.hasMessageThat()
.contains(
"pkg/some_output.bzl (generated by rule //pkg:source_file_masked_by_rule_name.bzl)"
+ " is not a source file and cannot be loaded in Starlark");
AssertionError maskedByRuleOutputError =
assertThrows(
AssertionError.class,
() -> getConfiguredTarget("//pkg:source_file_masked_by_rule_output_name_doc"));
assertThat(maskedByRuleOutputError)
.hasMessageThat()
.contains(
"pkg/source_file_masked_by_rule_output_name.bzl (generated by rule"
+ " //pkg:source_file_masked_by_rule_output_name_bzl_generator) is not a source"
+ " file and cannot be loaded in Starlark");
AssertionError ordinaryGeneratedFileError =
assertThrows(
AssertionError.class, () -> getConfiguredTarget("//pkg:ordinary_generated_file_doc"));
assertThat(ordinaryGeneratedFileError)
.hasMessageThat()
.contains(
"pkg/ordinary_generated_file.bzl (generated by rule //pkg:some_rule) is not a source"
+ " file and cannot be loaded in Starlark");
}
@Test
public void srcAlias_resolvesToActual() throws Exception {
scratch.file("alias_name.bzl");
scratch.file("alias_actual.bzl");
scratch.file(
"BUILD", //
"alias(",
" name = 'alias_name.bzl',",
" actual = 'alias_actual.bzl',",
")",
"starlark_doc_extract(",
" name = 'extract',",
" src = 'alias_name.bzl',",
")");
ModuleInfo moduleInfo = protoFromConfiguredTarget("//:extract");
assertThat(moduleInfo.getFile()).isEqualTo("//:alias_actual.bzl");
}
@Test
public void srcFilegroup_resolvesToFilegroupSrc() throws Exception {
scratch.file("masked_by_filegroup_name.bzl");
scratch.file("filegroup_src_actual.bzl");
scratch.file(
"BUILD", //
"filegroup(",
" name = 'masked_by_filegroup_name.bzl',",
" srcs = ['filegroup_src_actual.bzl'],",
")",
"starlark_doc_extract(",
" name = 'extract',",
" src = 'masked_by_filegroup_name.bzl',",
")");
ModuleInfo moduleInfo = protoFromConfiguredTarget("//:extract");
assertThat(moduleInfo.getFile()).isEqualTo("//:filegroup_src_actual.bzl");
}
@Test
public void srcFilegroup_mustHaveSingleSrc() throws Exception {
scratch.file("foo.bzl");
scratch.file("bar.bzl");
scratch.file(
"BUILD", //
"filegroup(",
" name = 'no_files',",
" srcs = [],",
")",
"filegroup(",
" name = 'two_files',",
" srcs = ['foo.bzl', 'bar.bzl'],",
")",
"starlark_doc_extract(",
" name = 'no_files_doc',",
" src = 'no_files',",
")",
"starlark_doc_extract(",
" name = 'two_files_doc',",
" src = 'two_files',",
")");
AssertionError extractNoFilesError =
assertThrows(AssertionError.class, () -> getConfiguredTarget("//:no_files_doc"));
assertThat(extractNoFilesError)
.hasMessageThat()
.contains("'//:no_files' must produce a single file");
AssertionError extractTwoFilesError =
assertThrows(AssertionError.class, () -> getConfiguredTarget("//:two_files_doc"));
assertThat(extractTwoFilesError)
.hasMessageThat()
.contains("'//:two_files' must produce a single file");
}
@Test
public void repositoryRule() throws Exception {
scratch.file(
"dep.bzl",
"def _impl(repository_ctx):",
" pass",
"",
"my_repo_rule = repository_rule(",
" implementation = _impl,",
" doc = '''My repository rule",
"",
" With details",
" ''',",
" attrs = {",
" 'a': attr.string(doc = 'My doc', default = 'foo'),",
" 'b': attr.string(mandatory = True),",
" '_c': attr.string(doc = 'Hidden attribute'),",
" },",
" environ = ['FOO_PATH', 'BAR_COMPILER'],",
")");
scratch.file(
"foo.bzl", //
"load('//:dep.bzl', 'my_repo_rule')",
"foo = struct(repo_rule = my_repo_rule)");
scratch.file(
"BUILD", //
"load('bzl_library.bzl', 'bzl_library')",
"bzl_library(",
" name = 'dep_bzl',",
" srcs = ['dep.bzl'],",
")",
"starlark_doc_extract(",
" name = 'extract',",
" src = 'foo.bzl',",
" deps = ['dep_bzl'],",
")");
ModuleInfo moduleInfo = protoFromConfiguredTarget("//:extract");
assertThat(moduleInfo.getRepositoryRuleInfoList())
.containsExactly(
RepositoryRuleInfo.newBuilder()
.setRuleName("foo.repo_rule")
.setOriginKey(
OriginKey.newBuilder().setName("my_repo_rule").setFile("//:dep.bzl").build())
.setDocString("My repository rule\n\nWith details")
.addAllAttribute(ModuleInfoExtractor.IMPLICIT_REPOSITORY_RULE_ATTRIBUTES)
.addAttribute(
AttributeInfo.newBuilder()
.setName("a")
.setType(AttributeType.STRING)
.setDocString("My doc")
.setDefaultValue("\"foo\""))
.addAttribute(
AttributeInfo.newBuilder()
.setName("b")
.setType(AttributeType.STRING)
.setMandatory(true))
.addEnviron("FOO_PATH")
.addEnviron("BAR_COMPILER")
.build());
}
@Test
public void moduleExtension() throws Exception {
scratch.file(
"dep.bzl",
"_install = tag_class(",
" doc = '''Install",
" ",
" With details''',",
" attrs = {",
" 'artifacts': attr.string_list(doc = 'Artifacts'),",
" '_hidden': attr.bool(),",
" },",
")",
"",
"_artifact = tag_class(",
" attrs = {",
" 'group': attr.string(),",
" 'artifact': attr.string(default = 'foo'),",
" },",
")",
"",
"def _impl(ctx):",
" pass",
"",
"my_ext = module_extension(",
" doc = '''My extension",
"",
" With details''',",
" tag_classes = {",
" 'install': _install,",
" 'artifact': _artifact,",
" },",
" implementation = _impl,",
")");
scratch.file(
"foo.bzl", //
"load('//:dep.bzl', 'my_ext')",
"foo = struct(ext = my_ext)");
scratch.file(
"BUILD", //
"load('bzl_library.bzl', 'bzl_library')",
"bzl_library(",
" name = 'dep_bzl',",
" srcs = ['dep.bzl'],",
")",
"starlark_doc_extract(",
" name = 'extract',",
" src = 'foo.bzl',",
" deps = ['dep_bzl'],",
")");
ModuleInfo moduleInfo = protoFromConfiguredTarget("//:extract");
assertThat(moduleInfo.getFile()).isEqualTo("//:foo.bzl");
assertThat(moduleInfo.getModuleExtensionInfoList())
.containsExactly(
ModuleExtensionInfo.newBuilder()
.setExtensionName("foo.ext")
.setDocString("My extension\n\nWith details")
.setOriginKey(OriginKey.newBuilder().setFile("//:dep.bzl").build())
.addTagClass(
ModuleExtensionTagClassInfo.newBuilder()
.setTagName("install")
.setDocString("Install\n\nWith details")
.addAttribute(
AttributeInfo.newBuilder()
.setName("artifacts")
.setType(AttributeType.STRING_LIST)
.setDocString("Artifacts")
.setDefaultValue("[]"))
.build())
.addTagClass(
ModuleExtensionTagClassInfo.newBuilder()
.setTagName("artifact")
.addAttribute(
AttributeInfo.newBuilder()
.setName("group")
.setType(AttributeType.STRING)
.setDefaultValue("\"\""))
.addAttribute(
AttributeInfo.newBuilder()
.setName("artifact")
.setType(AttributeType.STRING)
.setDefaultValue("\"foo\""))
.build())
.build());
}
@Test
public void repoName_inMainBzlmodModule() throws Exception {
scratch.overwriteFile(
"MODULE.bazel", //
"module(name = 'my_module', repo_name = 'legacy_internal_repo_name')");
scratch.file(
"foo.bzl", //
"def my_macro(arg = Label('//target:target')): pass",
"",
"my_rule = rule(",
" implementation = lambda ctx: None,",
" attrs = {'a': attr.label(default = '//target:target')},",
")");
scratch.file(
"BUILD", //
"starlark_doc_extract(",
" name = 'with_main_repo_name',",
" src = 'foo.bzl',",
" render_main_repo_name = True,",
")",
"",
// render_main_repo_name is false by default
"starlark_doc_extract(",
" name = 'without_main_repo_name',",
" src = 'foo.bzl',",
")");
invalidatePackages();
ModuleInfo withMainRepoName = protoFromConfiguredTarget("//:with_main_repo_name");
assertThat(withMainRepoName.getFile()).isEqualTo("@my_module//:foo.bzl");
assertThat(withMainRepoName.getRuleInfo(0).getOriginKey().getFile())
.isEqualTo("@my_module//:foo.bzl");
assertThat(withMainRepoName.getFuncInfo(0).getParameter(0).getDefaultValue())
.isEqualTo("Label(\"@my_module//target:target\")");
assertThat(getFirstRuleFirstAttr(withMainRepoName).getDefaultValue())
.isEqualTo("\"@my_module//target\"");
ModuleInfo withoutMainRepoName = protoFromConfiguredTarget("//:without_main_repo_name");
assertThat(withoutMainRepoName.getFile()).isEqualTo("//:foo.bzl");
assertThat(withoutMainRepoName.getFuncInfo(0).getParameter(0).getDefaultValue())
.isEqualTo("Label(\"//target:target\")");
assertThat(withoutMainRepoName.getRuleInfo(0).getOriginKey().getFile()).isEqualTo("//:foo.bzl");
assertThat(getFirstRuleFirstAttr(withoutMainRepoName).getDefaultValue())
.isEqualTo("\"//target\"");
}
@Test
public void repoName_inMainWorkspaceRepo() throws Exception {
setBuildLanguageOptions("--noenable_bzlmod");
rewriteWorkspace("workspace(name = 'my_repo')");
scratch.file(
"foo.bzl", //
"def my_macro(arg = Label('//target:target')): pass",
"",
"my_rule = rule(",
" implementation = lambda ctx: None,",
" attrs = {'a': attr.label(default = '//target:target')},",
")");
scratch.file(
"BUILD", //
"starlark_doc_extract(",
" name = 'with_main_repo_name',",
" src = 'foo.bzl',",
" render_main_repo_name = True,",
")",
"",
// render_main_repo_name is false by default
"starlark_doc_extract(",
" name = 'without_main_repo_name',",
" src = 'foo.bzl',",
")");
ModuleInfo withMainRepoName = protoFromConfiguredTarget("//:with_main_repo_name");
assertThat(withMainRepoName.getFile()).isEqualTo("@my_repo//:foo.bzl");
assertThat(withMainRepoName.getFuncInfo(0).getParameter(0).getDefaultValue())
.isEqualTo("Label(\"@my_repo//target:target\")");
assertThat(withMainRepoName.getRuleInfo(0).getOriginKey().getFile())
.isEqualTo("@my_repo//:foo.bzl");
assertThat(getFirstRuleFirstAttr(withMainRepoName).getDefaultValue())
.isEqualTo("\"@my_repo//target\"");
ModuleInfo withoutMainRepoName = protoFromConfiguredTarget("//:without_main_repo_name");
assertThat(withoutMainRepoName.getFile()).isEqualTo("//:foo.bzl");
assertThat(withoutMainRepoName.getFuncInfo(0).getParameter(0).getDefaultValue())
.isEqualTo("Label(\"//target:target\")");
assertThat(withoutMainRepoName.getRuleInfo(0).getOriginKey().getFile()).isEqualTo("//:foo.bzl");
assertThat(getFirstRuleFirstAttr(withoutMainRepoName).getDefaultValue())
.isEqualTo("\"//target\"");
}
@Test
public void repoName_inBzlmodDep() throws Exception {
scratch.overwriteFile(
"MODULE.bazel", "module(name = 'my_module')", "bazel_dep(name='dep_mod', version='0.1')");
registry.addModule(
BzlmodTestUtil.createModuleKey("dep_mod", "0.1"), "module(name='dep_mod', version='0.1')");
Path depModRepoPath = moduleRoot.getRelative("dep_mod~0.1");
scratch.file(depModRepoPath.getRelative("WORKSPACE").getPathString());
scratch.file(
depModRepoPath.getRelative("foo.bzl").getPathString(), //
"def my_macro(arg = Label('//target:target')): pass",
"",
"my_rule = rule(",
" implementation = lambda ctx: None,",
" attrs = {'a': attr.label(default = '//target:target')},",
")");
scratch.file(
depModRepoPath.getRelative("BUILD").getPathString(), //
"starlark_doc_extract(",
" name = 'extract',",
" src = 'foo.bzl',",
")");
invalidatePackages();
ModuleInfo moduleInfo = protoFromConfiguredTarget("@dep_mod~0.1//:extract");
assertThat(moduleInfo.getFile()).isEqualTo("@dep_mod//:foo.bzl");
assertThat(moduleInfo.getFuncInfo(0).getParameter(0).getDefaultValue())
.isEqualTo("Label(\"@dep_mod//target:target\")");
assertThat(moduleInfo.getRuleInfo(0).getOriginKey().getFile()).isEqualTo("@dep_mod//:foo.bzl");
assertThat(getFirstRuleFirstAttr(moduleInfo).getDefaultValue())
.isEqualTo("\"@dep_mod//target\"");
}
@Test
public void repoName_inWorkspaceDep() throws Exception {
rewriteWorkspace("local_repository(name = 'dep', path = 'dep_path')");
scratch.file("dep_path/WORKSPACE", "workspace(name = 'dep')");
scratch.file(
"dep_path/foo.bzl", //
"def my_macro(arg = Label('//target:target')): pass",
"",
"my_rule = rule(",
" implementation = lambda ctx: None,",
" attrs = {'a': attr.label(default = '//target:target')},",
")");
scratch.file(
"dep_path/BUILD", //
"starlark_doc_extract(",
" name = 'extract',",
" src = 'foo.bzl',",
")");
ModuleInfo moduleInfo = protoFromConfiguredTarget("@dep//:extract");
assertThat(moduleInfo.getFile()).isEqualTo("@dep//:foo.bzl");
assertThat(moduleInfo.getFuncInfo(0).getParameter(0).getDefaultValue())
.isEqualTo("Label(\"@dep//target:target\")");
assertThat(moduleInfo.getRuleInfo(0).getOriginKey().getFile()).isEqualTo("@dep//:foo.bzl");
assertThat(getFirstRuleFirstAttr(moduleInfo).getDefaultValue()).isEqualTo("\"@dep//target\"");
}
}