/*
* 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.io.ProjectFilesystem;
import com.facebook.buck.model.BuildTarget;
import com.facebook.buck.model.BuildTargetPattern;
import com.facebook.buck.model.BuildTargets;
import com.facebook.buck.model.Flavor;
import com.facebook.buck.model.FlavorDomain;
import com.facebook.buck.model.Flavored;
import com.facebook.buck.model.MacroException;
import com.facebook.buck.model.MacroFinder;
import com.facebook.buck.model.MacroReplacer;
import com.facebook.buck.parser.BuildTargetParseException;
import com.facebook.buck.parser.BuildTargetParser;
import com.facebook.buck.parser.BuildTargetPatternParser;
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.DefaultBuildTargetSourcePath;
import com.facebook.buck.rules.RuleKeyAppendable;
import com.facebook.buck.rules.SourcePath;
import com.facebook.buck.rules.SourcePathResolver;
import com.facebook.buck.rules.SourcePathRuleFinder;
import com.facebook.buck.rules.SymlinkTree;
import com.facebook.buck.rules.TargetGraph;
import com.facebook.buck.rules.Tool;
import com.facebook.buck.rules.args.StringArg;
import com.facebook.buck.rules.macros.AbstractMacroExpander;
import com.facebook.buck.rules.macros.ExecutableMacroExpander;
import com.facebook.buck.rules.macros.LocationMacro;
import com.facebook.buck.rules.macros.LocationMacroExpander;
import com.facebook.buck.rules.macros.MacroExpander;
import com.facebook.buck.rules.macros.MacroHandler;
import com.facebook.buck.rules.macros.StringExpander;
import com.facebook.buck.shell.AbstractGenruleDescription;
import com.facebook.buck.shell.Genrule;
import com.facebook.buck.util.Escaper;
import com.facebook.buck.util.HumanReadableException;
import com.facebook.buck.util.MoreCollectors;
import com.facebook.buck.util.immutables.BuckStyleImmutable;
import com.facebook.buck.versions.TargetNodeTranslator;
import com.facebook.buck.versions.TargetTranslatorOverridingDescription;
import com.facebook.buck.versions.VersionPropagator;
import com.google.common.base.CaseFormat;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableCollection;
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 com.google.common.collect.Ordering;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Pattern;
import org.immutables.value.Value;
public class CxxGenruleDescription extends AbstractGenruleDescription<CxxGenruleDescriptionArg>
implements Flavored,
VersionPropagator<CxxGenruleDescriptionArg>,
TargetTranslatorOverridingDescription<CxxGenruleDescriptionArg> {
private static final MacroFinder MACRO_FINDER = new MacroFinder();
private final FlavorDomain<CxxPlatform> cxxPlatforms;
public CxxGenruleDescription(FlavorDomain<CxxPlatform> cxxPlatforms) {
this.cxxPlatforms = cxxPlatforms;
}
public static boolean wrapsCxxGenrule(SourcePathRuleFinder ruleFinder, SourcePath path) {
Optional<BuildRule> rule = ruleFinder.getRule(path);
return rule.map(CxxGenrule.class::isInstance).orElse(false);
}
/**
* @return a new {@link BuildTargetSourcePath} for an existing {@link BuildTargetSourcePath} which
* refers to a {@link CxxGenrule} with the given {@code platform} flavor applied.
*/
public static SourcePath fixupSourcePath(
BuildRuleResolver ruleResolver,
SourcePathRuleFinder ruleFinder,
CxxPlatform platform,
SourcePath path)
throws NoSuchBuildTargetException {
Optional<BuildRule> rule = ruleFinder.getRule(path);
if (rule.isPresent() && rule.get() instanceof CxxGenrule) {
Genrule platformRule =
(Genrule)
ruleResolver.requireRule(
rule.get().getBuildTarget().withAppendedFlavors(platform.getFlavor()));
path = platformRule.getSourcePathToOutput();
}
return path;
}
public static ImmutableList<SourcePath> fixupSourcePaths(
BuildRuleResolver ruleResolver,
SourcePathRuleFinder ruleFinder,
CxxPlatform cxxPlatform,
ImmutableList<SourcePath> paths)
throws NoSuchBuildTargetException {
ImmutableList.Builder<SourcePath> fixed = ImmutableList.builder();
for (SourcePath path : paths) {
fixed.add(fixupSourcePath(ruleResolver, ruleFinder, cxxPlatform, path));
}
return fixed.build();
}
public static ImmutableSortedSet<SourcePath> fixupSourcePaths(
BuildRuleResolver ruleResolver,
SourcePathRuleFinder ruleFinder,
CxxPlatform cxxPlatform,
ImmutableSortedSet<SourcePath> paths)
throws NoSuchBuildTargetException {
ImmutableSortedSet.Builder<SourcePath> fixed =
new ImmutableSortedSet.Builder<>(Preconditions.checkNotNull(paths.comparator()));
for (SourcePath path : paths) {
fixed.add(fixupSourcePath(ruleResolver, ruleFinder, cxxPlatform, path));
}
return fixed.build();
}
public static <T> ImmutableMap<T, SourcePath> fixupSourcePaths(
BuildRuleResolver ruleResolver,
SourcePathRuleFinder ruleFinder,
CxxPlatform cxxPlatform,
ImmutableMap<T, SourcePath> paths)
throws NoSuchBuildTargetException {
ImmutableMap.Builder<T, SourcePath> fixed = ImmutableMap.builder();
for (Map.Entry<T, SourcePath> ent : paths.entrySet()) {
fixed.put(
ent.getKey(), fixupSourcePath(ruleResolver, ruleFinder, cxxPlatform, ent.getValue()));
}
return fixed.build();
}
private static String shquoteJoin(Iterable<String> args) {
return Joiner.on(' ').join(Iterables.transform(args, Escaper.SHELL_ESCAPER));
}
@Override
public Class<CxxGenruleDescriptionArg> getConstructorArgType() {
return CxxGenruleDescriptionArg.class;
}
@Override
public boolean hasFlavors(ImmutableSet<Flavor> flavors) {
return cxxPlatforms.containsAnyOf(flavors);
}
@Override
protected MacroHandler getMacroHandlerForParseTimeDeps() {
ImmutableMap.Builder<String, MacroExpander> macros = ImmutableMap.builder();
macros.put("exe", new ExecutableMacroExpander());
macros.put("location", new LocationMacroExpander());
macros.put("location-platform", new LocationMacroExpander());
macros.put("platform-name", new StringExpander(""));
macros.put("cc", new CxxPlatformParseTimeDepsExpander(cxxPlatforms));
macros.put("cxx", new CxxPlatformParseTimeDepsExpander(cxxPlatforms));
macros.put("cflags", new StringExpander(""));
macros.put("cxxflags", new StringExpander(""));
macros.put("cppflags", new ParseTimeDepsExpander(Filter.NONE));
macros.put("cxxppflags", new ParseTimeDepsExpander(Filter.NONE));
macros.put("solibs", new ParseTimeDepsExpander(Filter.NONE));
macros.put("ld", new CxxPlatformParseTimeDepsExpander(cxxPlatforms));
for (Linker.LinkableDepType style : Linker.LinkableDepType.values()) {
for (Filter filter : Filter.values()) {
macros.put(
String.format(
"ldflags-%s%s",
CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_HYPHEN, style.toString()),
filter == Filter.PARAM ? "-filter" : ""),
new ParseTimeDepsExpander(filter));
}
}
return new MacroHandler(macros.build());
}
@Override
protected Optional<MacroHandler> getMacroHandler(
BuildTarget buildTarget,
ProjectFilesystem filesystem,
BuildRuleResolver resolver,
TargetGraph targetGraph,
CxxGenruleDescriptionArg args) {
Optional<CxxPlatform> maybeCxxPlatform = cxxPlatforms.getValue(buildTarget);
if (!maybeCxxPlatform.isPresent()) {
return Optional.empty();
}
CxxPlatform cxxPlatform = maybeCxxPlatform.get();
ImmutableMap.Builder<String, MacroExpander> macros = ImmutableMap.builder();
macros.put("exe", new ExecutableMacroExpander());
macros.put("location", new CxxLocationMacroExpander(cxxPlatform));
macros.put("platform-name", new StringExpander(cxxPlatform.getFlavor().toString()));
macros.put(
"location-platform",
new LocationMacroExpander() {
@Override
protected BuildRule resolve(BuildRuleResolver resolver, LocationMacro input)
throws MacroException {
try {
return resolver.requireRule(
input.getTarget().withAppendedFlavors(cxxPlatform.getFlavor()));
} catch (NoSuchBuildTargetException e) {
throw new MacroException(e.getHumanReadableErrorMessage());
}
}
});
macros.put("cc", new ToolExpander(cxxPlatform.getCc().resolve(resolver)));
macros.put("cxx", new ToolExpander(cxxPlatform.getCxx().resolve(resolver)));
ImmutableList<String> asflags = cxxPlatform.getAsflags();
ImmutableList<String> cflags = cxxPlatform.getCflags();
ImmutableList<String> cxxflags = cxxPlatform.getCxxflags();
macros.put("cflags", new StringExpander(shquoteJoin(Iterables.concat(cflags, asflags))));
macros.put("cxxflags", new StringExpander(shquoteJoin(Iterables.concat(cxxflags, asflags))));
macros.put("cppflags", new CxxPreprocessorFlagsExpander(cxxPlatform, CxxSource.Type.C));
macros.put("cxxppflags", new CxxPreprocessorFlagsExpander(cxxPlatform, CxxSource.Type.CXX));
macros.put("ld", new ToolExpander(cxxPlatform.getLd().resolve(resolver)));
for (Linker.LinkableDepType depType : Linker.LinkableDepType.values()) {
for (Filter filter : Filter.values()) {
macros.put(
String.format(
"ldflags-%s%s",
depType.toString().toLowerCase().replace('_', '-'),
filter == Filter.PARAM ? "-filter" : ""),
new CxxLinkerFlagsExpander(
buildTarget, filesystem, cxxPlatform, depType, args.getOut(), filter));
}
}
return Optional.of(new MacroHandler(macros.build()));
}
@Override
public BuildRule createBuildRule(
TargetGraph targetGraph,
BuildRuleParams params,
BuildRuleResolver resolver,
CellPathResolver cellRoots,
CxxGenruleDescriptionArg args)
throws NoSuchBuildTargetException {
Optional<CxxPlatform> cxxPlatform = cxxPlatforms.getValue(params.getBuildTarget());
if (cxxPlatform.isPresent()) {
return super.createBuildRule(
targetGraph,
params.withAppendedFlavor(cxxPlatform.get().getFlavor()),
resolver,
cellRoots,
args);
}
return new CxxGenrule(params, resolver, args.getOut());
}
@Override
public void findDepsForTargetFromConstructorArgs(
BuildTarget buildTarget,
CellPathResolver cellRoots,
CxxGenruleDescriptionArg constructorArg,
ImmutableCollection.Builder<BuildTarget> extraDepsBuilder,
ImmutableCollection.Builder<BuildTarget> targetGraphOnlyDepsBuilder) {
// Add in all parse time deps from the C/C++ platforms.
for (CxxPlatform cxxPlatform : cxxPlatforms.getValues()) {
extraDepsBuilder.addAll(CxxPlatforms.getParseTimeDeps(cxxPlatform));
}
// Add in parse time deps from parent.
super.findDepsForTargetFromConstructorArgs(
buildTarget, cellRoots, constructorArg, extraDepsBuilder, targetGraphOnlyDepsBuilder);
}
private ImmutableMap<String, MacroReplacer> getMacroReplacersForTargetTranslation(
BuildTarget target, CellPathResolver cellNames, TargetNodeTranslator translator) {
BuildTargetPatternParser<BuildTargetPattern> buildTargetPatternParser =
BuildTargetPatternParser.forBaseName(target.getBaseName());
ImmutableMap.Builder<String, MacroReplacer> macros = ImmutableMap.builder();
ImmutableList.of("exe", "location", "location-platform", "cppflags", "cxxppflags", "solibs")
.forEach(
name ->
macros.put(
name,
new TargetTranslatorMacroReplacer(
new AsIsMacroReplacer(name),
Filter.NONE,
buildTargetPatternParser,
cellNames,
translator)));
ImmutableList.of("platform-name", "cc", "cflags", "cxx", "cxxflags", "ld")
.forEach(name -> macros.put(name, new AsIsMacroReplacer(name)));
for (Linker.LinkableDepType style : Linker.LinkableDepType.values()) {
for (Filter filter : Filter.values()) {
String name =
String.format(
"ldflags-%s%s",
CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_HYPHEN, style.toString()),
filter == Filter.PARAM ? "-filter" : "");
macros.put(
name,
new TargetTranslatorMacroReplacer(
new AsIsMacroReplacer(name),
filter,
buildTargetPatternParser,
cellNames,
translator));
}
}
return macros.build();
}
private String translateCmd(
BuildTarget root,
CellPathResolver cellNames,
TargetNodeTranslator translator,
String field,
String cmd) {
try {
return MACRO_FINDER.replace(
getMacroReplacersForTargetTranslation(root, cellNames, translator), cmd, false);
} catch (MacroException e) {
throw new HumanReadableException(
e, "%s: \"%s\": error expanding macros: %s", root, field, e.getMessage());
}
}
@Override
public Optional<CxxGenruleDescriptionArg> translateConstructorArg(
BuildTarget target,
CellPathResolver cellNames,
TargetNodeTranslator translator,
CxxGenruleDescriptionArg constructorArg) {
CxxGenruleDescriptionArg.Builder newConstructorArgBuilder = CxxGenruleDescriptionArg.builder();
translator.translateConstructorArg(
cellNames,
BuildTargetPatternParser.forBaseName(target.getBaseName()),
constructorArg,
newConstructorArgBuilder);
CxxGenruleDescriptionArg newIntermediate = newConstructorArgBuilder.build();
newConstructorArgBuilder.setCmd(
newIntermediate.getCmd().map(c -> translateCmd(target, cellNames, translator, "cmd", c)));
newConstructorArgBuilder.setBash(
newIntermediate.getBash().map(c -> translateCmd(target, cellNames, translator, "bash", c)));
newConstructorArgBuilder.setCmdExe(
newIntermediate
.getCmdExe()
.map(c -> translateCmd(target, cellNames, translator, "cmd_exe", c)));
return Optional.of(newConstructorArgBuilder.build());
}
@BuckStyleImmutable
@Value.Immutable
interface AbstractCxxGenruleDescriptionArg extends AbstractGenruleDescription.CommonArg {}
/**
* A build target macro expander just used at parse time to extract deps from the preprocessor
* flag macros.
*/
private static class ParseTimeDepsExpander extends FilterAndTargetsExpander {
public ParseTimeDepsExpander(Filter filter) {
super(filter);
}
@Override
public Class<FilterAndTargets> getInputClass() {
return FilterAndTargets.class;
}
@Override
protected String expand(
BuildRuleResolver resolver, ImmutableList<BuildRule> rule, Optional<Pattern> filter)
throws MacroException {
throw new IllegalStateException();
}
}
/** A macro expander that expands to a specific {@link Tool}. */
private static class ToolExpander implements MacroExpander {
private final Tool tool;
public ToolExpander(Tool tool) {
this.tool = tool;
}
@Override
public String expand(
BuildTarget target,
CellPathResolver cellNames,
BuildRuleResolver resolver,
ImmutableList<String> input)
throws MacroException {
SourcePathResolver pathResolver = new SourcePathResolver(new SourcePathRuleFinder(resolver));
return shquoteJoin(tool.getCommandPrefix(pathResolver));
}
@Override
public ImmutableList<BuildRule> extractBuildTimeDeps(
BuildTarget target,
CellPathResolver cellNames,
BuildRuleResolver resolver,
ImmutableList<String> input)
throws MacroException {
SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(resolver);
return ImmutableList.copyOf(tool.getDeps(ruleFinder));
}
@Override
public void extractParseTimeDeps(
BuildTarget target,
CellPathResolver cellNames,
ImmutableList<String> input,
ImmutableCollection.Builder<BuildTarget> buildDepsBuilder,
ImmutableCollection.Builder<BuildTarget> targetGraphOnlyDepsBuilder)
throws MacroException {
// We already return all platform-specific parse-time deps from
// `findDepsForTargetFromConstructorArgs`.
}
@Override
public Object extractRuleKeyAppendables(
BuildTarget target,
CellPathResolver cellNames,
BuildRuleResolver resolver,
ImmutableList<String> input)
throws MacroException {
return tool;
}
}
private abstract static class FilterAndTargetsExpander
extends AbstractMacroExpander<FilterAndTargets> {
private final Filter filter;
public FilterAndTargetsExpander(Filter filter) {
this.filter = filter;
}
@Override
protected final FilterAndTargets parse(
BuildTarget target, CellPathResolver cellNames, ImmutableList<String> input)
throws MacroException {
if (this.filter == Filter.PARAM && input.size() < 1) {
throw new MacroException("expected at least 1 argument");
}
Iterator<String> itr = input.iterator();
Optional<Pattern> filter =
this.filter == Filter.PARAM ? Optional.of(Pattern.compile(itr.next())) : Optional.empty();
ImmutableList.Builder<BuildTarget> targets = ImmutableList.builder();
while (itr.hasNext()) {
targets.add(
BuildTargetParser.INSTANCE.parse(
itr.next(), BuildTargetPatternParser.forBaseName(target.getBaseName()), cellNames));
}
return new FilterAndTargets(filter, targets.build());
}
protected ImmutableList<BuildRule> resolve(
BuildRuleResolver resolver, ImmutableList<BuildTarget> input) throws MacroException {
ImmutableList.Builder<BuildRule> rules = ImmutableList.builder();
for (BuildTarget ruleTarget : input) {
Optional<BuildRule> rule = resolver.getRuleOptional(ruleTarget);
if (!rule.isPresent()) {
throw new MacroException(String.format("no rule %s", ruleTarget));
}
rules.add(rule.get());
}
return rules.build();
}
protected abstract String expand(
BuildRuleResolver resolver, ImmutableList<BuildRule> rules, Optional<Pattern> filter)
throws MacroException;
@Override
public String expandFrom(
BuildTarget target,
CellPathResolver cellNames,
BuildRuleResolver resolver,
FilterAndTargets input)
throws MacroException {
return expand(resolver, resolve(resolver, input.targets), input.filter);
}
protected ImmutableList<BuildRule> extractBuildTimeDeps(
@SuppressWarnings("unused") BuildRuleResolver resolver,
ImmutableList<BuildRule> rules,
@SuppressWarnings("unused") Optional<Pattern> filter)
throws MacroException {
return rules;
}
@Override
public ImmutableList<BuildRule> extractBuildTimeDepsFrom(
BuildTarget target,
CellPathResolver cellNames,
BuildRuleResolver resolver,
FilterAndTargets input)
throws MacroException {
return extractBuildTimeDeps(resolver, resolve(resolver, input.targets), input.filter);
}
@Override
public void extractParseTimeDepsFrom(
BuildTarget target,
CellPathResolver cellNames,
FilterAndTargets input,
ImmutableCollection.Builder<BuildTarget> buildDepsBuilder,
ImmutableCollection.Builder<BuildTarget> targetGraphOnlyDepsBuilder) {
buildDepsBuilder.addAll(input.targets);
}
@Override
public Object extractRuleKeyAppendablesFrom(
BuildTarget target,
CellPathResolver cellNames,
BuildRuleResolver resolver,
FilterAndTargets input)
throws MacroException {
return input
.targets
.stream()
.map(DefaultBuildTargetSourcePath::new)
.collect(MoreCollectors.toImmutableSortedSet(Ordering.natural()));
}
}
/**
* A build target expander that replaces lists of build target with their transitive preprocessor
* input.
*/
private static class CxxPreprocessorFlagsExpander extends FilterAndTargetsExpander {
private final CxxPlatform cxxPlatform;
private final CxxSource.Type sourceType;
public CxxPreprocessorFlagsExpander(CxxPlatform cxxPlatform, CxxSource.Type sourceType) {
super(Filter.NONE);
this.cxxPlatform = cxxPlatform;
this.sourceType = sourceType;
}
@Override
public Class<FilterAndTargets> getInputClass() {
return FilterAndTargets.class;
}
/** Make sure all resolved targets are instances of {@link CxxPreprocessorDep}. */
@Override
protected ImmutableList<BuildRule> resolve(
BuildRuleResolver resolver, ImmutableList<BuildTarget> input) throws MacroException {
return FluentIterable.from(super.resolve(resolver, input))
.filter(CxxPreprocessorDep.class::isInstance)
.toList();
}
/** Get the transitive C/C++ preprocessor input rooted at the given rules. */
private Collection<CxxPreprocessorInput> getCxxPreprocessorInput(ImmutableList<BuildRule> rules)
throws MacroException {
try {
return CxxPreprocessables.getTransitiveCxxPreprocessorInput(cxxPlatform, rules);
} catch (NoSuchBuildTargetException e) {
throw new MacroException(
String.format("failed getting preprocessor input: %s", e.getMessage()), e);
}
}
/**
* Return the {@link PreprocessorFlags} object formed by the transitive C/C++ preprocessor input
* for the given rules.
*/
private PreprocessorFlags getPreprocessorFlags(
Iterable<CxxPreprocessorInput> transitivePreprocessorInput) {
PreprocessorFlags.Builder ppFlagsBuilder = PreprocessorFlags.builder();
ExplicitCxxToolFlags.Builder toolFlagsBuilder = CxxToolFlags.explicitBuilder();
toolFlagsBuilder.setPlatformFlags(
CxxSourceTypes.getPlatformPreprocessFlags(cxxPlatform, sourceType));
for (CxxPreprocessorInput input : transitivePreprocessorInput) {
ppFlagsBuilder.addAllIncludes(input.getIncludes());
ppFlagsBuilder.addAllFrameworkPaths(input.getFrameworks());
toolFlagsBuilder.addAllRuleFlags(input.getPreprocessorFlags().get(sourceType));
}
ppFlagsBuilder.setOtherFlags(toolFlagsBuilder.build());
return ppFlagsBuilder.build();
}
/**
* Expand the preprocessor input for the given rules into a shell-escaped string containing all
* flags and header trees.
*/
@Override
protected String expand(
BuildRuleResolver resolver, ImmutableList<BuildRule> rules, Optional<Pattern> filter)
throws MacroException {
SourcePathResolver pathResolver = new SourcePathResolver(new SourcePathRuleFinder(resolver));
PreprocessorFlags ppFlags = getPreprocessorFlags(getCxxPreprocessorInput(rules));
Preprocessor preprocessor =
CxxSourceTypes.getPreprocessor(cxxPlatform, sourceType).resolve(resolver);
CxxToolFlags flags =
ppFlags.toToolFlags(
pathResolver,
PathShortener.identity(),
CxxDescriptionEnhancer.frameworkPathToSearchPath(cxxPlatform, pathResolver),
preprocessor,
/* pch */ Optional.empty());
return Joiner.on(' ').join(Iterables.transform(flags.getAllFlags(), Escaper.SHELL_ESCAPER));
}
@Override
protected ImmutableList<BuildRule> extractBuildTimeDeps(
BuildRuleResolver resolver, ImmutableList<BuildRule> rules, Optional<Pattern> filter)
throws MacroException {
SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(resolver);
ImmutableList.Builder<BuildRule> deps = ImmutableList.builder();
for (CxxPreprocessorInput input : getCxxPreprocessorInput(rules)) {
deps.addAll(input.getDeps(resolver, ruleFinder));
}
return deps.build();
}
@Override
public Object extractRuleKeyAppendablesFrom(
final BuildTarget target,
final CellPathResolver cellNames,
final BuildRuleResolver resolver,
FilterAndTargets input)
throws MacroException {
final Iterable<CxxPreprocessorInput> transitivePreprocessorInput =
getCxxPreprocessorInput(resolve(resolver, input.targets));
final PreprocessorFlags ppFlags = getPreprocessorFlags(transitivePreprocessorInput);
return (RuleKeyAppendable)
sink -> {
ppFlags.appendToRuleKey(sink, cxxPlatform.getCompilerDebugPathSanitizer());
sink.setReflectively(
"headers",
FluentIterable.from(transitivePreprocessorInput)
.transformAndConcat(CxxPreprocessorInput::getIncludes)
.toList());
};
}
}
/**
* A build target expander that replaces lists of build target with their transitive preprocessor
* input.
*/
private static class CxxLinkerFlagsExpander extends FilterAndTargetsExpander {
private final BuildTarget buildTarget;
private final ProjectFilesystem filesystem;
private final CxxPlatform cxxPlatform;
private final Linker.LinkableDepType depType;
private final String out;
public CxxLinkerFlagsExpander(
BuildTarget buildTarget,
ProjectFilesystem filesystem,
CxxPlatform cxxPlatform,
Linker.LinkableDepType depType,
String out,
Filter filter) {
super(filter);
this.buildTarget = buildTarget;
this.filesystem = filesystem;
this.cxxPlatform = cxxPlatform;
this.depType = depType;
this.out = out;
}
@Override
public Class<FilterAndTargets> getInputClass() {
return FilterAndTargets.class;
}
/**
* @return a {@link SymlinkTree} containing all the transitive shared libraries from the given
* roots linked in by their library name.
*/
private SymlinkTree requireSymlinkTree(
BuildRuleResolver resolver, ImmutableList<BuildRule> rules) throws MacroException {
BuildTarget symlinkTreeTarget =
CxxDescriptionEnhancer.createSharedLibrarySymlinkTreeTarget(
buildTarget, cxxPlatform.getFlavor());
SymlinkTree symlinkTree =
resolver.getRuleOptionalWithType(symlinkTreeTarget, SymlinkTree.class).orElse(null);
if (symlinkTree == null) {
try {
symlinkTree =
resolver.addToIndex(
CxxDescriptionEnhancer.createSharedLibrarySymlinkTree(
new SourcePathRuleFinder(resolver),
buildTarget,
filesystem,
cxxPlatform,
rules,
NativeLinkable.class::isInstance));
} catch (NoSuchBuildTargetException e) {
throw new MacroException(
String.format("cannot create shared library symlink tree: %s: %s", e, e.getMessage()),
e);
}
}
return symlinkTree;
}
/**
* @return the list of {@link com.facebook.buck.rules.args.Arg} required for dynamic linking so
* that linked binaries can find their shared library dependencies at runtime.
*/
private ImmutableList<com.facebook.buck.rules.args.Arg> getSharedLinkArgs(
BuildRuleResolver resolver, ImmutableList<BuildRule> rules) throws MacroException {
// Embed a origin-relative library path into the binary so it can find the shared libraries.
// The shared libraries root is absolute. Also need an absolute path to the linkOutput
Path linkOutput = BuildTargets.getGenPath(filesystem, buildTarget, "%s").resolve(out);
Path absLinkOut = buildTarget.getCellPath().resolve(linkOutput);
SymlinkTree symlinkTree = requireSymlinkTree(resolver, rules);
return ImmutableList.copyOf(
StringArg.from(
Linkers.iXlinker(
"-rpath",
String.format(
"%s/%s",
cxxPlatform.getLd().resolve(resolver).origin(),
absLinkOut.getParent().relativize(symlinkTree.getRoot()).toString()))));
}
private NativeLinkableInput getNativeLinkableInput(
Iterable<BuildRule> rules, final Optional<Pattern> filter) throws MacroException {
try {
ImmutableMap<BuildTarget, NativeLinkable> nativeLinkables =
NativeLinkables.getNativeLinkables(
cxxPlatform,
FluentIterable.from(rules).filter(NativeLinkable.class),
depType,
!filter.isPresent()
? x -> true
: input -> {
Preconditions.checkArgument(input instanceof BuildRule);
BuildRule rule = (BuildRule) input;
return filter
.get()
.matcher(String.format("%s(%s)", rule.getType(), rule.getBuildTarget()))
.find();
});
ImmutableList.Builder<NativeLinkableInput> nativeLinkableInputs = ImmutableList.builder();
for (NativeLinkable nativeLinkable : nativeLinkables.values()) {
nativeLinkableInputs.add(
NativeLinkables.getNativeLinkableInput(cxxPlatform, depType, nativeLinkable));
}
return NativeLinkableInput.concat(nativeLinkableInputs.build());
} catch (NoSuchBuildTargetException e) {
throw new MacroException(
String.format("failed getting native linker args: %s", e.getMessage()), e);
}
}
/** Make sure all resolved targets are instances of {@link NativeLinkable}. */
@Override
protected ImmutableList<BuildRule> resolve(
BuildRuleResolver resolver, ImmutableList<BuildTarget> input) throws MacroException {
return FluentIterable.from(super.resolve(resolver, input))
.filter(NativeLinkable.class::isInstance)
.toList();
}
/** Return the args formed by the transitive native linkable input for the given rules. */
private ImmutableList<com.facebook.buck.rules.args.Arg> getLinkerArgs(
BuildRuleResolver resolver, ImmutableList<BuildRule> rules, Optional<Pattern> filter)
throws MacroException {
ImmutableList.Builder<com.facebook.buck.rules.args.Arg> args = ImmutableList.builder();
args.addAll(StringArg.from(cxxPlatform.getLdflags()));
if (depType == Linker.LinkableDepType.SHARED) {
args.addAll(getSharedLinkArgs(resolver, rules));
}
args.addAll(getNativeLinkableInput(rules, filter).getArgs());
return args.build();
}
/**
* Expand the native linkable input for the given rules into a shell-escaped string containing
* all linker flags.
*/
@Override
public String expand(
BuildRuleResolver resolver, ImmutableList<BuildRule> rules, Optional<Pattern> filter)
throws MacroException {
return shquoteJoin(
com.facebook.buck.rules.args.Arg.stringify(
getLinkerArgs(resolver, rules, filter),
new SourcePathResolver(new SourcePathRuleFinder(resolver))));
}
@Override
protected ImmutableList<BuildRule> extractBuildTimeDeps(
BuildRuleResolver resolver, ImmutableList<BuildRule> rules, Optional<Pattern> filter)
throws MacroException {
SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(resolver);
ImmutableList.Builder<BuildRule> deps = ImmutableList.builder();
deps.addAll(
getLinkerArgs(resolver, rules, filter)
.stream()
.flatMap(arg -> arg.getDeps(ruleFinder).stream())
.iterator());
if (depType == Linker.LinkableDepType.SHARED) {
deps.add(requireSymlinkTree(resolver, rules));
}
return deps.build();
}
@Override
public Object extractRuleKeyAppendablesFrom(
final BuildTarget target,
final CellPathResolver cellNames,
final BuildRuleResolver resolver,
FilterAndTargets inputs)
throws MacroException {
return getLinkerArgs(resolver, resolve(resolver, inputs.targets), inputs.filter);
}
}
private static class CxxPlatformParseTimeDepsExpander extends StringExpander {
private final FlavorDomain<CxxPlatform> cxxPlatforms;
public CxxPlatformParseTimeDepsExpander(FlavorDomain<CxxPlatform> cxxPlatforms) {
super("");
this.cxxPlatforms = cxxPlatforms;
}
@Override
public void extractParseTimeDeps(
BuildTarget target,
CellPathResolver cellNames,
ImmutableList<String> input,
ImmutableCollection.Builder<BuildTarget> buildDepsBuilder,
ImmutableCollection.Builder<BuildTarget> targetGraphOnlyDepsBuilder)
throws MacroException {
Optional<CxxPlatform> platform = cxxPlatforms.getValue(target.getFlavors());
if (platform.isPresent()) {
buildDepsBuilder.addAll(CxxPlatforms.getParseTimeDeps(platform.get()));
}
}
}
private static class FilterAndTargets {
public final Optional<Pattern> filter;
public final ImmutableList<BuildTarget> targets;
public FilterAndTargets(Optional<Pattern> filter, ImmutableList<BuildTarget> targets) {
this.filter = filter;
this.targets = targets;
}
}
private static class AsIsMacroReplacer implements MacroReplacer {
private final String name;
private AsIsMacroReplacer(String name) {
this.name = name;
}
@Override
public String replace(ImmutableList<String> args) throws MacroException {
return String.format("$(%s %s)", name, Joiner.on(' ').join(args));
}
}
private static class TargetTranslatorMacroReplacer implements MacroReplacer {
private final AsIsMacroReplacer asIsMacroReplacer;
private final Filter filter;
private final BuildTargetPatternParser<BuildTargetPattern> buildTargetBuildTargetParser;
private final CellPathResolver cellNames;
private final TargetNodeTranslator translator;
private TargetTranslatorMacroReplacer(
AsIsMacroReplacer asIsMacroReplacer,
Filter filter,
BuildTargetPatternParser<BuildTargetPattern> buildTargetBuildTargetParser,
CellPathResolver cellNames,
TargetNodeTranslator translator) {
this.asIsMacroReplacer = asIsMacroReplacer;
this.filter = filter;
this.buildTargetBuildTargetParser = buildTargetBuildTargetParser;
this.cellNames = cellNames;
this.translator = translator;
}
private BuildTarget parse(String input) throws MacroException {
try {
return BuildTargetParser.INSTANCE.parse(input, buildTargetBuildTargetParser, cellNames);
} catch (BuildTargetParseException e) {
throw new MacroException(e.getMessage(), e);
}
}
@Override
public String replace(ImmutableList<String> args) throws MacroException {
ImmutableList.Builder<String> strings = ImmutableList.builder();
if (filter == Filter.PARAM) {
strings.add(args.get(0));
}
for (String arg : args.subList(filter == Filter.PARAM ? 1 : 0, args.size())) {
BuildTarget target = parse(arg);
strings.add(
translator
.translate(cellNames, buildTargetBuildTargetParser, target)
.orElse(target)
.getFullyQualifiedName());
}
return asIsMacroReplacer.replace(strings.build());
}
}
private enum Filter {
NONE,
PARAM,
}
}