From 3c6725072b8c93d5cf8f19af1319822fbfba4665 Mon Sep 17 00:00:00 2001 From: brandjon Date: Tue, 29 Jan 2019 13:21:23 -0800 Subject: [PATCH] Introduce PyInfo to replace the legacy "py" struct provider This CL adds the PyInfo data type and registers it with a bootstrap. A follow-up CL will make the Python rules actually produce and consume this provider. The new provider's API exactly mirrors the current API of the legacy struct provider, as encapsulated by PyStructUtils. Work toward #7010. RELNOTES: None PiperOrigin-RevId: 231460993 --- .../com/google/devtools/build/docgen/BUILD | 3 + .../devtools/build/docgen/SymbolFamilies.java | 4 + .../java/com/google/devtools/build/lib/BUILD | 3 + .../bazel/rules/BazelRuleClassProvider.java | 4 + .../build/lib/rules/python/PyInfo.java | 251 ++++++++++++++++++ .../build/lib/rules/python/PyStructUtils.java | 8 +- .../devtools/build/lib/skylarkbuildapi/BUILD | 1 + .../build/lib/skylarkbuildapi/python/BUILD | 28 ++ .../skylarkbuildapi/python/PyBootstrap.java | 34 +++ .../lib/skylarkbuildapi/python/PyInfoApi.java | 136 ++++++++++ .../lib/syntax/SkylarkSignatureProcessor.java | 4 +- .../com/google/devtools/build/skydoc/BUILD | 3 + .../devtools/build/skydoc/SkydocMain.java | 4 + .../build/skydoc/fakebuildapi/python/BUILD | 22 ++ .../fakebuildapi/python/FakePyInfo.java | 73 +++++ .../devtools/build/lib/rules/python/BUILD | 18 ++ .../build/lib/rules/python/PyInfoTest.java | 176 ++++++++++++ 17 files changed, 767 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/google/devtools/build/lib/rules/python/PyInfo.java create mode 100644 src/main/java/com/google/devtools/build/lib/skylarkbuildapi/python/BUILD create mode 100644 src/main/java/com/google/devtools/build/lib/skylarkbuildapi/python/PyBootstrap.java create mode 100644 src/main/java/com/google/devtools/build/lib/skylarkbuildapi/python/PyInfoApi.java create mode 100644 src/main/java/com/google/devtools/build/skydoc/fakebuildapi/python/BUILD create mode 100644 src/main/java/com/google/devtools/build/skydoc/fakebuildapi/python/FakePyInfo.java create mode 100644 src/test/java/com/google/devtools/build/lib/rules/python/PyInfoTest.java diff --git a/src/main/java/com/google/devtools/build/docgen/BUILD b/src/main/java/com/google/devtools/build/docgen/BUILD index a93fcf99cd3d40..76063120e4a273 100644 --- a/src/main/java/com/google/devtools/build/docgen/BUILD +++ b/src/main/java/com/google/devtools/build/docgen/BUILD @@ -23,6 +23,7 @@ java_library( "//src/main/java/com/google/devtools/build/lib/skylarkbuildapi/cpp", "//src/main/java/com/google/devtools/build/lib/skylarkbuildapi/java", "//src/main/java/com/google/devtools/build/lib/skylarkbuildapi/platform", + "//src/main/java/com/google/devtools/build/lib/skylarkbuildapi/python", "//src/main/java/com/google/devtools/build/lib/skylarkbuildapi/repository", "//src/main/java/com/google/devtools/build/lib/skylarkbuildapi/test", "//src/main/java/com/google/devtools/build/skydoc/fakebuildapi", @@ -32,6 +33,7 @@ java_library( "//src/main/java/com/google/devtools/build/skydoc/fakebuildapi/cpp", "//src/main/java/com/google/devtools/build/skydoc/fakebuildapi/java", "//src/main/java/com/google/devtools/build/skydoc/fakebuildapi/platform", + "//src/main/java/com/google/devtools/build/skydoc/fakebuildapi/python", "//src/main/java/com/google/devtools/build/skydoc/fakebuildapi/repository", "//src/main/java/com/google/devtools/build/skydoc/fakebuildapi/test", "//src/main/java/com/google/devtools/common/options", @@ -84,6 +86,7 @@ filegroup( "//src/main/java/com/google/devtools/build/skydoc/fakebuildapi/cpp:srcs", "//src/main/java/com/google/devtools/build/skydoc/fakebuildapi/java:srcs", "//src/main/java/com/google/devtools/build/skydoc/fakebuildapi/platform:srcs", + "//src/main/java/com/google/devtools/build/skydoc/fakebuildapi/python:srcs", "//src/main/java/com/google/devtools/build/skydoc/fakebuildapi/repository:srcs", "//src/main/java/com/google/devtools/build/skydoc/fakebuildapi/test:srcs", "//src/main/java/com/google/devtools/build/skydoc/rendering:srcs", diff --git a/src/main/java/com/google/devtools/build/docgen/SymbolFamilies.java b/src/main/java/com/google/devtools/build/docgen/SymbolFamilies.java index 532f1e55a739a9..caa91de658189b 100644 --- a/src/main/java/com/google/devtools/build/docgen/SymbolFamilies.java +++ b/src/main/java/com/google/devtools/build/docgen/SymbolFamilies.java @@ -25,6 +25,7 @@ import com.google.devtools.build.lib.skylarkbuildapi.cpp.CcBootstrap; import com.google.devtools.build.lib.skylarkbuildapi.java.JavaBootstrap; import com.google.devtools.build.lib.skylarkbuildapi.platform.PlatformBootstrap; +import com.google.devtools.build.lib.skylarkbuildapi.python.PyBootstrap; import com.google.devtools.build.lib.skylarkbuildapi.repository.RepositoryBootstrap; import com.google.devtools.build.lib.skylarkbuildapi.test.TestingBootstrap; import com.google.devtools.build.lib.syntax.MethodLibrary; @@ -55,6 +56,7 @@ import com.google.devtools.build.skydoc.fakebuildapi.java.FakeJavaInfo.FakeJavaInfoProvider; import com.google.devtools.build.skydoc.fakebuildapi.java.FakeJavaProtoCommon; import com.google.devtools.build.skydoc.fakebuildapi.platform.FakePlatformCommon; +import com.google.devtools.build.skydoc.fakebuildapi.python.FakePyInfo.FakePyInfoProvider; import com.google.devtools.build.skydoc.fakebuildapi.repository.FakeRepositoryModule; import com.google.devtools.build.skydoc.fakebuildapi.test.FakeAnalysisFailureInfoProvider; import com.google.devtools.build.skydoc.fakebuildapi.test.FakeAnalysisTestResultInfoProvider; @@ -186,6 +188,7 @@ private Map collectBzlGlobals() { new FakeJavaProtoCommon(), new FakeJavaCcLinkParamsProvider.Provider()); PlatformBootstrap platformBootstrap = new PlatformBootstrap(new FakePlatformCommon()); + PyBootstrap pyBootstrap = new PyBootstrap(new FakePyInfoProvider()); RepositoryBootstrap repositoryBootstrap = new RepositoryBootstrap(new FakeRepositoryModule()); TestingBootstrap testingBootstrap = new TestingBootstrap(new FakeTestingModule(), new FakeAnalysisFailureInfoProvider(), @@ -198,6 +201,7 @@ private Map collectBzlGlobals() { configBootstrap.addBindingsToBuilder(envBuilder); javaBootstrap.addBindingsToBuilder(envBuilder); platformBootstrap.addBindingsToBuilder(envBuilder); + pyBootstrap.addBindingsToBuilder(envBuilder); repositoryBootstrap.addBindingsToBuilder(envBuilder); testingBootstrap.addBindingsToBuilder(envBuilder); diff --git a/src/main/java/com/google/devtools/build/lib/BUILD b/src/main/java/com/google/devtools/build/lib/BUILD index 74f885f2bdc46c..b7e94345e4b4ec 100644 --- a/src/main/java/com/google/devtools/build/lib/BUILD +++ b/src/main/java/com/google/devtools/build/lib/BUILD @@ -753,6 +753,7 @@ java_library( "//src/main/java/com/google/devtools/build/lib/skylarkbuildapi/apple", "//src/main/java/com/google/devtools/build/lib/skylarkbuildapi/cpp", "//src/main/java/com/google/devtools/build/lib/skylarkbuildapi/java", + "//src/main/java/com/google/devtools/build/lib/skylarkbuildapi/python", "//src/main/java/com/google/devtools/build/lib/vfs", "//src/main/java/com/google/devtools/build/skyframe", "//src/main/java/com/google/devtools/build/skyframe:skyframe-objects", @@ -1119,11 +1120,13 @@ java_library( "//src/main/java/com/google/devtools/build/lib/concurrent", "//src/main/java/com/google/devtools/build/lib/rules/cpp", "//src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec", + "//src/main/java/com/google/devtools/build/lib/skylarkbuildapi/python", "//src/main/java/com/google/devtools/build/lib/vfs:pathfragment", "//src/main/java/com/google/devtools/common/options", "//src/main/protobuf:crosstool_config_java_proto", "//src/main/protobuf:extra_actions_base_java_proto", "//third_party:guava", + "//third_party:jsr305", "//third_party/protobuf:protobuf_java", ], ) diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java index 3b5ca84ea51a8a..cb06a9bbf9c339 100644 --- a/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java +++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java @@ -82,11 +82,13 @@ import com.google.devtools.build.lib.rules.proto.ProtoConfiguration; import com.google.devtools.build.lib.rules.proto.ProtoInfo; import com.google.devtools.build.lib.rules.proto.ProtoLangToolchainRule; +import com.google.devtools.build.lib.rules.python.PyInfo; import com.google.devtools.build.lib.rules.python.PythonConfigurationLoader; import com.google.devtools.build.lib.rules.repository.CoreWorkspaceRules; import com.google.devtools.build.lib.rules.repository.NewLocalRepositoryRule; import com.google.devtools.build.lib.rules.test.TestingSupportRules; import com.google.devtools.build.lib.skylarkbuildapi.android.AndroidBootstrap; +import com.google.devtools.build.lib.skylarkbuildapi.python.PyBootstrap; import com.google.devtools.build.lib.util.OS; import com.google.devtools.build.lib.util.ResourceFileLoader; import com.google.devtools.build.lib.vfs.PathFragment; @@ -342,6 +344,8 @@ public void init(ConfiguredRuleClassProvider.Builder builder) { builder.addRuleDefinition(new BazelPyBinaryRule()); builder.addRuleDefinition(new BazelPyTestRule()); builder.addRuleDefinition(new BazelPyRuntimeRule()); + + builder.addSkylarkBootstrap(new PyBootstrap(PyInfo.PROVIDER)); } @Override diff --git a/src/main/java/com/google/devtools/build/lib/rules/python/PyInfo.java b/src/main/java/com/google/devtools/build/lib/rules/python/PyInfo.java new file mode 100644 index 00000000000000..2ea0b5893e6664 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/rules/python/PyInfo.java @@ -0,0 +1,251 @@ +// Copyright 2019 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.python; + +import com.google.common.base.Preconditions; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.collect.nestedset.NestedSet; +import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; +import com.google.devtools.build.lib.collect.nestedset.Order; +import com.google.devtools.build.lib.events.Location; +import com.google.devtools.build.lib.packages.BuiltinProvider; +import com.google.devtools.build.lib.packages.Info; +import com.google.devtools.build.lib.skylarkbuildapi.python.PyInfoApi; +import com.google.devtools.build.lib.syntax.EvalException; +import com.google.devtools.build.lib.syntax.EvalUtils; +import com.google.devtools.build.lib.syntax.Runtime; +import com.google.devtools.build.lib.syntax.SkylarkNestedSet; +import java.util.Objects; +import javax.annotation.Nullable; + +/** Instance of the provider type for the Python rules. */ +public class PyInfo extends Info implements PyInfoApi { + + public static final String STARLARK_NAME = "PyInfo"; + + public static final PyInfoProvider PROVIDER = new PyInfoProvider(); + + /** + * Returns true if the given depset has a content type that is a subtype of the given class, and + * has an order compatible with the given order. + */ + private static boolean depsetHasTypeAndCompatibleOrder( + SkylarkNestedSet depset, Class clazz, Order order) { + // Work around #7266 by special-casing the empty set in the type check. + boolean typeOk = depset.isEmpty() || depset.getContentType().canBeCastTo(clazz); + boolean orderOk = depset.getOrder().isCompatible(order); + return typeOk && orderOk; + } + + /** + * Returns the type name of a value and possibly additional description. + * + *

