// 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.AppleWatch1ExtensionRule.WATCH_APP_DEPS_ATTR; import static com.google.devtools.build.lib.rules.objc.ObjcProvider.FLAG; import static com.google.devtools.build.lib.rules.objc.ObjcProvider.Flag.HAS_WATCH1_EXTENSION; import static com.google.devtools.build.lib.rules.objc.ObjcProvider.MERGE_ZIP; import static com.google.devtools.build.lib.rules.objc.ObjcRuleClasses.WatchApplicationBundleRule.WATCH_APP_NAME_ATTR; import com.google.common.base.Joiner; 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.ConfiguredTarget; import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode; import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder; import com.google.devtools.build.lib.analysis.RuleContext; import com.google.devtools.build.lib.analysis.config.BuildOptions; import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; import com.google.devtools.build.lib.packages.Attribute.SplitTransition; import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory; import com.google.devtools.build.lib.rules.apple.AppleConfiguration.ConfigurationDistinguisher; import com.google.devtools.build.lib.rules.objc.IosExtension.ExtensionSplitArchTransition; import com.google.devtools.build.lib.rules.objc.WatchUtils.WatchOSVersion; import com.google.devtools.build.lib.rules.test.InstrumentedFilesCollector; import com.google.devtools.build.lib.rules.test.InstrumentedFilesProvider; import com.google.devtools.build.lib.syntax.Type; /** * Implementation for {@code apple_watch1_extension}. * * @deprecated The native bundling rules have been deprecated. This class will be removed in the * future. */ @Deprecated public class AppleWatch1Extension implements RuleConfiguredTargetFactory { static final SplitTransition<BuildOptions> MINIMUM_OS_AND_SPLIT_ARCH_TRANSITION = new ExtensionSplitArchTransition(WatchUtils.MINIMUM_OS_VERSION, ConfigurationDistinguisher.WATCH_OS1_EXTENSION); private static final ImmutableSet<Attribute> extensionDependencyAttributes = ImmutableSet.of(new Attribute("binary", Mode.SPLIT)); private static final ImmutableSet<Attribute> applicationDependencyAttributes = ImmutableSet.of(new Attribute(WATCH_APP_DEPS_ATTR, Mode.SPLIT)); @Override public ConfiguredTarget create(RuleContext ruleContext) throws InterruptedException, RuleErrorException { ruleContext.ruleWarning( "This rule is deprecated. Please use the new Apple build rules " + "(https://github.com/bazelbuild/rules_apple) to build Apple targets."); ObjcProvider.Builder extensionObjcProviderBuilder = new ObjcProvider.Builder(); XcodeProvider.Builder applicationXcodeProviderBuilder = new XcodeProvider.Builder(); XcodeProvider.Builder extensionXcodeProviderBuilder = new XcodeProvider.Builder(); NestedSetBuilder<Artifact> applicationFilesToBuild = NestedSetBuilder.stableOrder(); NestedSetBuilder<Artifact> extensionfilesToBuild = NestedSetBuilder.stableOrder(); // 1. Build watch application bundle. createWatchApplicationBundle( ruleContext, applicationXcodeProviderBuilder, applicationFilesToBuild); // 2. Build watch extension bundle. createWatchExtensionBundle(ruleContext, extensionXcodeProviderBuilder, applicationXcodeProviderBuilder, extensionObjcProviderBuilder, extensionfilesToBuild); // 3. Extract the watch application bundle into the extension bundle. registerWatchApplicationUnBundlingAction(ruleContext); RuleConfiguredTargetBuilder targetBuilder = ObjcRuleClasses.ruleConfiguredTarget(ruleContext, extensionfilesToBuild.build()) .addProvider(XcodeProvider.class, extensionXcodeProviderBuilder.build()) .addProvider( InstrumentedFilesProvider.class, InstrumentedFilesCollector.forward(ruleContext, "binary")); // 4. Exposed {@ObjcProvider} for bundling into final IPA. exposeObjcProvider(ruleContext, targetBuilder, extensionObjcProviderBuilder); return targetBuilder.build(); } /** * Exposes an {@link ObjcProvider} with the following to create the final IPA: * 1. Watch extension bundle. * 2. WatchKitSupport. * 3. A flag to indicate that watch os 1 extension is included. */ private void exposeObjcProvider( RuleContext ruleContext, RuleConfiguredTargetBuilder targetBuilder, ObjcProvider.Builder exposedObjcProviderBuilder) throws InterruptedException { exposedObjcProviderBuilder.add(MERGE_ZIP, ruleContext.getImplicitOutputArtifact(ReleaseBundlingSupport.IPA)); WatchUtils.registerActionsToAddWatchSupport(ruleContext, exposedObjcProviderBuilder, WatchOSVersion.OS1); exposedObjcProviderBuilder.add(FLAG, HAS_WATCH1_EXTENSION); targetBuilder.addProvider(ObjcProvider.class, exposedObjcProviderBuilder.build()); } /** * Creates a watch extension bundle. * * @param ruleContext rule context in which to create the bundle * @param extensionXcodeProviderBuilder {@link XcodeProvider.Builder} for the extension * @param applicationXcodeProviderBuilder {@link XcodeProvider.Builder} for the watch application * which is added as a dependency to the extension * @param objcProviderBuilder {@link ObjcProvider.Builder} for the extension * @param filesToBuild the list to contain the files to be built for this extension bundle */ private void createWatchExtensionBundle(RuleContext ruleContext, XcodeProvider.Builder extensionXcodeProviderBuilder, XcodeProvider.Builder applicationXcodeProviderBuilder, ObjcProvider.Builder objcProviderBuilder, NestedSetBuilder<Artifact> filesToBuild) throws InterruptedException { new WatchExtensionSupport(ruleContext, extensionDependencyAttributes, ObjcRuleClasses.intermediateArtifacts(ruleContext), watchExtensionBundleName(ruleContext), watchExtensionIpaArtifact(ruleContext), watchApplicationBundle(ruleContext), applicationXcodeProviderBuilder.build(), ConfigurationDistinguisher.WATCH_OS1_EXTENSION) .createBundle(filesToBuild, objcProviderBuilder, extensionXcodeProviderBuilder); } /** * Creates a watch application bundle. * @param ruleContext rule context in which to create the bundle * @param xcodeProviderBuilder {@link XcodeProvider.Builder} for the application * @param filesToBuild the list to contain the files to be built for this bundle */ private void createWatchApplicationBundle( RuleContext ruleContext, XcodeProvider.Builder xcodeProviderBuilder, NestedSetBuilder<Artifact> filesToBuild) throws InterruptedException { new WatchApplicationSupport( ruleContext, WatchOSVersion.OS1, applicationDependencyAttributes, new IntermediateArtifacts(ruleContext, "", watchApplicationBundleName(ruleContext)), watchApplicationBundleName(ruleContext), watchApplicationIpaArtifact(ruleContext), watchApplicationBundleName(ruleContext)) .createBundleAndXcodeproj( xcodeProviderBuilder, ImmutableList.<Artifact>of(), filesToBuild); } /** * Registers action to extract the watch application ipa (after signing if required) to the * extension bundle. * * For example, TestWatchApp.ipa will be unbundled into, * PlugIns/TestWatchExtension.appex * PlugIns/TestWatchExtension.appex/TestWatchApp.app */ private void registerWatchApplicationUnBundlingAction(RuleContext ruleContext) { Artifact watchApplicationIpa = watchApplicationIpaArtifact(ruleContext); Artifact watchApplicationBundle = watchApplicationBundle(ruleContext); String workingDirectory = watchApplicationBundle.getExecPathString().substring(0, watchApplicationBundle.getExecPathString().lastIndexOf('/')); ImmutableList<String> command = ImmutableList.of( "mkdir -p " + workingDirectory, "&&", String.format("/usr/bin/unzip -q -o %s -d %s", watchApplicationIpa.getExecPathString(), workingDirectory), "&&", String.format("cd %s/Payload", workingDirectory), "&&", String.format("/usr/bin/zip -q -r -0 ../%s *", watchApplicationBundle.getFilename())); ruleContext.registerAction( ObjcRuleClasses.spawnOnDarwinActionBuilder() .setProgressMessage("Extracting watch app: " + ruleContext.getLabel()) .setShellCommand(ImmutableList.of("/bin/bash", "-c", Joiner.on(" ").join(command))) .addInput(watchApplicationIpa) .addOutput(watchApplicationBundle) .build(ruleContext)); } /** * Returns a zip {@Artifact} containing extracted watch application - "TestWatchApp.app" * which is to be merged into the extension bundle. */ private Artifact watchApplicationBundle(RuleContext ruleContext) { return ruleContext.getRelatedArtifact(ruleContext.getUniqueDirectory( "_watch"), String.format("/%s", watchApplicationIpaArtifact(ruleContext) .getFilename().replace(".ipa", ".zip"))); } /** * Returns the {@Artifact} containing final watch application bundle. */ private Artifact watchApplicationIpaArtifact(RuleContext ruleContext) { return ruleContext.getRelatedArtifact(ruleContext.getUniqueDirectory("_watch"), String.format("/%s.ipa", watchApplicationBundleName(ruleContext))); } /** * Returns the {@Artifact} containing final watch extension bundle. */ private Artifact watchExtensionIpaArtifact(RuleContext ruleContext) throws InterruptedException { return ruleContext.getImplicitOutputArtifact(ReleaseBundlingSupport.IPA); } private String watchApplicationBundleName(RuleContext ruleContext) { return ruleContext.attributes().get(WATCH_APP_NAME_ATTR, Type.STRING); } private String watchExtensionBundleName(RuleContext ruleContext) { return ruleContext.getLabel().getName(); } }