// 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. * * @deprecated The native bundling rules have been deprecated. This class will be removed in the * future. */ @Deprecated 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; DottedVersion minimumOsVersion = appPlatformType == PlatformType.IOS ? WatchUtils.determineMinimumIosVersion( appleConfiguration.getMinimumOsForPlatformType(PlatformType.IOS)) : appleConfiguration.getMinimumOsForPlatformType(PlatformType.WATCHOS); 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)))); AppleConfiguration appleConfiguration = ruleContext.getFragment(AppleConfiguration.class); ruleContext.registerAction( ObjcRuleClasses.spawnAppleEnvActionBuilder( appleConfiguration, appleConfiguration.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(); if (watchOSVersion != WatchOSVersion.OS1) { 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); } } }