/*
* 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.Archive;
import com.facebook.buck.cxx.CxxBuckConfig;
import com.facebook.buck.cxx.CxxDeps;
import com.facebook.buck.cxx.CxxDescriptionEnhancer;
import com.facebook.buck.cxx.CxxPlatform;
import com.facebook.buck.cxx.CxxSourceRuleFactory;
import com.facebook.buck.cxx.Linker;
import com.facebook.buck.cxx.NativeLinkable;
import com.facebook.buck.cxx.NativeLinkableInput;
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.CommonDescriptionArg;
import com.facebook.buck.rules.Description;
import com.facebook.buck.rules.HasDeclaredDeps;
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.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.util.MoreCollectors;
import com.facebook.buck.util.RichStream;
import com.facebook.buck.util.immutables.BuckStyleImmutable;
import com.facebook.buck.versions.VersionPropagator;
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.ImmutableSortedMap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Sets;
import java.io.File;
import java.util.Map;
import java.util.Optional;
import org.immutables.value.Value;
public class HaskellLibraryDescription
implements Description<HaskellLibraryDescriptionArg>,
ImplicitDepsInferringDescription<
HaskellLibraryDescription.AbstractHaskellLibraryDescriptionArg>,
Flavored,
VersionPropagator<HaskellLibraryDescriptionArg> {
private static final FlavorDomain<Type> LIBRARY_TYPE =
FlavorDomain.from("Haskell Library Type", Type.class);
private final HaskellConfig haskellConfig;
private final CxxBuckConfig cxxBuckConfig;
private final FlavorDomain<CxxPlatform> cxxPlatforms;
public HaskellLibraryDescription(
HaskellConfig haskellConfig,
CxxBuckConfig cxxBuckConfig,
FlavorDomain<CxxPlatform> cxxPlatforms) {
this.haskellConfig = haskellConfig;
this.cxxBuckConfig = cxxBuckConfig;
this.cxxPlatforms = cxxPlatforms;
}
@Override
public Class<HaskellLibraryDescriptionArg> getConstructorArgType() {
return HaskellLibraryDescriptionArg.class;
}
private BuildTarget getBaseBuildTarget(BuildTarget target) {
return target.withoutFlavors(Sets.union(Type.FLAVOR_VALUES, cxxPlatforms.getFlavors()));
}
/** @return the package identifier to use for the library with the given target. */
private HaskellPackageInfo getPackageInfo(BuildTarget target) {
String name = String.format("%s-%s", target.getBaseName(), target.getShortName());
name = name.replace(File.separatorChar, '-');
name = name.replace('_', '-');
name = name.replaceFirst("^-*", "");
return HaskellPackageInfo.of(name, "1.0.0", name);
}
private HaskellCompileRule requireCompileRule(
BuildRuleParams params,
BuildRuleResolver resolver,
SourcePathResolver pathResolver,
SourcePathRuleFinder ruleFinder,
CxxPlatform cxxPlatform,
HaskellLibraryDescriptionArg args,
ImmutableSet<BuildRule> deps,
Linker.LinkableDepType depType)
throws NoSuchBuildTargetException {
return HaskellDescriptionUtils.requireCompileRule(
params,
resolver,
ruleFinder,
deps,
cxxPlatform,
haskellConfig,
depType,
Optional.empty(),
Optional.of(getPackageInfo(params.getBuildTarget())),
args.getCompilerFlags(),
HaskellSources.from(
params.getBuildTarget(),
resolver,
pathResolver,
ruleFinder,
cxxPlatform,
"srcs",
args.getSrcs()));
}
private Archive createStaticLibrary(
BuildTarget target,
BuildRuleParams baseParams,
BuildRuleResolver resolver,
SourcePathResolver pathResolver,
SourcePathRuleFinder ruleFinder,
CxxPlatform cxxPlatform,
HaskellLibraryDescriptionArg args,
ImmutableSet<BuildRule> deps,
Linker.LinkableDepType depType)
throws NoSuchBuildTargetException {
HaskellCompileRule compileRule =
requireCompileRule(
baseParams, resolver, pathResolver, ruleFinder, cxxPlatform, args, deps, depType);
return Archive.from(
target,
baseParams,
ruleFinder,
cxxPlatform,
cxxBuckConfig.getArchiveContents(),
CxxDescriptionEnhancer.getStaticLibraryPath(
baseParams.getProjectFilesystem(),
target,
cxxPlatform.getFlavor(),
depType == Linker.LinkableDepType.STATIC
? CxxSourceRuleFactory.PicType.PDC
: CxxSourceRuleFactory.PicType.PIC,
cxxPlatform.getStaticLibraryExtension()),
compileRule.getObjects());
}
private Archive requireStaticLibrary(
BuildTarget baseTarget,
BuildRuleParams baseParams,
BuildRuleResolver resolver,
SourcePathResolver pathResolver,
SourcePathRuleFinder ruleFinder,
CxxPlatform cxxPlatform,
HaskellLibraryDescriptionArg args,
ImmutableSet<BuildRule> deps,
Linker.LinkableDepType depType)
throws NoSuchBuildTargetException {
Preconditions.checkArgument(
Sets.intersection(
baseTarget.getFlavors(), Sets.union(Type.FLAVOR_VALUES, cxxPlatforms.getFlavors()))
.isEmpty());
BuildTarget target =
baseTarget.withAppendedFlavors(
depType == Linker.LinkableDepType.STATIC
? Type.STATIC.getFlavor()
: Type.STATIC_PIC.getFlavor(),
cxxPlatform.getFlavor());
Optional<Archive> archive = resolver.getRuleOptionalWithType(target, Archive.class);
if (archive.isPresent()) {
return archive.get();
}
return resolver.addToIndex(
createStaticLibrary(
target,
baseParams,
resolver,
pathResolver,
ruleFinder,
cxxPlatform,
args,
deps,
depType));
}
private HaskellPackageRule createPackage(
BuildTarget target,
BuildRuleParams baseParams,
BuildRuleResolver resolver,
SourcePathResolver pathResolver,
SourcePathRuleFinder ruleFinder,
CxxPlatform cxxPlatform,
HaskellLibraryDescriptionArg args,
ImmutableSet<BuildRule> deps,
Linker.LinkableDepType depType)
throws NoSuchBuildTargetException {
BuildRule library;
switch (depType) {
case SHARED:
library =
requireSharedLibrary(
getBaseBuildTarget(target),
baseParams,
resolver,
pathResolver,
ruleFinder,
cxxPlatform,
args,
deps);
break;
case STATIC:
case STATIC_PIC:
library =
requireStaticLibrary(
getBaseBuildTarget(target),
baseParams,
resolver,
pathResolver,
ruleFinder,
cxxPlatform,
args,
deps,
depType);
break;
default:
throw new IllegalStateException();
}
ImmutableSortedMap.Builder<String, HaskellPackage> depPackagesBuilder =
ImmutableSortedMap.naturalOrder();
for (BuildRule rule : deps) {
if (rule instanceof HaskellCompileDep) {
ImmutableList<HaskellPackage> packages =
((HaskellCompileDep) rule).getCompileInput(cxxPlatform, depType).getPackages();
for (HaskellPackage pkg : packages) {
depPackagesBuilder.put(pkg.getInfo().getIdentifier(), pkg);
}
}
}
ImmutableSortedMap<String, HaskellPackage> depPackages = depPackagesBuilder.build();
HaskellCompileRule compileRule =
requireCompileRule(
baseParams, resolver, pathResolver, ruleFinder, cxxPlatform, args, deps, depType);
return HaskellPackageRule.from(
target,
baseParams,
ruleFinder,
haskellConfig.getPackager().resolve(resolver),
haskellConfig.getHaskellVersion(),
getPackageInfo(target),
depPackages,
compileRule.getModules(),
ImmutableSortedSet.of(library.getSourcePathToOutput()),
ImmutableSortedSet.of(compileRule.getInterfaces()));
}
private HaskellPackageRule requirePackage(
BuildTarget baseTarget,
BuildRuleParams baseParams,
BuildRuleResolver resolver,
SourcePathResolver pathResolver,
SourcePathRuleFinder ruleFinder,
CxxPlatform cxxPlatform,
HaskellLibraryDescriptionArg args,
ImmutableSet<BuildRule> deps,
Linker.LinkableDepType depType)
throws NoSuchBuildTargetException {
Preconditions.checkArgument(
Sets.intersection(
baseTarget.getFlavors(), Sets.union(Type.FLAVOR_VALUES, cxxPlatforms.getFlavors()))
.isEmpty());
BuildTarget target = baseTarget.withAppendedFlavors(cxxPlatform.getFlavor());
switch (depType) {
case SHARED:
target = target.withAppendedFlavors(Type.PACKAGE_SHARED.getFlavor());
break;
case STATIC:
target = target.withAppendedFlavors(Type.PACKAGE_STATIC.getFlavor());
break;
case STATIC_PIC:
target = target.withAppendedFlavors(Type.PACKAGE_STATIC_PIC.getFlavor());
break;
default:
throw new IllegalStateException();
}
Optional<HaskellPackageRule> packageRule =
resolver.getRuleOptionalWithType(target, HaskellPackageRule.class);
if (packageRule.isPresent()) {
return packageRule.get();
}
return resolver.addToIndex(
createPackage(
target,
baseParams,
resolver,
pathResolver,
ruleFinder,
cxxPlatform,
args,
deps,
depType));
}
private HaskellLinkRule createSharedLibrary(
BuildTarget target,
BuildRuleParams baseParams,
BuildRuleResolver resolver,
SourcePathResolver pathResolver,
SourcePathRuleFinder ruleFinder,
CxxPlatform cxxPlatform,
HaskellLibraryDescriptionArg args,
ImmutableSet<BuildRule> deps)
throws NoSuchBuildTargetException {
HaskellCompileRule compileRule =
requireCompileRule(
baseParams,
resolver,
pathResolver,
ruleFinder,
cxxPlatform,
args,
deps,
Linker.LinkableDepType.SHARED);
return HaskellDescriptionUtils.createLinkRule(
target,
baseParams,
resolver,
ruleFinder,
cxxPlatform,
haskellConfig,
Linker.LinkType.SHARED,
ImmutableList.of(),
ImmutableList.copyOf(SourcePathArg.from(compileRule.getObjects())),
RichStream.from(deps).filter(NativeLinkable.class).toImmutableList(),
Linker.LinkableDepType.SHARED);
}
private HaskellLinkRule requireSharedLibrary(
BuildTarget baseTarget,
BuildRuleParams baseParams,
BuildRuleResolver resolver,
SourcePathResolver pathResolver,
SourcePathRuleFinder ruleFinder,
CxxPlatform cxxPlatform,
HaskellLibraryDescriptionArg args,
ImmutableSet<BuildRule> deps)
throws NoSuchBuildTargetException {
Preconditions.checkArgument(
Sets.intersection(
baseTarget.getFlavors(), Sets.union(Type.FLAVOR_VALUES, cxxPlatforms.getFlavors()))
.isEmpty());
BuildTarget target =
baseTarget.withAppendedFlavors(Type.SHARED.getFlavor(), cxxPlatform.getFlavor());
Optional<HaskellLinkRule> linkRule =
resolver.getRuleOptionalWithType(target, HaskellLinkRule.class);
if (linkRule.isPresent()) {
return linkRule.get();
}
return resolver.addToIndex(
createSharedLibrary(
target, baseParams, resolver, pathResolver, ruleFinder, cxxPlatform, args, deps));
}
@Override
public BuildRule createBuildRule(
TargetGraph targetGraph,
final BuildRuleParams params,
final BuildRuleResolver resolver,
CellPathResolver cellRoots,
final HaskellLibraryDescriptionArg args)
throws NoSuchBuildTargetException {
final BuildTarget buildTarget = params.getBuildTarget();
SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(resolver);
final SourcePathResolver pathResolver = new SourcePathResolver(ruleFinder);
CxxDeps allDeps =
CxxDeps.builder().addDeps(args.getDeps()).addPlatformDeps(args.getPlatformDeps()).build();
// See if we're building a particular "type" and "platform" of this library, and if so, extract
// them from the flavors attached to the build target.
Optional<Map.Entry<Flavor, Type>> type = LIBRARY_TYPE.getFlavorAndValue(buildTarget);
Optional<CxxPlatform> cxxPlatform = cxxPlatforms.getValue(buildTarget);
if (type.isPresent()) {
Preconditions.checkState(cxxPlatform.isPresent());
// Get the base build, without any flavors referring to the library type or platform.
BuildTarget baseTarget =
params
.getBuildTarget()
.withoutFlavors(Sets.union(Type.FLAVOR_VALUES, cxxPlatforms.getFlavors()));
ImmutableSet<BuildRule> deps = allDeps.get(resolver, cxxPlatform.get());
switch (type.get().getValue()) {
case PACKAGE_SHARED:
case PACKAGE_STATIC:
case PACKAGE_STATIC_PIC:
Linker.LinkableDepType depType;
if (type.get().getValue().equals(Type.PACKAGE_SHARED)) {
depType = Linker.LinkableDepType.SHARED;
} else if (type.get().getValue().equals(Type.PACKAGE_STATIC)) {
depType = Linker.LinkableDepType.STATIC;
} else {
depType = Linker.LinkableDepType.STATIC_PIC;
}
return requirePackage(
baseTarget,
params,
resolver,
pathResolver,
ruleFinder,
cxxPlatform.get(),
args,
deps,
depType);
case SHARED:
return requireSharedLibrary(
baseTarget,
params,
resolver,
pathResolver,
ruleFinder,
cxxPlatform.get(),
args,
deps);
case STATIC_PIC:
case STATIC:
return requireStaticLibrary(
baseTarget,
params,
resolver,
pathResolver,
ruleFinder,
cxxPlatform.get(),
args,
deps,
type.get().getValue() == Type.STATIC
? Linker.LinkableDepType.STATIC
: Linker.LinkableDepType.STATIC_PIC);
}
throw new IllegalStateException(
String.format(
"%s: unexpected type `%s`", params.getBuildTarget(), type.get().getValue()));
}
return new HaskellLibrary(params) {
@Override
public Iterable<BuildRule> getCompileDeps(CxxPlatform cxxPlatform) {
return RichStream.from(allDeps.get(resolver, cxxPlatform))
.filter(HaskellCompileDep.class::isInstance)
.toImmutableList();
}
@Override
public HaskellCompileInput getCompileInput(
CxxPlatform cxxPlatform, Linker.LinkableDepType depType)
throws NoSuchBuildTargetException {
HaskellPackageRule rule =
requirePackage(
getBaseBuildTarget(getBuildTarget()),
params,
resolver,
pathResolver,
ruleFinder,
cxxPlatform,
args,
allDeps.get(resolver, cxxPlatform),
depType);
return HaskellCompileInput.builder().addPackages(rule.getPackage()).build();
}
@Override
public Iterable<? extends NativeLinkable> getNativeLinkableDeps() {
return ImmutableList.of();
}
@Override
public Iterable<? extends NativeLinkable> getNativeLinkableExportedDepsForPlatform(
CxxPlatform cxxPlatform) {
return RichStream.from(allDeps.get(resolver, cxxPlatform))
.filter(NativeLinkable.class)
.toImmutableList();
}
@Override
public Iterable<? extends NativeLinkable> getNativeLinkableExportedDeps() {
return RichStream.from(allDeps.getForAllPlatforms(resolver))
.filter(NativeLinkable.class)
.toImmutableList();
}
@Override
public NativeLinkableInput getNativeLinkableInput(
CxxPlatform cxxPlatform, Linker.LinkableDepType type) throws NoSuchBuildTargetException {
Iterable<com.facebook.buck.rules.args.Arg> linkArgs;
switch (type) {
case STATIC:
case STATIC_PIC:
Archive archive =
requireStaticLibrary(
getBaseBuildTarget(getBuildTarget()),
params,
resolver,
pathResolver,
ruleFinder,
cxxPlatform,
args,
allDeps.get(resolver, cxxPlatform),
type);
linkArgs =
args.getLinkWhole()
? cxxPlatform.getLd().resolve(resolver).linkWhole(archive.toArg())
: ImmutableList.of(archive.toArg());
break;
case SHARED:
BuildRule rule =
requireSharedLibrary(
getBaseBuildTarget(getBuildTarget()),
params,
resolver,
pathResolver,
ruleFinder,
cxxPlatform,
args,
allDeps.get(resolver, cxxPlatform));
linkArgs = ImmutableList.of(SourcePathArg.of(rule.getSourcePathToOutput()));
break;
default:
throw new IllegalStateException();
}
return NativeLinkableInput.builder().addAllArgs(linkArgs).build();
}
@Override
public Linkage getPreferredLinkage(CxxPlatform cxxPlatform) {
return args.getPreferredLinkage();
}
@Override
public ImmutableMap<String, SourcePath> getSharedLibraries(CxxPlatform cxxPlatform)
throws NoSuchBuildTargetException {
ImmutableMap.Builder<String, SourcePath> libs = ImmutableMap.builder();
String sharedLibrarySoname =
CxxDescriptionEnhancer.getSharedLibrarySoname(
Optional.empty(), getBuildTarget(), cxxPlatform);
BuildRule sharedLibraryBuildRule =
requireSharedLibrary(
getBaseBuildTarget(getBuildTarget()),
params,
resolver,
pathResolver,
ruleFinder,
cxxPlatform,
args,
allDeps.get(resolver, cxxPlatform));
libs.put(sharedLibrarySoname, sharedLibraryBuildRule.getSourcePathToOutput());
return libs.build();
}
};
}
@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 void findDepsForTargetFromConstructorArgs(
BuildTarget buildTarget,
CellPathResolver cellRoots,
AbstractHaskellLibraryDescriptionArg constructorArg,
ImmutableCollection.Builder<BuildTarget> extraDepsBuilder,
ImmutableCollection.Builder<BuildTarget> targetGraphOnlyDepsBuilder) {
HaskellDescriptionUtils.getParseTimeDeps(
haskellConfig, cxxPlatforms.getValues(), extraDepsBuilder);
}
protected enum Type implements FlavorConvertible {
PACKAGE_SHARED(InternalFlavor.of("package-shared")),
PACKAGE_STATIC(InternalFlavor.of("package-static")),
PACKAGE_STATIC_PIC(InternalFlavor.of("package-static-pic")),
SHARED(CxxDescriptionEnhancer.SHARED_FLAVOR),
STATIC(CxxDescriptionEnhancer.STATIC_FLAVOR),
STATIC_PIC(CxxDescriptionEnhancer.STATIC_PIC_FLAVOR),
;
public static final ImmutableSet<Flavor> FLAVOR_VALUES =
ImmutableList.copyOf(Type.values())
.stream()
.map(Type::getFlavor)
.collect(MoreCollectors.toImmutableSet());
private final Flavor flavor;
Type(Flavor flavor) {
this.flavor = flavor;
}
@Override
public Flavor getFlavor() {
return flavor;
}
}
@BuckStyleImmutable
@Value.Immutable
interface AbstractHaskellLibraryDescriptionArg extends CommonDescriptionArg, HasDeclaredDeps {
@Value.Default
default SourceList getSrcs() {
return SourceList.EMPTY;
}
ImmutableList<String> getCompilerFlags();
@Value.Default
default PatternMatchedCollection<ImmutableSortedSet<BuildTarget>> getPlatformDeps() {
return PatternMatchedCollection.of();
}
@Value.Default
default boolean getLinkWhole() {
return false;
}
@Value.Default
default NativeLinkable.Linkage getPreferredLinkage() {
return NativeLinkable.Linkage.ANY;
}
}
}