diff options
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/rules/objc')
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(), |