com.google.devtools.build.lib.rules.objc.WatchExtensionSupport.java Source code

Java tutorial

Introduction

Here is the source code for com.google.devtools.build.lib.rules.objc.WatchExtensionSupport.java

Source

// 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);
        }
    }
}