blob: f4243f8bb1cbcc17114df087293499b838ab9966 [file] [log] [blame]
// Copyright 2016 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.devtools.build.lib.rules.objc;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.devtools.build.lib.rules.objc.ObjcProvider.BUNDLE_FILE;
import static com.google.devtools.build.lib.rules.objc.ObjcProvider.GENERAL_RESOURCE_DIR;
import static com.google.devtools.build.lib.rules.objc.ObjcProvider.GENERAL_RESOURCE_FILE;
import static com.google.devtools.build.lib.rules.objc.ObjcProvider.MERGE_ZIP;
import static com.google.devtools.build.lib.rules.objc.ObjcProvider.STRINGS;
import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.BundlingRule.FAMILIES_ATTR;
import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchExtensionBundleRule.WATCH_EXT_BUNDLE_ID_ATTR;
import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchExtensionBundleRule.WATCH_EXT_DEFAULT_PROVISIONING_PROFILE_ATTR;
import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchExtensionBundleRule.WATCH_EXT_ENTITLEMENTS_ATTR;
import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchExtensionBundleRule.WATCH_EXT_INFOPLISTS_ATTR;
import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchExtensionBundleRule.WATCH_EXT_PROVISIONING_PROFILE_ATTR;
import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchExtensionBundleRule.WATCH_EXT_RESOURCES_ATTR;
import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchExtensionBundleRule.WATCH_EXT_STRINGS_ATTR;
import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchExtensionBundleRule.WATCH_EXT_STRUCTURED_RESOURCES_ATTR;
import com.dd.plist.NSDictionary;
import com.dd.plist.NSObject;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.actions.FileWriteAction;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.rules.apple.AppleConfiguration;
import com.google.devtools.build.lib.rules.apple.AppleConfiguration.ConfigurationDistinguisher;
import com.google.devtools.build.lib.rules.apple.Platform.PlatformType;
import com.google.devtools.build.lib.rules.objc.ReleaseBundlingSupport.LinkedBinary;
import com.google.devtools.build.lib.rules.objc.TargetDeviceFamily.InvalidFamilyNameException;
import com.google.devtools.build.lib.rules.objc.TargetDeviceFamily.RepeatedFamilyNameException;
import com.google.devtools.build.lib.rules.objc.WatchUtils.WatchOSVersion;
import com.google.devtools.build.lib.syntax.Type;
import java.util.List;
import javax.annotation.Nullable;
/**
* Contains support methods to build WatchOS1 extension bundles - does normal bundle processing -
* compiling and linking the binary, resources, plists and creates a final
* (signed if necessary) bundle.
*/
public class WatchExtensionSupport {
private final RuleContext ruleContext;
private final ImmutableSet<Attribute> dependencyAttributes;
private final IntermediateArtifacts intermediateArtifacts;
private final String bundleName;
private final Artifact ipaArtifact;
private final Artifact watchApplicationBundle;
private final Attributes attributes;
private final XcodeProvider watchApplicationXcodeProvider;
private final ConfigurationDistinguisher configurationDistinguisher;
WatchExtensionSupport(
RuleContext ruleContext,
ImmutableSet<Attribute> dependencyAttributes,
IntermediateArtifacts intermediateArtifacts,
String bundleName,
Artifact ipaArtifact,
@Nullable Artifact watchApplicationBundle,
@Nullable XcodeProvider watchApplicationXcodeProvider,
ConfigurationDistinguisher configurationDistinguisher) {
this.ruleContext = ruleContext;
this.dependencyAttributes = dependencyAttributes;
this.intermediateArtifacts = intermediateArtifacts;
this.bundleName = bundleName;
this.ipaArtifact = ipaArtifact;
this.attributes = new Attributes(ruleContext);
this.watchApplicationXcodeProvider = checkNotNull(watchApplicationXcodeProvider);
this.watchApplicationBundle = checkNotNull(watchApplicationBundle);
this.configurationDistinguisher = configurationDistinguisher;
}
void createBundle(NestedSetBuilder<Artifact> filesToBuild,
ObjcProvider.Builder exposedObjcProviderBuilder, XcodeProvider.Builder xcodeProviderBuilder)
throws InterruptedException {
ObjcProvider releaseBundlingObjcProvider = releaseBundlingObjcProvider();
WatchUtils.addXcodeSettings(ruleContext, xcodeProviderBuilder);
registerWatchExtensionAutomaticPlistAction();
ImmutableSet<TargetDeviceFamily> families = attributes.families();
if (families.isEmpty()) {
ruleContext.attributeError(FAMILIES_ATTR, ReleaseBundling.INVALID_FAMILIES_ERROR);
}
ReleaseBundling.Builder releaseBundling = new ReleaseBundling.Builder()
.setIpaArtifact(ipaArtifact)
.setBundleId(attributes.bundleId())
.setProvisioningProfile(attributes.provisioningProfile())
.setProvisioningProfileAttributeName(WATCH_EXT_PROVISIONING_PROFILE_ATTR)
.setTargetDeviceFamilies(families)
.setIntermediateArtifacts(intermediateArtifacts)
.setInfoPlistsFromRule(attributes.infoPlists())
.addInfoplistInput(watchExtensionAutomaticPlist())
.setEntitlements(attributes.entitlements());
if (attributes.isBundleIdExplicitySpecified()) {
releaseBundling.setPrimaryBundleId(attributes.bundleId());
} else {
releaseBundling.setFallbackBundleId(attributes.bundleId());
}
AppleConfiguration appleConfiguration = ruleContext.getFragment(AppleConfiguration.class);
ReleaseBundlingSupport releaseBundlingSupport =
new ReleaseBundlingSupport(
ruleContext,
releaseBundlingObjcProvider,
LinkedBinary.DEPENDENCIES_ONLY,
ReleaseBundlingSupport.EXTENSION_BUNDLE_DIR_FORMAT,
bundleName,
WatchUtils.determineMinimumIosVersion(
appleConfiguration.getMinimumOsForPlatformType(PlatformType.IOS)),
releaseBundling.build(),
appleConfiguration.getMultiArchPlatform(PlatformType.IOS));
releaseBundlingSupport.registerActions(DsymOutputType.APP);
releaseBundlingSupport.addXcodeSettings(xcodeProviderBuilder);
releaseBundlingSupport
.addFilesToBuild(filesToBuild, Optional.of(DsymOutputType.APP))
.validateResources()
.validateAttributes()
.addExportedDebugArtifacts(exposedObjcProviderBuilder, DsymOutputType.APP);
XcodeSupport xcodeSupport =
new XcodeSupport(ruleContext)
.addFilesToBuild(filesToBuild)
.addXcodeSettings(
xcodeProviderBuilder,
releaseBundlingObjcProvider,
WatchOSVersion.OS1.getExtensionXcodeProductType(),
ruleContext
.getFragment(AppleConfiguration.class)
.getDependencySingleArchitecture(),
configurationDistinguisher)
.addDummySource(xcodeProviderBuilder);
for (Attribute attribute : dependencyAttributes) {
xcodeSupport.addDependencies(xcodeProviderBuilder, attribute);
}
// Generate xcodeproj for watch OS 1 extension as the main target with watch application
// target as the dependency.
xcodeProviderBuilder.addPropagatedDependencies(
ImmutableList.of(watchApplicationXcodeProvider));
xcodeSupport.registerActions(xcodeProviderBuilder.build());
}
/**
* Registers an action to generate a plist containing entries required for watch extension that
* should be added to the merged plist.
*/
private void registerWatchExtensionAutomaticPlistAction() {
List<String> uiRequiredDeviceCapabilities = ImmutableList.of("watch-companion");
NSDictionary watchExtensionAutomaticEntries = new NSDictionary();
watchExtensionAutomaticEntries.put("UIRequiredDeviceCapabilities",
NSObject.wrap(uiRequiredDeviceCapabilities.toArray()));
ruleContext.registerAction(
FileWriteAction.create(
ruleContext,
watchExtensionAutomaticPlist(),
watchExtensionAutomaticEntries.toGnuStepASCIIPropertyList(),
/*makeExecutable=*/ false));
}
private Artifact watchExtensionAutomaticPlist() {
return ruleContext.getRelatedArtifact(
ruleContext.getUniqueDirectory("plists"), "-automatic-watchExtensionInfo.plist");
}
private ObjcProvider releaseBundlingObjcProvider() {
ObjcProvider.Builder objcProviderBuilder = new ObjcProvider.Builder();
// Add dependency providers.
for (Attribute attribute : dependencyAttributes) {
objcProviderBuilder.addTransitiveAndPropagate(
ruleContext.getPrerequisites(
attribute.getName(), attribute.getAccessMode(), ObjcProvider.class));
}
// Expose the generated watch application bundle to the extension bundle.
objcProviderBuilder.add(MERGE_ZIP, watchApplicationBundle);
// Add resource files.
objcProviderBuilder.addAll(GENERAL_RESOURCE_FILE, attributes.resources())
.addAll(GENERAL_RESOURCE_FILE, attributes.strings())
.addAll(GENERAL_RESOURCE_DIR, ObjcCommon.xcodeStructuredResourceDirs(
attributes.structuredResources()))
.addAll(BUNDLE_FILE, BundleableFile.flattenedRawResourceFiles(attributes.resources()))
.addAll(
BUNDLE_FILE,
BundleableFile.structuredRawResourceFiles(attributes.structuredResources()))
.addAll(STRINGS, attributes.strings());
return objcProviderBuilder.build();
}
/**
* Rule attributes used for creating watch application bundle.
*/
private static class Attributes {
private final RuleContext ruleContext;
private Attributes(RuleContext ruleContext) {
this.ruleContext = ruleContext;
}
/**
* Returns the value of the {@code families} attribute in a form
* that is more useful than a list of strings. Returns an empty
* set for any invalid {@code families} attribute value, including
* an empty list.
*/
ImmutableSet<TargetDeviceFamily> families() {
List<String> rawFamilies = ruleContext.attributes().get(
AppleWatch1ExtensionRule.WATCH_EXT_FAMILIES_ATTR, Type.STRING_LIST);
try {
return ImmutableSet.copyOf(TargetDeviceFamily.fromNamesInRule(rawFamilies));
} catch (InvalidFamilyNameException | RepeatedFamilyNameException e) {
return ImmutableSet.of();
}
}
@Nullable
Artifact provisioningProfile() {
Artifact explicitProvisioningProfile =
getPrerequisiteArtifact(WATCH_EXT_PROVISIONING_PROFILE_ATTR);
if (explicitProvisioningProfile != null) {
return explicitProvisioningProfile;
}
return getPrerequisiteArtifact(WATCH_EXT_DEFAULT_PROVISIONING_PROFILE_ATTR);
}
String bundleId() {
Preconditions.checkState(!Strings.isNullOrEmpty(
ruleContext.attributes().get(WATCH_EXT_BUNDLE_ID_ATTR, Type.STRING)),
"requires a bundle_id value");
return ruleContext.attributes().get(WATCH_EXT_BUNDLE_ID_ATTR, Type.STRING);
}
ImmutableList<Artifact> infoPlists() {
return getPrerequisiteArtifacts(WATCH_EXT_INFOPLISTS_ATTR);
}
ImmutableList<Artifact> strings() {
return getPrerequisiteArtifacts(WATCH_EXT_STRINGS_ATTR);
}
ImmutableList<Artifact> resources() {
return getPrerequisiteArtifacts(WATCH_EXT_RESOURCES_ATTR);
}
ImmutableList<Artifact> structuredResources() {
return getPrerequisiteArtifacts(WATCH_EXT_STRUCTURED_RESOURCES_ATTR);
}
@Nullable
Artifact entitlements() {
return getPrerequisiteArtifact(WATCH_EXT_ENTITLEMENTS_ATTR);
}
private boolean isBundleIdExplicitySpecified() {
return ruleContext.attributes().isAttributeValueExplicitlySpecified(WATCH_EXT_BUNDLE_ID_ATTR);
}
private ImmutableList<Artifact> getPrerequisiteArtifacts(String attribute) {
return ruleContext.getPrerequisiteArtifacts(attribute, Mode.TARGET).list();
}
@Nullable
private Artifact getPrerequisiteArtifact(String attribute) {
return ruleContext.getPrerequisiteArtifact(attribute, Mode.TARGET);
}
}
}