| // Copyright 2015 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.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.DEBUG_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.dd.plist.NSArray; |
| import com.dd.plist.NSDictionary; |
| import com.dd.plist.NSObject; |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.base.Joiner; |
| import com.google.common.base.Optional; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Iterables; |
| import com.google.devtools.build.lib.actions.Artifact; |
| import com.google.devtools.build.lib.analysis.BuildInfo; |
| import com.google.devtools.build.lib.analysis.FilesToRunProvider; |
| import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode; |
| import com.google.devtools.build.lib.analysis.RuleContext; |
| import com.google.devtools.build.lib.analysis.Runfiles; |
| import com.google.devtools.build.lib.analysis.RunfilesSupport; |
| import com.google.devtools.build.lib.analysis.TransitiveInfoCollection; |
| import com.google.devtools.build.lib.analysis.actions.BinaryFileWriteAction; |
| import com.google.devtools.build.lib.analysis.actions.CustomCommandLine; |
| import com.google.devtools.build.lib.analysis.actions.FileWriteAction; |
| import com.google.devtools.build.lib.analysis.actions.SpawnAction; |
| import com.google.devtools.build.lib.analysis.actions.SymlinkAction; |
| import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction; |
| import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction.Substitution; |
| import com.google.devtools.build.lib.analysis.config.BuildOptions; |
| import com.google.devtools.build.lib.collect.nestedset.NestedSet; |
| import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; |
| import com.google.devtools.build.lib.collect.nestedset.Order; |
| import com.google.devtools.build.lib.packages.Attribute.SplitTransition; |
| import com.google.devtools.build.lib.packages.BuildType; |
| import com.google.devtools.build.lib.packages.ImplicitOutputsFunction.SafeImplicitOutputsFunction; |
| import com.google.devtools.build.lib.rules.apple.AppleCommandLineOptions; |
| 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.BundleSupport.ExtraActoolArgs; |
| import com.google.devtools.build.lib.rules.objc.Bundling.Builder; |
| import com.google.devtools.build.lib.shell.ShellUtils; |
| import com.google.devtools.build.xcode.xcodegen.proto.XcodeGenProtos.XcodeprojBuildSetting; |
| import java.util.List; |
| import java.util.Map.Entry; |
| import javax.annotation.Nullable; |
| |
| /** |
| * Support for released bundles, such as an application or extension. Such a bundle is generally |
| * composed of a top-level {@link BundleSupport bundle}, potentially signed, as well as some debug |
| * information, if {@link ObjcConfiguration#generateDsym() requested}. |
| * |
| * <p>Contains actions, validation logic and provider value generation. |
| * |
| * <p>Methods on this class can be called in any order without impacting the result. |
| */ |
| public final class ReleaseBundlingSupport { |
| |
| /** |
| * Template for the containing application folder. |
| */ |
| public static final SafeImplicitOutputsFunction IPA = fromTemplates("%{name}.ipa"); |
| |
| @VisibleForTesting |
| static final String NO_ASSET_CATALOG_ERROR_FORMAT = |
| "a value was specified (%s), but this app does not have any asset catalogs"; |
| @VisibleForTesting |
| static final String DEVICE_NO_PROVISIONING_PROFILE = |
| "Provisioning profile must be set for device build"; |
| |
| @VisibleForTesting |
| static final String PROVISIONING_PROFILE_BUNDLE_FILE = "embedded.mobileprovision"; |
| @VisibleForTesting |
| static final String APP_BUNDLE_DIR_FORMAT = "Payload/%s.app"; |
| @VisibleForTesting |
| static final String XCTEST_BUNDLE_DIR_FORMAT = "Payload/%s.xctest"; |
| @VisibleForTesting |
| static final String EXTENSION_BUNDLE_DIR_FORMAT = "PlugIns/%s.appex"; |
| @VisibleForTesting |
| static final String FRAMEWORK_BUNDLE_DIR_FORMAT = "Frameworks/%s.framework"; |
| |
| /** |
| * Command string for "sed" that tries to extract the application version number from a larger |
| * string. For example, from "foo_1.2.3_RC00" this would extract "1.2.3". This regex looks for |
| * versions of the format "x.y" or "x.y.z", which may be preceded and/or followed by other text, |
| * such as a project name or release candidate number. |
| * |
| * <p>This command also preserves double quotes around the string, if any. |
| */ |
| private static final String EXTRACT_VERSION_NUMBER_SED_COMMAND = |
| "s#\\(\"\\)\\{0,1\\}\\(.*_\\)\\{0,1\\}\\([0-9][0-9]*\\(\\.[0-9][0-9]*\\)\\{1,2\\}\\)" |
| + "\\(_[^\"]*\\)\\{0,1\\}\\(\"\\)\\{0,1\\}#\\1\\3\\6#"; |
| |
| private final Attributes attributes; |
| private final BundleSupport bundleSupport; |
| private final RuleContext ruleContext; |
| private final Bundling bundling; |
| private final ObjcProvider objcProvider; |
| private final LinkedBinary linkedBinary; |
| private final IntermediateArtifacts intermediateArtifacts; |
| private final ReleaseBundling releaseBundling; |
| private final Platform platform; |
| |
| /** |
| * Indicator as to whether this rule generates a binary directly or whether only dependencies |
| * should be considered. |
| */ |
| enum LinkedBinary { |
| /** |
| * This rule generates its own binary which should be included as well as dependency-generated |
| * binaries. |
| */ |
| LOCAL_AND_DEPENDENCIES, |
| |
| /** |
| * This rule does not generate its own binary, only consider binaries from dependencies. |
| */ |
| DEPENDENCIES_ONLY |
| } |
| |
| /** |
| * Creates a new release bundling support within the given rule context. |
| * |
| * @param ruleContext context for the application-generating rule |
| * @param objcProvider provider containing all dependencies' information as well as some of this |
| * rule's |
| * @param linkedBinary whether to look for a linked binary from this rule and dependencies or just |
| * the latter |
| * @param bundleDirFormat format string representing the bundle's directory with a single |
| * placeholder for the target name (e.g. {@code "Payload/%s.app"}) |
| * @param bundleName name of the bundle, used with bundleDirFormat |
| * @param bundleMinimumOsVersion the minimum OS version this bundle's plist should be generated |
| * for (<b>not</b> the minimum OS version its binary is compiled with, that needs to be set |
| * through the configuration) |
| * @param releaseBundling the {@link ReleaseBundling} containing information for creating a |
| * releaseable bundle. |
| * @param platform the platform that bundles will be created for using this support |
| */ |
| ReleaseBundlingSupport( |
| RuleContext ruleContext, |
| ObjcProvider objcProvider, |
| LinkedBinary linkedBinary, |
| String bundleDirFormat, |
| String bundleName, |
| DottedVersion bundleMinimumOsVersion, |
| ReleaseBundling releaseBundling, |
| Platform platform) { |
| this.platform = platform; |
| this.linkedBinary = linkedBinary; |
| this.attributes = new Attributes(ruleContext); |
| this.ruleContext = ruleContext; |
| this.objcProvider = objcProvider; |
| this.releaseBundling = releaseBundling; |
| this.intermediateArtifacts = releaseBundling.getIntermediateArtifacts(); |
| this.bundling = bundling(ruleContext, objcProvider, bundleDirFormat, bundleName, |
| bundleMinimumOsVersion); |
| // TODO(cparsons): Take the rule configuration as a param instead of inferring. |
| bundleSupport = new BundleSupport(ruleContext, |
| ruleContext.getFragment(AppleConfiguration.class), platform, |
| bundling, extraActoolArgs()); |
| } |
| |
| /** |
| * Creates a new application support within the given rule context. |
| * |
| * @param ruleContext context for the application-generating rule |
| * @param objcProvider provider containing all dependencies' information as well as some of this |
| * rule's |
| * @param linkedBinary whether to look for a linked binary from this rule and dependencies or just |
| * the latter |
| * @param bundleDirFormat format string representing the bundle's directory with a single |
| * placeholder for the target name (e.g. {@code "Payload/%s.app"}) |
| * @param bundleName name of the bundle, used with bundleDirFormat |
| * @param platform the platform that bundles will be created for using this support |
| */ |
| ReleaseBundlingSupport( |
| RuleContext ruleContext, |
| ObjcProvider objcProvider, |
| LinkedBinary linkedBinary, |
| String bundleDirFormat, |
| String bundleName, |
| DottedVersion bundleMinimumOsVersion, |
| Platform platform) throws InterruptedException { |
| this( |
| ruleContext, |
| objcProvider, |
| linkedBinary, |
| bundleDirFormat, |
| bundleName, |
| bundleMinimumOsVersion, |
| ReleaseBundling.releaseBundling(ruleContext), |
| platform); |
| } |
| |
| /** |
| * Creates a new application support within the given rule context. |
| * |
| * {@code bundleName} defaults to label name |
| * |
| * @param ruleContext context for the application-generating rule |
| * @param objcProvider provider containing all dependencies' information as well as some of this |
| * rule's |
| * @param linkedBinary whether to look for a linked binary from this rule and dependencies or just |
| * the latter |
| * @param bundleDirFormat format string representing the bundle's directory with a single |
| * placeholder for the target name (e.g. {@code "Payload/%s.app"}) |
| * @param bundleMinimumOsVersion the minimum OS version this bundle's plist should be generated |
| * for (<b>not</b> the minimum OS version its binary is compiled with, that needs to be set |
| * through the configuration) |
| * @param platform the platform that bundles will be created for using this support |
| * @throws InterruptedException |
| */ |
| ReleaseBundlingSupport( |
| RuleContext ruleContext, |
| ObjcProvider objcProvider, |
| LinkedBinary linkedBinary, |
| String bundleDirFormat, |
| DottedVersion bundleMinimumOsVersion, |
| Platform platform) throws InterruptedException { |
| this(ruleContext, objcProvider, linkedBinary, bundleDirFormat, ruleContext.getLabel().getName(), |
| bundleMinimumOsVersion, platform); |
| } |
| |
| /** |
| * Validates application-related attributes set on this rule and registers any errors with the |
| * rule context. |
| * |
| * @return this application support |
| */ |
| ReleaseBundlingSupport validateAttributes() { |
| // No asset catalogs. That means you cannot specify app_icon or |
| // launch_image attributes, since they must not exist. However, we don't |
| // run actool in this case, which means it does not do validity checks, |
| // and we MUST raise our own error somehow... |
| if (!objcProvider.hasAssetCatalogs()) { |
| if (releaseBundling.getAppIcon() != null) { |
| ruleContext.attributeError(APP_ICON_ATTR, |
| String.format(NO_ASSET_CATALOG_ERROR_FORMAT, releaseBundling.getAppIcon())); |
| } |
| if (releaseBundling.getLaunchImage() != null) { |
| ruleContext.attributeError("launch_image", |
| String.format(NO_ASSET_CATALOG_ERROR_FORMAT, releaseBundling.getLaunchImage())); |
| } |
| } |
| |
| if (releaseBundling.getProvisioningProfile() == null && platform.isDevice()) { |
| ruleContext.attributeError(releaseBundling.getProvisioningProfileAttrName(), |
| DEVICE_NO_PROVISIONING_PROFILE); |
| } |
| |
| return this; |
| } |
| |
| /** |
| * Validates that resources defined in this rule and its dependencies and written to this bundle |
| * are legal. |
| * |
| * @return this release bundling support |
| */ |
| ReleaseBundlingSupport validateResources() { |
| bundleSupport |
| .validatePlatform() |
| .validateResources(objcProvider); |
| return this; |
| } |
| |
| /** |
| * Registers actions required to build an application. This includes any |
| * {@link BundleSupport#registerActions(ObjcProvider) bundle} and bundle merge actions, signing |
| * this application if appropriate and combining several single-architecture binaries into one |
| * multi-architecture binary. |
| * |
| * @param dsymOutputType the file type of the dSYM bundle to be generated |
| * |
| * @return this application support |
| */ |
| ReleaseBundlingSupport registerActions(DsymOutputType dsymOutputType) |
| throws InterruptedException { |
| bundleSupport.registerActions(objcProvider); |
| |
| Artifact combinedArchBinary = prepareCombinedArchitecturesArtifact(); |
| registerCopyDsymFilesAction(dsymOutputType); |
| registerCopyDsymPlistAction(dsymOutputType); |
| registerCopyLinkmapFilesAction(); |
| registerSwiftStdlibActionsIfNecessary(combinedArchBinary); |
| registerSwiftSupportActionsIfNecessary(combinedArchBinary); |
| |
| registerEmbedLabelPlistAction(); |
| registerEnvironmentPlistAction(); |
| registerAutomaticPlistAction(); |
| |
| if (releaseBundling.getLaunchStoryboard() != null) { |
| registerLaunchStoryboardPlistAction(); |
| } |
| |
| registerBundleMergeActions(); |
| registerPostProcessAndSigningActions(); |
| |
| return this; |
| } |
| |
| private void registerEmbedLabelPlistAction() throws InterruptedException { |
| Artifact buildInfo = Iterables.getOnlyElement( |
| ruleContext.getBuildInfo(ObjcBuildInfoFactory.KEY)); |
| String generatedVersionPlistPath = getGeneratedVersionPlist().getShellEscapedExecPathString(); |
| String shellCommand = "VERSION=\"$(" |
| + "grep \"^" + BuildInfo.BUILD_EMBED_LABEL + "\" " |
| + buildInfo.getShellEscapedExecPathString() |
| + " | cut -d' ' -f2- | sed -e '" + EXTRACT_VERSION_NUMBER_SED_COMMAND + "' | " |
| + "sed -e 's#\"#\\\"#g')\" && " |
| + "cat >" + generatedVersionPlistPath + " <<EOF\n" |
| + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" |
| + "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" " |
| + "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n" |
| + "<plist version=\"1.0\">\n" |
| + "<dict>\n" |
| + "EOF\n" |
| |
| + "if [[ -n \"${VERSION}\" ]]; then\n" |
| + " for KEY in CFBundleVersion CFBundleShortVersionString; do\n" |
| + " echo \" <key>${KEY}</key>\n\" >> " |
| + generatedVersionPlistPath + "\n" |
| + " echo \" <string>${VERSION}</string>\n\" >> " |
| + generatedVersionPlistPath + "\n" |
| + " done\n" |
| + "fi\n" |
| |
| + "cat >>" + generatedVersionPlistPath + " <<EOF\n" |
| + "</dict>\n" |
| + "</plist>\n" |
| + "EOF\n"; |
| ruleContext.registerAction(new SpawnAction.Builder() |
| .setMnemonic("ObjcVersionPlist") |
| .setShellCommand(shellCommand) |
| .addInput(buildInfo) |
| .addOutput(getGeneratedVersionPlist()) |
| .build(ruleContext)); |
| } |
| |
| private void registerLaunchStoryboardPlistAction() { |
| String launchStoryboard = releaseBundling.getLaunchStoryboard().getFilename(); |
| String launchStoryboardName = launchStoryboard.substring(0, launchStoryboard.lastIndexOf('.')); |
| NSDictionary result = new NSDictionary(); |
| result.put("UILaunchStoryboardName", launchStoryboardName); |
| String contents = result.toGnuStepASCIIPropertyList(); |
| ruleContext.registerAction( |
| FileWriteAction.create(ruleContext, getLaunchStoryboardPlist(), contents, false)); |
| } |
| |
| private void registerEnvironmentPlistAction() { |
| AppleConfiguration configuration = ruleContext.getFragment(AppleConfiguration.class); |
| // Generates a .plist that contains environment values (such as the SDK used to build, the Xcode |
| // version, etc), which are parsed from various .plist files of the OS, namely Xcodes' and |
| // Platforms' plists. |
| // The resulting file is meant to be merged with the final bundle. |
| String platformWithVersion = |
| String.format( |
| "%s%s", |
| platform.getLowerCaseNameInPlist(), |
| configuration.getSdkVersionForPlatform(platform)); |
| ruleContext.registerAction( |
| ObjcRuleClasses.spawnAppleEnvActionBuilder(configuration, platform) |
| .setMnemonic("EnvironmentPlist") |
| .setExecutable(attributes.environmentPlist()) |
| .addArguments("--platform", platformWithVersion) |
| .addArguments("--output", getGeneratedEnvironmentPlist().getExecPathString()) |
| .addOutput(getGeneratedEnvironmentPlist()) |
| .build(ruleContext)); |
| } |
| |
| private void registerAutomaticPlistAction() { |
| ruleContext.registerAction( |
| FileWriteAction.create( |
| ruleContext, |
| getGeneratedAutomaticPlist(), |
| automaticEntries().toGnuStepASCIIPropertyList(), |
| /*makeExecutable=*/ false)); |
| } |
| |
| /** |
| * Returns a map containing entries that should be added to the merged plist. These are usually |
| * generated by Xcode automatically during the build process. |
| */ |
| private NSDictionary automaticEntries() { |
| List<Integer> uiDeviceFamily = |
| TargetDeviceFamily.UI_DEVICE_FAMILY_VALUES.get(bundleSupport.targetDeviceFamilies()); |
| AppleConfiguration appleConfiguration = ruleContext.getFragment(AppleConfiguration.class); |
| |
| NSDictionary result = new NSDictionary(); |
| |
| if (uiDeviceFamily != null) { |
| result.put("UIDeviceFamily", uiDeviceFamily.toArray()); |
| } |
| result.put("DTPlatformName", platform.getLowerCaseNameInPlist()); |
| result.put( |
| "DTSDKName", |
| platform.getLowerCaseNameInPlist() + appleConfiguration.getSdkVersionForPlatform(platform)); |
| result.put("CFBundleSupportedPlatforms", new NSArray(NSObject.wrap(platform.getNameInPlist()))); |
| result.put("MinimumOSVersion", bundling.getMinimumOsVersion().toString()); |
| |
| return result; |
| } |
| |
| /** |
| * Registers all actions necessary to create a processed and signed IPA from the initial merged |
| * IPA. |
| * |
| * <p>Includes user-provided actions to process IPA contents (via {@code ipa_post_processor}), |
| * and signing actions if the IPA is being built for device architectures. If signing is necessary |
| * also includes entitlements generation and processing actions. |
| * |
| * <p>Note that multiple "actions" on the IPA contents may be run in a single blaze action to |
| * avoid excessive zipping/unzipping of IPA contents. |
| */ |
| private void registerPostProcessAndSigningActions() { |
| Artifact processedIpa = releaseBundling.getIpaArtifact(); |
| Artifact unprocessedIpa = intermediateArtifacts.unprocessedIpa(); |
| |
| NestedSetBuilder<Artifact> inputs = |
| NestedSetBuilder.<Artifact>stableOrder().add(unprocessedIpa); |
| |
| String actionCommandLine = |
| "set -e && " |
| + "t=$(mktemp -d \"${TMPDIR:-/tmp}/signing_intermediate.XXXXXX\") && " |
| + "trap \"rm -rf ${t}\" EXIT && " |
| // Get an absolute path since we need to cd into the temp directory for zip. |
| + "signed_ipa=${PWD}/" |
| + processedIpa.getShellEscapedExecPathString() |
| + " && " |
| + "/usr/bin/unzip -qq " |
| + unprocessedIpa.getShellEscapedExecPathString() |
| + " -d ${t} && "; |
| |
| FilesToRunProvider processor = attributes.ipaPostProcessor(); |
| if (processor != null) { |
| actionCommandLine += processor.getExecutable().getShellEscapedExecPathString() + " ${t} && "; |
| } |
| |
| if (platform.isDevice()) { |
| actionCommandLine += deviceSigningCommandLine(); |
| |
| registerEntitlementsActions(); |
| inputs.add(releaseBundling.getProvisioningProfile()) |
| .add(intermediateArtifacts.entitlements()); |
| } else { |
| actionCommandLine += simulatorSigningCommandLine(); |
| } |
| |
| actionCommandLine += "cd ${t} && /usr/bin/zip -q -r \"${signed_ipa}\" ."; |
| |
| AppleConfiguration appleConfiguration = ruleContext.getFragment(AppleConfiguration.class); |
| SpawnAction.Builder processAction = |
| ObjcRuleClasses.spawnBashOnDarwinActionBuilder(actionCommandLine) |
| .setEnvironment(ObjcRuleClasses.appleToolchainEnvironment(appleConfiguration, platform)) |
| .setMnemonic("ObjcProcessIpa") |
| .setProgressMessage("Processing iOS IPA: " + ruleContext.getLabel()) |
| .disableSandboxing() |
| .addTransitiveInputs(inputs.build()) |
| .addOutput(processedIpa); |
| |
| if (processor != null) { |
| processAction.addTool(processor); |
| } |
| |
| ruleContext.registerAction(processAction.build(ruleContext)); |
| } |
| |
| private String deviceSigningCommandLine() { |
| StringBuilder codesignCommandLineBuilder = new StringBuilder(); |
| for (String dir : getDirsToSign()) { |
| codesignCommandLineBuilder.append(deviceCodesignCommand("${t}/" + dir)).append(" && "); |
| } |
| return codesignCommandLineBuilder.toString(); |
| } |
| |
| private String simulatorSigningCommandLine() { |
| StringBuilder codesignCommandLineBuilder = new StringBuilder(); |
| for (String dir : getDirsToSign()) { |
| codesignCommandLineBuilder |
| .append("/usr/bin/codesign --force --sign \"-\" ${t}/") |
| .append(dir) |
| .append(" && "); |
| } |
| return codesignCommandLineBuilder.toString(); |
| } |
| |
| private ImmutableList<String> getDirsToSign() { |
| // The order here is important. The innermost code must singed first. |
| ImmutableList.Builder<String> dirsToSign = new ImmutableList.Builder<>(); |
| String bundleDir = ShellUtils.shellEscape(bundling.getBundleDir()); |
| |
| // Explicitly sign the frameworks (raw .dylib files and .framework directories in Frameworks/). |
| // Unfortunately the --deep option on codesign doesn't do this automatically. |
| if (objcProvider.is(USES_SWIFT) |
| || !objcProvider.get(ObjcProvider.DYNAMIC_FRAMEWORK_FILE).isEmpty()) { |
| dirsToSign.add(bundleDir + "/Frameworks/*"); |
| } |
| dirsToSign.add(bundleDir); |
| |
| return dirsToSign.build(); |
| } |
| |
| /** |
| * Creates entitlement actions such that an entitlements file is generated in |
| * {@link IntermediateArtifacts#entitlements()} which can be used for signing in this bundle. |
| * |
| * <p>Entitlements are generated based on a plist-format entitlements file passed to this bundle's |
| * {@code entitlements} attribute or, if that is not set, entitlements extracted from the provided |
| * mobile provisioning profile. The team prefix is extracted from the provisioning profile and |
| * the following substitutions performed (assuming the prefix extracted was {@code PREFIX}): |
| * <ol> |
| * <li>"PREFIX.*" -> "PREFIX.BUNDLE_ID" (where BUNDLE_ID is this bundle's id) |
| * <li>"$(AppIdentifierPrefix)" -> "PREFIX." |
| * <li>"$(CFBundleIdentifier)" -> "BUNDLE_ID" (where BUNDLE_ID is this bundle's id) |
| * </ol> |
| * |
| * <p>Finally, if an entitlements file was provided via {@code --extra_entitlements} it is merged |
| * into the substituted entitlements. |
| */ |
| private void registerEntitlementsActions() { |
| Artifact teamPrefixFile = |
| intermediateArtifacts.appendExtensionForEntitlementArtifact(".team_prefix_file"); |
| registerExtractTeamPrefixAction(teamPrefixFile); |
| |
| Artifact entitlementsNeedingSubstitution = releaseBundling.getEntitlements(); |
| if (entitlementsNeedingSubstitution == null) { |
| entitlementsNeedingSubstitution = |
| intermediateArtifacts.appendExtensionForEntitlementArtifact( |
| ".entitlements_with_variables"); |
| registerExtractEntitlementsAction(entitlementsNeedingSubstitution); |
| } |
| |
| Artifact substitutedEntitlements = intermediateArtifacts.entitlements(); |
| if (attributes.extraEntitlements() != null || includeDebugEntitlements()) { |
| substitutedEntitlements = |
| intermediateArtifacts.appendExtensionForEntitlementArtifact(".substituted"); |
| |
| NestedSetBuilder<Artifact> entitlements = |
| NestedSetBuilder.<Artifact>stableOrder().add(substitutedEntitlements); |
| if (attributes.extraEntitlements() != null) { |
| entitlements.add(attributes.extraEntitlements()); |
| } |
| if (includeDebugEntitlements()) { |
| entitlements.add(attributes.deviceDebugEntitlements()); |
| } |
| |
| registerMergeEntitlementsAction(entitlements.build()); |
| } |
| |
| registerEntitlementsVariableSubstitutionAction( |
| entitlementsNeedingSubstitution, teamPrefixFile, substitutedEntitlements); |
| } |
| |
| private boolean includeDebugEntitlements() { |
| return attributes.deviceDebugEntitlements() != null |
| && ObjcRuleClasses.objcConfiguration(ruleContext).useDeviceDebugEntitlements(); |
| } |
| |
| private void registerMergeEntitlementsAction(NestedSet<Artifact> entitlements) { |
| PlMergeControlBytes controlBytes = |
| PlMergeControlBytes.fromPlists( |
| entitlements, |
| intermediateArtifacts.entitlements(), |
| PlMergeControlBytes.OutputFormat.XML); |
| |
| Artifact plMergeControlArtifact = ObjcRuleClasses.artifactByAppendingToBaseName(ruleContext, |
| artifactName(".merge-entitlements-control")); |
| |
| ruleContext.registerAction( |
| new BinaryFileWriteAction( |
| ruleContext.getActionOwner(), |
| plMergeControlArtifact, |
| controlBytes, |
| /*makeExecutable=*/ false)); |
| |
| ruleContext.registerAction( |
| new SpawnAction.Builder() |
| .setMnemonic("MergeEntitlementsFiles") |
| .setExecutable(attributes.plmerge()) |
| .addArgument("--control") |
| .addInputArgument(plMergeControlArtifact) |
| .addTransitiveInputs(entitlements) |
| .addOutput(intermediateArtifacts.entitlements()) |
| .build(ruleContext)); |
| } |
| |
| /** |
| * Adds bundle- and application-related settings to the given Xcode provider builder. |
| * |
| * @return this application support |
| */ |
| ReleaseBundlingSupport addXcodeSettings(XcodeProvider.Builder xcodeProviderBuilder) { |
| bundleSupport.addXcodeSettings(xcodeProviderBuilder); |
| // Add application-related Xcode build settings to the main target only. The companion library |
| // target does not need them. |
| xcodeProviderBuilder.addMainTargetXcodeprojBuildSettings(buildSettings()); |
| |
| return this; |
| } |
| |
| /** |
| * Adds any files to the given nested set builder that should be built if this application is the |
| * top level target in a blaze invocation. |
| * |
| * @param filesToBuild a collection of files to be built, where new artifacts to be built are |
| * going to be placed |
| * @param dsymOutputType the file type of the dSYM bundle to be built, or absent if no |
| * dSYM should be built for this bundle. A dSYM bundle will only be created if both this |
| * is present and the configuration values dictate dSYM is enabled |
| * |
| * @return this application support |
| */ |
| ReleaseBundlingSupport addFilesToBuild( |
| NestedSetBuilder<Artifact> filesToBuild, Optional<DsymOutputType> dsymOutputType) { |
| NestedSetBuilder<Artifact> debugSymbolBuilder = NestedSetBuilder.<Artifact>stableOrder(); |
| |
| for (Artifact linkmapFile : getLinkmapFiles().values()) { |
| filesToBuild.add(linkmapFile); |
| } |
| |
| if (ObjcRuleClasses.objcConfiguration(ruleContext).generateDsym() |
| && dsymOutputType.isPresent()) { |
| filesToBuild.addAll(getDsymFiles(dsymOutputType.get()).values()); |
| |
| // TODO(bazel-team): Remove the 'if' when the objc_binary rule does not generate a bundle any |
| // more. The reason this 'if' is here is because the plist is obtained from the ObjcProvider. |
| // Since objc_binary is the rule that adds this file to the provider, and not before, when |
| // running this the provider does not have the plist yet. This gets called again when running |
| // the *_application targets, and since they depend on objc_binaries, the provider has the |
| // files configured. When objc_binary stops bundling ipas as output, the bundling methods will |
| // only get called by *_application rules, with the plist configured in the provider. |
| Artifact cpuPlist = getAnyCpuSpecificDsymPlist(); |
| if (cpuPlist != null) { |
| filesToBuild.add(intermediateArtifacts.dsymPlist(dsymOutputType.get())); |
| } |
| |
| if (linkedBinary == LinkedBinary.LOCAL_AND_DEPENDENCIES) { |
| debugSymbolBuilder |
| .add(intermediateArtifacts.dsymPlist(dsymOutputType.get())) |
| .add(intermediateArtifacts.dsymSymbol(dsymOutputType.get())); |
| } |
| } |
| |
| filesToBuild |
| .add(releaseBundling.getIpaArtifact()) |
| .addTransitive(debugSymbolBuilder.build()) |
| .addTransitive(objcProvider.get(ObjcProvider.EXPORTED_DEBUG_ARTIFACTS)); |
| |
| return this; |
| } |
| |
| /** |
| * Adds dSYM artifacts (plist, arch-speficic binaries) to the {@link ObjcProvider} for export. |
| */ |
| public void addExportedDebugArtifacts( |
| ObjcProvider.Builder objcBuilder, DsymOutputType dsymOutputType) { |
| if (ObjcRuleClasses.objcConfiguration(ruleContext).generateDsym()) { |
| objcBuilder |
| .addAll(ObjcProvider.EXPORTED_DEBUG_ARTIFACTS, getDsymFiles(dsymOutputType).values()) |
| .add( |
| ObjcProvider.EXPORTED_DEBUG_ARTIFACTS, |
| intermediateArtifacts.dsymPlist(dsymOutputType)); |
| } |
| } |
| |
| /** |
| * Creates the {@link XcTestAppProvider} that can be used if this application is used as an |
| * {@code xctest_app}. |
| */ |
| XcTestAppProvider xcTestAppProvider() { |
| // We want access to #import-able things from our test rig's dependency graph, but we don't |
| // want to link anything since that stuff is shared automatically by way of the |
| // -bundle_loader linker flag. |
| // TODO(bazel-team): Handle the FRAMEWORK_DIR key properly. We probably want to add it to |
| // framework search paths, but not actually link it with the -framework flag. |
| ObjcProvider partialObjcProvider = |
| new ObjcProvider.Builder() |
| .addTransitiveAndPropagate(ObjcProvider.HEADER, objcProvider) |
| .addTransitiveAndPropagate(ObjcProvider.INCLUDE, objcProvider) |
| .addTransitiveAndPropagate(ObjcProvider.DEFINE, objcProvider) |
| .addTransitiveAndPropagate(ObjcProvider.SDK_DYLIB, objcProvider) |
| .addTransitiveAndPropagate(ObjcProvider.SDK_FRAMEWORK, objcProvider) |
| .addTransitiveAndPropagate(ObjcProvider.SOURCE, objcProvider) |
| .addTransitiveAndPropagate(ObjcProvider.WEAK_SDK_FRAMEWORK, objcProvider) |
| .addTransitiveAndPropagate(ObjcProvider.STATIC_FRAMEWORK_FILE, objcProvider) |
| .addTransitiveAndPropagate(ObjcProvider.DYNAMIC_FRAMEWORK_FILE, objcProvider) |
| .addTransitiveAndPropagate( |
| ObjcProvider.FRAMEWORK_SEARCH_PATH_ONLY, |
| objcProvider.get(ObjcProvider.STATIC_FRAMEWORK_DIR)) |
| .addTransitiveAndPropagate( |
| ObjcProvider.FRAMEWORK_SEARCH_PATH_ONLY, |
| objcProvider.get(ObjcProvider.DYNAMIC_FRAMEWORK_DIR)) |
| .build(); |
| return new XcTestAppProvider( |
| intermediateArtifacts.combinedArchitectureBinary(), |
| releaseBundling.getIpaArtifact(), |
| partialObjcProvider); |
| } |
| |
| /** |
| * Registers an action to generate a runner script based on a template. |
| */ |
| ReleaseBundlingSupport registerGenerateRunnerScriptAction(Artifact runnerScript, |
| Artifact ipaInput) { |
| ObjcConfiguration objcConfiguration = ObjcRuleClasses.objcConfiguration(ruleContext); |
| String escapedSimDevice = ShellUtils.shellEscape(objcConfiguration.getIosSimulatorDevice()); |
| String escapedSdkVersion = |
| ShellUtils.shellEscape(objcConfiguration.getIosSimulatorVersion().toString()); |
| ImmutableList<Substitution> substitutions = ImmutableList.of( |
| Substitution.of("%app_name%", ruleContext.getLabel().getName()), |
| Substitution.of("%ipa_file%", ipaInput.getRunfilesPathString()), |
| Substitution.of("%sim_device%", escapedSimDevice), |
| Substitution.of("%sdk_version%", escapedSdkVersion), |
| Substitution.of("%std_redirect_dylib_path%", |
| attributes.stdRedirectDylib().getRunfilesPathString())); |
| |
| ruleContext.registerAction( |
| new TemplateExpansionAction(ruleContext.getActionOwner(), attributes.runnerScriptTemplate(), |
| runnerScript, substitutions, true)); |
| return this; |
| } |
| |
| /** |
| * Returns a {@link RunfilesSupport} that uses the provided runner script as the executable. |
| */ |
| RunfilesSupport runfilesSupport(Artifact runnerScript) { |
| Runfiles runfiles = new Runfiles.Builder( |
| ruleContext.getWorkspaceName(), ruleContext.getConfiguration().legacyExternalRunfiles()) |
| .addArtifact(releaseBundling.getIpaArtifact()) |
| .addArtifact(runnerScript) |
| .addArtifact(attributes.stdRedirectDylib()) |
| .build(); |
| return RunfilesSupport.withExecutable(ruleContext, runfiles, runnerScript); |
| } |
| |
| private ExtraActoolArgs extraActoolArgs() { |
| ImmutableList.Builder<String> extraArgs = ImmutableList.builder(); |
| if (releaseBundling.getAppIcon() != null) { |
| extraArgs.add("--app-icon", releaseBundling.getAppIcon()); |
| } |
| if (releaseBundling.getLaunchImage() != null) { |
| extraArgs.add("--launch-image", releaseBundling.getLaunchImage()); |
| } |
| return new ExtraActoolArgs(extraArgs.build()); |
| } |
| |
| private Bundling bundling( |
| RuleContext ruleContext, |
| ObjcProvider objcProvider, |
| String bundleDirFormat, |
| String bundleName, |
| DottedVersion minimumOsVersion) { |
| ImmutableList<BundleableFile> extraBundleFiles; |
| AppleConfiguration appleConfiguration = ruleContext.getFragment(AppleConfiguration.class); |
| if (platform.isDevice()) { |
| extraBundleFiles = ImmutableList.of(new BundleableFile( |
| releaseBundling.getProvisioningProfile(), PROVISIONING_PROFILE_BUNDLE_FILE)); |
| } else { |
| extraBundleFiles = ImmutableList.of(); |
| } |
| |
| Bundling.Builder bundling = |
| new Builder() |
| .setName(bundleName) |
| .setExecutableName(bundleName) |
| // Architecture that determines which nested bundles are kept. |
| .setArchitecture(appleConfiguration.getDependencySingleArchitecture()) |
| .setBundleDirFormat(bundleDirFormat) |
| .addExtraBundleFiles(extraBundleFiles) |
| .setObjcProvider(objcProvider) |
| .setIntermediateArtifacts(intermediateArtifacts) |
| .setPrimaryBundleId(releaseBundling.getPrimaryBundleId()) |
| .setFallbackBundleId(releaseBundling.getFallbackBundleId()) |
| .setMinimumOsVersion(minimumOsVersion) |
| .setArtifactPrefix(releaseBundling.getArtifactPrefix()) |
| .setTargetDeviceFamilies(releaseBundling.getTargetDeviceFamilies()); |
| |
| // Add plists from rule first. |
| if (releaseBundling.getInfoPlistsFromRule() != null) { |
| bundling.addInfoplistInputs(releaseBundling.getInfoPlistsFromRule()); |
| } else { |
| bundling.addInfoplistInputFromRule(ruleContext); |
| } |
| |
| // Add generated plists next so that generated values can override the default values in the |
| // plists from rule. |
| bundling.setAutomaticEntriesInfoplistInput(getGeneratedAutomaticPlist()) |
| .addInfoplistInput(getGeneratedVersionPlist()) |
| .addInfoplistInput(getGeneratedEnvironmentPlist()) |
| .addInfoplistInputs(releaseBundling.getInfoplistInputs()); |
| |
| if (releaseBundling.getLaunchStoryboard() != null) { |
| bundling.addInfoplistInput(getLaunchStoryboardPlist()); |
| } |
| |
| return bundling.build(); |
| } |
| |
| private Artifact prepareCombinedArchitecturesArtifact() { |
| Artifact dependentMultiArchBinary = attributes.dependentMultiArchBinary(); |
| if (dependentMultiArchBinary != null) { |
| return dependentMultiArchBinary; |
| } |
| |
| Artifact resultingLinkedBinary = intermediateArtifacts.combinedArchitectureBinary(); |
| |
| new LipoSupport(ruleContext).registerCombineArchitecturesAction(linkedBinaries(), |
| resultingLinkedBinary, platform); |
| return resultingLinkedBinary; |
| } |
| |
| private NestedSet<Artifact> linkedBinaries() { |
| NestedSetBuilder<Artifact> linkedBinariesBuilder = NestedSetBuilder.<Artifact>stableOrder() |
| .addTransitive(attributes.dependentLinkedBinaries()); |
| if (linkedBinary == LinkedBinary.LOCAL_AND_DEPENDENCIES) { |
| linkedBinariesBuilder.add(intermediateArtifacts.strippedSingleArchitectureBinary()); |
| } |
| return linkedBinariesBuilder.build(); |
| } |
| |
| /** Returns this target's Xcode build settings. */ |
| private Iterable<XcodeprojBuildSetting> buildSettings() { |
| ImmutableList.Builder<XcodeprojBuildSetting> buildSettings = new ImmutableList.Builder<>(); |
| if (releaseBundling.getAppIcon() != null) { |
| buildSettings.add(XcodeprojBuildSetting.newBuilder() |
| .setName("ASSETCATALOG_COMPILER_APPICON_NAME") |
| .setValue(releaseBundling.getAppIcon()) |
| .build()); |
| } |
| if (releaseBundling.getLaunchImage() != null) { |
| buildSettings.add(XcodeprojBuildSetting.newBuilder() |
| .setName("ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME") |
| .setValue(releaseBundling.getLaunchImage()) |
| .build()); |
| } |
| |
| // Convert names to a sequence containing "1" and/or "2" for iPhone and iPad, respectively. |
| 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() |
| .setName("TARGETED_DEVICE_FAMILY") |
| .setValue(Joiner.on(',').join(familyIndexes)) |
| .build()); |
| |
| Artifact entitlements = releaseBundling.getEntitlements(); |
| if (entitlements != null) { |
| buildSettings.add(XcodeprojBuildSetting.newBuilder() |
| .setName("CODE_SIGN_ENTITLEMENTS") |
| .setValue("$(WORKSPACE_ROOT)/" + entitlements.getExecPathString()) |
| .build()); |
| } |
| |
| return buildSettings.build(); |
| } |
| |
| private void registerBundleMergeActions() { |
| Artifact bundleMergeControlArtifact = ObjcRuleClasses.artifactByAppendingToBaseName(ruleContext, |
| artifactName(".ipa-control")); |
| |
| BundleMergeControlBytes controlBytes = |
| new BundleMergeControlBytes( |
| bundling, |
| intermediateArtifacts.unprocessedIpa(), |
| ruleContext.getFragment(AppleConfiguration.class)); |
| |
| ruleContext.registerAction( |
| new BinaryFileWriteAction( |
| ruleContext.getActionOwner(), bundleMergeControlArtifact, controlBytes, |
| /*makeExecutable=*/false)); |
| |
| ruleContext.registerAction( |
| new SpawnAction.Builder() |
| .setMnemonic("IosBundle") |
| .setProgressMessage("Bundling iOS application: " + ruleContext.getLabel()) |
| .setExecutable(attributes.bundleMergeExecutable()) |
| .addInputArgument(bundleMergeControlArtifact) |
| .addTransitiveInputs(bundling.getBundleContentArtifacts()) |
| .addOutput(intermediateArtifacts.unprocessedIpa()) |
| .build(ruleContext)); |
| } |
| |
| private void registerCopyLinkmapFilesAction() { |
| for (Entry<Artifact, Artifact> linkmapFile : getLinkmapFiles().entrySet()) { |
| ruleContext.registerAction( |
| new SymlinkAction(ruleContext.getActionOwner(), linkmapFile.getKey(), |
| linkmapFile.getValue(), String.format("Copying Linkmap %s", |
| linkmapFile.getValue().prettyPrint()))); |
| } |
| } |
| |
| /** |
| * Registers the actions that copy the debug symbol files from the CPU-specific binaries that are |
| * part of this application. The only one step executed is that he dsym files have to be renamed |
| * to include their corresponding CPU architecture as a suffix. |
| * |
| * @param dsymOutputType the file type of the dSYM bundle to be copied |
| */ |
| private void registerCopyDsymFilesAction(DsymOutputType dsymOutputType) { |
| for (Entry<Artifact, Artifact> dsymFiles : getDsymFiles(dsymOutputType).entrySet()) { |
| ruleContext.registerAction( |
| new SymlinkAction( |
| ruleContext.getActionOwner(), |
| dsymFiles.getKey(), |
| dsymFiles.getValue(), |
| "Symlinking dSYM files")); |
| } |
| } |
| |
| /** |
| * Registers the action that copies the debug symbol plist from the binary. |
| * |
| * @param dsymOutputType the file type of the dSYM bundle to be copied |
| */ |
| private void registerCopyDsymPlistAction(DsymOutputType dsymOutputType) { |
| Artifact dsymPlist = getAnyCpuSpecificDsymPlist(); |
| if (dsymPlist != null) { |
| ruleContext.registerAction( |
| new SymlinkAction( |
| ruleContext.getActionOwner(), |
| dsymPlist, |
| intermediateArtifacts.dsymPlist(dsymOutputType), |
| "Symlinking dSYM plist")); |
| } |
| } |
| |
| /** |
| * Returns a map of input dsym artifacts from the CPU-specific binaries built for this |
| * ios_application to the new output dsym artifacts. |
| * |
| * @param dsymOutputType the file type of the dSYM bundle to be generated |
| */ |
| private ImmutableMap<Artifact, Artifact> getDsymFiles(DsymOutputType dsymOutputType) { |
| ImmutableMap.Builder<Artifact, Artifact> results = ImmutableMap.builder(); |
| for (Entry<String, Artifact> dsymFile : attributes.cpuSpecificDsymFiles().entrySet()) { |
| Artifact destDsym = intermediateArtifacts.dsymSymbol(dsymOutputType, dsymFile.getKey()); |
| results.put(dsymFile.getValue(), destDsym); |
| } |
| return results.build(); |
| } |
| |
| /** |
| * Returns any available CPU specific dSYM plist file. |
| */ |
| @Nullable |
| private Artifact getAnyCpuSpecificDsymPlist() { |
| for (Artifact dsymPlist : attributes.cpuSpecificDsymPlists().values()) { |
| // The plist files generated by the dsym tool are all equal, and don't really have any |
| // useful information. For now, just retrieving any one is OK, but ideally all of them should |
| // be merged. |
| return dsymPlist; |
| } |
| return null; |
| } |
| |
| /** |
| * Returns a map of input linkmap artifacts from the CPU-specific binaries built for this |
| * ios_application to the new output linkmap artifacts. |
| */ |
| private ImmutableMap<Artifact, Artifact> getLinkmapFiles() { |
| ImmutableMap.Builder<Artifact, Artifact> results = ImmutableMap.builder(); |
| for (Entry<String, Artifact> linkmapFile : attributes.cpuSpecificLinkmapFiles().entrySet()) { |
| Artifact destLinkMap = intermediateArtifacts.linkmap(linkmapFile.getKey()); |
| results.put(linkmapFile.getValue(), destLinkMap); |
| } |
| return results.build(); |
| } |
| |
| private void registerExtractTeamPrefixAction(Artifact teamPrefixFile) { |
| String shellCommand = "set -e && " |
| + "PLIST=$(mktemp -t teamprefix.plist) && trap \"rm ${PLIST}\" EXIT && " |
| + extractPlistCommand(releaseBundling.getProvisioningProfile()) + " > ${PLIST} && " |
| + "/usr/libexec/PlistBuddy -c 'Print ApplicationIdentifierPrefix:0' ${PLIST} > " |
| + teamPrefixFile.getShellEscapedExecPathString(); |
| ruleContext.registerAction( |
| ObjcRuleClasses.spawnBashOnDarwinActionBuilder(shellCommand) |
| .setMnemonic("ExtractIosTeamPrefix") |
| .disableSandboxing() |
| .addInput(releaseBundling.getProvisioningProfile()) |
| .addOutput(teamPrefixFile) |
| .build(ruleContext)); |
| } |
| |
| private ReleaseBundlingSupport registerExtractEntitlementsAction(Artifact entitlements) { |
| // See Apple Glossary (http://goo.gl/EkhXOb) |
| // An Application Identifier is constructed as: TeamID.BundleID |
| // TeamID is extracted from the provisioning profile. |
| // BundleID consists of a reverse-DNS string to identify the app, where the last component |
| // is the application name, and is specified as an attribute. |
| String shellCommand = "set -e && " |
| + "PLIST=$(mktemp -t entitlements.plist) && trap \"rm ${PLIST}\" EXIT && " |
| + extractPlistCommand(releaseBundling.getProvisioningProfile()) + " > ${PLIST} && " |
| + "/usr/libexec/PlistBuddy -x -c 'Print Entitlements' ${PLIST} > " |
| + entitlements.getShellEscapedExecPathString(); |
| ruleContext.registerAction( |
| ObjcRuleClasses.spawnBashOnDarwinActionBuilder(shellCommand) |
| .setMnemonic("ExtractIosEntitlements") |
| .disableSandboxing() |
| .setProgressMessage("Extracting entitlements: " + ruleContext.getLabel()) |
| .addInput(releaseBundling.getProvisioningProfile()) |
| .addOutput(entitlements) |
| .build(ruleContext)); |
| |
| return this; |
| } |
| |
| private void registerEntitlementsVariableSubstitutionAction( |
| Artifact inputEntitlements, Artifact prefix, Artifact substitutedEntitlements) { |
| String escapedBundleId = ShellUtils.shellEscape(releaseBundling.getBundleId()); |
| String shellCommand = |
| "set -e && " |
| + "PREFIX=\"$(cat " |
| + prefix.getShellEscapedExecPathString() |
| + ")\" && " |
| + "sed " |
| // Replace .* from default entitlements file with bundle ID where suitable. |
| + "-e \"s#${PREFIX}\\.\\*#${PREFIX}." |
| + escapedBundleId |
| + "#g\" " |
| |
| // Replace some variables that people put in their own entitlements files |
| + "-e \"s#\\$(AppIdentifierPrefix)#${PREFIX}.#g\" " |
| + "-e \"s#\\$(CFBundleIdentifier)#" |
| + escapedBundleId |
| + "#g\" " |
| + inputEntitlements.getShellEscapedExecPathString() |
| + " > " |
| + substitutedEntitlements.getShellEscapedExecPathString(); |
| ruleContext.registerAction( |
| new SpawnAction.Builder() |
| .setMnemonic("SubstituteIosEntitlements") |
| .setShellCommand(shellCommand) |
| .addInput(inputEntitlements) |
| .addInput(prefix) |
| .addOutput(substitutedEntitlements) |
| .build(ruleContext)); |
| } |
| |
| /** Registers an action to copy Swift standard library dylibs into app bundle. */ |
| private void registerSwiftStdlibActionsIfNecessary(Artifact combinedArchBinary) { |
| if (!objcProvider.is(USES_SWIFT)) { |
| return; |
| } |
| |
| AppleConfiguration appleConfiguration = ruleContext.getFragment(AppleConfiguration.class); |
| |
| CustomCommandLine.Builder commandLine = CustomCommandLine.builder(); |
| if (appleConfiguration.getXcodeToolchain() != null) { |
| commandLine.add("--toolchain").add(appleConfiguration.getXcodeToolchain()); |
| } |
| |
| commandLine |
| .add("--output_zip_path") |
| .addPath(intermediateArtifacts.swiftFrameworksFileZip().getExecPath()) |
| .add("--bundle_path") |
| .add("Frameworks") |
| .add("--platform") |
| .add(platform.getLowerCaseNameInPlist()) |
| .addExecPath("--scan-executable", combinedArchBinary); |
| |
| ruleContext.registerAction( |
| ObjcRuleClasses.spawnAppleEnvActionBuilder(appleConfiguration, platform) |
| .setMnemonic("SwiftStdlibCopy") |
| .setExecutable(attributes.swiftStdlibToolWrapper()) |
| .setCommandLine(commandLine.build()) |
| .addOutput(intermediateArtifacts.swiftFrameworksFileZip()) |
| .addInput(combinedArchBinary) |
| .build(ruleContext)); |
| } |
| |
| /** Registers an action to copy Swift standard library dylibs into SwiftSupport root directory. */ |
| private void registerSwiftSupportActionsIfNecessary(Artifact combinedArchBinary) { |
| if (!objcProvider.is(USES_SWIFT)) { |
| return; |
| } |
| |
| AppleConfiguration configuration = ruleContext.getFragment(AppleConfiguration.class); |
| |
| CustomCommandLine.Builder commandLine = CustomCommandLine.builder(); |
| if (configuration.getXcodeToolchain() != null) { |
| commandLine.add("--toolchain").add(configuration.getXcodeToolchain()); |
| } |
| |
| commandLine |
| .add("--output_zip_path") |
| .addPath(intermediateArtifacts.swiftSupportZip().getExecPath()) |
| .add("--bundle_path") |
| .add("SwiftSupport/" + platform.getLowerCaseNameInPlist()) |
| .add("--platform") |
| .add(platform.getLowerCaseNameInPlist()) |
| .addExecPath("--scan-executable", combinedArchBinary); |
| |
| ruleContext.registerAction( |
| ObjcRuleClasses.spawnAppleEnvActionBuilder(configuration, platform) |
| .setMnemonic("SwiftCopySwiftSupport") |
| .setExecutable(attributes.swiftStdlibToolWrapper()) |
| .setCommandLine(commandLine.build()) |
| .addOutput(intermediateArtifacts.swiftSupportZip()) |
| .addInput(combinedArchBinary) |
| .build(ruleContext)); |
| } |
| |
| private String extractPlistCommand(Artifact provisioningProfile) { |
| return "security cms -D -i " + ShellUtils.shellEscape(provisioningProfile.getExecPathString()); |
| } |
| |
| private String deviceCodesignCommand(String appDir) { |
| String signingCertName = ObjcRuleClasses.objcConfiguration(ruleContext).getSigningCertName(); |
| Artifact entitlements = intermediateArtifacts.entitlements(); |
| |
| final String identity; |
| if (signingCertName != null) { |
| identity = '"' + signingCertName + '"'; |
| } else { |
| // Extracts an identity hash from the configured provisioning profile. Note that this will use |
| // the first certificate identity in the profile, regardless of how many identities are |
| // configured in it (DeveloperCertificates:0). |
| identity = |
| "$(PLIST=$(mktemp -t cert.plist) && trap \"rm ${PLIST}\" EXIT && " |
| + extractPlistCommand(releaseBundling.getProvisioningProfile()) |
| + " > ${PLIST} && " |
| + "/usr/libexec/PlistBuddy -c 'Print DeveloperCertificates:0' ${PLIST} | " |
| + "openssl x509 -inform DER -noout -fingerprint | " |
| + "cut -d= -f2 | sed -e 's#:##g')"; |
| } |
| |
| return String.format( |
| "/usr/bin/codesign --force --sign %s --entitlements %s %s", |
| identity, |
| entitlements.getShellEscapedExecPathString(), |
| appDir); |
| } |
| |
| private Artifact getGeneratedVersionPlist() { |
| return ruleContext.getRelatedArtifact( |
| ruleContext.getUniqueDirectory("plists"), artifactName("-version.plist")); |
| } |
| |
| private Artifact getGeneratedEnvironmentPlist() { |
| return ruleContext.getRelatedArtifact( |
| ruleContext.getUniqueDirectory("plists"), artifactName("-environment.plist")); |
| } |
| |
| private Artifact getGeneratedAutomaticPlist() { |
| return ruleContext.getRelatedArtifact( |
| ruleContext.getUniqueDirectory("plists"), artifactName("-automatic.plist")); |
| } |
| |
| private Artifact getLaunchStoryboardPlist() { |
| return ruleContext.getRelatedArtifact( |
| ruleContext.getUniqueDirectory("plists"), artifactName("-launchstoryboard.plist")); |
| } |
| |
| /** |
| * Returns artifact name prefixed with prefix given in {@link ReleaseBundling} if available. |
| * This helps in creating unique artifact name when multiple bundles are created with a different |
| * name than the target name. |
| */ |
| private String artifactName(String artifactName) { |
| if (releaseBundling.getArtifactPrefix() != null) { |
| return String.format("-%s%s", releaseBundling.getArtifactPrefix(), artifactName); |
| } |
| return artifactName; |
| } |
| |
| /** |
| * Logic to access attributes to access tools required by application support. |
| * Attributes are required and guaranteed to return a value or throw unless they are annotated |
| * with {@link Nullable} in which case they can return {@code null} if no value is defined. |
| */ |
| private static class Attributes { |
| private final RuleContext ruleContext; |
| |
| private Attributes(RuleContext ruleContext) { |
| this.ruleContext = ruleContext; |
| } |
| |
| /** |
| * Returns this target's user-specified {@code ipa_post_processor} or null if not present. |
| */ |
| @Nullable |
| FilesToRunProvider ipaPostProcessor() { |
| if (!ruleContext.attributes().has("ipa_post_processor", BuildType.LABEL)) { |
| return null; |
| } |
| return ruleContext.getExecutablePrerequisite("ipa_post_processor", Mode.TARGET); |
| } |
| |
| /** |
| * Returns the multi-arch binary provided by the label under the "binary" attribute of the |
| * current rule, or null if there is no such binary available. |
| */ |
| @Nullable Artifact dependentMultiArchBinary() { |
| if (ruleContext.attributes().getAttributeDefinition("binary") == null) { |
| return null; |
| } |
| |
| for (ObjcProvider provider |
| : ruleContext.getPrerequisites("binary", Mode.DONT_CHECK, ObjcProvider.class)) { |
| if (!provider.get(ObjcProvider.MULTI_ARCH_LINKED_BINARIES).isEmpty()) { |
| return Iterables.getOnlyElement(provider.get(ObjcProvider.MULTI_ARCH_LINKED_BINARIES)); |
| } |
| } |
| return null; |
| } |
| |
| NestedSet<? extends Artifact> dependentLinkedBinaries() { |
| if (ruleContext.attributes().getAttributeDefinition("binary") == null) { |
| return NestedSetBuilder.emptySet(Order.STABLE_ORDER); |
| } |
| |
| NestedSetBuilder<Artifact> linkedBinaries = NestedSetBuilder.stableOrder(); |
| for (ObjcProvider provider |
| : ruleContext.getPrerequisites("binary", Mode.DONT_CHECK, ObjcProvider.class)) { |
| linkedBinaries.addTransitive(provider.get(ObjcProvider.LINKED_BINARY)); |
| } |
| |
| return linkedBinaries.build(); |
| } |
| |
| FilesToRunProvider bundleMergeExecutable() { |
| return checkNotNull(ruleContext.getExecutablePrerequisite("$bundlemerge", Mode.HOST)); |
| } |
| |
| /** |
| * Returns a reference to the plmerge executable. |
| */ |
| FilesToRunProvider plmerge() { |
| return ruleContext.getExecutablePrerequisite("$plmerge", Mode.HOST); |
| } |
| |
| Artifact stdRedirectDylib() { |
| return checkNotNull(ruleContext.getPrerequisiteArtifact("$std_redirect_dylib", Mode.HOST)); |
| } |
| |
| Artifact runnerScriptTemplate() { |
| return checkNotNull( |
| ruleContext.getPrerequisiteArtifact("$runner_script_template", Mode.HOST)); |
| } |
| |
| /** Returns the location of the swiftstdlibtoolwrapper. */ |
| FilesToRunProvider swiftStdlibToolWrapper() { |
| return ruleContext.getExecutablePrerequisite("$swiftstdlibtoolwrapper", Mode.HOST); |
| } |
| |
| /** |
| * Returns the location of the environment_plist. |
| */ |
| FilesToRunProvider environmentPlist() { |
| return ruleContext.getExecutablePrerequisite("$environment_plist", Mode.HOST); |
| } |
| |
| /** |
| * Returns a plist specified by the user via {@code --extra_entitlements} or {@code null}. |
| */ |
| @Nullable |
| Artifact extraEntitlements() { |
| if (ruleContext.attributes().getAttributeDefinition(EXTRA_ENTITLEMENTS_ATTR) == null) { |
| return null; |
| } |
| return ruleContext.getPrerequisiteArtifact(EXTRA_ENTITLEMENTS_ATTR, Mode.HOST); |
| } |
| |
| /** |
| * Returns a plist containing entitlements that allow the signed IPA to be debugged. |
| */ |
| @Nullable |
| Artifact deviceDebugEntitlements() { |
| if (ruleContext.attributes().getAttributeDefinition(DEBUG_ENTITLEMENTS_ATTR) == null) { |
| return null; |
| } |
| return ruleContext.getPrerequisiteArtifact(DEBUG_ENTITLEMENTS_ATTR, Mode.HOST); |
| } |
| |
| ImmutableMap<String, Artifact> cpuSpecificDsymFiles() { |
| return cpuSpecificArtifacts(ObjcProvider.DEBUG_SYMBOLS); |
| } |
| |
| ImmutableMap<String, Artifact> cpuSpecificDsymPlists() { |
| return cpuSpecificArtifacts(ObjcProvider.DEBUG_SYMBOLS_PLIST); |
| } |
| |
| ImmutableMap<String, Artifact> cpuSpecificLinkmapFiles() { |
| return cpuSpecificArtifacts(ObjcProvider.LINKMAP_FILE); |
| } |
| |
| ImmutableMap<String, Artifact> cpuSpecificArtifacts(ObjcProvider.Key<Artifact> key) { |
| ImmutableMap.Builder<String, Artifact> results = ImmutableMap.builder(); |
| if (ruleContext.attributes().has("binary", BuildType.LABEL)) { |
| for (TransitiveInfoCollection prerequisite |
| : ruleContext.getPrerequisites("binary", Mode.DONT_CHECK)) { |
| ObjcProvider prerequisiteProvider = prerequisite.getProvider(ObjcProvider.class); |
| if (prerequisiteProvider != null) { |
| Artifact sourceArtifact = Iterables.getOnlyElement(prerequisiteProvider.get(key), null); |
| if (sourceArtifact != null) { |
| String cpu = |
| prerequisite.getConfiguration().getFragment(AppleConfiguration.class).getIosCpu(); |
| results.put(cpu, sourceArtifact); |
| } |
| } |
| } |
| } |
| return results.build(); |
| } |
| } |
| |
| /** |
| * Transition that results in one configured target per architecture set in {@code |
| * --ios_multi_cpus}. |
| */ |
| protected static class SplitArchTransition implements SplitTransition<BuildOptions> { |
| |
| @Override |
| public final List<BuildOptions> split(BuildOptions buildOptions) { |
| List<String> iosMultiCpus = buildOptions.get(AppleCommandLineOptions.class).iosMultiCpus; |
| if (iosMultiCpus.isEmpty()) { |
| return defaultOptions(buildOptions); |
| } |
| |
| ImmutableList.Builder<BuildOptions> splitBuildOptions = ImmutableList.builder(); |
| for (String iosCpu : iosMultiCpus) { |
| BuildOptions splitOptions = buildOptions.clone(); |
| setArchitectureOptions(splitOptions, buildOptions, iosCpu); |
| setAdditionalOptions(splitOptions, buildOptions); |
| splitOptions.get(AppleCommandLineOptions.class).configurationDistinguisher = |
| getConfigurationDistinguisher(); |
| splitBuildOptions.add(splitOptions); |
| } |
| return splitBuildOptions.build(); |
| } |
| |
| /** |
| * Returns the default options to use if no split architectures are specified. |
| * |
| * @param originalOptions original options before this transition |
| */ |
| protected ImmutableList<BuildOptions> defaultOptions(BuildOptions originalOptions) { |
| return ImmutableList.of(); |
| } |
| |
| /** |
| * Sets or overwrites flags on the given split options. |
| * |
| * <p>Invoked once for each configuration produced by this transition. |
| * |
| * @param splitOptions options to use after this transition |
| * @param originalOptions original options before this transition |
| */ |
| protected void setAdditionalOptions(BuildOptions splitOptions, BuildOptions originalOptions) {} |
| |
| private static void setArchitectureOptions(BuildOptions splitOptions, |
| BuildOptions originalOptions, String iosCpu) { |
| splitOptions.get(AppleCommandLineOptions.class).applePlatformType = PlatformType.IOS; |
| splitOptions.get(AppleCommandLineOptions.class).appleSplitCpu = iosCpu; |
| splitOptions.get(AppleCommandLineOptions.class).iosCpu = iosCpu; |
| if (splitOptions.get(ObjcCommandLineOptions.class).enableCcDeps) { |
| // Only set the (CC-compilation) CPU for dependencies if explicitly required by the user. |
| // This helps users of the iOS rules who do not depend on CC rules as these CPU values |
| // require additional flags to work (e.g. a custom crosstool) which now only need to be set |
| // if this feature is explicitly requested. |
| AppleCrosstoolTransition.setAppleCrosstoolTransitionConfiguration(originalOptions, |
| splitOptions, "ios_" + iosCpu); |
| } |
| } |
| |
| @Override |
| public boolean defaultsToSelf() { |
| return true; |
| } |
| |
| /** Returns the configuration distinguisher for this transition instance. */ |
| protected ConfigurationDistinguisher getConfigurationDistinguisher() { |
| return ConfigurationDistinguisher.IOS_APPLICATION; |
| } |
| } |
| } |