| // 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(); |
| } |
| } |