/*
* 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 static com.facebook.buck.swift.SwiftLibraryDescription.SWIFT_COMPANION_FLAVOR;
import static com.facebook.buck.swift.SwiftLibraryDescription.SWIFT_COMPILE_FLAVOR;
import com.facebook.buck.cxx.CxxDescriptionEnhancer;
import com.facebook.buck.cxx.CxxHeadersDir;
import com.facebook.buck.cxx.CxxLink;
import com.facebook.buck.cxx.CxxPlatform;
import com.facebook.buck.cxx.CxxPreprocessables;
import com.facebook.buck.cxx.CxxPreprocessorDep;
import com.facebook.buck.cxx.CxxPreprocessorInput;
import com.facebook.buck.cxx.HeaderVisibility;
import com.facebook.buck.cxx.ImmutableCxxPreprocessorInputCacheKey;
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.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.HasRuntimeDeps;
import com.facebook.buck.rules.NoopBuildRule;
import com.facebook.buck.rules.SourcePath;
import com.facebook.buck.rules.args.FileListableLinkerInputArg;
import com.facebook.buck.rules.args.SourcePathArg;
import com.facebook.buck.rules.coercer.FrameworkPath;
import com.facebook.buck.util.MoreCollectors;
import com.facebook.buck.util.RichStream;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.util.Collection;
import java.util.Optional;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
/**
* An action graph representation of a Swift library from the target graph, providing the various
* interfaces to make it consumable by C/C native linkable rules.
*/
class SwiftLibrary extends NoopBuildRule
implements HasRuntimeDeps, NativeLinkable, CxxPreprocessorDep {
private final LoadingCache<
CxxPreprocessables.CxxPreprocessorInputCacheKey,
ImmutableMap<BuildTarget, CxxPreprocessorInput>>
transitiveCxxPreprocessorInputCache =
CxxPreprocessables.getTransitiveCxxPreprocessorInputCache(this);
private final BuildRuleResolver ruleResolver;
private final Collection<? extends BuildRule> exportedDeps;
private final ImmutableSet<FrameworkPath> frameworks;
private final ImmutableSet<FrameworkPath> libraries;
private final FlavorDomain<SwiftPlatform> swiftPlatformFlavorDomain;
private final Optional<Pattern> supportedPlatformsRegex;
private final Linkage linkage;
SwiftLibrary(
BuildRuleParams params,
BuildRuleResolver ruleResolver,
Collection<? extends BuildRule> exportedDeps,
FlavorDomain<SwiftPlatform> swiftPlatformFlavorDomain,
ImmutableSet<FrameworkPath> frameworks,
ImmutableSet<FrameworkPath> libraries,
Optional<Pattern> supportedPlatformsRegex,
Linkage linkage) {
super(params);
this.ruleResolver = ruleResolver;
this.exportedDeps = exportedDeps;
this.frameworks = frameworks;
this.libraries = libraries;
this.swiftPlatformFlavorDomain = swiftPlatformFlavorDomain;
this.supportedPlatformsRegex = supportedPlatformsRegex;
this.linkage = linkage;
}
private boolean isPlatformSupported(CxxPlatform cxxPlatform) {
return !supportedPlatformsRegex.isPresent()
|| supportedPlatformsRegex.get().matcher(cxxPlatform.getFlavor().toString()).find();
}
@Override
public Iterable<NativeLinkable> getNativeLinkableDeps() {
// TODO(beng, markwang): Use pseudo targets to represent the Swift
// runtime library's linker args here so NativeLinkables can
// deduplicate the linker flags on the build target (which would be the same for
// all libraries).
return RichStream.from(getDeclaredDeps())
.filter(NativeLinkable.class)
.collect(MoreCollectors.toImmutableSet());
}
@Override
public Iterable<? extends NativeLinkable> getNativeLinkableExportedDeps() {
throw new RuntimeException(
"SwiftLibrary does not support getting linkable exported deps "
+ "without a specific platform.");
}
@Override
public Iterable<? extends NativeLinkable> getNativeLinkableExportedDepsForPlatform(
CxxPlatform cxxPlatform) {
if (!isPlatformSupported(cxxPlatform)) {
return ImmutableList.of();
}
SwiftRuntimeNativeLinkable swiftRuntimeNativeLinkable =
new SwiftRuntimeNativeLinkable(swiftPlatformFlavorDomain.getValue(cxxPlatform.getFlavor()));
return RichStream.from(exportedDeps)
.filter(NativeLinkable.class)
.concat(RichStream.of(swiftRuntimeNativeLinkable))
.collect(MoreCollectors.toImmutableSet());
}
@Override
public NativeLinkableInput getNativeLinkableInput(
CxxPlatform cxxPlatform, Linker.LinkableDepType type) throws NoSuchBuildTargetException {
SwiftCompile rule = requireSwiftCompileRule(cxxPlatform.getFlavor());
NativeLinkableInput.Builder inputBuilder = NativeLinkableInput.builder();
inputBuilder
.addAllArgs(rule.getAstLinkArgs())
.addAllFrameworks(frameworks)
.addAllLibraries(libraries);
boolean isDynamic;
Linkage preferredLinkage = getPreferredLinkage(cxxPlatform);
switch (preferredLinkage) {
case STATIC:
isDynamic = false;
break;
case SHARED:
isDynamic = true;
break;
case ANY:
isDynamic = type == Linker.LinkableDepType.SHARED;
break;
default:
throw new IllegalStateException("unhandled linkage type: " + preferredLinkage);
}
if (isDynamic) {
CxxLink swiftLinkRule = requireSwiftLinkRule(cxxPlatform.getFlavor());
inputBuilder.addArgs(
FileListableLinkerInputArg.withSourcePathArg(
SourcePathArg.of(swiftLinkRule.getSourcePathToOutput())));
} else {
inputBuilder.addArgs(rule.getFileListLinkArg());
}
return inputBuilder.build();
}
@Override
public ImmutableMap<String, SourcePath> getSharedLibraries(CxxPlatform cxxPlatform)
throws NoSuchBuildTargetException {
if (!isPlatformSupported(cxxPlatform)) {
return ImmutableMap.of();
}
ImmutableMap.Builder<String, SourcePath> libs = ImmutableMap.builder();
BuildRule sharedLibraryBuildRule = requireSwiftLinkRule(cxxPlatform.getFlavor());
String sharedLibrarySoname =
CxxDescriptionEnhancer.getSharedLibrarySoname(
Optional.empty(), sharedLibraryBuildRule.getBuildTarget(), cxxPlatform);
libs.put(sharedLibrarySoname, sharedLibraryBuildRule.getSourcePathToOutput());
return libs.build();
}
SwiftCompile requireSwiftCompileRule(Flavor... flavors) throws NoSuchBuildTargetException {
BuildTarget requiredBuildTarget =
getBuildTarget()
.withAppendedFlavors(flavors)
.withoutFlavors(ImmutableSet.of(CxxDescriptionEnhancer.SHARED_FLAVOR))
.withoutFlavors(ImmutableSet.of(SWIFT_COMPANION_FLAVOR))
.withoutFlavors(LinkerMapMode.FLAVOR_DOMAIN.getFlavors())
.withAppendedFlavors(SWIFT_COMPILE_FLAVOR);
BuildRule rule = ruleResolver.requireRule(requiredBuildTarget);
if (!(rule instanceof SwiftCompile)) {
throw new RuntimeException(
String.format("Could not find SwiftCompile with target %s", requiredBuildTarget));
}
return (SwiftCompile) rule;
}
private CxxLink requireSwiftLinkRule(Flavor... flavors) throws NoSuchBuildTargetException {
BuildTarget requiredBuildTarget =
getBuildTarget()
.withoutFlavors(SWIFT_COMPANION_FLAVOR)
.withAppendedFlavors(CxxDescriptionEnhancer.SHARED_FLAVOR)
.withAppendedFlavors(flavors);
BuildRule rule = ruleResolver.requireRule(requiredBuildTarget);
if (!(rule instanceof CxxLink)) {
throw new RuntimeException(
String.format("Could not find CxxLink with target %s", requiredBuildTarget));
}
return (CxxLink) rule;
}
@Override
public NativeLinkable.Linkage getPreferredLinkage(CxxPlatform cxxPlatform) {
// don't create dylib for swift companion target.
if (getBuildTarget().getFlavors().contains(SWIFT_COMPANION_FLAVOR)) {
return Linkage.STATIC;
} else {
return linkage;
}
}
@Override
public Stream<BuildTarget> getRuntimeDeps() {
// We export all declared deps as runtime deps, to setup a transitive runtime dep chain which
// will pull in runtime deps (e.g. other binaries) or transitive C/C++ libraries. Since the
// `CxxLibrary` rules themselves are noop meta rules, they shouldn't add any unnecessary
// overhead.
return Stream.concat(
getDeclaredDeps().stream(), StreamSupport.stream(exportedDeps.spliterator(), false))
.map(BuildRule::getBuildTarget);
}
@Override
public Iterable<? extends CxxPreprocessorDep> getCxxPreprocessorDeps(CxxPlatform cxxPlatform) {
return getBuildDeps()
.stream()
.filter(CxxPreprocessorDep.class::isInstance)
.map(CxxPreprocessorDep.class::cast)
.collect(MoreCollectors.toImmutableSet());
}
@Override
public CxxPreprocessorInput getCxxPreprocessorInput(
CxxPlatform cxxPlatform, HeaderVisibility headerVisibility)
throws NoSuchBuildTargetException {
if (!isPlatformSupported(cxxPlatform)) {
return CxxPreprocessorInput.EMPTY;
}
BuildRule rule = requireSwiftCompileRule(cxxPlatform.getFlavor());
return CxxPreprocessorInput.builder()
.addIncludes(
CxxHeadersDir.of(CxxPreprocessables.IncludeType.LOCAL, rule.getSourcePathToOutput()))
.build();
}
@Override
public ImmutableMap<BuildTarget, CxxPreprocessorInput> getTransitiveCxxPreprocessorInput(
CxxPlatform cxxPlatform, HeaderVisibility headerVisibility)
throws NoSuchBuildTargetException {
if (getBuildTarget().getFlavors().contains(SWIFT_COMPANION_FLAVOR)) {
return ImmutableMap.of(
getBuildTarget(), getCxxPreprocessorInput(cxxPlatform, headerVisibility));
} else {
return transitiveCxxPreprocessorInputCache.getUnchecked(
ImmutableCxxPreprocessorInputCacheKey.of(cxxPlatform, headerVisibility));
}
}
}