aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/rules/objc/XcodeProvider.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/rules/objc/XcodeProvider.java')
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/objc/XcodeProvider.java452
1 files changed, 452 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/XcodeProvider.java b/src/main/java/com/google/devtools/build/lib/rules/objc/XcodeProvider.java
new file mode 100644
index 0000000000..b244cd9839
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/XcodeProvider.java
@@ -0,0 +1,452 @@
+// Copyright 2014 Google Inc. 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.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.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 com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.TransitiveInfoProvider;
+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.objc.ObjcProvider.Flag;
+import com.google.devtools.build.lib.syntax.Label;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.xcode.util.Interspersing;
+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.EnumSet;
+import java.util.LinkedHashSet;
+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 {
+ /**
+ * A builder for instances of {@link XcodeProvider}.
+ */
+ public static final class Builder {
+ private Label label;
+ private final NestedSetBuilder<String> userHeaderSearchPaths = NestedSetBuilder.stableOrder();
+ private final NestedSetBuilder<String> headerSearchPaths = NestedSetBuilder.stableOrder();
+ private Optional<InfoplistMerging> infoplistMerging = Optional.absent();
+ private final NestedSetBuilder<XcodeProvider> dependencies = NestedSetBuilder.stableOrder();
+ private final ImmutableList.Builder<XcodeprojBuildSetting> xcodeprojBuildSettings =
+ 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();
+
+ /**
+ * 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.userHeaderSearchPaths.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.headerSearchPaths.addAll(rootEach(root, paths));
+ return this;
+ }
+
+ /**
+ * Sets the Info.plist merging information. Used for applications. May be
+ * absent for other bundles.
+ */
+ public Builder setInfoplistMerging(InfoplistMerging infoplistMerging) {
+ this.infoplistMerging = Optional.of(infoplistMerging);
+ 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) {
+ inputsToXcodegen.addTransitive(dependencyish.inputsToXcodegen);
+ userHeaderSearchPaths.addTransitive(dependencyish.userHeaderSearchPaths);
+ headerSearchPaths.addTransitive(dependencyish.headerSearchPaths);
+ }
+
+ /**
+ * Adds {@link XcodeProvider}s corresponding to direct dependencies of this target which should
+ * be added in the {@code .xcodeproj} file.
+ */
+ public Builder addDependencies(Iterable<XcodeProvider> dependencies) {
+ for (XcodeProvider dependency : dependencies) {
+ this.dependencies.add(dependency);
+ this.dependencies.addTransitive(dependency.dependencies);
+ this.addTransitiveSets(dependency);
+ }
+ return this;
+ }
+
+ /**
+ * Adds additional build settings of this target.
+ */
+ public Builder addXcodeprojBuildSettings(
+ 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;
+ }
+
+ /**
+ * 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;
+ }
+
+ public XcodeProvider build() {
+ Preconditions.checkArgument(
+ !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 ImmutableList<XcodeProvider> topLevelTargets;
+
+ private Project(
+ NestedSet<Artifact> inputsToXcodegen, ImmutableList<XcodeProvider> topLevelTargets) {
+ this.inputsToXcodegen = inputsToXcodegen;
+ 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();
+ for (XcodeProvider target : topLevelTargets) {
+ inputsToXcodegen.addTransitive(target.inputsToXcodegen);
+ }
+ return new Project(inputsToXcodegen.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;
+ }
+
+ public ImmutableList<XcodeProvider> getTopLevelTargets() {
+ return topLevelTargets;
+ }
+
+ /**
+ * 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) {
+ Iterables.addAll(providerSet, target.providers());
+ }
+
+ ImmutableList.Builder<TargetControl> controls = new ImmutableList.Builder<>();
+ for (XcodeProvider provider : providerSet) {
+ controls.add(provider.targetControl());
+ }
+ return controls.build();
+ }
+ }
+
+ private final Label label;
+ private final NestedSet<String> userHeaderSearchPaths;
+ private final NestedSet<String> headerSearchPaths;
+ private final Optional<InfoplistMerging> infoplistMerging;
+ private final NestedSet<XcodeProvider> dependencies;
+ private final ImmutableList<XcodeprojBuildSetting> xcodeprojBuildSettings;
+ 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 XcodeProvider(Builder builder) {
+ this.label = Preconditions.checkNotNull(builder.label);
+ this.userHeaderSearchPaths = builder.userHeaderSearchPaths.build();
+ this.headerSearchPaths = builder.headerSearchPaths.build();
+ this.infoplistMerging = builder.infoplistMerging;
+ this.dependencies = builder.dependencies.build();
+ this.xcodeprojBuildSettings = builder.xcodeprojBuildSettings.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();
+ }
+
+ /**
+ * Creates a builder whose values are all initialized to this provider.
+ */
+ public Builder toBuilder() {
+ Builder builder = new Builder();
+ builder.label = label;
+ builder.userHeaderSearchPaths.addAll(userHeaderSearchPaths);
+ builder.headerSearchPaths.addTransitive(headerSearchPaths);
+ builder.infoplistMerging = infoplistMerging;
+ builder.dependencies.addTransitive(dependencies);
+ builder.xcodeprojBuildSettings.addAll(xcodeprojBuildSettings);
+ builder.copts.addAll(copts);
+ builder.productType = productType;
+ builder.headers.addAll(headers);
+ builder.compilationArtifacts = compilationArtifacts;
+ builder.objcProvider = objcProvider;
+ builder.testHost = testHost;
+ builder.inputsToXcodegen.addTransitive(inputsToXcodegen);
+ return builder;
+ }
+
+ /**
+ * Returns a list of this provider and all its transitive dependencies.
+ */
+ private Iterable<XcodeProvider> providers() {
+ Set<XcodeProvider> providers = new LinkedHashSet<>();
+ providers.add(this);
+ Iterables.addAll(providers, dependencies);
+ for (XcodeProvider justTestHost : testHost.asSet()) {
+ providers.add(justTestHost);
+ Iterables.addAll(providers, justTestHost.dependencies);
+ }
+ return ImmutableList.copyOf(providers);
+ }
+
+ private static final EnumSet<XcodeProductType> CAN_LINK_PRODUCT_TYPES = EnumSet.of(
+ XcodeProductType.APPLICATION, XcodeProductType.BUNDLE, XcodeProductType.UNIT_TEST);
+
+ private TargetControl targetControl() {
+ String buildFilePath = label.getPackageFragment().getSafePathString() + "/BUILD";
+ // TODO(bazel-team): Add provisioning profile information when Xcodegen supports it.
+ TargetControl.Builder targetControl = TargetControl.newBuilder()
+ .setName(label.getName())
+ .setLabel(label.toString())
+ .setProductType(productType.getIdentifier())
+ .addAllImportedLibrary(Artifact.toExecPaths(objcProvider.get(IMPORTED_LIBRARY)))
+ .addAllUserHeaderSearchPath(userHeaderSearchPaths)
+ .addAllHeaderSearchPath(headerSearchPaths)
+ .addAllSupportFile(Artifact.toExecPaths(headers))
+ .addAllCopt(compilationModeCopts)
+ .addAllCopt(Interspersing.prependEach("-D", objcProvider.get(DEFINE)))
+ .addAllCopt(copts)
+ .addAllLinkopt(
+ Interspersing.beforeEach("-force_load", objcProvider.get(FORCE_LOAD_FOR_XCODEGEN)))
+ .addAllLinkopt(IosSdkCommands.DEFAULT_LINKER_FLAGS)
+ .addAllLinkopt(Interspersing.beforeEach(
+ "-weak_framework", SdkFramework.names(objcProvider.get(WEAK_SDK_FRAMEWORK))))
+ .addAllBuildSetting(xcodeprojBuildSettings)
+ .addAllBuildSetting(IosSdkCommands.defaultWarningsForXcode())
+ .addAllSdkFramework(SdkFramework.names(objcProvider.get(SDK_FRAMEWORK)))
+ .addAllFramework(PathFragment.safePathStrings(objcProvider.get(FRAMEWORK_DIR)))
+ .addAllXcassetsDir(PathFragment.safePathStrings(objcProvider.get(XCASSETS_DIR)))
+ .addAllXcdatamodel(PathFragment.safePathStrings(
+ Xcdatamodel.xcdatamodelDirs(objcProvider.get(XCDATAMODEL))))
+ .addAllBundleImport(PathFragment.safePathStrings(objcProvider.get(BUNDLE_IMPORT_DIR)))
+ .addAllSdkDylib(objcProvider.get(SDK_DYLIB))
+ .addAllGeneralResourceFile(Artifact.toExecPaths(objcProvider.get(GENERAL_RESOURCE_FILE)))
+ .addSupportFile(buildFilePath);
+
+ if (CAN_LINK_PRODUCT_TYPES.contains(productType)) {
+ for (XcodeProvider dependency : dependencies) {
+ // Only add a library target to a binary's dependencies if it has source files to compile.
+ // 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 exception to this rule is the objc_bundle_library target. Bundles are generally used
+ // for resources and can lack a PBXSourceFilesBuildPhase in the project file and still be
+ // considered valid by Xcode.
+ boolean hasSources = dependency.compilationArtifacts.isPresent()
+ && dependency.compilationArtifacts.get().getArchive().isPresent();
+ if (hasSources || (dependency.productType == XcodeProductType.BUNDLE)) {
+ targetControl.addDependency(DependencyControl.newBuilder()
+ .setTargetLabel(dependency.label.toString())
+ .build());
+ }
+ }
+ for (XcodeProvider justTestHost : testHost.asSet()) {
+ targetControl.addDependency(DependencyControl.newBuilder()
+ .setTargetLabel(justTestHost.label.toString())
+ .setTestHost(true)
+ .build());
+ }
+ }
+
+ for (InfoplistMerging merging : infoplistMerging.asSet()) {
+ for (Artifact infoplist : merging.getPlistWithEverything().asSet()) {
+ targetControl.setInfoplist(infoplist.getExecPathString());
+ }
+ }
+ for (CompilationArtifacts artifacts : compilationArtifacts.asSet()) {
+ targetControl
+ .addAllSourceFile(Artifact.toExecPaths(artifacts.getSrcs()))
+ .addAllNonArcSourceFile(Artifact.toExecPaths(artifacts.getNonArcSrcs()));
+
+ for (Artifact pchFile : artifacts.getPchFile().asSet()) {
+ targetControl
+ .setPchPath(pchFile.getExecPathString())
+ .addSupportFile(pchFile.getExecPathString());
+ }
+ }
+
+ if (objcProvider.is(Flag.USES_CPP)) {
+ targetControl.addSdkDylib("libc++");
+ }
+
+ return targetControl.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 + "/."}
+ */
+ @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 {
+ return prefix + "/" + input.getSafePathString();
+ }
+ }
+ });
+ }
+}