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')"); + } +}