blob: 0d8541db34bad18455e3706fd27cd2fe57b7475a [file] [log] [blame]
// 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.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.analysis.FileProvider;
import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder;
import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
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.StructImpl;
import com.google.devtools.build.lib.rules.cpp.CppFileTypes;
import com.google.devtools.build.lib.syntax.EvalException;
import com.google.devtools.build.lib.syntax.SkylarkType;
import com.google.devtools.build.lib.util.FileType;
/**
* Static helper class for creating and accessing Python provider information.
*
* <p>This class exposes a unified view over both the legacy and modern Python providers.
*/
public class PyProviderUtils {
// Disable construction.
private PyProviderUtils() {}
/** Returns whether a given target has the {@code PyInfo} provider. */
public static boolean hasModernProvider(TransitiveInfoCollection target) {
return target.get(PyInfo.PROVIDER) != null;
}
/**
* Returns the {@code PyInfo} provider from the given target info, or null if the provider is not
* present.
*/
public static PyInfo getModernProvider(TransitiveInfoCollection target) {
return target.get(PyInfo.PROVIDER);
}
/** Returns whether a given target has the legacy "py" provider. */
public static boolean hasLegacyProvider(TransitiveInfoCollection target) {
return target.get(PyStructUtils.PROVIDER_NAME) != null;
}
/**
* Returns the struct representing the legacy "py" provider, from the given target info.
*
* @throws EvalException if the provider does not exist or has the wrong type.
*/
public static StructImpl getLegacyProvider(TransitiveInfoCollection target) throws EvalException {
Object info = target.get(PyStructUtils.PROVIDER_NAME);
if (info == null) {
throw new EvalException(/*location=*/ null, "Target does not have 'py' provider");
}
return SkylarkType.cast(
info,
StructImpl.class,
null,
"'%s' provider should be a struct",
PyStructUtils.PROVIDER_NAME);
}
/**
* Returns the transitive sources of a given target.
*
* <p>If the target has a py provider, the value from that provider is used. Otherwise, we fall
* back on collecting .py source files from the target's filesToBuild.
*
* @throws EvalException if the legacy struct provider is present but malformed
*/
// TODO(bazel-team): Eliminate the fallback behavior by returning an appropriate py provider from
// the relevant rules.
public static NestedSet<Artifact> getTransitiveSources(TransitiveInfoCollection target)
throws EvalException {
if (hasModernProvider(target)) {
return getModernProvider(target).getTransitiveSourcesSet();
} else if (hasLegacyProvider(target)) {
return PyStructUtils.getTransitiveSources(getLegacyProvider(target));
} else {
NestedSet<Artifact> files = target.getProvider(FileProvider.class).getFilesToBuild();
return NestedSetBuilder.<Artifact>compileOrder()
.addAll(FileType.filter(files.toList(), PyRuleClasses.PYTHON_SOURCE))
.build();
}
}
/**
* Returns whether a target uses shared libraries.
*
* <p>If the target has a py provider, the value from that provider is used. Otherwise, we fall
* back on checking whether the target's filesToBuild contains a shared library file type (e.g., a
* .so file).
*
* @throws EvalException if the legacy struct provider is present but malformed
*/
public static boolean getUsesSharedLibraries(TransitiveInfoCollection target)
throws EvalException {
if (hasModernProvider(target)) {
return getModernProvider(target).getUsesSharedLibraries();
} else if (hasLegacyProvider(target)) {
return PyStructUtils.getUsesSharedLibraries(getLegacyProvider(target));
} else {
NestedSet<Artifact> files = target.getProvider(FileProvider.class).getFilesToBuild();
return FileType.contains(files.toList(), CppFileTypes.SHARED_LIBRARY);
}
}
/**
* Returns the transitive import paths of a target.
*
* <p>If the target has a py provider, the value from that provider is used. Otherwise, we default
* to an empty set.
*
* @throws EvalException if the legacy struct provider is present but malformed
*/
public static NestedSet<String> getImports(TransitiveInfoCollection target) throws EvalException {
if (hasModernProvider(target)) {
return getModernProvider(target).getImportsSet();
} else if (hasLegacyProvider(target)) {
return PyStructUtils.getImports(getLegacyProvider(target));
} else {
return NestedSetBuilder.emptySet(Order.COMPILE_ORDER);
}
}
/**
* Returns whether the target has transitive sources requiring Python 2.
*
* <p>If the target has a py provider, the value from that provider is used. Otherwise, we default
* to false.
*/
public static boolean getHasPy2OnlySources(TransitiveInfoCollection target) throws EvalException {
if (hasModernProvider(target)) {
return getModernProvider(target).getHasPy2OnlySources();
} else if (hasLegacyProvider(target)) {
return PyStructUtils.getHasPy2OnlySources(getLegacyProvider(target));
} else {
return false;
}
}
/**
* Returns whether the target has transitive sources requiring Python 3.
*
* <p>If the target has a py provider, the value from that provider is used. Otherwise, we default
* to false.
*/
public static boolean getHasPy3OnlySources(TransitiveInfoCollection target) throws EvalException {
if (hasModernProvider(target)) {
return getModernProvider(target).getHasPy3OnlySources();
} else if (hasLegacyProvider(target)) {
return PyStructUtils.getHasPy3OnlySources(getLegacyProvider(target));
} else {
return false;
}
}
/**
* Returns a builder to construct the legacy and/or modern Python providers and add them to a
* configured target.
*
* <p>If {@code createLegacy} is false, only the modern {@code PyInfo} provider is produced.
* Otherwise both {@code PyInfo} and the "py" provider are produced.
*/
public static Builder builder(boolean createLegacy) {
return new Builder(createLegacy);
}
/** A builder to add both the legacy and modern providers to a configured target. */
public static class Builder {
private final PyInfo.Builder modernBuilder = PyInfo.builder();
private final PyStructUtils.Builder legacyBuilder = PyStructUtils.builder();
private final boolean createLegacy;
// Use the static builder() method instead.
private Builder(boolean createLegacy) {
this.createLegacy = createLegacy;
}
public Builder setTransitiveSources(NestedSet<Artifact> transitiveSources) {
modernBuilder.setTransitiveSources(transitiveSources);
legacyBuilder.setTransitiveSources(transitiveSources);
return this;
}
public Builder setUsesSharedLibraries(boolean usesSharedLibraries) {
modernBuilder.setUsesSharedLibraries(usesSharedLibraries);
legacyBuilder.setUsesSharedLibraries(usesSharedLibraries);
return this;
}
public Builder setImports(NestedSet<String> imports) {
modernBuilder.setImports(imports);
legacyBuilder.setImports(imports);
return this;
}
public Builder setHasPy2OnlySources(boolean hasPy2OnlySources) {
modernBuilder.setHasPy2OnlySources(hasPy2OnlySources);
legacyBuilder.setHasPy2OnlySources(hasPy2OnlySources);
return this;
}
public Builder setHasPy3OnlySources(boolean hasPy3OnlySources) {
modernBuilder.setHasPy3OnlySources(hasPy3OnlySources);
legacyBuilder.setHasPy3OnlySources(hasPy3OnlySources);
return this;
}
public RuleConfiguredTargetBuilder buildAndAddToTarget(
RuleConfiguredTargetBuilder targetBuilder) {
targetBuilder.addNativeDeclaredProvider(modernBuilder.build());
if (createLegacy) {
targetBuilder.addSkylarkTransitiveInfo(PyStructUtils.PROVIDER_NAME, legacyBuilder.build());
}
return targetBuilder;
}
}
}