/* * Copyright 2014-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.io.ProjectFilesystem; 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.InternalFlavor; import com.facebook.buck.model.MacroException; import com.facebook.buck.model.MacroFinder; 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.BuildTargetSourcePath; import com.facebook.buck.rules.CellPathResolver; import com.facebook.buck.rules.CommonDescriptionArg; import com.facebook.buck.rules.Description; import com.facebook.buck.rules.ExplicitBuildTargetSourcePath; import com.facebook.buck.rules.HasDeclaredDeps; import com.facebook.buck.rules.ImplicitDepsInferringDescription; import com.facebook.buck.rules.PathSourcePath; 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.FileListableLinkerInputArg; import com.facebook.buck.rules.args.SourcePathArg; import com.facebook.buck.rules.args.StringArg; import com.facebook.buck.rules.coercer.FrameworkPath; import com.facebook.buck.rules.coercer.PatternMatchedCollection; import com.facebook.buck.rules.coercer.SourceList; import com.facebook.buck.rules.coercer.VersionMatchedCollection; import com.facebook.buck.rules.macros.LocationMacroExpander; import com.facebook.buck.rules.macros.MacroHandler; import com.facebook.buck.rules.macros.StringExpander; import com.facebook.buck.util.HumanReadableException; import com.facebook.buck.util.MoreCollectors; import com.facebook.buck.util.Optionals; import com.facebook.buck.util.immutables.BuckStyleImmutable; import com.facebook.buck.versions.Version; import com.facebook.buck.versions.VersionPropagator; import com.google.common.base.Preconditions; import com.google.common.cache.LoadingCache; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableListMultimap; 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.nio.file.Paths; import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.regex.Pattern; import org.immutables.value.Value; public class PrebuiltCxxLibraryDescription implements Description<PrebuiltCxxLibraryDescriptionArg>, ImplicitDepsInferringDescription< PrebuiltCxxLibraryDescription.AbstractPrebuiltCxxLibraryDescriptionArg>, VersionPropagator<PrebuiltCxxLibraryDescriptionArg> { private static final MacroFinder MACRO_FINDER = new MacroFinder(); enum Type implements FlavorConvertible { EXPORTED_HEADERS(CxxDescriptionEnhancer.EXPORTED_HEADER_SYMLINK_TREE_FLAVOR), SHARED(CxxDescriptionEnhancer.SHARED_FLAVOR), SHARED_INTERFACE(InternalFlavor.of("shared-interface")); 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("C/C++ Library Type", Type.class); private final CxxBuckConfig cxxBuckConfig; private final FlavorDomain<CxxPlatform> cxxPlatforms; public PrebuiltCxxLibraryDescription( CxxBuckConfig cxxBuckConfig, FlavorDomain<CxxPlatform> cxxPlatforms) { this.cxxBuckConfig = cxxBuckConfig; this.cxxPlatforms = cxxPlatforms; } @Override public Class<PrebuiltCxxLibraryDescriptionArg> getConstructorArgType() { return PrebuiltCxxLibraryDescriptionArg.class; } // Using the {@code MACRO_FINDER} above, return the given string with any `platform` or // `location` macros replaced with the name of the given platform or build rule location. private static String expandMacros( MacroHandler handler, BuildTarget target, CellPathResolver cellNames, BuildRuleResolver ruleResolver, String arg) { try { return MACRO_FINDER.replace( handler.getMacroReplacers(target, cellNames, ruleResolver), arg, true); } catch (MacroException e) { throw new HumanReadableException("%s: %s", target, e.getMessage()); } } // Platform unlike most macro expanders needs access to the cxx build flavor. // Because of that it can't be like normal expanders. So just create a handler here. private static MacroHandler getMacroHandler(final Optional<CxxPlatform> cxxPlatform) { String flav = cxxPlatform.map(input -> input.getFlavor().toString()).orElse(""); return new MacroHandler( ImmutableMap.of( "location", new LocationMacroExpander(), "platform", new StringExpander(flav))); } private static SourcePath getApplicableSourcePath( final BuildTarget target, final CellPathResolver cellRoots, final ProjectFilesystem filesystem, final BuildRuleResolver ruleResolver, final CxxPlatform cxxPlatform, Optional<String> versionSubDir, final String basePathString, final Optional<String> addedPathString) { ImmutableList<BuildRule> deps; MacroHandler handler = getMacroHandler(Optional.of(cxxPlatform)); try { deps = handler.extractBuildTimeDeps(target, cellRoots, ruleResolver, basePathString); } catch (MacroException e) { deps = ImmutableList.of(); } Path libDirPath = filesystem.getPath(expandMacros(handler, target, cellRoots, ruleResolver, basePathString)); if (versionSubDir.isPresent()) { libDirPath = filesystem.getPath(versionSubDir.get()).resolve(libDirPath); } // If there are no deps then this is just referencing a path that should already be there // So just expand the macros and return a PathSourcePath if (deps.isEmpty()) { Path resultPath = libDirPath; if (addedPathString.isPresent()) { resultPath = libDirPath.resolve( expandMacros(handler, target, cellRoots, ruleResolver, addedPathString.get())); } resultPath = target.getBasePath().resolve(resultPath); return new PathSourcePath(filesystem, resultPath); } // If we get here then this is referencing the output from a build rule. // This always return a ExplicitBuildTargetSourcePath Path p = filesystem.resolve(libDirPath); if (addedPathString.isPresent()) { p = p.resolve(addedPathString.get()); } p = filesystem.relativize(p); return new ExplicitBuildTargetSourcePath(deps.iterator().next().getBuildTarget(), p); } public static String getSoname( BuildTarget target, CellPathResolver cellNames, BuildRuleResolver ruleResolver, CxxPlatform cxxPlatform, Optional<String> soname, Optional<String> libName) { String unexpanded = soname.orElse( String.format( "lib%s.%s", libName.orElse(target.getShortName()), cxxPlatform.getSharedLibraryExtension())); return expandMacros( getMacroHandler(Optional.of(cxxPlatform)), target, cellNames, ruleResolver, unexpanded); } private static SourcePath getLibraryPath( BuildTarget target, CellPathResolver cellRoots, ProjectFilesystem filesystem, BuildRuleResolver ruleResolver, CxxPlatform cxxPlatform, Optional<String> versionSubDir, Optional<String> libDir, Optional<String> libName, String suffix) { String libDirString = libDir.orElse("lib"); String fileNameString = String.format("lib%s%s", libName.orElse(target.getShortName()), suffix); return getApplicableSourcePath( target, cellRoots, filesystem, ruleResolver, cxxPlatform, versionSubDir, libDirString, Optional.of(fileNameString)); } static SourcePath getSharedLibraryPath( BuildTarget target, CellPathResolver cellNames, ProjectFilesystem filesystem, BuildRuleResolver ruleResolver, CxxPlatform cxxPlatform, Optional<String> versionSubDir, Optional<String> libDir, Optional<String> libName) { return getLibraryPath( target, cellNames, filesystem, ruleResolver, cxxPlatform, versionSubDir, libDir, libName, String.format(".%s", cxxPlatform.getSharedLibraryExtension())); } static SourcePath getStaticLibraryPath( BuildTarget target, CellPathResolver cellNames, ProjectFilesystem filesystem, BuildRuleResolver ruleResolver, CxxPlatform cxxPlatform, Optional<String> versionSubDir, Optional<String> libDir, Optional<String> libName) { return getLibraryPath( target, cellNames, filesystem, ruleResolver, cxxPlatform, versionSubDir, libDir, libName, ".a"); } private static SourcePath getStaticPicLibraryPath( BuildTarget target, CellPathResolver cellNames, final ProjectFilesystem filesystem, BuildRuleResolver ruleResolver, CxxPlatform cxxPlatform, Optional<String> versionSubDir, Optional<String> libDir, Optional<String> libName) { return getLibraryPath( target, cellNames, filesystem, ruleResolver, cxxPlatform, versionSubDir, libDir, libName, "_pic.a"); } /** * @return a {@link HeaderSymlinkTree} for the exported headers of this prebuilt C/C++ library. */ private static HeaderSymlinkTree createExportedHeaderSymlinkTreeBuildRule( BuildRuleParams params, BuildRuleResolver resolver, CxxPlatform cxxPlatform, PrebuiltCxxLibraryDescriptionArg args) throws NoSuchBuildTargetException { return CxxDescriptionEnhancer.createHeaderSymlinkTree( params, resolver, cxxPlatform, parseExportedHeaders(params, resolver, cxxPlatform, args), HeaderVisibility.PUBLIC, true); } private static ImmutableMap<Path, SourcePath> parseExportedHeaders( BuildRuleParams params, BuildRuleResolver resolver, CxxPlatform cxxPlatform, PrebuiltCxxLibraryDescriptionArg args) throws NoSuchBuildTargetException { ImmutableMap.Builder<String, SourcePath> headers = ImmutableMap.builder(); SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(resolver); SourcePathResolver pathResolver = new SourcePathResolver(ruleFinder); headers.putAll( CxxDescriptionEnhancer.parseOnlyHeaders( params.getBuildTarget(), ruleFinder, pathResolver, "exported_headers", args.getExportedHeaders())); headers.putAll( CxxDescriptionEnhancer.parseOnlyPlatformHeaders( params.getBuildTarget(), resolver, ruleFinder, pathResolver, cxxPlatform, "exported_headers", args.getExportedHeaders(), "exported_platform, headers", args.getExportedPlatformHeaders())); return CxxPreprocessables.resolveHeaderMap( args.getHeaderNamespace().map(Paths::get).orElse(params.getBuildTarget().getBasePath()), headers.build()); } /** @return a {@link CxxLink} rule for a shared library version of this prebuilt C/C++ library. */ private BuildRule createSharedLibraryBuildRule( BuildRuleParams params, BuildRuleResolver ruleResolver, CellPathResolver cellRoots, CxxPlatform cxxPlatform, Optional<ImmutableMap<BuildTarget, Version>> selectedVersions, PrebuiltCxxLibraryDescriptionArg args) throws NoSuchBuildTargetException { SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(ruleResolver); final SourcePathResolver pathResolver = new SourcePathResolver(ruleFinder); BuildTarget target = params.getBuildTarget(); String soname = getSoname( target, cellRoots, ruleResolver, cxxPlatform, args.getSoname(), args.getLibName()); Optional<String> versionSubDir = selectedVersions.isPresent() && args.getVersionedSubDir().isPresent() ? Optional.of( args.getVersionedSubDir().get().getOnlyMatchingValue(selectedVersions.get())) : Optional.empty(); // Use the static PIC variant, if available. SourcePath staticLibraryPath = getStaticPicLibraryPath( target, cellRoots, params.getProjectFilesystem(), ruleResolver, cxxPlatform, versionSubDir, args.getLibDir(), args.getLibName()); if (!params.getProjectFilesystem().exists(pathResolver.getAbsolutePath(staticLibraryPath))) { staticLibraryPath = getStaticLibraryPath( target, cellRoots, params.getProjectFilesystem(), ruleResolver, cxxPlatform, versionSubDir, args.getLibDir(), args.getLibName()); } // Otherwise, we need to build it from the static lib. BuildTarget sharedTarget = BuildTarget.builder(params.getBuildTarget()) .addFlavors(CxxDescriptionEnhancer.SHARED_FLAVOR) .build(); // If not, setup a single link rule to link it from the static lib. Path builtSharedLibraryPath = BuildTargets.getGenPath(params.getProjectFilesystem(), sharedTarget, "%s").resolve(soname); return CxxLinkableEnhancer.createCxxLinkableBuildRule( cxxBuckConfig, cxxPlatform, params .copyAppendingExtraDeps( getBuildRules( params.getBuildTarget(), cellRoots, ruleResolver, Optionals.toStream(args.getLibDir()).collect(MoreCollectors.toImmutableList()))) .copyAppendingExtraDeps( getBuildRules( params.getBuildTarget(), cellRoots, ruleResolver, args.getIncludeDirs())), ruleResolver, pathResolver, ruleFinder, sharedTarget, Linker.LinkType.SHARED, Optional.of(soname), builtSharedLibraryPath, Linker.LinkableDepType.SHARED, /* thinLto */ false, FluentIterable.from(params.getBuildDeps()).filter(NativeLinkable.class), Optional.empty(), Optional.empty(), ImmutableSet.of(), NativeLinkableInput.builder() .addAllArgs( StringArg.from( CxxFlags.getFlagsWithPlatformMacroExpansion( args.getExportedLinkerFlags(), args.getExportedPlatformLinkerFlags(), cxxPlatform))) .addAllArgs( cxxPlatform .getLd() .resolve(ruleResolver) .linkWhole(SourcePathArg.of(staticLibraryPath))) .build(), Optional.empty()); } /** * Makes sure all build rules needed to produce the shared library are added to the action graph. * * @return the {@link SourcePath} representing the actual shared library. */ private SourcePath requireSharedLibrary( BuildTarget target, BuildRuleResolver resolver, SourcePathResolver pathResolver, CellPathResolver cellRoots, ProjectFilesystem filesystem, CxxPlatform cxxPlatform, Optional<String> versionSubdir, PrebuiltCxxLibraryDescriptionArg args) throws NoSuchBuildTargetException { SourcePath sharedLibraryPath = PrebuiltCxxLibraryDescription.getSharedLibraryPath( target, cellRoots, filesystem, resolver, cxxPlatform, versionSubdir, args.getLibDir(), args.getLibName()); // If the shared library is prebuilt, just return a reference to it. // TODO(alisdair): this code misbehaves. whether the file exists should have been figured // out earlier during parsing/target graph creation, or it should be later when steps being // produced. This is preventing distributed build loading files lazily. if (sharedLibraryPath instanceof BuildTargetSourcePath || filesystem.exists(pathResolver.getAbsolutePath(sharedLibraryPath))) { return sharedLibraryPath; } // Otherwise, generate it's build rule. CxxLink sharedLibrary = (CxxLink) resolver.requireRule( target.withAppendedFlavors( cxxPlatform.getFlavor(), CxxDescriptionEnhancer.SHARED_FLAVOR)); return sharedLibrary.getSourcePathToOutput(); } private BuildRule createSharedLibraryInterface( BuildTarget baseTarget, BuildRuleParams baseParams, BuildRuleResolver resolver, CellPathResolver cellRoots, CxxPlatform cxxPlatform, Optional<String> versionSubdir, PrebuiltCxxLibraryDescriptionArg args) throws NoSuchBuildTargetException { if (!args.isSupportsSharedLibraryInterface()) { throw new HumanReadableException( "%s: rule does not support shared library interfaces", baseTarget, cxxPlatform.getFlavor()); } Optional<SharedLibraryInterfaceFactory> factory = cxxPlatform.getSharedLibraryInterfaceFactory(); if (!factory.isPresent()) { throw new HumanReadableException( "%s: C/C++ platform %s does not support shared library interfaces", baseTarget, cxxPlatform.getFlavor()); } SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(resolver); SourcePathResolver pathResolver = new SourcePathResolver(ruleFinder); SourcePath sharedLibrary = requireSharedLibrary( baseTarget, resolver, pathResolver, cellRoots, baseParams.getProjectFilesystem(), cxxPlatform, versionSubdir, args); return factory .get() .createSharedInterfaceLibrary( baseTarget.withAppendedFlavors( Type.SHARED_INTERFACE.getFlavor(), cxxPlatform.getFlavor()), baseParams, resolver, pathResolver, ruleFinder, sharedLibrary); } @Override public BuildRule createBuildRule( TargetGraph targetGraph, final BuildRuleParams params, final BuildRuleResolver ruleResolver, CellPathResolver cellRoots, final PrebuiltCxxLibraryDescriptionArg args) throws NoSuchBuildTargetException { // See if we're building a particular "type" of this library, and if so, extract // it as an enum. Optional<Map.Entry<Flavor, Type>> type = LIBRARY_TYPE.getFlavorAndValue(params.getBuildTarget()); Optional<Map.Entry<Flavor, CxxPlatform>> platform = cxxPlatforms.getFlavorAndValue(params.getBuildTarget()); Optional<ImmutableMap<BuildTarget, Version>> selectedVersions = targetGraph.get(params.getBuildTarget()).getSelectedVersions(); final Optional<String> versionSubdir = selectedVersions.isPresent() && args.getVersionedSubDir().isPresent() ? Optional.of( args.getVersionedSubDir().get().getOnlyMatchingValue(selectedVersions.get())) : Optional.empty(); // If we *are* building a specific type of this lib, call into the type specific // rule builder methods. Currently, we only support building a shared lib from the // pre-existing static lib, which we do here. if (type.isPresent()) { Preconditions.checkState(platform.isPresent()); BuildTarget baseTarget = params.getBuildTarget().withoutFlavors(type.get().getKey(), platform.get().getKey()); if (type.get().getValue() == Type.EXPORTED_HEADERS) { return createExportedHeaderSymlinkTreeBuildRule( params, ruleResolver, platform.get().getValue(), args); } else if (type.get().getValue() == Type.SHARED) { return createSharedLibraryBuildRule( params, ruleResolver, cellRoots, platform.get().getValue(), selectedVersions, args); } else if (type.get().getValue() == Type.SHARED_INTERFACE) { return createSharedLibraryInterface( baseTarget, params, ruleResolver, cellRoots, platform.get().getValue(), versionSubdir, args); } } if (selectedVersions.isPresent() && args.getVersionedSubDir().isPresent()) { ImmutableList<String> versionSubDirs = args.getVersionedSubDir() .orElse(VersionMatchedCollection.<String>of()) .getMatchingValues(selectedVersions.get()); if (versionSubDirs.size() != 1) { throw new HumanReadableException( "%s: could not get a single version sub dir: %s, %s, %s", params.getBuildTarget(), args.getVersionedSubDir(), versionSubDirs, selectedVersions); } } // Otherwise, we return the generic placeholder of this library, that dependents can use // get the real build rules via querying the action graph. final SourcePathResolver pathResolver = new SourcePathResolver(new SourcePathRuleFinder(ruleResolver)); final boolean headerOnly = args.getHeaderOnly().orElse(false); final boolean forceStatic = args.getForceStatic().orElse(false); return new PrebuiltCxxLibrary(params) { private final Map<Pair<Flavor, Linker.LinkableDepType>, NativeLinkableInput> nativeLinkableCache = new HashMap<>(); private final LoadingCache< CxxPreprocessables.CxxPreprocessorInputCacheKey, ImmutableMap<BuildTarget, CxxPreprocessorInput>> transitiveCxxPreprocessorInputCache = CxxPreprocessables.getTransitiveCxxPreprocessorInputCache(this); private boolean hasHeaders(CxxPlatform cxxPlatform) { if (!args.getExportedHeaders().isEmpty()) { return true; } for (SourceList sourceList : args.getExportedPlatformHeaders() .getMatchingValues(cxxPlatform.getFlavor().toString())) { if (!sourceList.isEmpty()) { return true; } } return false; } private ImmutableListMultimap<CxxSource.Type, String> getExportedPreprocessorFlags( CxxPlatform cxxPlatform) { return CxxFlags.getLanguageFlags( args.getExportedPreprocessorFlags(), args.getExportedPlatformPreprocessorFlags(), args.getExportedLangPreprocessorFlags(), cxxPlatform); } @Override public ImmutableList<String> getExportedLinkerFlags(CxxPlatform cxxPlatform) { return CxxFlags.getFlagsWithPlatformMacroExpansion( args.getExportedLinkerFlags(), args.getExportedPlatformLinkerFlags(), cxxPlatform); } private String getSoname(CxxPlatform cxxPlatform) { return PrebuiltCxxLibraryDescription.getSoname( getBuildTarget(), cellRoots, ruleResolver, cxxPlatform, args.getSoname(), args.getLibName()); } private boolean isPlatformSupported(CxxPlatform cxxPlatform) { return !args.getSupportedPlatformsRegex().isPresent() || args.getSupportedPlatformsRegex() .get() .matcher(cxxPlatform.getFlavor().toString()) .find(); } /** * Makes sure all build rules needed to produce the shared library are added to the action * graph. * * @return the {@link SourcePath} representing the actual shared library. */ private SourcePath requireSharedLibrary(CxxPlatform cxxPlatform, boolean link) throws NoSuchBuildTargetException { if (link && args.isSupportsSharedLibraryInterface() && cxxPlatform.getSharedLibraryInterfaceFactory().isPresent()) { BuildTarget target = params .getBuildTarget() .withAppendedFlavors(cxxPlatform.getFlavor(), Type.SHARED_INTERFACE.getFlavor()); BuildRule rule = ruleResolver.requireRule(target); return Preconditions.checkNotNull(rule.getSourcePathToOutput()); } return PrebuiltCxxLibraryDescription.this.requireSharedLibrary( params.getBuildTarget(), ruleResolver, pathResolver, cellRoots, params.getProjectFilesystem(), cxxPlatform, versionSubdir, args); } /** * @return the {@link Optional} containing a {@link SourcePath} representing the actual static * PIC library. */ private Optional<SourcePath> getStaticPicLibrary(CxxPlatform cxxPlatform) { SourcePath staticPicLibraryPath = PrebuiltCxxLibraryDescription.getStaticPicLibraryPath( getBuildTarget(), cellRoots, params.getProjectFilesystem(), ruleResolver, cxxPlatform, versionSubdir, args.getLibDir(), args.getLibName()); if (params .getProjectFilesystem() .exists(pathResolver.getAbsolutePath(staticPicLibraryPath))) { return Optional.of(staticPicLibraryPath); } // If a specific static-pic variant isn't available, then just use the static variant. SourcePath staticLibraryPath = PrebuiltCxxLibraryDescription.getStaticLibraryPath( getBuildTarget(), cellRoots, getProjectFilesystem(), ruleResolver, cxxPlatform, versionSubdir, args.getLibDir(), args.getLibName()); if (params.getProjectFilesystem().exists(pathResolver.getAbsolutePath(staticLibraryPath))) { return Optional.of(staticLibraryPath); } return Optional.empty(); } @Override public Iterable<? extends CxxPreprocessorDep> getCxxPreprocessorDeps( CxxPlatform cxxPlatform) { if (!isPlatformSupported(cxxPlatform)) { return ImmutableList.of(); } return FluentIterable.from(getBuildDeps()).filter(CxxPreprocessorDep.class); } @Override public CxxPreprocessorInput getCxxPreprocessorInput( final CxxPlatform cxxPlatform, HeaderVisibility headerVisibility) throws NoSuchBuildTargetException { CxxPreprocessorInput.Builder builder = CxxPreprocessorInput.builder(); switch (headerVisibility) { case PUBLIC: if (hasHeaders(cxxPlatform)) { CxxPreprocessables.addHeaderSymlinkTree( builder, getBuildTarget(), ruleResolver, cxxPlatform, headerVisibility, CxxPreprocessables.IncludeType.SYSTEM); } builder.putAllPreprocessorFlags( Preconditions.checkNotNull(getExportedPreprocessorFlags(cxxPlatform))); builder.addAllFrameworks(args.getFrameworks()); final Iterable<SourcePath> includePaths = args.getIncludeDirs() .stream() .map( input -> PrebuiltCxxLibraryDescription.getApplicableSourcePath( params.getBuildTarget(), cellRoots, params.getProjectFilesystem(), ruleResolver, cxxPlatform, versionSubdir, input, Optional.empty())) .collect(MoreCollectors.toImmutableList()); for (SourcePath includePath : includePaths) { builder.addIncludes( CxxHeadersDir.of(CxxPreprocessables.IncludeType.SYSTEM, includePath)); } return builder.build(); case PRIVATE: return builder.build(); } // We explicitly don't put this in a default statement because we // want the compiler to warn if someone modifies the HeaderVisibility enum. throw new RuntimeException("Invalid header visibility: " + headerVisibility); } @Override public ImmutableMap<BuildTarget, CxxPreprocessorInput> getTransitiveCxxPreprocessorInput( CxxPlatform cxxPlatform, HeaderVisibility headerVisibility) throws NoSuchBuildTargetException { return transitiveCxxPreprocessorInputCache.getUnchecked( ImmutableCxxPreprocessorInputCacheKey.of(cxxPlatform, headerVisibility)); } @Override public Iterable<NativeLinkable> getNativeLinkableDeps() { return getDeclaredDeps() .stream() .filter(r -> r instanceof NativeLinkable) .map(r -> (NativeLinkable) r) .collect(MoreCollectors.toImmutableList()); } @Override public Iterable<NativeLinkable> getNativeLinkableDepsForPlatform(CxxPlatform cxxPlatform) { if (!isPlatformSupported(cxxPlatform)) { return ImmutableList.of(); } return getNativeLinkableDeps(); } @Override public Iterable<? extends NativeLinkable> getNativeLinkableExportedDeps() { return args.getExportedDeps() .stream() .map(ruleResolver::getRule) .filter(r -> r instanceof NativeLinkable) .map(r -> (NativeLinkable) r) .collect(MoreCollectors.toImmutableList()); } @Override public Iterable<? extends NativeLinkable> getNativeLinkableExportedDepsForPlatform( CxxPlatform cxxPlatform) { if (!isPlatformSupported(cxxPlatform)) { return ImmutableList.of(); } return getNativeLinkableExportedDeps(); } private NativeLinkableInput getNativeLinkableInputUncached( CxxPlatform cxxPlatform, Linker.LinkableDepType type) throws NoSuchBuildTargetException { if (!isPlatformSupported(cxxPlatform)) { return NativeLinkableInput.of(); } // Build the library path and linker arguments that we pass through the // {@link NativeLinkable} interface for linking. ImmutableList.Builder<com.facebook.buck.rules.args.Arg> linkerArgsBuilder = ImmutableList.builder(); linkerArgsBuilder.addAll( StringArg.from(Preconditions.checkNotNull(getExportedLinkerFlags(cxxPlatform)))); if (!headerOnly) { if (type == Linker.LinkableDepType.SHARED) { Preconditions.checkState(getPreferredLinkage(cxxPlatform) != Linkage.STATIC); final SourcePath sharedLibrary = requireSharedLibrary(cxxPlatform, true); if (args.getLinkWithoutSoname()) { if (!(sharedLibrary instanceof PathSourcePath)) { throw new HumanReadableException( "%s: can only link prebuilt DSOs without sonames", getBuildTarget()); } linkerArgsBuilder.add(new RelativeLinkArg((PathSourcePath) sharedLibrary)); } else { linkerArgsBuilder.add(SourcePathArg.of(requireSharedLibrary(cxxPlatform, true))); } } else { Preconditions.checkState(getPreferredLinkage(cxxPlatform) != Linkage.SHARED); SourcePath staticLibraryPath = type == Linker.LinkableDepType.STATIC_PIC ? getStaticPicLibrary(cxxPlatform).get() : PrebuiltCxxLibraryDescription.getStaticLibraryPath( getBuildTarget(), cellRoots, params.getProjectFilesystem(), ruleResolver, cxxPlatform, versionSubdir, args.getLibDir(), args.getLibName()); SourcePathArg staticLibrary = SourcePathArg.of(staticLibraryPath); if (args.isLinkWhole()) { Linker linker = cxxPlatform.getLd().resolve(ruleResolver); linkerArgsBuilder.addAll(linker.linkWhole(staticLibrary)); } else { linkerArgsBuilder.add(FileListableLinkerInputArg.withSourcePathArg(staticLibrary)); } } } final ImmutableList<com.facebook.buck.rules.args.Arg> linkerArgs = linkerArgsBuilder.build(); return NativeLinkableInput.of(linkerArgs, args.getFrameworks(), args.getLibraries()); } @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; } @Override public NativeLinkable.Linkage getPreferredLinkage(CxxPlatform cxxPlatform) { if (headerOnly) { return Linkage.ANY; } else if (forceStatic) { return Linkage.STATIC; } else if (args.getPreferredLinkage().orElse(Linkage.ANY) != Linkage.ANY) { return args.getPreferredLinkage().get(); } else if (args.isProvided() || !getStaticPicLibrary(cxxPlatform).isPresent()) { return Linkage.SHARED; } else { return Linkage.ANY; } } @Override public Iterable<AndroidPackageable> getRequiredPackageables() { return AndroidPackageableCollector.getPackageableRules(params.getBuildDeps()); } @Override public void addToCollector(AndroidPackageableCollector collector) { if (args.getCanBeAsset()) { collector.addNativeLinkableAsset(this); } else { collector.addNativeLinkable(this); } } @Override public ImmutableMap<String, SourcePath> getSharedLibraries(CxxPlatform cxxPlatform) throws NoSuchBuildTargetException { if (!isPlatformSupported(cxxPlatform)) { return ImmutableMap.of(); } String resolvedSoname = getSoname(cxxPlatform); ImmutableMap.Builder<String, SourcePath> solibs = ImmutableMap.builder(); if (!headerOnly && !args.isProvided()) { SourcePath sharedLibrary = requireSharedLibrary(cxxPlatform, false); solibs.put(resolvedSoname, sharedLibrary); } return solibs.build(); } @Override public Optional<NativeLinkTarget> getNativeLinkTarget(CxxPlatform cxxPlatform) { if (getPreferredLinkage(cxxPlatform) == Linkage.SHARED) { return Optional.empty(); } return Optional.of( new NativeLinkTarget() { @Override public BuildTarget getBuildTarget() { return params.getBuildTarget(); } @Override public NativeLinkTargetMode getNativeLinkTargetMode(CxxPlatform cxxPlatform) { return NativeLinkTargetMode.library(getSoname(cxxPlatform)); } @Override public Iterable<? extends NativeLinkable> getNativeLinkTargetDeps( CxxPlatform cxxPlatform) { return Iterables.concat( getNativeLinkableDepsForPlatform(cxxPlatform), getNativeLinkableExportedDepsForPlatform(cxxPlatform)); } @Override public NativeLinkableInput getNativeLinkTargetInput(CxxPlatform cxxPlatform) throws NoSuchBuildTargetException { return NativeLinkableInput.builder() .addAllArgs(StringArg.from(getExportedLinkerFlags(cxxPlatform))) .addAllArgs( cxxPlatform .getLd() .resolve(ruleResolver) .linkWhole(SourcePathArg.of(getStaticPicLibrary(cxxPlatform).get()))) .build(); } @Override public Optional<Path> getNativeLinkTargetOutputPath(CxxPlatform cxxPlatform) { return Optional.empty(); } }); } }; } @Override public void findDepsForTargetFromConstructorArgs( BuildTarget buildTarget, CellPathResolver cellRoots, AbstractPrebuiltCxxLibraryDescriptionArg constructorArg, ImmutableCollection.Builder<BuildTarget> extraDepsBuilder, ImmutableCollection.Builder<BuildTarget> targetGraphOnlyDepsBuilder) { if (constructorArg.getLibDir().isPresent()) { addDepsFromParam( buildTarget, cellRoots, constructorArg.getLibDir().get(), extraDepsBuilder, targetGraphOnlyDepsBuilder); } for (String include : constructorArg.getIncludeDirs()) { addDepsFromParam( buildTarget, cellRoots, include, extraDepsBuilder, targetGraphOnlyDepsBuilder); } } private ImmutableList<BuildRule> getBuildRules( BuildTarget target, CellPathResolver cellNames, BuildRuleResolver ruleResolver, Iterable<String> paramValues) { ImmutableList.Builder<BuildRule> builder = ImmutableList.builder(); MacroHandler macroHandler = getMacroHandler(Optional.empty()); for (String p : paramValues) { try { builder.addAll(macroHandler.extractBuildTimeDeps(target, cellNames, ruleResolver, p)); } catch (MacroException e) { throw new HumanReadableException(e, "%s : %s in \"%s\"", target, e.getMessage(), p); } } return builder.build(); } private void addDepsFromParam( BuildTarget target, CellPathResolver cellNames, String paramValue, ImmutableCollection.Builder<BuildTarget> buildDepsBuilder, ImmutableCollection.Builder<BuildTarget> targetGraphOnlyDepsBuilder) { try { // doesn't matter that the platform expander doesn't do anything. MacroHandler macroHandler = getMacroHandler(Optional.empty()); // Then get the parse time deps. macroHandler.extractParseTimeDeps( target, cellNames, paramValue, buildDepsBuilder, targetGraphOnlyDepsBuilder); } catch (MacroException e) { throw new HumanReadableException(e, "%s : %s in \"%s\"", target, e.getMessage(), paramValue); } } @BuckStyleImmutable @Value.Immutable interface AbstractPrebuiltCxxLibraryDescriptionArg extends CommonDescriptionArg, HasDeclaredDeps { ImmutableList<String> getIncludeDirs(); Optional<String> getLibName(); Optional<String> getLibDir(); Optional<Boolean> getHeaderOnly(); @Value.Default default SourceList getExportedHeaders() { return SourceList.EMPTY; } @Value.Default default PatternMatchedCollection<SourceList> getExportedPlatformHeaders() { return PatternMatchedCollection.of(); } Optional<String> getHeaderNamespace(); @Value.Default default boolean isProvided() { return false; } @Value.Default default boolean isLinkWhole() { return false; } Optional<Boolean> getForceStatic(); Optional<NativeLinkable.Linkage> getPreferredLinkage(); ImmutableList<String> getExportedPreprocessorFlags(); @Value.Default default PatternMatchedCollection<ImmutableList<String>> getExportedPlatformPreprocessorFlags() { return PatternMatchedCollection.of(); } ImmutableMap<CxxSource.Type, ImmutableList<String>> getExportedLangPreprocessorFlags(); ImmutableList<String> getExportedLinkerFlags(); @Value.Default default PatternMatchedCollection<ImmutableList<String>> getExportedPlatformLinkerFlags() { return PatternMatchedCollection.of(); } Optional<String> getSoname(); @Value.Default default boolean getLinkWithoutSoname() { return false; } @Value.Default default boolean getCanBeAsset() { return false; } @Value.NaturalOrder ImmutableSortedSet<FrameworkPath> getFrameworks(); @Value.NaturalOrder ImmutableSortedSet<FrameworkPath> getLibraries(); @Value.NaturalOrder ImmutableSortedSet<BuildTarget> getExportedDeps(); Optional<Pattern> getSupportedPlatformsRegex(); Optional<VersionMatchedCollection<String>> getVersionedSubDir(); @Value.Default default boolean isSupportsSharedLibraryInterface() { return false; } } }