Skip to content

Commit a9cdc3b

Browse files
rickeylevfweikert
authored andcommitted
python: port PyRuntimeInfo to Starlark and enable it.
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
1 parent a37382e commit a9cdc3b

File tree

8 files changed

+205
-433
lines changed

8 files changed

+205
-433
lines changed

src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java

-2
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,6 @@
115115
import com.google.devtools.build.lib.rules.python.PyCcLinkParamsProvider;
116116
import com.google.devtools.build.lib.rules.python.PyInfo;
117117
import com.google.devtools.build.lib.rules.python.PyRuleClasses.PySymlink;
118-
import com.google.devtools.build.lib.rules.python.PyRuntimeInfo;
119118
import com.google.devtools.build.lib.rules.python.PyRuntimeRule;
120119
import com.google.devtools.build.lib.rules.python.PyStarlarkTransitions;
121120
import com.google.devtools.build.lib.rules.python.PythonConfiguration;
@@ -475,7 +474,6 @@ public void init(ConfiguredRuleClassProvider.Builder builder) {
475474
builder.addStarlarkBootstrap(
476475
new PyBootstrap(
477476
PyInfo.PROVIDER,
478-
PyRuntimeInfo.PROVIDER,
479477
PyStarlarkTransitions.INSTANCE,
480478
new GoogleLegacyStubs.PyWrapCcHelper(),
481479
new GoogleLegacyStubs.PyWrapCcInfoProvider(),

src/main/java/com/google/devtools/build/lib/rules/python/PyRuntimeInfo.java

+41-269
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,18 @@
1616

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

19-
import com.google.common.base.Preconditions;
19+
import com.google.common.annotations.VisibleForTesting;
2020
import com.google.devtools.build.lib.actions.Artifact;
21+
import com.google.devtools.build.lib.cmdline.Label;
2122
import com.google.devtools.build.lib.collect.nestedset.Depset;
2223
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
23-
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
24-
import com.google.devtools.build.lib.collect.nestedset.Order;
25-
import com.google.devtools.build.lib.packages.BuiltinProvider;
2624
import com.google.devtools.build.lib.packages.Info;
25+
import com.google.devtools.build.lib.packages.StarlarkInfo;
26+
import com.google.devtools.build.lib.packages.StarlarkProviderWrapper;
2727
import com.google.devtools.build.lib.starlarkbuildapi.python.PyRuntimeInfoApi;
28-
import com.google.devtools.build.lib.vfs.PathFragment;
29-
import java.util.Objects;
3028
import javax.annotation.Nullable;
3129
import net.starlark.java.eval.EvalException;
3230
import net.starlark.java.eval.Starlark;
33-
import net.starlark.java.eval.StarlarkThread;
34-
import net.starlark.java.syntax.Location;
3531

3632
/**
3733
* Instance of the provider type that describes Python runtimes.
@@ -43,307 +39,83 @@
4339
* invariants mirror the user-visible API on {@link PyRuntimeInfoApi} except that {@code None} is
4440
* replaced by null.
4541
*/
46-
public final class PyRuntimeInfo implements Info, PyRuntimeInfoApi<Artifact> {
47-
48-
/** The Starlark-accessible top-level builtin name for this provider type. */
49-
public static final String STARLARK_NAME = "PyRuntimeInfo";
50-
42+
@VisibleForTesting
43+
public final class PyRuntimeInfo {
5144
/** The singular {@code PyRuntimeInfo} provider type object. */
5245
public static final PyRuntimeInfoProvider PROVIDER = new PyRuntimeInfoProvider();
5346

54-
private final Location location;
55-
@Nullable private final PathFragment interpreterPath;
56-
@Nullable private final Artifact interpreter;
57-
// Validated on initialization to contain Artifact
58-
@Nullable private final Depset files;
59-
@Nullable private final Artifact coverageTool;
60-
@Nullable private final Depset coverageFiles;
61-
/** Invariant: either PY2 or PY3. */
62-
private final PythonVersion pythonVersion;
63-
64-
private final String stubShebang;
65-
@Nullable private final Artifact bootstrapTemplate;
66-
67-
private PyRuntimeInfo(
68-
@Nullable Location location,
69-
@Nullable PathFragment interpreterPath,
70-
@Nullable Artifact interpreter,
71-
@Nullable Depset files,
72-
@Nullable Artifact coverageTool,
73-
@Nullable Depset coverageFiles,
74-
PythonVersion pythonVersion,
75-
@Nullable String stubShebang,
76-
@Nullable Artifact bootstrapTemplate) {
77-
Preconditions.checkArgument((interpreterPath == null) != (interpreter == null));
78-
Preconditions.checkArgument((interpreter == null) == (files == null));
79-
Preconditions.checkArgument((coverageTool == null) == (coverageFiles == null));
80-
Preconditions.checkArgument(pythonVersion.isTargetValue());
81-
this.location = location != null ? location : Location.BUILTIN;
82-
this.files = files;
83-
this.interpreterPath = interpreterPath;
84-
this.interpreter = interpreter;
85-
this.coverageTool = coverageTool;
86-
this.coverageFiles = coverageFiles;
87-
this.pythonVersion = pythonVersion;
88-
if (stubShebang != null && !stubShebang.isEmpty()) {
89-
this.stubShebang = stubShebang;
90-
} else {
91-
this.stubShebang = PyRuntimeInfoApi.DEFAULT_STUB_SHEBANG;
92-
}
93-
this.bootstrapTemplate = bootstrapTemplate;
94-
}
95-
96-
@Override
97-
public PyRuntimeInfoProvider getProvider() {
98-
return PROVIDER;
99-
}
100-
101-
@Override
102-
public Location getCreationLocation() {
103-
return location;
104-
}
105-
106-
/** Constructs an instance from native rule logic (built-in location) for an in-build runtime. */
107-
public static PyRuntimeInfo createForInBuildRuntime(
108-
Artifact interpreter,
109-
NestedSet<Artifact> files,
110-
@Nullable Artifact coverageTool,
111-
@Nullable NestedSet<Artifact> coverageFiles,
112-
PythonVersion pythonVersion,
113-
@Nullable String stubShebang,
114-
@Nullable Artifact bootstrapTemplate) {
115-
return new PyRuntimeInfo(
116-
/* location= */ null,
117-
/* interpreterPath= */ null,
118-
interpreter,
119-
Depset.of(Artifact.class, files),
120-
coverageTool,
121-
coverageFiles == null ? null : Depset.of(Artifact.class, coverageFiles),
122-
pythonVersion,
123-
stubShebang,
124-
bootstrapTemplate);
125-
}
126-
127-
/** Constructs an instance from native rule logic (built-in location) for a platform runtime. */
128-
public static PyRuntimeInfo createForPlatformRuntime(
129-
PathFragment interpreterPath,
130-
@Nullable Artifact coverageTool,
131-
@Nullable NestedSet<Artifact> coverageFiles,
132-
PythonVersion pythonVersion,
133-
@Nullable String stubShebang,
134-
@Nullable Artifact bootstrapTemplate) {
135-
return new PyRuntimeInfo(
136-
/* location= */ null,
137-
interpreterPath,
138-
/* interpreter= */ null,
139-
/* files= */ null,
140-
coverageTool,
141-
coverageFiles == null ? null : Depset.of(Artifact.class, coverageFiles),
142-
pythonVersion,
143-
stubShebang,
144-
bootstrapTemplate);
145-
}
146-
147-
@Override
148-
public boolean equals(Object other) {
149-
// PyRuntimeInfo implements value equality, but note that it contains identity-equality fields
150-
// (depsets), so you generally shouldn't rely on equality comparisons.
151-
if (!(other instanceof PyRuntimeInfo)) {
152-
return false;
153-
}
154-
PyRuntimeInfo otherInfo = (PyRuntimeInfo) other;
155-
return (this.interpreterPath.equals(otherInfo.interpreterPath)
156-
&& this.interpreter.equals(otherInfo.interpreter)
157-
&& this.files.equals(otherInfo.files)
158-
&& this.coverageTool.equals(otherInfo.coverageTool)
159-
&& this.coverageFiles.equals(otherInfo.coverageFiles)
160-
&& this.stubShebang.equals(otherInfo.stubShebang));
161-
}
47+
// Only present so PyRuntimeRule can reference it as a default.
48+
static final String DEFAULT_STUB_SHEBANG = "#!/usr/bin/env python3";
16249

163-
@Override
164-
public int hashCode() {
165-
return Objects.hash(
166-
PyRuntimeInfo.class,
167-
interpreterPath,
168-
interpreter,
169-
coverageTool,
170-
coverageFiles,
171-
files,
172-
stubShebang);
173-
}
50+
// Only present so PyRuntimeRule can reference it as a default.
51+
// Must call getToolsLabel() when using this.
52+
static final String DEFAULT_BOOTSTRAP_TEMPLATE = "//tools/python:python_bootstrap_template.txt";
17453

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

190-
@Nullable
191-
public PathFragment getInterpreterPath() {
192-
return interpreterPath;
56+
private PyRuntimeInfo(StarlarkInfo info) {
57+
this.info = info;
19358
}
19459

195-
@Override
19660
@Nullable
19761
public String getInterpreterPathString() {
198-
return interpreterPath == null ? null : interpreterPath.getPathString();
62+
Object value = info.getValue("interpreter_path");
63+
return value == Starlark.NONE ? null : (String) value;
19964
}
20065

201-
@Override
20266
@Nullable
20367
public Artifact getInterpreter() {
204-
return interpreter;
68+
Object value = info.getValue("interpreter");
69+
return value == Starlark.NONE ? null : (Artifact) value;
20570
}
20671

207-
@Override
208-
public String getStubShebang() {
209-
return stubShebang;
72+
public String getStubShebang() throws EvalException {
73+
return info.getValue("stub_shebang", String.class);
21074
}
21175

212-
@Override
21376
@Nullable
21477
public Artifact getBootstrapTemplate() {
215-
return bootstrapTemplate;
78+
Object value = info.getValue("bootstrap_template");
79+
return value == Starlark.NONE ? null : (Artifact) value;
21680
}
21781

21882
@Nullable
219-
public NestedSet<Artifact> getFiles() {
220-
try {
221-
return files == null ? null : files.getSet(Artifact.class);
222-
} catch (Depset.TypeException ex) {
223-
throw new IllegalStateException("for files, " + ex.getMessage());
83+
public NestedSet<Artifact> getFiles() throws EvalException {
84+
Object value = info.getValue("files");
85+
if (value == NONE) {
86+
return null;
87+
} else {
88+
return Depset.cast(value, Artifact.class, "files");
22489
}
22590
}
22691

227-
@Override
22892
@Nullable
229-
public Depset getFilesForStarlark() {
230-
return files;
93+
public Artifact getCoverageTool() throws EvalException {
94+
return info.getValue("coverage_tool", Artifact.class);
23195
}
23296

233-
@Override
23497
@Nullable
235-
public Artifact getCoverageTool() {
236-
return coverageTool;
98+
public NestedSet<Artifact> getCoverageToolFiles() throws EvalException {
99+
Object value = info.getValue("coverage_files");
100+
return Depset.cast(value, Artifact.class, "coverage_files");
237101
}
238102

239-
@Nullable
240-
public NestedSet<Artifact> getCoverageToolFiles() {
241-
try {
242-
return coverageFiles == null ? null : coverageFiles.getSet(Artifact.class);
243-
} catch (Depset.TypeException ex) {
244-
throw new IllegalStateException("for coverage_runfiles, " + ex.getMessage());
245-
}
246-
}
247-
248-
@Override
249-
@Nullable
250-
public Depset getCoverageToolFilesForStarlark() {
251-
return coverageFiles;
252-
}
253-
254-
public PythonVersion getPythonVersion() {
255-
return pythonVersion;
256-
}
257-
258-
@Override
259-
public String getPythonVersionForStarlark() {
260-
return pythonVersion.name();
103+
public PythonVersion getPythonVersion() throws EvalException {
104+
return PythonVersion.parseTargetValue(info.getValue("python_version", String.class));
261105
}
262106

263107
/** The class of the {@code PyRuntimeInfo} provider type. */
264-
public static class PyRuntimeInfoProvider extends BuiltinProvider<PyRuntimeInfo>
265-
implements PyRuntimeInfoApi.PyRuntimeInfoProviderApi {
108+
public static class PyRuntimeInfoProvider extends StarlarkProviderWrapper<PyRuntimeInfo> {
266109

267110
private PyRuntimeInfoProvider() {
268-
super(STARLARK_NAME, PyRuntimeInfo.class);
111+
super(
112+
Label.parseCanonicalUnchecked("@_builtins//:common/python/providers.bzl"),
113+
"PyRuntimeInfo");
269114
}
270115

271116
@Override
272-
public PyRuntimeInfo constructor(
273-
Object interpreterPathUncast,
274-
Object interpreterUncast,
275-
Object filesUncast,
276-
Object coverageToolUncast,
277-
Object coverageFilesUncast,
278-
String pythonVersion,
279-
String stubShebang,
280-
Object bootstrapTemplateUncast,
281-
StarlarkThread thread)
282-
throws EvalException {
283-
String interpreterPath =
284-
interpreterPathUncast == NONE ? null : (String) interpreterPathUncast;
285-
Artifact interpreter = interpreterUncast == NONE ? null : (Artifact) interpreterUncast;
286-
Artifact bootstrapTemplate = null;
287-
if (bootstrapTemplateUncast != NONE) {
288-
bootstrapTemplate = (Artifact) bootstrapTemplateUncast;
289-
}
290-
Depset filesDepset = null;
291-
if (filesUncast != NONE) {
292-
// Validate type of filesDepset.
293-
Depset.cast(filesUncast, Artifact.class, "files");
294-
filesDepset = (Depset) filesUncast;
295-
}
296-
Artifact coverageTool = coverageToolUncast == NONE ? null : (Artifact) coverageToolUncast;
297-
Depset coverageDepset = null;
298-
if (coverageFilesUncast != NONE) {
299-
// Validate type of filesDepset.
300-
Depset.cast(coverageFilesUncast, Artifact.class, "coverage_files");
301-
coverageDepset = (Depset) coverageFilesUncast;
302-
}
303-
304-
if ((interpreter == null) == (interpreterPath == null)) {
305-
throw Starlark.errorf(
306-
"exactly one of the 'interpreter' or 'interpreter_path' arguments must be specified");
307-
}
308-
boolean isInBuildRuntime = interpreter != null;
309-
if (!isInBuildRuntime && filesDepset != null) {
310-
throw Starlark.errorf("cannot specify 'files' if 'interpreter_path' is given");
311-
}
312-
313-
PythonVersion parsedPythonVersion;
314-
try {
315-
parsedPythonVersion = PythonVersion.parseTargetValue(pythonVersion);
316-
} catch (IllegalArgumentException ex) {
317-
throw Starlark.errorf("illegal value for 'python_version': %s", ex.getMessage());
318-
}
319-
320-
Location loc = thread.getCallerLocation();
321-
if (isInBuildRuntime) {
322-
if (filesDepset == null) {
323-
filesDepset = Depset.of(Artifact.class, NestedSetBuilder.emptySet(Order.STABLE_ORDER));
324-
}
325-
return new PyRuntimeInfo(
326-
loc,
327-
/* interpreterPath= */ null,
328-
interpreter,
329-
filesDepset,
330-
coverageTool,
331-
coverageDepset,
332-
parsedPythonVersion,
333-
stubShebang,
334-
bootstrapTemplate);
335-
} else {
336-
return new PyRuntimeInfo(
337-
loc,
338-
PathFragment.create(interpreterPath),
339-
/* interpreter= */ null,
340-
/* files= */ null,
341-
coverageTool,
342-
coverageDepset,
343-
parsedPythonVersion,
344-
stubShebang,
345-
bootstrapTemplate);
346-
}
117+
public PyRuntimeInfo wrap(Info value) {
118+
return new PyRuntimeInfo((StarlarkInfo) value);
347119
}
348120
}
349121
}

0 commit comments

Comments
 (0)