/*
* Copyright 2014-present Facebook, Inc.
*
* 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.facebook.buck.python;
import com.facebook.buck.cxx.CxxBuckConfig;
import com.facebook.buck.cxx.CxxConstructorArg;
import com.facebook.buck.cxx.CxxDescriptionEnhancer;
import com.facebook.buck.cxx.CxxFlags;
import com.facebook.buck.cxx.CxxLinkableEnhancer;
import com.facebook.buck.cxx.CxxPlatform;
import com.facebook.buck.cxx.CxxPlatforms;
import com.facebook.buck.cxx.CxxPreprocessAndCompile;
import com.facebook.buck.cxx.CxxPreprocessables;
import com.facebook.buck.cxx.CxxPreprocessorInput;
import com.facebook.buck.cxx.CxxSource;
import com.facebook.buck.cxx.CxxSourceRuleFactory;
import com.facebook.buck.cxx.HeaderSymlinkTree;
import com.facebook.buck.cxx.HeaderVisibility;
import com.facebook.buck.cxx.Linker;
import com.facebook.buck.cxx.LinkerMapMode;
import com.facebook.buck.cxx.Linkers;
import com.facebook.buck.cxx.NativeLinkTarget;
import com.facebook.buck.cxx.NativeLinkTargetMode;
import com.facebook.buck.cxx.NativeLinkable;
import com.facebook.buck.cxx.NativeLinkableInput;
import com.facebook.buck.io.ProjectFilesystem;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.model.BuildTargets;
import com.facebook.buck.model.Flavor;
import com.facebook.buck.model.FlavorDomain;
import com.facebook.buck.parser.NoSuchBuildTargetException;
import com.facebook.buck.rules.BuildRule;
import com.facebook.buck.rules.BuildRuleParams;
import com.facebook.buck.rules.BuildRuleResolver;
import com.facebook.buck.rules.CellPathResolver;
import com.facebook.buck.rules.Description;
import com.facebook.buck.rules.ImplicitDepsInferringDescription;
import com.facebook.buck.rules.SourcePath;
import com.facebook.buck.rules.SourcePathResolver;
import com.facebook.buck.rules.SourcePathRuleFinder;
import com.facebook.buck.rules.SymlinkTree;
import com.facebook.buck.rules.TargetGraph;
import com.facebook.buck.rules.args.SourcePathArg;
import com.facebook.buck.rules.args.StringArg;
import com.facebook.buck.util.MoreCollectors;
import com.facebook.buck.util.OptionalCompat;
import com.facebook.buck.util.RichStream;
import com.facebook.buck.util.immutables.BuckStyleImmutable;
import com.facebook.buck.versions.VersionPropagator;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import java.nio.file.Path;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
import org.immutables.value.Value;
public class CxxPythonExtensionDescription
implements Description<CxxPythonExtensionDescriptionArg>,
ImplicitDepsInferringDescription<
CxxPythonExtensionDescription.AbstractCxxPythonExtensionDescriptionArg>,
VersionPropagator<CxxPythonExtensionDescriptionArg> {
private enum Type {
EXTENSION,
}
private static final FlavorDomain<Type> LIBRARY_TYPE =
new FlavorDomain<>(
"C/C++ Library Type",
ImmutableMap.of(CxxDescriptionEnhancer.SHARED_FLAVOR, Type.EXTENSION));
private final FlavorDomain<PythonPlatform> pythonPlatforms;
private final CxxBuckConfig cxxBuckConfig;
private final FlavorDomain<CxxPlatform> cxxPlatforms;
public CxxPythonExtensionDescription(
FlavorDomain<PythonPlatform> pythonPlatforms,
CxxBuckConfig cxxBuckConfig,
FlavorDomain<CxxPlatform> cxxPlatforms) {
this.pythonPlatforms = pythonPlatforms;
this.cxxBuckConfig = cxxBuckConfig;
this.cxxPlatforms = cxxPlatforms;
}
@Override
public Class<CxxPythonExtensionDescriptionArg> getConstructorArgType() {
return CxxPythonExtensionDescriptionArg.class;
}
@VisibleForTesting
static BuildTarget getExtensionTarget(
BuildTarget target, Flavor pythonPlatform, Flavor platform) {
return CxxDescriptionEnhancer.createSharedLibraryBuildTarget(
BuildTarget.builder(target).addFlavors(pythonPlatform).build(),
platform,
Linker.LinkType.SHARED);
}
@VisibleForTesting
static String getExtensionName(String moduleName) {
// .so is used on OS X too (as opposed to dylib).
return String.format("%s.so", moduleName);
}
private Path getExtensionPath(
ProjectFilesystem filesystem,
BuildTarget target,
String moduleName,
Flavor pythonPlatform,
Flavor platform) {
return BuildTargets.getGenPath(
filesystem, getExtensionTarget(target, pythonPlatform, platform), "%s")
.resolve(getExtensionName(moduleName));
}
private ImmutableList<com.facebook.buck.rules.args.Arg> getExtensionArgs(
BuildRuleParams params,
BuildRuleResolver ruleResolver,
SourcePathResolver pathResolver,
SourcePathRuleFinder ruleFinder,
CellPathResolver cellRoots,
CxxPlatform cxxPlatform,
CxxPythonExtensionDescriptionArg args,
ImmutableSet<BuildRule> deps)
throws NoSuchBuildTargetException {
// Extract all C/C++ sources from the constructor arg.
ImmutableMap<String, CxxSource> srcs =
CxxDescriptionEnhancer.parseCxxSources(
params.getBuildTarget(), ruleResolver, ruleFinder, pathResolver, cxxPlatform, args);
ImmutableMap<Path, SourcePath> headers =
CxxDescriptionEnhancer.parseHeaders(
params.getBuildTarget(),
ruleResolver,
ruleFinder,
pathResolver,
Optional.of(cxxPlatform),
args);
// Setup the header symlink tree and combine all the preprocessor input from this rule
// and all dependencies.
HeaderSymlinkTree headerSymlinkTree =
CxxDescriptionEnhancer.requireHeaderSymlinkTree(
params, ruleResolver, cxxPlatform, headers, HeaderVisibility.PRIVATE, true);
Optional<SymlinkTree> sandboxTree = Optional.empty();
if (cxxBuckConfig.sandboxSources()) {
sandboxTree = CxxDescriptionEnhancer.createSandboxTree(params, ruleResolver, cxxPlatform);
}
ImmutableList<CxxPreprocessorInput> cxxPreprocessorInput =
CxxDescriptionEnhancer.collectCxxPreprocessorInput(
params,
cxxPlatform,
deps,
CxxFlags.getLanguageFlags(
args.getPreprocessorFlags(),
args.getPlatformPreprocessorFlags(),
args.getLangPreprocessorFlags(),
cxxPlatform),
ImmutableList.of(headerSymlinkTree),
ImmutableSet.of(),
CxxPreprocessables.getTransitiveCxxPreprocessorInput(cxxPlatform, deps),
args.getIncludeDirs(),
sandboxTree);
// Generate rule to build the object files.
ImmutableMap<CxxPreprocessAndCompile, SourcePath> picObjects =
CxxSourceRuleFactory.requirePreprocessAndCompileRules(
params,
ruleResolver,
pathResolver,
ruleFinder,
cxxBuckConfig,
cxxPlatform,
cxxPreprocessorInput,
CxxFlags.getLanguageFlags(
args.getCompilerFlags(),
args.getPlatformCompilerFlags(),
args.getLangCompilerFlags(),
cxxPlatform),
args.getPrefixHeader(),
args.getPrecompiledHeader(),
srcs,
CxxSourceRuleFactory.PicType.PIC,
sandboxTree);
ImmutableList.Builder<com.facebook.buck.rules.args.Arg> argsBuilder = ImmutableList.builder();
argsBuilder.addAll(
CxxDescriptionEnhancer.toStringWithMacrosArgs(
params.getBuildTarget(),
cellRoots,
ruleResolver,
cxxPlatform,
CxxFlags.getFlagsWithMacrosWithPlatformMacroExpansion(
args.getLinkerFlags(), args.getPlatformLinkerFlags(), cxxPlatform)));
// Embed a origin-relative library path into the binary so it can find the shared libraries.
argsBuilder.addAll(
StringArg.from(
Linkers.iXlinker(
"-rpath",
String.format("%s/", cxxPlatform.getLd().resolve(ruleResolver).libOrigin()))));
// Add object files into the args.
argsBuilder.addAll(SourcePathArg.from(picObjects.values()));
return argsBuilder.build();
}
private ImmutableSet<BuildRule> getPlatformDeps(
BuildRuleResolver ruleResolver,
PythonPlatform pythonPlatform,
CxxPlatform cxxPlatform,
CxxPythonExtensionDescriptionArg args) {
ImmutableSet.Builder<BuildRule> rules = ImmutableSet.builder();
// Add declared deps.
rules.addAll(args.getCxxDeps().get(ruleResolver, cxxPlatform));
// Add platform specific deps.
rules.addAll(
ruleResolver.getAllRules(
Iterables.concat(
args.getPlatformDeps().getMatchingValues(pythonPlatform.getFlavor().toString()))));
// Add a dep on the python C/C++ library.
if (pythonPlatform.getCxxLibrary().isPresent()) {
rules.add(ruleResolver.getRule(pythonPlatform.getCxxLibrary().get()));
}
return rules.build();
}
private BuildRule createExtensionBuildRule(
BuildRuleParams params,
BuildRuleResolver ruleResolver,
CellPathResolver cellRoots,
PythonPlatform pythonPlatform,
CxxPlatform cxxPlatform,
CxxPythonExtensionDescriptionArg args)
throws NoSuchBuildTargetException {
SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(ruleResolver);
SourcePathResolver pathResolver = new SourcePathResolver(ruleFinder);
String moduleName = args.getModuleName().orElse(params.getBuildTarget().getShortName());
String extensionName = getExtensionName(moduleName);
Path extensionPath =
getExtensionPath(
params.getProjectFilesystem(),
params.getBuildTarget(),
moduleName,
pythonPlatform.getFlavor(),
cxxPlatform.getFlavor());
ImmutableSet<BuildRule> deps = getPlatformDeps(ruleResolver, pythonPlatform, cxxPlatform, args);
return CxxLinkableEnhancer.createCxxLinkableBuildRule(
cxxBuckConfig,
cxxPlatform,
params,
ruleResolver,
pathResolver,
ruleFinder,
getExtensionTarget(
params.getBuildTarget(), pythonPlatform.getFlavor(), cxxPlatform.getFlavor()),
Linker.LinkType.SHARED,
Optional.of(extensionName),
extensionPath,
Linker.LinkableDepType.SHARED,
/* thinLto */ false,
RichStream.from(deps).filter(NativeLinkable.class).toImmutableList(),
args.getCxxRuntimeType(),
Optional.empty(),
ImmutableSet.of(),
NativeLinkableInput.builder()
.setArgs(
getExtensionArgs(
params.withBuildTarget(
params
.getBuildTarget()
.withoutFlavors(LinkerMapMode.FLAVOR_DOMAIN.getFlavors())),
ruleResolver,
pathResolver,
ruleFinder,
cellRoots,
cxxPlatform,
args,
deps))
.setFrameworks(args.getFrameworks())
.setLibraries(args.getLibraries())
.build());
}
@Override
public BuildRule createBuildRule(
TargetGraph targetGraph,
final BuildRuleParams params,
final BuildRuleResolver ruleResolver,
CellPathResolver cellRoots,
final CxxPythonExtensionDescriptionArg args)
throws NoSuchBuildTargetException {
Optional<Map.Entry<Flavor, CxxPlatform>> platform =
cxxPlatforms.getFlavorAndValue(params.getBuildTarget());
if (params.getBuildTarget().getFlavors().contains(CxxDescriptionEnhancer.SANDBOX_TREE_FLAVOR)) {
return CxxDescriptionEnhancer.createSandboxTreeBuildRule(
ruleResolver, args, platform.get().getValue(), params);
}
// See if we're building a particular "type" of this library, and if so, extract
// it as an enum.
final Optional<Map.Entry<Flavor, Type>> type =
LIBRARY_TYPE.getFlavorAndValue(params.getBuildTarget());
final Optional<Map.Entry<Flavor, PythonPlatform>> pythonPlatform =
pythonPlatforms.getFlavorAndValue(params.getBuildTarget());
// If we *are* building a specific type of this lib, call into the type specific
// rule builder methods. Currently, we only support building a shared lib from the
// pre-existing static lib, which we do here.
if (type.isPresent() && platform.isPresent() && pythonPlatform.isPresent()) {
Preconditions.checkState(type.get().getValue() == Type.EXTENSION);
return createExtensionBuildRule(
params,
ruleResolver,
cellRoots,
pythonPlatform.get().getValue(),
platform.get().getValue(),
args);
}
// Otherwise, we return the generic placeholder of this library, that dependents can use
// get the real build rules via querying the action graph.
SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(ruleResolver);
final SourcePathResolver pathResolver = new SourcePathResolver(ruleFinder);
Path baseModule = PythonUtil.getBasePath(params.getBuildTarget(), args.getBaseModule());
String moduleName = args.getModuleName().orElse(params.getBuildTarget().getShortName());
final Path module = baseModule.resolve(getExtensionName(moduleName));
return new CxxPythonExtension(params) {
@Override
protected BuildRule getExtension(PythonPlatform pythonPlatform, CxxPlatform cxxPlatform)
throws NoSuchBuildTargetException {
return ruleResolver.requireRule(
getBuildTarget()
.withAppendedFlavors(
pythonPlatform.getFlavor(),
cxxPlatform.getFlavor(),
CxxDescriptionEnhancer.SHARED_FLAVOR));
}
@Override
public Path getModule() {
return module;
}
@Override
public Iterable<BuildRule> getPythonPackageDeps(
PythonPlatform pythonPlatform, CxxPlatform cxxPlatform) {
return PythonUtil.getDeps(
pythonPlatform, cxxPlatform, args.getDeps(), args.getPlatformDeps())
.stream()
.map(ruleResolver::getRule)
.filter(PythonPackagable.class::isInstance)
.collect(MoreCollectors.toImmutableList());
}
@Override
public PythonPackageComponents getPythonPackageComponents(
PythonPlatform pythonPlatform, CxxPlatform cxxPlatform)
throws NoSuchBuildTargetException {
BuildRule extension = getExtension(pythonPlatform, cxxPlatform);
SourcePath output = extension.getSourcePathToOutput();
return PythonPackageComponents.of(
ImmutableMap.of(module, output),
ImmutableMap.of(),
ImmutableMap.of(),
ImmutableSet.of(),
Optional.of(false));
}
@Override
public NativeLinkTarget getNativeLinkTarget(final PythonPlatform pythonPlatform) {
return new NativeLinkTarget() {
@Override
public BuildTarget getBuildTarget() {
return params.getBuildTarget().withAppendedFlavors(pythonPlatform.getFlavor());
}
@Override
public NativeLinkTargetMode getNativeLinkTargetMode(CxxPlatform cxxPlatform) {
return NativeLinkTargetMode.library();
}
@Override
public Iterable<? extends NativeLinkable> getNativeLinkTargetDeps(
CxxPlatform cxxPlatform) {
return RichStream.from(getPlatformDeps(ruleResolver, pythonPlatform, cxxPlatform, args))
.filter(NativeLinkable.class)
.toImmutableList();
}
@Override
public NativeLinkableInput getNativeLinkTargetInput(CxxPlatform cxxPlatform)
throws NoSuchBuildTargetException {
return NativeLinkableInput.builder()
.addAllArgs(
getExtensionArgs(
params.withBuildTarget(
params
.getBuildTarget()
.withAppendedFlavors(
pythonPlatform.getFlavor(),
CxxDescriptionEnhancer.SHARED_FLAVOR)),
ruleResolver,
pathResolver,
ruleFinder,
cellRoots,
cxxPlatform,
args,
getPlatformDeps(ruleResolver, pythonPlatform, cxxPlatform, args)))
.addAllFrameworks(args.getFrameworks())
.build();
}
@Override
public Optional<Path> getNativeLinkTargetOutputPath(CxxPlatform cxxPlatform) {
return Optional.empty();
}
};
}
@Override
public Stream<BuildTarget> getRuntimeDeps() {
return getDeclaredDeps().stream().map(BuildRule::getBuildTarget);
}
};
}
@Override
public void findDepsForTargetFromConstructorArgs(
BuildTarget buildTarget,
CellPathResolver cellRoots,
AbstractCxxPythonExtensionDescriptionArg constructorArg,
ImmutableCollection.Builder<BuildTarget> extraDepsBuilder,
ImmutableCollection.Builder<BuildTarget> targetGraphOnlyDepsBuilder) {
// Get any parse time deps from the C/C++ platforms.
extraDepsBuilder.addAll(CxxPlatforms.getParseTimeDeps(cxxPlatforms.getValues()));
for (PythonPlatform pythonPlatform : pythonPlatforms.getValues()) {
extraDepsBuilder.addAll(OptionalCompat.asSet(pythonPlatform.getCxxLibrary()));
}
}
@BuckStyleImmutable
@Value.Immutable
interface AbstractCxxPythonExtensionDescriptionArg extends CxxConstructorArg {
Optional<String> getBaseModule();
Optional<String> getModuleName();
}
}