/*
* Copyright 2014-present Facebook, Inc.
*
* 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.facebook.buck.android;
import com.facebook.buck.cxx.CompilerProvider;
import com.facebook.buck.cxx.CxxBuckConfig;
import com.facebook.buck.cxx.CxxPlatform;
import com.facebook.buck.cxx.CxxToolProvider;
import com.facebook.buck.cxx.DefaultLinkerProvider;
import com.facebook.buck.cxx.ElfSharedLibraryInterfaceFactory;
import com.facebook.buck.cxx.GnuArchiver;
import com.facebook.buck.cxx.GnuLinker;
import com.facebook.buck.cxx.HeaderVerification;
import com.facebook.buck.cxx.Linker;
import com.facebook.buck.cxx.LinkerProvider;
import com.facebook.buck.cxx.MungingDebugPathSanitizer;
import com.facebook.buck.cxx.PosixNmSymbolNameTool;
import com.facebook.buck.cxx.PrefixMapDebugPathSanitizer;
import com.facebook.buck.cxx.PreprocessorProvider;
import com.facebook.buck.io.ExecutableFinder;
import com.facebook.buck.io.ProjectFilesystem;
import com.facebook.buck.log.Logger;
import com.facebook.buck.model.Flavor;
import com.facebook.buck.model.InternalFlavor;
import com.facebook.buck.rules.ConstantToolProvider;
import com.facebook.buck.rules.Tool;
import com.facebook.buck.rules.ToolProvider;
import com.facebook.buck.rules.VersionedTool;
import com.facebook.buck.util.environment.Platform;
import com.facebook.infer.annotation.Assertions;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Pattern;
public class NdkCxxPlatforms {
private static final Logger LOG = Logger.get(NdkCxxPlatforms.class);
/**
* Magic path prefix we use to denote the machine-specific location of the Android NDK. Why "@"?
* It's uncommon enough to mark that path element as special while not being a metacharacter in
* either make, shell, or regular expression syntax.
*
* <p>We also have prefixes for tool specific paths, even though they're sub-paths of
* `@ANDROID_NDK_ROOT@`. This is to sanitize host-specific sub-directories in the toolchain (e.g.
* darwin-x86_64) which would otherwise break determinism and caching when using
* cross-compilation.
*/
public static final String ANDROID_NDK_ROOT = "@ANDROID_NDK_ROOT@";
/**
* Magic string we substitute into debug paths in place of the build-host name, erasing the
* difference between say, building on Darwin and building on Linux.
*/
public static final String BUILD_HOST_SUBST = "@BUILD_HOST@";
public static final NdkCxxPlatformCompiler.Type DEFAULT_COMPILER_TYPE =
NdkCxxPlatformCompiler.Type.GCC;
public static final String DEFAULT_TARGET_APP_PLATFORM = "android-9";
public static final ImmutableSet<String> DEFAULT_CPU_ABIS =
ImmutableSet.of("arm", "armv7", "x86");
public static final NdkCxxRuntime DEFAULT_CXX_RUNTIME = NdkCxxRuntime.GNUSTL;
private static final ImmutableMap<Platform, Host> BUILD_PLATFORMS =
ImmutableMap.of(
Platform.LINUX, Host.LINUX_X86_64,
Platform.MACOS, Host.DARWIN_X86_64,
Platform.WINDOWS, Host.WINDOWS_X86_64);
/** Defaults for c and c++ flags */
public static final ImmutableList<String> DEFAULT_COMMON_CFLAGS =
ImmutableList.of(
// Default to the C11 standard.
"-std=gnu11");
public static final ImmutableList<String> DEFAULT_COMMON_CXXFLAGS =
ImmutableList.of(
// Default to the C++11 standard.
"-std=gnu++11", "-fno-exceptions", "-fno-rtti");
public static final ImmutableList<String> DEFAULT_COMMON_CPPFLAGS =
ImmutableList.of(
// Disable searching for headers provided by the system. This limits headers to just
// those provided by the NDK and any library dependencies.
"-nostdinc",
// Default macro definitions applied to all builds.
"-DNDEBUG",
"-DANDROID");
// Utility class, do not instantiate.
private NdkCxxPlatforms() {}
static int getNdkMajorVersion(String ndkVersion) {
return ndkVersion.startsWith("r9")
? 9
: ndkVersion.startsWith("r10")
? 10
: ndkVersion.startsWith("11.")
? 11
: ndkVersion.startsWith("12.")
? 12
: ndkVersion.startsWith("13.")
? 13
: ndkVersion.startsWith("14.")
? 14
: ndkVersion.startsWith("15.") ? 15 : -1;
}
public static String getDefaultGccVersionForNdk(Optional<String> ndkVersion) {
if (ndkVersion.isPresent() && getNdkMajorVersion(ndkVersion.get()) < 11) {
return "4.8";
}
return "4.9";
}
public static String getDefaultClangVersionForNdk(Optional<String> ndkVersion) {
if (ndkVersion.isPresent() && getNdkMajorVersion(ndkVersion.get()) < 11) {
return "3.5";
}
return "3.8";
}
public static boolean isSupportedConfiguration(Path ndkRoot, NdkCxxRuntime cxxRuntime) {
// TODO(12846101): With ndk r12, Android has started to use libc++abi. Buck
// needs to figure out how to support that.
String ndkVersion = readVersion(ndkRoot);
return !(cxxRuntime == NdkCxxRuntime.LIBCXX && getNdkMajorVersion(ndkVersion) >= 12);
}
public static ImmutableMap<TargetCpuType, NdkCxxPlatform> getPlatforms(
CxxBuckConfig config,
AndroidBuckConfig androidConfig,
ProjectFilesystem filesystem,
Path ndkRoot,
NdkCxxPlatformCompiler compiler,
NdkCxxRuntime cxxRuntime,
String androidPlatform,
Set<String> cpuAbis,
Platform platform) {
return getPlatforms(
config,
androidConfig,
filesystem,
ndkRoot,
compiler,
cxxRuntime,
androidPlatform,
cpuAbis,
platform,
new ExecutableFinder(),
/* strictToolchainPaths */ true);
}
/** @return the map holding the available {@link NdkCxxPlatform}s. */
public static ImmutableMap<TargetCpuType, NdkCxxPlatform> getPlatforms(
CxxBuckConfig config,
AndroidBuckConfig androidConfig,
ProjectFilesystem filesystem,
Path ndkRoot,
NdkCxxPlatformCompiler compiler,
NdkCxxRuntime cxxRuntime,
String androidPlatform,
Set<String> cpuAbis,
Platform platform,
ExecutableFinder executableFinder,
boolean strictToolchainPaths) {
ImmutableMap.Builder<TargetCpuType, NdkCxxPlatform> ndkCxxPlatformBuilder =
ImmutableMap.builder();
// ARM Platform
if (cpuAbis.contains("arm")) {
NdkCxxPlatformTargetConfiguration targetConfiguration =
getTargetConfiguration(TargetCpuType.ARM, compiler, androidPlatform);
NdkCxxPlatform armeabi =
build(
config,
androidConfig,
filesystem,
InternalFlavor.of("android-arm"),
platform,
ndkRoot,
targetConfiguration,
cxxRuntime,
executableFinder,
strictToolchainPaths);
ndkCxxPlatformBuilder.put(TargetCpuType.ARM, armeabi);
}
// ARMv7 Platform
if (cpuAbis.contains("armv7")) {
NdkCxxPlatformTargetConfiguration targetConfiguration =
getTargetConfiguration(TargetCpuType.ARMV7, compiler, androidPlatform);
NdkCxxPlatform armeabiv7 =
build(
config,
androidConfig,
filesystem,
InternalFlavor.of("android-armv7"),
platform,
ndkRoot,
targetConfiguration,
cxxRuntime,
executableFinder,
strictToolchainPaths);
ndkCxxPlatformBuilder.put(TargetCpuType.ARMV7, armeabiv7);
}
// ARM64 Platform
if (cpuAbis.contains("arm64")) {
NdkCxxPlatformTargetConfiguration targetConfiguration =
getTargetConfiguration(TargetCpuType.ARM64, compiler, androidPlatform);
NdkCxxPlatform arm64 =
build(
config,
androidConfig,
filesystem,
InternalFlavor.of("android-arm64"),
platform,
ndkRoot,
targetConfiguration,
cxxRuntime,
executableFinder,
strictToolchainPaths);
ndkCxxPlatformBuilder.put(TargetCpuType.ARM64, arm64);
}
// x86 Platform
if (cpuAbis.contains("x86")) {
NdkCxxPlatformTargetConfiguration targetConfiguration =
getTargetConfiguration(TargetCpuType.X86, compiler, androidPlatform);
NdkCxxPlatform x86 =
build(
config,
androidConfig,
filesystem,
InternalFlavor.of("android-x86"),
platform,
ndkRoot,
targetConfiguration,
cxxRuntime,
executableFinder,
strictToolchainPaths);
ndkCxxPlatformBuilder.put(TargetCpuType.X86, x86);
}
// x86_64 Platform
if (cpuAbis.contains("x86_64")) {
NdkCxxPlatformTargetConfiguration targetConfiguration =
getTargetConfiguration(TargetCpuType.X86_64, compiler, androidPlatform);
// CHECKSTYLE.OFF: LocalVariableName
NdkCxxPlatform x86_64 =
// CHECKSTYLE.ON
build(
config,
androidConfig,
filesystem,
InternalFlavor.of("android-x86_64"),
platform,
ndkRoot,
targetConfiguration,
cxxRuntime,
executableFinder,
strictToolchainPaths);
ndkCxxPlatformBuilder.put(TargetCpuType.X86_64, x86_64);
}
return ndkCxxPlatformBuilder.build();
}
@VisibleForTesting
static NdkCxxPlatformTargetConfiguration getTargetConfiguration(
TargetCpuType targetCpuType, NdkCxxPlatformCompiler compiler, String androidPlatform) {
switch (targetCpuType) {
case ARM:
ImmutableList<String> armeabiArchFlags =
ImmutableList.of("-march=armv5te", "-mtune=xscale", "-msoft-float", "-mthumb");
return NdkCxxPlatformTargetConfiguration.builder()
.setToolchain(Toolchain.ARM_LINUX_ANDROIDEABI)
.setTargetArch(TargetArch.ARM)
.setTargetArchAbi(TargetArchAbi.ARMEABI)
.setTargetAppPlatform(androidPlatform)
.setCompiler(compiler)
.setToolchainTarget(ToolchainTarget.ARM_LINUX_ANDROIDEABI)
.putAssemblerFlags(NdkCxxPlatformCompiler.Type.GCC, armeabiArchFlags)
.putAssemblerFlags(
NdkCxxPlatformCompiler.Type.CLANG,
ImmutableList.<String>builder()
.add("-target", "armv5te-none-linux-androideabi")
.addAll(armeabiArchFlags)
.build())
.putCompilerFlags(
NdkCxxPlatformCompiler.Type.GCC,
ImmutableList.<String>builder().add("-Os").addAll(armeabiArchFlags).build())
.putCompilerFlags(
NdkCxxPlatformCompiler.Type.CLANG,
ImmutableList.<String>builder()
.add("-target", "armv5te-none-linux-androideabi", "-Os")
.addAll(armeabiArchFlags)
.build())
.putLinkerFlags(
NdkCxxPlatformCompiler.Type.GCC,
ImmutableList.of("-march=armv5te", "-Wl,--fix-cortex-a8"))
.putLinkerFlags(
NdkCxxPlatformCompiler.Type.CLANG,
ImmutableList.of(
"-target",
"armv5te-none-linux-androideabi",
"-march=armv5te",
"-Wl,--fix-cortex-a8"))
.build();
case ARMV7:
ImmutableList<String> armeabiv7ArchFlags =
ImmutableList.of("-march=armv7-a", "-mfpu=vfpv3-d16", "-mfloat-abi=softfp", "-mthumb");
return NdkCxxPlatformTargetConfiguration.builder()
.setToolchain(Toolchain.ARM_LINUX_ANDROIDEABI)
.setTargetArch(TargetArch.ARM)
.setTargetArchAbi(TargetArchAbi.ARMEABI_V7A)
.setTargetAppPlatform(androidPlatform)
.setCompiler(compiler)
.setToolchainTarget(ToolchainTarget.ARM_LINUX_ANDROIDEABI)
.putAssemblerFlags(NdkCxxPlatformCompiler.Type.GCC, armeabiv7ArchFlags)
.putAssemblerFlags(
NdkCxxPlatformCompiler.Type.CLANG,
ImmutableList.<String>builder()
.add("-target", "armv7-none-linux-androideabi")
.addAll(armeabiv7ArchFlags)
.build())
.putCompilerFlags(
NdkCxxPlatformCompiler.Type.GCC,
ImmutableList.<String>builder()
.add("-finline-limit=64", "-Os")
.addAll(armeabiv7ArchFlags)
.build())
.putCompilerFlags(
NdkCxxPlatformCompiler.Type.CLANG,
ImmutableList.<String>builder()
.add("-target", "armv7-none-linux-androideabi", "-Os")
.addAll(armeabiv7ArchFlags)
.build())
.putLinkerFlags(NdkCxxPlatformCompiler.Type.GCC, ImmutableList.<String>of())
.putLinkerFlags(
NdkCxxPlatformCompiler.Type.CLANG,
ImmutableList.of("-target", "armv7-none-linux-androideabi"))
.build();
case ARM64:
ImmutableList<String> arm64ArchFlags = ImmutableList.of("-march=armv8-a");
return NdkCxxPlatformTargetConfiguration.builder()
.setToolchain(Toolchain.AARCH64_LINUX_ANDROID)
.setTargetArch(TargetArch.ARM64)
.setTargetArchAbi(TargetArchAbi.ARM64_V8A)
.setTargetAppPlatform(androidPlatform)
.setCompiler(compiler)
.setToolchainTarget(ToolchainTarget.AARCH64_LINUX_ANDROID)
.putAssemblerFlags(NdkCxxPlatformCompiler.Type.GCC, arm64ArchFlags)
.putAssemblerFlags(
NdkCxxPlatformCompiler.Type.CLANG,
ImmutableList.<String>builder()
.add("-target", "aarch64-none-linux-android")
.addAll(arm64ArchFlags)
.build())
.putCompilerFlags(
NdkCxxPlatformCompiler.Type.GCC,
ImmutableList.<String>builder()
.add("-O2")
.add("-fomit-frame-pointer")
.add("-fstrict-aliasing")
.add("-funswitch-loops")
.add("-finline-limit=300")
.addAll(arm64ArchFlags)
.build())
.putCompilerFlags(
NdkCxxPlatformCompiler.Type.CLANG,
ImmutableList.<String>builder()
.add("-target", "aarch64-none-linux-android")
.add("-O2")
.add("-fomit-frame-pointer")
.add("-fstrict-aliasing")
.addAll(arm64ArchFlags)
.build())
.putLinkerFlags(NdkCxxPlatformCompiler.Type.GCC, ImmutableList.<String>of())
.putLinkerFlags(
NdkCxxPlatformCompiler.Type.CLANG,
ImmutableList.of("-target", "aarch64-none-linux-android"))
.build();
case X86:
return NdkCxxPlatformTargetConfiguration.builder()
.setToolchain(Toolchain.X86)
.setTargetArch(TargetArch.X86)
.setTargetArchAbi(TargetArchAbi.X86)
.setTargetAppPlatform(androidPlatform)
.setCompiler(compiler)
.setToolchainTarget(ToolchainTarget.I686_LINUX_ANDROID)
.putAssemblerFlags(NdkCxxPlatformCompiler.Type.GCC, ImmutableList.<String>of())
.putAssemblerFlags(
NdkCxxPlatformCompiler.Type.CLANG,
ImmutableList.of("-target", "i686-none-linux-android"))
.putCompilerFlags(
NdkCxxPlatformCompiler.Type.GCC,
ImmutableList.of("-funswitch-loops", "-finline-limit=300", "-O2"))
.putCompilerFlags(
NdkCxxPlatformCompiler.Type.CLANG,
ImmutableList.of("-target", "i686-none-linux-android", "-O2"))
.putLinkerFlags(NdkCxxPlatformCompiler.Type.GCC, ImmutableList.<String>of())
.putLinkerFlags(
NdkCxxPlatformCompiler.Type.CLANG,
ImmutableList.of("-target", "i686-none-linux-android"))
.build();
case X86_64:
return NdkCxxPlatformTargetConfiguration.builder()
.setToolchain(Toolchain.X86_64)
.setTargetArch(TargetArch.X86_64)
.setTargetArchAbi(TargetArchAbi.X86_64)
.setTargetAppPlatform(androidPlatform)
.setCompiler(compiler)
.setToolchainTarget(ToolchainTarget.X86_64_LINUX_ANDROID)
.putAssemblerFlags(NdkCxxPlatformCompiler.Type.GCC, ImmutableList.<String>of())
.putAssemblerFlags(NdkCxxPlatformCompiler.Type.CLANG, ImmutableList.<String>of())
.putCompilerFlags(
NdkCxxPlatformCompiler.Type.GCC,
ImmutableList.of("-funswitch-loops", "-finline-limit=300", "-O2"))
.putCompilerFlags(
NdkCxxPlatformCompiler.Type.CLANG,
ImmutableList.of("-target", "x86_64-none-linux-android", "-O2"))
.putLinkerFlags(NdkCxxPlatformCompiler.Type.GCC, ImmutableList.<String>of())
.putLinkerFlags(
NdkCxxPlatformCompiler.Type.CLANG,
ImmutableList.of("-target", "x86_64-none-linux-android"))
.build();
case MIPS:
break;
}
throw new AssertionError();
}
@VisibleForTesting
static NdkCxxPlatform build(
CxxBuckConfig config,
AndroidBuckConfig androidConfig,
ProjectFilesystem filesystem,
Flavor flavor,
Platform platform,
Path ndkRoot,
NdkCxxPlatformTargetConfiguration targetConfiguration,
NdkCxxRuntime cxxRuntime,
ExecutableFinder executableFinder,
boolean strictToolchainPaths) {
// Create a version string to use when generating rule keys via the NDK tools we'll generate
// below. This will be used in lieu of hashing the contents of the tools, so that builds from
// different host platforms (which produce identical output) will share the cache with one
// another.
NdkCxxPlatformCompiler.Type compilerType = targetConfiguration.getCompiler().getType();
String version =
Joiner.on('-')
.join(
ImmutableList.of(
readVersion(ndkRoot),
targetConfiguration.getToolchain(),
targetConfiguration.getTargetAppPlatform(),
compilerType,
targetConfiguration.getCompiler().getVersion(),
targetConfiguration.getCompiler().getGccVersion(),
cxxRuntime));
Host host = Preconditions.checkNotNull(BUILD_PLATFORMS.get(platform));
NdkCxxToolchainPaths toolchainPaths =
new NdkCxxToolchainPaths(
filesystem,
ndkRoot,
targetConfiguration,
host.toString(),
cxxRuntime,
strictToolchainPaths);
// Sanitized paths will have magic placeholders for parts of the paths that
// are machine/host-specific. See comments on ANDROID_NDK_ROOT and
// BUILD_HOST_SUBST above.
NdkCxxToolchainPaths sanitizedPaths = toolchainPaths.getSanitizedPaths();
// Build up the map of paths that must be sanitized.
ImmutableBiMap.Builder<Path, Path> sanitizePathsBuilder = ImmutableBiMap.builder();
sanitizePathsBuilder.put(toolchainPaths.getNdkToolRoot(), sanitizedPaths.getNdkToolRoot());
if (compilerType != NdkCxxPlatformCompiler.Type.GCC) {
sanitizePathsBuilder.put(
toolchainPaths.getNdkGccToolRoot(), sanitizedPaths.getNdkGccToolRoot());
}
sanitizePathsBuilder.put(ndkRoot, Paths.get(ANDROID_NDK_ROOT));
CxxToolProvider.Type type =
compilerType == NdkCxxPlatformCompiler.Type.CLANG
? CxxToolProvider.Type.CLANG
: CxxToolProvider.Type.GCC;
ToolProvider ccTool =
new ConstantToolProvider(
getCTool(toolchainPaths, compilerType.getCc(), version, executableFinder));
ToolProvider cxxTool =
new ConstantToolProvider(
getCTool(toolchainPaths, compilerType.getCxx(), version, executableFinder));
CompilerProvider cc = new CompilerProvider(ccTool, type);
PreprocessorProvider cpp = new PreprocessorProvider(ccTool, type);
CompilerProvider cxx = new CompilerProvider(cxxTool, type);
PreprocessorProvider cxxpp = new PreprocessorProvider(cxxTool, type);
CxxPlatform.Builder cxxPlatformBuilder = CxxPlatform.builder();
ImmutableBiMap<Path, Path> sanitizePaths = sanitizePathsBuilder.build();
PrefixMapDebugPathSanitizer compilerDebugPathSanitizer =
new PrefixMapDebugPathSanitizer(
config.getDebugPathSanitizerLimit(),
File.separatorChar,
Paths.get("."),
sanitizePaths,
filesystem.getRootPath().toAbsolutePath(),
type,
filesystem);
MungingDebugPathSanitizer assemblerDebugPathSanitizer =
new MungingDebugPathSanitizer(
config.getDebugPathSanitizerLimit(), File.separatorChar, Paths.get("."), sanitizePaths);
cxxPlatformBuilder
.setFlavor(flavor)
.setAs(cc)
.addAllAsflags(getAsflags(targetConfiguration, toolchainPaths))
.setAspp(cpp)
.setCc(cc)
.addAllCflags(getCflagsInternal(targetConfiguration, toolchainPaths, androidConfig))
.setCpp(cpp)
.addAllCppflags(getCppflags(targetConfiguration, toolchainPaths, androidConfig))
.setCxx(cxx)
.addAllCxxflags(getCxxflagsInternal(targetConfiguration, toolchainPaths, androidConfig))
.setCxxpp(cxxpp)
.addAllCxxppflags(getCxxppflags(targetConfiguration, toolchainPaths, androidConfig))
.setLd(
new DefaultLinkerProvider(
LinkerProvider.Type.GNU,
new ConstantToolProvider(
getCcLinkTool(
targetConfiguration,
toolchainPaths,
compilerType.getCxx(),
version,
cxxRuntime,
executableFinder))))
.addAllLdflags(targetConfiguration.getLinkerFlags(compilerType))
// Default linker flags added by the NDK
.addLdflags(
// Add a deterministic build ID to Android builds.
// We use it to find symbols from arbitrary binaries.
"-Wl,--build-id",
// Enforce the NX (no execute) security feature
"-Wl,-z,noexecstack",
// Strip unused code
"-Wl,--gc-sections",
// Refuse to produce dynamic objects with undefined symbols
"-Wl,-z,defs",
// Forbid dangerous copy "relocations"
"-Wl,-z,nocopyreloc",
// We always pass the runtime library on the command line, so setting this flag
// means the resulting link will only use it if it was actually needed it.
"-Wl,--as-needed")
.setStrip(getGccTool(toolchainPaths, "strip", version, executableFinder))
.setSymbolNameTool(
new PosixNmSymbolNameTool(getGccTool(toolchainPaths, "nm", version, executableFinder)))
.setAr(new GnuArchiver(getGccTool(toolchainPaths, "ar", version, executableFinder)))
.setRanlib(getGccTool(toolchainPaths, "ranlib", version, executableFinder))
// NDK builds are cross compiled, so the header is the same regardless of the host platform.
.setCompilerDebugPathSanitizer(compilerDebugPathSanitizer)
.setAssemblerDebugPathSanitizer(assemblerDebugPathSanitizer)
.setSharedLibraryExtension("so")
.setSharedLibraryVersionedExtensionFormat("so.%s")
.setStaticLibraryExtension("a")
.setObjectFileExtension("o")
.setSharedLibraryInterfaceFactory(
config.shouldUseSharedLibraryInterfaces()
? Optional.of(
ElfSharedLibraryInterfaceFactory.of(
new ConstantToolProvider(
getGccTool(toolchainPaths, "objcopy", version, executableFinder))))
: Optional.empty())
.setPublicHeadersSymlinksEnabled(config.getPublicHeadersSymlinksEnabled())
.setPrivateHeadersSymlinksEnabled(config.getPrivateHeadersSymlinksEnabled());
// Add the NDK root path to the white-list so that headers from the NDK won't trigger the
// verification warnings. Ideally, long-term, we'd model NDK libs/headers via automatically
// generated nodes/descriptions so that they wouldn't need to special case it here.
HeaderVerification headerVerification = config.getHeaderVerification();
try {
headerVerification =
headerVerification.withPlatformWhitelist(
ImmutableList.of(
"^"
+ Pattern.quote(ndkRoot.toRealPath().toString() + File.separatorChar)
+ ".*"));
} catch (IOException e) {
LOG.warn(e, "NDK path could not be resolved: %s", ndkRoot);
}
cxxPlatformBuilder.setHeaderVerification(headerVerification);
LOG.debug("NDK root: %s", ndkRoot.toString());
LOG.debug(
"Headers verification platform whitelist: %s", headerVerification.getPlatformWhitelist());
if (cxxRuntime != NdkCxxRuntime.SYSTEM) {
cxxPlatformBuilder.putRuntimeLdflags(
Linker.LinkableDepType.SHARED, "-l" + cxxRuntime.getSharedName());
cxxPlatformBuilder.putRuntimeLdflags(
Linker.LinkableDepType.STATIC, "-l" + cxxRuntime.getStaticName());
}
CxxPlatform cxxPlatform = cxxPlatformBuilder.build();
NdkCxxPlatform.Builder builder = NdkCxxPlatform.builder();
builder
.setCxxPlatform(cxxPlatform)
.setCxxRuntime(cxxRuntime)
.setObjdump(getGccTool(toolchainPaths, "objdump", version, executableFinder));
if (cxxRuntime != NdkCxxRuntime.SYSTEM) {
builder.setCxxSharedRuntimePath(
toolchainPaths.getCxxRuntimeLibsDirectory().resolve(cxxRuntime.getSoname()));
}
return builder.build();
}
/**
* It returns the version of the Android NDK located at the {@code ndkRoot} or throws the
* exception.
*
* @param ndkRoot the path where Android NDK is located.
* @return the version of the Android NDK located in {@code ndkRoot}.
*/
private static String readVersion(Path ndkRoot) {
return DefaultAndroidDirectoryResolver.findNdkVersionFromDirectory(ndkRoot).get();
}
private static Path getToolPath(
NdkCxxToolchainPaths toolchainPaths, String tool, ExecutableFinder executableFinder) {
Path expected = toolchainPaths.getToolPath(tool);
Optional<Path> path = executableFinder.getOptionalExecutable(expected, ImmutableMap.of());
Preconditions.checkState(path.isPresent(), expected.toString());
return path.get();
}
private static Path getGccToolPath(
NdkCxxToolchainPaths toolchainPaths, String tool, ExecutableFinder executableFinder) {
Path expected = toolchainPaths.getGccToolchainBinPath().resolve(tool);
Optional<Path> path = executableFinder.getOptionalExecutable(expected, ImmutableMap.of());
Preconditions.checkState(path.isPresent(), expected.toString());
return path.get();
}
private static Tool getGccTool(
NdkCxxToolchainPaths toolchainPaths,
String tool,
String version,
ExecutableFinder executableFinder) {
return VersionedTool.of(getGccToolPath(toolchainPaths, tool, executableFinder), tool, version);
}
private static Tool getCTool(
NdkCxxToolchainPaths toolchainPaths,
String tool,
String version,
ExecutableFinder executableFinder) {
return VersionedTool.of(getToolPath(toolchainPaths, tool, executableFinder), tool, version);
}
private static ImmutableList<String> getCxxRuntimeIncludeFlags(
NdkCxxPlatformTargetConfiguration targetConfiguration, NdkCxxToolchainPaths toolchainPaths) {
ImmutableList.Builder<String> flags = ImmutableList.builder();
switch (toolchainPaths.getCxxRuntime()) {
case GNUSTL:
flags.add(
"-isystem", toolchainPaths.getCxxRuntimeDirectory().resolve("include").toString());
flags.add(
"-isystem",
toolchainPaths
.getCxxRuntimeDirectory()
.resolve("libs")
.resolve(targetConfiguration.getTargetArchAbi().toString())
.resolve("include")
.toString());
break;
case LIBCXX:
flags.add(
"-isystem",
toolchainPaths
.getCxxRuntimeDirectory()
.resolve("libcxx")
.resolve("include")
.toString());
flags.add(
"-isystem",
toolchainPaths
.getCxxRuntimeDirectory()
.getParent()
.resolve("llvm-libc++abi")
.resolve("libcxxabi")
.resolve("include")
.toString());
flags.add(
"-isystem",
toolchainPaths
.getNdkRoot()
.resolve("sources")
.resolve("android")
.resolve("support")
.resolve("include")
.toString());
break;
// $CASES-OMITTED$
default:
flags.add(
"-isystem", toolchainPaths.getCxxRuntimeDirectory().resolve("include").toString());
}
return flags.build();
}
private static Linker getCcLinkTool(
NdkCxxPlatformTargetConfiguration targetConfiguration,
NdkCxxToolchainPaths toolchainPaths,
String tool,
String version,
NdkCxxRuntime cxxRuntime,
ExecutableFinder executableFinder) {
ImmutableList.Builder<String> flags = ImmutableList.builder();
// Clang still needs to find GCC tools.
if (targetConfiguration.getCompiler().getType() == NdkCxxPlatformCompiler.Type.CLANG) {
flags.add("-gcc-toolchain", toolchainPaths.getNdkGccToolRoot().toString());
}
// Set the sysroot to the platform-specific path.
flags.add("--sysroot=" + toolchainPaths.getSysroot());
// TODO(#7264008): This was added for windows support but it's not clear why it's needed.
if (targetConfiguration.getCompiler().getType() == NdkCxxPlatformCompiler.Type.GCC) {
flags.add("-B" + toolchainPaths.getLibexecGccToolPath(), "-B" + toolchainPaths.getLibPath());
}
// Add the path to the C/C++ runtime libraries, if necessary.
if (cxxRuntime != NdkCxxRuntime.SYSTEM) {
flags.add("-L" + toolchainPaths.getCxxRuntimeLibsDirectory().toString());
}
return new GnuLinker(
VersionedTool.builder()
.setPath(getToolPath(toolchainPaths, tool, executableFinder))
.setName(tool)
.setVersion(version)
.setExtraArgs(flags.build())
.build());
}
/** Flags to be used when either preprocessing or compiling C or C++ sources. */
private static ImmutableList<String> getCommonFlags(
NdkCxxPlatformTargetConfiguration targetConfiguration, NdkCxxToolchainPaths toolchainPaths) {
ImmutableList.Builder<String> flags = ImmutableList.builder();
// Clang still needs to find the GCC tools.
if (targetConfiguration.getCompiler().getType() == NdkCxxPlatformCompiler.Type.CLANG) {
flags.add("-gcc-toolchain", toolchainPaths.getNdkGccToolRoot().toString());
}
// TODO(#7264008): This was added for windows support but it's not clear why it's needed.
if (targetConfiguration.getCompiler().getType() == NdkCxxPlatformCompiler.Type.GCC) {
flags.add(
"-B" + toolchainPaths.getLibexecGccToolPath(),
"-B" + toolchainPaths.getToolchainBinPath());
}
// Enable default warnings and turn them into errors.
flags.add("-Wall", "-Werror");
// NOTE: We pass all compiler flags to the preprocessor to make sure any necessary internal
// macros get defined and we also pass the include paths to the to the compiler since we're
// not whether we're doing combined preprocessing/compiling or not.
if (targetConfiguration.getCompiler().getType() == NdkCxxPlatformCompiler.Type.CLANG) {
flags.add("-Wno-unused-command-line-argument");
}
// NDK builds enable stack protector and debug symbols by default.
flags.add("-fstack-protector", "-g3");
return flags.build();
}
/** Flags to be used when either preprocessing or compiling C sources. */
private static ImmutableList<String> getCommonCFlags(AndroidBuckConfig config) {
return ImmutableList.<String>builder()
.addAll(DEFAULT_COMMON_CFLAGS)
.addAll(config.getExtraNdkCFlags())
.build();
}
/** Flags to be used when either preprocessing or compiling C++ sources. */
private static ImmutableList<String> getCommonCxxFlags(AndroidBuckConfig config) {
return ImmutableList.<String>builder()
.addAll(DEFAULT_COMMON_CXXFLAGS)
.addAll(config.getExtraNdkCxxFlags())
.build();
}
/** Flags to be used when preprocessing C or C++ sources. */
private static ImmutableList<String> getCommonPreprocessorFlags(AndroidBuckConfig config) {
return ImmutableList.<String>builder()
.addAll(DEFAULT_COMMON_CPPFLAGS)
.addAll(config.getExtraNdkCppFlags())
.build();
}
private static ImmutableList<String> getCommonIncludes(NdkCxxToolchainPaths toolchainPaths) {
return ImmutableList.of(
"-isystem",
toolchainPaths.getNdkToolRoot().resolve("include").toString(),
"-isystem",
toolchainPaths.getLibPath().resolve("include").toString(),
"-isystem",
toolchainPaths.getSysroot().resolve("usr").resolve("include").toString(),
"-isystem",
toolchainPaths.getSysroot().resolve("usr").resolve("include").resolve("linux").toString());
}
private static ImmutableList<String> getAsflags(
NdkCxxPlatformTargetConfiguration targetConfiguration, NdkCxxToolchainPaths toolchainPaths) {
return ImmutableList.<String>builder()
.addAll(getCommonFlags(targetConfiguration, toolchainPaths))
// Default assembler flags added by the NDK to enforce the NX (no execute) security feature.
.add("-Xassembler", "--noexecstack")
.addAll(targetConfiguration.getAssemblerFlags(targetConfiguration.getCompiler().getType()))
.build();
}
private static ImmutableList<String> getCppflags(
NdkCxxPlatformTargetConfiguration targetConfiguration,
NdkCxxToolchainPaths toolchainPaths,
AndroidBuckConfig config) {
return ImmutableList.<String>builder()
.addAll(getCommonIncludes(toolchainPaths))
.addAll(getCommonPreprocessorFlags(config))
.addAll(getCommonFlags(targetConfiguration, toolchainPaths))
.addAll(getCommonCFlags(config))
.addAll(targetConfiguration.getCompilerFlags(targetConfiguration.getCompiler().getType()))
.build();
}
private static ImmutableList<String> getCxxppflags(
NdkCxxPlatformTargetConfiguration targetConfiguration,
NdkCxxToolchainPaths toolchainPaths,
AndroidBuckConfig config) {
ImmutableList.Builder<String> flags = ImmutableList.builder();
flags.addAll(getCxxRuntimeIncludeFlags(targetConfiguration, toolchainPaths));
flags.addAll(getCommonIncludes(toolchainPaths));
flags.addAll(getCommonPreprocessorFlags(config));
flags.addAll(getCommonFlags(targetConfiguration, toolchainPaths));
flags.addAll(getCommonCxxFlags(config));
if (targetConfiguration.getCompiler().getType() == NdkCxxPlatformCompiler.Type.GCC) {
flags.add("-Wno-literal-suffix");
}
flags.addAll(targetConfiguration.getCompilerFlags(targetConfiguration.getCompiler().getType()));
return flags.build();
}
/** Flags used when compiling either C or C++ sources. */
private static ImmutableList<String> getCommonNdkCxxPlatformCompilerFlags() {
return ImmutableList.of(
// Default compiler flags provided by the NDK build makefiles.
"-ffunction-sections", "-funwind-tables", "-fomit-frame-pointer", "-fno-strict-aliasing");
}
private static ImmutableList<String> getCflagsInternal(
NdkCxxPlatformTargetConfiguration targetConfiguration,
NdkCxxToolchainPaths toolchainPaths,
AndroidBuckConfig config) {
return ImmutableList.<String>builder()
.addAll(targetConfiguration.getCompilerFlags(targetConfiguration.getCompiler().getType()))
.addAll(getCommonCFlags(config))
.addAll(getCommonFlags(targetConfiguration, toolchainPaths))
.addAll(getCommonNdkCxxPlatformCompilerFlags())
.build();
}
private static ImmutableList<String> getCxxflagsInternal(
NdkCxxPlatformTargetConfiguration targetConfiguration,
NdkCxxToolchainPaths toolchainPaths,
AndroidBuckConfig config) {
return ImmutableList.<String>builder()
.addAll(targetConfiguration.getCompilerFlags(targetConfiguration.getCompiler().getType()))
.addAll(getCommonCxxFlags(config))
.addAll(getCommonFlags(targetConfiguration, toolchainPaths))
.addAll(getCommonNdkCxxPlatformCompilerFlags())
.build();
}
/** The CPU architectures to target. */
public enum TargetCpuType {
ARM,
ARMV7,
ARM64,
X86,
X86_64,
MIPS,
}
/** The build toolchain, named (including compiler version) after the target platform/arch. */
public enum Toolchain {
X86("x86"),
X86_64("x86_64"),
ARM_LINUX_ANDROIDEABI("arm-linux-androideabi"),
AARCH64_LINUX_ANDROID("aarch64-linux-android"),
;
private final String value;
Toolchain(String value) {
this.value = Preconditions.checkNotNull(value);
}
@Override
public String toString() {
return value;
}
}
/** Name of the target CPU architecture. */
public enum TargetArch {
X86("x86"),
X86_64("x86_64"),
ARM("arm"),
ARM64("arm64"),
;
private final String value;
TargetArch(String value) {
this.value = Preconditions.checkNotNull(value);
}
@Override
public String toString() {
return value;
}
}
/** Name of the target CPU + ABI. */
public enum TargetArchAbi {
X86("x86"),
X86_64("x86_64"),
ARMEABI("armeabi"),
ARMEABI_V7A("armeabi-v7a"),
ARM64_V8A("arm64-v8a"),
;
private final String value;
TargetArchAbi(String value) {
this.value = Preconditions.checkNotNull(value);
}
@Override
public String toString() {
return value;
}
}
/** The OS and Architecture that we're building on. */
public enum Host {
DARWIN_X86_64("darwin-x86_64"),
LINUX_X86_64("linux-x86_64"),
WINDOWS_X86_64("windows-x86_64"),
;
private final String value;
Host(String value) {
this.value = Preconditions.checkNotNull(value);
}
@Override
public String toString() {
return value;
}
}
/** The toolchains name for the platform being targeted. */
public enum ToolchainTarget {
I686_LINUX_ANDROID("i686-linux-android"),
X86_64_LINUX_ANDROID("x86_64-linux-android"),
ARM_LINUX_ANDROIDEABI("arm-linux-androideabi"),
AARCH64_LINUX_ANDROID("aarch64-linux-android"),
;
private final String value;
ToolchainTarget(String value) {
this.value = Preconditions.checkNotNull(value);
}
@Override
public String toString() {
return value;
}
}
static class NdkCxxToolchainPaths {
private Path ndkRoot;
private String ndkVersion;
private NdkCxxPlatformTargetConfiguration targetConfiguration;
private String hostName;
private NdkCxxRuntime cxxRuntime;
private Map<String, Path> cachedPaths;
private boolean strict;
private int ndkMajorVersion;
private ProjectFilesystem filesystem;
NdkCxxToolchainPaths(
ProjectFilesystem filesystem,
Path ndkRoot,
NdkCxxPlatformTargetConfiguration targetConfiguration,
String hostName,
NdkCxxRuntime cxxRuntime,
boolean strict) {
this(
filesystem,
ndkRoot,
readVersion(ndkRoot),
targetConfiguration,
hostName,
cxxRuntime,
strict);
}
private NdkCxxToolchainPaths(
ProjectFilesystem filesystem,
Path ndkRoot,
String ndkVersion,
NdkCxxPlatformTargetConfiguration targetConfiguration,
String hostName,
NdkCxxRuntime cxxRuntime,
boolean strict) {
this.filesystem = filesystem;
this.cachedPaths = new HashMap<>();
this.strict = strict;
this.targetConfiguration = targetConfiguration;
this.hostName = hostName;
this.cxxRuntime = cxxRuntime;
this.ndkRoot = ndkRoot;
this.ndkVersion = ndkVersion;
this.ndkMajorVersion = getNdkMajorVersion(ndkVersion);
Assertions.assertCondition(ndkMajorVersion > 0, "Unknown ndk version: " + ndkVersion);
}
NdkCxxToolchainPaths getSanitizedPaths() {
return new NdkCxxToolchainPaths(
filesystem,
Paths.get(ANDROID_NDK_ROOT),
ndkVersion,
targetConfiguration,
BUILD_HOST_SUBST,
cxxRuntime,
false);
}
Path processPathPattern(Path root, String pattern) {
String key = root.toString() + "/" + pattern;
Path result = cachedPaths.get(key);
if (result == null) {
String[] segments = pattern.split("/");
result = root;
for (String s : segments) {
if (s.contains("{")) {
s = s.replace("{toolchain}", targetConfiguration.getToolchain().toString());
s =
s.replace(
"{toolchain_target}", targetConfiguration.getToolchainTarget().toString());
s = s.replace("{compiler_version}", targetConfiguration.getCompiler().getVersion());
s = s.replace("{compiler_type}", targetConfiguration.getCompiler().getType().getName());
s =
s.replace(
"{gcc_compiler_version}", targetConfiguration.getCompiler().getGccVersion());
s = s.replace("{hostname}", hostName);
s = s.replace("{target_platform}", targetConfiguration.getTargetAppPlatform());
s = s.replace("{target_arch}", targetConfiguration.getTargetArch().toString());
s = s.replace("{target_arch_abi}", targetConfiguration.getTargetArchAbi().toString());
}
result = result.resolve(s);
}
if (strict) {
Assertions.assertCondition(
result.toFile().exists(), result.toString() + " doesn't exist.");
}
cachedPaths.put(key, result);
}
return result;
}
private boolean isGcc() {
return targetConfiguration.getCompiler().getType() == NdkCxxPlatformCompiler.Type.GCC;
}
Path processPathPattern(String s) {
return processPathPattern(ndkRoot, s);
}
Path getNdkToolRoot() {
if (isGcc()) {
return processPathPattern("toolchains/{toolchain}-{compiler_version}/prebuilt/{hostname}");
} else {
if (ndkMajorVersion < 11) {
return processPathPattern("toolchains/llvm-{compiler_version}/prebuilt/{hostname}");
} else {
return processPathPattern("toolchains/llvm/prebuilt/{hostname}");
}
}
}
/**
* @return the path to use as the system root, targeted to the given target platform and
* architecture.
*/
Path getSysroot() {
return processPathPattern("platforms/{target_platform}/arch-{target_arch}");
}
Path getLibexecGccToolPath() {
Assertions.assertCondition(isGcc());
if (ndkMajorVersion < 12) {
return processPathPattern(
getNdkToolRoot(), "libexec/gcc/{toolchain_target}/{compiler_version}");
} else {
return processPathPattern(
getNdkToolRoot(), "libexec/gcc/{toolchain_target}/{compiler_version}.x");
}
}
Path getLibPath() {
String pattern;
if (isGcc()) {
if (ndkMajorVersion < 12) {
pattern = "lib/{compiler_type}/{toolchain_target}/{compiler_version}";
} else {
pattern = "lib/{compiler_type}/{toolchain_target}/{compiler_version}.x";
}
} else {
if (ndkMajorVersion < 11) {
pattern = "lib/{compiler_type}/{compiler_version}";
} else {
pattern = "lib64/{compiler_type}/{compiler_version}";
}
}
return processPathPattern(getNdkToolRoot(), pattern);
}
Path getNdkGccToolRoot() {
return processPathPattern(
"toolchains/{toolchain}-{gcc_compiler_version}/prebuilt/{hostname}");
}
Path getToolchainBinPath() {
if (isGcc()) {
return processPathPattern(getNdkToolRoot(), "{toolchain_target}/bin");
} else {
return processPathPattern(getNdkToolRoot(), "bin");
}
}
private Path getGccToolchainBinPath() {
return processPathPattern(getNdkGccToolRoot(), "{toolchain_target}/bin");
}
private Path getCxxRuntimeDirectory() {
if (cxxRuntime == NdkCxxRuntime.GNUSTL) {
return processPathPattern(
"sources/cxx-stl/" + cxxRuntime.getName() + "/{gcc_compiler_version}");
} else {
return processPathPattern("sources/cxx-stl/" + cxxRuntime.getName());
}
}
private Path getCxxRuntimeLibsDirectory() {
return processPathPattern(getCxxRuntimeDirectory(), "libs/{target_arch_abi}");
}
Path getToolPath(String tool) {
if (isGcc()) {
return processPathPattern(getNdkToolRoot(), "bin/{toolchain_target}-" + tool);
} else {
return processPathPattern(getNdkToolRoot(), "bin/" + tool);
}
}
public Path getNdkRoot() {
return ndkRoot;
}
public NdkCxxRuntime getCxxRuntime() {
return cxxRuntime;
}
}
}