/* * Copyright 2016-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.haskell; import com.facebook.buck.cxx.CxxDeps; import com.facebook.buck.cxx.CxxDescriptionEnhancer; import com.facebook.buck.cxx.CxxPlatform; import com.facebook.buck.cxx.Linker; import com.facebook.buck.cxx.Linkers; import com.facebook.buck.cxx.NativeLinkable; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.model.Flavor; import com.facebook.buck.model.FlavorConvertible; import com.facebook.buck.model.FlavorDomain; import com.facebook.buck.model.Flavored; import com.facebook.buck.model.InternalFlavor; 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.CommandTool; import com.facebook.buck.rules.CommonDescriptionArg; import com.facebook.buck.rules.DefaultBuildTargetSourcePath; import com.facebook.buck.rules.Description; import com.facebook.buck.rules.HasDeclaredDeps; import com.facebook.buck.rules.ImplicitDepsInferringDescription; 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.coercer.PatternMatchedCollection; import com.facebook.buck.rules.coercer.SourceList; import com.facebook.buck.rules.query.Query; import com.facebook.buck.rules.query.QueryUtils; import com.facebook.buck.util.MoreIterables; import com.facebook.buck.util.RichStream; import com.facebook.buck.util.immutables.BuckStyleImmutable; import com.facebook.buck.versions.VersionRoot; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; 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.Optional; import org.immutables.value.Value; public class HaskellBinaryDescription implements Description<HaskellBinaryDescriptionArg>, ImplicitDepsInferringDescription< HaskellBinaryDescription.AbstractHaskellBinaryDescriptionArg>, Flavored, VersionRoot<HaskellBinaryDescriptionArg> { private static final FlavorDomain<Type> BINARY_TYPE = FlavorDomain.from("Haskell Binary Type", Type.class); private final HaskellConfig haskellConfig; private final FlavorDomain<CxxPlatform> cxxPlatforms; private final CxxPlatform defaultCxxPlatform; public HaskellBinaryDescription( HaskellConfig haskellConfig, FlavorDomain<CxxPlatform> cxxPlatforms, CxxPlatform defaultCxxPlatform) { this.haskellConfig = haskellConfig; this.cxxPlatforms = cxxPlatforms; this.defaultCxxPlatform = defaultCxxPlatform; } @Override public Class<HaskellBinaryDescriptionArg> getConstructorArgType() { return HaskellBinaryDescriptionArg.class; } private Linker.LinkableDepType getLinkStyle(BuildTarget target, HaskellBinaryDescriptionArg arg) { Optional<Type> type = BINARY_TYPE.getValue(target); if (type.isPresent()) { return type.get().getLinkStyle(); } if (arg.getLinkStyle().isPresent()) { return arg.getLinkStyle().get(); } return Linker.LinkableDepType.STATIC; } @Override public BuildRule createBuildRule( TargetGraph targetGraph, BuildRuleParams params, BuildRuleResolver resolver, CellPathResolver cellRoots, HaskellBinaryDescriptionArg args) throws NoSuchBuildTargetException { SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(resolver); SourcePathResolver pathResolver = new SourcePathResolver(ruleFinder); CxxPlatform cxxPlatform = cxxPlatforms.getValue(params.getBuildTarget()).orElse(defaultCxxPlatform); Linker.LinkableDepType depType = getLinkStyle(params.getBuildTarget(), args); // The target to use for the link rule. BuildTarget binaryTarget = params.getBuildTarget().withFlavors(InternalFlavor.of("binary")); // Maintain backwards compatibility to ease upgrade flows. if (haskellConfig.shouldUsedOldBinaryOutputLocation().orElse(true)) { binaryTarget = binaryTarget.withAppendedFlavors(cxxPlatform.getFlavor()); } ImmutableSet.Builder<BuildRule> depsBuilder = ImmutableSet.builder(); depsBuilder.addAll( CxxDeps.builder() .addDeps(args.getDeps()) .addPlatformDeps(args.getPlatformDeps()) .build() .get(resolver, cxxPlatform)); args.getDepsQuery() .ifPresent( query -> QueryUtils.resolveDepQuery( params.getBuildTarget(), query, resolver, cellRoots, targetGraph, args.getDeps()) .filter(NativeLinkable.class::isInstance) .forEach(depsBuilder::add)); ImmutableSet<BuildRule> deps = depsBuilder.build(); ImmutableList.Builder<String> linkFlagsBuilder = ImmutableList.builder(); ImmutableList.Builder<com.facebook.buck.rules.args.Arg> linkArgsBuilder = ImmutableList.builder(); CommandTool.Builder executableBuilder = new CommandTool.Builder(); // Add the binary as the first argument. executableBuilder.addArg(SourcePathArg.of(new DefaultBuildTargetSourcePath(binaryTarget))); // Special handling for dynamically linked binaries. if (depType == Linker.LinkableDepType.SHARED) { // Create a symlink tree with for all shared libraries needed by this binary. SymlinkTree sharedLibraries = resolver.addToIndex( CxxDescriptionEnhancer.createSharedLibrarySymlinkTree( ruleFinder, params.getBuildTarget(), params.getProjectFilesystem(), cxxPlatform, deps, NativeLinkable.class::isInstance)); // Embed a origin-relative library path into the binary so it can find the shared libraries. // The shared libraries root is absolute. Also need an absolute path to the linkOutput Path absBinaryDir = params .getBuildTarget() .getCellPath() .resolve(HaskellLinkRule.getOutputDir(binaryTarget, params.getProjectFilesystem())); linkFlagsBuilder.addAll( MoreIterables.zipAndConcat( Iterables.cycle("-optl"), Linkers.iXlinker( "-rpath", String.format( "%s/%s", cxxPlatform.getLd().resolve(resolver).origin(), absBinaryDir.relativize(sharedLibraries.getRoot()).toString())))); // Add all the shared libraries and the symlink tree as inputs to the tool that represents // this binary, so that users can attach the proper deps. executableBuilder.addDep(sharedLibraries); executableBuilder.addInputs(sharedLibraries.getLinks().values()); } // Generate the compile rule and add its objects to the link. HaskellCompileRule compileRule = resolver.addToIndex( HaskellDescriptionUtils.requireCompileRule( params, resolver, ruleFinder, RichStream.from(deps).filter(HaskellCompileDep.class::isInstance).toImmutableSet(), cxxPlatform, haskellConfig, depType, args.getMain(), Optional.empty(), args.getCompilerFlags(), HaskellSources.from( params.getBuildTarget(), resolver, pathResolver, ruleFinder, cxxPlatform, "srcs", args.getSrcs()))); linkArgsBuilder.addAll(SourcePathArg.from(compileRule.getObjects())); ImmutableList<String> linkFlags = linkFlagsBuilder.build(); ImmutableList<com.facebook.buck.rules.args.Arg> linkArgs = linkArgsBuilder.build(); final CommandTool executable = executableBuilder.build(); final HaskellLinkRule linkRule = HaskellDescriptionUtils.createLinkRule( binaryTarget, params, resolver, ruleFinder, cxxPlatform, haskellConfig, Linker.LinkType.EXECUTABLE, linkFlags, linkArgs, RichStream.from(deps).filter(NativeLinkable.class).toImmutableList(), depType); return new HaskellBinary( params.copyAppendingExtraDeps(linkRule), ruleFinder, deps, executable, linkRule.getSourcePathToOutput()); } @Override public void findDepsForTargetFromConstructorArgs( BuildTarget buildTarget, CellPathResolver cellRoots, AbstractHaskellBinaryDescriptionArg constructorArg, ImmutableCollection.Builder<BuildTarget> extraDepsBuilder, ImmutableCollection.Builder<BuildTarget> targetGraphOnlyDepsBuilder) { HaskellDescriptionUtils.getParseTimeDeps( haskellConfig, ImmutableList.of( cxxPlatforms.getValue(buildTarget.getFlavors()).orElse(defaultCxxPlatform)), extraDepsBuilder); constructorArg .getDepsQuery() .ifPresent( depsQuery -> QueryUtils.extractParseTimeTargets(buildTarget, cellRoots, depsQuery) .forEach(extraDepsBuilder::add)); } @Override public boolean hasFlavors(ImmutableSet<Flavor> flavors) { if (cxxPlatforms.containsAnyOf(flavors)) { return true; } for (Type type : Type.values()) { if (flavors.contains(type.getFlavor())) { return true; } } return false; } @Override public boolean isVersionRoot(ImmutableSet<Flavor> flavors) { return true; } protected enum Type implements FlavorConvertible { SHARED(CxxDescriptionEnhancer.SHARED_FLAVOR, Linker.LinkableDepType.SHARED), STATIC_PIC(CxxDescriptionEnhancer.STATIC_PIC_FLAVOR, Linker.LinkableDepType.STATIC_PIC), STATIC(CxxDescriptionEnhancer.STATIC_FLAVOR, Linker.LinkableDepType.STATIC), ; private final Flavor flavor; private final Linker.LinkableDepType linkStyle; Type(Flavor flavor, Linker.LinkableDepType linkStyle) { this.flavor = flavor; this.linkStyle = linkStyle; } @Override public Flavor getFlavor() { return flavor; } public Linker.LinkableDepType getLinkStyle() { return linkStyle; } } @BuckStyleImmutable @Value.Immutable interface AbstractHaskellBinaryDescriptionArg extends CommonDescriptionArg, HasDeclaredDeps { @Value.Default default SourceList getSrcs() { return SourceList.EMPTY; } ImmutableList<String> getCompilerFlags(); @Value.Default default PatternMatchedCollection<ImmutableSortedSet<BuildTarget>> getPlatformDeps() { return PatternMatchedCollection.of(); } Optional<Query> getDepsQuery(); Optional<String> getMain(); Optional<Linker.LinkableDepType> getLinkStyle(); } }