For depsets, this includes its content type and order. + */ + private static String describeType(Object value) { + String typeName = EvalUtils.getDataTypeName(value, /*fullDetails=*/ true); + if (value instanceof SkylarkNestedSet) { + return ((SkylarkNestedSet) value).getOrder().getSkylarkName() + "-ordered " + typeName; + } else { + return typeName; + } + } + + private final SkylarkNestedSet transitiveSources; + private final boolean usesSharedLibraries; + private final SkylarkNestedSet imports; + private final boolean hasPy2OnlySources; + private final boolean hasPy3OnlySources; + + private PyInfo( + @Nullable Location location, + SkylarkNestedSet transitiveSources, + boolean usesSharedLibraries, + SkylarkNestedSet imports, + boolean hasPy2OnlySources, + boolean hasPy3OnlySources) { + super(PROVIDER, location); + Preconditions.checkArgument( + depsetHasTypeAndCompatibleOrder(transitiveSources, Artifact.class, Order.COMPILE_ORDER)); + // TODO(brandjon): PyCommon currently requires COMPILE_ORDER, but we'll probably want to change + // that to NAIVE_LINK (preorder). In the meantime, order isn't an invariant of the provider + // itself, so we use STABLE here to accept any order. + Preconditions.checkArgument( + depsetHasTypeAndCompatibleOrder(imports, String.class, Order.STABLE_ORDER)); + this.transitiveSources = transitiveSources; + this.usesSharedLibraries = usesSharedLibraries; + this.imports = imports; + this.hasPy2OnlySources = hasPy2OnlySources; + this.hasPy3OnlySources = hasPy3OnlySources; + } + + @Override + public boolean equals(Object other) { + // PyInfo implements value equality, but note that it contains identity-equality fields + // (depsets), so you generally shouldn't rely on equality comparisons. + if (!(other instanceof PyInfo)) { + return false; + } + PyInfo otherInfo = (PyInfo) other; + return (this.transitiveSources.equals(otherInfo.transitiveSources) + && this.usesSharedLibraries == otherInfo.usesSharedLibraries + && this.imports.equals(otherInfo.imports) + && this.hasPy2OnlySources == otherInfo.hasPy2OnlySources + && this.hasPy3OnlySources == otherInfo.hasPy3OnlySources); + } + + @Override + public int hashCode() { + return Objects.hash( + PyInfo.class, + transitiveSources, + usesSharedLibraries, + imports, + hasPy2OnlySources, + hasPy3OnlySources); + } + + @Override + public SkylarkNestedSet getTransitiveSources() { + return transitiveSources; + } + + @Override + public boolean getUsesSharedLibraries() { + return usesSharedLibraries; + } + + @Override + public SkylarkNestedSet getImports() { + return imports; + } + + @Override + public boolean getHasPy2OnlySources() { + return hasPy2OnlySources; + } + + @Override + public boolean getHasPy3OnlySources() { + return hasPy3OnlySources; + } + + /** The singular PyInfo provider type object. */ + public static class PyInfoProvider extends BuiltinProvider + implements PyInfoApi.PyInfoProviderApi { + + private PyInfoProvider() { + super(STARLARK_NAME, PyInfo.class); + } + + @Override + public PyInfo constructor( + SkylarkNestedSet transitiveSources, + boolean usesSharedLibraries, + Object importsUncast, + boolean hasPy2OnlySources, + boolean hasPy3OnlySources, + Location loc) + throws EvalException { + SkylarkNestedSet imports = + importsUncast.equals(Runtime.UNBOUND) + ? SkylarkNestedSet.of(String.class, NestedSetBuilder.emptySet(Order.COMPILE_ORDER)) + : (SkylarkNestedSet) importsUncast; + + if (!depsetHasTypeAndCompatibleOrder( + transitiveSources, Artifact.class, Order.COMPILE_ORDER)) { + throw new EvalException( + loc, + String.format( + "'transitive_sources' field should be a postorder-compatible depset of Files (got " + + "a '%s')", + describeType(transitiveSources))); + } + if (!depsetHasTypeAndCompatibleOrder(imports, String.class, Order.STABLE_ORDER)) { + throw new EvalException( + loc, + String.format( + "'imports' field should be a depset of strings (got a '%s')", + describeType(imports))); + } + + return new PyInfo( + loc, + transitiveSources, + usesSharedLibraries, + imports, + hasPy2OnlySources, + hasPy3OnlySources); + } + } + + public static Builder builder() { + return new Builder(); + } + + /** Builder for {@link PyInfo}. */ + public static class Builder { + Location location = null; + NestedSet transitiveSources = NestedSetBuilder.emptySet(Order.COMPILE_ORDER); + boolean usesSharedLibraries = false; + NestedSet imports = NestedSetBuilder.emptySet(Order.COMPILE_ORDER); + boolean hasPy2OnlySources = false; + boolean hasPy3OnlySources = false; + + // Use the static builder() method instead. + private Builder() {} + + public Builder setLocation(Location location) { + this.location = location; + return this; + } + + public Builder setTransitiveSources(NestedSet transitiveSources) { + this.transitiveSources = transitiveSources; + return this; + } + + public Builder setUsesSharedLibraries(boolean usesSharedLibraries) { + this.usesSharedLibraries = usesSharedLibraries; + return this; + } + + public Builder setImports(NestedSet imports) { + this.imports = imports; + return this; + } + + public Builder setHasPy2OnlySources(boolean hasPy2OnlySources) { + this.hasPy2OnlySources = hasPy2OnlySources; + return this; + } + + public Builder setHasPy3OnlySources(boolean hasPy3OnlySources) { + this.hasPy3OnlySources = hasPy3OnlySources; + return this; + } + + public PyInfo build() { + Preconditions.checkNotNull(transitiveSources); + return new PyInfo( + location, + SkylarkNestedSet.of(Artifact.class, transitiveSources), + usesSharedLibraries, + SkylarkNestedSet.of(String.class, imports), + hasPy2OnlySources, + hasPy3OnlySources); + } + } +} diff --git a/src/main/java/com/google/devtools/build/lib/rules/python/PyStructUtils.java b/src/main/java/com/google/devtools/build/lib/rules/python/PyStructUtils.java index 5e3ae8c52e45a4..f2a45b98fb4a2f 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/python/PyStructUtils.java +++ b/src/main/java/com/google/devtools/build/lib/rules/python/PyStructUtils.java @@ -28,7 +28,7 @@ import com.google.devtools.build.lib.syntax.SkylarkType; /** Static helper class for creating and accessing instances of the legacy "py" struct provider. */ -// TODO(#7010): Replace this with a real provider. +// TODO(#7010): Remove this in favor of PyInfo. public class PyStructUtils { // Disable construction. @@ -76,7 +76,7 @@ private PyStructUtils() {} builder.put(USES_SHARED_LIBRARIES, false); builder.put( IMPORTS, - SkylarkNestedSet.of(String.class, NestedSetBuilder.compileOrder().build())); + SkylarkNestedSet.of(String.class, NestedSetBuilder.emptySet(Order.COMPILE_ORDER))); builder.put(HAS_PY2_ONLY_SOURCES, false); builder.put(HAS_PY3_ONLY_SOURCES, false); DEFAULTS = builder.build(); @@ -110,7 +110,7 @@ public static NestedSet getTransitiveSources(StructImpl info) throws E "'%s' provider's '%s' field should be a depset of Files (got a '%s')", PROVIDER_NAME, TRANSITIVE_SOURCES, - EvalUtils.getDataTypeNameFromClass(fieldValue.getClass())); + EvalUtils.getDataTypeName(fieldValue, /*fullDetails=*/ true)); NestedSet unwrappedValue = castValue.getSet(Artifact.class); if (!unwrappedValue.getOrder().isCompatible(Order.COMPILE_ORDER)) { throw new EvalException( @@ -137,7 +137,7 @@ public static boolean getUsesSharedLibraries(StructImpl info) throws EvalExcepti "'%s' provider's '%s' field should be a boolean (got a '%s')", PROVIDER_NAME, USES_SHARED_LIBRARIES, - EvalUtils.getDataTypeNameFromClass(fieldValue.getClass())); + EvalUtils.getDataTypeName(fieldValue, /*fullDetails=*/ true)); } /** diff --git a/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/BUILD b/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/BUILD index 73ad12c5000e2f..47a56946d93e7a 100644 --- a/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/BUILD +++ b/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/BUILD @@ -19,6 +19,7 @@ filegroup( "//src/main/java/com/google/devtools/build/lib/skylarkbuildapi/cpp:srcs", "//src/main/java/com/google/devtools/build/lib/skylarkbuildapi/java:srcs", "//src/main/java/com/google/devtools/build/lib/skylarkbuildapi/platform:srcs", + "//src/main/java/com/google/devtools/build/lib/skylarkbuildapi/python:srcs", "//src/main/java/com/google/devtools/build/lib/skylarkbuildapi/repository:srcs", "//src/main/java/com/google/devtools/build/lib/skylarkbuildapi/test:srcs", ], diff --git a/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/python/BUILD b/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/python/BUILD new file mode 100644 index 00000000000000..220dcb18839686 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/python/BUILD @@ -0,0 +1,28 @@ +# Description: +# This package contains interfaces representing the skylark "build API" +# (but not the implementation of that API). Ultimately, this package +# may be broken out of the Bazel package hierarchy to be standalone. +# Thus, this package should not depend on Bazel-specific packages (only +# those which contain pure-Skylark concepts, such as the interpreter or +# annotation interfaces). + +package(default_visibility = ["//src:__subpackages__"]) + +licenses(["notice"]) # Apache 2.0 + +filegroup( + name = "srcs", + srcs = glob(["**"]), +) + +java_library( + name = "python", + srcs = glob(["*.java"]), + deps = [ + "//src/main/java/com/google/devtools/build/lib:events", + "//src/main/java/com/google/devtools/build/lib:skylarkinterface", + "//src/main/java/com/google/devtools/build/lib:syntax", + "//src/main/java/com/google/devtools/build/lib/skylarkbuildapi", + "//third_party:guava", + ], +) diff --git a/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/python/PyBootstrap.java b/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/python/PyBootstrap.java new file mode 100644 index 00000000000000..5c182de58b4e53 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/python/PyBootstrap.java @@ -0,0 +1,34 @@ +// Copyright 2019 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.skylarkbuildapi.python; + +import com.google.common.collect.ImmutableMap; +import com.google.devtools.build.lib.skylarkbuildapi.Bootstrap; +import com.google.devtools.build.lib.skylarkbuildapi.python.PyInfoApi.PyInfoProviderApi; + +/** {@link Bootstrap} for Starlark objects related to the Python rules. */ +public class PyBootstrap implements Bootstrap { + + private final PyInfoProviderApi pyInfoProviderApi; + + public PyBootstrap(PyInfoProviderApi pyInfoProviderApi) { + this.pyInfoProviderApi = pyInfoProviderApi; + } + + @Override + public void addBindingsToBuilder(ImmutableMap.Builder builder) { + builder.put("PyInfo", pyInfoProviderApi); + } +} diff --git a/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/python/PyInfoApi.java b/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/python/PyInfoApi.java new file mode 100644 index 00000000000000..4fbb185069c4fb --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/skylarkbuildapi/python/PyInfoApi.java @@ -0,0 +1,136 @@ +// Copyright 2019 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.skylarkbuildapi.python; + +import com.google.devtools.build.lib.events.Location; +import com.google.devtools.build.lib.skylarkbuildapi.FileApi; +import com.google.devtools.build.lib.skylarkbuildapi.ProviderApi; +import com.google.devtools.build.lib.skylarkinterface.Param; +import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable; +import com.google.devtools.build.lib.skylarkinterface.SkylarkConstructor; +import com.google.devtools.build.lib.skylarkinterface.SkylarkModule; +import com.google.devtools.build.lib.skylarkinterface.SkylarkModuleCategory; +import com.google.devtools.build.lib.skylarkinterface.SkylarkValue; +import com.google.devtools.build.lib.syntax.EvalException; +import com.google.devtools.build.lib.syntax.SkylarkNestedSet; + +/** Provider instance for the Python rules. */ +@SkylarkModule( + name = "PyInfo", + doc = "Encapsulates information provided by the Python rules.", + category = SkylarkModuleCategory.PROVIDER) +public interface PyInfoApi extends SkylarkValue { + + @SkylarkCallable( + name = "transitive_sources", + structField = true, + doc = + "A (postorder-compatible) depset of .py files appearing in the " + + "target's srcs and the srcs of the target's transitive " + + "deps.") + SkylarkNestedSet getTransitiveSources(); + + @SkylarkCallable( + name = "uses_shared_libraries", + structField = true, + doc = + "Whether any of this target's transitive deps has a shared library file " + + "(such as a .so file)." + + "" + + "

This field is currently unused in Bazel and may go away in the future.") + boolean getUsesSharedLibraries(); + + @SkylarkCallable( + name = "imports", + structField = true, + doc = + "A depset of import path strings to be added to the PYTHONPATH of " + + "executable Python targets. These are accumulated from the transitive " + + "deps." + + "" + + "

The order of the depset is not guaranteed and may be changed in the future. It " + + "is recommended to use default order (the default).") + SkylarkNestedSet getImports(); + + @SkylarkCallable( + name = "has_py2_only_sources", + structField = true, + doc = "Whether any of this target's transitive sources requires a Python 2 runtime.") + boolean getHasPy2OnlySources(); + + @SkylarkCallable( + name = "has_py3_only_sources", + structField = true, + doc = "Whether any of this target's transitive sources requires a Python 3 runtime.") + boolean getHasPy3OnlySources(); + + /** Provider type for {@link PyInfoApi} objects. */ + @SkylarkModule(name = "Provider", documented = false, doc = "") + interface PyInfoProviderApi extends ProviderApi { + + @SkylarkCallable( + name = "PyInfo", + doc = "The PyInfo constructor.", + parameters = { + @Param( + name = "transitive_sources", + type = SkylarkNestedSet.class, + generic1 = FileApi.class, + positional = false, + named = true, + doc = "The value for the new object's transitive_sources field."), + @Param( + name = "uses_shared_libraries", + type = Boolean.class, + positional = false, + named = true, + defaultValue = "False", + doc = "The value for the new object's uses_shared_libraries field."), + @Param( + name = "imports", + type = SkylarkNestedSet.class, + generic1 = String.class, + positional = false, + named = true, + defaultValue = "unbound", + doc = "The value for the new object's imports field."), + @Param( + name = "has_py2_only_sources", + type = Boolean.class, + positional = false, + named = true, + defaultValue = "False", + doc = "The value for the new object's has_py2_only_sources field."), + @Param( + name = "has_py3_only_sources", + type = Boolean.class, + positional = false, + named = true, + defaultValue = "False", + doc = "The value for the new object's has_py3_only_sources field.") + }, + selfCall = true, + useLocation = true) + @SkylarkConstructor(objectType = PyInfoApi.class, receiverNameForDoc = "PyInfo") + PyInfoApi constructor( + SkylarkNestedSet transitiveSources, + boolean usesSharedLibraries, + Object importsUncast, + boolean hasPy2OnlySources, + boolean hasPy3OnlySources, + Location loc) + throws EvalException; + } +} diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSignatureProcessor.java b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSignatureProcessor.java index 865801fd9284c7..eabff4e619684a 100644 --- a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSignatureProcessor.java +++ b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSignatureProcessor.java @@ -223,7 +223,9 @@ private static Parameter getParameter( return new Parameter.Star<>(Identifier.of(param.name()), officialType); } else if (mandatory) { return new Parameter.Mandatory<>(Identifier.of(param.name()), officialType); - } else if (defaultValue != null && enforcedType != null) { + } else if (defaultValue != null + && !defaultValue.equals(Runtime.UNBOUND) + && enforcedType != null) { Preconditions.checkArgument(enforcedType.contains(defaultValue), "In function '%s', parameter '%s' has default value %s that isn't of enforced type %s", name, param.name(), Printer.repr(defaultValue), enforcedType); diff --git a/src/main/java/com/google/devtools/build/skydoc/BUILD b/src/main/java/com/google/devtools/build/skydoc/BUILD index c6e3688ca92442..0fbe823f5b5b6d 100644 --- a/src/main/java/com/google/devtools/build/skydoc/BUILD +++ b/src/main/java/com/google/devtools/build/skydoc/BUILD @@ -22,6 +22,7 @@ filegroup( "//src/main/java/com/google/devtools/build/skydoc/fakebuildapi/cpp:srcs", "//src/main/java/com/google/devtools/build/skydoc/fakebuildapi/java:srcs", "//src/main/java/com/google/devtools/build/skydoc/fakebuildapi/platform:srcs", + "//src/main/java/com/google/devtools/build/skydoc/fakebuildapi/python:srcs", "//src/main/java/com/google/devtools/build/skydoc/fakebuildapi/repository:srcs", "//src/main/java/com/google/devtools/build/skydoc/fakebuildapi/test:srcs", "//src/main/java/com/google/devtools/build/skydoc/rendering:srcs", @@ -64,6 +65,7 @@ java_library( "//src/main/java/com/google/devtools/build/lib/skylarkbuildapi/cpp", "//src/main/java/com/google/devtools/build/lib/skylarkbuildapi/java", "//src/main/java/com/google/devtools/build/lib/skylarkbuildapi/platform", + "//src/main/java/com/google/devtools/build/lib/skylarkbuildapi/python", "//src/main/java/com/google/devtools/build/lib/skylarkbuildapi/repository", "//src/main/java/com/google/devtools/build/lib/skylarkbuildapi/test", "//src/main/java/com/google/devtools/build/lib/vfs:pathfragment", @@ -74,6 +76,7 @@ java_library( "//src/main/java/com/google/devtools/build/skydoc/fakebuildapi/cpp", "//src/main/java/com/google/devtools/build/skydoc/fakebuildapi/java", "//src/main/java/com/google/devtools/build/skydoc/fakebuildapi/platform", + "//src/main/java/com/google/devtools/build/skydoc/fakebuildapi/python", "//src/main/java/com/google/devtools/build/skydoc/fakebuildapi/repository", "//src/main/java/com/google/devtools/build/skydoc/fakebuildapi/test", "//src/main/java/com/google/devtools/build/skydoc/rendering", diff --git a/src/main/java/com/google/devtools/build/skydoc/SkydocMain.java b/src/main/java/com/google/devtools/build/skydoc/SkydocMain.java index bb64b94e1bd7c7..16855c9b6e1762 100644 --- a/src/main/java/com/google/devtools/build/skydoc/SkydocMain.java +++ b/src/main/java/com/google/devtools/build/skydoc/SkydocMain.java @@ -47,6 +47,7 @@ import com.google.devtools.build.lib.skylarkbuildapi.java.GeneratedExtensionRegistryProviderApi; import com.google.devtools.build.lib.skylarkbuildapi.java.JavaBootstrap; import com.google.devtools.build.lib.skylarkbuildapi.platform.PlatformBootstrap; +import com.google.devtools.build.lib.skylarkbuildapi.python.PyBootstrap; import com.google.devtools.build.lib.skylarkbuildapi.repository.RepositoryBootstrap; import com.google.devtools.build.lib.skylarkbuildapi.test.TestingBootstrap; import com.google.devtools.build.lib.syntax.BaseFunction; @@ -86,6 +87,7 @@ import com.google.devtools.build.skydoc.fakebuildapi.java.FakeJavaInfo.FakeJavaInfoProvider; import com.google.devtools.build.skydoc.fakebuildapi.java.FakeJavaProtoCommon; import com.google.devtools.build.skydoc.fakebuildapi.platform.FakePlatformCommon; +import com.google.devtools.build.skydoc.fakebuildapi.python.FakePyInfo.FakePyInfoProvider; import com.google.devtools.build.skydoc.fakebuildapi.repository.FakeRepositoryModule; import com.google.devtools.build.skydoc.fakebuildapi.test.FakeAnalysisFailureInfoProvider; import com.google.devtools.build.skydoc.fakebuildapi.test.FakeAnalysisTestResultInfoProvider; @@ -477,6 +479,7 @@ private static GlobalFrame globalFrame(List ruleInfoList, new FakeJavaProtoCommon(), new FakeJavaCcLinkParamsProvider.Provider()); PlatformBootstrap platformBootstrap = new PlatformBootstrap(new FakePlatformCommon()); + PyBootstrap pyBootstrap = new PyBootstrap(new FakePyInfoProvider()); RepositoryBootstrap repositoryBootstrap = new RepositoryBootstrap(new FakeRepositoryModule()); TestingBootstrap testingBootstrap = new TestingBootstrap(new FakeTestingModule(), new FakeAnalysisFailureInfoProvider(), @@ -493,6 +496,7 @@ private static GlobalFrame globalFrame(List ruleInfoList, configBootstrap.addBindingsToBuilder(envBuilder); javaBootstrap.addBindingsToBuilder(envBuilder); platformBootstrap.addBindingsToBuilder(envBuilder); + pyBootstrap.addBindingsToBuilder(envBuilder); repositoryBootstrap.addBindingsToBuilder(envBuilder); testingBootstrap.addBindingsToBuilder(envBuilder); addNonBootstrapGlobals(envBuilder); diff --git a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/python/BUILD b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/python/BUILD new file mode 100644 index 00000000000000..5b4cf6855e36ab --- /dev/null +++ b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/python/BUILD @@ -0,0 +1,22 @@ +package( + default_visibility = ["//src:__subpackages__"], +) + +licenses(["notice"]) # Apache 2.0 + +filegroup( + name = "srcs", + srcs = glob(["**"]), +) + +java_library( + name = "python", + srcs = glob(["*.java"]), + deps = [ + "//src/main/java/com/google/devtools/build/lib:events", + "//src/main/java/com/google/devtools/build/lib:skylarkinterface", + "//src/main/java/com/google/devtools/build/lib:syntax", + "//src/main/java/com/google/devtools/build/lib/skylarkbuildapi", + "//src/main/java/com/google/devtools/build/lib/skylarkbuildapi/python", + ], +) diff --git a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/python/FakePyInfo.java b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/python/FakePyInfo.java new file mode 100644 index 00000000000000..b495e04cdca63b --- /dev/null +++ b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/python/FakePyInfo.java @@ -0,0 +1,73 @@ +// Copyright 2019 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.skydoc.fakebuildapi.python; + +import com.google.devtools.build.lib.events.Location; +import com.google.devtools.build.lib.skylarkbuildapi.FileApi; +import com.google.devtools.build.lib.skylarkbuildapi.python.PyInfoApi; +import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter; +import com.google.devtools.build.lib.syntax.EvalException; +import com.google.devtools.build.lib.syntax.SkylarkNestedSet; + +/** Fake implementation of {@link PyInfoApi}. */ +public class FakePyInfo implements PyInfoApi { + + @Override + public SkylarkNestedSet getTransitiveSources() { + return null; + } + + @Override + public boolean getUsesSharedLibraries() { + return false; + } + + @Override + public SkylarkNestedSet getImports() { + return null; + } + + @Override + public boolean getHasPy2OnlySources() { + return false; + } + + @Override + public boolean getHasPy3OnlySources() { + return false; + } + + @Override + public void repr(SkylarkPrinter printer) {} + + /** Fake implementation of {@link PyInfoProviderApi}. */ + public static class FakePyInfoProvider implements PyInfoProviderApi { + + @Override + public PyInfoApi constructor( + SkylarkNestedSet transitiveSources, + boolean usesSharedLibraries, + Object importsUncast, + boolean hasPy2OnlySources, + boolean hasPy3OnlySources, + Location loc) + throws EvalException { + return new FakePyInfo(); + } + + @Override + public void repr(SkylarkPrinter printer) {} + } +} diff --git a/src/test/java/com/google/devtools/build/lib/rules/python/BUILD b/src/test/java/com/google/devtools/build/lib/rules/python/BUILD index 261a85d4f04306..422e6b271ded41 100644 --- a/src/test/java/com/google/devtools/build/lib/rules/python/BUILD +++ b/src/test/java/com/google/devtools/build/lib/rules/python/BUILD @@ -14,6 +14,7 @@ test_suite( tests = [ ":BazelPythonConfigurationTest", ":PyBinaryConfiguredTargetTest", + ":PyInfoTest", ":PyLibraryConfiguredTargetTest", ":PyProviderUtilsTest", ":PyStructUtilsTest", @@ -203,6 +204,23 @@ java_test( ], ) +java_test( + name = "PyInfoTest", + srcs = ["PyInfoTest.java"], + deps = [ + "//src/main/java/com/google/devtools/build/lib:events", + "//src/main/java/com/google/devtools/build/lib:python-rules", + "//src/main/java/com/google/devtools/build/lib/actions", + "//src/main/java/com/google/devtools/build/lib/collect/nestedset", + "//src/main/java/com/google/devtools/build/lib/vfs:pathfragment", + "//src/test/java/com/google/devtools/build/lib:analysis_testutil", + "//src/test/java/com/google/devtools/build/lib:testutil", + "//src/test/java/com/google/devtools/build/lib/skylark:testutil", + "//third_party:junit4", + "//third_party:truth", + ], +) + java_test( name = "PythonStarlarkApiTest", srcs = ["PythonStarlarkApiTest.java"], diff --git a/src/test/java/com/google/devtools/build/lib/rules/python/PyInfoTest.java b/src/test/java/com/google/devtools/build/lib/rules/python/PyInfoTest.java new file mode 100644 index 00000000000000..db5d3aff6068c3 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/rules/python/PyInfoTest.java @@ -0,0 +1,176 @@ +// Copyright 2019 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.python; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.collect.nestedset.NestedSet; +import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; +import com.google.devtools.build.lib.collect.nestedset.Order; +import com.google.devtools.build.lib.events.Location; +import com.google.devtools.build.lib.skylark.util.SkylarkTestCase; +import com.google.devtools.build.lib.vfs.PathFragment; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for {@link PyInfo}. */ +@RunWith(JUnit4.class) +public class PyInfoTest extends SkylarkTestCase { + + private static final Location dummyLoc = + new Location(0, 0) { + @Override + public PathFragment getPath() { + return null; + } + }; + + private Artifact dummyArtifact; + + @Before + public void setUp() throws Exception { + dummyArtifact = getSourceArtifact("dummy"); + update("PyInfo", PyInfo.PROVIDER); + update("dummy_file", dummyArtifact); + } + + /** We need this because {@code NestedSet}s don't have value equality. */ + private static void assertHasOrderAndContainsExactly( + NestedSet set, Order order, Object... values) { + assertThat(set.getOrder()).isEqualTo(order); + assertThat(set).containsExactly(values); + } + + /** Checks values set by the builder. */ + @Test + public void builderExplicit() throws Exception { + NestedSet sources = NestedSetBuilder.create(Order.COMPILE_ORDER, dummyArtifact); + NestedSet imports = NestedSetBuilder.create(Order.COMPILE_ORDER, "abc"); + PyInfo info = + PyInfo.builder() + .setLocation(dummyLoc) + .setTransitiveSources(sources) + .setUsesSharedLibraries(true) + .setImports(imports) + .setHasPy2OnlySources(true) + .setHasPy3OnlySources(true) + .build(); + assertThat(info.getCreationLoc()).isEqualTo(dummyLoc); + assertHasOrderAndContainsExactly( + info.getTransitiveSources().getSet(Artifact.class), Order.COMPILE_ORDER, dummyArtifact); + assertThat(info.getUsesSharedLibraries()).isTrue(); + assertHasOrderAndContainsExactly( + info.getImports().getSet(String.class), Order.COMPILE_ORDER, "abc"); + assertThat(info.getHasPy2OnlySources()).isTrue(); + assertThat(info.getHasPy3OnlySources()).isTrue(); + } + + /** Checks the defaults set by the builder. */ + @Test + public void builderDefaults() throws Exception { + // transitive_sources is mandatory, so create a dummy value but no need to assert on it. + NestedSet sources = NestedSetBuilder.create(Order.COMPILE_ORDER, dummyArtifact); + PyInfo info = PyInfo.builder().setTransitiveSources(sources).build(); + assertThat(info.getCreationLoc()).isEqualTo(Location.BUILTIN); + assertThat(info.getUsesSharedLibraries()).isFalse(); + assertHasOrderAndContainsExactly(info.getImports().getSet(String.class), Order.COMPILE_ORDER); + assertThat(info.getHasPy2OnlySources()).isFalse(); + assertThat(info.getHasPy3OnlySources()).isFalse(); + } + + @Test + public void starlarkConstructor() throws Exception { + eval( + "info = PyInfo(", + " transitive_sources = depset(direct=[dummy_file]),", + " uses_shared_libraries = True,", + " imports = depset(direct=['abc']),", + " has_py2_only_sources = True,", + " has_py3_only_sources = True,", + ")"); + PyInfo info = (PyInfo) lookup("info"); + assertThat(info.getCreationLoc().getStartOffset()).isEqualTo(7); + assertHasOrderAndContainsExactly( + info.getTransitiveSources().getSet(Artifact.class), Order.STABLE_ORDER, dummyArtifact); + assertThat(info.getUsesSharedLibraries()).isTrue(); + assertHasOrderAndContainsExactly( + info.getImports().getSet(String.class), Order.STABLE_ORDER, "abc"); + assertThat(info.getHasPy2OnlySources()).isTrue(); + assertThat(info.getHasPy3OnlySources()).isTrue(); + } + + @Test + public void starlarkConstructorDefaults() throws Exception { + eval("info = PyInfo(transitive_sources = depset(direct=[dummy_file]))"); + PyInfo info = (PyInfo) lookup("info"); + assertThat(info.getCreationLoc().getStartOffset()).isEqualTo(7); + assertHasOrderAndContainsExactly( + info.getTransitiveSources().getSet(Artifact.class), Order.STABLE_ORDER, dummyArtifact); + assertThat(info.getUsesSharedLibraries()).isFalse(); + assertHasOrderAndContainsExactly(info.getImports().getSet(String.class), Order.COMPILE_ORDER); + assertThat(info.getHasPy2OnlySources()).isFalse(); + assertThat(info.getHasPy3OnlySources()).isFalse(); + } + + @Test + public void starlarkConstructorErrors_TransitiveSources() throws Exception { + checkEvalErrorContains( + "'transitive_sources' has no default value", // + "PyInfo()"); + checkEvalErrorContains( + "expected value of type 'depset of Files' for parameter 'transitive_sources'", + "PyInfo(transitive_sources = 'abc')"); + checkEvalErrorContains( + "expected value of type 'depset of Files' for parameter 'transitive_sources'", + "PyInfo(transitive_sources = depset(direct=['abc']))"); + checkEvalErrorContains( + "'transitive_sources' field should be a postorder-compatible depset of Files", + "PyInfo(transitive_sources = depset(direct=[dummy_file], order='preorder'))"); + } + + @Test + public void starlarkConstructorErrors_UsesSharedLibraries() throws Exception { + checkEvalErrorContains( + "expected value of type 'bool' for parameter 'uses_shared_libraries'", + "PyInfo(transitive_sources = depset([]), uses_shared_libraries = 'abc')"); + } + + @Test + public void starlarkConstructorErrors_Imports() throws Exception { + checkEvalErrorContains( + "expected value of type 'depset of strings' for parameter 'imports'", + "PyInfo(transitive_sources = depset([]), imports = 'abc')"); + checkEvalErrorContains( + "expected value of type 'depset of strings' for parameter 'imports'", + "PyInfo(transitive_sources = depset([]), imports = depset(direct=[123]))"); + } + + @Test + public void starlarkConstructorErrors_HasPy2OnlySources() throws Exception { + checkEvalErrorContains( + "expected value of type 'bool' for parameter 'has_py2_only_sources'", + "PyInfo(transitive_sources = depset([]), has_py2_only_sources = 'abc')"); + } + + @Test + public void starlarkConstructorErrors_HasPy3OnlySources() throws Exception { + checkEvalErrorContains( + "expected value of type 'bool' for parameter 'has_py3_only_sources'", + "PyInfo(transitive_sources = depset([]), has_py3_only_sources = 'abc')"); + } +}