com.facebook.buck.python.CxxPythonExtensionDescription.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.buck.python.CxxPythonExtensionDescription.java

Source

/*
 * 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.model.HasBuildTarget;
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.BuildTargetSourcePath;
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.rules.coercer.PatternMatchedCollection;
import com.facebook.buck.util.OptionalCompat;
import com.facebook.buck.versions.VersionPropagator;
import com.facebook.infer.annotation.SuppressFieldNotInitialized;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.base.Suppliers;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;

import java.nio.file.Path;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;

public class CxxPythonExtensionDescription implements Description<CxxPythonExtensionDescription.Arg>,
        ImplicitDepsInferringDescription<CxxPythonExtensionDescription.Arg>,
        VersionPropagator<CxxPythonExtensionDescription.Arg> {

    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 Arg createUnpopulatedConstructorArg() {
        return new Arg();
    }

    @VisibleForTesting
    protected static BuildTarget getExtensionTarget(BuildTarget target, Flavor pythonPlatform, Flavor platform) {
        return CxxDescriptionEnhancer.createSharedLibraryBuildTarget(
                BuildTarget.builder(target).addFlavors(pythonPlatform).build(), platform, Linker.LinkType.SHARED);
    }

    @VisibleForTesting
    protected static String getExtensionName(String moduleName) {
        // .so is used on OS X too (as opposed to dylib).
        return String.format("%s.so", moduleName);
    }

    @VisibleForTesting
    protected 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,
            CxxPlatform cxxPlatform, Arg args) throws NoSuchBuildTargetException {

        // Extract all C/C++ sources from the constructor arg.
        ImmutableMap<String, CxxSource> srcs = CxxDescriptionEnhancer.parseCxxSources(params.getBuildTarget(),
                pathResolver, cxxPlatform, args);
        ImmutableMap<Path, SourcePath> headers = CxxDescriptionEnhancer.parseHeaders(params.getBuildTarget(),
                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,
                pathResolver, 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,
                        CxxFlags.getLanguageFlags(args.preprocessorFlags, args.platformPreprocessorFlags,
                                args.langPreprocessorFlags, cxxPlatform),
                        ImmutableList.of(headerSymlinkTree), ImmutableSet.of(),
                        CxxPreprocessables.getTransitiveCxxPreprocessorInput(cxxPlatform, params.getDeps()),
                        args.includeDirs, sandboxTree);

        // Generate rule to build the object files.
        ImmutableMap<CxxPreprocessAndCompile, SourcePath> picObjects = CxxSourceRuleFactory
                .requirePreprocessAndCompileRules(params, ruleResolver, pathResolver, ruleFinder, cxxBuckConfig,
                        cxxPlatform, cxxPreprocessorInput,
                        CxxFlags.getLanguageFlags(args.compilerFlags, args.platformCompilerFlags,
                                args.langCompilerFlags, cxxPlatform),
                        args.prefixHeader, args.precompiledHeader, srcs, CxxSourceRuleFactory.PicType.PIC,
                        sandboxTree);

        ImmutableList.Builder<com.facebook.buck.rules.args.Arg> argsBuilder = ImmutableList.builder();
        argsBuilder
                .addAll(StringArg.from(CxxFlags.getFlags(args.linkerFlags, args.platformLinkerFlags, 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(pathResolver, picObjects.values()));

        return argsBuilder.build();
    }

    private ImmutableList<BuildRule> getPlatformDeps(BuildRuleParams params, BuildRuleResolver ruleResolver,
            PythonPlatform pythonPlatform, Arg args) {

        ImmutableList.Builder<BuildRule> rules = ImmutableList.builder();

        // Add declared deps.
        rules.addAll(params.getDeclaredDeps().get());

        // Add platform specific deps.
        rules.addAll(ruleResolver.getAllRules(
                Iterables.concat(args.platformDeps.getMatchingValues(pythonPlatform.getFlavor().toString()))));

        // Add a dep on the python C/C++ library.
        if (pythonPlatform.getCxxLibrary().isPresent()) {
            rules.add(ruleResolver.getRule(pythonPlatform.getCxxLibrary().get().getBuildTarget()));
        }

        return rules.build();
    }

    private <A extends Arg> BuildRule createExtensionBuildRule(BuildRuleParams params,
            BuildRuleResolver ruleResolver, PythonPlatform pythonPlatform, CxxPlatform cxxPlatform, A args)
            throws NoSuchBuildTargetException {
        SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(ruleResolver);
        SourcePathResolver pathResolver = new SourcePathResolver(ruleFinder);
        String moduleName = args.moduleName.orElse(params.getBuildTarget().getShortName());
        String extensionName = getExtensionName(moduleName);
        Path extensionPath = getExtensionPath(params.getProjectFilesystem(), params.getBuildTarget(), moduleName,
                pythonPlatform.getFlavor(), cxxPlatform.getFlavor());
        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,
                FluentIterable.from(params.getDeps()).filter(NativeLinkable.class), args.cxxRuntimeType,
                Optional.empty(), ImmutableSet.of(),
                NativeLinkableInput.builder()
                        .setArgs(getExtensionArgs(
                                params.copyWithBuildTarget(params.getBuildTarget()
                                        .withoutFlavors(LinkerMapMode.FLAVOR_DOMAIN.getFlavors())),
                                ruleResolver, pathResolver, ruleFinder, cxxPlatform, args))
                        .setFrameworks(args.frameworks).setLibraries(args.libraries).build());
    }

    @Override
    public <A extends Arg> BuildRule createBuildRule(TargetGraph targetGraph, final BuildRuleParams params,
            final BuildRuleResolver ruleResolver, final A 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.copyWithDeps(
                            Suppliers.ofInstance(ImmutableSortedSet.copyOf(
                                    getPlatformDeps(params, ruleResolver, pythonPlatform.get().getValue(), args))),
                            Suppliers.ofInstance(ImmutableSortedSet.of())),
                    ruleResolver, 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.baseModule);
        String moduleName = args.moduleName.orElse(params.getBuildTarget().getShortName());
        final Path module = baseModule.resolve(getExtensionName(moduleName));
        return new CxxPythonExtension(params, pathResolver) {

            @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 PythonPackageComponents getPythonPackageComponents(PythonPlatform pythonPlatform,
                    CxxPlatform cxxPlatform) throws NoSuchBuildTargetException {
                BuildRule extension = getExtension(pythonPlatform, cxxPlatform);
                SourcePath output = new BuildTargetSourcePath(extension.getBuildTarget());
                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 FluentIterable.from(getPlatformDeps(params, ruleResolver, pythonPlatform, args))
                                .filter(NativeLinkable.class);
                    }

                    @Override
                    public NativeLinkableInput getNativeLinkTargetInput(CxxPlatform cxxPlatform)
                            throws NoSuchBuildTargetException {
                        return NativeLinkableInput.builder()
                                .addAllArgs(getExtensionArgs(
                                        params.copyWithChanges(
                                                params.getBuildTarget().withAppendedFlavors(
                                                        pythonPlatform.getFlavor(),
                                                        CxxDescriptionEnhancer.SHARED_FLAVOR),
                                                Suppliers.ofInstance(ImmutableSortedSet.copyOf(getPlatformDeps(
                                                        params, ruleResolver, pythonPlatform, args))),
                                                Suppliers.ofInstance(ImmutableSortedSet.of())),
                                        ruleResolver, pathResolver, ruleFinder, cxxPlatform, args))
                                .addAllFrameworks(args.frameworks).build();
                    }

                    @Override
                    public Optional<Path> getNativeLinkTargetOutputPath(CxxPlatform cxxPlatform) {
                        return Optional.empty();
                    }

                };
            }

            @Override
            public Stream<SourcePath> getRuntimeDeps() {
                return getDeclaredDeps().stream().map(HasBuildTarget::getBuildTarget)
                        .map(BuildTargetSourcePath::new);
            }

        };
    }

    @Override
    public Iterable<BuildTarget> findDepsForTargetFromConstructorArgs(BuildTarget buildTarget,
            CellPathResolver cellRoots, Arg constructorArg) {
        ImmutableSet.Builder<BuildTarget> deps = ImmutableSet.builder();

        // Get any parse time deps from the C/C++ platforms.
        deps.addAll(CxxPlatforms.getParseTimeDeps(cxxPlatforms.getValues()));

        for (PythonPlatform pythonPlatform : pythonPlatforms.getValues()) {
            deps.addAll(OptionalCompat.asSet(pythonPlatform.getCxxLibrary()));
        }

        return deps.build();
    }

    @SuppressFieldNotInitialized
    public static class Arg extends CxxConstructorArg {
        public PatternMatchedCollection<ImmutableSortedSet<BuildTarget>> platformDeps = PatternMatchedCollection
                .of();
        public Optional<String> baseModule;
        public Optional<String> moduleName;
    }

}