// 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.LegacyCompilationSupport.AUTOMATIC_SDK_FRAMEWORKS; 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_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.STATIC_FRAMEWORK_DIR; 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 static com.google.devtools.build.lib.rules.objc.XcodeProductType.WATCH_OS1_APPLICATION; 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.ImmutableSet; 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(); // Automatic SDK frameworks are no longer propagated through ObjcProvider; they are now added // during the link action. To preserve the existing Xcode project generation, we need to add // them to the Xcode target below, unless it is a watchOS 1 application. Set<SdkFramework> automaticSdkFrameworks = (productType != WATCH_OS1_APPLICATION) ? ImmutableSet.copyOf(AUTOMATIC_SDK_FRAMEWORKS) : ImmutableSet.<SdkFramework>of(); // 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(automaticSdkFrameworks)) .addAllSdkFramework(SdkFramework.names(objcProvider.get(SDK_FRAMEWORK))) .addAllFramework(PathFragment.safePathStrings(objcProvider.get(STATIC_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(); } }