blob: 77bb5844e147857d2e2eb0bb59a6fcc4176e7375 [file] [log] [blame]
// Copyright 2014 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.common.base.Verify;
import com.google.devtools.build.docgen.annot.DocCategory;
import com.google.devtools.build.lib.analysis.config.BuildOptions;
import com.google.devtools.build.lib.analysis.config.Fragment;
import com.google.devtools.build.lib.analysis.config.RequiresOptions;
import com.google.devtools.build.lib.analysis.starlark.annotations.StarlarkConfigurationField;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.util.OS;
import com.google.devtools.common.options.TriState;
import javax.annotation.Nullable;
import net.starlark.java.annot.StarlarkBuiltin;
import net.starlark.java.annot.StarlarkMethod;
import net.starlark.java.eval.StarlarkValue;
/**
* The configuration fragment containing information about the various pieces of infrastructure
* needed to run Python compilations.
*/
@Immutable
@StarlarkBuiltin(
name = "py",
doc = "A configuration fragment for Python.",
category = DocCategory.CONFIGURATION_FRAGMENT)
@RequiresOptions(options = {PythonOptions.class})
public class PythonConfiguration extends Fragment implements StarlarkValue {
private final PythonVersion version;
private final PythonVersion defaultVersion;
private final TriState buildPythonZip;
// TODO(brandjon): Remove this once migration to PY3-as-default is complete.
private final boolean py2OutputsAreSuffixed;
// TODO(brandjon): Remove this once migration to Python toolchains is complete.
private final boolean useToolchains;
private final boolean defaultToExplicitInitPy;
private final boolean disablePy2;
@Nullable private final Label nativeRulesAllowlist;
private final boolean disallowNativeRules;
public PythonConfiguration(BuildOptions buildOptions) {
PythonOptions pythonOptions = buildOptions.get(PythonOptions.class);
PythonVersion pythonVersion = pythonOptions.getPythonVersion();
this.version = pythonVersion;
this.defaultVersion = pythonOptions.getDefaultPythonVersion();
this.buildPythonZip = pythonOptions.buildPythonZip;
this.py2OutputsAreSuffixed = pythonOptions.incompatiblePy2OutputsAreSuffixed;
this.useToolchains = pythonOptions.incompatibleUsePythonToolchains;
this.defaultToExplicitInitPy = pythonOptions.incompatibleDefaultToExplicitInitPy;
this.disablePy2 = pythonOptions.disablePy2;
this.nativeRulesAllowlist = pythonOptions.nativeRulesAllowlist;
this.disallowNativeRules = pythonOptions.disallowNativeRules;
}
@Override
public boolean isImmutable() {
return true; // immutable and Starlark-hashable
}
/**
* Returns the Python version to use.
*
* <p>Specified using either the {@code --python_version} flag and {@code python_version} rule
* attribute (new API), or the {@code default_python_version} rule attribute (old API).
*/
public PythonVersion getPythonVersion() {
return version;
}
/**
* Returns the default Python version to use on targets that omit their {@code python_version}
* attribute.
*
* <p>Specified using {@code --incompatible_py3_is_default}. Long-term, the default will simply be
* hardcoded as {@code PY3}.
*
* <p>This information is stored on the configuration for the benefit of callers in rule analysis.
* However, transitions have access to the option fragment instead of the configuration fragment,
* and should rely on {@link PythonOptions#getDefaultPythonVersion} instead.
*/
public PythonVersion getDefaultPythonVersion() {
return defaultVersion;
}
@StarlarkMethod(
name = "default_python_version",
structField = true,
doc = "The default python version from --incompatible_py3_is_default")
public String getDefaultPythonVersionForStarlark() {
return defaultVersion.name();
}
@Override
public void processForOutputPathMnemonic(Fragment.OutputDirectoriesContext ctx)
throws Fragment.OutputDirectoriesContext.AddToMnemonicException {
Preconditions.checkState(version.isTargetValue());
// The only possible Python target version values are PY2 and PY3. Historically, PY3 targets got
// a "-py3" suffix and PY2 targets got the empty suffix, so that the bazel-bin symlink pointed
// to Python 2 targets. When --incompatible_py2_outputs_are_suffixed is enabled, this is
// reversed: PY2 targets get "-py2" and PY3 targets get the empty suffix.
Verify.verify(
PythonVersion.TARGET_VALUES.size() == 2, // If there is only 1, we don't need this method.
"Detected a change in PythonVersion.TARGET_VALUES so that there are no longer two Python "
+ "versions. Please check that PythonConfiguration#getOutputDirectoryName() is still "
+ "needed and is still able to avoid output directory clashes, then update this "
+ "canary message.");
ctx.markAsExplicitInOutputPathFor("python_version");
if (py2OutputsAreSuffixed) {
if (version == PythonVersion.PY2) {
ctx.addToMnemonic("py2");
}
} else {
if (version == PythonVersion.PY3) {
ctx.addToMnemonic("py3");
}
}
}
/** Returns whether to build the executable zip file for Python binaries. */
@StarlarkMethod(
name = "build_python_zip",
structField = true,
doc = "The effective value of --build_python_zip")
public boolean buildPythonZip() {
switch (buildPythonZip) {
case YES:
return true;
case NO:
return false;
default:
return OS.getCurrent() == OS.WINDOWS;
}
}
/**
* Returns true if executable Python rules should obtain their runtime from the Python toolchain
* rather than legacy flags.
*/
@StarlarkMethod(
name = "use_toolchains",
structField = true,
doc = "The value from the --incompatible_use_python_toolchains flag")
public boolean useToolchains() {
return useToolchains;
}
@StarlarkMethod(
name = "default_to_explicit_init_py",
structField = true,
doc = "The value from the --incompatible_default_to_explicit_init_py flag")
/**
* Returns true if executable Python rules should only write out empty __init__ files to their
* runfiles tree when explicitly requested via {@code legacy_create_init}.
*/
public boolean defaultToExplicitInitPy() {
return defaultToExplicitInitPy;
}
@StarlarkMethod(
name = "disable_py2",
structField = true,
doc = "The value of the --incompatible_python_disable_py2 flag.")
public boolean getDisablePy2() {
return disablePy2;
}
@StarlarkMethod(
name = "disallow_native_rules",
structField = true,
doc = "The value of the --incompatible_python_disallow_native_rules flag.")
public boolean getDisallowNativeRules() {
return disallowNativeRules;
}
@StarlarkConfigurationField(
name = "native_rules_allowlist",
doc = "The value of --python_native_rules_allowlist; may be None if not specified")
@Nullable
public Label getNativeRulesAllowlist() {
return nativeRulesAllowlist;
}
}