com.android.build.gradle.internal.NdkHandler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gradle-core Show documentation
Show all versions of gradle-core Show documentation
Core library to build Android Gradle plugin.
/*
* Copyright (C) 2014 The Android Open Source Project
*
* 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.android.build.gradle.internal;
import static com.android.SdkConstants.FN_LOCAL_PROPERTIES;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.build.gradle.internal.core.Abi;
import com.android.build.gradle.internal.core.Toolchain;
import com.android.sdklib.AndroidTargetHash;
import com.android.sdklib.AndroidVersion;
import com.android.sdklib.repository.PreciseRevision;
import com.android.utils.FileUtils;
import com.android.utils.Pair;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.io.Closeables;
import org.gradle.api.InvalidUserDataException;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
/**
* Handles NDK related information.
*/
public class NdkHandler {
@Nullable
private String platformVersion;
private boolean resolvedSdkVersion;
private final Toolchain toolchain;
private final String toolchainVersion;
private final File ndkDirectory;
private Map, PreciseRevision> defaultToolchainVersions = Maps.newHashMap();
public NdkHandler(
@NonNull File projectDir,
@Nullable String platformVersion,
@NonNull String toolchainName,
@NonNull String toolchainVersion) {
if (platformVersion != null) {
setPlatformVersion(platformVersion);
}
this.toolchain = Toolchain.getByName(toolchainName);
this.toolchainVersion = toolchainVersion;
ndkDirectory = findNdkDirectory(projectDir);
}
@Nullable
private String getPlatformVersion() {
if (!resolvedSdkVersion) {
resolveCompileSdkVersion();
}
return platformVersion;
}
/**
* Retrieve the newest supported version if it is not the specified version is not supported.
*
* An older NDK may not support the specified compiledSdkVersion. In that case, determine what
* is the newest supported version and modifycompileSdkVersion.
*/
private void resolveCompileSdkVersion() {
if (platformVersion == null) {
return;
}
File platformFolder = new File(ndkDirectory, "/platforms/" + platformVersion);
if (!platformFolder.exists()) {
int targetVersion;
try {
targetVersion = Integer.parseInt(platformVersion.substring("android-".length()));
} catch (NumberFormatException ignore) {
// If the targetVerison is not a number, most likely it is a preview version.
// In that case, assume we are using the highest available version.
targetVersion = Integer.MAX_VALUE;
}
File[] platformFolders = new File(ndkDirectory, "/platforms/").listFiles(
new FileFilter() {
@Override
public boolean accept(File file) {
return file.isDirectory();
}
});
int highestVersion = 0;
for(File platform :platformFolders) {
if (platform.getName().startsWith("android-")) {
try {
int version = Integer.parseInt(
platform.getName().substring("android-".length()));
if (version > highestVersion && version < targetVersion) {
highestVersion = version;
platformVersion = "android-" + version;
}
} catch(NumberFormatException ignore) {
}
}
}
}
resolvedSdkVersion = true;
}
public void setPlatformVersion(@NonNull String platformVersion) {
// Ensure compileSdkVersion is in platform hash string format (e.g. "android-21").
AndroidVersion androidVersion = AndroidTargetHash.getVersionFromHash(platformVersion);
if (androidVersion == null) {
this.platformVersion = null;
} else {
this.platformVersion = AndroidTargetHash.getPlatformHashString(androidVersion);
}
resolvedSdkVersion = false;
}
public Toolchain getToolchain() {
return toolchain;
}
public String getToolchainVersion() {
return toolchainVersion;
}
/**
* Determine the location of the NDK directory.
*
* The NDK directory can be set in the local.properties file or using the ANDROID_NDK_HOME
* environment variable.
*/
private static File findNdkDirectory(File projectDir) {
File localProperties = new File(projectDir, FN_LOCAL_PROPERTIES);
if (localProperties.isFile()) {
Properties properties = new Properties();
InputStreamReader reader = null;
try {
//noinspection IOResourceOpenedButNotSafelyClosed
FileInputStream fis = new FileInputStream(localProperties);
reader = new InputStreamReader(fis, Charsets.UTF_8);
properties.load(reader);
} catch (FileNotFoundException ignored) {
// ignore since we check up front and we don't want to fail on it anyway
// in case there's an env var.
} catch (IOException e) {
throw new RuntimeException(String.format("Unable to read %1$s.", localProperties), e);
} finally {
try {
Closeables.close(reader, true /* swallowIOException */);
} catch (IOException e) {
// ignore.
}
}
String ndkDirProp = properties.getProperty("ndk.dir");
if (ndkDirProp != null) {
return new File(ndkDirProp);
}
} else {
String envVar = System.getenv("ANDROID_NDK_HOME");
if (envVar != null) {
return new File(envVar);
}
}
return null;
}
/**
* Returns the directory of the NDK.
*/
@Nullable
public File getNdkDirectory() {
return ndkDirectory;
}
/**
* Return true if NDK directory is configured.
*/
public boolean isNdkDirConfigured() {
return ndkDirectory != null;
}
private static String getToolchainPrefix(Toolchain toolchain, Abi abi) {
if (toolchain == Toolchain.GCC) {
return abi.getGccToolchainPrefix();
} else {
return "llvm";
}
}
/**
* Return the directory containing the toolchain.
*
* @param toolchain toolchain to use.
* @param toolchainVersion toolchain version to use.
* @param abi target ABI of the toolchaina
* @return a directory that contains the executables.
*/
private File getToolchainPath(
Toolchain toolchain,
String toolchainVersion,
Abi abi) {
String version = toolchainVersion.isEmpty()
? getDefaultToolchainVersion(toolchain, abi).toString()
: toolchainVersion;
File prebuiltFolder = new File(
ndkDirectory,
"toolchains/" + getToolchainPrefix(toolchain, abi) + "-" + version + "/prebuilt");
String osName = System.getProperty("os.name").toLowerCase(Locale.ENGLISH);
String hostOs;
if (osName.contains("windows")) {
hostOs = "windows";
} else if (osName.contains("mac")) {
hostOs = "darwin";
} else {
hostOs = "linux";
}
// There should only be one directory in the prebuilt folder. If there are more than one
// attempt to determine the right one based on the operating system.
File[] toolchainPaths = prebuiltFolder.listFiles(
new FileFilter() {
@Override
public boolean accept(File file) {
return file.isDirectory();
}
});
if (toolchainPaths == null) {
throw new InvalidUserDataException("Unable to find toolchain: " + prebuiltFolder);
}
if (toolchainPaths.length == 1) {
return toolchainPaths[0];
}
// Use 64-bit toolchain if available.
File toolchainPath = new File(prebuiltFolder, hostOs + "-x86_64");
if (toolchainPath.isDirectory()) {
return toolchainPath;
}
// Fallback to 32-bit if we can't find the 64-bit toolchain.
String osString = (osName.equals("windows")) ? hostOs : hostOs + "-x86";
toolchainPath = new File(prebuiltFolder, osString);
if (toolchainPath.isDirectory()) {
return toolchainPath;
} else {
throw new InvalidUserDataException("Unable to find toolchain prebuilt folder in: "
+ prebuiltFolder);
}
}
/**
* Returns the sysroot directory for the toolchain.
*/
public String getSysroot(Abi abi) {
if (getPlatformVersion() == null) {
return "";
} else {
return ndkDirectory + "/platforms/" + getPlatformVersion() + "/arch-"
+ abi.getArchitecture();
}
}
/**
* Return the directory containing prebuilt binaries such as gdbserver.
*/
public File getPrebuiltDirectory(Abi abi) {
return new File(ndkDirectory, "prebuilt/android-" + abi.getArchitecture());
}
/**
* Return true if compiledSdkVersion supports 64 bits ABI.
*/
public boolean supports64Bits() {
if (getPlatformVersion() == null) {
return false;
}
String targetString = getPlatformVersion().replace("android-", "");
try {
return Integer.parseInt(targetString) >= 20;
} catch (NumberFormatException ignored) {
// "android-L" supports 64-bits.
return true;
}
}
/**
* Return the default version of the specified toolchain for a target abi.
*
* The default version is the highest version found in the NDK for the specified toolchain and
* ABI. The result is cached for performance.
*/
private PreciseRevision getDefaultToolchainVersion(Toolchain toolchain, final Abi abi) {
PreciseRevision defaultVersion = defaultToolchainVersions.get(Pair.of(toolchain, abi));
if (defaultVersion != null) {
return defaultVersion;
}
final String toolchainPrefix = getToolchainPrefix(toolchain, abi);
File toolchains = new File(ndkDirectory, "toolchains");
File[] toolchainsForAbi = toolchains.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.startsWith(toolchainPrefix);
}
});
if (toolchainsForAbi == null || toolchainsForAbi.length == 0) {
throw new RuntimeException(
"No toolchains found in the NDK toolchains folder for ABI with prefix: "
+ toolchainPrefix);
}
// Once we have a list of toolchains, we look the highest version
PreciseRevision bestRevision = null;
for (File toolchainFolder : toolchainsForAbi) {
String folderName = toolchainFolder.getName();
String version = folderName.substring(toolchainPrefix.length() + 1);
try {
PreciseRevision revision = PreciseRevision.parseRevision(version);
if (bestRevision == null || revision.compareTo(bestRevision) > 0) {
bestRevision = revision;
}
} catch (NumberFormatException ignore) {
}
}
defaultToolchainVersions.put(Pair.of(toolchain, abi), bestRevision);
if (bestRevision == null) {
throw new RuntimeException("Unable to find a valid toolchain in " + toolchains);
}
return bestRevision;
}
/**
* Return the version of gcc that will be used by the NDK.
*
* Gcc is used by clang for linking. It also contains gnu-libstdc++.
*
* If the gcc toolchain is used, then it's simply the toolchain version requested by the user.
* If clang is used, then it depends the target abi.
*/
public String getGccToolchainVersion(Abi abi) {
return (toolchain == Toolchain.GCC && !toolchainVersion.isEmpty())
? toolchainVersion
: getDefaultToolchainVersion(Toolchain.GCC, abi).toString();
}
/**
* Return the folder containing gcc that will be used by the NDK.
*/
public File getDefaultGccToolchainPath(Abi abi) {
return getToolchainPath(Toolchain.GCC, getGccToolchainVersion(abi), abi);
}
/**
* Returns a list of all ABI.
*/
public static Collection getAbiList() {
return ImmutableList.copyOf(Abi.values());
}
/**
* Returns a list of 32-bits ABI.
*/
public static Collection getAbiList32() {
ImmutableList.Builder builder = ImmutableList.builder();
for (Abi abi : Abi.values()) {
if (!abi.supports64Bits()) {
builder.add(abi);
}
}
return builder.build();
}
/**
* Returns a list of supported ABI.
*/
public Collection getSupportedAbis() {
return supports64Bits() ? getAbiList() : getAbiList32();
}
/**
* Return the executable for compiling C code.
*/
public File getCCompiler(Abi abi) {
String compiler = toolchain == Toolchain.CLANG ? "clang" : abi.getGccExecutablePrefix() + "-gcc";
return new File(getToolchainPath(toolchain, toolchainVersion, abi), "bin/" + compiler);
}
/**
* Return the executable for compiling C++ code.
*/
public File getCppCompiler(Abi abi) {
String compiler = toolchain == Toolchain.CLANG ? "clang++" : abi.getGccExecutablePrefix() + "-g++";
return new File(getToolchainPath(toolchain, toolchainVersion, abi), "bin/" + compiler);
}
/**
* Return the executable for removing debug symbols from a shared object.
*/
public File getStripCommand(Abi abi) {
return FileUtils.join(
getDefaultGccToolchainPath(abi),
"bin",
abi.getGccExecutablePrefix() + "-strip");
}
/**
* Return a list of include directories for an STl.
*/
public List getStlIncludes(@Nullable String stlName, @NonNull Abi abi) {
File stlBaseDir = new File(ndkDirectory, "sources/cxx-stl/");
if (stlName == null || stlName.isEmpty()) {
stlName = "system";
} else if (stlName.contains("_")) {
stlName = stlName.substring(0, stlName.indexOf('_'));
}
List includeDirs = Lists.newArrayList();
if (stlName.equals("system")) {
includeDirs.add(new File(stlBaseDir, "system/include"));
} else if (stlName.equals("stlport")) {
includeDirs.add(new File(stlBaseDir, "stlport/stlport"));
includeDirs.add(new File(stlBaseDir, "gabi++/include"));
} else if (stlName.equals("gnustl")) {
String gccToolchainVersion = getGccToolchainVersion(abi);
includeDirs.add(new File(stlBaseDir, "gnu-libstdc++/" + gccToolchainVersion + "/include"));
includeDirs.add(new File(stlBaseDir, "gnu-libstdc++/" + gccToolchainVersion +
"/libs/" + abi.getName() + "/include"));
includeDirs.add(new File(stlBaseDir, "gnu-libstdc++/" + gccToolchainVersion +
"/include/backward"));
} else if (stlName.equals("gabi++")) {
includeDirs.add(new File(stlBaseDir, "gabi++/include"));
} else if (stlName.equals("c++")) {
includeDirs.add(new File(stlBaseDir, "llvm-libc++/libcxx/include"));
includeDirs.add(new File(stlBaseDir, "gabi++/include"));
includeDirs.add(new File(stlBaseDir, "../android/support/include"));
}
return includeDirs;
}
}