// Copyright 2016 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.rules.objc; import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.BundlingRule.FAMILIES_ATTR; 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.BUNDLE_ID_ATTR; import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.ReleaseBundlingRule.DEFAULT_PROVISIONING_PROFILE_ATTR; import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.ReleaseBundlingRule.LAUNCH_IMAGE_ATTR; import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.ReleaseBundlingRule.LAUNCH_STORYBOARD_ATTR; import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.ReleaseBundlingRule.PROVISIONING_PROFILE_ATTR; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Strings; 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.collect.nestedset.NestedSet; import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; 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.syntax.Type; import com.google.devtools.build.lib.util.Preconditions; import java.util.List; /** * Contains information regarding the creation of a released bundle such as an application * or extension. The information which generally includes app icons, launch image, targeted devices * and other data for potential signing is used to create a releasable bundle out of the bundle * created using {@link Bundling} object. */ @Immutable final class ReleaseBundling { static final class Builder { private Artifact ipaArtifact; private String bundleId; private String primaryBundleId; private String fallbackBundleId; private String appIcon; private String launchImage; private Artifact launchStoryboard; private Artifact provisioningProfile; private String provisioningProfileAttributeName; private final NestedSetBuilder infoplistInputs = NestedSetBuilder.stableOrder(); private Iterable infoPlistsFromRule; private ImmutableSet families; private IntermediateArtifacts intermediateArtifacts; private String artifactPrefix; private Artifact entitlements; private Artifact extraEntitlements; public Builder setIpaArtifact(Artifact ipaArtifact) { this.ipaArtifact = ipaArtifact; return this; } public Builder setBundleId(String bundleId) { this.bundleId = bundleId; return this; } public Builder setPrimaryBundleId(String primaryId) { this.primaryBundleId = primaryId; return this; } public Builder setFallbackBundleId(String fallbackId) { this.fallbackBundleId = fallbackId; return this; } public Builder setAppIcon(String appIcon) { this.appIcon = appIcon; return this; } public Builder setLaunchImage(String launchImage) { this.launchImage = launchImage; return this; } public Builder setLaunchStoryboard(Artifact launchStoryboard) { this.launchStoryboard = launchStoryboard; return this; } public Builder setProvisioningProfile(Artifact provisioningProfile) { this.provisioningProfile = provisioningProfile; return this; } public Builder setProvisioningProfileAttributeName(String provisioningProfileAttributeName) { this.provisioningProfileAttributeName = provisioningProfileAttributeName; return this; } public Builder addInfoplistInput(Artifact infoPlist) { this.infoplistInputs.add(infoPlist); return this; } public Builder addInfoplistInputs(Iterable infoplists) { this.infoplistInputs.addAll(infoplists); return this; } public Builder setInfoPlistsFromRule(Iterable infoPlistsFromRule) { this.infoPlistsFromRule = infoPlistsFromRule; return this; } public Builder setIntermediateArtifacts(IntermediateArtifacts intermediateArtifacts) { this.intermediateArtifacts = intermediateArtifacts; return this; } public Builder setTargetDeviceFamilies(ImmutableSet families) { this.families = families; return this; } public Builder setArtifactPrefix(String artifactPrefix) { this.artifactPrefix = artifactPrefix; return this; } public Builder setEntitlements(Artifact entitlements) { this.entitlements = entitlements; return this; } public Builder setExtraEntitlements(Artifact extraEntitlements) { this.extraEntitlements = extraEntitlements; return this; } public ReleaseBundling build() { Preconditions.checkNotNull(intermediateArtifacts, "intermediateArtifacts"); Preconditions.checkNotNull(families, FAMILIES_ATTR); return new ReleaseBundling( ipaArtifact, bundleId, primaryBundleId, fallbackBundleId, appIcon, launchImage, launchStoryboard, provisioningProfile, provisioningProfileAttributeName, infoplistInputs.build(), infoPlistsFromRule, families, intermediateArtifacts, artifactPrefix, entitlements, extraEntitlements); } } /** * Returns a {@link ReleaseBundling} object constructed using the information available in given * context. */ public static ReleaseBundling releaseBundling(RuleContext ruleContext) throws InterruptedException { Preconditions.checkState(!Strings.isNullOrEmpty( ruleContext.attributes().get(BUNDLE_ID_ATTR, Type.STRING)), "requires a bundle_id value"); String primaryBundleId = null; String fallbackBundleId = null; Artifact provisioningProfile; if (ruleContext.attributes().isAttributeValueExplicitlySpecified(BUNDLE_ID_ATTR)) { primaryBundleId = ruleContext.attributes().get(BUNDLE_ID_ATTR, Type.STRING); } else { fallbackBundleId = ruleContext.attributes().get(BUNDLE_ID_ATTR, Type.STRING); } Artifact explicitProvisioningProfile = ruleContext.getPrerequisiteArtifact(PROVISIONING_PROFILE_ATTR, Mode.TARGET); if (explicitProvisioningProfile != null) { provisioningProfile = explicitProvisioningProfile; } else { provisioningProfile = ruleContext.getPrerequisiteArtifact(DEFAULT_PROVISIONING_PROFILE_ATTR, Mode.TARGET); } ImmutableSet families = null; List rawFamilies = ruleContext.attributes().get(FAMILIES_ATTR, Type.STRING_LIST); try { families = ImmutableSet.copyOf(TargetDeviceFamily.fromNamesInRule(rawFamilies)); } catch (InvalidFamilyNameException | RepeatedFamilyNameException e) { families = ImmutableSet.of(); } if (families.isEmpty()) { ruleContext.attributeError(FAMILIES_ATTR, INVALID_FAMILIES_ERROR); } return new ReleaseBundling.Builder() .setIpaArtifact(ruleContext.getImplicitOutputArtifact(ReleaseBundlingSupport.IPA)) .setBundleId(ruleContext.attributes().get(BUNDLE_ID_ATTR, Type.STRING)) .setPrimaryBundleId(primaryBundleId) .setFallbackBundleId(fallbackBundleId) .setAppIcon(Strings.emptyToNull(ruleContext.attributes().get(APP_ICON_ATTR, Type.STRING))) .setLaunchImage(Strings.emptyToNull( ruleContext.attributes().get(LAUNCH_IMAGE_ATTR, Type.STRING))) .setLaunchStoryboard( ruleContext.getPrerequisiteArtifact(LAUNCH_STORYBOARD_ATTR, Mode.TARGET)) .setProvisioningProfile(provisioningProfile) .setProvisioningProfileAttributeName(PROVISIONING_PROFILE_ATTR) .setTargetDeviceFamilies(families) .setIntermediateArtifacts(ObjcRuleClasses.intermediateArtifacts(ruleContext)) .setEntitlements(ruleContext.getPrerequisiteArtifact("entitlements", Mode.TARGET)) .setExtraEntitlements( ruleContext.getPrerequisiteArtifact(":extra_entitlements", Mode.TARGET)) .build(); } @VisibleForTesting static final String INVALID_FAMILIES_ERROR = "Expected one or two strings from the list 'iphone', 'ipad'"; private final Artifact ipaArtifact; private final String bundleId; private final String fallbackBundleId; private final String primaryBundleId; private final String appIcon; private final String launchImage; private final Artifact launchStoryboard; private final Artifact provisioningProfile; private final String provisioningProfileAttributeName; private final NestedSet infoplistInputs; private final ImmutableSet families; private final IntermediateArtifacts intermediateArtifacts; private final Iterable infoPlistsFromRule; private final String artifactPrefix; private final Artifact entitlements; private final Artifact extraEntitlements; private ReleaseBundling( Artifact ipaArtifact, String bundleId, String primaryBundleId, String fallbackBundleId, String appIcon, String launchImage, Artifact launchStoryboard, Artifact provisioningProfile, String provisioningProfileAttributeName, NestedSet infoplistInputs, Iterable infoPlistsFromRule, ImmutableSet families, IntermediateArtifacts intermediateArtifacts, String artifactPrefix, Artifact entitlements, Artifact extraEntitlements) { this.ipaArtifact = Preconditions.checkNotNull(ipaArtifact); this.bundleId = bundleId; this.primaryBundleId = primaryBundleId; this.fallbackBundleId = fallbackBundleId; this.appIcon = appIcon; this.launchImage = launchImage; this.launchStoryboard = launchStoryboard; this.provisioningProfile = provisioningProfile; this.provisioningProfileAttributeName = Preconditions.checkNotNull(provisioningProfileAttributeName); this.infoplistInputs = Preconditions.checkNotNull(infoplistInputs); this.infoPlistsFromRule = infoPlistsFromRule; this.families = Preconditions.checkNotNull(families); this.intermediateArtifacts = Preconditions.checkNotNull(intermediateArtifacts); this.artifactPrefix = artifactPrefix; this.entitlements = entitlements; this.extraEntitlements = extraEntitlements; } /** * Returns the {@link Artifact} containing the final ipa bundle. */ public Artifact getIpaArtifact() { return ipaArtifact; } /** * Returns the identifier of this bundle. */ public String getBundleId() { return bundleId; } /** * Returns primary bundle ID to use, can be null. */ public String getPrimaryBundleId() { return primaryBundleId; } /** * Returns fallback bundle ID to use when primary isn't set. */ public String getFallbackBundleId() { return fallbackBundleId; } /** * Returns the app icon name for this bundle, can be null. */ public String getAppIcon() { return appIcon; } /** * Returns the launch image name for this bundle, can be null. */ public String getLaunchImage() { return launchImage; } /** * Returns an {@link Artifact} containing launch storyboard for this bundle, can be null. */ public Artifact getLaunchStoryboard() { return launchStoryboard; } /** * Returns an {@link Artifact} containing provisioning profile used to sign this bundle, * can be null. */ public Artifact getProvisioningProfile() { return provisioningProfile; } /** * Returns the list of plists to be merged to final bundle. */ public NestedSet getInfoplistInputs() { return infoplistInputs; } /** * Returns the list of {@link TargetDeviceFamily} values this bundle is targeting. * If empty, the default values specified by {@link FAMILIES_ATTR} will be used. */ public ImmutableSet getTargetDeviceFamilies() { return families; } /** * Returns {@link IntermediateArtifacts} used to create this bundle. */ public IntermediateArtifacts getIntermediateArtifacts() { return intermediateArtifacts; } /** * Returns the name of the attribute which is used to specifiy the provisioning profile. */ public String getProvisioningProfileAttrName() { return provisioningProfileAttributeName; } /** * Adds any info plists specified in the given rule's {@code infoplists} attribute as inputs to * this bundle's {@code Info.plist} (which is merged from any such added plists plus some * additional information). */ public Iterable getInfoPlistsFromRule() { return infoPlistsFromRule; } /** * Returns the prefix to be added to all generated artifact names, can be null. This is useful * to disambiguate artifacts for multiple bundles created with different names withing same rule. */ public String getArtifactPrefix() { return artifactPrefix; } /** * Returns an {@link Artifact} containing the entitlements used to sign this bundle for * non-simulator builds; can be null. */ public Artifact getEntitlements() { return entitlements; } /** * Returns an {@link Artifact} containing the extra entitlements passed via command line that is * used to sign this bundle for non-simulator builds; can be null. */ public Artifact getExtraEntitlements() { return extraEntitlements; } }