| // 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.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.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.Optional; |
| 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.AppleConfiguration.ConfigurationDistinguisher; |
| import com.google.devtools.build.lib.rules.apple.DottedVersion; |
| import com.google.devtools.build.lib.rules.apple.Platform; |
| import com.google.devtools.build.lib.rules.apple.Platform.PlatformType; |
| import com.google.devtools.build.lib.rules.objc.ReleaseBundlingSupport.LinkedBinary; |
| 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; |
| |
| /** |
| * @param ruleContext the current rule context |
| * @param watchOSVersion the version of watchOS for which to create an application bundle |
| * @param dependencyAttributes attributes on the current rule context to obtain transitive |
| * resources from |
| * @param intermediateArtifacts the utility object to obtain namespacing for intermediate bundling |
| * artifacts |
| * @param bundleName the name of the bundle |
| * @param ipaArtifact the output ipa created by this application bundling |
| * @param artifactPrefix the string prefix to prepend to bundling artifacts for the application -- |
| * this prevents intermediate artifacts under this same rule context (such as watch extension |
| * bundling) from conflicting |
| */ |
| WatchApplicationSupport( |
| RuleContext ruleContext, |
| WatchOSVersion watchOSVersion, |
| ImmutableSet<Attribute> dependencyAttributes, |
| IntermediateArtifacts intermediateArtifacts, |
| String bundleName, |
| Artifact ipaArtifact, |
| String artifactPrefix) { |
| 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); |
| } |
| |
| /** |
| * Registers actions to create a watch application bundle. |
| * |
| * @param innerBundleZips any zip files to be unzipped and merged into the application bundle |
| * @param filesToBuild files to build for the rule; the watchOS application .ipa is added to this |
| * set |
| */ |
| void createBundle( |
| Iterable<Artifact> innerBundleZips, |
| NestedSetBuilder<Artifact> filesToBuild) |
| throws InterruptedException { |
| |
| ObjcProvider objcProvider = objcProvider(innerBundleZips); |
| |
| createBundle( |
| Optional.<XcodeProvider.Builder>absent(), |
| objcProvider, |
| filesToBuild); |
| } |
| |
| /** |
| * Registers actions to create a watch application bundle and xcode project. |
| * |
| * @param xcodeProviderBuilder provider builder which xcode project generation information is |
| * added to (for later consumption by depending rules) |
| * @param innerBundleZips any zip files to be unzipped and merged into the application bundle |
| * @param filesToBuild files to build for the rule; the watchOS application .ipa is added to this |
| * set |
| */ |
| void createBundleAndXcodeproj( |
| XcodeProvider.Builder xcodeProviderBuilder, |
| Iterable<Artifact> innerBundleZips, |
| NestedSetBuilder<Artifact> filesToBuild) |
| throws InterruptedException { |
| ObjcProvider objcProvider = objcProvider(innerBundleZips); |
| |
| createBundle( |
| Optional.of(xcodeProviderBuilder), objcProvider, filesToBuild); |
| |
| // Add common watch settings. |
| WatchUtils.addXcodeSettings(ruleContext, xcodeProviderBuilder); |
| |
| // Add watch application specific xcode settings. |
| addXcodeSettings(xcodeProviderBuilder); |
| |
| XcodeSupport xcodeSupport = |
| new XcodeSupport(ruleContext, intermediateArtifacts, labelForWatchApplication()) |
| .addXcodeSettings( |
| xcodeProviderBuilder, |
| objcProvider, |
| watchOSVersion.getApplicationXcodeProductType(), |
| ruleContext.getFragment(AppleConfiguration.class).getIosCpu(), |
| ConfigurationDistinguisher.WATCH_OS1_EXTENSION); |
| |
| for (Attribute attribute : dependencyAttributes) { |
| xcodeSupport.addDependencies(xcodeProviderBuilder, attribute); |
| } |
| } |
| |
| private void createBundle( |
| Optional<XcodeProvider.Builder> xcodeProviderBuilder, |
| ObjcProvider depsObjcProvider, |
| NestedSetBuilder<Artifact> filesToBuild) |
| throws InterruptedException { |
| registerActions(); |
| |
| 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()); |
| } |
| |
| AppleConfiguration appleConfiguration = ruleContext.getFragment(AppleConfiguration.class); |
| |
| PlatformType appPlatformType = watchOSVersion == WatchOSVersion.OS1 |
| ? PlatformType.IOS : PlatformType.WATCHOS; |
| Platform appPlatform = appleConfiguration.getMultiArchPlatform(appPlatformType); |
| DottedVersion minimumOsVersion = appPlatformType == PlatformType.IOS |
| ? WatchUtils.determineMinimumIosVersion( |
| ObjcRuleClasses.objcConfiguration(ruleContext).getMinimumOs()) |
| : appleConfiguration.getSdkVersionForPlatform(appPlatform); |
| |
| ReleaseBundlingSupport releaseBundlingSupport = |
| new ReleaseBundlingSupport( |
| ruleContext, |
| depsObjcProvider, |
| LinkedBinary.DEPENDENCIES_ONLY, |
| watchOSVersion.getApplicationBundleDirFormat(), |
| bundleName, |
| minimumOsVersion, |
| releaseBundling.build(), |
| appleConfiguration.getMultiArchPlatform(appPlatformType)) |
| .registerActions(DsymOutputType.APP); |
| |
| if (xcodeProviderBuilder.isPresent()) { |
| releaseBundlingSupport.addXcodeSettings(xcodeProviderBuilder.get()); |
| } |
| |
| releaseBundlingSupport |
| .addFilesToBuild(filesToBuild, Optional.<DsymOutputType>absent()) |
| .validateResources() |
| .validateAttributes(); |
| } |
| |
| /** |
| * Returns the {@link TargetDeviceFamily} that the watch application bundle is targeting. This |
| * is always {@code TargetDeviceFamily.WATCH}, except for WatchOS1, which has the following |
| * special rules: |
| * |
| * 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).getMultiArchPlatform(PlatformType.IOS); |
| if (watchOSVersion != WatchOSVersion.OS1 || 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 -f %s %s", WatchUtils.WATCH_KIT_STUB_PATH, watchKitStubBinaryPath), |
| "&&", |
| // 2. Copy WK stub as bundle resource. |
| "mkdir -p " + watchKitStubResourcePath, |
| "&&", |
| String.format("cp -f %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) |
| .getMultiArchPlatform(PlatformType.WATCHOS)) |
| .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(Iterable<Artifact> innerBundleZips) { |
| ObjcProvider.Builder objcProviderBuilder = new ObjcProvider.Builder(); |
| objcProviderBuilder.addAll(MERGE_ZIP, innerBundleZips); |
| |
| // 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); |
| } |
| } |
| } |