blob: 4bca017252b76ff75362929c7fbf69a100c6efb5 [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.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", "MyInfo", "MyOtherInfo", "my_aspect", "my_macro", "my_rule")
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~v0.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", "MyInfo", "my_aspect", "my_macro", "my_rule")
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_lambda", "return_nested")
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 unexportedRepositoryRule_notDocumented() throws Exception {
scratch.file(
"foo.bzl",
"""
def _my_impl(repository_ctx):
pass
s = struct(
unexported_repo_rule = repository_rule(
doc = "Unexported repo rule",
implementation = _my_impl,
),
)
""");
scratch.file(
"BUILD",
"""
starlark_doc_extract(
name = "extract",
src = "foo.bzl",
)
""");
ModuleInfo moduleInfo = protoFromConfiguredTarget("//:extract");
assertThat(moduleInfo.getRepositoryRuleInfoList()).isEmpty();
}
@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~v0.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~//: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\"");
}
}