/* * Copyright 2013-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.cxx; import com.facebook.buck.android.AndroidPackageable; import com.facebook.buck.android.AndroidPackageableCollector; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.model.Flavor; import com.facebook.buck.model.Pair; 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.Arg; 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.RichStream; import com.google.common.base.Function; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; 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 com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Iterables; import java.nio.file.Path; import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.regex.Pattern; import java.util.stream.Stream; /** * An action graph representation of a C/C++ library from the target graph, providing the various * interfaces to make it consumable by C/C++ preprocessing and native linkable rules. */ public class CxxLibrary extends NoopBuildRule implements AbstractCxxLibrary, HasRuntimeDeps, NativeTestable, NativeLinkTarget { private final BuildRuleResolver ruleResolver; private final CxxDeps deps; private final CxxDeps exportedDeps; private final Predicate<CxxPlatform> headerOnly; private final Function<? super CxxPlatform, Iterable<? extends Arg>> exportedLinkerFlags; private final Function<? super CxxPlatform, NativeLinkableInput> linkTargetInput; private final Optional<Pattern> supportedPlatformsRegex; private final ImmutableSet<FrameworkPath> frameworks; private final ImmutableSet<FrameworkPath> libraries; private final Linkage linkage; private final boolean linkWhole; private final Optional<String> soname; private final ImmutableSortedSet<BuildTarget> tests; private final boolean canBeAsset; private final boolean reexportAllHeaderDependencies; /** * Whether Native Linkable dependencies should be propagated for the purpose of computing objects * to link at link time. Setting this to false makes this library invisible to linking, so it and * its link-time dependencies are ignored. */ private final boolean propagateLinkables; private final Map<Pair<Flavor, Linker.LinkableDepType>, NativeLinkableInput> nativeLinkableCache = new HashMap<>(); private final LoadingCache< CxxPreprocessables.CxxPreprocessorInputCacheKey, ImmutableMap<BuildTarget, CxxPreprocessorInput>> transitiveCxxPreprocessorInputCache = CxxPreprocessables.getTransitiveCxxPreprocessorInputCache(this); public CxxLibrary( BuildRuleParams params, BuildRuleResolver ruleResolver, CxxDeps deps, CxxDeps exportedDeps, Predicate<CxxPlatform> headerOnly, Function<? super CxxPlatform, Iterable<? extends Arg>> exportedLinkerFlags, Function<? super CxxPlatform, NativeLinkableInput> linkTargetInput, Optional<Pattern> supportedPlatformsRegex, ImmutableSet<FrameworkPath> frameworks, ImmutableSet<FrameworkPath> libraries, Linkage linkage, boolean linkWhole, Optional<String> soname, ImmutableSortedSet<BuildTarget> tests, boolean canBeAsset, boolean propagateLinkables, boolean reexportAllHeaderDependencies) { super(params); this.ruleResolver = ruleResolver; this.deps = deps; this.exportedDeps = exportedDeps; this.headerOnly = headerOnly; this.exportedLinkerFlags = exportedLinkerFlags; this.linkTargetInput = linkTargetInput; this.supportedPlatformsRegex = supportedPlatformsRegex; this.frameworks = frameworks; this.libraries = libraries; this.linkage = linkage; this.linkWhole = linkWhole; this.soname = soname; this.tests = tests; this.canBeAsset = canBeAsset; this.propagateLinkables = propagateLinkables; this.reexportAllHeaderDependencies = reexportAllHeaderDependencies; } private boolean isPlatformSupported(CxxPlatform cxxPlatform) { return !supportedPlatformsRegex.isPresent() || supportedPlatformsRegex.get().matcher(cxxPlatform.getFlavor().toString()).find(); } @Override public Iterable<? extends CxxPreprocessorDep> getCxxPreprocessorDeps(CxxPlatform cxxPlatform) { if (!isPlatformSupported(cxxPlatform)) { return ImmutableList.of(); } return RichStream.from(exportedDeps.get(ruleResolver, cxxPlatform)) .concat( this.reexportAllHeaderDependencies ? RichStream.from(deps.get(ruleResolver, cxxPlatform)) : Stream.empty()) .filter(CxxPreprocessorDep.class) .toImmutableList(); } @Override public CxxPreprocessorInput getCxxPreprocessorInput( CxxPlatform cxxPlatform, HeaderVisibility headerVisibility) throws NoSuchBuildTargetException { return ruleResolver .requireMetadata( getBuildTarget() .withAppendedFlavors( CxxLibraryDescription.MetadataType.CXX_PREPROCESSOR_INPUT.getFlavor(), cxxPlatform.getFlavor(), headerVisibility.getFlavor()), CxxPreprocessorInput.class) .orElseThrow(IllegalStateException::new); } @Override public ImmutableMap<BuildTarget, CxxPreprocessorInput> getTransitiveCxxPreprocessorInput( CxxPlatform cxxPlatform, HeaderVisibility headerVisibility) { return transitiveCxxPreprocessorInputCache.getUnchecked( ImmutableCxxPreprocessorInputCacheKey.of(cxxPlatform, headerVisibility)); } @Override public Iterable<NativeLinkable> getNativeLinkableDeps() { if (!propagateLinkables) { return ImmutableList.of(); } return RichStream.from(deps.getForAllPlatforms(ruleResolver)) .filter(NativeLinkable.class) .toImmutableList(); } @Override public Iterable<NativeLinkable> getNativeLinkableDepsForPlatform(CxxPlatform cxxPlatform) { if (!propagateLinkables) { return ImmutableList.of(); } if (!isPlatformSupported(cxxPlatform)) { return ImmutableList.of(); } return RichStream.from(deps.get(ruleResolver, cxxPlatform)) .filter(NativeLinkable.class) .toImmutableList(); } @Override public Iterable<? extends NativeLinkable> getNativeLinkableExportedDeps() { if (!propagateLinkables) { return ImmutableList.of(); } return RichStream.from(exportedDeps.getForAllPlatforms(ruleResolver)) .filter(NativeLinkable.class) .toImmutableList(); } @Override public Iterable<? extends NativeLinkable> getNativeLinkableExportedDepsForPlatform( CxxPlatform cxxPlatform) { if (!propagateLinkables) { return ImmutableList.of(); } if (!isPlatformSupported(cxxPlatform)) { return ImmutableList.of(); } return RichStream.from(exportedDeps.get(ruleResolver, cxxPlatform)) .filter(NativeLinkable.class) .toImmutableList(); } private NativeLinkableInput getNativeLinkableInputUncached( CxxPlatform cxxPlatform, Linker.LinkableDepType type) throws NoSuchBuildTargetException { if (!isPlatformSupported(cxxPlatform)) { return NativeLinkableInput.of(); } // Build up the arguments used to link this library. If we're linking the // whole archive, wrap the library argument in the necessary "ld" flags. ImmutableList.Builder<Arg> linkerArgsBuilder = ImmutableList.builder(); linkerArgsBuilder.addAll(Preconditions.checkNotNull(exportedLinkerFlags.apply(cxxPlatform))); if (!headerOnly.apply(cxxPlatform) && propagateLinkables) { boolean isStatic; switch (linkage) { case STATIC: isStatic = true; break; case SHARED: isStatic = false; break; case ANY: isStatic = type != Linker.LinkableDepType.SHARED; break; default: throw new IllegalStateException("unhandled linkage type: " + linkage); } if (isStatic) { Archive archive = (Archive) requireBuildRule( cxxPlatform.getFlavor(), type == Linker.LinkableDepType.STATIC ? CxxDescriptionEnhancer.STATIC_FLAVOR : CxxDescriptionEnhancer.STATIC_PIC_FLAVOR); if (linkWhole) { Linker linker = cxxPlatform.getLd().resolve(ruleResolver); linkerArgsBuilder.addAll(linker.linkWhole(archive.toArg())); } else { Arg libraryArg = archive.toArg(); if (libraryArg instanceof SourcePathArg) { linkerArgsBuilder.add( FileListableLinkerInputArg.withSourcePathArg((SourcePathArg) libraryArg)); } else { linkerArgsBuilder.add(libraryArg); } } } else { BuildRule rule = requireBuildRule( cxxPlatform.getFlavor(), cxxPlatform.getSharedLibraryInterfaceFactory().isPresent() ? CxxLibraryDescription.Type.SHARED_INTERFACE.getFlavor() : CxxLibraryDescription.Type.SHARED.getFlavor()); linkerArgsBuilder.add( SourcePathArg.of(Preconditions.checkNotNull(rule.getSourcePathToOutput()))); } } final ImmutableList<Arg> linkerArgs = linkerArgsBuilder.build(); return NativeLinkableInput.of( linkerArgs, Preconditions.checkNotNull(frameworks), Preconditions.checkNotNull(libraries)); } @Override public NativeLinkableInput getNativeLinkableInput( CxxPlatform cxxPlatform, Linker.LinkableDepType type) throws NoSuchBuildTargetException { Pair<Flavor, Linker.LinkableDepType> key = new Pair<>(cxxPlatform.getFlavor(), type); NativeLinkableInput input = nativeLinkableCache.get(key); if (input == null) { input = getNativeLinkableInputUncached(cxxPlatform, type); nativeLinkableCache.put(key, input); } return input; } public BuildRule requireBuildRule(Flavor... flavors) throws NoSuchBuildTargetException { return ruleResolver.requireRule(getBuildTarget().withAppendedFlavors(flavors)); } @Override public NativeLinkable.Linkage getPreferredLinkage(CxxPlatform cxxPlatform) { return linkage; } @Override public Iterable<AndroidPackageable> getRequiredPackageables() { return AndroidPackageableCollector.getPackageableRules( RichStream.from( Iterables.concat( deps.getForAllPlatforms(ruleResolver), exportedDeps.getForAllPlatforms(ruleResolver))) .toImmutableList()); } @Override public void addToCollector(AndroidPackageableCollector collector) { if (canBeAsset) { collector.addNativeLinkableAsset(this); } else { collector.addNativeLinkable(this); } } @Override public ImmutableMap<String, SourcePath> getSharedLibraries(CxxPlatform cxxPlatform) throws NoSuchBuildTargetException { if (headerOnly.apply(cxxPlatform)) { return ImmutableMap.of(); } if (!isPlatformSupported(cxxPlatform)) { return ImmutableMap.of(); } ImmutableMap.Builder<String, SourcePath> libs = ImmutableMap.builder(); String sharedLibrarySoname = CxxDescriptionEnhancer.getSharedLibrarySoname(soname, getBuildTarget(), cxxPlatform); BuildRule sharedLibraryBuildRule = requireBuildRule(cxxPlatform.getFlavor(), CxxDescriptionEnhancer.SHARED_FLAVOR); libs.put(sharedLibrarySoname, sharedLibraryBuildRule.getSourcePathToOutput()); return libs.build(); } @Override public boolean isTestedBy(BuildTarget testTarget) { return tests.contains(testTarget); } @Override public NativeLinkTargetMode getNativeLinkTargetMode(CxxPlatform cxxPlatform) { return NativeLinkTargetMode.library( CxxDescriptionEnhancer.getSharedLibrarySoname(soname, getBuildTarget(), cxxPlatform)); } @Override public Iterable<? extends NativeLinkable> getNativeLinkTargetDeps(CxxPlatform cxxPlatform) { return Iterables.concat( getNativeLinkableDepsForPlatform(cxxPlatform), getNativeLinkableExportedDepsForPlatform(cxxPlatform)); } @Override public NativeLinkableInput getNativeLinkTargetInput(CxxPlatform cxxPlatform) { return linkTargetInput.apply(cxxPlatform); } @Override public Optional<Path> getNativeLinkTargetOutputPath(CxxPlatform cxxPlatform) { return Optional.empty(); } @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 RichStream.from(getDeclaredDeps().stream()) .concat(exportedDeps.getForAllPlatforms(ruleResolver).stream()) .map(BuildRule::getBuildTarget); } public Iterable<? extends Arg> getExportedLinkerFlags(CxxPlatform cxxPlatform) { return exportedLinkerFlags.apply(cxxPlatform); } }