Skip to content

Commit

Permalink
python: port PyRuntimeInfo to Starlark and enable it.
Browse files Browse the repository at this point in the history
Because providers are based on identity, and there are Java tests that need
to reference the provider, it's hard to split up the changes, so it's all
in a single change.

The Java class is kept to make usage by the Java tests easier; it isn't
actually used outside of tests.

Work towards bazelbuild#15684

PiperOrigin-RevId: 522119112
Change-Id: I3fef9b0477c110b5f32f8e26612c04bbb92ecb7b
  • Loading branch information
rickeylev authored and fweikert committed May 25, 2023
1 parent a37382e commit a9cdc3b
Show file tree
Hide file tree
Showing 8 changed files with 205 additions and 433 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,6 @@
import com.google.devtools.build.lib.rules.python.PyCcLinkParamsProvider;
import com.google.devtools.build.lib.rules.python.PyInfo;
import com.google.devtools.build.lib.rules.python.PyRuleClasses.PySymlink;
import com.google.devtools.build.lib.rules.python.PyRuntimeInfo;
import com.google.devtools.build.lib.rules.python.PyRuntimeRule;
import com.google.devtools.build.lib.rules.python.PyStarlarkTransitions;
import com.google.devtools.build.lib.rules.python.PythonConfiguration;
Expand Down Expand Up @@ -475,7 +474,6 @@ public void init(ConfiguredRuleClassProvider.Builder builder) {
builder.addStarlarkBootstrap(
new PyBootstrap(
PyInfo.PROVIDER,
PyRuntimeInfo.PROVIDER,
PyStarlarkTransitions.INSTANCE,
new GoogleLegacyStubs.PyWrapCcHelper(),
new GoogleLegacyStubs.PyWrapCcInfoProvider(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,18 @@

import static net.starlark.java.eval.Starlark.NONE;

import com.google.common.base.Preconditions;
import com.google.common.annotations.VisibleForTesting;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.collect.nestedset.Depset;
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.packages.BuiltinProvider;
import com.google.devtools.build.lib.packages.Info;
import com.google.devtools.build.lib.packages.StarlarkInfo;
import com.google.devtools.build.lib.packages.StarlarkProviderWrapper;
import com.google.devtools.build.lib.starlarkbuildapi.python.PyRuntimeInfoApi;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.util.Objects;
import javax.annotation.Nullable;
import net.starlark.java.eval.EvalException;
import net.starlark.java.eval.Starlark;
import net.starlark.java.eval.StarlarkThread;
import net.starlark.java.syntax.Location;

/**
* Instance of the provider type that describes Python runtimes.
Expand All @@ -43,307 +39,83 @@
* invariants mirror the user-visible API on {@link PyRuntimeInfoApi} except that {@code None} is
* replaced by null.
*/
public final class PyRuntimeInfo implements Info, PyRuntimeInfoApi<Artifact> {

/** The Starlark-accessible top-level builtin name for this provider type. */
public static final String STARLARK_NAME = "PyRuntimeInfo";

@VisibleForTesting
public final class PyRuntimeInfo {
/** The singular {@code PyRuntimeInfo} provider type object. */
public static final PyRuntimeInfoProvider PROVIDER = new PyRuntimeInfoProvider();

private final Location location;
@Nullable private final PathFragment interpreterPath;
@Nullable private final Artifact interpreter;
// Validated on initialization to contain Artifact
@Nullable private final Depset files;
@Nullable private final Artifact coverageTool;
@Nullable private final Depset coverageFiles;
/** Invariant: either PY2 or PY3. */
private final PythonVersion pythonVersion;

private final String stubShebang;
@Nullable private final Artifact bootstrapTemplate;

private PyRuntimeInfo(
@Nullable Location location,
@Nullable PathFragment interpreterPath,
@Nullable Artifact interpreter,
@Nullable Depset files,
@Nullable Artifact coverageTool,
@Nullable Depset coverageFiles,
PythonVersion pythonVersion,
@Nullable String stubShebang,
@Nullable Artifact bootstrapTemplate) {
Preconditions.checkArgument((interpreterPath == null) != (interpreter == null));
Preconditions.checkArgument((interpreter == null) == (files == null));
Preconditions.checkArgument((coverageTool == null) == (coverageFiles == null));
Preconditions.checkArgument(pythonVersion.isTargetValue());
this.location = location != null ? location : Location.BUILTIN;
this.files = files;
this.interpreterPath = interpreterPath;
this.interpreter = interpreter;
this.coverageTool = coverageTool;
this.coverageFiles = coverageFiles;
this.pythonVersion = pythonVersion;
if (stubShebang != null && !stubShebang.isEmpty()) {
this.stubShebang = stubShebang;
} else {
this.stubShebang = PyRuntimeInfoApi.DEFAULT_STUB_SHEBANG;
}
this.bootstrapTemplate = bootstrapTemplate;
}

@Override
public PyRuntimeInfoProvider getProvider() {
return PROVIDER;
}

@Override
public Location getCreationLocation() {
return location;
}

/** Constructs an instance from native rule logic (built-in location) for an in-build runtime. */
public static PyRuntimeInfo createForInBuildRuntime(
Artifact interpreter,
NestedSet<Artifact> files,
@Nullable Artifact coverageTool,
@Nullable NestedSet<Artifact> coverageFiles,
PythonVersion pythonVersion,
@Nullable String stubShebang,
@Nullable Artifact bootstrapTemplate) {
return new PyRuntimeInfo(
/* location= */ null,
/* interpreterPath= */ null,
interpreter,
Depset.of(Artifact.class, files),
coverageTool,
coverageFiles == null ? null : Depset.of(Artifact.class, coverageFiles),
pythonVersion,
stubShebang,
bootstrapTemplate);
}

/** Constructs an instance from native rule logic (built-in location) for a platform runtime. */
public static PyRuntimeInfo createForPlatformRuntime(
PathFragment interpreterPath,
@Nullable Artifact coverageTool,
@Nullable NestedSet<Artifact> coverageFiles,
PythonVersion pythonVersion,
@Nullable String stubShebang,
@Nullable Artifact bootstrapTemplate) {
return new PyRuntimeInfo(
/* location= */ null,
interpreterPath,
/* interpreter= */ null,
/* files= */ null,
coverageTool,
coverageFiles == null ? null : Depset.of(Artifact.class, coverageFiles),
pythonVersion,
stubShebang,
bootstrapTemplate);
}

@Override
public boolean equals(Object other) {
// PyRuntimeInfo implements value equality, but note that it contains identity-equality fields
// (depsets), so you generally shouldn't rely on equality comparisons.
if (!(other instanceof PyRuntimeInfo)) {
return false;
}
PyRuntimeInfo otherInfo = (PyRuntimeInfo) other;
return (this.interpreterPath.equals(otherInfo.interpreterPath)
&& this.interpreter.equals(otherInfo.interpreter)
&& this.files.equals(otherInfo.files)
&& this.coverageTool.equals(otherInfo.coverageTool)
&& this.coverageFiles.equals(otherInfo.coverageFiles)
&& this.stubShebang.equals(otherInfo.stubShebang));
}
// Only present so PyRuntimeRule can reference it as a default.
static final String DEFAULT_STUB_SHEBANG = "#!/usr/bin/env python3";

@Override
public int hashCode() {
return Objects.hash(
PyRuntimeInfo.class,
interpreterPath,
interpreter,
coverageTool,
coverageFiles,
files,
stubShebang);
}
// Only present so PyRuntimeRule can reference it as a default.
// Must call getToolsLabel() when using this.
static final String DEFAULT_BOOTSTRAP_TEMPLATE = "//tools/python:python_bootstrap_template.txt";

/**
* Returns true if this is an in-build runtime as opposed to a platform runtime -- that is, if
* this refers to a target within the build as opposed to a path to a system interpreter.
*
* <p>{@link #getInterpreter} and {@link #getFiles} are non-null if and only if this is an
* in-build runtime, whereas {@link #getInterpreterPath} is non-null if and only if this is a
* platform runtime.
*
* <p>Note: It is still possible for an in-build runtime to reference the system interpreter, as
* in the case where it is a wrapper script.
*/
public boolean isInBuild() {
return getInterpreter() != null;
}
private final StarlarkInfo info;

@Nullable
public PathFragment getInterpreterPath() {
return interpreterPath;
private PyRuntimeInfo(StarlarkInfo info) {
this.info = info;
}

@Override
@Nullable
public String getInterpreterPathString() {
return interpreterPath == null ? null : interpreterPath.getPathString();
Object value = info.getValue("interpreter_path");
return value == Starlark.NONE ? null : (String) value;
}

@Override
@Nullable
public Artifact getInterpreter() {
return interpreter;
Object value = info.getValue("interpreter");
return value == Starlark.NONE ? null : (Artifact) value;
}

@Override
public String getStubShebang() {
return stubShebang;
public String getStubShebang() throws EvalException {
return info.getValue("stub_shebang", String.class);
}

@Override
@Nullable
public Artifact getBootstrapTemplate() {
return bootstrapTemplate;
Object value = info.getValue("bootstrap_template");
return value == Starlark.NONE ? null : (Artifact) value;
}

@Nullable
public NestedSet<Artifact> getFiles() {
try {
return files == null ? null : files.getSet(Artifact.class);
} catch (Depset.TypeException ex) {
throw new IllegalStateException("for files, " + ex.getMessage());
public NestedSet<Artifact> getFiles() throws EvalException {
Object value = info.getValue("files");
if (value == NONE) {
return null;
} else {
return Depset.cast(value, Artifact.class, "files");
}
}

@Override
@Nullable
public Depset getFilesForStarlark() {
return files;
public Artifact getCoverageTool() throws EvalException {
return info.getValue("coverage_tool", Artifact.class);
}

@Override
@Nullable
public Artifact getCoverageTool() {
return coverageTool;
public NestedSet<Artifact> getCoverageToolFiles() throws EvalException {
Object value = info.getValue("coverage_files");
return Depset.cast(value, Artifact.class, "coverage_files");
}

@Nullable
public NestedSet<Artifact> getCoverageToolFiles() {
try {
return coverageFiles == null ? null : coverageFiles.getSet(Artifact.class);
} catch (Depset.TypeException ex) {
throw new IllegalStateException("for coverage_runfiles, " + ex.getMessage());
}
}

@Override
@Nullable
public Depset getCoverageToolFilesForStarlark() {
return coverageFiles;
}

public PythonVersion getPythonVersion() {
return pythonVersion;
}

@Override
public String getPythonVersionForStarlark() {
return pythonVersion.name();
public PythonVersion getPythonVersion() throws EvalException {
return PythonVersion.parseTargetValue(info.getValue("python_version", String.class));
}

/** The class of the {@code PyRuntimeInfo} provider type. */
public static class PyRuntimeInfoProvider extends BuiltinProvider<PyRuntimeInfo>
implements PyRuntimeInfoApi.PyRuntimeInfoProviderApi {
public static class PyRuntimeInfoProvider extends StarlarkProviderWrapper<PyRuntimeInfo> {

private PyRuntimeInfoProvider() {
super(STARLARK_NAME, PyRuntimeInfo.class);
super(
Label.parseCanonicalUnchecked("@_builtins//:common/python/providers.bzl"),
"PyRuntimeInfo");
}

@Override
public PyRuntimeInfo constructor(
Object interpreterPathUncast,
Object interpreterUncast,
Object filesUncast,
Object coverageToolUncast,
Object coverageFilesUncast,
String pythonVersion,
String stubShebang,
Object bootstrapTemplateUncast,
StarlarkThread thread)
throws EvalException {
String interpreterPath =
interpreterPathUncast == NONE ? null : (String) interpreterPathUncast;
Artifact interpreter = interpreterUncast == NONE ? null : (Artifact) interpreterUncast;
Artifact bootstrapTemplate = null;
if (bootstrapTemplateUncast != NONE) {
bootstrapTemplate = (Artifact) bootstrapTemplateUncast;
}
Depset filesDepset = null;
if (filesUncast != NONE) {
// Validate type of filesDepset.
Depset.cast(filesUncast, Artifact.class, "files");
filesDepset = (Depset) filesUncast;
}
Artifact coverageTool = coverageToolUncast == NONE ? null : (Artifact) coverageToolUncast;
Depset coverageDepset = null;
if (coverageFilesUncast != NONE) {
// Validate type of filesDepset.
Depset.cast(coverageFilesUncast, Artifact.class, "coverage_files");
coverageDepset = (Depset) coverageFilesUncast;
}

if ((interpreter == null) == (interpreterPath == null)) {
throw Starlark.errorf(
"exactly one of the 'interpreter' or 'interpreter_path' arguments must be specified");
}
boolean isInBuildRuntime = interpreter != null;
if (!isInBuildRuntime && filesDepset != null) {
throw Starlark.errorf("cannot specify 'files' if 'interpreter_path' is given");
}

PythonVersion parsedPythonVersion;
try {
parsedPythonVersion = PythonVersion.parseTargetValue(pythonVersion);
} catch (IllegalArgumentException ex) {
throw Starlark.errorf("illegal value for 'python_version': %s", ex.getMessage());
}

Location loc = thread.getCallerLocation();
if (isInBuildRuntime) {
if (filesDepset == null) {
filesDepset = Depset.of(Artifact.class, NestedSetBuilder.emptySet(Order.STABLE_ORDER));
}
return new PyRuntimeInfo(
loc,
/* interpreterPath= */ null,
interpreter,
filesDepset,
coverageTool,
coverageDepset,
parsedPythonVersion,
stubShebang,
bootstrapTemplate);
} else {
return new PyRuntimeInfo(
loc,
PathFragment.create(interpreterPath),
/* interpreter= */ null,
/* files= */ null,
coverageTool,
coverageDepset,
parsedPythonVersion,
stubShebang,
bootstrapTemplate);
}
public PyRuntimeInfo wrap(Info value) {
return new PyRuntimeInfo((StarlarkInfo) value);
}
}
}
Loading

0 comments on commit a9cdc3b

Please sign in to comment.