/*
* 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.apple;
import com.facebook.buck.cxx.CxxPlatform;
import com.facebook.buck.model.BuildTarget;
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.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.Hint;
import com.facebook.buck.rules.ImplicitDepsInferringDescription;
import com.facebook.buck.rules.SourcePathRuleFinder;
import com.facebook.buck.rules.TargetGraph;
import com.facebook.buck.rules.args.MacroArg;
import com.facebook.buck.shell.AbstractGenruleDescription;
import com.facebook.buck.util.HumanReadableException;
import com.facebook.buck.util.immutables.BuckStyleImmutable;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
import com.google.common.collect.MultimapBuilder;
import com.google.common.collect.Sets;
import java.util.Optional;
import java.util.Set;
import org.immutables.value.Value;
public class ApplePackageDescription
implements Description<ApplePackageDescriptionArg>,
Flavored,
ImplicitDepsInferringDescription<
ApplePackageDescription.AbstractApplePackageDescriptionArg> {
private final CxxPlatform defaultCxxPlatform;
private final AppleConfig config;
private final FlavorDomain<AppleCxxPlatform> appleCxxPlatformFlavorDomain;
public ApplePackageDescription(
AppleConfig config,
CxxPlatform defaultCxxPlatform,
FlavorDomain<AppleCxxPlatform> appleCxxPlatformFlavorDomain) {
this.defaultCxxPlatform = defaultCxxPlatform;
this.config = config;
this.appleCxxPlatformFlavorDomain = appleCxxPlatformFlavorDomain;
}
@Override
public BuildRule createBuildRule(
TargetGraph targetGraph,
BuildRuleParams params,
BuildRuleResolver resolver,
CellPathResolver cellRoots,
ApplePackageDescriptionArg args)
throws NoSuchBuildTargetException {
final BuildRule bundle =
resolver.getRule(propagateFlavorsToTarget(params.getBuildTarget(), args.getBundle()));
SourcePathRuleFinder ruleFinder = new SourcePathRuleFinder(resolver);
final Optional<ApplePackageConfigAndPlatformInfo> applePackageConfigAndPlatformInfo =
getApplePackageConfig(
params.getBuildTarget(),
MacroArg.toMacroArgFunction(
AbstractGenruleDescription.PARSE_TIME_MACRO_HANDLER,
params.getBuildTarget(),
cellRoots,
resolver));
if (applePackageConfigAndPlatformInfo.isPresent()) {
return new ExternallyBuiltApplePackage(
params.copyReplacingExtraDeps(
() ->
ImmutableSortedSet.<BuildRule>naturalOrder()
.add(bundle)
.addAll(
applePackageConfigAndPlatformInfo
.get()
.getExpandedArg()
.getDeps(ruleFinder))
.build()),
applePackageConfigAndPlatformInfo.get(),
Preconditions.checkNotNull(bundle.getSourcePathToOutput()),
bundle.isCacheable());
} else {
return new BuiltinApplePackage(params, bundle);
}
}
@Override
public Class<ApplePackageDescriptionArg> getConstructorArgType() {
return ApplePackageDescriptionArg.class;
}
private BuildTarget propagateFlavorsToTarget(BuildTarget fromTarget, BuildTarget toTarget) {
return BuildTarget.builder(toTarget).addAllFlavors(fromTarget.getFlavors()).build();
}
/** Propagate the packages's flavors to its dependents. */
@Override
public void findDepsForTargetFromConstructorArgs(
BuildTarget buildTarget,
CellPathResolver cellRoots,
AbstractApplePackageDescriptionArg constructorArg,
ImmutableCollection.Builder<BuildTarget> extraDepsBuilder,
ImmutableCollection.Builder<BuildTarget> targetGraphOnlyDepsBuilder) {
extraDepsBuilder.add(propagateFlavorsToTarget(buildTarget, constructorArg.getBundle()));
addDepsFromParam(extraDepsBuilder, targetGraphOnlyDepsBuilder, buildTarget, cellRoots);
}
@Override
public Optional<ImmutableSet<FlavorDomain<?>>> flavorDomains() {
return Optional.of(ImmutableSet.of(appleCxxPlatformFlavorDomain));
}
@Override
public boolean hasFlavors(ImmutableSet<Flavor> flavors) {
return true;
}
@BuckStyleImmutable
@Value.Immutable
interface AbstractApplePackageDescriptionArg extends CommonDescriptionArg {
@Hint(isDep = false)
BuildTarget getBundle();
}
/**
* Get the correct package configuration based on the platform flavors of this build target.
*
* <p>Validates that all named platforms yields the identical package config.
*
* @return If found, a package config for this target.
* @throws HumanReadableException if there are multiple possible package configs.
*/
private Optional<ApplePackageConfigAndPlatformInfo> getApplePackageConfig(
BuildTarget target, Function<String, com.facebook.buck.rules.args.Arg> macroExpander) {
Set<Flavor> platformFlavors = getPlatformFlavorsOrDefault(target);
// Ensure that different platforms generate the same config.
// The value of this map is just for error reporting.
Multimap<Optional<ApplePackageConfigAndPlatformInfo>, Flavor> packageConfigs =
MultimapBuilder.hashKeys().arrayListValues().build();
for (Flavor flavor : platformFlavors) {
AppleCxxPlatform platform = appleCxxPlatformFlavorDomain.getValue(flavor);
Optional<ApplePackageConfig> packageConfig =
config.getPackageConfigForPlatform(platform.getAppleSdk().getApplePlatform());
packageConfigs.put(
packageConfig.isPresent()
? Optional.of(
ApplePackageConfigAndPlatformInfo.of(
packageConfig.get(), macroExpander, platform))
: Optional.empty(),
flavor);
}
if (packageConfigs.isEmpty()) {
return Optional.empty();
} else if (packageConfigs.keySet().size() == 1) {
return Iterables.getOnlyElement(packageConfigs.keySet());
} else {
throw new HumanReadableException(
"In target %s: Multi-architecture package has different package configs for targets: %s",
target.getFullyQualifiedName(), packageConfigs.asMap().values());
}
}
/**
* Retrieve deps from macros in externally configured rules.
*
* <p>This is used for ImplicitDepsInferringDescription, so it is flavor agnostic.
*/
private void addDepsFromParam(
ImmutableCollection.Builder<BuildTarget> buildDepsBuilder,
ImmutableCollection.Builder<BuildTarget> targetGraphOnlyDepsBuilder,
BuildTarget target,
CellPathResolver cellNames) {
// Add all macro expanded dependencies for these platforms.
for (Flavor flavor : appleCxxPlatformFlavorDomain.getFlavors()) {
AppleCxxPlatform platform = appleCxxPlatformFlavorDomain.getValue(flavor);
Optional<ApplePackageConfig> packageConfig =
config.getPackageConfigForPlatform(platform.getAppleSdk().getApplePlatform());
if (packageConfig.isPresent()) {
try {
AbstractGenruleDescription.PARSE_TIME_MACRO_HANDLER.extractParseTimeDeps(
target,
cellNames,
packageConfig.get().getCommand(),
buildDepsBuilder,
targetGraphOnlyDepsBuilder);
} catch (MacroException e) {
throw new HumanReadableException(
e,
"%s (for platform %s): %s",
target,
platform.getAppleSdk().getApplePlatform().getName(),
e.getMessage());
}
}
}
}
private ImmutableSet<Flavor> getPlatformFlavorsOrDefault(BuildTarget target) {
Sets.SetView<Flavor> intersection =
Sets.intersection(appleCxxPlatformFlavorDomain.getFlavors(), target.getFlavors());
if (intersection.isEmpty()) {
return ImmutableSet.of(defaultCxxPlatform.getFlavor());
} else {
return intersection.immutableCopy();
}
}
}