diff options
Diffstat (limited to 'src/main')
6 files changed, 224 insertions, 113 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java index ca1071dc87..d5e19f766d 100644 --- a/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java +++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java @@ -343,6 +343,7 @@ public class BazelRuleClassProvider { builder.addRuleDefinition(new ObjcRuleClasses.CompileDependencyRule()); builder.addRuleDefinition(new ObjcRuleClasses.ResourceToolsRule()); builder.addRuleDefinition(new ObjcRuleClasses.XcrunRule()); + builder.addRuleDefinition(new ObjcRuleClasses.IpaRule()); builder.addRuleDefinition(new AppleToolchain.RequiresXcodeConfigRule()); builder.addRuleDefinition(new IosApplicationRule()); builder.addRuleDefinition(new IosExtensionBinaryRule()); diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/IntermediateArtifacts.java b/src/main/java/com/google/devtools/build/lib/rules/objc/IntermediateArtifacts.java index 1ff60eb73f..f52cbb6260 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/objc/IntermediateArtifacts.java +++ b/src/main/java/com/google/devtools/build/lib/rules/objc/IntermediateArtifacts.java @@ -62,6 +62,13 @@ public final class IntermediateArtifacts { } /** + * Returns the location of this target's generated entitlements file. + */ + public Artifact entitlements() { + return appendExtensionForEntitlementArtifact(".entitlements"); + } + + /** * Returns a derived artifact in the bin directory obtained by appending some extension to the end * of the given {@link PathFragment}. */ @@ -362,4 +369,11 @@ public final class IntermediateArtifacts { prunedSourceArtifactPath, ruleContext.getBinOrGenfilesDirectory()); } + + /** + * Returns the location of this target's merged but not post-processed or signed IPA. + */ + public Artifact unprocessedIpa() { + return appendExtension(".unprocessed.ipa"); + } } diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/IosApplicationRule.java b/src/main/java/com/google/devtools/build/lib/rules/objc/IosApplicationRule.java index 9438dd5b01..b7c369c7a8 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/objc/IosApplicationRule.java +++ b/src/main/java/com/google/devtools/build/lib/rules/objc/IosApplicationRule.java @@ -27,6 +27,7 @@ import com.google.devtools.build.lib.packages.ImplicitOutputsFunction; import com.google.devtools.build.lib.packages.RuleClass; import com.google.devtools.build.lib.packages.RuleClass.Builder; import com.google.devtools.build.lib.rules.apple.AppleConfiguration; +import com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.IpaRule; /** * Rule definition for ios_application. @@ -75,8 +76,12 @@ public class IosApplicationRule implements RuleDefinition { return RuleDefinition.Metadata.builder() .name("ios_application") .factoryClass(IosApplication.class) - .ancestors(BaseRuleClasses.BaseRule.class, ObjcRuleClasses.ReleaseBundlingRule.class, - ObjcRuleClasses.XcodegenRule.class, ObjcRuleClasses.SimulatorRule.class) + .ancestors( + BaseRuleClasses.BaseRule.class, + ObjcRuleClasses.ReleaseBundlingRule.class, + ObjcRuleClasses.XcodegenRule.class, + ObjcRuleClasses.SimulatorRule.class, + IpaRule.class) .build(); } } diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/IosExtensionRule.java b/src/main/java/com/google/devtools/build/lib/rules/objc/IosExtensionRule.java index 624cb996a2..1ff2ec4c22 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/objc/IosExtensionRule.java +++ b/src/main/java/com/google/devtools/build/lib/rules/objc/IosExtensionRule.java @@ -24,6 +24,7 @@ import com.google.devtools.build.lib.packages.ImplicitOutputsFunction; import com.google.devtools.build.lib.packages.RuleClass; import com.google.devtools.build.lib.packages.RuleClass.Builder; import com.google.devtools.build.lib.rules.apple.AppleConfiguration; +import com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.IpaRule; /** * Rule definition for ios_extension. @@ -60,8 +61,11 @@ public class IosExtensionRule implements RuleDefinition { return RuleDefinition.Metadata.builder() .name("ios_extension") .factoryClass(IosExtension.class) - .ancestors(BaseRuleClasses.BaseRule.class, ObjcRuleClasses.ReleaseBundlingRule.class, - ObjcRuleClasses.XcodegenRule.class) + .ancestors( + BaseRuleClasses.BaseRule.class, + ObjcRuleClasses.ReleaseBundlingRule.class, + ObjcRuleClasses.XcodegenRule.class, + IpaRule.class) .build(); } } diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcRuleClasses.java b/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcRuleClasses.java index 7ce57f500e..1389c5fd78 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcRuleClasses.java +++ b/src/main/java/com/google/devtools/build/lib/rules/objc/ObjcRuleClasses.java @@ -14,6 +14,7 @@ package com.google.devtools.build.lib.rules.objc; +import static com.google.devtools.build.lib.packages.Attribute.ANY_RULE; import static com.google.devtools.build.lib.packages.Attribute.ConfigurationTransition.HOST; import static com.google.devtools.build.lib.packages.Attribute.attr; import static com.google.devtools.build.lib.packages.BuildType.LABEL; @@ -972,6 +973,43 @@ public class ObjcRuleClasses { } /** + * Common attributes for {@code objc_*} rules that create a signed IPA. + */ + public static class IpaRule implements RuleDefinition { + @Override + public RuleClass build(Builder builder, RuleDefinitionEnvironment env) { + return builder + /* <!-- #BLAZE_RULE($objc_signing_rule).ATTRIBUTE(ipa_post_processor) --> + A tool that edits this target's IPA output after it is assembled but before it is + (optionally) signed. + <p> + The tool is invoked with a single positional argument which represents the path to a + directory containing the unzipped contents of the IPA. The only entry in this directory + will be the <code>Payload</code> root directory of the IPA. Any changes made by the tool + must be made in this directory, whose contents will be (optionally) signed and then + zipped up as the final IPA after the tool terminates. + <p> + The tool's execution must be hermetic given these inputs to ensure that its result can be + safely cached. + <!-- #END_BLAZE_RULE.ATTRIBUTE -->*/ + .add( + attr("ipa_post_processor", LABEL) + .allowedRuleClasses(ANY_RULE) + .allowedFileTypes(FileTypeSet.ANY_FILE) + .exec()) + .build(); + } + + @Override + public Metadata getMetadata() { + return RuleDefinition.Metadata.builder() + .name("$objc_ipa_rule") + .type(RuleClassType.ABSTRACT) + .build(); + } + } + + /** * Common attributes for {@code objc_*} rules that use the iOS simulator. */ public static class SimulatorRule implements RuleDefinition { diff --git a/src/main/java/com/google/devtools/build/lib/rules/objc/ReleaseBundlingSupport.java b/src/main/java/com/google/devtools/build/lib/rules/objc/ReleaseBundlingSupport.java index 1bfed353b3..9e15d8986d 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/objc/ReleaseBundlingSupport.java +++ b/src/main/java/com/google/devtools/build/lib/rules/objc/ReleaseBundlingSupport.java @@ -37,6 +37,7 @@ 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.BuildConfiguration; @@ -83,11 +84,6 @@ public final class ReleaseBundlingSupport { */ public static final SafeImplicitOutputsFunction IPA = fromTemplates("%{name}.ipa"); - // This is not an actual implicit output. This function is used to compute the name of an - // artifact. - public static final SafeImplicitOutputsFunction IPA_UNSIGNED = - fromTemplates("%{name}.ipa.unsigned"); - @VisibleForTesting static final String NO_ASSET_CATALOG_ERROR_FORMAT = "a value was specified (%s), but this app does not have any asset catalogs"; @@ -232,6 +228,12 @@ public final class ReleaseBundlingSupport { validateLaunchScreen(); + AppleConfiguration appleConfiguration = ruleContext.getFragment(AppleConfiguration.class); + if (attributes.provisioningProfile() == null + && appleConfiguration.getBundlingPlatform() != Platform.IOS_SIMULATOR) { + ruleContext.attributeError("provisioning_profile", DEVICE_NO_PROVISIONING_PROFILE); + } + return this; } @@ -278,7 +280,7 @@ public final class ReleaseBundlingSupport { * multi-architecture binary. * * @return this application support - * @throws InterruptedException + * @throws InterruptedException */ ReleaseBundlingSupport registerActions() throws InterruptedException { bundleSupport.registerActions(objcProvider); @@ -287,18 +289,6 @@ public final class ReleaseBundlingSupport { registerTransformAndCopyBreakpadFilesAction(); registerSwiftStdlibActionsIfNecessary(); - AppleConfiguration appleConfiguration = ruleContext.getFragment(AppleConfiguration.class); - Artifact ipaOutput = ruleContext.getImplicitOutputArtifact(IPA); - - Artifact maybeSignedIpa; - if (appleConfiguration.getBundlingPlatform() == Platform.IOS_SIMULATOR) { - maybeSignedIpa = ipaOutput; - } else if (attributes.provisioningProfile() == null) { - throw new IllegalStateException(DEVICE_NO_PROVISIONING_PROFILE); - } else { - maybeSignedIpa = registerBundleSigningActions(ipaOutput); - } - registerEmbedLabelPlistAction(); registerEnvironmentPlistAction(); registerAutomaticPlistAction(); @@ -307,10 +297,8 @@ public final class ReleaseBundlingSupport { registerLaunchStoryboardPlistAction(); } - BundleMergeControlBytes bundleMergeControlBytes = new BundleMergeControlBytes( - bundling, maybeSignedIpa, appleConfiguration, bundleSupport.targetDeviceFamilies()); - registerBundleMergeActions( - maybeSignedIpa, bundling.getBundleContentArtifacts(), bundleMergeControlBytes); + registerBundleMergeActions(); + registerPostProcessAndSigningActions(); return this; } @@ -429,9 +417,93 @@ public final class ReleaseBundlingSupport { return result; } - private Artifact registerBundleSigningActions(Artifact ipaOutput) throws InterruptedException { - IntermediateArtifacts intermediateArtifacts = - ObjcRuleClasses.intermediateArtifacts(ruleContext); + /** + * 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() throws InterruptedException { + Artifact processedIpa = ruleContext.getImplicitOutputArtifact(IPA); + Artifact unprocessedIpa = intermediateArtifacts.unprocessedIpa(); + + boolean processingNeeded = false; + NestedSetBuilder<Artifact> inputs = + NestedSetBuilder.<Artifact>stableOrder().add(unprocessedIpa); + + String actionCommandLine = + "set -e && " + + "t=$(mktemp -d -t signing_intermediate) && " + + "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) { + processingNeeded = true; + actionCommandLine += processor.getExecutable().getShellEscapedExecPathString() + " ${t} && "; + } + + AppleConfiguration appleConfiguration = ruleContext.getFragment(AppleConfiguration.class); + if (appleConfiguration.getBundlingPlatform() == Platform.IOS_DEVICE) { + processingNeeded = true; + registerEntitlementsActions(); + actionCommandLine += signingCommandLine(); + inputs.add(attributes.provisioningProfile()).add(intermediateArtifacts.entitlements()); + } + + actionCommandLine += "cd ${t} && /usr/bin/zip -q -r \"${signed_ipa}\" ."; + + if (processingNeeded) { + SpawnAction.Builder processAction = + ObjcRuleClasses.spawnBashOnDarwinActionBuilder(actionCommandLine) + .setMnemonic("ObjcProcessIpa") + .setProgressMessage("Processing iOS IPA: " + ruleContext.getLabel()) + .addTransitiveInputs(inputs.build()) + .addOutput(processedIpa); + + if (processor != null) { + processAction.addTool(processor); + } + + ruleContext.registerAction(processAction.build(ruleContext)); + } else { + ruleContext.registerAction( + new SymlinkAction( + ruleContext.getActionOwner(), unprocessedIpa, processedIpa, "Processing IPA")); + } + } + + private String signingCommandLine() { + ImmutableList.Builder<String> dirsToSign = new ImmutableList.Builder<>(); + + // Explicitly sign Swift dylibs. Unfortunately --deep option on codesign doesn't do this + // automatically. + // The order here is important. The innermost code must singed first. + String bundleDir = ShellUtils.shellEscape(bundling.getBundleDir()); + if (objcProvider.is(USES_SWIFT)) { + dirsToSign.add(bundleDir + "/Frameworks/*"); + } + dirsToSign.add(bundleDir); + + StringBuilder codesignCommandLineBuilder = new StringBuilder(); + for (String dir : dirsToSign.build()) { + codesignCommandLineBuilder.append(codesignCommand("${t}/" + dir)).append(" && "); + } + return codesignCommandLineBuilder.toString(); + } + + private void registerEntitlementsActions() throws InterruptedException { Artifact teamPrefixFile = intermediateArtifacts.appendExtensionForEntitlementArtifact(".team_prefix_file"); registerExtractTeamPrefixAction(teamPrefixFile); @@ -443,13 +515,7 @@ public final class ReleaseBundlingSupport { ".entitlements_with_variables"); registerExtractEntitlementsAction(entitlementsNeedingSubstitution); } - Artifact entitlements = - intermediateArtifacts.appendExtensionForEntitlementArtifact(".entitlements"); - registerEntitlementsVariableSubstitutionAction( - entitlementsNeedingSubstitution, entitlements, teamPrefixFile); - Artifact ipaUnsigned = ruleContext.getImplicitOutputArtifact(IPA_UNSIGNED); - registerSignBundleAction(entitlements, ipaOutput, ipaUnsigned); - return ipaUnsigned; + registerEntitlementsVariableSubstitutionAction(entitlementsNeedingSubstitution, teamPrefixFile); } /** @@ -687,69 +753,31 @@ public final class ReleaseBundlingSupport { return buildSettings.build(); } - private ReleaseBundlingSupport registerSignBundleAction( - Artifact entitlements, Artifact ipaOutput, Artifact ipaUnsigned) { - // TODO(bazel-team): Support variable substitution - - ImmutableList.Builder<String> dirsToSign = new ImmutableList.Builder<>(); - - // Explicitly sign Swift dylibs. Unfortunately --deep option on codesign doesn't do this - // automatically. - // The order here is important. The innermost code must singed first. - String bundleDir = ShellUtils.shellEscape(bundling.getBundleDir()); - if (objcProvider.is(USES_SWIFT)) { - dirsToSign.add(bundleDir + "/Frameworks/*"); - } - dirsToSign.add(bundleDir); - - StringBuilder codesignCommandLineBuilder = new StringBuilder(); - for (String dir : dirsToSign.build()) { - codesignCommandLineBuilder - .append(codesignCommand(entitlements, "${t}/" + dir)) - .append(" && "); - } - - // TODO(bazel-team): Support nested code signing. - String shellCommand = "set -e && " - + "t=$(mktemp -d -t signing_intermediate) && " - + "trap \"rm -rf ${t}\" EXIT && " - // Get an absolute path since we need to cd into the temp directory for zip. - + "signed_ipa=${PWD}/" + ipaOutput.getShellEscapedExecPathString() + " && " - + "/usr/bin/unzip -qq " + ipaUnsigned.getShellEscapedExecPathString() + " -d ${t} && " - + codesignCommandLineBuilder.toString() - // Using zip since we need to preserve permissions - + "cd ${t} && /usr/bin/zip -q -r \"${signed_ipa}\" ."; - ruleContext.registerAction( - ObjcRuleClasses.spawnBashOnDarwinActionBuilder(shellCommand) - .setMnemonic("IosSignBundle") - .setProgressMessage("Signing iOS bundle: " + ruleContext.getLabel()) - .addInput(ipaUnsigned) - .addInput(attributes.provisioningProfile()) - .addInput(entitlements) - .addOutput(ipaOutput) - .build(ruleContext)); - - return this; - } - - private void registerBundleMergeActions(Artifact ipaUnsigned, - NestedSet<Artifact> bundleContentArtifacts, BundleMergeControlBytes controlBytes) { + private void registerBundleMergeActions() { Artifact bundleMergeControlArtifact = ObjcRuleClasses.artifactByAppendingToBaseName(ruleContext, ".ipa-control"); + BundleMergeControlBytes controlBytes = + new BundleMergeControlBytes( + bundling, + intermediateArtifacts.unprocessedIpa(), + ruleContext.getFragment(AppleConfiguration.class), + bundleSupport.targetDeviceFamilies()); + 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(bundleContentArtifacts) - .addOutput(ipaUnsigned) - .build(ruleContext)); + 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)); } /** @@ -841,28 +869,37 @@ public final class ReleaseBundlingSupport { return this; } - private void registerEntitlementsVariableSubstitutionAction(Artifact in, Artifact out, - Artifact prefix) { + private void registerEntitlementsVariableSubstitutionAction( + Artifact inputEntitlements, Artifact prefix) { + Artifact substitutedEntitlements = intermediateArtifacts.entitlements(); String escapedBundleId = ShellUtils.shellEscape(attributes.bundleId()); - 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\" " - - + in.getShellEscapedExecPathString() + " " - + "> " + out.getShellEscapedExecPathString(); - ruleContext.registerAction(new SpawnAction.Builder() - .setMnemonic("SubstituteIosEntitlements") - .setShellCommand(shellCommand) - .addInput(in) - .addInput(prefix) - .addOutput(out) - .build(ruleContext)); + 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. */ @@ -892,8 +929,9 @@ public final class ReleaseBundlingSupport { return "security cms -D -i " + ShellUtils.shellEscape(provisioningProfile.getExecPathString()); } - private String codesignCommand(Artifact entitlements, String appDir) { + private String codesignCommand(String appDir) { String signingCertName = ObjcRuleClasses.objcConfiguration(ruleContext).getSigningCertName(); + Artifact entitlements = intermediateArtifacts.entitlements(); final String identity; if (signingCertName != null) { @@ -975,6 +1013,17 @@ public final class ReleaseBundlingSupport { return ruleContext.getPrerequisiteArtifact(":default_provisioning_profile", Mode.TARGET); } + /** + * 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); + } + @Nullable Artifact entitlements() { return ruleContext.getPrerequisiteArtifact("entitlements", Mode.TARGET); |