/*
* 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.cxx;
import com.facebook.buck.android.AndroidPackageable;
import com.facebook.buck.android.AndroidPackageableCollector;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.model.MacroException;
import com.facebook.buck.model.MacroFinder;
import com.facebook.buck.model.MacroMatchResult;
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.CellPathResolver;
import com.facebook.buck.rules.CommonDescriptionArg;
import com.facebook.buck.rules.Description;
import com.facebook.buck.rules.HasDeclaredDeps;
import com.facebook.buck.rules.NoopBuildRule;
import com.facebook.buck.rules.PathSourcePath;
import com.facebook.buck.rules.SourcePath;
import com.facebook.buck.rules.SourcePathRuleFinder;
import com.facebook.buck.rules.TargetGraph;
import com.facebook.buck.rules.args.Arg;
import com.facebook.buck.rules.args.SourcePathArg;
import com.facebook.buck.rules.args.StringArg;
import com.facebook.buck.rules.coercer.PatternMatchedCollection;
import com.facebook.buck.util.HumanReadableException;
import com.facebook.buck.util.immutables.BuckStyleImmutable;
import com.facebook.buck.util.immutables.BuckStyleTuple;
import com.facebook.buck.versions.VersionPropagator;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.FluentIterable;
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 java.util.Optional;
import java.util.regex.Pattern;
import org.immutables.value.Value;
@Value.Immutable
@BuckStyleTuple
abstract class AbstractPrebuiltCxxLibraryGroupDescription
implements Description<PrebuiltCxxLibraryGroupDescriptionArg>,
VersionPropagator<PrebuiltCxxLibraryGroupDescriptionArg> {
private static final MacroFinder FINDER = new MacroFinder();
private static final String LIB_MACRO = "lib";
private static final String REL_LIB_MACRO = "rel-lib";
/** If the arg contains a library reference, parse it and return it's name and argument. */
private Optional<Pair<String, String>> getLibRef(ImmutableSet<String> macros, String arg) {
Optional<MacroMatchResult> result;
try {
result = FINDER.match(macros, arg);
} catch (MacroException e) {
throw new HumanReadableException(e, e.getMessage());
}
if (!result.isPresent()) {
return Optional.empty();
}
if (result.get().getMacroType().equals("")) {
throw new HumanReadableException("expected library reference");
}
if (result.get().getMacroInput().size() != 1) {
throw new HumanReadableException("expected a single library reference argument");
}
return Optional.of(
new Pair<>(result.get().getMacroType(), result.get().getMacroInput().get(0)));
}
@Override
public Class<PrebuiltCxxLibraryGroupDescriptionArg> getConstructorArgType() {
return PrebuiltCxxLibraryGroupDescriptionArg.class;
}
/**
* @return the link args formed from the user-provided static link line after resolving library
* macro references.
*/
private Iterable<Arg> getStaticLinkArgs(
BuildTarget target, ImmutableList<SourcePath> libs, ImmutableList<String> args) {
ImmutableList.Builder<Arg> builder = ImmutableList.builder();
for (String arg : args) {
Optional<Pair<String, String>> libRef = getLibRef(ImmutableSet.of(LIB_MACRO), arg);
if (libRef.isPresent()) {
int index;
try {
index = Integer.parseInt(libRef.get().getSecond());
} catch (NumberFormatException e) {
throw new HumanReadableException("%s: ", target);
}
if (index < 0 || index >= libs.size()) {
throw new HumanReadableException("%s: ", target);
}
builder.add(SourcePathArg.of(libs.get(index)));
} else {
builder.add(StringArg.of(arg));
}
}
return builder.build();
}
/**
* @return the link args formed from the user-provided shared link line after resolving library
* macro references.
*/
private Iterable<Arg> getSharedLinkArgs(
BuildTarget target, ImmutableMap<String, SourcePath> libs, ImmutableList<String> args) {
ImmutableList.Builder<Arg> builder = ImmutableList.builder();
for (String arg : args) {
Optional<Pair<String, String>> libRef =
getLibRef(ImmutableSet.of(LIB_MACRO, REL_LIB_MACRO), arg);
if (libRef.isPresent()) {
SourcePath lib = libs.get(libRef.get().getSecond());
if (lib == null) {
throw new HumanReadableException(
"%s: library \"%s\" (in \"%s\") must refer to keys in the `sharedLibs` parameter",
target, libRef.get().getSecond(), arg);
}
Arg libArg;
if (libRef.get().getFirst().equals(LIB_MACRO)) {
libArg = SourcePathArg.of(lib);
} else if (libRef.get().getFirst().equals(REL_LIB_MACRO)) {
if (!(lib instanceof PathSourcePath)) {
throw new HumanReadableException(
"%s: can only link prebuilt DSOs without sonames", target);
}
libArg = new RelativeLinkArg((PathSourcePath) lib);
} else {
throw new IllegalStateException();
}
builder.add(libArg);
} else {
builder.add(StringArg.of(arg));
}
}
return builder.build();
}
@Override
public BuildRule createBuildRule(
TargetGraph targetGraph,
final BuildRuleParams params,
final BuildRuleResolver resolver,
CellPathResolver cellRoots,
final PrebuiltCxxLibraryGroupDescriptionArg args)
throws NoSuchBuildTargetException {
SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(resolver);
return new CustomPrebuiltCxxLibrary(params) {
private final LoadingCache<
CxxPreprocessables.CxxPreprocessorInputCacheKey,
ImmutableMap<BuildTarget, CxxPreprocessorInput>>
transitiveCxxPreprocessorInputCache =
CxxPreprocessables.getTransitiveCxxPreprocessorInputCache(this);
@Override
public Iterable<AndroidPackageable> getRequiredPackageables() {
return AndroidPackageableCollector.getPackageableRules(params.getBuildDeps());
}
@Override
public void addToCollector(AndroidPackageableCollector collector) {
collector.addNativeLinkable(this);
}
@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(
CxxPlatform cxxPlatform, HeaderVisibility headerVisibility)
throws NoSuchBuildTargetException {
CxxPreprocessorInput.Builder builder = CxxPreprocessorInput.builder();
switch (headerVisibility) {
case PUBLIC:
builder.putAllPreprocessorFlags(
CxxFlags.getLanguageFlags(
args.getExportedPreprocessorFlags(),
PatternMatchedCollection.of(),
ImmutableMap.of(),
cxxPlatform));
for (SourcePath includeDir : args.getIncludeDirs()) {
builder.addIncludes(
CxxHeadersDir.of(CxxPreprocessables.IncludeType.SYSTEM, includeDir));
}
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<? extends NativeLinkable> getNativeLinkableDeps() {
return FluentIterable.from(params.getDeclaredDeps().get()).filter(NativeLinkable.class);
}
@Override
public Iterable<? extends NativeLinkable> getNativeLinkableExportedDeps() {
return FluentIterable.from(args.getExportedDeps())
.transform(resolver::getRule)
.filter(NativeLinkable.class);
}
@Override
public NativeLinkableInput getNativeLinkableInput(
CxxPlatform cxxPlatform, Linker.LinkableDepType type) throws NoSuchBuildTargetException {
if (!isPlatformSupported(cxxPlatform)) {
return NativeLinkableInput.of();
}
NativeLinkableInput.Builder builder = NativeLinkableInput.builder();
switch (type) {
case STATIC:
builder.addAllArgs(
getStaticLinkArgs(
getBuildTarget(),
CxxGenruleDescription.fixupSourcePaths(
resolver, ruleFinder, cxxPlatform, args.getStaticLibs()),
args.getStaticLink()));
break;
case STATIC_PIC:
builder.addAllArgs(
getStaticLinkArgs(
getBuildTarget(),
CxxGenruleDescription.fixupSourcePaths(
resolver, ruleFinder, cxxPlatform, args.getStaticPicLibs()),
args.getStaticPicLink()));
break;
case SHARED:
builder.addAllArgs(
getSharedLinkArgs(
getBuildTarget(),
CxxGenruleDescription.fixupSourcePaths(
resolver,
ruleFinder,
cxxPlatform,
ImmutableMap.<String, SourcePath>builder()
.putAll(args.getSharedLibs())
.putAll(args.getProvidedSharedLibs())
.build()),
args.getSharedLink()));
break;
}
return builder.build();
}
@Override
public Linkage getPreferredLinkage(CxxPlatform cxxPlatform) {
// If we both shared and static libs, we support any linkage.
if (!args.getSharedLink().isEmpty()
&& !(args.getStaticLink().isEmpty() && args.getStaticPicLink().isEmpty())) {
return Linkage.ANY;
}
// Otherwise, if we have a shared library, we only support shared linkage.
if (!args.getSharedLink().isEmpty()) {
return Linkage.SHARED;
}
// Otherwise, if we have a static library, we only support static linkage.
if (!(args.getStaticLink().isEmpty() && args.getStaticPicLink().isEmpty())) {
return Linkage.STATIC;
}
// Otherwise, header only libs use any linkage.
return Linkage.ANY;
}
@Override
public Iterable<? extends NativeLinkable> getNativeLinkableDepsForPlatform(
CxxPlatform cxxPlatform) {
if (!isPlatformSupported(cxxPlatform)) {
return ImmutableList.of();
}
return getNativeLinkableDeps();
}
@Override
public Iterable<? extends NativeLinkable> getNativeLinkableExportedDepsForPlatform(
CxxPlatform cxxPlatform) {
if (!isPlatformSupported(cxxPlatform)) {
return ImmutableList.of();
}
return getNativeLinkableExportedDeps();
}
@Override
public ImmutableMap<String, SourcePath> getSharedLibraries(CxxPlatform cxxPlatform)
throws NoSuchBuildTargetException {
if (!isPlatformSupported(cxxPlatform)) {
return ImmutableMap.of();
}
return args.getSharedLibs();
}
private boolean isPlatformSupported(CxxPlatform cxxPlatform) {
return !args.getSupportedPlatformsRegex().isPresent()
|| args.getSupportedPlatformsRegex()
.get()
.matcher(cxxPlatform.getFlavor().toString())
.find();
}
};
}
private abstract static class CustomPrebuiltCxxLibrary extends NoopBuildRule
implements AbstractCxxLibrary {
public CustomPrebuiltCxxLibrary(BuildRuleParams params) {
super(params);
}
}
@BuckStyleImmutable
@Value.Immutable
interface AbstractPrebuiltCxxLibraryGroupDescriptionArg
extends CommonDescriptionArg, HasDeclaredDeps {
ImmutableList<String> getExportedPreprocessorFlags();
ImmutableList<SourcePath> getIncludeDirs();
/** The link arguments to use when linking using the static link style. */
ImmutableList<String> getStaticLink();
/** Libraries references in the static link args above. */
ImmutableList<SourcePath> getStaticLibs();
/** The link arguments to use when linking using the static-pic link style. */
ImmutableList<String> getStaticPicLink();
/** Libraries references in the static-pic link args above. */
ImmutableList<SourcePath> getStaticPicLibs();
/** The link arguments to use when linking using the shared link style. */
ImmutableList<String> getSharedLink();
/** Libraries references in the shared link args above. */
ImmutableMap<String, SourcePath> getSharedLibs();
ImmutableMap<String, SourcePath> getProvidedSharedLibs();
@Value.NaturalOrder
ImmutableSortedSet<BuildTarget> getExportedDeps();
Optional<Pattern> getSupportedPlatformsRegex();
}
}