/*
* 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.swift;
import com.facebook.buck.cxx.CxxBuckConfig;
import com.facebook.buck.cxx.CxxDescriptionEnhancer;
import com.facebook.buck.cxx.CxxLibrary;
import com.facebook.buck.cxx.CxxLibraryDescription;
import com.facebook.buck.cxx.CxxLinkableEnhancer;
import com.facebook.buck.cxx.CxxPlatform;
import com.facebook.buck.cxx.Linker;
import com.facebook.buck.cxx.LinkerMapMode;
import com.facebook.buck.cxx.NativeLinkable;
import com.facebook.buck.cxx.NativeLinkableInput;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.model.BuildTargets;
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.HasSrcs;
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.coercer.FrameworkPath;
import com.facebook.buck.util.HumanReadableException;
import com.facebook.buck.util.MoreCollectors;
import com.facebook.buck.util.RichStream;
import com.facebook.buck.util.immutables.BuckStyleImmutable;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicates;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Sets;
import java.nio.file.Path;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.regex.Pattern;
import org.immutables.value.Value;
public class SwiftLibraryDescription implements Description<SwiftLibraryDescriptionArg>, Flavored {
static final Flavor SWIFT_COMPANION_FLAVOR = InternalFlavor.of("swift-companion");
static final Flavor SWIFT_COMPILE_FLAVOR = InternalFlavor.of("swift-compile");
private static final Set<Flavor> SUPPORTED_FLAVORS =
ImmutableSet.of(
SWIFT_COMPANION_FLAVOR, SWIFT_COMPILE_FLAVOR, LinkerMapMode.NO_LINKER_MAP.getFlavor());
public enum Type implements FlavorConvertible {
SHARED(CxxDescriptionEnhancer.SHARED_FLAVOR),
STATIC(CxxDescriptionEnhancer.STATIC_FLAVOR),
MACH_O_BUNDLE(CxxDescriptionEnhancer.MACH_O_BUNDLE_FLAVOR),
;
private final Flavor flavor;
Type(Flavor flavor) {
this.flavor = flavor;
}
@Override
public Flavor getFlavor() {
return flavor;
}
}
private static final FlavorDomain<Type> LIBRARY_TYPE =
FlavorDomain.from("Swift Library Type", Type.class);
private final CxxBuckConfig cxxBuckConfig;
private final SwiftBuckConfig swiftBuckConfig;
private final FlavorDomain<CxxPlatform> cxxPlatformFlavorDomain;
private final FlavorDomain<SwiftPlatform> swiftPlatformFlavorDomain;
public SwiftLibraryDescription(
CxxBuckConfig cxxBuckConfig,
SwiftBuckConfig swiftBuckConfig,
FlavorDomain<CxxPlatform> cxxPlatformFlavorDomain,
FlavorDomain<SwiftPlatform> swiftPlatformFlavorDomain) {
this.cxxBuckConfig = cxxBuckConfig;
this.swiftBuckConfig = swiftBuckConfig;
this.cxxPlatformFlavorDomain = cxxPlatformFlavorDomain;
this.swiftPlatformFlavorDomain = swiftPlatformFlavorDomain;
}
@Override
public Class<SwiftLibraryDescriptionArg> getConstructorArgType() {
return SwiftLibraryDescriptionArg.class;
}
@Override
public Optional<ImmutableSet<FlavorDomain<?>>> flavorDomains() {
return Optional.of(
ImmutableSet.of(
// Missing: swift-companion
// Missing: swift-compile
cxxPlatformFlavorDomain));
}
@Override
public boolean hasFlavors(ImmutableSet<Flavor> flavors) {
ImmutableSet<Flavor> currentUnsupportedFlavors =
ImmutableSet.copyOf(Sets.filter(flavors, Predicates.not(SUPPORTED_FLAVORS::contains)));
if (currentUnsupportedFlavors.isEmpty()) {
return true;
}
return cxxPlatformFlavorDomain.containsAnyOf(flavors);
}
@Override
public BuildRule createBuildRule(
TargetGraph targetGraph,
BuildRuleParams params,
final BuildRuleResolver resolver,
CellPathResolver cellRoots,
SwiftLibraryDescriptionArg args)
throws NoSuchBuildTargetException {
Optional<LinkerMapMode> flavoredLinkerMapMode =
LinkerMapMode.FLAVOR_DOMAIN.getValue(params.getBuildTarget());
params = LinkerMapMode.removeLinkerMapModeFlavorInParams(params, flavoredLinkerMapMode);
final BuildTarget buildTarget = params.getBuildTarget();
// 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, CxxPlatform>> platform =
cxxPlatformFlavorDomain.getFlavorAndValue(buildTarget);
final ImmutableSortedSet<Flavor> buildFlavors = buildTarget.getFlavors();
ImmutableSortedSet<BuildRule> filteredExtraDeps =
params
.getExtraDeps()
.get()
.stream()
.filter(
input ->
!input
.getBuildTarget()
.getUnflavoredBuildTarget()
.equals(buildTarget.getUnflavoredBuildTarget()))
.collect(MoreCollectors.toImmutableSortedSet());
params = params.copyReplacingExtraDeps(Suppliers.ofInstance(filteredExtraDeps));
if (!buildFlavors.contains(SWIFT_COMPANION_FLAVOR) && platform.isPresent()) {
final CxxPlatform cxxPlatform = platform.get().getValue();
Optional<SwiftPlatform> swiftPlatform = swiftPlatformFlavorDomain.getValue(buildTarget);
if (!swiftPlatform.isPresent()) {
throw new HumanReadableException("Platform %s is missing swift compiler", cxxPlatform);
}
// 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);
if (!buildFlavors.contains(SWIFT_COMPILE_FLAVOR) && type.isPresent()) {
Set<Flavor> flavors = Sets.newHashSet(params.getBuildTarget().getFlavors());
flavors.remove(type.get().getKey());
BuildTarget target =
BuildTarget.builder(params.getBuildTarget().getUnflavoredBuildTarget())
.addAllFlavors(flavors)
.build();
if (flavoredLinkerMapMode.isPresent()) {
target = target.withAppendedFlavors(flavoredLinkerMapMode.get().getFlavor());
}
BuildRuleParams typeParams = params.withBuildTarget(target);
switch (type.get().getValue()) {
case SHARED:
return createSharedLibraryBuildRule(
typeParams,
resolver,
target,
swiftPlatform.get(),
cxxPlatform,
args.getSoname(),
flavoredLinkerMapMode);
case STATIC:
case MACH_O_BUNDLE:
// TODO(tho@uber.com) create build rule for other types.
}
throw new RuntimeException("unhandled library build type");
}
// All swift-compile rules of swift-lib deps are required since we need their swiftmodules
// during compilation.
final Function<BuildRule, BuildRule> requireSwiftCompile =
input -> {
try {
Preconditions.checkArgument(input instanceof SwiftLibrary);
return ((SwiftLibrary) input).requireSwiftCompileRule(cxxPlatform.getFlavor());
} catch (NoSuchBuildTargetException e) {
throw new HumanReadableException(
e, "Could not find SwiftCompile with target %s", buildTarget);
}
};
params =
params.copyAppendingExtraDeps(
params
.getBuildDeps()
.stream()
.filter(SwiftLibrary.class::isInstance)
.map(requireSwiftCompile)
.collect(MoreCollectors.toImmutableSet()));
params =
params.copyAppendingExtraDeps(
params
.getBuildDeps()
.stream()
.filter(CxxLibrary.class::isInstance)
.map(
input -> {
BuildTarget companionTarget =
input.getBuildTarget().withAppendedFlavors(SWIFT_COMPANION_FLAVOR);
return resolver.getRuleOptional(companionTarget).map(requireSwiftCompile);
})
.filter(Optional::isPresent)
.map(Optional::get)
.collect(MoreCollectors.toImmutableSortedSet()));
return new SwiftCompile(
cxxPlatform,
swiftBuckConfig,
params,
swiftPlatform.get().getSwiftc(),
args.getFrameworks(),
args.getModuleName().orElse(buildTarget.getShortName()),
BuildTargets.getGenPath(params.getProjectFilesystem(), buildTarget, "%s"),
args.getSrcs(),
args.getCompilerFlags(),
args.getEnableObjcInterop(),
args.getBridgingHeader());
}
// Otherwise, we return the generic placeholder of this library.
params = LinkerMapMode.restoreLinkerMapModeFlavorInParams(params, flavoredLinkerMapMode);
return new SwiftLibrary(
params,
resolver,
ImmutableSet.of(),
swiftPlatformFlavorDomain,
args.getFrameworks(),
args.getLibraries(),
args.getSupportedPlatformsRegex(),
args.getPreferredLinkage().orElse(NativeLinkable.Linkage.ANY));
}
private BuildRule createSharedLibraryBuildRule(
BuildRuleParams params,
BuildRuleResolver resolver,
BuildTarget buildTarget,
SwiftPlatform swiftPlatform,
CxxPlatform cxxPlatform,
Optional<String> soname,
Optional<LinkerMapMode> flavoredLinkerMapMode)
throws NoSuchBuildTargetException {
SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(resolver);
SourcePathResolver sourcePathResolver = new SourcePathResolver(ruleFinder);
String sharedLibrarySoname =
CxxDescriptionEnhancer.getSharedLibrarySoname(
soname, buildTarget.withoutFlavors(SUPPORTED_FLAVORS), cxxPlatform);
Path sharedLibOutput =
CxxDescriptionEnhancer.getSharedLibraryPath(
params.getProjectFilesystem(), buildTarget, sharedLibrarySoname);
SwiftRuntimeNativeLinkable swiftRuntimeLinkable = new SwiftRuntimeNativeLinkable(swiftPlatform);
BuildTarget requiredBuildTarget =
buildTarget
.withoutFlavors(CxxDescriptionEnhancer.SHARED_FLAVOR)
.withoutFlavors(LinkerMapMode.FLAVOR_DOMAIN.getFlavors())
.withAppendedFlavors(SWIFT_COMPILE_FLAVOR);
SwiftCompile rule = (SwiftCompile) resolver.requireRule(requiredBuildTarget);
NativeLinkableInput.Builder inputBuilder =
NativeLinkableInput.builder()
.from(
swiftRuntimeLinkable.getNativeLinkableInput(
cxxPlatform, Linker.LinkableDepType.SHARED))
.addAllArgs(rule.getAstLinkArgs())
.addArgs(rule.getFileListLinkArg());
return resolver.addToIndex(
CxxLinkableEnhancer.createCxxLinkableBuildRule(
cxxBuckConfig,
cxxPlatform,
LinkerMapMode.restoreLinkerMapModeFlavorInParams(params, flavoredLinkerMapMode),
resolver,
sourcePathResolver,
ruleFinder,
buildTarget,
Linker.LinkType.SHARED,
Optional.of(sharedLibrarySoname),
sharedLibOutput,
Linker.LinkableDepType.SHARED,
/* thinLto */ false,
RichStream.from(params.getBuildDeps())
.filter(NativeLinkable.class)
.concat(RichStream.of(swiftRuntimeLinkable))
.collect(MoreCollectors.toImmutableSet()),
Optional.empty(),
Optional.empty(),
ImmutableSet.of(),
inputBuilder.build()));
}
public Optional<BuildRule> createCompanionBuildRule(
final TargetGraph targetGraph,
final BuildRuleParams params,
final BuildRuleResolver resolver,
CellPathResolver cellRoots,
CxxLibraryDescription.CommonArg args)
throws NoSuchBuildTargetException {
BuildTarget buildTarget = params.getBuildTarget();
if (!isSwiftTarget(buildTarget)) {
boolean hasSwiftSource =
!SwiftDescriptions.filterSwiftSources(
new SourcePathResolver(new SourcePathRuleFinder(resolver)), args.getSrcs())
.isEmpty();
return hasSwiftSource
? Optional.of(
resolver.requireRule(buildTarget.withAppendedFlavors(SWIFT_COMPANION_FLAVOR)))
: Optional.empty();
}
SwiftLibraryDescriptionArg.Builder delegateArgsBuilder = SwiftLibraryDescriptionArg.builder();
SwiftDescriptions.populateSwiftLibraryDescriptionArg(
new SourcePathResolver(new SourcePathRuleFinder(resolver)),
delegateArgsBuilder,
args,
buildTarget);
SwiftLibraryDescriptionArg delegateArgs = delegateArgsBuilder.build();
if (!delegateArgs.getSrcs().isEmpty()) {
return Optional.of(
resolver.addToIndex(
createBuildRule(targetGraph, params, resolver, cellRoots, delegateArgs)));
} else {
return Optional.empty();
}
}
public static boolean isSwiftTarget(BuildTarget buildTarget) {
return buildTarget.getFlavors().contains(SWIFT_COMPANION_FLAVOR)
|| buildTarget.getFlavors().contains(SWIFT_COMPILE_FLAVOR);
}
@BuckStyleImmutable
@Value.Immutable
interface AbstractSwiftLibraryDescriptionArg
extends CommonDescriptionArg, HasDeclaredDeps, HasSrcs {
Optional<String> getModuleName();
ImmutableList<String> getCompilerFlags();
@Value.NaturalOrder
ImmutableSortedSet<FrameworkPath> getFrameworks();
@Value.NaturalOrder
ImmutableSortedSet<FrameworkPath> getLibraries();
Optional<Boolean> getEnableObjcInterop();
Optional<Pattern> getSupportedPlatformsRegex();
Optional<String> getSoname();
Optional<SourcePath> getBridgingHeader();
Optional<NativeLinkable.Linkage> getPreferredLinkage();
}
}