| // 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_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.dd.plist.NSDictionary; |
| import com.dd.plist.NSObject; |
| 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.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.apple.AppleConfiguration.ConfigurationDistinguisher; |
| 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.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 java.util.List; |
| import javax.annotation.Nullable; |
| |
| /** |
| * Contains support methods to build WatchOS1 extension bundles - 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 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, |
| ImmutableSet<Attribute> dependencyAttributes, |
| IntermediateArtifacts intermediateArtifacts, |
| String bundleName, |
| Artifact ipaArtifact, |
| @Nullable Artifact watchApplicationBundle, |
| @Nullable XcodeProvider watchApplicationXcodeProvider, |
| ConfigurationDistinguisher configurationDistinguisher) { |
| this.ruleContext = ruleContext; |
| this.dependencyAttributes = dependencyAttributes; |
| this.intermediateArtifacts = intermediateArtifacts; |
| this.bundleName = bundleName; |
| this.ipaArtifact = ipaArtifact; |
| this.attributes = new Attributes(ruleContext); |
| this.watchApplicationXcodeProvider = checkNotNull(watchApplicationXcodeProvider); |
| this.watchApplicationBundle = checkNotNull(watchApplicationBundle); |
| this.configurationDistinguisher = configurationDistinguisher; |
| } |
| |
| void createBundle(NestedSetBuilder<Artifact> filesToBuild, |
| ObjcProvider.Builder exposedObjcProviderBuilder, XcodeProvider.Builder xcodeProviderBuilder) |
| throws InterruptedException { |
| |
| ObjcProvider releaseBundlingObjcProvider = releaseBundlingObjcProvider(); |
| |
| WatchUtils.addXcodeSettings(ruleContext, xcodeProviderBuilder); |
| |
| registerWatchExtensionAutomaticPlistAction(); |
| |
| 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()); |
| } |
| |
| AppleConfiguration appleConfiguration = ruleContext.getFragment(AppleConfiguration.class); |
| |
| ReleaseBundlingSupport releaseBundlingSupport = |
| new ReleaseBundlingSupport( |
| ruleContext, |
| releaseBundlingObjcProvider, |
| LinkedBinary.DEPENDENCIES_ONLY, |
| ReleaseBundlingSupport.EXTENSION_BUNDLE_DIR_FORMAT, |
| bundleName, |
| WatchUtils.determineMinimumIosVersion( |
| appleConfiguration.getMinimumOsForPlatformType(PlatformType.IOS)), |
| releaseBundling.build(), |
| appleConfiguration.getMultiArchPlatform(PlatformType.IOS)); |
| |
| releaseBundlingSupport.registerActions(DsymOutputType.APP); |
| releaseBundlingSupport.addXcodeSettings(xcodeProviderBuilder); |
| |
| releaseBundlingSupport |
| .addFilesToBuild(filesToBuild, Optional.of(DsymOutputType.APP)) |
| .validateResources() |
| .validateAttributes() |
| .addExportedDebugArtifacts(exposedObjcProviderBuilder, DsymOutputType.APP); |
| |
| XcodeSupport xcodeSupport = |
| new XcodeSupport(ruleContext) |
| .addFilesToBuild(filesToBuild) |
| .addXcodeSettings( |
| xcodeProviderBuilder, |
| releaseBundlingObjcProvider, |
| WatchOSVersion.OS1.getExtensionXcodeProductType(), |
| ruleContext |
| .getFragment(AppleConfiguration.class) |
| .getDependencySingleArchitecture(), |
| configurationDistinguisher) |
| .addDummySource(xcodeProviderBuilder); |
| |
| for (Attribute attribute : dependencyAttributes) { |
| xcodeSupport.addDependencies(xcodeProviderBuilder, attribute); |
| } |
| |
| // 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( |
| FileWriteAction.create( |
| ruleContext, |
| watchExtensionAutomaticPlist(), |
| watchExtensionAutomaticEntries.toGnuStepASCIIPropertyList(), |
| /*makeExecutable=*/ false)); |
| } |
| |
| private Artifact watchExtensionAutomaticPlist() { |
| return ruleContext.getRelatedArtifact( |
| ruleContext.getUniqueDirectory("plists"), "-automatic-watchExtensionInfo.plist"); |
| } |
| |
| private ObjcProvider releaseBundlingObjcProvider() { |
| ObjcProvider.Builder objcProviderBuilder = new ObjcProvider.Builder(); |
| // Add dependency providers. |
| for (Attribute attribute : dependencyAttributes) { |
| objcProviderBuilder.addTransitiveAndPropagate( |
| ruleContext.getPrerequisites( |
| attribute.getName(), attribute.getAccessMode(), ObjcProvider.class)); |
| } |
| |
| // 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( |
| AppleWatch1ExtensionRule.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); |
| } |
| } |
| } |