| // Copyright 2015 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.apple; |
| |
| import com.google.common.base.Optional; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.devtools.build.lib.analysis.config.BuildConfiguration; |
| import com.google.devtools.build.lib.analysis.config.BuildOptions; |
| import com.google.devtools.build.lib.analysis.config.ConfigurationEnvironment; |
| import com.google.devtools.build.lib.analysis.config.ConfigurationFragmentFactory; |
| import com.google.devtools.build.lib.analysis.config.FragmentOptions; |
| import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException; |
| import com.google.devtools.build.lib.cmdline.Label; |
| import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; |
| import com.google.devtools.build.lib.rules.apple.AppleCommandLineOptions.AppleBitcodeMode; |
| import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable; |
| import com.google.devtools.build.lib.skylarkinterface.SkylarkModule; |
| import com.google.devtools.build.lib.util.Preconditions; |
| |
| import java.util.Map; |
| |
| import javax.annotation.Nullable; |
| |
| /** |
| * A configuration containing flags required for Apple platforms and tools. |
| */ |
| @SkylarkModule(name = "apple", doc = "A configuration fragment for Apple platforms") |
| @Immutable |
| public class AppleConfiguration extends BuildConfiguration.Fragment { |
| public static final String XCODE_VERSION_ENV_NAME = "XCODE_VERSION_OVERRIDE"; |
| /** |
| * Environment variable name for the apple SDK version. If unset, uses the system default of the |
| * host for the platform in the value of {@link #APPLE_SDK_PLATFORM_ENV_NAME}. |
| **/ |
| public static final String APPLE_SDK_VERSION_ENV_NAME = "APPLE_SDK_VERSION_OVERRIDE"; |
| /** |
| * Environment variable name for the apple SDK platform. This should be set for all actions that |
| * require an apple SDK. The valid values consist of {@link Platform} names. |
| **/ |
| public static final String APPLE_SDK_PLATFORM_ENV_NAME = "APPLE_SDK_PLATFORM"; |
| |
| private static final DottedVersion MINIMUM_BITCODE_XCODE_VERSION = DottedVersion.fromString("7"); |
| |
| private final DottedVersion iosSdkVersion; |
| private final DottedVersion watchOsSdkVersion; |
| private final DottedVersion tvOsSdkVersion; |
| private final DottedVersion macOsXSdkVersion; |
| private final String iosCpu; |
| private final Optional<DottedVersion> xcodeVersion; |
| private final ImmutableList<String> iosMultiCpus; |
| private final AppleBitcodeMode bitcodeMode; |
| private final Label xcodeConfigLabel; |
| @Nullable private final Label defaultProvisioningProfileLabel; |
| |
| AppleConfiguration(AppleCommandLineOptions appleOptions, |
| Optional<DottedVersion> xcodeVersionOverride, |
| DottedVersion iosSdkVersion) { |
| this.iosSdkVersion = Preconditions.checkNotNull(iosSdkVersion, "iosSdkVersion"); |
| this.watchOsSdkVersion = |
| Preconditions.checkNotNull(appleOptions.watchOsSdkVersion, "watchOsSdkVersion"); |
| this.tvOsSdkVersion = |
| Preconditions.checkNotNull(appleOptions.tvOsSdkVersion, "tvOsSdkVersion"); |
| this.macOsXSdkVersion = |
| Preconditions.checkNotNull(appleOptions.macOsXSdkVersion, "macOsXSdkVersion"); |
| |
| this.xcodeVersion = Preconditions.checkNotNull(xcodeVersionOverride); |
| this.iosCpu = Preconditions.checkNotNull(appleOptions.iosCpu, "iosCpu"); |
| this.iosMultiCpus = ImmutableList.copyOf( |
| Preconditions.checkNotNull(appleOptions.iosMultiCpus, "iosMultiCpus")); |
| this.bitcodeMode = appleOptions.appleBitcodeMode; |
| this.xcodeConfigLabel = |
| Preconditions.checkNotNull(appleOptions.xcodeVersionConfig, "xcodeConfigLabel"); |
| this.defaultProvisioningProfileLabel = appleOptions.defaultProvisioningProfile; |
| } |
| |
| /** |
| * Returns the SDK version for ios SDKs (whether they be for simulator or device). This is |
| * directly derived from --ios_sdk_version. |
| * |
| * @deprecated - use {@link #getSdkVersionForPlatform()} |
| */ |
| @Deprecated public DottedVersion getIosSdkVersion() { |
| return getSdkVersionForPlatform(Platform.IOS_DEVICE); |
| } |
| |
| /** |
| * Returns the SDK version for a platform (whether they be for simulator or device). This is |
| * directly derived from command line args. |
| */ |
| public DottedVersion getSdkVersionForPlatform(Platform platform) { |
| switch (platform) { |
| case IOS_DEVICE: |
| case IOS_SIMULATOR: |
| return iosSdkVersion; |
| case TVOS_DEVICE: |
| case TVOS_SIMULATOR: |
| return tvOsSdkVersion; |
| case WATCHOS_DEVICE: |
| case WATCHOS_SIMULATOR: |
| return watchOsSdkVersion; |
| case MACOS_X: |
| return macOsXSdkVersion; |
| } |
| throw new AssertionError(); |
| |
| } |
| |
| /** |
| * Returns the value of the xcode version, if available. This is determined based on a combination |
| * of the {@code --xcode_version} build flag and the {@code xcode_config} target defined in the |
| * {@code --xcode_version_config} flag. |
| */ |
| public Optional<DottedVersion> getXcodeVersion() { |
| return xcodeVersion; |
| } |
| |
| /** |
| * Returns a map of environment variables (derived from configuration) that should be propagated |
| * for actions pertaining to building ios applications. Keys are variable names and values are |
| * their corresponding values. |
| */ |
| // TODO(bazel-team): Repurpose for non-ios platforms. |
| public Map<String, String> getEnvironmentForIosAction() { |
| ImmutableMap.Builder<String, String> mapBuilder = ImmutableMap.builder(); |
| mapBuilder.putAll(appleTargetPlatformEnv(Platform.forIosArch(getIosCpu()))); |
| return mapBuilder.build(); |
| } |
| |
| /** |
| * Returns a map of environment variables that should be propagated for actions that build on an |
| * apple host system. These environment variables are needed by the apple toolchain. Keys are |
| * variable names and values are their corresponding values. |
| */ |
| @SkylarkCallable( |
| name = "apple_host_system_env", |
| doc = |
| "Returns a map of environment variables that should be propagated for actions that " |
| + "build on an apple host system. These environment variables are needed by the apple " |
| + "toolchain. Keys are variable names and values are their corresponding values." |
| ) |
| public Map<String, String> getAppleHostSystemEnv() { |
| Optional<DottedVersion> xcodeVersion = getXcodeVersion(); |
| ImmutableMap.Builder<String, String> builder = ImmutableMap.builder(); |
| if (xcodeVersion.isPresent()) { |
| builder.put(AppleConfiguration.XCODE_VERSION_ENV_NAME, xcodeVersion.get().toString()); |
| } |
| return builder.build(); |
| } |
| |
| /** |
| * Returns a map of environment variables (derived from configuration) that should be propagated |
| * for actions pertaining to building applications for apple platforms. These environment |
| * variables are needed to use apple toolkits. Keys are variable names and values are their |
| * corresponding values. |
| */ |
| public Map<String, String> appleTargetPlatformEnv(Platform platform) { |
| ImmutableMap.Builder<String, String> builder = ImmutableMap.builder(); |
| |
| // TODO(bazel-team): Handle non-ios platforms. |
| if (platform == Platform.IOS_DEVICE || platform == Platform.IOS_SIMULATOR) { |
| String sdkVersion = getSdkVersionForPlatform(platform).toString(); |
| builder.put(AppleConfiguration.APPLE_SDK_VERSION_ENV_NAME, sdkVersion) |
| .put(AppleConfiguration.APPLE_SDK_PLATFORM_ENV_NAME, platform.getNameInPlist()); |
| } |
| return builder.build(); |
| } |
| |
| public String getIosCpu() { |
| return iosCpu; |
| } |
| |
| /** |
| * Returns the platform of the configuration for the current bundle, based on configured |
| * architectures (for example, {@code i386} maps to {@link Platform#IOS_SIMULATOR}). |
| * |
| * <p>If {@link #getIosMultiCpus()} is set, returns {@link Platform#IOS_DEVICE} if any of the |
| * architectures matches it, otherwise returns the mapping for {@link #getIosCpu()}. |
| * |
| * <p>Note that this method should not be used to determine the platform for code compilation. |
| * Derive the platform from {@link #getIosCpu()} instead. |
| */ |
| // TODO(bazel-team): This method should be enabled to return multiple values once all call sites |
| // (in particular actool, bundlemerge, momc) have been upgraded to support multiple values. |
| public Platform getBundlingPlatform() { |
| for (String architecture : getIosMultiCpus()) { |
| if (Platform.forIosArch(architecture) == Platform.IOS_DEVICE) { |
| return Platform.IOS_DEVICE; |
| } |
| } |
| return Platform.forIosArch(getIosCpu()); |
| } |
| |
| /** |
| * Returns the architecture for which we keep dependencies that should be present only once (in a |
| * single architecture). |
| * |
| * <p>When building with multiple architectures there are some dependencies we want to avoid |
| * duplicating: they would show up more than once in the same location in the final application |
| * bundle which is illegal. Instead we pick one architecture for which to keep all dependencies |
| * and discard any others. |
| */ |
| public String getDependencySingleArchitecture() { |
| if (!getIosMultiCpus().isEmpty()) { |
| return getIosMultiCpus().get(0); |
| } |
| return getIosCpu(); |
| } |
| |
| /** |
| * List of all CPUs that this invocation is being built for. Different from {@link #getIosCpu()} |
| * which is the specific CPU <b>this target</b> is being built for. |
| */ |
| public ImmutableList<String> getIosMultiCpus() { |
| return iosMultiCpus; |
| } |
| |
| /** |
| * Returns the label of the default provisioning profile to use when bundling/signing an ios |
| * application. Returns null if the target platform is not an iOS device (for example, if |
| * iOS simulator is being targeted). |
| */ |
| @Nullable public Label getDefaultProvisioningProfileLabel() { |
| return defaultProvisioningProfileLabel; |
| } |
| |
| /** |
| * Returns the bitcode mode to use for compilation steps. Users can control bitcode |
| * mode using the {@code apple_bitcode} build flag. |
| * |
| * @see AppleBitcodeMode |
| */ |
| public AppleBitcodeMode getBitcodeMode() { |
| return bitcodeMode; |
| } |
| |
| /** |
| * Returns the label of the xcode_config rule to use for resolving the host system xcode version. |
| */ |
| public Label getXcodeConfigLabel() { |
| return xcodeConfigLabel; |
| } |
| |
| /** |
| * Loads {@link AppleConfiguration} from build options. |
| */ |
| public static class Loader implements ConfigurationFragmentFactory { |
| @Override |
| public AppleConfiguration create(ConfigurationEnvironment env, BuildOptions buildOptions) |
| throws InvalidConfigurationException { |
| AppleCommandLineOptions appleOptions = buildOptions.get(AppleCommandLineOptions.class); |
| XcodeVersionProperties xcodeVersionProperties = getXcodeVersionProperties(env, appleOptions); |
| |
| DottedVersion iosSdkVersion = (appleOptions.iosSdkVersion != null) |
| ? appleOptions.iosSdkVersion : xcodeVersionProperties.getDefaultIosSdkVersion(); |
| AppleConfiguration configuration = |
| new AppleConfiguration(appleOptions, xcodeVersionProperties.getXcodeVersion(), |
| iosSdkVersion); |
| |
| validate(configuration); |
| return configuration; |
| } |
| |
| private void validate(AppleConfiguration config) |
| throws InvalidConfigurationException { |
| Optional<DottedVersion> xcodeVersion = config.getXcodeVersion(); |
| if (config.getBitcodeMode() != AppleBitcodeMode.NONE |
| && xcodeVersion.isPresent() |
| && xcodeVersion.get().compareTo(MINIMUM_BITCODE_XCODE_VERSION) < 0) { |
| throw new InvalidConfigurationException( |
| String.format("apple_bitcode mode '%s' is unsupported for xcode version '%s'", |
| config.getBitcodeMode(), xcodeVersion.get())); |
| } |
| } |
| |
| @Override |
| public Class<? extends BuildConfiguration.Fragment> creates() { |
| return AppleConfiguration.class; |
| } |
| |
| @Override |
| public ImmutableSet<Class<? extends FragmentOptions>> requiredOptions() { |
| return ImmutableSet.<Class<? extends FragmentOptions>>of(AppleCommandLineOptions.class); |
| } |
| |
| /** |
| * Uses the {@link AppleCommandLineOptions#xcodeVersion} and |
| * {@link AppleCommandLineOptions#xcodeVersionConfig} command line options to determine and |
| * return the effective xcode version properties. Returns absent if no explicit xcode version |
| * is declared, and host system defaults should be used. |
| * |
| * @param env the current configuration environment |
| * @param appleOptions the command line options |
| * @throws InvalidConfigurationException if the options given (or configuration targets) were |
| * malformed and thus the xcode version could not be determined |
| */ |
| private XcodeVersionProperties getXcodeVersionProperties(ConfigurationEnvironment env, |
| AppleCommandLineOptions appleOptions) throws InvalidConfigurationException { |
| Optional<DottedVersion> xcodeVersionCommandLineFlag = |
| Optional.fromNullable(appleOptions.xcodeVersion); |
| Label xcodeVersionConfigLabel = appleOptions.xcodeVersionConfig; |
| |
| return XcodeConfig.resolveXcodeVersion(env, xcodeVersionConfigLabel, |
| xcodeVersionCommandLineFlag, "xcode_version_config"); |
| } |
| } |
| } |