blob: 228445f4435782a4ebd3cc6c7084f772b3674729 [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.devtools.build.lib.rules.objc.ObjcProvider.ROOT_MERGE_ZIP;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Ordering;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.rules.apple.AppleConfiguration;
import com.google.devtools.build.lib.rules.apple.DottedVersion;
import com.google.devtools.build.lib.rules.apple.Platform.PlatformType;
import com.google.devtools.build.xcode.xcodegen.proto.XcodeGenProtos.XcodeprojBuildSetting;
/**
* Contains support methods for common processing and generating of watch extension and application
* bundles.
*/
// TODO(b/30503590): Refactor this into a support class -- such classes are better than this static
// utility.
final class WatchUtils {
@VisibleForTesting
/** Bundle directory format for watch applications for watch OS 2. */
static final String WATCH2_APP_BUNDLE_DIR_FORMAT = "Watch/%s.app";
/**
* Supported Apple watch OS versions.
*/
enum WatchOSVersion {
OS1(
XcodeProductType.WATCH_OS1_APPLICATION,
XcodeProductType.WATCH_OS1_EXTENSION,
ReleaseBundlingSupport.APP_BUNDLE_DIR_FORMAT,
"WatchKitSupport"),
OS2(
XcodeProductType.WATCH_OS2_APPLICATION,
XcodeProductType.WATCH_OS2_EXTENSION,
WATCH2_APP_BUNDLE_DIR_FORMAT,
"WatchKitSupport2");
private final XcodeProductType applicationXcodeProductType;
private final XcodeProductType extensionXcodeProductType;
private final String applicationBundleDirFormat;
private final String watchKitSupportDirName;
WatchOSVersion(
XcodeProductType applicationXcodeProductType,
XcodeProductType extensionXcodeProductType,
String applicationBundleDirFormat,
String watchKitSupportDirName) {
this.applicationXcodeProductType = applicationXcodeProductType;
this.extensionXcodeProductType = extensionXcodeProductType;
this.applicationBundleDirFormat = applicationBundleDirFormat;
this.watchKitSupportDirName = watchKitSupportDirName;
}
/**
* Returns the {@link XcodeProductType} to be used for the watch application's Xcode target.
*/
XcodeProductType getApplicationXcodeProductType() {
return applicationXcodeProductType;
}
/**
* Returns the {@link XcodeProductType} to be used for the watch extension's Xcode target.
*/
XcodeProductType getExtensionXcodeProductType() {
return extensionXcodeProductType;
}
/** Returns the bundle directory format of the watch application relative to its container. */
String getApplicationBundleDirFormat() {
return applicationBundleDirFormat;
}
/**
* Returns the name of the directory in the final iOS bundle which should contain the WatchKit
* support stub.
*/
String getWatchKitSupportDirName() {
return watchKitSupportDirName;
}
}
@VisibleForTesting
static final String WATCH_KIT_STUB_PATH =
"${SDKROOT}/Library/Application\\ Support/WatchKit/WK";
// Apple only accepts watch extension and application starting at 8.2.
@VisibleForTesting
static final DottedVersion MINIMUM_OS_VERSION = DottedVersion.fromString("8.2");
/**
* Adds common xcode build settings required for watch bundles to the given xcode provider
* builder.
*/
static void addXcodeSettings(RuleContext ruleContext,
XcodeProvider.Builder xcodeProviderBuilder) {
xcodeProviderBuilder.addMainTargetXcodeprojBuildSettings(xcodeSettings(ruleContext));
}
/**
* Watch Extension are not accepted by Apple below iOS version 8.2. While applications built with
* a minimum iOS version of less than 8.2 may contain watch extension in their bundle, the
* extension itself needs to be built with 8.2 or higher. This logic overrides (if necessary)
* any flag-set minimum iOS version for extensions only so that this requirement is not
* violated.
*/
static DottedVersion determineMinimumIosVersion(DottedVersion fromFlag) {
return Ordering.natural().max(fromFlag, MINIMUM_OS_VERSION);
}
static boolean isBuildingForWatchOS1Version(WatchOSVersion watchOSVersion) {
return watchOSVersion == WatchOSVersion.OS1;
}
/**
* Adds WatchKitSupport stub to the final ipa and exposes it to given @{link ObjcProvider.Builder}
* based on watch OS version.
*
* For example, for watch OS 1, the resulting ipa will have:
* Payload/TestApp.app
* WatchKitSupport
* WatchKitSupport/WK
*/
static void registerActionsToAddWatchSupport(
RuleContext ruleContext, ObjcProvider.Builder objcProviderBuilder,
WatchOSVersion watchOSVersion) {
Artifact watchSupportZip = watchSupportZip(ruleContext);
String workingDirectory = watchSupportZip.getExecPathString()
.substring(0, watchSupportZip.getExecPathString().lastIndexOf('/'));
String watchKitSupportDirName = watchOSVersion.getWatchKitSupportDirName();
String watchKitSupportPath = workingDirectory + "/" + watchKitSupportDirName;
ImmutableList<String> command = ImmutableList.of(
// 1. Copy WK stub binary to watchKitSupportPath.
"mkdir -p " + watchKitSupportPath,
"&&",
String.format("cp -f %s %s", WATCH_KIT_STUB_PATH, watchKitSupportPath),
// 2. cd to working directory.
"&&",
"cd " + workingDirectory,
// 3. Zip watchSupport.
"&&",
String.format(
"/usr/bin/zip -q -r -0 %s %s",
watchSupportZip.getFilename(),
watchKitSupportDirName));
ruleContext.registerAction(
ObjcRuleClasses.spawnAppleEnvActionBuilder(
ruleContext,
ruleContext
.getFragment(AppleConfiguration.class)
.getMultiArchPlatform(PlatformType.WATCHOS))
.setProgressMessage("Copying Watchkit support to app bundle")
.setShellCommand(ImmutableList.of("/bin/bash", "-c", Joiner.on(" ").join(command)))
.addOutput(watchSupportZip)
.build(ruleContext));
objcProviderBuilder.add(ROOT_MERGE_ZIP, watchSupportZip(ruleContext));
}
private static Artifact watchSupportZip(RuleContext ruleContext) {
return ruleContext.getRelatedArtifact(
ruleContext.getUniqueDirectory("_watch"), "/WatchKitSupport.zip");
}
private static Iterable<XcodeprojBuildSetting> xcodeSettings(RuleContext ruleContext) {
ImmutableList.Builder<XcodeprojBuildSetting> xcodeSettings = new ImmutableList.Builder<>();
xcodeSettings.add(
XcodeprojBuildSetting.newBuilder()
.setName("RESOURCES_TARGETED_DEVICE_FAMILY")
.setValue(TargetDeviceFamily.WATCH.getNameInRule())
.build());
xcodeSettings.add(
XcodeprojBuildSetting.newBuilder()
.setName("IPHONEOS_DEPLOYMENT_TARGET")
.setValue(determineMinimumIosVersion(
ObjcRuleClasses.objcConfiguration(ruleContext).getMinimumOs()).toString())
.build());
return xcodeSettings.build();
}
}