blob: 12d80b753d6253e690c2dd308a4bc4d3cba62a89 [file] [log] [blame]
// Copyright 2014 The Bazel Authors. All rights reserved.
//
// 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.google.devtools.build.lib.rules.objc;
import static com.google.devtools.build.lib.rules.objc.ObjcProvider.BUNDLE_IMPORT_DIR;
import static com.google.devtools.build.lib.rules.objc.ObjcProvider.CC_LIBRARY;
import static com.google.devtools.build.lib.rules.objc.ObjcProvider.DEFINE;
import static com.google.devtools.build.lib.rules.objc.ObjcProvider.FORCE_LOAD_FOR_XCODEGEN;
import static com.google.devtools.build.lib.rules.objc.ObjcProvider.FRAMEWORK_DIR;
import static com.google.devtools.build.lib.rules.objc.ObjcProvider.FRAMEWORK_SEARCH_PATH_ONLY;
import static com.google.devtools.build.lib.rules.objc.ObjcProvider.GENERAL_RESOURCE_DIR;
import static com.google.devtools.build.lib.rules.objc.ObjcProvider.GENERAL_RESOURCE_FILE;
import static com.google.devtools.build.lib.rules.objc.ObjcProvider.IMPORTED_LIBRARY;
import static com.google.devtools.build.lib.rules.objc.ObjcProvider.SDK_DYLIB;
import static com.google.devtools.build.lib.rules.objc.ObjcProvider.SDK_FRAMEWORK;
import static com.google.devtools.build.lib.rules.objc.ObjcProvider.WEAK_SDK_FRAMEWORK;
import static com.google.devtools.build.lib.rules.objc.ObjcProvider.XCASSETS_DIR;
import static com.google.devtools.build.lib.rules.objc.ObjcProvider.XCDATAMODEL;
import static com.google.devtools.build.lib.rules.objc.XcodeProductType.LIBRARY_STATIC;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.analysis.TransitiveInfoProvider;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
import com.google.devtools.build.lib.rules.apple.AppleConfiguration.ConfigurationDistinguisher;
import com.google.devtools.build.lib.rules.apple.AppleToolchain;
import com.google.devtools.build.lib.rules.cpp.LinkerInputs;
import com.google.devtools.build.lib.rules.objc.ObjcProvider.Flag;
import com.google.devtools.build.lib.util.Preconditions;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.xcode.xcodegen.proto.XcodeGenProtos.DependencyControl;
import com.google.devtools.build.xcode.xcodegen.proto.XcodeGenProtos.TargetControl;
import com.google.devtools.build.xcode.xcodegen.proto.XcodeGenProtos.XcodeprojBuildSetting;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Provider which provides transitive dependency information that is specific to Xcodegen. In
* particular, it provides a sequence of targets which can be used to create a self-contained
* {@code .xcodeproj} file.
*/
@Immutable
public final class XcodeProvider implements TransitiveInfoProvider {
private static final String COMPANION_LIB_TARGET_LABEL_SUFFIX = "_static_lib";
/**
* A builder for instances of {@link XcodeProvider}.
*/
public static final class Builder {
private Label label;
// Propagated dependencies and search paths are seen by all transitive dependents of this
// target. Non propagated dependencies are only seen by this target; none of the direct and
// transitive dependents of this target will see this provider. Strictly propagated dependencies
// are only seen by this target and only direct dependents; transitive dependents won't see this
// provider.
private final NestedSetBuilder<String> propagatedUserHeaderSearchPaths =
NestedSetBuilder.linkOrder();
private final NestedSetBuilder<String> nonPropagatedUserHeaderSearchPaths =
NestedSetBuilder.linkOrder();
private final NestedSetBuilder<String> strictlyPropagatedUserHeaderSearchPaths =
NestedSetBuilder.linkOrder();
private final NestedSetBuilder<String> propagatedHeaderSearchPaths =
NestedSetBuilder.linkOrder();
private final NestedSetBuilder<String> nonPropagatedHeaderSearchPaths =
NestedSetBuilder.linkOrder();
private final NestedSetBuilder<String> strictlyPropagatedHeaderSearchPaths =
NestedSetBuilder.linkOrder();
// Dependencies must be in link order because XCode observes the dependency ordering for
// binary linking.
private final NestedSetBuilder<XcodeProvider> propagatedDependencies =
NestedSetBuilder.linkOrder();
private final NestedSetBuilder<XcodeProvider> nonPropagatedDependencies =
NestedSetBuilder.linkOrder();
private final NestedSetBuilder<XcodeProvider> strictlyPropagatedDependencies =
NestedSetBuilder.linkOrder();
private Optional<Artifact> bundleInfoplist = Optional.absent();
private final NestedSetBuilder<XcodeProvider> jreDependencies = NestedSetBuilder.linkOrder();
private final ImmutableList.Builder<XcodeprojBuildSetting> xcodeprojBuildSettings =
new ImmutableList.Builder<>();
private final ImmutableList.Builder<XcodeprojBuildSetting>
companionTargetXcodeprojBuildSettings = new ImmutableList.Builder<>();
private final ImmutableList.Builder<String> copts = new ImmutableList.Builder<>();
private final ImmutableList.Builder<String> compilationModeCopts =
new ImmutableList.Builder<>();
private XcodeProductType productType;
private final ImmutableList.Builder<Artifact> headers = new ImmutableList.Builder<>();
private Optional<CompilationArtifacts> compilationArtifacts = Optional.absent();
private ObjcProvider objcProvider;
private Optional<XcodeProvider> testHost = Optional.absent();
private final NestedSetBuilder<Artifact> inputsToXcodegen = NestedSetBuilder.stableOrder();
private final NestedSetBuilder<Artifact> additionalSources = NestedSetBuilder.stableOrder();
private final ImmutableList.Builder<XcodeProvider> extensions = new ImmutableList.Builder<>();
private String architecture;
private boolean generateCompanionLibTarget = false;
private ConfigurationDistinguisher configurationDistinguisher;
/**
* Sets the label of the build target which corresponds to this Xcode target.
*/
public Builder setLabel(Label label) {
this.label = label;
return this;
}
/**
* Adds user header search paths for this target.
*/
public Builder addUserHeaderSearchPaths(Iterable<PathFragment> userHeaderSearchPaths) {
this.propagatedUserHeaderSearchPaths
.addAll(rootEach("$(WORKSPACE_ROOT)", userHeaderSearchPaths));
return this;
}
/**
* Adds header search paths for this target. Each path is interpreted relative to the given
* root, such as {@code "$(WORKSPACE_ROOT)"}.
*/
public Builder addHeaderSearchPaths(String root, Iterable<PathFragment> paths) {
this.propagatedHeaderSearchPaths.addAll(rootEach(root, paths));
return this;
}
/**
* Adds non-propagated header search paths for this target. Each relative path is interpreted
* relative to the given root, such as {@code "$(WORKSPACE_ROOT)"}.
*/
public Builder addNonPropagatedHeaderSearchPaths(String root, Iterable<PathFragment> paths) {
this.nonPropagatedHeaderSearchPaths.addAll(rootEach(root, paths));
return this;
}
/**
* Sets the Info.plist for the bundle represented by this provider.
*/
public Builder setBundleInfoplist(Artifact bundleInfoplist) {
this.bundleInfoplist = Optional.of(bundleInfoplist);
return this;
}
/**
* Adds items in the {@link NestedSet}s of the given target to the corresponding sets in this
* builder. This is useful if the given target is a dependency or like a dependency
* (e.g. a test host). The given provider is not registered as a dependency with this provider.
*/
private void addTransitiveSets(XcodeProvider dependencyish) {
additionalSources.addTransitive(dependencyish.additionalSources);
inputsToXcodegen.addTransitive(dependencyish.inputsToXcodegen);
propagatedUserHeaderSearchPaths.addTransitive(dependencyish.propagatedUserHeaderSearchPaths);
propagatedHeaderSearchPaths.addTransitive(dependencyish.propagatedHeaderSearchPaths);
}
/**
* Adds {@link XcodeProvider}s corresponding to direct dependencies of this target which should
* be added in the {@code .xcodeproj} file and propagated up the dependency chain.
*/
public Builder addPropagatedDependencies(Iterable<XcodeProvider> dependencies) {
return addDependencies(dependencies, /*doPropagate=*/true);
}
/**
* Adds {@link XcodeProvider}s corresponding to direct dependencies of this target which should
* be added in the {@code .xcodeproj} file and only propagate the header search paths to direct
* dependents of this target. Propagated dependencies of this target are still propagated as the
* code still needs to be linked, and only limits the propagation of this target's header search
* paths.
*/
public Builder addPropagatedDependenciesWithStrictDependencyHeaders(
Iterable<XcodeProvider> dependencies) {
for (XcodeProvider dependency : dependencies) {
this.strictlyPropagatedDependencies.add(dependency);
this.propagatedDependencies.addTransitive(dependency.propagatedDependencies);
this.nonPropagatedDependencies.addTransitive(dependency.strictlyPropagatedDependencies);
this.strictlyPropagatedUserHeaderSearchPaths.addTransitive(
dependency.propagatedUserHeaderSearchPaths);
this.strictlyPropagatedHeaderSearchPaths.addTransitive(
dependency.propagatedHeaderSearchPaths);
}
return this;
}
/**
* Adds {@link XcodeProvider}s corresponding to direct dependencies of this target which should
* be added in the {@code .xcodeproj} file and not propagated up the dependency chain.
*/
public Builder addNonPropagatedDependencies(Iterable<XcodeProvider> dependencies) {
return addDependencies(dependencies, /*doPropagate=*/false);
}
/**
* Adds {@link XcodeProvider}s corresponding to direct J2ObjC JRE dependencies of this target
* which should be added in the {@code .xcodeproj} file and propagated up the dependency chain.
*/
public Builder addJreDependencies(Iterable<XcodeProvider> dependencies) {
for (XcodeProvider dependency : dependencies) {
this.jreDependencies.add(dependency);
this.jreDependencies.addTransitive(dependency.propagatedDependencies);
}
return addDependencies(dependencies, /*doPropagate=*/true);
}
private Builder addDependencies(Iterable<XcodeProvider> dependencies, boolean doPropagate) {
for (XcodeProvider dependency : dependencies) {
// TODO(bazel-team): This is messy. Maybe we should make XcodeProvider be able to specify
// how to depend on it rather than require this method to choose based on the dependency's
// type.
if (dependency.productType == XcodeProductType.EXTENSION
|| dependency.productType == XcodeProductType.WATCH_OS1_EXTENSION) {
this.extensions.add(dependency);
this.inputsToXcodegen.addTransitive(dependency.inputsToXcodegen);
this.additionalSources.addTransitive(dependency.additionalSources);
} else {
if (doPropagate) {
this.propagatedDependencies.add(dependency);
this.propagatedDependencies.addTransitive(dependency.propagatedDependencies);
this.jreDependencies.addTransitive(dependency.jreDependencies);
this.addTransitiveSets(dependency);
} else {
this.nonPropagatedDependencies.add(dependency);
this.nonPropagatedDependencies.addTransitive(dependency.propagatedDependencies);
this.nonPropagatedUserHeaderSearchPaths
.addTransitive(dependency.propagatedUserHeaderSearchPaths);
this.nonPropagatedHeaderSearchPaths
.addTransitive(dependency.propagatedHeaderSearchPaths);
this.inputsToXcodegen.addTransitive(dependency.inputsToXcodegen);
}
this.nonPropagatedUserHeaderSearchPaths.addTransitive(
dependency.strictlyPropagatedUserHeaderSearchPaths);
this.nonPropagatedHeaderSearchPaths.addTransitive(
dependency.strictlyPropagatedHeaderSearchPaths);
}
}
return this;
}
/**
* Adds additional build settings of this target and its companion library target, if it exists.
*/
public Builder addXcodeprojBuildSettings(
Iterable<XcodeprojBuildSetting> xcodeprojBuildSettings) {
this.xcodeprojBuildSettings.addAll(xcodeprojBuildSettings);
this.companionTargetXcodeprojBuildSettings.addAll(xcodeprojBuildSettings);
return this;
}
/**
* Adds additional build settings of this target without adding them to the companion lib
* target, if it exists.
*/
public Builder addMainTargetXcodeprojBuildSettings(
Iterable<XcodeprojBuildSetting> xcodeprojBuildSettings) {
this.xcodeprojBuildSettings.addAll(xcodeprojBuildSettings);
return this;
}
/**
* Sets the copts to use when compiling the Xcode target.
*/
public Builder addCopts(Iterable<String> copts) {
this.copts.addAll(copts);
return this;
}
/**
* Sets the copts derived from compilation mode to use when compiling the Xcode target. These
* will be included before the DEFINE options.
*/
public Builder addCompilationModeCopts(Iterable<String> copts) {
this.compilationModeCopts.addAll(copts);
return this;
}
/**
* Sets the product type for the PBXTarget in the .xcodeproj file.
*/
public Builder setProductType(XcodeProductType productType) {
this.productType = productType;
return this;
}
/**
* Adds to the header files of this target. It needs not to include the header files of
* dependencies.
*/
public Builder addHeaders(Iterable<Artifact> headers) {
this.headers.addAll(headers);
return this;
}
/**
* The compilation artifacts for this target.
*/
public Builder setCompilationArtifacts(CompilationArtifacts compilationArtifacts) {
this.compilationArtifacts = Optional.of(compilationArtifacts);
return this;
}
/**
* Any additional sources not included in {@link #setCompilationArtifacts}.
*/
public Builder addAdditionalSources(Artifact... artifacts) {
this.additionalSources.addAll(Arrays.asList(artifacts));
return this;
}
/**
* Sets the {@link ObjcProvider} corresponding to this target.
*/
public Builder setObjcProvider(ObjcProvider objcProvider) {
this.objcProvider = objcProvider;
return this;
}
/**
* Sets the test host. This is used for xctest targets.
*/
public Builder setTestHost(XcodeProvider testHost) {
Preconditions.checkState(!this.testHost.isPresent());
this.testHost = Optional.of(testHost);
this.addTransitiveSets(testHost);
return this;
}
/**
* Adds inputs that are passed to Xcodegen when generating the project file.
*/
public Builder addInputsToXcodegen(Iterable<Artifact> inputsToXcodegen) {
this.inputsToXcodegen.addAll(inputsToXcodegen);
return this;
}
/**
* Sets the CPU architecture this xcode target was constructed for, derived from
* {@link ObjcConfiguration#getIosCpu()}.
*/
public Builder setArchitecture(String architecture) {
this.architecture = architecture;
return this;
}
/**
* Generates an extra LIBRARY_STATIC Xcode target with the same compilation artifacts. Dependent
* Xcode targets will pick this companion library target as its dependency, rather than the
* main Xcode target of this provider.
*/
// TODO(bazel-team): Remove this when the binary rule types and bundling rule types are merged.
public Builder generateCompanionLibTarget() {
this.generateCompanionLibTarget = true;
return this;
}
/**
* Sets the distinguisher that will cause this xcode provider to discard any dependencies from
* sources that are tagged with a different distinguisher.
*/
public Builder setConfigurationDistinguisher(ConfigurationDistinguisher distinguisher) {
this.configurationDistinguisher = distinguisher;
return this;
}
public XcodeProvider build() {
Preconditions.checkState(
!testHost.isPresent() || (productType == XcodeProductType.UNIT_TEST),
"%s product types cannot have a test host (test host: %s).", productType, testHost);
return new XcodeProvider(this);
}
}
/**
* A collection of top-level targets that can be used to create a complete project.
*/
public static final class Project {
private final NestedSet<Artifact> inputsToXcodegen;
private final NestedSet<Artifact> additionalSources;
private final ImmutableList<XcodeProvider> topLevelTargets;
private Project(
NestedSet<Artifact> inputsToXcodegen, NestedSet<Artifact> additionalSources,
ImmutableList<XcodeProvider> topLevelTargets) {
this.inputsToXcodegen = inputsToXcodegen;
this.additionalSources = additionalSources;
this.topLevelTargets = topLevelTargets;
}
public static Project fromTopLevelTarget(XcodeProvider topLevelTarget) {
return fromTopLevelTargets(ImmutableList.of(topLevelTarget));
}
public static Project fromTopLevelTargets(Iterable<XcodeProvider> topLevelTargets) {
NestedSetBuilder<Artifact> inputsToXcodegen = NestedSetBuilder.stableOrder();
NestedSetBuilder<Artifact> additionalSources = NestedSetBuilder.stableOrder();
for (XcodeProvider target : topLevelTargets) {
inputsToXcodegen.addTransitive(target.inputsToXcodegen);
additionalSources.addTransitive(target.additionalSources);
}
return new Project(inputsToXcodegen.build(), additionalSources.build(),
ImmutableList.copyOf(topLevelTargets));
}
/**
* Returns artifacts that are passed to the Xcodegen action when generating a project file that
* contains all of the given targets.
*/
public NestedSet<Artifact> getInputsToXcodegen() {
return inputsToXcodegen;
}
/**
* Returns artifacts that are additional sources for the Xcodegen action.
*/
public NestedSet<Artifact> getAdditionalSources() {
return additionalSources;
}
/**
* Returns all the target controls that must be added to the xcodegen control. No other target
* controls are needed to generate a functional project file. This method creates a new list
* whenever it is called.
*/
public ImmutableList<TargetControl> targets() {
// Collect all the dependencies of all the providers, filtering out duplicates.
Set<XcodeProvider> providerSet = new LinkedHashSet<>();
for (XcodeProvider target : topLevelTargets) {
target.collectProviders(providerSet);
}
ImmutableList.Builder<TargetControl> controls = new ImmutableList.Builder<>();
Map<Label, XcodeProvider> labelToProvider = new HashMap<>();
for (XcodeProvider provider : providerSet) {
XcodeProvider oldProvider = labelToProvider.put(provider.label, provider);
if (oldProvider != null) {
if (!oldProvider.architecture.equals(provider.architecture)
|| oldProvider.configurationDistinguisher != provider.configurationDistinguisher) {
// Do not include duplicate dependencies whose architecture or configuration
// distinguisher does not match this project's. This check avoids having multiple
// conflicting Xcode targets for the same BUILD target that are only distinguished by
// these fields (which Xcode does not care about).
continue;
}
throw new IllegalStateException("Depending on multiple versions of the same xcode target "
+ "is not allowed but occurred for: " + provider.label);
}
controls.addAll(provider.targetControls());
}
return controls.build();
}
}
private final Label label;
private final NestedSet<String> propagatedUserHeaderSearchPaths;
private final NestedSet<String> nonPropagatedUserHeaderSearchPaths;
private final NestedSet<String> strictlyPropagatedUserHeaderSearchPaths;
private final NestedSet<String> propagatedHeaderSearchPaths;
private final NestedSet<String> nonPropagatedHeaderSearchPaths;
private final NestedSet<String> strictlyPropagatedHeaderSearchPaths;
private final Optional<Artifact> bundleInfoplist;
private final NestedSet<XcodeProvider> propagatedDependencies;
private final NestedSet<XcodeProvider> nonPropagatedDependencies;
private final NestedSet<XcodeProvider> strictlyPropagatedDependencies;
private final NestedSet<XcodeProvider> jreDependencies;
private final ImmutableList<XcodeprojBuildSetting> xcodeprojBuildSettings;
private final ImmutableList<XcodeprojBuildSetting> companionTargetXcodeprojBuildSettings;
private final ImmutableList<String> copts;
private final ImmutableList<String> compilationModeCopts;
private final XcodeProductType productType;
private final ImmutableList<Artifact> headers;
private final Optional<CompilationArtifacts> compilationArtifacts;
private final ObjcProvider objcProvider;
private final Optional<XcodeProvider> testHost;
private final NestedSet<Artifact> inputsToXcodegen;
private final NestedSet<Artifact> additionalSources;
private final ImmutableList<XcodeProvider> extensions;
private final String architecture;
private final boolean generateCompanionLibTarget;
private final ConfigurationDistinguisher configurationDistinguisher;
private XcodeProvider(Builder builder) {
this.label = Preconditions.checkNotNull(builder.label);
this.propagatedUserHeaderSearchPaths = builder.propagatedUserHeaderSearchPaths.build();
this.nonPropagatedUserHeaderSearchPaths = builder.nonPropagatedUserHeaderSearchPaths.build();
this.strictlyPropagatedUserHeaderSearchPaths =
builder.strictlyPropagatedUserHeaderSearchPaths.build();
this.propagatedHeaderSearchPaths = builder.propagatedHeaderSearchPaths.build();
this.nonPropagatedHeaderSearchPaths = builder.nonPropagatedHeaderSearchPaths.build();
this.strictlyPropagatedHeaderSearchPaths = builder.strictlyPropagatedHeaderSearchPaths.build();
this.bundleInfoplist = builder.bundleInfoplist;
this.propagatedDependencies = builder.propagatedDependencies.build();
this.nonPropagatedDependencies = builder.nonPropagatedDependencies.build();
this.strictlyPropagatedDependencies = builder.strictlyPropagatedDependencies.build();
this.jreDependencies = builder.jreDependencies.build();
this.xcodeprojBuildSettings = builder.xcodeprojBuildSettings.build();
this.companionTargetXcodeprojBuildSettings =
builder.companionTargetXcodeprojBuildSettings.build();
this.copts = builder.copts.build();
this.compilationModeCopts = builder.compilationModeCopts.build();
this.productType = Preconditions.checkNotNull(builder.productType);
this.headers = builder.headers.build();
this.compilationArtifacts = builder.compilationArtifacts;
this.objcProvider = Preconditions.checkNotNull(builder.objcProvider);
this.testHost = Preconditions.checkNotNull(builder.testHost);
this.inputsToXcodegen = builder.inputsToXcodegen.build();
this.additionalSources = builder.additionalSources.build();
this.extensions = builder.extensions.build();
this.architecture = Preconditions.checkNotNull(builder.architecture);
this.generateCompanionLibTarget = builder.generateCompanionLibTarget;
this.configurationDistinguisher =
Preconditions.checkNotNull(builder.configurationDistinguisher);
}
private void collectProviders(Set<XcodeProvider> allProviders) {
if (allProviders.add(this)) {
Iterable<XcodeProvider> allDependencies =
Iterables.concat(
propagatedDependencies, nonPropagatedDependencies, strictlyPropagatedDependencies);
for (XcodeProvider dependency : allDependencies) {
dependency.collectProviders(allProviders);
}
for (XcodeProvider justTestHost : testHost.asSet()) {
justTestHost.collectProviders(allProviders);
}
for (XcodeProvider extension : extensions) {
extension.collectProviders(allProviders);
}
}
}
@VisibleForTesting
static final EnumSet<XcodeProductType> CAN_LINK_PRODUCT_TYPES =
EnumSet.of(
XcodeProductType.APPLICATION,
XcodeProductType.BUNDLE,
XcodeProductType.UNIT_TEST,
XcodeProductType.EXTENSION,
XcodeProductType.FRAMEWORK,
XcodeProductType.WATCH_OS1_EXTENSION,
XcodeProductType.WATCH_OS1_APPLICATION);
/**
* Returns the name of the Xcode target that corresponds to a build target with the given name.
* This changes the label to make it a legal Xcode target name (which means removing slashes and
* the colon). It also makes the label more readable in the Xcode UI by putting the target name
* first and the package elements in reverse. This means the "important" part is visible even if
* the project navigator is too narrow to show the entire name.
*/
static String xcodeTargetName(Label label) {
return xcodeTargetName(label, /*labelSuffix=*/"");
}
/**
* Returns the name of the companion Xcode library target that corresponds to a build target with
* the given name. See {@link XcodeSupport#generateCompanionLibXcodeTarget} for the rationale of
* the companion library target and {@link #xcodeTargetName(Label)} for naming details.
*/
static String xcodeCompanionLibTargetName(Label label) {
return xcodeTargetName(label, COMPANION_LIB_TARGET_LABEL_SUFFIX);
}
private static String xcodeTargetName(Label label, String labelSuffix) {
String pathFromWorkspaceRoot = label + labelSuffix;
if (label.getPackageIdentifier().getRepository().isMain()) {
pathFromWorkspaceRoot = pathFromWorkspaceRoot.replace("//", "")
.replace(':', '/');
} else {
pathFromWorkspaceRoot = pathFromWorkspaceRoot.replace("//", "_")
.replace(':', '/').replace("@", "external_");
}
List<String> components = Splitter.on('/').splitToList(pathFromWorkspaceRoot);
return Joiner.on('_').join(Lists.reverse(components));
}
/**
* Returns the name of the xcode target in this provider to be referenced as a dep for dependents.
*/
private String dependencyXcodeTargetName() {
return generateCompanionLibTarget ? xcodeCompanionLibTargetName(label) : xcodeTargetName(label);
}
private Iterable<TargetControl> targetControls() {
TargetControl mainTargetControl = targetControl();
if (generateCompanionLibTarget) {
return ImmutableList.of(mainTargetControl, companionLibTargetControl(mainTargetControl));
} else {
return ImmutableList.of(mainTargetControl);
}
}
private TargetControl targetControl() {
String buildFilePath = label.getPackageFragment().getSafePathString() + "/BUILD";
NestedSet<String> userHeaderSearchPaths =
NestedSetBuilder.<String>linkOrder()
.addTransitive(propagatedUserHeaderSearchPaths)
.addTransitive(nonPropagatedUserHeaderSearchPaths)
.addTransitive(strictlyPropagatedUserHeaderSearchPaths)
.build();
NestedSet<String> headerSearchPaths =
NestedSetBuilder.<String>linkOrder()
.addTransitive(propagatedHeaderSearchPaths)
.addTransitive(nonPropagatedHeaderSearchPaths)
.addTransitive(strictlyPropagatedHeaderSearchPaths)
.build();
// TODO(bazel-team): Add provisioning profile information when Xcodegen supports it.
TargetControl.Builder targetControl =
TargetControl.newBuilder()
.setName(label.getName())
.setLabel(xcodeTargetName(label))
.setProductType(productType.getIdentifier())
.addSupportFile(buildFilePath)
.addAllImportedLibrary(Artifact.toExecPaths(objcProvider.get(IMPORTED_LIBRARY)))
.addAllImportedLibrary(Artifact.toExecPaths(ccLibraries(objcProvider)))
.addAllUserHeaderSearchPath(userHeaderSearchPaths)
.addAllHeaderSearchPath(headerSearchPaths)
.addAllSupportFile(Artifact.toExecPaths(headers))
.addAllCopt(compilationModeCopts)
.addAllCopt(CompilationSupport.DEFAULT_COMPILER_FLAGS)
.addAllCopt(Interspersing.prependEach("-D", objcProvider.get(DEFINE)))
.addAllCopt(copts)
.addAllLinkopt(
Interspersing.beforeEach("-force_load", objcProvider.get(FORCE_LOAD_FOR_XCODEGEN)))
.addAllLinkopt(CompilationSupport.DEFAULT_LINKER_FLAGS)
.addAllLinkopt(
Interspersing.beforeEach(
"-weak_framework", SdkFramework.names(objcProvider.get(WEAK_SDK_FRAMEWORK))))
.addAllBuildSetting(xcodeprojBuildSettings)
.addAllBuildSetting(AppleToolchain.defaultWarningsForXcode())
.addAllSdkFramework(SdkFramework.names(objcProvider.get(SDK_FRAMEWORK)))
.addAllFramework(PathFragment.safePathStrings(objcProvider.get(FRAMEWORK_DIR)))
.addAllFrameworkSearchPathOnly(
PathFragment.safePathStrings(objcProvider.get(FRAMEWORK_SEARCH_PATH_ONLY)))
.addAllXcassetsDir(PathFragment.safePathStrings(objcProvider.get(XCASSETS_DIR)))
.addAllXcdatamodel(
PathFragment.safePathStrings(
Xcdatamodels.datamodelDirs(objcProvider.get(XCDATAMODEL))))
.addAllBundleImport(PathFragment.safePathStrings(objcProvider.get(BUNDLE_IMPORT_DIR)))
.addAllSdkDylib(objcProvider.get(SDK_DYLIB))
.addAllGeneralResourceFile(
Artifact.toExecPaths(objcProvider.get(GENERAL_RESOURCE_FILE)))
.addAllGeneralResourceFile(
PathFragment.safePathStrings(objcProvider.get(GENERAL_RESOURCE_DIR)));
if (CAN_LINK_PRODUCT_TYPES.contains(productType)) {
// For builds with --ios_multi_cpus set, we may have several copies of some XCodeProviders
// in the dependencies (one per cpu architecture). We deduplicate the corresponding
// xcode target names with a LinkedHashSet before adding to the TargetControl.
Set<String> jreTargetNames = new HashSet<>();
for (XcodeProvider jreDependency : jreDependencies) {
jreTargetNames.add(jreDependency.dependencyXcodeTargetName());
}
Set<DependencyControl> dependencySet = new LinkedHashSet<>();
Set<DependencyControl> jreDependencySet = new LinkedHashSet<>();
for (XcodeProvider dependency : propagatedDependencies) {
// Only add a library target to a binary's dependencies if it has source files to compile
// and it is not from the "non_propagated_deps" attribute. Xcode cannot build targets
// without a source file in the PBXSourceFilesBuildPhase, so if such a target is present in
// the control file, it is only to get Xcodegen to put headers and resources not used by the
// final binary in the Project Navigator.
//
// The exceptions to this rule are objc_bundle_library and ios_extension targets. Bundles
// are generally used for resources and can lack a PBXSourceFilesBuildPhase in the project
// file and still be considered valid by Xcode.
//
// ios_extension targets are an exception because they have no CompilationArtifact object
// but do have a dummy source file to make Xcode happy.
boolean hasSources = dependency.compilationArtifacts.isPresent()
&& dependency.compilationArtifacts.get().getArchive().isPresent();
if (hasSources
|| (dependency.productType == XcodeProductType.BUNDLE
|| (dependency.productType == XcodeProductType.WATCH_OS1_APPLICATION))) {
String dependencyXcodeTargetName = dependency.dependencyXcodeTargetName();
Set<DependencyControl> set = jreTargetNames.contains(dependencyXcodeTargetName)
? jreDependencySet : dependencySet;
set.add(DependencyControl.newBuilder()
.setTargetLabel(dependencyXcodeTargetName)
.build());
}
}
for (DependencyControl dependencyControl : dependencySet) {
targetControl.addDependency(dependencyControl);
}
// Make sure that JRE dependencies are ordered after other propagated dependencies.
for (DependencyControl dependencyControl : jreDependencySet) {
targetControl.addDependency(dependencyControl);
}
}
for (XcodeProvider justTestHost : testHost.asSet()) {
targetControl.addDependency(DependencyControl.newBuilder()
.setTargetLabel(xcodeTargetName(justTestHost.label))
.setTestHost(true)
.build());
}
for (XcodeProvider extension : extensions) {
targetControl.addDependency(DependencyControl.newBuilder()
.setTargetLabel(xcodeTargetName(extension.label))
.build());
}
if (bundleInfoplist.isPresent()) {
targetControl.setInfoplist(bundleInfoplist.get().getExecPathString());
}
for (CompilationArtifacts artifacts : compilationArtifacts.asSet()) {
targetControl
.addAllSourceFile(Artifact.toExecPaths(artifacts.getSrcs()))
.addAllSupportFile(Artifact.toExecPaths(artifacts.getAdditionalHdrs()))
.addAllSupportFile(Artifact.toExecPaths(artifacts.getPrivateHdrs()))
.addAllNonArcSourceFile(Artifact.toExecPaths(artifacts.getNonArcSrcs()));
for (Artifact pchFile : artifacts.getPchFile().asSet()) {
targetControl
.setPchPath(pchFile.getExecPathString())
.addSupportFile(pchFile.getExecPathString());
}
}
for (Artifact artifact : additionalSources) {
targetControl.addSourceFile(artifact.getExecPathString());
}
if (objcProvider.is(Flag.USES_CPP)) {
targetControl.addSdkDylib("libc++");
}
return targetControl.build();
}
private TargetControl companionLibTargetControl(TargetControl mainTargetControl) {
return TargetControl.newBuilder()
.mergeFrom(mainTargetControl)
.setName(label.getName() + COMPANION_LIB_TARGET_LABEL_SUFFIX)
.setLabel(xcodeCompanionLibTargetName(label))
.setProductType(LIBRARY_STATIC.getIdentifier())
.clearInfoplist()
.clearDependency()
.clearBuildSetting()
.addAllBuildSetting(companionTargetXcodeprojBuildSettings)
.addAllBuildSetting(AppleToolchain.defaultWarningsForXcode())
.build();
}
/**
* Prepends the given path to each path in {@code paths}. Empty paths are
* transformed to the value of {@code variable} rather than {@code variable + "/."}. Absolute
* paths are returned without modifications.
*/
@VisibleForTesting
static Iterable<String> rootEach(final String prefix, Iterable<PathFragment> paths) {
Preconditions.checkArgument(prefix.startsWith("$"),
"prefix should start with a build setting variable like '$(NAME)': %s", prefix);
Preconditions.checkArgument(!prefix.endsWith("/"),
"prefix should not end with '/': %s", prefix);
return Iterables.transform(paths, new Function<PathFragment, String>() {
@Override
public String apply(PathFragment input) {
if (input.getSafePathString().equals(".")) {
return prefix;
} else if (input.isAbsolute()) {
return input.getSafePathString();
} else {
return prefix + "/" + input.getSafePathString();
}
}
});
}
private ImmutableList<Artifact> ccLibraries(ObjcProvider objcProvider) {
ImmutableList.Builder<Artifact> ccLibraryBuilder = ImmutableList.builder();
for (LinkerInputs.LibraryToLink libraryToLink : objcProvider.get(CC_LIBRARY)) {
ccLibraryBuilder.add(libraryToLink.getArtifact());
}
return ccLibraryBuilder.build();
}
}