@Override public ImmutableList<Step> getBuildSteps( BuildContext context, BuildableContext buildableContext) { ImmutableList.Builder<Step> stepsBuilder = ImmutableList.builder(); Path metadataPath = getMetadataPath(); Path infoPlistInputPath = getResolver().getPath(infoPlist); Path infoPlistSubstitutionTempPath = BuildTargets.getScratchPath(getBuildTarget(), "%s.plist"); Path infoPlistOutputPath = metadataPath.resolve("Info.plist"); stepsBuilder.add( new MakeCleanDirectoryStep(bundleRoot), new MkdirStep(metadataPath), // TODO(user): This is only appropriate for .app bundles. new WriteFileStep("APPLWRUN", metadataPath.resolve("PkgInfo"), /* executable */ false), new FindAndReplaceStep( infoPlistInputPath, infoPlistSubstitutionTempPath, InfoPlistSubstitution.createVariableExpansionFunction( withDefaults( infoPlistSubstitutions, ImmutableMap.of( "EXECUTABLE_NAME", binaryName, "PRODUCT_NAME", binaryName)))), new PlistProcessStep( infoPlistSubstitutionTempPath, infoPlistOutputPath, getInfoPlistAdditionalKeys(platformName, sdkName), getInfoPlistOverrideKeys(platformName), PlistProcessStep.OutputFormat.BINARY)); if (binary.isPresent() && binary.get().getPathToOutput() != null) { stepsBuilder.add(new MkdirStep(bundleRoot.resolve(this.destinations.getExecutablesPath()))); Path bundleBinaryPath = bundleRoot.resolve(binaryPath); stepsBuilder.add(CopyStep.forFile(binary.get().getPathToOutput(), bundleBinaryPath)); stepsBuilder.add( new DsymStep( dsymutil.getCommandPrefix(getResolver()), bundleBinaryPath, bundleBinaryPath.resolveSibling( bundleBinaryPath.getFileName().toString() + ".dSYM"))); stepsBuilder.add( new DefaultShellStep( ImmutableList.<String>builder() .addAll(strip.getCommandPrefix(getResolver())) .add("-S") .add(getProjectFilesystem().resolve(bundleBinaryPath).toString()) .build())); } Path bundleDestinationPath = bundleRoot.resolve(this.destinations.getResourcesPath()); for (SourcePath dir : resourceDirs) { stepsBuilder.add(new MkdirStep(bundleDestinationPath)); stepsBuilder.add( CopyStep.forDirectory( getResolver().getPath(dir), bundleDestinationPath, CopyStep.DirectoryMode.DIRECTORY_AND_CONTENTS)); } for (SourcePath dir : dirsContainingResourceDirs) { stepsBuilder.add(new MkdirStep(bundleDestinationPath)); stepsBuilder.add( CopyStep.forDirectory( getResolver().getPath(dir), bundleDestinationPath, CopyStep.DirectoryMode.CONTENTS_ONLY)); } for (SourcePath file : resourceFiles) { stepsBuilder.add(new MkdirStep(bundleDestinationPath)); Path resolvedFilePath = getResolver().getPath(file); Path destinationPath = bundleDestinationPath.resolve(resolvedFilePath.getFileName()); addResourceProcessingSteps(resolvedFilePath, destinationPath, stepsBuilder); } addStepsToCopyExtensionBundlesDependencies(stepsBuilder); if (resourceVariantFiles.isPresent()) { for (SourcePath variantSourcePath : resourceVariantFiles.get()) { Path variantFilePath = getResolver().getPath(variantSourcePath); Path variantDirectory = variantFilePath.getParent(); if (variantDirectory == null || !variantDirectory.toString().endsWith(".lproj")) { throw new HumanReadableException( "Variant files have to be in a directory with name ending in '.lproj', " + "but '%s' is not.", variantFilePath); } Path bundleVariantDestinationPath = bundleDestinationPath.resolve(variantDirectory.getFileName()); stepsBuilder.add(new MkdirStep(bundleVariantDestinationPath)); Path destinationPath = bundleVariantDestinationPath.resolve(variantFilePath.getFileName()); addResourceProcessingSteps(variantFilePath, destinationPath, stepsBuilder); } } if (assetCatalog.isPresent()) { Path bundleDir = assetCatalog.get().getOutputDir(); stepsBuilder.add( CopyStep.forDirectory(bundleDir, bundleRoot, CopyStep.DirectoryMode.CONTENTS_ONLY)); } // Copy the .mobileprovision file if the platform requires it. if (provisioningProfiles.isPresent()) { Optional<Path> entitlementsPlist = Optional.absent(); final String srcRoot = context.getProjectRoot().resolve(getBuildTarget().getBasePath()).toString(); Optional<String> entitlementsPlistString = InfoPlistSubstitution.getVariableExpansionForPlatform( CODE_SIGN_ENTITLEMENTS, platformName, withDefaults( infoPlistSubstitutions, ImmutableMap.of( "SOURCE_ROOT", srcRoot, "SRCROOT", srcRoot))); if (entitlementsPlistString.isPresent()) { entitlementsPlist = Optional.of(Paths.get(entitlementsPlistString.get())); } final Path signingEntitlementsTempPath = BuildTargets.getScratchPath(getBuildTarget(), "%s.xcent"); stepsBuilder.add( new ProvisioningProfileCopyStep( infoPlistOutputPath, Optional.<String>absent(), // Provisioning profile UUID -- find automatically. entitlementsPlist, provisioningProfiles.get(), bundleDestinationPath.resolve("embedded.mobileprovision"), signingEntitlementsTempPath)); stepsBuilder.add( new CodeSignStep( bundleDestinationPath, signingEntitlementsTempPath, codeSignIdentity.get().getHash())); } // Ensure the bundle directory is archived so we can fetch it later. buildableContext.recordArtifact(getPathToOutput()); return stepsBuilder.build(); }
AppleBundle( BuildRuleParams params, SourcePathResolver resolver, Either<AppleBundleExtension, String> extension, SourcePath infoPlist, Map<String, String> infoPlistSubstitutions, Optional<BuildRule> binary, AppleBundleDestinations destinations, Set<SourcePath> resourceDirs, Set<SourcePath> resourceFiles, Set<SourcePath> dirsContainingResourceDirs, ImmutableSet<SourcePath> extensionBundlePaths, Optional<ImmutableSet<SourcePath>> resourceVariantFiles, Tool ibtool, Tool dsymutil, Tool strip, Optional<AppleAssetCatalog> assetCatalog, Set<BuildTarget> tests, AppleSdk sdk, ImmutableSet<CodeSignIdentity> allValidCodeSignIdentities, Optional<SourcePath> provisioningProfileSearchPath) { super(params, resolver); this.extension = extension.isLeft() ? extension.getLeft().toFileExtension() : extension.getRight(); this.infoPlist = infoPlist; this.infoPlistSubstitutions = ImmutableMap.copyOf(infoPlistSubstitutions); this.binary = binary; this.destinations = destinations; this.resourceDirs = resourceDirs; this.resourceFiles = resourceFiles; this.dirsContainingResourceDirs = dirsContainingResourceDirs; this.extensionBundlePaths = extensionBundlePaths; this.resourceVariantFiles = resourceVariantFiles; this.ibtool = ibtool; this.dsymutil = dsymutil; this.strip = strip; this.assetCatalog = assetCatalog; this.binaryName = getBinaryName(getBuildTarget()); this.bundleRoot = getBundleRoot(getBuildTarget(), this.extension); this.binaryPath = this.destinations.getExecutablesPath().resolve(this.binaryName); this.tests = ImmutableSortedSet.copyOf(tests); this.platformName = sdk.getApplePlatform().getName(); this.sdkName = sdk.getName(); // We need to resolve the possible set of profiles and code sign identity at construction time // because they form part of the rule key. if (binary.isPresent() && ApplePlatform.needsCodeSign(this.platformName)) { final Path searchPath; if (provisioningProfileSearchPath.isPresent()) { searchPath = resolver.getResolvedPath(provisioningProfileSearchPath.get()); } else { searchPath = Paths.get( System.getProperty("user.home") + "/Library/MobileDevice/Provisioning Profiles"); } Optional<ImmutableSet<ProvisioningProfileMetadata>> provisioningProfiles; try { provisioningProfiles = Optional.of(ProvisioningProfileCopyStep.findProfilesInPath(searchPath)); } catch (InterruptedException e) { // We get here if the user pressed Ctrl-C during the profile discovery step. // In this case, we'll fail anyway since the set of profiles will be empty. provisioningProfiles = Optional.of(ImmutableSet.<ProvisioningProfileMetadata>of()); } this.provisioningProfiles = provisioningProfiles; Optional<CodeSignIdentity> foundIdentity = Optional.absent(); Optional<String> customIdentity = InfoPlistSubstitution.getVariableExpansionForPlatform( CODE_SIGN_IDENTITY, this.platformName, this.infoPlistSubstitutions); if (customIdentity.isPresent()) { LOG.debug("Bundle specifies custom code signing identity: " + customIdentity.get()); if (CodeSignIdentity.isHash(customIdentity.get())) { for (CodeSignIdentity identity : allValidCodeSignIdentities) { if (identity.getHash().equals(customIdentity.get())) { foundIdentity = Optional.of(identity); break; } } } else { for (CodeSignIdentity identity : allValidCodeSignIdentities) { if (identity.getFullName().startsWith(customIdentity.get())) { foundIdentity = Optional.of(identity); break; } } } } else if (!allValidCodeSignIdentities.isEmpty()) { LOG.debug("Using default code signing identity"); Iterator<CodeSignIdentity> it = allValidCodeSignIdentities.iterator(); foundIdentity = Optional.of(it.next()); } if (!foundIdentity.isPresent()) { throw new HumanReadableException( "The platform " + platformName + " for this target " + "requires code signing but couldn't find a compatible code signing identity to use."); } LOG.debug("Code signing identity is " + foundIdentity.toString()); this.codeSignIdentity = foundIdentity; } else { this.provisioningProfiles = Optional.absent(); this.codeSignIdentity = Optional.absent(); } }