blob: d8df7fa7d06e5033340a70a9324fd6d4636a594a [file] [log] [blame]
// Copyright 2021 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.android;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.analysis.PlatformOptions;
import com.google.devtools.build.lib.analysis.config.BuildOptions;
import com.google.devtools.build.lib.analysis.config.BuildOptionsView;
import com.google.devtools.build.lib.analysis.config.CoreOptions;
import com.google.devtools.build.lib.analysis.config.FragmentOptions;
import com.google.devtools.build.lib.analysis.config.transitions.SplitTransition;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.events.EventHandler;
import com.google.devtools.build.lib.rules.android.AndroidConfiguration.ConfigurationDistinguisher;
import com.google.devtools.build.lib.rules.cpp.CppOptions;
import com.google.devtools.build.lib.starlarkbuildapi.android.AndroidSplitTransitionApi;
import java.util.List;
import net.starlark.java.eval.Printer;
/** Android Split configuration transition for properly handling native dependencies */
final class AndroidSplitTransition implements SplitTransition, AndroidSplitTransitionApi {
@Override
public ImmutableSet<Class<? extends FragmentOptions>> requiresOptionFragments() {
return ImmutableSet.of(
AndroidConfiguration.Options.class,
CoreOptions.class,
CppOptions.class,
PlatformOptions.class);
}
@Override
public ImmutableMap<String, BuildOptions> split(
BuildOptionsView buildOptions, EventHandler eventHandler) {
AndroidConfiguration.Options androidOptions =
buildOptions.get(AndroidConfiguration.Options.class);
CppOptions cppOptions = buildOptions.get(CppOptions.class);
/*
* The intended order of checks is:
* - When --incompatible_enable_android_toolchain_resolution is set:
* - --android_platforms
* - Split using the values of this flag as the target platform
* - If this is unset, use the first value from --platforms.
* - If this isn't a valid Android platform, an error will be thrown during the build.
* - Fall back to legacy flag logic:
* - --fat_apk_cpus
* - Split using the values of this flag as --cpu
* - If this is unset, fall though.
* - --android_cpu
* - Don't split, just use the value of this flag as --cpu
* - This will not update the output path to include "-android".
* - If this is unset, fall though.
* - Default
* - This will not update the output path to include "-android".
* - Don't split, using the same previously set --cpu value.
*/
if (androidOptions.incompatibleUseToolchainResolution) {
// Always use --android_platforms when toolchain resolution is enabled.
List<Label> platformsToSplit = androidOptions.androidPlatforms;
if (platformsToSplit.isEmpty()) {
// If --android_platforms is unset, instead use only the first value from --platforms.
Label targetPlatform =
Iterables.getFirst(buildOptions.get(PlatformOptions.class).platforms, null);
platformsToSplit = ImmutableList.of(targetPlatform);
}
return handleAndroidPlatforms(buildOptions, androidOptions, platformsToSplit);
}
// Fall back to the legacy flags.
if (!androidOptions.fatApkCpus.isEmpty()) {
return handleFatApkCpus(buildOptions, androidOptions);
} else if (!androidOptions.cpu.isEmpty()
&& androidOptions.androidCrosstoolTop != null
&& !androidOptions.androidCrosstoolTop.equals(cppOptions.crosstoolTop)) {
return handleAndroidCpu(buildOptions, androidOptions);
} else {
return handleDefaultSplit(buildOptions, buildOptions.get(CoreOptions.class).cpu);
}
}
private void addNonCpuSplits(
ImmutableMap.Builder<String, BuildOptions> result,
String name,
BuildOptionsView buildOptions) {
AndroidConfiguration.Options androidOptions =
buildOptions.get(AndroidConfiguration.Options.class);
if (!androidOptions.fatApkHwasan) {
return;
}
if (name.contains("arm64-v8a")) {
BuildOptionsView hwasanSplitOptions = buildOptions.clone();
// A HWASAN build is different from a regular one in these ways:
// - The native library install directory gets a "-hwasan" suffix
// - Some compiler/linker command line options are different (defined in the Android C++
// toolchain)
// - The name of the output directory is changed so that HWASAN and non-HWASAN artifacts
// do not conflict
hwasanSplitOptions.get(CppOptions.class).outputDirectoryTag = "hwasan";
hwasanSplitOptions.get(AndroidConfiguration.Options.class).hwasan = true;
result.put(name + "-hwasan", hwasanSplitOptions.underlying());
}
}
/**
* Splits the configuration based on the values of --android_platforms. Each split will set the
* --platforms flag to one value from --android_platforms, as well as clean up a few other flags
* around native CC builds.
*/
private ImmutableMap<String, BuildOptions> handleAndroidPlatforms(
BuildOptionsView buildOptions,
AndroidConfiguration.Options androidOptions,
List<Label> androidPlatforms) {
ImmutableMap.Builder<String, BuildOptions> result = ImmutableMap.builder();
for (Label platform : ImmutableSortedSet.copyOf(androidPlatforms)) {
BuildOptionsView splitOptions = buildOptions.clone();
// Disable fat APKs for the child configurations.
splitOptions.get(AndroidConfiguration.Options.class).fatApkCpus = ImmutableList.of();
splitOptions.get(AndroidConfiguration.Options.class).androidPlatforms = ImmutableList.of();
// The cpu flag will be set by platform mapping if a mapping exists.
splitOptions.get(PlatformOptions.class).platforms = ImmutableList.of(platform);
setCcFlagsFromAndroid(androidOptions, splitOptions);
result.put(platform.getName(), splitOptions.underlying());
addNonCpuSplits(result, platform.getName(), splitOptions);
}
return result.buildOrThrow();
}
/** Returns a single-split transition that uses the "--cpu" and does not change any flags. */
private ImmutableMap<String, BuildOptions> handleDefaultSplit(
BuildOptionsView buildOptions, String cpu) {
// Avoid a clone when nothing changes.
ImmutableMap.Builder<String, BuildOptions> result = ImmutableMap.builder();
result.put(cpu, buildOptions.underlying());
addNonCpuSplits(result, cpu, buildOptions);
return result.buildOrThrow();
}
/**
* Returns a transition that sets "--cpu" to the value of "--android_cpu" and sets other C++ flags
* based on the corresponding Android flags.
*/
private ImmutableMap<String, BuildOptions> handleAndroidCpu(
BuildOptionsView buildOptions, AndroidConfiguration.Options androidOptions) {
BuildOptionsView splitOptions = buildOptions.clone();
splitOptions.get(CoreOptions.class).cpu = androidOptions.cpu;
setCcFlagsFromAndroid(androidOptions, splitOptions);
// Ensure platforms aren't set so that platform mapping can take place.
splitOptions.get(PlatformOptions.class).platforms = ImmutableList.of();
// Because configuration is based on cpu flags we need to disable C++ toolchain resolution
splitOptions.get(CppOptions.class).enableCcToolchainResolution = false;
return handleDefaultSplit(splitOptions, androidOptions.cpu);
}
/**
* Returns a multi-split transition that sets "--cpu" with the values of "--fat_apk_cpu" and sets
* other C++ flags based on the corresponding Android flags.
*/
private ImmutableMap<String, BuildOptions> handleFatApkCpus(
BuildOptionsView buildOptions, AndroidConfiguration.Options androidOptions) {
ImmutableMap.Builder<String, BuildOptions> result = ImmutableMap.builder();
for (String cpu : ImmutableSortedSet.copyOf(androidOptions.fatApkCpus)) {
BuildOptionsView splitOptions = buildOptions.clone();
// Disable fat APKs for the child configurations.
splitOptions.get(AndroidConfiguration.Options.class).fatApkCpus = ImmutableList.of();
splitOptions.get(AndroidConfiguration.Options.class).androidPlatforms = ImmutableList.of();
// Set the cpu & android_cpu.
// TODO(bazel-team): --android_cpu doesn't follow --cpu right now; it should.
splitOptions.get(AndroidConfiguration.Options.class).cpu = cpu;
splitOptions.get(CoreOptions.class).cpu = cpu;
setCcFlagsFromAndroid(androidOptions, splitOptions);
// Ensure platforms aren't set so that platform mapping can take place.
splitOptions.get(PlatformOptions.class).platforms = ImmutableList.of();
// Because configuration is based on cpu flags we need to disable C++ toolchain resolution
splitOptions.get(CppOptions.class).enableCcToolchainResolution = false;
result.put(cpu, splitOptions.underlying());
addNonCpuSplits(result, cpu, splitOptions);
}
return result.buildOrThrow();
}
private void setCcFlagsFromAndroid(
AndroidConfiguration.Options androidOptions, BuildOptionsView newOptions) {
// Set the CC options needed to build native code.
CppOptions newCppOptions = newOptions.get(CppOptions.class);
newCppOptions.cppCompiler = androidOptions.cppCompiler;
newCppOptions.libcTopLabel = androidOptions.androidLibcTopLabel;
newCppOptions.dynamicMode = androidOptions.dynamicMode;
if (androidOptions.androidCrosstoolTop != null) {
newCppOptions.crosstoolTop = androidOptions.androidCrosstoolTop;
}
newOptions.get(AndroidConfiguration.Options.class).configurationDistinguisher =
ConfigurationDistinguisher.ANDROID;
}
@Override
public boolean isImmutable() {
return true;
}
@Override
public void repr(Printer printer) {
printer.append("android_common.multi_cpu_configuration");
}
}