Java tutorial
/* * 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.toolchain.ndk.impl; import com.facebook.buck.android.AndroidBuckConfig; import com.facebook.buck.android.toolchain.ndk.AndroidNdk; import com.facebook.buck.android.toolchain.ndk.AndroidNdkConstants; import com.facebook.buck.android.toolchain.ndk.NdkCompilerType; import com.facebook.buck.android.toolchain.ndk.NdkCxxPlatform; import com.facebook.buck.android.toolchain.ndk.NdkCxxPlatformCompiler; import com.facebook.buck.android.toolchain.ndk.NdkCxxPlatformTargetConfiguration; import com.facebook.buck.android.toolchain.ndk.NdkCxxRuntime; import com.facebook.buck.android.toolchain.ndk.NdkCxxRuntimeType; import com.facebook.buck.android.toolchain.ndk.NdkTargetArchAbi; import com.facebook.buck.android.toolchain.ndk.TargetCpuType; import com.facebook.buck.android.toolchain.ndk.UnresolvedNdkCxxPlatform; import com.facebook.buck.core.exceptions.HumanReadableException; import com.facebook.buck.core.model.Flavor; import com.facebook.buck.core.model.InternalFlavor; import com.facebook.buck.core.sourcepath.PathSourcePath; import com.facebook.buck.core.toolchain.ToolchainProvider; import com.facebook.buck.core.toolchain.tool.Tool; import com.facebook.buck.core.toolchain.tool.impl.VersionedTool; import com.facebook.buck.core.toolchain.toolprovider.ToolProvider; import com.facebook.buck.core.toolchain.toolprovider.impl.ConstantToolProvider; import com.facebook.buck.core.util.log.Logger; import com.facebook.buck.cxx.toolchain.ArchiveContents; import com.facebook.buck.cxx.toolchain.ArchiverProvider; import com.facebook.buck.cxx.toolchain.CompilerProvider; import com.facebook.buck.cxx.toolchain.CxxBuckConfig; import com.facebook.buck.cxx.toolchain.CxxPlatform; import com.facebook.buck.cxx.toolchain.CxxToolProvider; import com.facebook.buck.cxx.toolchain.DebugPathSanitizer; import com.facebook.buck.cxx.toolchain.ElfSharedLibraryInterfaceParams; import com.facebook.buck.cxx.toolchain.GnuArchiver; import com.facebook.buck.cxx.toolchain.HeaderVerification; import com.facebook.buck.cxx.toolchain.MungingDebugPathSanitizer; import com.facebook.buck.cxx.toolchain.PosixNmSymbolNameTool; import com.facebook.buck.cxx.toolchain.PrefixMapDebugPathSanitizer; import com.facebook.buck.cxx.toolchain.PreprocessorProvider; import com.facebook.buck.cxx.toolchain.SharedLibraryInterfaceParams; import com.facebook.buck.cxx.toolchain.linker.DefaultLinkerProvider; import com.facebook.buck.cxx.toolchain.linker.GnuLinker; import com.facebook.buck.cxx.toolchain.linker.Linker; import com.facebook.buck.cxx.toolchain.linker.LinkerProvider; import com.facebook.buck.io.ExecutableFinder; import com.facebook.buck.io.file.MorePaths; import com.facebook.buck.io.filesystem.ProjectFilesystem; import com.facebook.buck.util.VersionStringComparator; import com.facebook.buck.util.environment.Platform; import com.facebook.buck.util.environment.PlatformType; 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.ImmutableList.Builder; 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.Objects; 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 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 String DEFAULT_TARGET_APP_PLATFORM = "android-16"; 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); // TODO(cjhopman): Does the preprocessor need the -std= flags? Right now we don't send them. /** 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"); public static final ImmutableList<String> DEFAULT_COMMON_CXXPPFLAGS = DEFAULT_COMMON_CPPFLAGS; /** Flags used when compiling either C or C++ sources. */ public static final ImmutableList<String> DEFAULT_COMMON_COMPILER_FLAGS = ImmutableList.of( // Default compiler flags provided by the NDK build makefiles. "-ffunction-sections", "-funwind-tables", "-fomit-frame-pointer", "-fno-strict-aliasing"); /** Default linker flags added by the NDK. */ public static final ImmutableList<String> DEFAULT_COMMON_LDFLAGS = ImmutableList.of( // 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"); // 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 : ndkVersion.startsWith("16.") ? 16 : ndkVersion.startsWith("17.") ? 17 : ndkVersion.startsWith("18.") ? 18 : -1; } public static NdkCompilerType getDefaultCompilerTypeForNdk(String ndkVersion) { return getNdkMajorVersion(ndkVersion) < 18 ? NdkCompilerType.GCC : NdkCompilerType.CLANG; } public static NdkCxxRuntime getDefaultCxxRuntimeForNdk(String ndkVersion) { return getNdkMajorVersion(ndkVersion) < 18 ? NdkCxxRuntime.GNUSTL : NdkCxxRuntime.LIBCXX; } public static String getDefaultGccVersionForNdk(String ndkVersion) { return getNdkMajorVersion(ndkVersion) < 11 ? "4.8" : "4.9"; } public static String getDefaultClangVersionForNdk(String ndkVersion) { int ndkMajorVersion = getNdkMajorVersion(ndkVersion); if (ndkMajorVersion < 11) { return "3.5"; } else if (ndkMajorVersion < 15) { return "3.8"; } else if (ndkMajorVersion < 17) { return "5.0"; } else if (ndkMajorVersion < 18) { return "6.0.2"; } else { return "7.0.2"; } } 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); } /** Gets all the unresolved {@link NdkCxxPlatform} based on the buckconfig. */ public static ImmutableMap<TargetCpuType, UnresolvedNdkCxxPlatform> getPlatforms(CxxBuckConfig config, AndroidBuckConfig androidConfig, ProjectFilesystem filesystem, Platform platform, ToolchainProvider toolchainProvider, String ndkVersion) { AndroidNdk androidNdk = toolchainProvider.getByName(AndroidNdk.DEFAULT_NAME, AndroidNdk.class); Path ndkRoot = androidNdk.getNdkRootPath(); NdkCompilerType compilerType = androidConfig.getNdkCompiler() .orElse(NdkCxxPlatforms.getDefaultCompilerTypeForNdk(ndkVersion)); String gccVersion = androidConfig.getNdkGccVersion() .orElse(NdkCxxPlatforms.getDefaultGccVersionForNdk(ndkVersion)); String clangVersion = androidConfig.getNdkClangVersion() .orElse(NdkCxxPlatforms.getDefaultClangVersionForNdk(ndkVersion)); String compilerVersion = compilerType == NdkCompilerType.GCC ? gccVersion : clangVersion; NdkCxxPlatformCompiler compiler = NdkCxxPlatformCompiler.builder().setType(compilerType) .setVersion(compilerVersion).setGccVersion(gccVersion).build(); return getPlatforms(config, androidConfig, filesystem, ndkRoot, compiler, androidConfig.getNdkCxxRuntime().orElseGet(() -> getDefaultCxxRuntimeForNdk(ndkVersion)), androidConfig.getNdkCxxRuntimeType().orElse(NdkCxxRuntimeType.DYNAMIC), androidConfig.getNdkCpuAbis().orElseGet(() -> getDefaultCpuAbis(ndkVersion)), platform); } @VisibleForTesting static ImmutableSet<String> getDefaultCpuAbis(String ndkVersion) { int ndkMajorVersion = getNdkMajorVersion(ndkVersion); if (ndkMajorVersion > 16) { return ImmutableSet.of("armv7", "x86"); } else { return ImmutableSet.of("arm", "armv7", "x86"); } } @VisibleForTesting public static ImmutableMap<TargetCpuType, UnresolvedNdkCxxPlatform> getPlatforms(CxxBuckConfig config, AndroidBuckConfig androidConfig, ProjectFilesystem filesystem, Path ndkRoot, NdkCxxPlatformCompiler compiler, NdkCxxRuntime cxxRuntime, NdkCxxRuntimeType runtimeType, Set<String> cpuAbis, Platform platform) { return getPlatforms(config, androidConfig, filesystem, ndkRoot, compiler, cxxRuntime, runtimeType, cpuAbis, platform, new ExecutableFinder(), /* strictToolchainPaths */ true); } /** @return the map holding the available {@link NdkCxxPlatform}s. */ public static ImmutableMap<TargetCpuType, UnresolvedNdkCxxPlatform> getPlatforms(CxxBuckConfig config, AndroidBuckConfig androidConfig, ProjectFilesystem filesystem, Path ndkRoot, NdkCxxPlatformCompiler compiler, NdkCxxRuntime cxxRuntime, NdkCxxRuntimeType runtimeType, Set<String> cpuAbis, Platform platform, ExecutableFinder executableFinder, boolean strictToolchainPaths) { ImmutableMap.Builder<TargetCpuType, UnresolvedNdkCxxPlatform> ndkCxxPlatformBuilder = ImmutableMap .builder(); // ARM Platform if (cpuAbis.contains("arm")) { ndkCxxPlatformBuilder.put(TargetCpuType.ARM, getNdkCxxPlatform(config, androidConfig, filesystem, ndkRoot, compiler, cxxRuntime, runtimeType, platform, executableFinder, strictToolchainPaths, "arm", TargetCpuType.ARM, "android-arm")); } // ARMv7 Platform if (cpuAbis.contains("armv7")) { ndkCxxPlatformBuilder.put(TargetCpuType.ARMV7, getNdkCxxPlatform(config, androidConfig, filesystem, ndkRoot, compiler, cxxRuntime, runtimeType, platform, executableFinder, strictToolchainPaths, "armv7", TargetCpuType.ARMV7, "android-armv7")); } // ARM64 Platform if (cpuAbis.contains("arm64")) { ndkCxxPlatformBuilder.put(TargetCpuType.ARM64, getNdkCxxPlatform(config, androidConfig, filesystem, ndkRoot, compiler, cxxRuntime, runtimeType, platform, executableFinder, strictToolchainPaths, "arm64", TargetCpuType.ARM64, "android-arm64")); } // x86 Platform if (cpuAbis.contains("x86")) { ndkCxxPlatformBuilder.put(TargetCpuType.X86, getNdkCxxPlatform(config, androidConfig, filesystem, ndkRoot, compiler, cxxRuntime, runtimeType, platform, executableFinder, strictToolchainPaths, "x86", TargetCpuType.X86, "android-x86")); } // x86_64 Platform if (cpuAbis.contains("x86_64")) { ndkCxxPlatformBuilder.put(TargetCpuType.X86_64, getNdkCxxPlatform(config, androidConfig, filesystem, ndkRoot, compiler, cxxRuntime, runtimeType, platform, executableFinder, strictToolchainPaths, "x86_64", TargetCpuType.X86_64, "android-x86_64")); } return ndkCxxPlatformBuilder.build(); } private static UnresolvedNdkCxxPlatform getNdkCxxPlatform(CxxBuckConfig config, AndroidBuckConfig androidConfig, ProjectFilesystem filesystem, Path ndkRoot, NdkCxxPlatformCompiler compiler, NdkCxxRuntime cxxRuntime, NdkCxxRuntimeType runtimeType, Platform platform, ExecutableFinder executableFinder, boolean strictToolchainPaths, String cpuAbi, TargetCpuType cpuType, String flavorValue) { Flavor flavor = InternalFlavor.of(flavorValue); Optional<UnresolvedNdkCxxPlatform> dynamicPlatform = getDynamicNdkCxxPlatform(androidConfig, cpuAbi, flavor); if (dynamicPlatform.isPresent()) { return dynamicPlatform.get(); } String androidPlatform = androidConfig.getNdkAppPlatformForCpuAbi(cpuAbi) .orElse(NdkCxxPlatforms.DEFAULT_TARGET_APP_PLATFORM); NdkCxxPlatformTargetConfiguration targetConfiguration = getTargetConfiguration(cpuType, compiler, androidPlatform); return build(config, androidConfig, filesystem, flavor, platform, ndkRoot, targetConfiguration, cxxRuntime, runtimeType, executableFinder, strictToolchainPaths); } private static Optional<UnresolvedNdkCxxPlatform> getDynamicNdkCxxPlatform(AndroidBuckConfig androidConfig, String abi, Flavor flavor) { return androidConfig.getNdkCxxToolchainTargetForAbi(abi) .map(target -> new ProviderBackedUnresolvedNdkCxxPlatform(target, flavor)); } @VisibleForTesting static NdkCxxPlatformTargetConfiguration getTargetConfiguration(TargetCpuType targetCpuType, NdkCxxPlatformCompiler compiler, String androidPlatform) { if (targetCpuType == TargetCpuType.MIPS) { throw new AssertionError(); } return NdkCxxPlatformTargetConfiguration.builder().setTargetCpuType(targetCpuType) .setTargetAppPlatform(androidPlatform).setCompiler(compiler).build(); } @VisibleForTesting static Host getHost(Platform platform) { return Objects.requireNonNull(BUILD_PLATFORMS.get(platform)); } @VisibleForTesting static UnresolvedNdkCxxPlatform build(CxxBuckConfig config, AndroidBuckConfig androidConfig, ProjectFilesystem filesystem, Flavor flavor, Platform platform, Path ndkRoot, NdkCxxPlatformTargetConfiguration targetConfiguration, NdkCxxRuntime cxxRuntime, NdkCxxRuntimeType runtimeType, ExecutableFinder executableFinder, boolean strictToolchainPaths) { String ndkVersion = readVersion(ndkRoot); if (getNdkMajorVersion(ndkVersion) > 17 && cxxRuntime != NdkCxxRuntime.LIBCXX && cxxRuntime != NdkCxxRuntime.SYSTEM) { throw new HumanReadableException( "C++ runtime %s was removed in Android NDK 18.\n" + "Detected Android NDK version is %s.\n" + "Configuration needs to be changed in order to build with the current Android NDK", cxxRuntime.toString(), ndkVersion); } // 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. NdkCompilerType 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 = getHost(platform); NdkCxxToolchainPaths toolchainPaths = new NdkCxxToolchainPaths(filesystem, ndkRoot, targetConfiguration, host.toString(), cxxRuntime, strictToolchainPaths, getUseUnifiedHeaders(androidConfig, ndkVersion)); // 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, String> sanitizePathsBuilder = ImmutableBiMap.builder(); sanitizePathsBuilder.put(toolchainPaths.getNdkToolRoot(), MorePaths.pathWithUnixSeparators(sanitizedPaths.getNdkToolRoot())); if (compilerType != NdkCompilerType.GCC) { sanitizePathsBuilder.put(toolchainPaths.getNdkGccToolRoot(), MorePaths.pathWithUnixSeparators(sanitizedPaths.getNdkGccToolRoot())); } sanitizePathsBuilder.put(ndkRoot, AndroidNdkConstants.ANDROID_NDK_ROOT); CxxToolProvider.Type type = compilerType == NdkCompilerType.CLANG ? CxxToolProvider.Type.CLANG : CxxToolProvider.Type.GCC; ToolProvider ccTool = new ConstantToolProvider( getCTool(toolchainPaths, compilerType.cc, version, executableFinder)); ToolProvider cxxTool = new ConstantToolProvider( getCTool(toolchainPaths, compilerType.cxx, version, executableFinder)); CompilerProvider cc = new CompilerProvider(ccTool, () -> type, config.getUseDetailedUntrackedHeaderMessages(), true); PreprocessorProvider cpp = new PreprocessorProvider(ccTool, type, true); CompilerProvider cxx = new CompilerProvider(cxxTool, () -> type, config.getUseDetailedUntrackedHeaderMessages(), true); PreprocessorProvider cxxpp = new PreprocessorProvider(cxxTool, type, true); CxxPlatform.Builder cxxPlatformBuilder = CxxPlatform.builder(); ImmutableBiMap<Path, String> sanitizePaths = sanitizePathsBuilder.build(); PrefixMapDebugPathSanitizer compilerDebugPathSanitizer = new PrefixMapDebugPathSanitizer(".", sanitizePaths, true); DebugPathSanitizer assemblerDebugPathSanitizer = new MungingDebugPathSanitizer( config.getDebugPathSanitizerLimit(), File.separatorChar, Paths.get("."), sanitizePaths); cxxPlatformBuilder.setFlavor(flavor).setAs(cc) .addAllAsflags(getAsflags(targetConfiguration, toolchainPaths)).setAspp(cpp).setCc(cc) .addAllCflags(getCCompilationFlags(targetConfiguration, toolchainPaths, androidConfig)).setCpp(cpp) .addAllCppflags(getCPreprocessorFlags(targetConfiguration, toolchainPaths, androidConfig)) .setCxx(cxx) .addAllCxxflags(getCxxCompilationFlags(targetConfiguration, toolchainPaths, androidConfig)) .setCxxpp(cxxpp) .addAllCxxppflags(getCxxPreprocessorFlags(targetConfiguration, toolchainPaths, androidConfig)) .setLd(new DefaultLinkerProvider(LinkerProvider.Type.GNU, new ConstantToolProvider(getCcLinkTool(targetConfiguration, toolchainPaths, compilerType.cxx, version, cxxRuntime, executableFinder)), config.shouldCacheLinks())) .addAllLdflags(getLdFlags(targetConfiguration, androidConfig)) .setStrip(getGccTool(toolchainPaths, "strip", version, executableFinder)) .setSymbolNameTool( new PosixNmSymbolNameTool(getGccTool(toolchainPaths, "nm", version, executableFinder))) .setAr(ArchiverProvider .from(new GnuArchiver(getGccTool(toolchainPaths, "ar", version, executableFinder)))) .setArchiveContents(config.getArchiveContents().orElse(ArchiveContents.NORMAL)) .setRanlib( new ConstantToolProvider(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") .setSharedLibraryInterfaceParams( config.getSharedLibraryInterfaces() != SharedLibraryInterfaceParams.Type.DISABLED ? Optional.of(ElfSharedLibraryInterfaceParams.of( new ConstantToolProvider( getGccTool(toolchainPaths, "objcopy", version, executableFinder)), ImmutableList.of(), config.getSharedLibraryInterfaces() == SharedLibraryInterfaceParams.Type.DEFINED_ONLY)) : Optional.empty()) .setPublicHeadersSymlinksEnabled(config.getPublicHeadersSymlinksEnabled()) .setPrivateHeadersSymlinksEnabled(config.getPrivateHeadersSymlinksEnabled()) .setFilepathLengthLimited(config.getFilepathLengthLimited()); // 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.getHeaderVerificationOrIgnore(); 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.sharedName); cxxPlatformBuilder.putRuntimeLdflags(Linker.LinkableDepType.STATIC, "-l" + cxxRuntime.staticName); if (getNdkMajorVersion(ndkVersion) >= 12 && cxxRuntime == NdkCxxRuntime.LIBCXX) { if (getNdkMajorVersion(ndkVersion) < 17 || targetConfiguration.getTargetArchAbi() == NdkTargetArchAbi.ARMEABI_V7A || targetConfiguration.getTargetArchAbi() == NdkTargetArchAbi.X86) { cxxPlatformBuilder.putRuntimeLdflags(Linker.LinkableDepType.STATIC, "-landroid_support"); } cxxPlatformBuilder.putRuntimeLdflags(Linker.LinkableDepType.STATIC, "-lc++abi"); if (targetConfiguration.getTargetArchAbi() == NdkTargetArchAbi.ARMEABI) { cxxPlatformBuilder.putRuntimeLdflags(Linker.LinkableDepType.STATIC, "-latomic"); } } } CxxPlatform cxxPlatform = cxxPlatformBuilder.build(); NdkCxxPlatform.Builder builder = NdkCxxPlatform.builder(); builder.setCxxPlatform(cxxPlatform).setCxxRuntime(cxxRuntime) .setObjdump(getGccTool(toolchainPaths, "objdump", version, executableFinder)); if ((cxxRuntime != NdkCxxRuntime.SYSTEM) && (runtimeType != NdkCxxRuntimeType.STATIC)) { builder.setCxxSharedRuntimePath(PathSourcePath.of(filesystem, toolchainPaths.getCxxRuntimeLibsDirectory().resolve(cxxRuntime.getSoname()))); } return StaticUnresolvedNdkCxxPlatform.of(builder.build()); } @VisibleForTesting static boolean getUseUnifiedHeaders(AndroidBuckConfig androidConfig, String ndkVersion) { VersionStringComparator comparator = new VersionStringComparator(); Optional<Boolean> useUnifiedHeadersFromConfig = androidConfig.getNdkUnifiedHeaders(); if (useUnifiedHeadersFromConfig.isPresent()) { boolean useUnifiedHeaders = useUnifiedHeadersFromConfig.get(); if (useUnifiedHeaders && comparator.compare(ndkVersion, "14") < 0) { throw new HumanReadableException("Unified Headers can be only used with Android NDK 14 and newer.\n" + "Current configuration has Unified Headers enabled, but detected Android NDK version is %s.\n" + "Either change the configuration or upgrade to a newer Android NDK", ndkVersion); } else if (!useUnifiedHeaders && comparator.compare(ndkVersion, "16") >= 0) { throw new HumanReadableException("Non-unified headers were removed in Android NDK 16.\n" + "Current configuration has Unified Headers disabled, but detected Android NDK version is %s.\n" + "Configuration needs to be changed in order to build with the current Android NDK", ndkVersion); } return useUnifiedHeaders; } else { // If setting is not set then use Unified Headers starting from NDK 16 (which has no other // way), while older NDKs use the deprecated headers. return comparator.compare(ndkVersion, "16") >= 0; } } /** * 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 AndroidNdkResolver.findNdkVersionFromDirectory(ndkRoot).get(); } private static PathSourcePath 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 PathSourcePath.of(toolchainPaths.filesystem, path.get()); } private static PathSourcePath 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 PathSourcePath.of(toolchainPaths.filesystem, 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", MorePaths.pathWithUnixSeparators(toolchainPaths.getCxxRuntimeDirectory().resolve("include"))); flags.add("-isystem", MorePaths.pathWithUnixSeparators(toolchainPaths.getCxxRuntimeDirectory().resolve("libs") .resolve(targetConfiguration.getTargetArchAbi().toString()).resolve("include"))); break; case LIBCXX: String ndkVersion = readVersion(toolchainPaths.getNdkRoot()); // NDK r12b has a different include path for the LLVM headers if (getNdkMajorVersion(ndkVersion) <= 12) { flags.add("-isystem", MorePaths.pathWithUnixSeparators( toolchainPaths.getCxxRuntimeDirectory().resolve("libcxx").resolve("include"))); flags.add("-isystem", MorePaths.pathWithUnixSeparators(toolchainPaths.getCxxRuntimeDirectory() .getParent().resolve("llvm-libc++abi").resolve("libcxxabi").resolve("include"))); } else { flags.add("-isystem", MorePaths .pathWithUnixSeparators(toolchainPaths.getCxxRuntimeDirectory().resolve("include"))); flags.add("-isystem", MorePaths.pathWithUnixSeparators(toolchainPaths.getCxxRuntimeDirectory() .getParent().resolve("llvm-libc++abi").resolve("include"))); } flags.add("-isystem", MorePaths.pathWithUnixSeparators(toolchainPaths.getNdkRoot().resolve("sources") .resolve("android").resolve("support").resolve("include"))); break; // $CASES-OMITTED$ default: flags.add("-isystem", MorePaths.pathWithUnixSeparators(toolchainPaths.getCxxRuntimeDirectory().resolve("include"))); } 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() == NdkCompilerType.CLANG) { flags.add("-gcc-toolchain", MorePaths.pathWithUnixSeparators(toolchainPaths.getNdkGccToolRoot())); } // Set the sysroot to the platform-specific path. flags.add("--sysroot=" + MorePaths.pathWithUnixSeparators(toolchainPaths.getPlatformSysroot())); // TODO(#7264008): This was added for windows support but it's not clear why it's needed. if (targetConfiguration.getCompiler().getType() == NdkCompilerType.GCC) { flags.add("-B" + MorePaths.pathWithUnixSeparators(toolchainPaths.getLibexecGccToolPath()), "-B" + MorePaths.pathWithUnixSeparators(toolchainPaths.getLibPath())); } // Add the path to the C/C++ runtime libraries, if necessary. if (cxxRuntime != NdkCxxRuntime.SYSTEM) { flags.add("-L" + MorePaths.pathWithUnixSeparators(toolchainPaths.getCxxRuntimeLibsDirectory())); } return new GnuLinker(VersionedTool.builder().setPath(getToolPath(toolchainPaths, tool, executableFinder)) .setName(tool).setVersion(version).setExtraArgs(flags.build()).build()); } private static ImmutableList<String> getLdFlags(NdkCxxPlatformTargetConfiguration targetConfiguration, AndroidBuckConfig config) { return ImmutableList.<String>builder() .addAll(targetConfiguration.getLinkerFlags(targetConfiguration.getCompiler().getType())) .addAll(DEFAULT_COMMON_LDFLAGS).addAll(config.getExtraNdkLdFlags()).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() == NdkCompilerType.CLANG) { flags.add("-gcc-toolchain", MorePaths.pathWithUnixSeparators(toolchainPaths.getNdkGccToolRoot())); } // TODO(#7264008): This was added for windows support but it's not clear why it's needed. if (targetConfiguration.getCompiler().getType() == NdkCompilerType.GCC) { flags.add("-B" + MorePaths.pathWithUnixSeparators(toolchainPaths.getLibexecGccToolPath()), "-B" + MorePaths.pathWithUnixSeparators(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() == NdkCompilerType.CLANG) { flags.add("-Wno-unused-command-line-argument"); } // NDK builds enable stack protector and debug symbols by default. flags.add("-fstack-protector", "-g3"); if (toolchainPaths.isUnifiedHeaders()) { flags.add("-D__ANDROID_API__=" + targetConfiguration.getTargetAppPlatformLevel()); } return flags.build(); } private static ImmutableList<String> getCommonIncludes(NdkCxxToolchainPaths toolchainPaths) { ImmutableList.Builder<String> flags = new Builder<String>().add("-isystem", MorePaths.pathWithUnixSeparators(toolchainPaths.getNdkToolRoot().resolve("include")), "-isystem", MorePaths.pathWithUnixSeparators(toolchainPaths.getLibPath().resolve("include")), "-isystem", MorePaths.pathWithUnixSeparators( toolchainPaths.getIncludeSysroot().resolve("usr").resolve("include")), "-isystem", MorePaths.pathWithUnixSeparators( toolchainPaths.getIncludeSysroot().resolve("usr").resolve("include").resolve("linux"))); if (toolchainPaths.isUnifiedHeaders()) { flags.add("-isystem", MorePaths.pathWithUnixSeparators(toolchainPaths.getArchSpecificIncludes())); } return flags.build(); } 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(); } // TODO(cjhopman): The way that c/cpp/cxx/cxxpp flags work is rather unintuitive. The // documentation states that cflags/cxxflags are added to both preprocess and compile, // cppflags/cxxppflags are added only to the preprocessor flags. At runtime, we typically do // preprocess+compile, and in that case we're going to add both the preprocess and the compile // flags to the command line. Still, BUCK expects that a CxxPlatform can do all of // preprocess/compile/preprocess+compile. Many of the flags are duplicated across both preprocess // and compile to support that (and then typically our users have to deal with ridiculously long // command lines because we only ever do preprocess+compile). private static ImmutableList<String> getCPreprocessorFlags( NdkCxxPlatformTargetConfiguration targetConfiguration, NdkCxxToolchainPaths toolchainPaths, AndroidBuckConfig config) { return ImmutableList.<String>builder().addAll(getCommonIncludes(toolchainPaths)) .addAll(DEFAULT_COMMON_CPPFLAGS).addAll(getCommonFlags(targetConfiguration, toolchainPaths)) .addAll(DEFAULT_COMMON_CFLAGS) .addAll(targetConfiguration.getCompilerFlags(targetConfiguration.getCompiler().getType())) .addAll(config.getExtraNdkCFlags()).build(); } private static ImmutableList<String> getCxxPreprocessorFlags( NdkCxxPlatformTargetConfiguration targetConfiguration, NdkCxxToolchainPaths toolchainPaths, AndroidBuckConfig config) { ImmutableList.Builder<String> flags = ImmutableList.builder(); flags.addAll(getCxxRuntimeIncludeFlags(targetConfiguration, toolchainPaths)); flags.addAll(getCommonIncludes(toolchainPaths)); flags.addAll(DEFAULT_COMMON_CXXPPFLAGS); flags.addAll(getCommonFlags(targetConfiguration, toolchainPaths)); flags.addAll(DEFAULT_COMMON_CXXFLAGS); if (targetConfiguration.getCompiler().getType() == NdkCompilerType.GCC) { flags.add("-Wno-literal-suffix"); } flags.addAll(targetConfiguration.getCompilerFlags(targetConfiguration.getCompiler().getType())); flags.addAll(config.getExtraNdkCxxFlags()); return flags.build(); } private static ImmutableList<String> getCCompilationFlags(NdkCxxPlatformTargetConfiguration targetConfiguration, NdkCxxToolchainPaths toolchainPaths, AndroidBuckConfig config) { return ImmutableList.<String>builder() .addAll(targetConfiguration.getCompilerFlags(targetConfiguration.getCompiler().getType())) .addAll(DEFAULT_COMMON_CFLAGS).addAll(getCommonFlags(targetConfiguration, toolchainPaths)) .addAll(DEFAULT_COMMON_COMPILER_FLAGS).addAll(config.getExtraNdkCFlags()).build(); } private static ImmutableList<String> getCxxCompilationFlags( NdkCxxPlatformTargetConfiguration targetConfiguration, NdkCxxToolchainPaths toolchainPaths, AndroidBuckConfig config) { return ImmutableList.<String>builder() .addAll(targetConfiguration.getCompilerFlags(targetConfiguration.getCompiler().getType())) .addAll(DEFAULT_COMMON_CXXFLAGS).addAll(getCommonFlags(targetConfiguration, toolchainPaths)) .addAll(DEFAULT_COMMON_COMPILER_FLAGS).addAll(config.getExtraNdkCxxFlags()).build(); } /** 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 = Objects.requireNonNull(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 = Objects.requireNonNull(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 boolean unifiedHeaders; private int ndkMajorVersion; private ProjectFilesystem filesystem; NdkCxxToolchainPaths(ProjectFilesystem filesystem, Path ndkRoot, NdkCxxPlatformTargetConfiguration targetConfiguration, String hostName, NdkCxxRuntime cxxRuntime, boolean strict, boolean unifiedHeaders) { this(filesystem, ndkRoot, readVersion(ndkRoot), targetConfiguration, hostName, cxxRuntime, strict, unifiedHeaders); } private NdkCxxToolchainPaths(ProjectFilesystem filesystem, Path ndkRoot, String ndkVersion, NdkCxxPlatformTargetConfiguration targetConfiguration, String hostName, NdkCxxRuntime cxxRuntime, boolean strict, boolean unifiedHeaders) { this.filesystem = filesystem; this.cachedPaths = new HashMap<>(); this.strict = strict; this.unifiedHeaders = unifiedHeaders; 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(AndroidNdkConstants.ANDROID_NDK_ROOT), ndkVersion, targetConfiguration, BUILD_HOST_SUBST, cxxRuntime, false, unifiedHeaders); } Path processPathPattern(Path root, String pattern, boolean appendExtension) { String key = root + "/" + 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().name); 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 (appendExtension) { result = appendExtensionIfNeeded(result); } if (strict) { Assertions.assertCondition(result.toFile().exists(), result + " doesn't exist."); } cachedPaths.put(key, result); } return result; } Path processDirectoryPathPattern(Path root, String pattern) { return processPathPattern(root, pattern, false); } Path processExecutablePathPattern(Path root, String pattern) { return processPathPattern(root, pattern, true); } Path processDirectoryPathPattern(String s) { return processDirectoryPathPattern(ndkRoot, s); } private boolean isGcc() { return targetConfiguration.getCompiler().getType() == NdkCompilerType.GCC; } Path getNdkToolRoot() { if (isGcc()) { return processDirectoryPathPattern("toolchains/{toolchain}-{compiler_version}/prebuilt/{hostname}"); } else { if (ndkMajorVersion < 11) { return processDirectoryPathPattern("toolchains/llvm-{compiler_version}/prebuilt/{hostname}"); } else { return processDirectoryPathPattern("toolchains/llvm/prebuilt/{hostname}"); } } } boolean isUnifiedHeaders() { return unifiedHeaders; } /** Appends an executable extension if the current platform requires it. */ private static Path appendExtensionIfNeeded(Path path) { if (Platform.detect().getType() == PlatformType.WINDOWS) { return path.resolveSibling(path.getFileName() + ".exe"); } return path; } /** @return the path to arch-specific include files; only use with unified headers */ Path getArchSpecificIncludes() { return processDirectoryPathPattern("sysroot/usr/include/{toolchain_target}"); } /** * @return the path to use as the system root, targeted to the given target platform and * architecture. */ Path getIncludeSysroot() { if (isUnifiedHeaders()) { return processDirectoryPathPattern("sysroot"); } return getPlatformSysroot(); } Path getPlatformSysroot() { return processDirectoryPathPattern("platforms/{target_platform}/arch-{target_arch}"); } Path getLibexecGccToolPath() { Assertions.assertCondition(isGcc()); if (ndkMajorVersion < 12) { return processDirectoryPathPattern(getNdkToolRoot(), "libexec/gcc/{toolchain_target}/{compiler_version}"); } else if (ndkMajorVersion < 18) { return processDirectoryPathPattern(getNdkToolRoot(), "libexec/gcc/{toolchain_target}/{compiler_version}.x"); } else { return processDirectoryPathPattern(getNdkToolRoot(), "lib/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 processDirectoryPathPattern(getNdkToolRoot(), pattern); } Path getNdkGccToolRoot() { return processDirectoryPathPattern("toolchains/{toolchain}-{gcc_compiler_version}/prebuilt/{hostname}"); } Path getToolchainBinPath() { if (isGcc()) { return processDirectoryPathPattern(getNdkToolRoot(), "{toolchain_target}/bin"); } else { return processDirectoryPathPattern(getNdkToolRoot(), "bin"); } } private Path getGccToolchainBinPath() { return processDirectoryPathPattern(getNdkGccToolRoot(), "{toolchain_target}/bin"); } private Path getCxxRuntimeDirectory() { if (cxxRuntime == NdkCxxRuntime.GNUSTL) { return processDirectoryPathPattern( "sources/cxx-stl/" + cxxRuntime.name + "/{gcc_compiler_version}"); } else { return processDirectoryPathPattern("sources/cxx-stl/" + cxxRuntime.name); } } private Path getCxxRuntimeLibsDirectory() { return processDirectoryPathPattern(getCxxRuntimeDirectory(), "libs/{target_arch_abi}"); } Path getToolPath(String tool) { if (isGcc()) { return processExecutablePathPattern(getNdkToolRoot(), "bin/{toolchain_target}-" + tool); } else { return processExecutablePathPattern(getNdkToolRoot(), "bin/" + tool); } } public Path getNdkRoot() { return ndkRoot; } public NdkCxxRuntime getCxxRuntime() { return cxxRuntime; } } }