aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/rules
diff options
context:
space:
mode:
authorGravatar Googler <noreply@google.com>2016-04-05 00:33:31 +0000
committerGravatar Kristina Chodorow <kchodorow@google.com>2016-04-05 14:08:58 +0000
commitf07fd407c2b618b4ed0dc05edaa8d38a6918f5c4 (patch)
tree625b829d8162bae986f3c5edec006a3fbcd31909 /src/main/java/com/google/devtools/build/lib/rules
parentae8d6873a400393f14ab6ed7bd8d810ebfed5beb (diff)
Add 'apple_watch1_extension' and 'apple_watch_extension_binary' to support building watch OS 1 apps.
RELNOTES: Support apple_watch1_extension and apple_watch_extension_binary rules for creating watch OS 1 extensions. -- MOS_MIGRATED_REVID=119000703
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/rules')
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/objc/AppleWatch1Extension.java224
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/objc/AppleWatch1ExtensionRule.java98
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/objc/AppleWatchExtensionBinary.java33
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/objc/AppleWatchExtensionBinaryRule.java61
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/objc/BundleMergeControlBytes.java9
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/objc/BundleSupport.java49
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/objc/Bundling.java22
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/objc/IntermediateArtifacts.java27
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/objc/IosApplication.java27
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/objc/IosApplicationRule.java2
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/objc/IosExtension.java53
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/objc/ObjcCommandLineOptions.java3
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/objc/ObjcCommon.java2
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/objc/ObjcProvider.java22
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/objc/ObjcRuleClasses.java385
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/objc/ObjcXcodeprojRule.java2
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/objc/ReleaseBundling.java62
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/objc/ReleaseBundlingSupport.java40
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/objc/ReleaseBundlingTargetFactory.java7
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/objc/TargetDeviceFamily.java7
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/objc/WatchApplicationSupport.java352
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/objc/WatchExtensionSupport.java301
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/objc/WatchUtils.java172
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/objc/XcodeProductType.java4
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/objc/XcodeProvider.java13
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/objc/XcodeSupport.java25
26 files changed, 1932 insertions, 70 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/AppleWatch1Extension.java b/src/main/java/com/google/devtools/build/lib/rules/objc/AppleWatch1Extension.java
new file mode 100644
index 0000000000..7e0b328549
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/AppleWatch1Extension.java
@@ -0,0 +1,224 @@
+// Copyright 2016 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.AppleWatch1ExtensionRule.WATCH_APP_DEPS_ATTR;
+import static com.google.devtools.build.lib.rules.objc.ObjcProvider.FLAG;
+import static com.google.devtools.build.lib.rules.objc.ObjcProvider.Flag.HAS_WATCH1_EXTENSION;
+import static com.google.devtools.build.lib.rules.objc.ObjcProvider.MERGE_ZIP;
+import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchApplicationBundleRule.WATCH_APP_NAME_ATTR;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.ConfiguredTarget;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.config.BuildOptions;
+import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
+import com.google.devtools.build.lib.packages.Attribute.SplitTransition;
+import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory;
+import com.google.devtools.build.lib.rules.objc.IosExtension.ExtensionSplitArchTransition;
+import com.google.devtools.build.lib.rules.objc.ReleaseBundlingSupport.SplitArchTransition.ConfigurationDistinguisher;
+import com.google.devtools.build.lib.rules.objc.WatchUtils.WatchOSVersion;
+import com.google.devtools.build.lib.rules.test.InstrumentedFilesCollector;
+import com.google.devtools.build.lib.rules.test.InstrumentedFilesProvider;
+import com.google.devtools.build.lib.syntax.Type;
+
+/**
+ * Implementation for {@code apple_watch1_extension}.
+ */
+public class AppleWatch1Extension implements RuleConfiguredTargetFactory {
+
+ static final SplitTransition<BuildOptions> MINIMUM_OS_AND_SPLIT_ARCH_TRANSITION =
+ new ExtensionSplitArchTransition(WatchUtils.MINIMUM_OS_VERSION,
+ ConfigurationDistinguisher.WATCH_OS1_EXTENSION);
+ private static final ImmutableSet<Attribute> extensionDependencyAttributes =
+ ImmutableSet.of(new Attribute("binary", Mode.SPLIT));
+ private static final ImmutableSet<Attribute> applicationDependencyAttributes =
+ ImmutableSet.of(new Attribute(WATCH_APP_DEPS_ATTR, Mode.SPLIT));
+
+ @Override
+ public ConfiguredTarget create(RuleContext ruleContext) throws InterruptedException {
+ ObjcProvider.Builder applicationObjcProviderBuilder = new ObjcProvider.Builder();
+ ObjcProvider.Builder extensionObjcProviderBuilder = new ObjcProvider.Builder();
+ XcodeProvider.Builder applicationXcodeProviderBuilder = new XcodeProvider.Builder();
+ XcodeProvider.Builder extensionXcodeProviderBuilder = new XcodeProvider.Builder();
+ NestedSetBuilder<Artifact> applicationFilesToBuild = NestedSetBuilder.stableOrder();
+ NestedSetBuilder<Artifact> extensionfilesToBuild = NestedSetBuilder.stableOrder();
+
+ // 1. Build watch application bundle.
+ createWatchApplicationBundle(ruleContext, applicationXcodeProviderBuilder,
+ applicationObjcProviderBuilder, applicationFilesToBuild);
+
+ // 2. Build watch extension bundle.
+ createWatchExtensionBundle(ruleContext, extensionXcodeProviderBuilder,
+ applicationXcodeProviderBuilder, extensionObjcProviderBuilder, extensionfilesToBuild);
+
+ // 3. Extract the watch application bundle into the extension bundle.
+ registerWatchApplicationUnBundlingAction(ruleContext);
+
+ RuleConfiguredTargetBuilder targetBuilder =
+ ObjcRuleClasses.ruleConfiguredTarget(ruleContext, extensionfilesToBuild.build())
+ .addProvider(XcodeProvider.class, extensionXcodeProviderBuilder.build())
+ .addProvider(
+ InstrumentedFilesProvider.class,
+ InstrumentedFilesCollector.forward(ruleContext, "binary"));
+
+ // 4. Exposed {@ObjcProvider} for bundling into final IPA.
+ exposeObjcProvider(ruleContext, targetBuilder);
+
+ return targetBuilder.build();
+ }
+
+ /**
+ * Exposes an {@link ObjcProvider} with the following to create the final IPA:
+ * 1. Watch extension bundle.
+ * 2. WatchKitSupport.
+ * 3. A flag to indicate that watch os 1 extension is included.
+ */
+ private void exposeObjcProvider(RuleContext ruleContext,
+ RuleConfiguredTargetBuilder targetBuilder) throws InterruptedException {
+ ObjcProvider.Builder exposedObjcProviderBuilder = new ObjcProvider.Builder();
+
+ exposedObjcProviderBuilder.add(MERGE_ZIP,
+ ruleContext.getImplicitOutputArtifact(ReleaseBundlingSupport.IPA));
+ WatchUtils.registerActionsToAddWatchSupport(ruleContext, exposedObjcProviderBuilder,
+ WatchOSVersion.OS1);
+ exposedObjcProviderBuilder.add(FLAG, HAS_WATCH1_EXTENSION);
+
+ targetBuilder.addProvider(ObjcProvider.class, exposedObjcProviderBuilder.build());
+ }
+
+ /**
+ * Creates a watch extension bundle.
+ *
+ * @param ruleContext rule context in which to create the bundle
+ * @param extensionXcodeProviderBuilder {@link XcodeProvider.Builder} for the extension
+ * @param applicationXcodeProviderBuilder {@link XcodeProvider.Builder} for the watch application
+ * which is added as a dependency to the extension
+ * @param objcProviderBuilder {@link ObjcProvider.Builder} for the extension
+ * @param filesToBuild the list to contain the files to be built for this extension bundle
+ */
+ private void createWatchExtensionBundle(RuleContext ruleContext,
+ XcodeProvider.Builder extensionXcodeProviderBuilder,
+ XcodeProvider.Builder applicationXcodeProviderBuilder,
+ ObjcProvider.Builder objcProviderBuilder,
+ NestedSetBuilder<Artifact> filesToBuild) throws InterruptedException {
+ new WatchExtensionSupport(ruleContext,
+ WatchOSVersion.OS1,
+ extensionDependencyAttributes,
+ ObjcRuleClasses.intermediateArtifacts(ruleContext),
+ watchExtensionBundleName(ruleContext),
+ watchExtensionIpaArtifact(ruleContext),
+ watchApplicationBundle(ruleContext),
+ applicationXcodeProviderBuilder.build(),
+ ConfigurationDistinguisher.WATCH_OS1_EXTENSION)
+ .createBundle(filesToBuild, objcProviderBuilder, extensionXcodeProviderBuilder);
+ }
+
+ /**
+ * Creates a watch application bundle.
+ *
+ * @param ruleContext rule context in which to create the bundle
+ * @param xcodeProviderBuilder {@link XcodeProvider.Builder} for the application
+ * @param objcProviderBuilder {@link ObjcProvider.Builder} for the application
+ * @param filesToBuild the list to contain the files to be built for this bundle
+ */
+ private void createWatchApplicationBundle(RuleContext ruleContext,
+ XcodeProvider.Builder xcodeProviderBuilder,
+ ObjcProvider.Builder objcProviderBuilder,
+ NestedSetBuilder<Artifact> filesToBuild) throws InterruptedException {
+ new WatchApplicationSupport(ruleContext,
+ WatchOSVersion.OS1,
+ applicationDependencyAttributes,
+ new IntermediateArtifacts(ruleContext, "",
+ watchApplicationBundleName(ruleContext)),
+ watchApplicationBundleName(ruleContext),
+ watchApplicationIpaArtifact(ruleContext),
+ watchApplicationBundleName(ruleContext),
+ ConfigurationDistinguisher.WATCH_OS1_EXTENSION)
+ .createBundle(xcodeProviderBuilder, objcProviderBuilder, filesToBuild);
+ }
+
+ /**
+ * Registers action to extract the watch application ipa (after signing if required) to the
+ * extension bundle.
+ *
+ * For example, TestWatchApp.ipa will be unbundled into,
+ * PlugIns/TestWatchExtension.appex
+ * PlugIns/TestWatchExtension.appex/TestWatchApp.app
+ */
+ private void registerWatchApplicationUnBundlingAction(RuleContext ruleContext) {
+ Artifact watchApplicationIpa = watchApplicationIpaArtifact(ruleContext);
+ Artifact watchApplicationBundle = watchApplicationBundle(ruleContext);
+
+ String workingDirectory = watchApplicationBundle.getExecPathString().substring(0,
+ watchApplicationBundle.getExecPathString().lastIndexOf('/'));
+
+ ImmutableList<String> command = ImmutableList.of(
+ "mkdir -p " + workingDirectory,
+ "&&",
+ String.format("/usr/bin/unzip -q %s -d %s",
+ watchApplicationIpa.getExecPathString(),
+ workingDirectory),
+ "&&",
+ String.format("cd %s/Payload", workingDirectory),
+ "&&",
+ String.format("/usr/bin/zip -q -r -0 ../%s *", watchApplicationBundle.getFilename()));
+ ruleContext.registerAction(
+ ObjcRuleClasses.spawnOnDarwinActionBuilder()
+ .setProgressMessage("Extracting watch app: " + ruleContext.getLabel())
+ .setShellCommand(ImmutableList.of("/bin/bash", "-c", Joiner.on(" ").join(command)))
+ .addInput(watchApplicationIpa)
+ .addOutput(watchApplicationBundle)
+ .build(ruleContext));
+ }
+
+ /**
+ * Returns a zip {@Artifact} containing extracted watch application - "TestWatchApp.app"
+ * which is to be merged into the extension bundle.
+ */
+ private Artifact watchApplicationBundle(RuleContext ruleContext) {
+ return ruleContext.getRelatedArtifact(ruleContext.getUniqueDirectory(
+ "_watch"), String.format("/%s", watchApplicationIpaArtifact(ruleContext)
+ .getFilename().replace(".ipa", ".zip")));
+ }
+
+ /**
+ * Returns the {@Artifact} containing final watch application bundle.
+ */
+ private Artifact watchApplicationIpaArtifact(RuleContext ruleContext) {
+ return ruleContext.getRelatedArtifact(ruleContext.getUniqueDirectory("_watch"),
+ String.format("/%s.ipa", watchApplicationBundleName(ruleContext)));
+ }
+
+ /**
+ * Returns the {@Artifact} containing final watch extension bundle.
+ */
+ private Artifact watchExtensionIpaArtifact(RuleContext ruleContext) throws InterruptedException {
+ return ruleContext.getImplicitOutputArtifact(ReleaseBundlingSupport.IPA);
+ }
+
+ private String watchApplicationBundleName(RuleContext ruleContext) {
+ return ruleContext.attributes().get(WATCH_APP_NAME_ATTR, Type.STRING);
+ }
+
+ private String watchExtensionBundleName(RuleContext ruleContext) {
+ return ruleContext.getLabel().getName();
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/AppleWatch1ExtensionRule.java b/src/main/java/com/google/devtools/build/lib/rules/objc/AppleWatch1ExtensionRule.java
new file mode 100644
index 0000000000..04bf382eec
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/AppleWatch1ExtensionRule.java
@@ -0,0 +1,98 @@
+// Copyright 2016 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.packages.Attribute.attr;
+import static com.google.devtools.build.lib.packages.BuildType.LABEL;
+import static com.google.devtools.build.lib.packages.BuildType.LABEL_LIST;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.devtools.build.lib.analysis.BaseRuleClasses;
+import com.google.devtools.build.lib.analysis.RuleDefinition;
+import com.google.devtools.build.lib.analysis.RuleDefinitionEnvironment;
+import com.google.devtools.build.lib.packages.ImplicitOutputsFunction;
+import com.google.devtools.build.lib.packages.RuleClass;
+import com.google.devtools.build.lib.packages.RuleClass.Builder;
+import com.google.devtools.build.lib.rules.apple.AppleConfiguration;
+
+/**
+ * Rule definition for apple_watch1_extension.
+ */
+public class AppleWatch1ExtensionRule implements RuleDefinition {
+
+ private static final Iterable<String> ALLOWED_DEPS_RULE_CLASSES =
+ ImmutableSet.of("objc_library", "objc_import");
+ static final String WATCH_APP_DEPS_ATTR = "app_deps";
+
+ @Override
+ public RuleClass build(Builder builder, RuleDefinitionEnvironment env) {
+ return builder
+ .requiresConfigurationFragments(ObjcConfiguration.class, AppleConfiguration.class)
+ /*<!-- #BLAZE_RULE(apple_watch1_extension).IMPLICIT_OUTPUTS -->
+ <ul>
+ <li><code><var>name</var>.ipa</code>: the extension bundle as an <code>.ipa</code>
+ file</li>
+ <li><code><var>name</var>.xcodeproj/project.pbxproj</code>: An Xcode project file which
+ can be used to develop or build on a Mac.</li>
+ </ul>
+ <!-- #END_BLAZE_RULE.IMPLICIT_OUTPUTS -->*/
+ .setImplicitOutputsFunction(
+ ImplicitOutputsFunction.fromFunctions(ReleaseBundlingSupport.IPA, XcodeSupport.PBXPROJ))
+ /* <!-- #BLAZE_RULE(apple_watch1_extension).ATTRIBUTE(binary) -->
+ The binary target containing the logic for the watch extension.
+ ${SYNOPSIS}
+ <!-- #END_BLAZE_RULE.ATTRIBUTE -->*/
+ .add(attr("binary", LABEL)
+ .allowedRuleClasses("apple_watch_extension_binary")
+ .allowedFileTypes()
+ .mandatory()
+ .direct_compile_time_input()
+ .cfg(AppleWatch1Extension.MINIMUM_OS_AND_SPLIT_ARCH_TRANSITION))
+ /* <!-- #BLAZE_RULE($apple_watch1_extension).ATTRIBUTE(deps) -->
+ The list of targets whose resources files are bundled together to form final watch
+ application bundle.
+ <!-- #END_BLAZE_RULE.ATTRIBUTE -->*/
+ .add(attr(WATCH_APP_DEPS_ATTR, LABEL_LIST)
+ .direct_compile_time_input()
+ .allowedRuleClasses(ALLOWED_DEPS_RULE_CLASSES)
+ .allowedFileTypes()
+ .cfg(AppleWatch1Extension.MINIMUM_OS_AND_SPLIT_ARCH_TRANSITION))
+ .build();
+ }
+
+ @Override
+
+ public Metadata getMetadata() {
+ return RuleDefinition.Metadata.builder()
+ .name("apple_watch1_extension")
+ .factoryClass(AppleWatch1Extension.class)
+ .ancestors(BaseRuleClasses.BaseRule.class,
+ ObjcRuleClasses.XcodegenRule.class,
+ ObjcRuleClasses.WatchApplicationBundleRule.class,
+ ObjcRuleClasses.WatchExtensionBundleRule.class)
+ .build();
+ }
+}
+
+/*<!-- #BLAZE_RULE (NAME = apple_watch1_extension, TYPE = BINARY, FAMILY = Objective-C) -->
+
+<p>This rule produces an extension bundle for apple watch OS 1 which also contains the watch
+application bundle</p>
+
+${IMPLICIT_OUTPUTS}
+
+${ATTRIBUTE_DEFINITION}
+
+<!-- #END_BLAZE_RULE -->*/
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/AppleWatchExtensionBinary.java b/src/main/java/com/google/devtools/build/lib/rules/objc/AppleWatchExtensionBinary.java
new file mode 100644
index 0000000000..675c82a0d7
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/AppleWatchExtensionBinary.java
@@ -0,0 +1,33 @@
+// Copyright 2016 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 com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.rules.objc.CompilationSupport.ExtraLinkArgs;
+
+/**
+ * Implementation for the "apple_watch_extension_binary" rule.
+ */
+public class AppleWatchExtensionBinary extends BinaryLinkingTargetFactory {
+
+ public AppleWatchExtensionBinary() {
+ super(HasReleaseBundlingSupport.NO, XcodeProductType.LIBRARY_STATIC);
+ }
+
+ @Override
+ protected ExtraLinkArgs getExtraLinkArgs(RuleContext ruleContext) {
+ return new ExtraLinkArgs("-fapplication-extension", "-framework", "WatchKit");
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/AppleWatchExtensionBinaryRule.java b/src/main/java/com/google/devtools/build/lib/rules/objc/AppleWatchExtensionBinaryRule.java
new file mode 100644
index 0000000000..a65502e7a1
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/AppleWatchExtensionBinaryRule.java
@@ -0,0 +1,61 @@
+// Copyright 2016 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 com.google.devtools.build.lib.analysis.BaseRuleClasses;
+import com.google.devtools.build.lib.analysis.RuleDefinition;
+import com.google.devtools.build.lib.analysis.RuleDefinitionEnvironment;
+import com.google.devtools.build.lib.packages.RuleClass;
+import com.google.devtools.build.lib.packages.RuleClass.Builder;
+import com.google.devtools.build.lib.rules.apple.AppleConfiguration;
+
+/**
+ * Rule definition for apple_watch_extension_binary.
+ */
+public class AppleWatchExtensionBinaryRule implements RuleDefinition {
+ @Override
+ public RuleClass build(Builder builder, RuleDefinitionEnvironment env) {
+ return builder
+ .requiresConfigurationFragments(ObjcConfiguration.class, J2ObjcConfiguration.class,
+ AppleConfiguration.class)
+ /*<!-- #BLAZE_RULE(apple_watch_extension_binary).IMPLICIT_OUTPUTS -->
+ <ul>
+ <li><code><var>name</var>.xcodeproj/project.pbxproj</code>: An Xcode project file which
+ can be used to develop or build on a Mac.</li>
+ </ul>
+ <!-- #END_BLAZE_RULE.IMPLICIT_OUTPUTS -->*/
+ .setImplicitOutputsFunction(XcodeSupport.PBXPROJ)
+ .build();
+ }
+
+ @Override
+ public Metadata getMetadata() {
+ return RuleDefinition.Metadata.builder()
+ .name("apple_watch_extension_binary")
+ .factoryClass(AppleWatchExtensionBinary.class)
+ .ancestors(BaseRuleClasses.BaseRule.class, ObjcRuleClasses.LinkingRule.class,
+ ObjcRuleClasses.XcodegenRule.class)
+ .build();
+ }
+}
+
+/*<!-- #BLAZE_RULE (NAME = apple_watch_extension_binary, TYPE = BINARY, FAMILY = Objective-C) -->
+
+<p>This rule produces a binary for watch extension by linking one or more
+Objective-C libraries.</p>
+
+${IMPLICIT_OUTPUTS}
+
+<!-- #END_BLAZE_RULE -->*/
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/BundleMergeControlBytes.java b/src/main/java/com/google/devtools/build/lib/rules/objc/BundleMergeControlBytes.java
index 70925770e5..f7a83beeb0 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/BundleMergeControlBytes.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/BundleMergeControlBytes.java
@@ -63,7 +63,7 @@ final class BundleMergeControlBytes extends ByteSource {
if (bundling.getBundleInfoplist().isPresent()) {
control.setBundleInfoPlistFile((bundling.getBundleInfoplist().get().getExecPathString()));
}
-
+
for (Artifact mergeZip : bundling.getMergeZips()) {
control.addMergeZip(MergeZip.newBuilder()
.setEntryNamePrefix(mergeZipPrefix)
@@ -71,6 +71,13 @@ final class BundleMergeControlBytes extends ByteSource {
.build());
}
+ for (Artifact rootMergeZip : bundling.getRootMergeZips()) {
+ control.addMergeZip(MergeZip.newBuilder()
+ .setEntryNamePrefix("")
+ .setSourcePath(rootMergeZip.getExecPathString())
+ .build());
+ }
+
control.setOutFile(mergedIpa.getExecPathString());
for (Artifact linkedBinary : bundling.getCombinedArchitectureBinary().asSet()) {
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/BundleSupport.java b/src/main/java/com/google/devtools/build/lib/rules/objc/BundleSupport.java
index d1b7ab4c32..abbeba537c 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/BundleSupport.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/BundleSupport.java
@@ -20,6 +20,7 @@ import static com.google.devtools.build.lib.rules.objc.ObjcProvider.STRINGS;
import static com.google.devtools.build.lib.rules.objc.ObjcProvider.XCASSETS_DIR;
import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
import com.google.common.base.Verify;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
@@ -218,10 +219,36 @@ final class BundleSupport {
ImmutableSet<TargetDeviceFamily> targetDeviceFamilies() {
return bundling.getTargetDeviceFamilies();
}
+
+ /**
+ * Returns true if this bundle is targeted to {@link TargetDeviceFamily#WATCH}, false otherwise.
+ */
+ boolean isBuildingForWatch() {
+ return Iterables.any(targetDeviceFamilies(),
+ new Predicate<TargetDeviceFamily>() {
+ @Override
+ public boolean apply(TargetDeviceFamily targetDeviceFamily) {
+ return targetDeviceFamily.name().equalsIgnoreCase(TargetDeviceFamily.WATCH.getNameInRule());
+ }
+ });
+ }
+
+ /**
+ * Returns a set containing the {@link TargetDeviceFamily} values the resources in this bundle
+ * are targeting. When watch is included as one of the families, (for example [iphone, watch] for
+ * simulator builds, assets should always be compiled for {@link TargetDeviceFamily#WATCH}.
+ */
+ private ImmutableSet<TargetDeviceFamily> targetDeviceFamiliesForResources() {
+ if (isBuildingForWatch()) {
+ return ImmutableSet.of(TargetDeviceFamily.WATCH);
+ } else {
+ return targetDeviceFamilies();
+ }
+ }
private void registerInterfaceBuilderActions(ObjcProvider objcProvider) {
for (Artifact storyboardInput : objcProvider.get(ObjcProvider.STORYBOARD)) {
- String archiveRoot = BundleableFile.flatBundlePath(storyboardInput.getExecPath()) + "c";
+ String archiveRoot = storyboardArchiveRoot(storyboardInput);
Artifact zipOutput = bundling.getIntermediateArtifacts()
.compiledStoryboardZip(storyboardInput);
@@ -236,6 +263,22 @@ final class BundleSupport {
}
}
+ /**
+ * Returns the root file path to which storyboard interfaces are compiled.
+ */
+ protected String storyboardArchiveRoot(Artifact storyboardInput) {
+ // When storyboards are compiled for {@link TargetDeviceFamily#WATCH}, return the containing
+ // directory if it ends with .lproj to account for localization or "." representing the bundle
+ // root otherwise. Examples: Payload/Foo.app/Base.lproj/<compiled_file>,
+ // Payload/Foo.app/<compile_file_1>
+ if (isBuildingForWatch()) {
+ String containingDir = storyboardInput.getExecPath().getParentDirectory().getBaseName();
+ return containingDir.endsWith(".lproj") ? (containingDir + "/") : ".";
+ } else {
+ return BundleableFile.flatBundlePath(storyboardInput.getExecPath()) + "c";
+ }
+ }
+
private CommandLine ibActionsCommandLine(String archiveRoot, Artifact zipOutput,
Artifact storyboardInput) {
CustomCommandLine.Builder commandLine =
@@ -248,7 +291,7 @@ final class BundleSupport {
.add("--module")
.add(ruleContext.getLabel().getName());
- for (TargetDeviceFamily targetDeviceFamily : targetDeviceFamilies()) {
+ for (TargetDeviceFamily targetDeviceFamily : targetDeviceFamiliesForResources()) {
commandLine.add("--target-device").add(targetDeviceFamily.name().toLowerCase(Locale.US));
}
@@ -406,7 +449,7 @@ final class BundleSupport {
.add("--minimum-deployment-target")
.add(bundling.getMinimumOsVersion().toString());
- for (TargetDeviceFamily targetDeviceFamily : targetDeviceFamilies()) {
+ for (TargetDeviceFamily targetDeviceFamily : targetDeviceFamiliesForResources()) {
commandLine.add("--target-device").add(targetDeviceFamily.name().toLowerCase(Locale.US));
}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/Bundling.java b/src/main/java/com/google/devtools/build/lib/rules/objc/Bundling.java
index 90864205a0..35c228bb0b 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/Bundling.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/Bundling.java
@@ -21,6 +21,7 @@ import static com.google.devtools.build.lib.rules.objc.ObjcProvider.IMPORTED_LIB
import static com.google.devtools.build.lib.rules.objc.ObjcProvider.LIBRARY;
import static com.google.devtools.build.lib.rules.objc.ObjcProvider.MERGE_ZIP;
import static com.google.devtools.build.lib.rules.objc.ObjcProvider.NESTED_BUNDLE;
+import static com.google.devtools.build.lib.rules.objc.ObjcProvider.ROOT_MERGE_ZIP;
import static com.google.devtools.build.lib.rules.objc.ObjcProvider.STORYBOARD;
import static com.google.devtools.build.lib.rules.objc.ObjcProvider.STRINGS;
import static com.google.devtools.build.lib.rules.objc.ObjcProvider.XCDATAMODEL;
@@ -289,6 +290,9 @@ final class Bundling {
Optional<Artifact> combinedArchitectureBinary = combinedArchitectureBinary();
NestedSet<BundleableFile> binaryStringsFiles = binaryStringsFiles();
NestedSet<Artifact> mergeZips = mergeZips(actoolzipOutput);
+ NestedSet<Artifact> rootMergeZips =
+ NestedSetBuilder.<Artifact>stableOrder()
+ .addTransitive(objcProvider.get(ROOT_MERGE_ZIP)).build();
bundleFilesBuilder.addAll(binaryStringsFiles).addAll(objcProvider.get(BUNDLE_FILE));
ImmutableList<BundleableFile> bundleFiles =
@@ -300,6 +304,7 @@ final class Bundling {
.addAll(combinedArchitectureBinary.asSet())
.addAll(bundleInfoplist.asSet())
.addTransitive(mergeZips)
+ .addTransitive(rootMergeZips)
.addAll(BundleableFile.toArtifacts(binaryStringsFiles))
.addAll(BundleableFile.toArtifacts(bundleFiles));
@@ -312,6 +317,7 @@ final class Bundling {
actoolzipOutput,
bundleContentArtifactsBuilder.build(),
mergeZips,
+ rootMergeZips,
primaryBundleId,
fallbackBundleId,
architecture,
@@ -340,6 +346,7 @@ final class Bundling {
private final Optional<Artifact> actoolzipOutput;
private final NestedSet<Artifact> bundleContentArtifacts;
private final NestedSet<Artifact> mergeZips;
+ private final NestedSet<Artifact> rootMergeZips;
private final String primaryBundleId;
private final String fallbackBundleId;
private final DottedVersion minimumOsVersion;
@@ -359,6 +366,7 @@ final class Bundling {
Optional<Artifact> actoolzipOutput,
NestedSet<Artifact> bundleContentArtifacts,
NestedSet<Artifact> mergeZips,
+ NestedSet<Artifact> rootMergeZips,
String primaryBundleId,
String fallbackBundleId,
String architecture,
@@ -378,6 +386,7 @@ final class Bundling {
this.actoolzipOutput = Preconditions.checkNotNull(actoolzipOutput);
this.bundleContentArtifacts = Preconditions.checkNotNull(bundleContentArtifacts);
this.mergeZips = Preconditions.checkNotNull(mergeZips);
+ this.rootMergeZips = Preconditions.checkNotNull(rootMergeZips);
this.fallbackBundleId = fallbackBundleId;
this.primaryBundleId = primaryBundleId;
this.architecture = Preconditions.checkNotNull(architecture);
@@ -495,6 +504,19 @@ final class Bundling {
}
/**
+ * Returns all zip files whose contents should be merged into final ipa and outside the
+ * main bundle. For instance, if a merge zip contains files dir1/file1, then the resulting
+ * bundling would have additional files at:
+ * <ul>
+ * <li>dir1/file1
+ * <li>{bundleDir}/other_files
+ * </ul>
+ */
+ public NestedSet<Artifact> getRootMergeZips() {
+ return rootMergeZips;
+ }
+
+ /**
* Returns the variable substitutions that should be used when merging the plist info file of
* this bundle.
*/
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/IntermediateArtifacts.java b/src/main/java/com/google/devtools/build/lib/rules/objc/IntermediateArtifacts.java
index e57187fbc6..62cec0444c 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/IntermediateArtifacts.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/IntermediateArtifacts.java
@@ -35,10 +35,17 @@ public final class IntermediateArtifacts {
private final RuleContext ruleContext;
private final String archiveFileNameSuffix;
+ private final String outputPrefix;
IntermediateArtifacts(RuleContext ruleContext, String archiveFileNameSuffix) {
+ this(ruleContext, archiveFileNameSuffix, "");
+ }
+
+ IntermediateArtifacts(RuleContext ruleContext, String archiveFileNameSuffix,
+ String outputPrefix) {
this.ruleContext = ruleContext;
this.archiveFileNameSuffix = Preconditions.checkNotNull(archiveFileNameSuffix);
+ this.outputPrefix = Preconditions.checkNotNull(outputPrefix);
}
/**
@@ -52,7 +59,7 @@ public final class IntermediateArtifacts {
Artifact artifact =
ruleContext.getDerivedArtifact(
entitlementsDirectory.replaceName(
- entitlementsDirectory.getBaseName() + extension),
+ addOutputPrefix(entitlementsDirectory.getBaseName(), extension)),
ruleContext.getConfiguration().getBinDirectory());
return artifact;
}
@@ -69,7 +76,8 @@ public final class IntermediateArtifacts {
* of the given {@link PathFragment}.
*/
private Artifact appendExtension(PathFragment original, String extension) {
- return scopedArtifact(FileSystemUtils.appendExtension(original, extension));
+ return scopedArtifact(FileSystemUtils.appendExtension(original,
+ addOutputPrefix("", extension)));
}
/**
@@ -78,7 +86,7 @@ public final class IntermediateArtifacts {
*/
private Artifact appendExtension(String extension) {
PathFragment name = new PathFragment(ruleContext.getLabel().getName());
- return scopedArtifact(name.replaceName(name.getBaseName() + extension));
+ return scopedArtifact(name.replaceName(addOutputPrefix(name.getBaseName(), extension)));
}
/**
@@ -97,7 +105,7 @@ public final class IntermediateArtifacts {
private Artifact appendExtensionInGenfiles(String extension) {
PathFragment name = new PathFragment(ruleContext.getLabel().getName());
return scopedArtifact(
- name.replaceName(name.getBaseName() + extension), /* inGenfiles = */ true);
+ name.replaceName(addOutputPrefix(name.getBaseName(), extension)), /* inGenfiles = */ true);
}
/**
@@ -404,4 +412,15 @@ public final class IntermediateArtifacts {
public Artifact unprocessedIpa() {
return appendExtension(".unprocessed.ipa");
}
+
+ /**
+ * Returns artifact name prefixed with an output prefix if specified.
+ */
+ private String addOutputPrefix(String baseName, String artifactName) {
+ if (!outputPrefix.isEmpty()) {
+ return String.format("%s-%s%s", baseName, outputPrefix, artifactName);
+ }
+ return String.format("%s%s", baseName, artifactName);
+ }
+
}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/IosApplication.java b/src/main/java/com/google/devtools/build/lib/rules/objc/IosApplication.java
index e23af613bd..f57e53a4b6 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/IosApplication.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/IosApplication.java
@@ -14,7 +14,10 @@
package com.google.devtools.build.lib.rules.objc;
+import com.google.common.base.Predicate;
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.RuleConfiguredTarget.Mode;
import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder;
@@ -23,6 +26,7 @@ import com.google.devtools.build.lib.analysis.config.BuildOptions;
import com.google.devtools.build.lib.packages.Attribute.SplitTransition;
import com.google.devtools.build.lib.rules.apple.AppleConfiguration;
import com.google.devtools.build.lib.rules.apple.Platform;
+import com.google.devtools.build.lib.rules.objc.ObjcProvider.Flag;
import com.google.devtools.build.lib.rules.objc.ReleaseBundlingSupport.SplitArchTransition;
import com.google.devtools.build.lib.rules.objc.ReleaseBundlingSupport.SplitArchTransition.ConfigurationDistinguisher;
@@ -48,6 +52,29 @@ public class IosApplication extends ReleaseBundlingTargetFactory {
super(ReleaseBundlingSupport.APP_BUNDLE_DIR_FORMAT, XcodeProductType.APPLICATION,
DEPENDENCY_ATTRIBUTES, ConfigurationDistinguisher.IOS_APPLICATION);
}
+
+ /**
+ * Validates that there is exactly one watch extension for each OS version.
+ */
+ @Override
+ protected void validateAttributes(RuleContext ruleContext) {
+ Iterable<ObjcProvider> extensionProviders = ruleContext.getPrerequisites(
+ "extensions", Mode.TARGET, ObjcProvider.class);
+ if (hasMoreThanOneWatchExtension(extensionProviders, Flag.HAS_WATCH1_EXTENSION)) {
+ ruleContext.attributeError("extensions", "An iOS application can contain exactly one "
+ + "watch extension for each watch OS version");
+ }
+ }
+
+ private boolean hasMoreThanOneWatchExtension(Iterable<ObjcProvider> objcProviders,
+ final Flag watchExtensionVersionFlag) {
+ return Lists.newArrayList(Iterables.filter(objcProviders, new Predicate<ObjcProvider>() {
+ @Override
+ public boolean apply(ObjcProvider objcProvider) {
+ return objcProvider.is(watchExtensionVersionFlag);
+ }
+ })).size() > 1;
+ }
@Override
protected void configureTarget(RuleConfiguredTargetBuilder target, RuleContext ruleContext,
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/IosApplicationRule.java b/src/main/java/com/google/devtools/build/lib/rules/objc/IosApplicationRule.java
index b7c369c7a8..c7a0a2bb26 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/IosApplicationRule.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/IosApplicationRule.java
@@ -61,7 +61,7 @@ public class IosApplicationRule implements RuleDefinition {
Any extensions to include in the final application.
<!-- #END_BLAZE_RULE.ATTRIBUTE -->*/
.add(attr("extensions", LABEL_LIST)
- .allowedRuleClasses("ios_extension")
+ .allowedRuleClasses("ios_extension", "apple_watch1_extension")
.allowedFileTypes()
.direct_compile_time_input())
.add(attr("$runner_script_template", LABEL).cfg(HOST)
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/IosExtension.java b/src/main/java/com/google/devtools/build/lib/rules/objc/IosExtension.java
index 8602a83bbf..b07a36ebb8 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/IosExtension.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/IosExtension.java
@@ -35,6 +35,10 @@ import java.io.Serializable;
*/
public class IosExtension extends ReleaseBundlingTargetFactory {
+ // Apple only accepts extensions starting at 8.0.
+ @VisibleForTesting
+ static final DottedVersion EXTENSION_MINIMUM_OS_VERSION = DottedVersion.fromString("8.0");
+
/**
* Transition that when applied to a target generates a configured target for each value in
* {@code --ios_multi_cpus}, such that {@code --ios_cpu} is set to a different one of those values
@@ -44,11 +48,8 @@ public class IosExtension extends ReleaseBundlingTargetFactory {
* --ios_minimum_os} is at least {@code 8.0} as Apple requires this for extensions.
*/
static final SplitTransition<BuildOptions> MINIMUM_OS_AND_SPLIT_ARCH_TRANSITION =
- new ExtensionSplitArchTransition();
-
- // Apple only accepts extensions starting at 8.0.
- @VisibleForTesting
- static final DottedVersion EXTENSION_MINIMUM_OS_VERSION = DottedVersion.fromString("8.0");
+ new ExtensionSplitArchTransition(EXTENSION_MINIMUM_OS_VERSION,
+ ConfigurationDistinguisher.IOS_EXTENSION);
public IosExtension() {
super(ReleaseBundlingSupport.EXTENSION_BUNDLE_DIR_FORMAT, XcodeProductType.EXTENSION,
@@ -58,7 +59,8 @@ public class IosExtension extends ReleaseBundlingTargetFactory {
@Override
protected DottedVersion bundleMinimumOsVersion(RuleContext ruleContext) {
- return determineMinimumOsVersion(ObjcRuleClasses.objcConfiguration(ruleContext).getMinimumOs());
+ return determineMinimumOsVersion(ObjcRuleClasses.objcConfiguration(ruleContext).getMinimumOs(),
+ EXTENSION_MINIMUM_OS_VERSION);
}
@Override
@@ -69,24 +71,43 @@ public class IosExtension extends ReleaseBundlingTargetFactory {
.build();
}
- private static DottedVersion determineMinimumOsVersion(DottedVersion fromFlag) {
- // Extensions are not accepted by Apple below version 8.0. While applications built with a
- // minimum iOS version of less than 8.0 may contain extensions in their bundle, the extension
- // itself needs to be built with 8.0 or higher. This logic overrides (if necessary) any
- // flag-set minimum iOS version for extensions only so that this requirement is not violated.
- return Ordering.natural().max(fromFlag, EXTENSION_MINIMUM_OS_VERSION);
+
+ /**
+ * Overrides (if necessary) any flag-set minimum iOS version for extensions only with given
+ * minimum OS version.
+ *
+ * Extensions are not accepted by Apple below given mininumOSVersion. While applications built
+ * with a minimum iOS version of less than give version may contain extensions in their bundle,
+ * the extension itself needs to be built with given version or higher.
+ *
+ * @param fromFlag the minimum OS version from command line flag
+ * @param minimumOSVersion the minumum OS version the extension should be built with
+ */
+ private static DottedVersion determineMinimumOsVersion(DottedVersion fromFlag,
+ DottedVersion minimumOSVersion) {
+ return Ordering.natural().max(fromFlag, minimumOSVersion);
}
/**
* Split transition that configures the minimum iOS version in addition to architecture splitting.
*/
- private static class ExtensionSplitArchTransition extends SplitArchTransition
+ static class ExtensionSplitArchTransition extends SplitArchTransition
implements Serializable {
+ private final DottedVersion minimumOSVersion;
+ private final ConfigurationDistinguisher configurationDistinguisher;
+
+ ExtensionSplitArchTransition(DottedVersion minimumOSVersion,
+ ConfigurationDistinguisher configurationDistinguisher) {
+ this.minimumOSVersion = minimumOSVersion;
+ this.configurationDistinguisher = configurationDistinguisher;
+ }
+
@Override
protected ImmutableList<BuildOptions> defaultOptions(BuildOptions originalOptions) {
ObjcCommandLineOptions objcOptions = originalOptions.get(ObjcCommandLineOptions.class);
- DottedVersion newMinimumVersion = determineMinimumOsVersion(objcOptions.iosMinimumOs);
+ DottedVersion newMinimumVersion = determineMinimumOsVersion(objcOptions.iosMinimumOs,
+ minimumOSVersion);
if (newMinimumVersion.equals(objcOptions.iosMinimumOs)) {
return ImmutableList.of();
@@ -102,12 +123,12 @@ public class IosExtension extends ReleaseBundlingTargetFactory {
@Override
protected void setAdditionalOptions(BuildOptions splitOptions, BuildOptions originalOptions) {
DottedVersion fromFlag = originalOptions.get(ObjcCommandLineOptions.class).iosMinimumOs;
- setMinimumOsVersion(splitOptions, determineMinimumOsVersion(fromFlag));
+ setMinimumOsVersion(splitOptions, determineMinimumOsVersion(fromFlag, minimumOSVersion));
}
@Override
protected ConfigurationDistinguisher getConfigurationDistinguisher() {
- return ConfigurationDistinguisher.IOS_EXTENSION;
+ return configurationDistinguisher;
}
private void setMinimumOsVersion(BuildOptions splitOptions, DottedVersion newMinimumVersion) {
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcCommandLineOptions.java b/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcCommandLineOptions.java
index 86e5b3df81..36bb1ea586 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcCommandLineOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcCommandLineOptions.java
@@ -206,7 +206,8 @@ public class ObjcCommandLineOptions extends FragmentOptions {
@Override
public List<SplitTransition<BuildOptions>> getPotentialSplitTransitions() {
return ImmutableList.of(
- IosApplication.SPLIT_ARCH_TRANSITION, IosExtension.MINIMUM_OS_AND_SPLIT_ARCH_TRANSITION);
+ IosApplication.SPLIT_ARCH_TRANSITION, IosExtension.MINIMUM_OS_AND_SPLIT_ARCH_TRANSITION,
+ AppleWatch1Extension.MINIMUM_OS_AND_SPLIT_ARCH_TRANSITION);
}
/** Converter for the iOS configuration distinguisher. */
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcCommon.java b/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcCommon.java
index f2dd17810a..7f80a960b6 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcCommon.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcCommon.java
@@ -745,7 +745,7 @@ public final class ObjcCommon {
* <p>When XCode sees a included resource directory of "a/b/res", the entire directory structure
* up to "res" will be copied into the app bundle.
*/
- private static Iterable<PathFragment> xcodeStructuredResourceDirs(Iterable<Artifact> artifacts) {
+ static Iterable<PathFragment> xcodeStructuredResourceDirs(Iterable<Artifact> artifacts) {
ImmutableSet.Builder<PathFragment> containers = new ImmutableSet.Builder<>();
for (Artifact artifact : artifacts) {
PathFragment ownerRuleDirectory = artifact.getArtifactOwner().getLabel().getPackageFragment();
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcProvider.java b/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcProvider.java
index aaaa7004f0..99968e924c 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcProvider.java
@@ -214,6 +214,17 @@ public final class ObjcProvider implements TransitiveInfoProvider {
new Key<>(STABLE_ORDER, "merge_zip", Artifact.class);
/**
+ * Merge zips to include in the ipa and outside the bundle root.
+ *
+ * e.g. For a bundle Test.ipa, unzipped content will be in:
+ * Test.ipa/<unzipped>
+ * Test.ipa/Payload
+ * Test.ipa/Payload/Test.app
+ */
+ public static final Key<Artifact> ROOT_MERGE_ZIP =
+ new Key<>(STABLE_ORDER, "root_merge_zip", Artifact.class);
+
+ /**
* Exec paths of {@code .framework} directories corresponding to frameworks to link. These cause
* -F arguments (framework search paths) to be added to each compile action, and -framework (link
* framework) arguments to be added to each link action.
@@ -312,8 +323,12 @@ public final class ObjcProvider implements TransitiveInfoProvider {
/**
* Indicates that the resulting bundle will have embedded frameworks. This affects linking step.
*/
- USES_FRAMEWORKS
-
+ USES_FRAMEWORKS,
+
+ /**
+ * Indicates that watch os 1 extension is present in the bundle.
+ */
+ HAS_WATCH1_EXTENSION
}
/**
@@ -325,7 +340,8 @@ public final class ObjcProvider implements TransitiveInfoProvider {
ImmutableList.<Key<?>>of(LIBRARY, IMPORTED_LIBRARY, LINKED_BINARY, FORCE_LOAD_LIBRARY,
FORCE_LOAD_FOR_XCODEGEN, HEADER, SOURCE, DEFINE, ASSET_CATALOG, GENERAL_RESOURCE_FILE,
SDK_DYLIB, XCDATAMODEL, MODULE_MAP, MERGE_ZIP, FRAMEWORK_FILE, DEBUG_SYMBOLS,
- DEBUG_SYMBOLS_PLIST, BREAKPAD_FILE, STORYBOARD, XIB, STRINGS, LINKOPT, J2OBJC_LIBRARY);
+ DEBUG_SYMBOLS_PLIST, BREAKPAD_FILE, STORYBOARD, XIB, STRINGS, LINKOPT, J2OBJC_LIBRARY,
+ ROOT_MERGE_ZIP);
private final ImmutableMap<Key<?>, NestedSet<?>> items;
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcRuleClasses.java b/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcRuleClasses.java
index ee98c7d0d9..ef9e07911a 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcRuleClasses.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcRuleClasses.java
@@ -369,7 +369,7 @@ public class ObjcRuleClasses {
Base.lproj), it will be placed under a directory of that name in the
final bundle. This allows for localizable strings.
<!-- #END_BLAZE_RULE.ATTRIBUTE -->*/
- .add(attr("strings", LABEL_LIST).legacyAllowAnyFileType()
+ .add(attr("strings", LABEL_LIST)
.allowedFileTypes(STRINGS_TYPE)
.direct_compile_time_input())
/* <!-- #BLAZE_RULE($objc_resources_rule).ATTRIBUTE(xibs) -->
@@ -962,6 +962,26 @@ public class ObjcRuleClasses {
return "example." + rule.getName();
}
}))
+ .build();
+ }
+ @Override
+ public Metadata getMetadata() {
+ return RuleDefinition.Metadata.builder()
+ .name("$objc_release_bundling_rule")
+ .type(RuleClassType.ABSTRACT)
+ .ancestors(BundlingRule.class, ReleaseBundlingToolsRule.class)
+ .build();
+ }
+ }
+
+ /**
+ * Common attributes for rules that require tools to create a bundle meant for
+ * release (e.g. application or extension).
+ */
+ public static class ReleaseBundlingToolsRule implements RuleDefinition {
+ @Override
+ public RuleClass build(Builder builder, RuleDefinitionEnvironment env) {
+ return builder
.add(
attr("$bundlemerge", LABEL)
.cfg(HOST)
@@ -977,9 +997,8 @@ public class ObjcRuleClasses {
@Override
public Metadata getMetadata() {
return RuleDefinition.Metadata.builder()
- .name("$objc_release_bundling_rule")
+ .name("$release_bundling_tools_rule")
.type(RuleClassType.ABSTRACT)
- .ancestors(BundlingRule.class)
.build();
}
}
@@ -1067,5 +1086,365 @@ public class ObjcRuleClasses {
.build();
}
}
+
+ /**
+ * Attributes for {@code apple_watch*} rules that creates a watch extension bundle.
+ */
+ public static class WatchExtensionBundleRule implements RuleDefinition {
+ static final String WATCH_EXT_BUNDLE_ID_ATTR = "ext_bundle_id";
+ static final String WATCH_EXT_DEFAULT_PROVISIONING_PROFILE_ATTR =
+ ":default_ext_provisioning_profile";
+ static final String WATCH_EXT_ENTITLEMENTS_ATTR = "ext_entitlements";
+ static final String WATCH_EXT_PROVISIONING_PROFILE_ATTR = "ext_provisioning_profile";
+ static final String WATCH_EXT_INFOPLISTS_ATTR = "ext_infoplists";
+ static final String WATCH_EXT_RESOURCES_ATTR = "ext_resources";
+ static final String WATCH_EXT_STRUCTURED_RESOURCES_ATTR = "ext_structured_resources";
+ static final String WATCH_EXT_STRINGS_ATTR = "ext_strings";
+ static final String WATCH_EXT_FAMILIES_ATTR = "ext_families";
+
+ @Override
+ public RuleClass build(Builder builder, RuleDefinitionEnvironment env) {
+ return builder
+ /* <!-- #BLAZE_RULE($watch_extension_bundle_rule).ATTRIBUTE(ext_bundle_id) -->
+ The bundle ID (reverse-DNS path followed by app name) of the watch extension binary.
+
+ If specified, it will override the bundle ID specified in the associated plist file. If
+ no bundle ID is specified on either this attribute or in the plist file, a junk value
+ will be used.
+ <!-- #END_BLAZE_RULE.ATTRIBUTE -->*/
+ .add(
+ attr(WATCH_EXT_BUNDLE_ID_ATTR, STRING)
+ .value(
+ new Attribute.ComputedDefault() {
+ @Override
+ public Object getDefault(AttributeMap rule) {
+ // For tests and similar, we don't want to force people to explicitly
+ // specify throw-away data.
+ return "example.ext." + rule.getName();
+ }
+ }))
+ /* <!-- #BLAZE_RULE($watch_extension_bundle_rule).ATTRIBUTE(ext_entitlements) -->
+ The entitlements file required for device builds of watch extension.
+
+ See
+ <a href="https://developer.apple.com/library/mac/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/AboutEntitlements.html">the apple documentation</a>
+ for more information. If absent, the default entitlements from the
+ provisioning profile will be used.
+ <p>
+ The following variables are substituted as per
+ <a href="https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html">their definitions in Apple's documentation</a>:
+ $(AppIdentifierPrefix) and $(CFBundleIdentifier).
+ <!-- #END_BLAZE_RULE.ATTRIBUTE -->*/
+ .add(attr(WATCH_EXT_ENTITLEMENTS_ATTR, LABEL)
+ .allowedFileTypes(ENTITLEMENTS_TYPE))
+ /* <!-- #BLAZE_RULE($watch_extension_bundle_rule).ATTRIBUTE(ext_families) -->
+ The device families to which the watch extension is targeted.
+
+ This is known as the <code>TARGETED_DEVICE_FAMILY</code> build setting
+ in Xcode project files. It is a list of one or more of the strings
+ <code>"iphone"</code> and <code>"ipad"</code>.
+
+ <p>By default this is set to <code>"iphone"</code>, if explicitly specified may not be
+ empty.</p>
+ <p>The watch application is always built for <code>"watch"</code> for device builds and
+ <code>"iphone, watch"</code> for simulator builds.
+ <!-- #END_BLAZE_RULE.ATTRIBUTE -->*/
+ .add(
+ attr(WATCH_EXT_FAMILIES_ATTR, STRING_LIST)
+ .value(ImmutableList.of(TargetDeviceFamily.IPHONE.getNameInRule())))
+ /* <!-- #BLAZE_RULE($watch_extension_bundle_rule).ATTRIBUTE(ext_infoplists) -->
+ Infoplist files to be merged. The merged output corresponds to <i>appname</i>-Info.plist
+ in Xcode projects. Duplicate keys between infoplist files will cause an error if
+ and only if the values conflict. If both <code>infoplist</code> and
+ <code>infoplists</code> are specified, the files defined in both attributes will be used.
+ Blaze will perform variable substitution on the plist files for the following values:
+ <ul>
+ <li><code>${EXECUTABLE_NAME}</code>: The name of the executable generated and included
+ in the bundle by blaze, which can be used as the value for
+ <code>CFBundleExecutable</code> within the plist.
+ <li><code>${BUNDLE_NAME}</code>: This target's name and bundle suffix (.bundle or .app)
+ in the form<code><var>name</var></code>.<code>suffix</code>.
+ <li><code>${PRODUCT_NAME}</code>: This target's name.
+ </ul>
+ <!-- #END_BLAZE_RULE.ATTRIBUTE -->*/
+ .add(attr(WATCH_EXT_INFOPLISTS_ATTR, BuildType.LABEL_LIST).allowedFileTypes(PLIST_TYPE))
+ /* <!-- #BLAZE_RULE($watch_extension_bundle_rule).ATTRIBUTE(ext_provisioning_profile) -->
+ The provisioning profile (.mobileprovision file) to use when bundling
+ the watch extension.
+
+ This is only used for non-simulator builds.
+ <!-- #END_BLAZE_RULE.ATTRIBUTE -->*/
+ .add(
+ attr(WATCH_EXT_PROVISIONING_PROFILE_ATTR, LABEL)
+ .singleArtifact()
+ .allowedFileTypes(FileType.of(".mobileprovision")))
+ .add(
+ attr(WATCH_EXT_DEFAULT_PROVISIONING_PROFILE_ATTR, LABEL)
+ .singleArtifact()
+ .allowedFileTypes(FileType.of(".mobileprovision"))
+ .value(
+ new LateBoundLabel<BuildConfiguration>(ObjcConfiguration.class) {
+ @Override
+ public Label getDefault(Rule rule, AttributeMap attributes,
+ BuildConfiguration configuration) {
+ AppleConfiguration appleConfiguration =
+ configuration.getFragment(AppleConfiguration.class);
+ if (appleConfiguration.getBundlingPlatform() != Platform.IOS_DEVICE) {
+ return null;
+ }
+ if (rule.isAttributeValueExplicitlySpecified(
+ WATCH_EXT_PROVISIONING_PROFILE_ATTR)) {
+ return null;
+ }
+ return appleConfiguration.getDefaultProvisioningProfileLabel();
+ }
+ }))
+ /* <!-- #BLAZE_RULE($watch_extension_bundle_rule).ATTRIBUTE(ext_resources) -->
+ Files to include in the final watch extension bundle.
+
+ They are not processed or compiled in any way besides the processing
+ done by the rules that actually generate them. These files are placed
+ in the root of the bundle (e.g. Foo.app/...) in most cases.
+ However, if they appear to be localized (i.e. are contained in a
+ directory called *.lproj), they will be placed in a directory of the
+ same name in the app bundle.
+ <!-- #END_BLAZE_RULE.ATTRIBUTE -->*/
+ .add(attr(WATCH_EXT_RESOURCES_ATTR, LABEL_LIST).legacyAllowAnyFileType()
+ .direct_compile_time_input())
+ /* <!-- #BLAZE_RULE($watch_extension_bundle_rule).ATTRIBUTE(ext_structured_resources)-->
+ Files to include in the final watch extension bundle.
+
+ They are not processed or compiled in any way besides the processing
+ done by the rules that actually generate them. In differences to
+ <code>resources</code> these files are placed in the bundle root in
+ the same structure passed to this argument, so
+ <code>["res/foo.png"]</code> will end up in
+ <code>Foo.app/res/foo.png</code>.
+ <p>Note that in the generated XCode project file, all files in the top directory of
+ the specified files will be included in the Xcode-generated app bundle. So
+ specifying <code>["res/foo.png"]</code> will lead to the inclusion of all files in
+ directory <code>res</code>.
+ <!-- #END_BLAZE_RULE.ATTRIBUTE -->*/
+ .add(attr(WATCH_EXT_STRUCTURED_RESOURCES_ATTR, LABEL_LIST)
+ .legacyAllowAnyFileType()
+ .direct_compile_time_input())
+ /* <!-- #BLAZE_RULE($watch_extension_bundle_rule).ATTRIBUTE(ext_strings) -->
+ Files which are plists of strings, often localizable to be added to watch extension.
+
+ These files are converted to binary plists (if they are not already)
+ and placed in the bundle root of the final package. If this file's
+ immediate containing directory is named *.lproj (e.g. en.lproj,
+ Base.lproj), it will be placed under a directory of that name in the
+ final bundle. This allows for localizable strings.
+ <!-- #END_BLAZE_RULE.ATTRIBUTE -->*/
+ .add(attr(WATCH_EXT_STRINGS_ATTR, LABEL_LIST)
+ .allowedFileTypes(STRINGS_TYPE)
+ .direct_compile_time_input())
+ .build();
+ }
+
+ @Override
+ public Metadata getMetadata() {
+ return RuleDefinition.Metadata.builder()
+ .name("$watch_extension_bundle_rule")
+ .type(RuleClassType.ABSTRACT)
+ .ancestors(
+ AppleToolchain.RequiresXcodeConfigRule.class,
+ ResourceToolsRule.class,
+ ReleaseBundlingToolsRule.class,
+ XcrunRule.class)
+ .build();
+ }
+ }
+
+ /**
+ * Attributes for {@code apple_watch*} rules that creates a watch application bundle.
+ */
+ public static class WatchApplicationBundleRule implements RuleDefinition {
+ static final String WATCH_APP_NAME_ATTR = "app_name";
+ static final String WATCH_APP_ICON_ATTR = "app_icon";
+ static final String WATCH_APP_BUNDLE_ID_ATTR = "app_bundle_id";
+ static final String WATCH_APP_DEFAULT_PROVISIONING_PROFILE_ATTR =
+ ":default_app_provisioning_profile";
+ static final String WATCH_APP_ENTITLEMENTS_ATTR = "app_entitlements";
+ static final String WATCH_APP_PROVISIONING_PROFILE_ATTR = "app_provisioning_profile";
+ static final String WATCH_APP_ASSET_CATALOGS_ATTR = "app_asset_catalogs";
+ static final String WATCH_APP_INFOPLISTS_ATTR = "app_infoplists";
+ static final String WATCH_APP_STORYBOARDS_ATTR = "app_storyboards";
+ static final String WATCH_APP_RESOURCES_ATTR = "app_resources";
+ static final String WATCH_APP_STRUCTURED_RESOURCES_ATTR = "app_structured_resources";
+ static final String WATCH_APP_STRINGS_ATTR = "app_strings";
+
+ @Override
+ public RuleClass build(Builder builder, RuleDefinitionEnvironment env) {
+ return builder
+ /* <!-- #BLAZE_RULE($watch_application_bundle_rule).ATTRIBUTE(app_name) -->
+ Name of the final watch application binary.
+ <!-- #END_BLAZE_RULE.ATTRIBUTE -->*/
+ .add(attr(WATCH_APP_NAME_ATTR, STRING).mandatory())
+ /* <!-- #BLAZE_RULE($watch_application_bundle_rule).ATTRIBUTE(app_icon) -->
+ The name of the watch application icon.
+
+ The icon should be in one of the asset catalogs of this target or
+ a (transitive) dependency. In a new project, this is initialized
+ to "AppIcon" by Xcode.
+ <p>
+ If the application icon is not in an asset catalog, do not use this
+ attribute. Instead, add a CFBundleIcons entry to the Info.plist file.
+ <!-- #END_BLAZE_RULE.ATTRIBUTE -->*/
+ .add(attr(WATCH_APP_ICON_ATTR, STRING))
+ /* <!-- #BLAZE_RULE($watch_application_bundle_rule).ATTRIBUTE(app_entitlements) -->
+ The entitlements file required for device builds of watch application.
+
+ See
+ <a href="https://developer.apple.com/library/mac/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/AboutEntitlements.html">the apple documentation</a>
+ for more information. If absent, the default entitlements from the
+ provisioning profile will be used.
+ <p>
+ The following variables are substituted as per
+ <a href="https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html">their definitions in Apple's documentation</a>:
+ $(AppIdentifierPrefix) and $(CFBundleIdentifier).
+ <!-- #END_BLAZE_RULE.ATTRIBUTE -->*/
+ .add(attr(WATCH_APP_ENTITLEMENTS_ATTR, LABEL)
+ .allowedFileTypes(ENTITLEMENTS_TYPE))
+ /* <!-- #BLAZE_RULE($watch_application_bundle_rule).ATTRIBUTE(app_asset_catalogs) -->
+ Files that comprise the asset catalogs of the final linked binary.
+
+ Each file must have a containing directory named *.xcassets. This
+ containing directory becomes the root of one of the asset catalogs
+ linked with any binary that depends directly or indirectly on this
+ target.
+ <!-- #END_BLAZE_RULE.ATTRIBUTE -->*/
+ .add(attr(WATCH_APP_ASSET_CATALOGS_ATTR, LABEL_LIST).legacyAllowAnyFileType()
+ .direct_compile_time_input())
+ /* <!-- #BLAZE_RULE($watch_application_bundle_rule).ATTRIBUTE(app_bundle_id) -->
+ The bundle ID (reverse-DNS path followed by app name) of the watch application binary.
+
+ If specified, it will override the bundle ID specified in the associated plist file. If
+ no bundle ID is specified on either this attribute or in the plist file, a junk value
+ will be used.
+ <!-- #END_BLAZE_RULE.ATTRIBUTE -->*/
+ .add(
+ attr(WATCH_APP_BUNDLE_ID_ATTR, STRING)
+ .value(
+ new Attribute.ComputedDefault(WATCH_APP_NAME_ATTR) {
+ @Override
+ public Object getDefault(AttributeMap rule) {
+ // For tests and similar, we don't want to force people to explicitly
+ // specify throw-away data.
+ return "example.app." + rule.get(WATCH_APP_NAME_ATTR, STRING);
+ }
+ }))
+ /* <!-- #BLAZE_RULE($watch_application_bundle_rule).ATTRIBUTE(app_infoplists) -->
+ Infoplist files to be merged. The merged output corresponds to <i>appname</i>-Info.plist
+ in Xcode projects. Duplicate keys between infoplist files will cause an error if
+ and only if the values conflict. If both <code>infoplist</code> and
+ <code>infoplists</code> are specified, the files defined in both attributes will be used.
+ Blaze will perform variable substitution on the plist files for the following values:
+ <ul>
+ <li><code>${EXECUTABLE_NAME}</code>: The name of the executable generated and included
+ in the bundle by blaze, which can be used as the value for
+ <code>CFBundleExecutable</code> within the plist.
+ <li><code>${BUNDLE_NAME}</code>: This target's name and bundle suffix (.bundle or .app)
+ in the form<code><var>name</var></code>.<code>suffix</code>.
+ <li><code>${PRODUCT_NAME}</code>: This target's name.
+ </ul>
+ <!-- #END_BLAZE_RULE.ATTRIBUTE -->*/
+ .add(attr(WATCH_APP_INFOPLISTS_ATTR, BuildType.LABEL_LIST).allowedFileTypes(PLIST_TYPE))
+ /* <!-- #BLAZE_RULE($watch_application_bundle_rule).ATTRIBUTE(app_provisioning_profile)-->
+ The provisioning profile (.mobileprovision file) to use when bundling
+ the watch application.
+
+ This is only used for non-simulator builds.
+ <!-- #END_BLAZE_RULE.ATTRIBUTE -->*/
+ .add(
+ attr(WATCH_APP_PROVISIONING_PROFILE_ATTR, LABEL)
+ .singleArtifact()
+ .allowedFileTypes(FileType.of(".mobileprovision")))
+ .add(
+ attr(WATCH_APP_DEFAULT_PROVISIONING_PROFILE_ATTR, LABEL)
+ .singleArtifact()
+ .allowedFileTypes(FileType.of(".mobileprovision"))
+ .value(
+ new LateBoundLabel<BuildConfiguration>(ObjcConfiguration.class) {
+ @Override
+ public Label getDefault(Rule rule, AttributeMap attributes,
+ BuildConfiguration configuration) {
+ AppleConfiguration appleConfiguration =
+ configuration.getFragment(AppleConfiguration.class);
+ if (appleConfiguration.getBundlingPlatform() != Platform.IOS_DEVICE) {
+ return null;
+ }
+ if (rule.isAttributeValueExplicitlySpecified(
+ WATCH_APP_PROVISIONING_PROFILE_ATTR)) {
+ return null;
+ }
+ return appleConfiguration.getDefaultProvisioningProfileLabel();
+ }
+ }))
+ /* <!-- #BLAZE_RULE($objc_resources_rule).ATTRIBUTE(app_storyboards) -->
+ Files which are .storyboard resources for the watch application, possibly
+ localizable.
+
+ These files are compiled and placed in the bundle root of the final package.
+ <!-- #END_BLAZE_RULE.ATTRIBUTE -->*/
+ .add(attr(WATCH_APP_STORYBOARDS_ATTR, LABEL_LIST)
+ .allowedFileTypes(STORYBOARD_TYPE))
+ /* <!-- #BLAZE_RULE($watch_application_bundle_rule).ATTRIBUTE(app_resources) -->
+ Files to include in the final watch application bundle.
+
+ They are not processed or compiled in any way besides the processing
+ done by the rules that actually generate them. These files are placed
+ in the root of the bundle (e.g. Foo.app/...) in most cases.
+ However, if they appear to be localized (i.e. are contained in a
+ directory called *.lproj), they will be placed in a directory of the
+ same name in the app bundle.
+ <!-- #END_BLAZE_RULE.ATTRIBUTE -->*/
+ .add(attr(WATCH_APP_RESOURCES_ATTR, LABEL_LIST).legacyAllowAnyFileType()
+ .direct_compile_time_input())
+ /* <!-- #BLAZE_RULE($watch_application_bundle_rule).ATTRIBUTE(app_structured_resources)-->
+ Files to include in the final watch application bundle.
+
+ They are not processed or compiled in any way besides the processing
+ done by the rules that actually generate them. In differences to
+ <code>resources</code> these files are placed in the bundle root in
+ the same structure passed to this argument, so
+ <code>["res/foo.png"]</code> will end up in
+ <code>Foo.app/res/foo.png</code>.
+ <p>Note that in the generated XCode project file, all files in the top directory of
+ the specified files will be included in the Xcode-generated app bundle. So
+ specifying <code>["res/foo.png"]</code> will lead to the inclusion of all files in
+ directory <code>res</code>.
+ <!-- #END_BLAZE_RULE.ATTRIBUTE -->*/
+ .add(attr(WATCH_APP_STRUCTURED_RESOURCES_ATTR, LABEL_LIST)
+ .legacyAllowAnyFileType()
+ .direct_compile_time_input())
+ /* <!-- #BLAZE_RULE($watch_application_bundle_rule).ATTRIBUTE(app_strings) -->
+ Files which are plists of strings, often localizable to be added to watch application.
+
+ These files are converted to binary plists (if they are not already)
+ and placed in the bundle root of the final package. If this file's
+ immediate containing directory is named *.lproj (e.g. en.lproj,
+ Base.lproj), it will be placed under a directory of that name in the
+ final bundle. This allows for localizable strings.
+ <!-- #END_BLAZE_RULE.ATTRIBUTE -->*/
+ .add(attr(WATCH_APP_STRINGS_ATTR, LABEL_LIST)
+ .allowedFileTypes(STRINGS_TYPE)
+ .direct_compile_time_input())
+ .build();
+ }
+ @Override
+ public Metadata getMetadata() {
+ return RuleDefinition.Metadata.builder()
+ .name("$watch_application_bundle_rule")
+ .type(RuleClassType.ABSTRACT)
+ .ancestors(
+ AppleToolchain.RequiresXcodeConfigRule.class,
+ ResourceToolsRule.class,
+ ReleaseBundlingToolsRule.class,
+ XcrunRule.class)
+ .build();
+ }
+ }
}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcXcodeprojRule.java b/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcXcodeprojRule.java
index 4b6f29cf00..fcb83b0d47 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcXcodeprojRule.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcXcodeprojRule.java
@@ -50,6 +50,8 @@ public class ObjcXcodeprojRule implements RuleDefinition {
"ios_application",
"ios_extension_binary",
"ios_extension",
+ "apple_watch_extension_binary",
+ "apple_watch1_extension",
"ios_framework",
"ios_framework_binary",
"experimental_ios_test",
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/ReleaseBundling.java b/src/main/java/com/google/devtools/build/lib/rules/objc/ReleaseBundling.java
index 4a8e560baf..ce72d80618 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/ReleaseBundling.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/ReleaseBundling.java
@@ -59,7 +59,10 @@ final class ReleaseBundling {
private final NestedSetBuilder<Artifact> infoplistInputs = NestedSetBuilder.stableOrder();
private Iterable<Artifact> infoPlistsFromRule;
private ImmutableSet<TargetDeviceFamily> families;
+ private IntermediateArtifacts intermediateArtifacts;
private String artifactPrefix;
+ private Artifact entitlements;
+ private Artifact extraEntitlements;
public Builder setIpaArtifact(Artifact ipaArtifact) {
this.ipaArtifact = ipaArtifact;
@@ -121,6 +124,11 @@ final class ReleaseBundling {
return this;
}
+ public Builder setIntermediateArtifacts(IntermediateArtifacts intermediateArtifacts) {
+ this.intermediateArtifacts = intermediateArtifacts;
+ return this;
+ }
+
public Builder setTargetDeviceFamilies(ImmutableSet<TargetDeviceFamily> families) {
this.families = families;
return this;
@@ -131,7 +139,18 @@ final class ReleaseBundling {
return this;
}
+ public Builder setEntitlements(Artifact entitlements) {
+ this.entitlements = entitlements;
+ return this;
+ }
+
+ public Builder setExtraEntitlements(Artifact extraEntitlements) {
+ this.extraEntitlements = extraEntitlements;
+ return this;
+ }
+
public ReleaseBundling build() {
+ Preconditions.checkNotNull(intermediateArtifacts, "intermediateArtifacts");
Preconditions.checkNotNull(families, FAMILIES_ATTR);
return new ReleaseBundling(
ipaArtifact,
@@ -146,7 +165,10 @@ final class ReleaseBundling {
infoplistInputs.build(),
infoPlistsFromRule,
families,
- artifactPrefix);
+ intermediateArtifacts,
+ artifactPrefix,
+ entitlements,
+ extraEntitlements);
}
}
@@ -203,6 +225,10 @@ final class ReleaseBundling {
.setProvisioningProfile(provisioningProfile)
.setProvisioningProfileAttributeName(PROVISIONING_PROFILE_ATTR)
.setTargetDeviceFamilies(families)
+ .setIntermediateArtifacts(ObjcRuleClasses.intermediateArtifacts(ruleContext))
+ .setEntitlements(ruleContext.getPrerequisiteArtifact("entitlements", Mode.TARGET))
+ .setExtraEntitlements(
+ ruleContext.getPrerequisiteArtifact(":extra_entitlements", Mode.TARGET))
.build();
}
@@ -220,8 +246,11 @@ final class ReleaseBundling {
private final String provisioningProfileAttributeName;
private final NestedSet<Artifact> infoplistInputs;
private final ImmutableSet<TargetDeviceFamily> families;
+ private final IntermediateArtifacts intermediateArtifacts;
private final Iterable<Artifact> infoPlistsFromRule;
private final String artifactPrefix;
+ private final Artifact entitlements;
+ private final Artifact extraEntitlements;
private ReleaseBundling(
Artifact ipaArtifact,
@@ -236,7 +265,10 @@ final class ReleaseBundling {
NestedSet<Artifact> infoplistInputs,
Iterable<Artifact> infoPlistsFromRule,
ImmutableSet<TargetDeviceFamily> families,
- String artifactPrefix) {
+ IntermediateArtifacts intermediateArtifacts,
+ String artifactPrefix,
+ Artifact entitlements,
+ Artifact extraEntitlements) {
this.ipaArtifact = Preconditions.checkNotNull(ipaArtifact);
this.bundleId = bundleId;
this.primaryBundleId = primaryBundleId;
@@ -250,7 +282,10 @@ final class ReleaseBundling {
this.infoplistInputs = Preconditions.checkNotNull(infoplistInputs);
this.infoPlistsFromRule = infoPlistsFromRule;
this.families = Preconditions.checkNotNull(families);
+ this.intermediateArtifacts = Preconditions.checkNotNull(intermediateArtifacts);
this.artifactPrefix = artifactPrefix;
+ this.entitlements = entitlements;
+ this.extraEntitlements = extraEntitlements;
}
/**
@@ -326,6 +361,13 @@ final class ReleaseBundling {
}
/**
+ * Returns {@link IntermediateArtifacts} used to create this bundle.
+ */
+ public IntermediateArtifacts getIntermediateArtifacts() {
+ return intermediateArtifacts;
+ }
+
+ /**
* Returns the name of the attribute which is used to specifiy the provisioning profile.
*/
public String getProvisioningProfileAttrName() {
@@ -348,4 +390,20 @@ final class ReleaseBundling {
public String getArtifactPrefix() {
return artifactPrefix;
}
+
+ /**
+ * Returns an {@link Artifact} containing the entitlements used to sign this bundle for
+ * non-simulator builds; can be null.
+ */
+ public Artifact getEntitlements() {
+ return entitlements;
+ }
+
+ /**
+ * Returns an {@link Artifact} containing the extra entitlements passed via command line that is
+ * used to sign this bundle for non-simulator builds; can be null.
+ */
+ public Artifact getExtraEntitlements() {
+ return extraEntitlements;
+ }
}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/ReleaseBundlingSupport.java b/src/main/java/com/google/devtools/build/lib/rules/objc/ReleaseBundlingSupport.java
index 617300ecab..8b15f7ac6a 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/ReleaseBundlingSupport.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/ReleaseBundlingSupport.java
@@ -18,8 +18,6 @@ import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.devtools.build.lib.packages.ImplicitOutputsFunction.fromTemplates;
import static com.google.devtools.build.lib.rules.objc.ObjcProvider.Flag.USES_SWIFT;
import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.ReleaseBundlingRule.APP_ICON_ATTR;
-import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.ReleaseBundlingRule.ENTITLEMENTS_ATTR;
-import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.ReleaseBundlingRule.EXTRA_ENTITLEMENTS_ATTR;
import static com.google.devtools.build.lib.rules.objc.TargetDeviceFamily.UI_DEVICE_FAMILY_VALUES;
import com.google.common.annotations.VisibleForTesting;
@@ -172,7 +170,7 @@ public final class ReleaseBundlingSupport {
this.ruleContext = ruleContext;
this.objcProvider = objcProvider;
this.releaseBundling = releaseBundling;
- this.intermediateArtifacts = ObjcRuleClasses.intermediateArtifacts(ruleContext);
+ this.intermediateArtifacts = releaseBundling.getIntermediateArtifacts();
this.bundling = bundling(ruleContext, objcProvider, bundleDirFormat, bundleName,
bundleMinimumOsVersion);
bundleSupport = new BundleSupport(ruleContext, bundling, extraActoolArgs());
@@ -520,12 +518,12 @@ public final class ReleaseBundlingSupport {
* <p>Finally, if an entitlements file was provided via {@code --extra_entitlements} it is merged
* into the substituted entitlements.
*/
- private void registerEntitlementsActions() throws InterruptedException {
+ private void registerEntitlementsActions() {
Artifact teamPrefixFile =
intermediateArtifacts.appendExtensionForEntitlementArtifact(".team_prefix_file");
registerExtractTeamPrefixAction(teamPrefixFile);
- Artifact entitlementsNeedingSubstitution = attributes.entitlements();
+ Artifact entitlementsNeedingSubstitution = releaseBundling.getEntitlements();
if (entitlementsNeedingSubstitution == null) {
entitlementsNeedingSubstitution =
intermediateArtifacts.appendExtensionForEntitlementArtifact(
@@ -534,10 +532,11 @@ public final class ReleaseBundlingSupport {
}
Artifact substitutedEntitlements = intermediateArtifacts.entitlements();
- if (attributes.extraEntitlements() != null) {
+ if (releaseBundling.getExtraEntitlements() != null) {
substitutedEntitlements =
intermediateArtifacts.appendExtensionForEntitlementArtifact(".substituted");
- registerMergeEntitlementsAction(substitutedEntitlements, attributes.extraEntitlements());
+ registerMergeEntitlementsAction(substitutedEntitlements,
+ releaseBundling.getExtraEntitlements());
}
registerEntitlementsVariableSubstitutionAction(
@@ -760,6 +759,12 @@ public final class ReleaseBundlingSupport {
}
private void registerCombineArchitecturesAction() {
+ // Skip combining binaries when building for watch as there is only one stub binary and it
+ // it should not be corrupted when combining.
+ if (bundleSupport.isBuildingForWatch()) {
+ return;
+ }
+
Artifact resultingLinkedBinary = intermediateArtifacts.combinedArchitectureBinary();
NestedSet<Artifact> linkedBinaries = linkedBinaries();
@@ -802,7 +807,12 @@ public final class ReleaseBundlingSupport {
}
// Convert names to a sequence containing "1" and/or "2" for iPhone and iPad, respectively.
- ImmutableSet<TargetDeviceFamily> families = bundleSupport.targetDeviceFamilies();
+ ImmutableSet<TargetDeviceFamily> families;
+ if (bundleSupport.isBuildingForWatch()) {
+ families = ImmutableSet.of(TargetDeviceFamily.WATCH);
+ } else {
+ families = bundleSupport.targetDeviceFamilies();
+ }
Iterable<Integer> familyIndexes =
families.isEmpty() ? ImmutableList.<Integer>of() : UI_DEVICE_FAMILY_VALUES.get(families);
buildSettings.add(XcodeprojBuildSetting.newBuilder()
@@ -810,7 +820,7 @@ public final class ReleaseBundlingSupport {
.setValue(Joiner.on(',').join(familyIndexes))
.build());
- Artifact entitlements = attributes.entitlements();
+ Artifact entitlements = releaseBundling.getEntitlements();
if (entitlements != null) {
buildSettings.add(XcodeprojBuildSetting.newBuilder()
.setName("CODE_SIGN_ENTITLEMENTS")
@@ -1163,16 +1173,6 @@ public final class ReleaseBundlingSupport {
return ruleContext.getExecutablePrerequisite("ipa_post_processor", Mode.TARGET);
}
- @Nullable
- Artifact entitlements() {
- return ruleContext.getPrerequisiteArtifact(ENTITLEMENTS_ATTR, Mode.TARGET);
- }
-
- @Nullable
- Artifact extraEntitlements() {
- return ruleContext.getPrerequisiteArtifact(EXTRA_ENTITLEMENTS_ATTR, Mode.TARGET);
- }
-
NestedSet<? extends Artifact> dependentLinkedBinaries() {
if (ruleContext.attributes().getAttributeDefinition("binary") == null) {
return NestedSetBuilder.emptySet(Order.STABLE_ORDER);
@@ -1330,7 +1330,7 @@ public final class ReleaseBundlingSupport {
* transition may exist with the same value in a single Bazel invocation.
*/
enum ConfigurationDistinguisher {
- IOS_EXTENSION, IOS_APPLICATION, FRAMEWORK, UNKNOWN
+ IOS_EXTENSION, IOS_APPLICATION, FRAMEWORK, UNKNOWN, WATCH_OS1_EXTENSION
}
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/ReleaseBundlingTargetFactory.java b/src/main/java/com/google/devtools/build/lib/rules/objc/ReleaseBundlingTargetFactory.java
index 373271a163..4a53915177 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/ReleaseBundlingTargetFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/ReleaseBundlingTargetFactory.java
@@ -62,6 +62,7 @@ public abstract class ReleaseBundlingTargetFactory implements RuleConfiguredTarg
@Override
public ConfiguredTarget create(RuleContext ruleContext) throws InterruptedException {
+ validateAttributes(ruleContext);
ObjcCommon common = common(ruleContext);
XcodeProvider.Builder xcodeProviderBuilder = new XcodeProvider.Builder();
@@ -108,6 +109,12 @@ public abstract class ReleaseBundlingTargetFactory implements RuleConfiguredTarg
}
/**
+ * Validates application-related attributes set on this rule and registers any errors with the
+ * rule context. Default implemenation does nothing; subclasses may override it.
+ */
+ protected void validateAttributes(RuleContext ruleContext) {}
+
+ /**
* Returns the minimum OS version this bundle's plist and resources should be generated for
* (<b>not</b> the minimum OS version its binary is compiled with, that needs to be set in the
* configuration).
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/TargetDeviceFamily.java b/src/main/java/com/google/devtools/build/lib/rules/objc/TargetDeviceFamily.java
index 511adbd3f4..5074a6ee64 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/TargetDeviceFamily.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/TargetDeviceFamily.java
@@ -29,7 +29,7 @@ import java.util.Set;
* Possible values in the {@code TARGETED_DEVICE_FAMILY} build setting.
*/
public enum TargetDeviceFamily {
- IPAD, IPHONE;
+ IPAD, IPHONE, WATCH;
/**
* An exception that indicates the name of a device family was not recognized or is somehow
@@ -59,8 +59,11 @@ public enum TargetDeviceFamily {
ImmutableMap.<Set<TargetDeviceFamily>, List<Integer>>builder()
.put(ImmutableSet.of(TargetDeviceFamily.IPHONE), ImmutableList.of(1))
.put(ImmutableSet.of(TargetDeviceFamily.IPAD), ImmutableList.of(2))
+ .put(ImmutableSet.of(TargetDeviceFamily.WATCH), ImmutableList.of(4))
.put(ImmutableSet.of(TargetDeviceFamily.IPHONE, TargetDeviceFamily.IPAD),
ImmutableList.of(1, 2))
+ .put(ImmutableSet.of(TargetDeviceFamily.IPHONE, TargetDeviceFamily.WATCH),
+ ImmutableList.of(1, 4))
.build();
/**
@@ -71,7 +74,7 @@ public enum TargetDeviceFamily {
}
private static final ImmutableBiMap<TargetDeviceFamily, String> BY_NAME_IN_RULE =
- ImmutableBiMap.of(IPAD, "ipad", IPHONE, "iphone");
+ ImmutableBiMap.of(IPAD, "ipad", IPHONE, "iphone", WATCH, "watch");
/**
* Converts a sequence containing the strings returned by {@link #getNameInRule()} to a set of
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/WatchApplicationSupport.java b/src/main/java/com/google/devtools/build/lib/rules/objc/WatchApplicationSupport.java
new file mode 100644
index 0000000000..1bfb602e2a
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/WatchApplicationSupport.java
@@ -0,0 +1,352 @@
+// Copyright 2016 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.ASSET_CATALOG;
+import static com.google.devtools.build.lib.rules.objc.ObjcProvider.BUNDLE_FILE;
+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.STORYBOARD;
+import static com.google.devtools.build.lib.rules.objc.ObjcProvider.STRINGS;
+import static com.google.devtools.build.lib.rules.objc.ObjcProvider.XCASSETS_DIR;
+import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchApplicationBundleRule.WATCH_APP_ASSET_CATALOGS_ATTR;
+import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchApplicationBundleRule.WATCH_APP_BUNDLE_ID_ATTR;
+import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchApplicationBundleRule.WATCH_APP_DEFAULT_PROVISIONING_PROFILE_ATTR;
+import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchApplicationBundleRule.WATCH_APP_ENTITLEMENTS_ATTR;
+import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchApplicationBundleRule.WATCH_APP_ICON_ATTR;
+import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchApplicationBundleRule.WATCH_APP_INFOPLISTS_ATTR;
+import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchApplicationBundleRule.WATCH_APP_PROVISIONING_PROFILE_ATTR;
+import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchApplicationBundleRule.WATCH_APP_RESOURCES_ATTR;
+import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchApplicationBundleRule.WATCH_APP_STORYBOARDS_ATTR;
+import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchApplicationBundleRule.WATCH_APP_STRINGS_ATTR;
+import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchApplicationBundleRule.WATCH_APP_STRUCTURED_RESOURCES_ATTR;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.cmdline.Label;
+import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
+import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
+import com.google.devtools.build.lib.rules.apple.AppleConfiguration;
+import com.google.devtools.build.lib.rules.apple.Platform;
+import com.google.devtools.build.lib.rules.objc.ObjcProvider.Builder;
+import com.google.devtools.build.lib.rules.objc.ReleaseBundlingSupport.LinkedBinary;
+import com.google.devtools.build.lib.rules.objc.ReleaseBundlingSupport.SplitArchTransition.ConfigurationDistinguisher;
+import com.google.devtools.build.lib.rules.objc.WatchUtils.WatchOSVersion;
+import com.google.devtools.build.lib.syntax.Type;
+import com.google.devtools.build.xcode.xcodegen.proto.XcodeGenProtos.XcodeprojBuildSetting;
+
+import javax.annotation.Nullable;
+
+/**
+ * Contains support methods to build watch application bundle - does normal bundle processing -
+ * resources, plists and creates a final (signed if necessary) bundle.
+ */
+final class WatchApplicationSupport {
+
+ private final RuleContext ruleContext;
+ private final WatchOSVersion watchOSVersion;
+ private final ImmutableSet<Attribute> dependencyAttributes;
+ private final String bundleName;
+ private final IntermediateArtifacts intermediateArtifacts;
+ private final Attributes attributes;
+ private final Artifact ipaArtifact;
+ private final String artifactPrefix;
+ private final ConfigurationDistinguisher configurationDistinguisher;
+
+ WatchApplicationSupport(RuleContext ruleContext, WatchOSVersion watchOSVersion,
+ ImmutableSet<Attribute> dependencyAttributes, IntermediateArtifacts intermediateArtifacts,
+ String bundleName, Artifact ipaArtifact, String artifactPrefix,
+ ConfigurationDistinguisher configurationDistinguisher) {
+ this.ruleContext = ruleContext;
+ this.watchOSVersion = watchOSVersion;
+ this.dependencyAttributes = dependencyAttributes;
+ this.intermediateArtifacts = intermediateArtifacts;
+ this.bundleName = bundleName;
+ this.ipaArtifact = ipaArtifact;
+ this.artifactPrefix = artifactPrefix;
+ this.attributes = new Attributes(ruleContext);
+ this.configurationDistinguisher = configurationDistinguisher;
+ }
+
+ void createBundle(XcodeProvider.Builder xcodeProviderBuilder,
+ ObjcProvider.Builder objcProviderBuilder, NestedSetBuilder<Artifact> filesToBuild)
+ throws InterruptedException {
+ // Add common watch settings.
+ WatchUtils.addXcodeSettings(ruleContext, xcodeProviderBuilder);
+
+ // Add watch application specific xcode settings.
+ addXcodeSettings(xcodeProviderBuilder);
+
+ registerActions();
+
+ ObjcProvider objcProvider = objcProvider(objcProviderBuilder);
+ ReleaseBundling.Builder releaseBundling = new ReleaseBundling.Builder()
+ .setIpaArtifact(ipaArtifact)
+ .setBundleId(attributes.bundleId())
+ .setAppIcon(attributes.appIcon())
+ .setProvisioningProfile(attributes.provisioningProfile())
+ .setProvisioningProfileAttributeName(WATCH_APP_PROVISIONING_PROFILE_ATTR)
+ .setTargetDeviceFamilies(families())
+ .setIntermediateArtifacts(intermediateArtifacts)
+ .setInfoPlistsFromRule(attributes.infoPlists())
+ .setArtifactPrefix(artifactPrefix)
+ .setEntitlements(attributes.entitlements());
+
+ if (attributes.isBundleIdExplicitySpecified()) {
+ releaseBundling.setPrimaryBundleId(attributes.bundleId());
+ } else {
+ releaseBundling.setFallbackBundleId(attributes.bundleId());
+ }
+
+ new ReleaseBundlingSupport(
+ ruleContext,
+ objcProvider,
+ LinkedBinary.DEPENDENCIES_ONLY,
+ ReleaseBundlingSupport.APP_BUNDLE_DIR_FORMAT,
+ bundleName,
+ WatchUtils.determineMinimumOsVersion(
+ ObjcRuleClasses.objcConfiguration(ruleContext).getMinimumOs()),
+ releaseBundling.build())
+ .registerActions(DsymOutputType.APP)
+ .addXcodeSettings(xcodeProviderBuilder)
+ .addFilesToBuild(filesToBuild, DsymOutputType.APP)
+ .validateResources()
+ .validateAttributes();
+
+ XcodeSupport xcodeSupport = new XcodeSupport(ruleContext, intermediateArtifacts,
+ labelForWatchApplication())
+ .addXcodeSettings(xcodeProviderBuilder, objcProvider,
+ watchOSVersion.getApplicationXcodeProductType(),
+ ruleContext.getFragment(AppleConfiguration.class).getIosCpu(),
+ configurationDistinguisher);
+
+ for (Attribute attribute : dependencyAttributes) {
+ xcodeSupport.addDependencies(xcodeProviderBuilder, attribute);
+ }
+ }
+
+ /**
+ * Returns the {@link TargetDeviceFamily} that the watch application bundle is targeting.
+ * For simulator builds, this returns a set of {@code TargetDeviceFamily.IPHONE} and
+ * {@code TargetDeviceFamily.WATCH} and for non-simulator builds, this returns
+ * {@code TargetDeviceFamily.WATCH}.
+ */
+ private ImmutableSet<TargetDeviceFamily> families() {
+ Platform platform = ruleContext.getFragment(AppleConfiguration.class).getBundlingPlatform();
+ if (platform == Platform.IOS_DEVICE) {
+ return ImmutableSet.of(TargetDeviceFamily.WATCH);
+ } else {
+ return ImmutableSet.of(TargetDeviceFamily.IPHONE, TargetDeviceFamily.WATCH);
+ }
+ }
+
+ /**
+ * Adds watch application specific xcode settings - TARGETED_DEVICE_FAMILY is set to "1, 4"
+ * for enabling building for simulator.
+ */
+ private void addXcodeSettings(XcodeProvider.Builder xcodeProviderBuilder) {
+ xcodeProviderBuilder.addMainTargetXcodeprojBuildSettings(ImmutableList.of(
+ XcodeprojBuildSetting.newBuilder()
+ .setName("TARGETED_DEVICE_FAMILY[sdk=iphonesimulator*]")
+ .setValue(Joiner.on(',').join(TargetDeviceFamily.UI_DEVICE_FAMILY_VALUES.get(
+ families())))
+ .build()));
+ }
+
+ /**
+ * Registers actions to copy WatchKit stub binary at
+ * $(SDK_ROOT)/Library/Application Support/WatchKit/WK as bundle binary and as stub resource.
+ *
+ * For example, for a bundle named "Foo.app", the contents will be,
+ * - Foo.app/Foo (WK stub as binary)
+ * - Foo.app/_WatchKitStub/WK (WK stub as resource)
+ */
+ private void registerActions() {
+ Artifact watchKitStubZip = watchKitStubZip();
+ String workingDirectory = watchKitStubZip.getExecPathString()
+ .substring(0, watchKitStubZip.getExecPathString().lastIndexOf('/'));
+ String watchKitStubBinaryPath = workingDirectory + "/" + bundleName;
+ String watchKitStubResourcePath = workingDirectory + "/_WatchKitStub";
+
+ ImmutableList<String> command = ImmutableList.of(
+ // 1. Copy WK stub as binary
+ String.format("cp %s %s", WatchUtils.WATCH_KIT_STUB_PATH, watchKitStubBinaryPath),
+ "&&",
+ // 2. Copy WK stub as bundle resource.
+ "mkdir -p " + watchKitStubResourcePath,
+ "&&",
+ String.format("cp %s %s", WatchUtils.WATCH_KIT_STUB_PATH, watchKitStubResourcePath),
+ // 3. Zip them.
+ "&&",
+ "cd " + workingDirectory,
+ "&&",
+ String.format(
+ "/usr/bin/zip -q -r -0 %s %s",
+ watchKitStubZip.getFilename(),
+ Joiner.on(" ").join(ImmutableList.of("_WatchKitStub", bundleName))));
+
+ ruleContext.registerAction(ObjcRuleClasses.spawnAppleEnvActionBuilder(ruleContext,
+ ruleContext.getFragment(AppleConfiguration.class).getBundlingPlatform())
+ .setProgressMessage(
+ "Copying WatchKit binary and stub resource: " + ruleContext.getLabel())
+ .setShellCommand(ImmutableList.of("/bin/bash", "-c", Joiner.on(" ").join(command)))
+ .addOutput(watchKitStubZip)
+ .build(ruleContext));
+ }
+
+ private ObjcProvider objcProvider(Builder objcProviderBuilder) {
+ // Add all resource files applicable to watch application from dependency providers.
+ for (Attribute attribute : dependencyAttributes) {
+ Iterable<ObjcProvider> dependencyObjcProviders = ruleContext.getPrerequisites(
+ attribute.getName(), attribute.getAccessMode(), ObjcProvider.class);
+ for (ObjcProvider dependencyObjcProvider : dependencyObjcProviders) {
+ objcProviderBuilder.addTransitiveAndPropagate(GENERAL_RESOURCE_FILE,
+ dependencyObjcProvider);
+ objcProviderBuilder.addTransitiveAndPropagate(GENERAL_RESOURCE_DIR,
+ dependencyObjcProvider);
+ objcProviderBuilder.addTransitiveAndPropagate(BUNDLE_FILE,
+ dependencyObjcProvider);
+ objcProviderBuilder.addTransitiveAndPropagate(XCASSETS_DIR,
+ dependencyObjcProvider);
+ objcProviderBuilder.addTransitiveAndPropagate(ASSET_CATALOG,
+ dependencyObjcProvider);
+ objcProviderBuilder.addTransitiveAndPropagate(STRINGS,
+ dependencyObjcProvider);
+ objcProviderBuilder.addTransitiveAndPropagate(STORYBOARD,
+ dependencyObjcProvider);
+ }
+ }
+ // Add zip containing WatchKit stubs.
+ objcProviderBuilder.add(ObjcProvider.MERGE_ZIP, watchKitStubZip());
+
+ // Add resource files.
+ objcProviderBuilder.addAll(GENERAL_RESOURCE_FILE, attributes.storyboards())
+ .addAll(GENERAL_RESOURCE_FILE, attributes.resources())
+ .addAll(GENERAL_RESOURCE_FILE, attributes.strings())
+ .addAll(GENERAL_RESOURCE_DIR,
+ ObjcCommon.xcodeStructuredResourceDirs(attributes.structuredResources()))
+ .addAll(BUNDLE_FILE, BundleableFile.flattenedRawResourceFiles(attributes.resources()))
+ .addAll(
+ BUNDLE_FILE,
+ BundleableFile.structuredRawResourceFiles(attributes.structuredResources()))
+ .addAll(XCASSETS_DIR, ObjcCommon.uniqueContainers(attributes.assetCatalogs(),
+ ObjcCommon.ASSET_CATALOG_CONTAINER_TYPE))
+ .addAll(ASSET_CATALOG, attributes.assetCatalogs())
+ .addAll(STRINGS, attributes.strings())
+ .addAll(STORYBOARD, attributes.storyboards());
+
+ return objcProviderBuilder.build();
+ }
+
+ private Label labelForWatchApplication()
+ throws InterruptedException {
+ try {
+ return Label.create(ruleContext.getLabel().getPackageName(), bundleName);
+ } catch (LabelSyntaxException labelSyntaxException) {
+ throw new InterruptedException("Exception while creating target label for watch "
+ + "appplication " + labelSyntaxException);
+ }
+ }
+
+ /**
+ * Returns a zip {@link Artifact} containing stub binary and stub resource that are to be added
+ * to the bundle.
+ */
+ private Artifact watchKitStubZip() {
+ return ruleContext.getRelatedArtifact(
+ ruleContext.getUniqueDirectory("_watch"), "/WatchKitStub.zip");
+ }
+
+ /**
+ * Rule attributes used for creating watch application bundle.
+ */
+ private static class Attributes {
+ private final RuleContext ruleContext;
+
+ private Attributes(RuleContext ruleContext) {
+ this.ruleContext = ruleContext;
+ }
+
+ @Nullable
+ String appIcon() {
+ return Strings.emptyToNull(ruleContext.attributes().get(WATCH_APP_ICON_ATTR, Type.STRING));
+ }
+
+ @Nullable
+ Artifact provisioningProfile() {
+ Artifact explicitProvisioningProfile =
+ getPrerequisiteArtifact(WATCH_APP_PROVISIONING_PROFILE_ATTR);
+ if (explicitProvisioningProfile != null) {
+ return explicitProvisioningProfile;
+ }
+ return getPrerequisiteArtifact(WATCH_APP_DEFAULT_PROVISIONING_PROFILE_ATTR);
+ }
+
+ String bundleId() {
+ Preconditions.checkState(!Strings.isNullOrEmpty(
+ ruleContext.attributes().get(WATCH_APP_BUNDLE_ID_ATTR, Type.STRING)),
+ "requires a bundle_id value");
+ return ruleContext.attributes().get(WATCH_APP_BUNDLE_ID_ATTR, Type.STRING);
+ }
+
+ ImmutableList<Artifact> infoPlists() {
+ return getPrerequisiteArtifacts(WATCH_APP_INFOPLISTS_ATTR);
+ }
+
+ ImmutableList<Artifact> assetCatalogs() {
+ return getPrerequisiteArtifacts(WATCH_APP_ASSET_CATALOGS_ATTR);
+ }
+
+ ImmutableList<Artifact> strings() {
+ return getPrerequisiteArtifacts(WATCH_APP_STRINGS_ATTR);
+ }
+
+ ImmutableList<Artifact> storyboards() {
+ return getPrerequisiteArtifacts(WATCH_APP_STORYBOARDS_ATTR);
+ }
+
+ ImmutableList<Artifact> resources() {
+ return getPrerequisiteArtifacts(WATCH_APP_RESOURCES_ATTR);
+ }
+
+ ImmutableList<Artifact> structuredResources() {
+ return getPrerequisiteArtifacts(WATCH_APP_STRUCTURED_RESOURCES_ATTR);
+ }
+
+ @Nullable
+ Artifact entitlements() {
+ return getPrerequisiteArtifact(WATCH_APP_ENTITLEMENTS_ATTR);
+ }
+
+ private boolean isBundleIdExplicitySpecified() {
+ return ruleContext.attributes().isAttributeValueExplicitlySpecified(WATCH_APP_BUNDLE_ID_ATTR);
+ }
+
+ private ImmutableList<Artifact> getPrerequisiteArtifacts(String attribute) {
+ return ruleContext.getPrerequisiteArtifacts(attribute, Mode.TARGET).list();
+ }
+
+ @Nullable
+ private Artifact getPrerequisiteArtifact(String attribute) {
+ return ruleContext.getPrerequisiteArtifact(attribute, Mode.TARGET);
+ }
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/WatchExtensionSupport.java b/src/main/java/com/google/devtools/build/lib/rules/objc/WatchExtensionSupport.java
new file mode 100644
index 0000000000..1aa561ffe7
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/WatchExtensionSupport.java
@@ -0,0 +1,301 @@
+// Copyright 2016 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.common.base.Preconditions.checkNotNull;
+import static com.google.devtools.build.lib.rules.objc.ObjcProvider.BUNDLE_FILE;
+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.MERGE_ZIP;
+import static com.google.devtools.build.lib.rules.objc.ObjcProvider.STRINGS;
+import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.BundlingRule.FAMILIES_ATTR;
+import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchExtensionBundleRule.WATCH_EXT_BUNDLE_ID_ATTR;
+import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchExtensionBundleRule.WATCH_EXT_DEFAULT_PROVISIONING_PROFILE_ATTR;
+import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchExtensionBundleRule.WATCH_EXT_ENTITLEMENTS_ATTR;
+import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchExtensionBundleRule.WATCH_EXT_FAMILIES_ATTR;
+import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchExtensionBundleRule.WATCH_EXT_INFOPLISTS_ATTR;
+import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchExtensionBundleRule.WATCH_EXT_PROVISIONING_PROFILE_ATTR;
+import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchExtensionBundleRule.WATCH_EXT_RESOURCES_ATTR;
+import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchExtensionBundleRule.WATCH_EXT_STRINGS_ATTR;
+import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchExtensionBundleRule.WATCH_EXT_STRUCTURED_RESOURCES_ATTR;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.analysis.actions.FileWriteAction;
+import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
+import com.google.devtools.build.lib.rules.apple.AppleConfiguration;
+import com.google.devtools.build.lib.rules.objc.ObjcProvider.Builder;
+import com.google.devtools.build.lib.rules.objc.ReleaseBundlingSupport.LinkedBinary;
+import com.google.devtools.build.lib.rules.objc.ReleaseBundlingSupport.SplitArchTransition.ConfigurationDistinguisher;
+import com.google.devtools.build.lib.rules.objc.TargetDeviceFamily.InvalidFamilyNameException;
+import com.google.devtools.build.lib.rules.objc.TargetDeviceFamily.RepeatedFamilyNameException;
+import com.google.devtools.build.lib.rules.objc.WatchUtils.WatchOSVersion;
+import com.google.devtools.build.lib.syntax.Type;
+
+import com.dd.plist.NSDictionary;
+import com.dd.plist.NSObject;
+
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+/**
+ * Contains support methods to build watch extension bundle - does normal bundle processing -
+ * compiling and linking the binary, resources, plists and creates a final
+ * (signed if necessary) bundle.
+ */
+public class WatchExtensionSupport {
+
+ private final RuleContext ruleContext;
+ private final WatchOSVersion watchOSVersion;
+ private final ImmutableSet<Attribute> dependencyAttributes;
+ private final IntermediateArtifacts intermediateArtifacts;
+ private final String bundleName;
+ private final Artifact ipaArtifact;
+ private final Artifact watchApplicationBundle;
+ private final Attributes attributes;
+ private final XcodeProvider watchApplicationXcodeProvider;
+ private final ConfigurationDistinguisher configurationDistinguisher;
+
+ WatchExtensionSupport(RuleContext ruleContext,
+ WatchOSVersion watchOSVersion,
+ ImmutableSet<Attribute> dependencyAttributes,
+ IntermediateArtifacts intermediateArtifacts,
+ String bundleName,
+ Artifact ipaArtifact,
+ Artifact watchApplicationBundle,
+ XcodeProvider watchApplicationXcodeProvider,
+ ConfigurationDistinguisher configurationDistinguisher) {
+ this.ruleContext = ruleContext;
+ this.watchOSVersion = watchOSVersion;
+ this.dependencyAttributes = dependencyAttributes;
+ this.intermediateArtifacts = intermediateArtifacts;
+ this.bundleName = bundleName;
+ this.ipaArtifact = ipaArtifact;
+ this.attributes = new Attributes(ruleContext);
+ if (WatchUtils.isBuildingForWatchOS1Version(watchOSVersion)) {
+ checkNotNull(watchApplicationXcodeProvider);
+ checkNotNull(watchApplicationBundle);
+ }
+ this.watchApplicationXcodeProvider = watchApplicationXcodeProvider;
+ this.watchApplicationBundle = watchApplicationBundle;
+ this.configurationDistinguisher = configurationDistinguisher;
+ }
+
+ void createBundle(NestedSetBuilder<Artifact> filesToBuild,
+ ObjcProvider.Builder objcProviderBuilder, XcodeProvider.Builder xcodeProviderBuilder)
+ throws InterruptedException {
+ WatchUtils.addXcodeSettings(ruleContext, xcodeProviderBuilder);
+
+ registerWatchExtensionAutomaticPlistAction();
+
+ ObjcProvider objcProvider = objcProvider(objcProviderBuilder);
+
+ ImmutableSet<TargetDeviceFamily> families = attributes.families();
+
+ if (families.isEmpty()) {
+ ruleContext.attributeError(FAMILIES_ATTR, ReleaseBundling.INVALID_FAMILIES_ERROR);
+ }
+
+ ReleaseBundling.Builder releaseBundling = new ReleaseBundling.Builder()
+ .setIpaArtifact(ipaArtifact)
+ .setBundleId(attributes.bundleId())
+ .setProvisioningProfile(attributes.provisioningProfile())
+ .setProvisioningProfileAttributeName(WATCH_EXT_PROVISIONING_PROFILE_ATTR)
+ .setTargetDeviceFamilies(families)
+ .setIntermediateArtifacts(intermediateArtifacts)
+ .setInfoPlistsFromRule(attributes.infoPlists())
+ .addInfoplistInput(watchExtensionAutomaticPlist())
+ .setEntitlements(attributes.entitlements());
+
+ if (attributes.isBundleIdExplicitySpecified()) {
+ releaseBundling.setPrimaryBundleId(attributes.bundleId());
+ } else {
+ releaseBundling.setFallbackBundleId(attributes.bundleId());
+ }
+
+ ReleaseBundlingSupport releaseBundlingSupport =
+ new ReleaseBundlingSupport(
+ ruleContext,
+ objcProvider,
+ LinkedBinary.DEPENDENCIES_ONLY,
+ ReleaseBundlingSupport.EXTENSION_BUNDLE_DIR_FORMAT,
+ bundleName,
+ WatchUtils.determineMinimumOsVersion(
+ ObjcRuleClasses.objcConfiguration(ruleContext).getMinimumOs()),
+ releaseBundling.build());
+
+ releaseBundlingSupport
+ .registerActions(DsymOutputType.APP)
+ .addXcodeSettings(xcodeProviderBuilder)
+ .addFilesToBuild(filesToBuild, DsymOutputType.APP)
+ .validateResources()
+ .validateAttributes();
+
+ XcodeSupport xcodeSupport = new XcodeSupport(ruleContext)
+ .addFilesToBuild(filesToBuild)
+ .addXcodeSettings(xcodeProviderBuilder, objcProvider,
+ watchOSVersion.getExtensionXcodeProductType(),
+ ruleContext.getFragment(AppleConfiguration.class).getDependencySingleArchitecture(),
+ configurationDistinguisher)
+ .addDummySource(xcodeProviderBuilder);
+
+ for (Attribute attribute : dependencyAttributes) {
+ xcodeSupport.addDependencies(xcodeProviderBuilder, attribute);
+ }
+
+ if (WatchUtils.isBuildingForWatchOS1Version(watchOSVersion)) {
+ // Generate xcodeproj for watch OS 1 extension as the main target with watch application
+ // target as the dependency.
+ xcodeProviderBuilder.addPropagatedDependencies(
+ ImmutableList.of(watchApplicationXcodeProvider));
+ xcodeSupport.registerActions(xcodeProviderBuilder.build());
+ }
+ }
+
+ /**
+ * Registers an action to generate a plist containing entries required for watch extension that
+ * should be added to the merged plist.
+ */
+ private void registerWatchExtensionAutomaticPlistAction() {
+ List<String> uiRequiredDeviceCapabilities = ImmutableList.of("watch-companion");
+ NSDictionary watchExtensionAutomaticEntries = new NSDictionary();
+ watchExtensionAutomaticEntries.put("UIRequiredDeviceCapabilities",
+ NSObject.wrap(uiRequiredDeviceCapabilities.toArray()));
+
+ ruleContext.registerAction(
+ new FileWriteAction(
+ ruleContext.getActionOwner(),
+ watchExtensionAutomaticPlist(),
+ watchExtensionAutomaticEntries.toGnuStepASCIIPropertyList(),
+ /*makeExecutable=*/ false));
+ }
+
+ private Artifact watchExtensionAutomaticPlist() {
+ return ruleContext.getRelatedArtifact(
+ ruleContext.getUniqueDirectory("plists"), "-automatic-watchExtensionInfo.plist");
+ }
+
+ private ObjcProvider objcProvider(Builder objcProviderBuilder) {
+ // Add dependency providers.
+ for (Attribute attribute : dependencyAttributes) {
+ objcProviderBuilder.addTransitiveAndPropagate(
+ ruleContext.getPrerequisites(
+ attribute.getName(), attribute.getAccessMode(), ObjcProvider.class));
+ }
+
+ if (WatchUtils.isBuildingForWatchOS1Version(watchOSVersion)) {
+ // Expose the generated watch application bundle to the extension bundle.
+ objcProviderBuilder.add(MERGE_ZIP, watchApplicationBundle);
+ }
+
+ // Add resource files.
+ objcProviderBuilder.addAll(GENERAL_RESOURCE_FILE, attributes.resources())
+ .addAll(GENERAL_RESOURCE_FILE, attributes.strings())
+ .addAll(GENERAL_RESOURCE_DIR, ObjcCommon.xcodeStructuredResourceDirs(
+ attributes.structuredResources()))
+ .addAll(BUNDLE_FILE, BundleableFile.flattenedRawResourceFiles(attributes.resources()))
+ .addAll(
+ BUNDLE_FILE,
+ BundleableFile.structuredRawResourceFiles(attributes.structuredResources()))
+ .addAll(STRINGS, attributes.strings());
+
+ return objcProviderBuilder.build();
+ }
+
+ /**
+ * Rule attributes used for creating watch application bundle.
+ */
+ private static class Attributes {
+ private final RuleContext ruleContext;
+
+ private Attributes(RuleContext ruleContext) {
+ this.ruleContext = ruleContext;
+ }
+
+ /**
+ * Returns the value of the {@code families} attribute in a form
+ * that is more useful than a list of strings. Returns an empty
+ * set for any invalid {@code families} attribute value, including
+ * an empty list.
+ */
+ ImmutableSet<TargetDeviceFamily> families() {
+ List<String> rawFamilies = ruleContext.attributes().get(WATCH_EXT_FAMILIES_ATTR,
+ Type.STRING_LIST);
+ try {
+ return ImmutableSet.copyOf(TargetDeviceFamily.fromNamesInRule(rawFamilies));
+ } catch (InvalidFamilyNameException | RepeatedFamilyNameException e) {
+ return ImmutableSet.of();
+ }
+ }
+
+ @Nullable
+ Artifact provisioningProfile() {
+ Artifact explicitProvisioningProfile =
+ getPrerequisiteArtifact(WATCH_EXT_PROVISIONING_PROFILE_ATTR);
+ if (explicitProvisioningProfile != null) {
+ return explicitProvisioningProfile;
+ }
+ return getPrerequisiteArtifact(WATCH_EXT_DEFAULT_PROVISIONING_PROFILE_ATTR);
+ }
+
+ String bundleId() {
+ Preconditions.checkState(!Strings.isNullOrEmpty(
+ ruleContext.attributes().get(WATCH_EXT_BUNDLE_ID_ATTR, Type.STRING)),
+ "requires a bundle_id value");
+ return ruleContext.attributes().get(WATCH_EXT_BUNDLE_ID_ATTR, Type.STRING);
+ }
+
+ ImmutableList<Artifact> infoPlists() {
+ return getPrerequisiteArtifacts(WATCH_EXT_INFOPLISTS_ATTR);
+ }
+
+ ImmutableList<Artifact> strings() {
+ return getPrerequisiteArtifacts(WATCH_EXT_STRINGS_ATTR);
+ }
+
+ ImmutableList<Artifact> resources() {
+ return getPrerequisiteArtifacts(WATCH_EXT_RESOURCES_ATTR);
+ }
+
+ ImmutableList<Artifact> structuredResources() {
+ return getPrerequisiteArtifacts(WATCH_EXT_STRUCTURED_RESOURCES_ATTR);
+ }
+
+ @Nullable
+ Artifact entitlements() {
+ return getPrerequisiteArtifact(WATCH_EXT_ENTITLEMENTS_ATTR);
+ }
+
+ private boolean isBundleIdExplicitySpecified() {
+ return ruleContext.attributes().isAttributeValueExplicitlySpecified(WATCH_EXT_BUNDLE_ID_ATTR);
+ }
+
+ private ImmutableList<Artifact> getPrerequisiteArtifacts(String attribute) {
+ return ruleContext.getPrerequisiteArtifacts(attribute, Mode.TARGET).list();
+ }
+
+ @Nullable
+ private Artifact getPrerequisiteArtifact(String attribute) {
+ return ruleContext.getPrerequisiteArtifact(attribute, Mode.TARGET);
+ }
+ }
+
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/WatchUtils.java b/src/main/java/com/google/devtools/build/lib/rules/objc/WatchUtils.java
new file mode 100644
index 0000000000..cc3492f997
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/WatchUtils.java
@@ -0,0 +1,172 @@
+// Copyright 2016 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.ROOT_MERGE_ZIP;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Ordering;
+import com.google.devtools.build.lib.actions.Artifact;
+import com.google.devtools.build.lib.analysis.RuleContext;
+import com.google.devtools.build.lib.rules.apple.AppleConfiguration;
+import com.google.devtools.build.lib.rules.apple.DottedVersion;
+import com.google.devtools.build.xcode.xcodegen.proto.XcodeGenProtos.XcodeprojBuildSetting;
+
+/**
+ * Contains support methods for common processing and generating of watch extension and
+ * application bundles.
+ */
+final class WatchUtils {
+
+ /**
+ * Supported Apple watch OS versions.
+ */
+ enum WatchOSVersion {
+ OS1(XcodeProductType.WATCH_OS1_APPLICATION, XcodeProductType.WATCH_OS1_EXTENSION,
+ "WatchKitSupport");
+
+ private final XcodeProductType applicationXcodeProductType;
+ private final XcodeProductType extensionXcodeProductType;
+ private final String watchKitSupportDirName;
+
+ WatchOSVersion(XcodeProductType applicationXcodeProductType,
+ XcodeProductType extensionXcodeProductType,
+ String watchKitSupportDirName) {
+ this.applicationXcodeProductType = applicationXcodeProductType;
+ this.extensionXcodeProductType = extensionXcodeProductType;
+ this.watchKitSupportDirName = watchKitSupportDirName;
+ }
+
+ /**
+ * Returns the {@link XcodeProductType} to be used for the watch application's Xcode target.
+ */
+ XcodeProductType getApplicationXcodeProductType() {
+ return applicationXcodeProductType;
+ }
+
+ /**
+ * Returns the {@link XcodeProductType} to be used for the watch extension's Xcode target.
+ */
+ XcodeProductType getExtensionXcodeProductType() {
+ return extensionXcodeProductType;
+ }
+
+ /**
+ * Returns the name of the directory in the final iOS bundle which should contain the WatchKit
+ * support stub.
+ */
+ String getWatchKitSupportDirName() {
+ return watchKitSupportDirName;
+ }
+ }
+
+ @VisibleForTesting
+ static final String WATCH_KIT_STUB_PATH =
+ "${SDKROOT}/Library/Application\\ Support/WatchKit/WK";
+
+ // Apple only accepts watch extension and application starting at 8.2.
+ @VisibleForTesting
+ static final DottedVersion MINIMUM_OS_VERSION = DottedVersion.fromString("8.2");
+
+ /**
+ * Adds common xcode build settings required for watch bundles to the given xcode provider
+ * builder.
+ */
+ static void addXcodeSettings(RuleContext ruleContext,
+ XcodeProvider.Builder xcodeProviderBuilder) {
+ xcodeProviderBuilder.addMainTargetXcodeprojBuildSettings(xcodeSettings(ruleContext));
+ }
+
+ /**
+ * Watch Extension are not accepted by Apple below version 8.2. While applications built with a
+ * minimum iOS version of less than 8.2 may contain watch extension in their bundle, the
+ * extension itself needs to be built with 8.2 or higher. This logic overrides (if necessary)
+ * any flag-set minimum iOS version for extensions only so that this requirement is not
+ * violated.
+ */
+ static DottedVersion determineMinimumOsVersion(DottedVersion fromFlag) {
+ return Ordering.natural().max(fromFlag, MINIMUM_OS_VERSION);
+ }
+
+ static boolean isBuildingForWatchOS1Version(WatchOSVersion watchOSVersion) {
+ return watchOSVersion == WatchOSVersion.OS1;
+ }
+
+ /**
+ * Adds WatchKitSupport stub to the final ipa and exposes it to given @{link ObjcProvider.Builder}
+ * based on watch OS version.
+ *
+ * For example, for watch OS 1, the resulting ipa will have:
+ * Payload/TestApp.app
+ * WatchKitSupport
+ * WatchKitSupport/WK
+ */
+ static void registerActionsToAddWatchSupport(
+ RuleContext ruleContext, ObjcProvider.Builder objcProviderBuilder,
+ WatchOSVersion watchOSVersion) {
+ Artifact watchSupportZip = watchSupportZip(ruleContext);
+ String workingDirectory = watchSupportZip.getExecPathString()
+ .substring(0, watchSupportZip.getExecPathString().lastIndexOf('/'));
+ String watchKitSupportDirName = watchOSVersion.getWatchKitSupportDirName();
+ String watchKitSupportPath = workingDirectory + "/" + watchKitSupportDirName;
+
+ ImmutableList<String> command = ImmutableList.of(
+ // 1. Copy WK stub binary to watchKitSupportPath.
+ "mkdir -p " + watchKitSupportPath,
+ "&&",
+ String.format("cp %s %s", WATCH_KIT_STUB_PATH, watchKitSupportPath),
+ // 2. cd to working directory.
+ "&&",
+ "cd " + workingDirectory,
+ // 3. Zip watchSupport.
+ "&&",
+ String.format(
+ "/usr/bin/zip -q -r -0 %s %s",
+ watchSupportZip.getFilename(),
+ watchKitSupportDirName));
+
+ ruleContext.registerAction(ObjcRuleClasses.spawnAppleEnvActionBuilder(ruleContext,
+ ruleContext.getFragment(AppleConfiguration.class).getBundlingPlatform())
+ .setProgressMessage("Copying Watchkit support to app bundle")
+ .setShellCommand(ImmutableList.of("/bin/bash", "-c", Joiner.on(" ").join(command)))
+ .addOutput(watchSupportZip)
+ .build(ruleContext));
+
+ objcProviderBuilder.add(ROOT_MERGE_ZIP, watchSupportZip(ruleContext));
+ }
+
+ private static Artifact watchSupportZip(RuleContext ruleContext) {
+ return ruleContext.getRelatedArtifact(
+ ruleContext.getUniqueDirectory("_watch"), "/WatchKitSupport.zip");
+ }
+
+ private static Iterable<XcodeprojBuildSetting> xcodeSettings(RuleContext ruleContext) {
+ ImmutableList.Builder<XcodeprojBuildSetting> xcodeSettings = new ImmutableList.Builder<>();
+ xcodeSettings.add(
+ XcodeprojBuildSetting.newBuilder()
+ .setName("RESOURCES_TARGETED_DEVICE_FAMILY")
+ .setValue(TargetDeviceFamily.WATCH.getNameInRule())
+ .build());
+ xcodeSettings.add(
+ XcodeprojBuildSetting.newBuilder()
+ .setName("IPHONEOS_DEPLOYMENT_TARGET")
+ .setValue(determineMinimumOsVersion(
+ ObjcRuleClasses.objcConfiguration(ruleContext).getMinimumOs()).toString())
+ .build());
+ return xcodeSettings.build();
+ }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/XcodeProductType.java b/src/main/java/com/google/devtools/build/lib/rules/objc/XcodeProductType.java
index 61e87c0a7d..df912c3df3 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/XcodeProductType.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/XcodeProductType.java
@@ -24,7 +24,9 @@ enum XcodeProductType {
APPLICATION("com.apple.product-type.application"),
UNIT_TEST("com.apple.product-type.bundle.unit-test"),
EXTENSION("com.apple.product-type.app-extension"),
- FRAMEWORK("com.apple.product-type.framework");
+ FRAMEWORK("com.apple.product-type.framework"),
+ WATCH_OS1_APPLICATION("com.apple.product-type.application.watchapp"),
+ WATCH_OS1_EXTENSION("com.apple.product-type.watchkit-extension");
private final String identifier;
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
index baae5551e1..45ba704c3e 100644
--- 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
@@ -183,7 +183,8 @@ public final class XcodeProvider implements TransitiveInfoProvider {
// 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) {
+ 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);
@@ -489,7 +490,8 @@ public final class XcodeProvider implements TransitiveInfoProvider {
@VisibleForTesting
static final EnumSet<XcodeProductType> CAN_LINK_PRODUCT_TYPES = EnumSet.of(
XcodeProductType.APPLICATION, XcodeProductType.BUNDLE, XcodeProductType.UNIT_TEST,
- XcodeProductType.EXTENSION, XcodeProductType.FRAMEWORK);
+ 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.
@@ -606,11 +608,12 @@ public final class XcodeProvider implements TransitiveInfoProvider {
// 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)) {
+ if (hasSources || (dependency.productType == XcodeProductType.BUNDLE
+ || (dependency.productType == XcodeProductType.WATCH_OS1_APPLICATION))) {
String dependencyXcodeTargetName = dependency.dependencyXcodeTargetName();
dependencySet.add(DependencyControl.newBuilder()
- .setTargetLabel(dependencyXcodeTargetName)
- .build());
+ .setTargetLabel(dependencyXcodeTargetName)
+ .build());
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/XcodeSupport.java b/src/main/java/com/google/devtools/build/lib/rules/objc/XcodeSupport.java
index 4c1dcac8d8..e50f2da56e 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/objc/XcodeSupport.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/objc/XcodeSupport.java
@@ -25,6 +25,7 @@ import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.actions.BinaryFileWriteAction;
import com.google.devtools.build.lib.analysis.actions.SpawnAction;
import com.google.devtools.build.lib.analysis.actions.SymlinkAction;
+import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.packages.ImplicitOutputsFunction.SafeImplicitOutputsFunction;
import com.google.devtools.build.lib.rules.apple.AppleConfiguration;
@@ -56,12 +57,26 @@ public final class XcodeSupport {
fromTemplates("%{name}.xcodeproj/project.pbxproj");
private final RuleContext ruleContext;
+ private final IntermediateArtifacts intermediateArtifacts;
+ private final Label xcodeTargetLabel;
/**
* Creates a new xcode support for the given context.
*/
- XcodeSupport(RuleContext ruleContext) {
+ XcodeSupport(RuleContext ruleContext) {
+ this(ruleContext, ObjcRuleClasses.intermediateArtifacts(ruleContext), ruleContext.getLabel());
+ }
+
+ /**
+ * Creates a new xcode support for the given context and {@link IntermediateArtifacts} with given
+ * target label.
+ */
+ public XcodeSupport(
+ RuleContext ruleContext, IntermediateArtifacts intermediateArtifacts,
+ Label xcodeTargetLabel) {
this.ruleContext = ruleContext;
+ this.intermediateArtifacts = intermediateArtifacts;
+ this.xcodeTargetLabel = xcodeTargetLabel;
}
/**
@@ -83,9 +98,6 @@ public final class XcodeSupport {
* @return this xcode support
*/
XcodeSupport addDummySource(XcodeProvider.Builder xcodeProviderBuilder) {
- IntermediateArtifacts intermediateArtifacts =
- ObjcRuleClasses.intermediateArtifacts(ruleContext);
-
ruleContext.registerAction(new SymlinkAction(
ruleContext.getActionOwner(),
ruleContext.getPrerequisiteArtifact("$dummy_source", Mode.TARGET),
@@ -154,7 +166,7 @@ public final class XcodeSupport {
ObjcProvider objcProvider, XcodeProductType productType, String architecture,
ConfigurationDistinguisher configurationDistinguisher) {
xcodeProviderBuilder
- .setLabel(ruleContext.getLabel())
+ .setLabel(xcodeTargetLabel)
.setArchitecture(architecture)
.setConfigurationDistinguisher(configurationDistinguisher)
.setObjcProvider(objcProvider)
@@ -210,8 +222,7 @@ public final class XcodeSupport {
}
private void registerXcodegenActions(XcodeProvider.Project project) throws InterruptedException {
- Artifact controlFile =
- ObjcRuleClasses.intermediateArtifacts(ruleContext).pbxprojControlArtifact();
+ Artifact controlFile = intermediateArtifacts.pbxprojControlArtifact();
ruleContext.registerAction(new BinaryFileWriteAction(
ruleContext.getActionOwner(),