blob: 4f2aef9832b11b5dcfb63737a7196dd600a4aeed [file] [log] [blame]
/*
* Copyright (C) 2017 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.tasks;
import static com.android.build.gradle.external.cmake.CmakeUtils.getObjectToString;
import static com.android.build.gradle.internal.cxx.cmake.MakeCmakeMessagePathsAbsoluteKt.makeCmakeMessagePathsAbsolute;
import static com.android.build.gradle.internal.cxx.configure.CmakeCommandLineKt.convertCmakeCommandLineArgumentsToStringList;
import static com.android.build.gradle.internal.cxx.configure.CmakeCommandLineKt.getBuildRootFolder;
import static com.android.build.gradle.internal.cxx.configure.CmakeCommandLineKt.getGenerator;
import static com.android.build.gradle.internal.cxx.configure.CmakeCommandLineKt.onlyKeepServerArguments;
import static com.android.build.gradle.internal.cxx.configure.CmakeSourceFileNamingKt.hasCmakeHeaderFileExtensions;
import static com.android.build.gradle.internal.cxx.json.CompilationDatabaseIndexingVisitorKt.indexCompilationDatabase;
import static com.android.build.gradle.internal.cxx.json.CompilationDatabaseToolchainVisitorKt.populateCompilationDatabaseToolchains;
import static com.android.build.gradle.internal.cxx.logging.LoggingEnvironmentKt.errorln;
import static com.android.build.gradle.internal.cxx.logging.LoggingEnvironmentKt.infoln;
import static com.android.build.gradle.internal.cxx.logging.LoggingEnvironmentKt.warnln;
import static com.android.build.gradle.internal.cxx.model.CxxAbiModelKt.getJsonFile;
import static com.android.build.gradle.internal.cxx.model.CxxCmakeAbiModelKt.getCompileCommandsJsonFile;
import static com.android.build.gradle.internal.cxx.settings.CxxAbiModelCMakeSettingsRewriterKt.getBuildCommandArguments;
import static com.android.build.gradle.internal.cxx.settings.CxxAbiModelCMakeSettingsRewriterKt.getFinalCmakeCommandLineArguments;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.build.gradle.external.cmake.CmakeUtils;
import com.android.build.gradle.external.cmake.server.BuildFiles;
import com.android.build.gradle.external.cmake.server.CmakeInputsResult;
import com.android.build.gradle.external.cmake.server.CodeModel;
import com.android.build.gradle.external.cmake.server.ComputeResult;
import com.android.build.gradle.external.cmake.server.Configuration;
import com.android.build.gradle.external.cmake.server.ConfigureCommandResult;
import com.android.build.gradle.external.cmake.server.FileGroup;
import com.android.build.gradle.external.cmake.server.HandshakeRequest;
import com.android.build.gradle.external.cmake.server.HandshakeResult;
import com.android.build.gradle.external.cmake.server.IncludePath;
import com.android.build.gradle.external.cmake.server.Project;
import com.android.build.gradle.external.cmake.server.ProtocolVersion;
import com.android.build.gradle.external.cmake.server.Server;
import com.android.build.gradle.external.cmake.server.ServerFactory;
import com.android.build.gradle.external.cmake.server.ServerUtils;
import com.android.build.gradle.external.cmake.server.Target;
import com.android.build.gradle.external.cmake.server.receiver.InteractiveMessage;
import com.android.build.gradle.external.cmake.server.receiver.ServerReceiver;
import com.android.build.gradle.internal.LoggerWrapper;
import com.android.build.gradle.internal.cxx.configure.CommandLineArgument;
import com.android.build.gradle.internal.cxx.json.AndroidBuildGradleJsons;
import com.android.build.gradle.internal.cxx.json.CompilationDatabaseToolchain;
import com.android.build.gradle.internal.cxx.json.NativeBuildConfigValue;
import com.android.build.gradle.internal.cxx.json.NativeHeaderFileValue;
import com.android.build.gradle.internal.cxx.json.NativeLibraryValue;
import com.android.build.gradle.internal.cxx.json.NativeSourceFileValue;
import com.android.build.gradle.internal.cxx.json.NativeToolchainValue;
import com.android.build.gradle.internal.cxx.json.StringTable;
import com.android.build.gradle.internal.cxx.logging.PassThroughPrintWriterLoggingEnvironment;
import com.android.build.gradle.internal.cxx.logging.ThreadLoggingEnvironment;
import com.android.build.gradle.internal.cxx.model.CxxAbiModel;
import com.android.build.gradle.internal.cxx.model.CxxBuildModel;
import com.android.build.gradle.internal.cxx.model.CxxVariantModel;
import com.android.ide.common.process.ProcessException;
import com.android.repository.Revision;
import com.android.utils.ILogger;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.primitives.UnsignedInts;
import com.google.gson.stream.JsonReader;
import com.google.wireless.android.sdk.stats.GradleBuildVariant;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* This strategy uses the Vanilla-CMake that supports Cmake server version 1.0 to configure the
* project and generate the android build JSON.
*/
class CmakeServerExternalNativeJsonGenerator extends CmakeExternalNativeJsonGenerator {
private static final String CMAKE_SERVER_LOG_PREFIX = "CMAKE SERVER: ";
public CmakeServerExternalNativeJsonGenerator(
@NonNull CxxBuildModel build,
@NonNull CxxVariantModel variant,
@NonNull List<CxxAbiModel> abis,
@NonNull GradleBuildVariant.Builder stats) {
super(build, variant, abis, stats);
}
/**
* @param toolchains - toolchains map
* @return the hash of the only entry in the map, ideally the toolchains map should have only
* one entry.
*/
@Nullable
private static String getOnlyToolchainName(
@NonNull Map<String, NativeToolchainValue> toolchains) {
if (toolchains.size() != 1) {
throw new RuntimeException(
String.format(
"Invalid number %d of toolchains. Only one toolchain should be present.",
toolchains.size()));
}
return toolchains.keySet().iterator().next();
}
@NonNull
private static String getCmakeInfoString(@NonNull Server cmakeServer) throws IOException {
return String.format(
"Cmake path: %s, version: %s",
cmakeServer.getCmakePath(),
CmakeUtils.getVersion(new File(cmakeServer.getCmakePath())).toString());
}
@NonNull
@Override
public String executeProcessAndGetOutput(@NonNull CxxAbiModel abi)
throws ProcessException, IOException {
// Once a Cmake server object is created
// - connect to the server
// - perform a handshake
// - configure and compute.
// Create the NativeBuildConfigValue and write the required JSON file.
File cmakeServerLogFile = abi.getCmake().getCmakeServerLogFile().getAbsoluteFile();
cmakeServerLogFile.getParentFile().mkdirs();
try (ThreadLoggingEnvironment ignore =
new PassThroughPrintWriterLoggingEnvironment(
new PrintWriter(cmakeServerLogFile, "UTF-8"), CMAKE_SERVER_LOG_PREFIX)) {
// Create a new cmake server for the given Cmake and configure the given project.
ServerReceiver serverReceiver =
new ServerReceiver()
.setMessageReceiver(
message ->
logInteractiveMessage(
message, getMakefile().getParentFile()))
.setDiagnosticReceiver(message -> infoln(message));
File cmakeBinFolder = cmake.getCmakeExe().getParentFile();
Server cmakeServer = ServerFactory.create(cmakeBinFolder, serverReceiver);
if (cmakeServer == null) {
Revision actual = CmakeUtils.getVersion(cmakeBinFolder);
throw new RuntimeException(
String.format(
"Actual CMake version '%s.%s.%s' did not satisfy requested minimum or default "
+ "CMake minimum version '%s'. Possibly cmake.dir doesn't match "
+ "android.externalNativeBuild.cmake.version.",
actual.getMajor(),
actual.getMinor(),
actual.getMicro(),
cmake.getMinimumCmakeVersion()));
}
if (!cmakeServer.connect()) {
throw new RuntimeException(
"Unable to connect to Cmake server located at: "
+ cmakeBinFolder.getAbsolutePath());
}
try {
List<CommandLineArgument> arguments = getFinalCmakeCommandLineArguments(abi);
List<String> cacheArgumentsList =
convertCmakeCommandLineArgumentsToStringList(
onlyKeepServerArguments(arguments));
ConfigureCommandResult configureCommandResult;
// Handshake
doHandshake(
getGenerator(arguments),
variant.getModule().getMakeFile().getParentFile(),
new File(getBuildRootFolder(arguments)),
cmakeServer);
// Configure
String[] argsArray = cacheArgumentsList.toArray(new String[0]);
configureCommandResult = cmakeServer.configure(argsArray);
if (!ServerUtils.isConfigureResultValid(configureCommandResult.configureResult)) {
throw new ProcessException(
String.format(
"Error configuring CMake server (%s).\r\n%s",
cmakeServer.getCmakePath(),
configureCommandResult.interactiveMessages));
}
ComputeResult computeResult = doCompute(cmakeServer);
if (!ServerUtils.isComputedResultValid(computeResult)) {
throw new ProcessException(
"Error computing CMake server result.\r\n"
+ configureCommandResult.interactiveMessages);
}
generateAndroidGradleBuild(abi, cmakeServer);
return configureCommandResult.interactiveMessages;
} finally {
cmakeServer.disconnect();
}
}
}
/**
* Logs info/warning/error for the given interactive message. Throws a RunTimeException in case
* of an 'error' message type.
*/
private static void logInteractiveMessage(
@NonNull InteractiveMessage message, @NonNull File makeFileDirectory) {
// CMake error/warning prefix strings. The CMake errors and warnings are part of the
// message type "message" even though CMake is reporting errors/warnings (Note: They could
// have a title that says if it's an error or warning, we check that first before checking
// the prefix of the message string). Hence we would need to parse the output message to
// figure out if we need to log them as error or warning.
final String CMAKE_ERROR_PREFIX = "CMake Error";
final String CMAKE_WARNING_PREFIX = "CMake Warning";
// If the final message received is of type error, log and error and throw an exception.
// Note: This is not the same as a message with type "message" with error information, that
// case is handled below.
if (message.type != null && message.type.equals("error")) {
errorln(makeCmakeMessagePathsAbsolute(message.errorMessage, makeFileDirectory));
return;
}
String correctedMessage = makeCmakeMessagePathsAbsolute(message.message, makeFileDirectory);
if ((message.title != null && message.title.equals("Error"))
|| message.message.startsWith(CMAKE_ERROR_PREFIX)) {
errorln(correctedMessage);
return;
}
if ((message.title != null && message.title.equals("Warning"))
|| message.message.startsWith(CMAKE_WARNING_PREFIX)) {
warnln(correctedMessage);
return;
}
infoln(correctedMessage);
}
/**
* Requests a handshake to a connected Cmake server.
*
* @throws IOException I/O failure. Note: The function throws RuntimeException if we receive an
* invalid/erroneous handshake result.
*/
private void doHandshake(
@NonNull String generator,
@NonNull File sourceDirectory,
@NonNull File buildDirectory,
@NonNull Server cmakeServer)
throws IOException {
List<ProtocolVersion> supportedProtocolVersions = cmakeServer.getSupportedVersion();
if (supportedProtocolVersions == null || supportedProtocolVersions.isEmpty()) {
throw new RuntimeException(
String.format(
"Gradle does not support the Cmake server version. %s",
getCmakeInfoString(cmakeServer)));
}
HandshakeResult handshakeResult =
cmakeServer.handshake(
getHandshakeRequest(
generator,
sourceDirectory,
buildDirectory,
supportedProtocolVersions.get(0)));
if (!ServerUtils.isHandshakeResultValid(handshakeResult)) {
throw new RuntimeException(
String.format(
"Invalid handshake result from Cmake server: \n%s\n%s",
getObjectToString(handshakeResult), getCmakeInfoString(cmakeServer)));
}
}
/**
* Create a default handshake request for the given Cmake server-protocol version
*
* @return handshake request
*/
private HandshakeRequest getHandshakeRequest(
@NonNull String generator,
@NonNull File sourceDirectory,
@NonNull File buildDirectory,
@NonNull ProtocolVersion cmakeServerProtocolVersion) {
if (!sourceDirectory.isDirectory()) {
errorln("Not a directory: %s", sourceDirectory);
}
HandshakeRequest handshakeRequest = new HandshakeRequest();
handshakeRequest.cookie = "gradle-cmake-cookie";
handshakeRequest.generator = generator;
handshakeRequest.protocolVersion = cmakeServerProtocolVersion;
handshakeRequest.buildDirectory = normalizeFilePath(buildDirectory);
handshakeRequest.sourceDirectory = normalizeFilePath(sourceDirectory);
return handshakeRequest;
}
/**
* Generate build system files in the build directly, or compute the given project and returns
* the computed result.
*
* @param cmakeServer Connected cmake server.
* @throws IOException I/O failure. Note: The function throws RuntimeException if we receive an
* invalid/erroneous ComputeResult.
*/
private static ComputeResult doCompute(@NonNull Server cmakeServer) throws IOException {
return cmakeServer.compute();
}
/**
* Generates nativeBuildConfigValue by generating the code model from the cmake server and
* writes the android_gradle_build.json.
*
* @throws IOException I/O failure
*/
private void generateAndroidGradleBuild(
@NonNull CxxAbiModel config, @NonNull Server cmakeServer) throws IOException {
NativeBuildConfigValue nativeBuildConfigValue =
getNativeBuildConfigValue(config, cmakeServer);
AndroidBuildGradleJsons.writeNativeBuildConfigValueToJsonFile(
getJsonFile(config), nativeBuildConfigValue);
}
/**
* Returns NativeBuildConfigValue for the given abi from the given Cmake server.
*
* @return returns NativeBuildConfigValue
* @throws IOException I/O failure
*/
@VisibleForTesting
protected NativeBuildConfigValue getNativeBuildConfigValue(
@NonNull CxxAbiModel abi, @NonNull Server cmakeServer) throws IOException {
NativeBuildConfigValue nativeBuildConfigValue = createDefaultNativeBuildConfigValue();
assert nativeBuildConfigValue.stringTable != null;
StringTable strings = new StringTable(nativeBuildConfigValue.stringTable);
// Build file
assert nativeBuildConfigValue.buildFiles != null;
nativeBuildConfigValue.buildFiles.addAll(getBuildFiles(abi, cmakeServer));
// Clean commands
assert nativeBuildConfigValue.cleanCommands != null;
nativeBuildConfigValue.cleanCommands.add(
CmakeUtils.getCleanCommand(cmake.getCmakeExe(), abi.getCxxBuildFolder()));
// Build targets command.
assert nativeBuildConfigValue.buildTargetsCommand != null;
nativeBuildConfigValue.buildTargetsCommand =
CmakeUtils.getBuildTargetsCommand(
cmake.getCmakeExe(),
abi.getCxxBuildFolder(),
getBuildCommandArguments(abi));
CodeModel codeModel = cmakeServer.codemodel();
if (!ServerUtils.isCodeModelValid(codeModel)) {
throw new RuntimeException(
String.format(
"Invalid code model received from Cmake server: \n%s\n%s",
getObjectToString(codeModel), getCmakeInfoString(cmakeServer)));
}
// C and Cpp extensions
assert nativeBuildConfigValue.cFileExtensions != null;
nativeBuildConfigValue.cFileExtensions.addAll(CmakeUtils.getCExtensionSet(codeModel));
assert nativeBuildConfigValue.cppFileExtensions != null;
nativeBuildConfigValue.cppFileExtensions.addAll(CmakeUtils.getCppExtensionSet(codeModel));
// toolchains
nativeBuildConfigValue.toolchains =
getNativeToolchains(
abi,
cmakeServer,
nativeBuildConfigValue.cppFileExtensions,
nativeBuildConfigValue.cFileExtensions);
String toolchainHashString = getOnlyToolchainName(nativeBuildConfigValue.toolchains);
// Fill in the required fields in NativeBuildConfigValue from the code model obtained from
// Cmake server.
for (Configuration config : codeModel.configurations) {
for (Project project : config.projects) {
for (Target target : project.targets) {
// Ignore targets that aren't valid.
if (!canAddTargetToNativeLibrary(target)) {
continue;
}
NativeLibraryValue nativeLibraryValue =
getNativeLibraryValue(abi, abi.getCxxBuildFolder(), target, strings);
nativeLibraryValue.toolchain = toolchainHashString;
String libraryName =
target.name + "-" + config.name + "-" + abi.getAbi().getTag();
assert nativeBuildConfigValue.libraries != null;
nativeBuildConfigValue.libraries.put(libraryName, nativeLibraryValue);
} // target
} // project
}
return nativeBuildConfigValue;
}
@VisibleForTesting
protected NativeLibraryValue getNativeLibraryValue(
@NonNull CxxAbiModel abi,
@NonNull File workingDirectory,
@NonNull Target target,
StringTable strings)
throws FileNotFoundException {
return getNativeLibraryValue(
cmake.getCmakeExe(),
abi.getCxxBuildFolder(),
isDebuggable(),
new JsonReader(new FileReader(getCompileCommandsJsonFile(abi.getCmake()))),
abi.getAbi().getTag(),
workingDirectory,
target,
strings);
}
@VisibleForTesting
static NativeLibraryValue getNativeLibraryValue(
@NonNull File cmakeExecutable,
@NonNull File outputFolder,
boolean isDebuggable,
@NonNull JsonReader compileCommandsJson,
@NonNull String abi,
@NonNull File workingDirectory,
@NonNull Target target,
@NonNull StringTable strings) {
NativeLibraryValue nativeLibraryValue = new NativeLibraryValue();
nativeLibraryValue.abi = abi;
nativeLibraryValue.buildCommand =
CmakeUtils.getBuildCommand(cmakeExecutable, outputFolder, target.name);
nativeLibraryValue.artifactName = target.name;
nativeLibraryValue.buildType = isDebuggable ? "debug" : "release";
// We'll have only one output, so get the first one.
if (target.artifacts.length > 0) {
nativeLibraryValue.output = new File(target.artifacts[0]);
}
nativeLibraryValue.files = new ArrayList<>();
nativeLibraryValue.headers = new ArrayList<>();
// Maps each source file to the index of the corresponding strings table entry, which
// contains the build flags for that source file.
// It is important to not use a File or Path as the key to the dictionary, but instead
// use the corresponding normalized path. Two File/Path objects with the same normalized
// string representation may not be equivalent due to "../" or "./" substrings in them
// (b/123123307).
Map<String, Integer> compilationDatabaseFlags = Maps.newHashMap();
int workingDirectoryOrdinal = strings.intern(normalizeFilePath(workingDirectory));
for (FileGroup fileGroup : target.fileGroups) {
for (String source : fileGroup.sources) {
// CMake returns an absolute path or a path relative to the source directory,
// whichever one is shorter.
Path sourceFilePath = Paths.get(source);
if (!sourceFilePath.isAbsolute()) {
sourceFilePath = Paths.get(target.sourceDirectory, source);
}
// Even if CMake returns an absolute path, we still call normalize() to be symmetric
// with indexCompilationDatabase() which always uses normalized paths.
Path normalizedSourceFilePath = sourceFilePath.normalize();
if (!normalizedSourceFilePath.toString().isEmpty()) {
sourceFilePath = normalizedSourceFilePath;
}
// else {
// Normalized path should not be empty, unless CMake sends us really bogus data
// such as such as sourceDirectory="a/b", source="../../". This is not supposed
// to happen because (1) sourceDirectory should not be relative, and (2) source
// should contain at least a file name.
//
// Although it is very unlikely, this branch protects against that case by using
// the non-normalized path, which also makes the case more debuggable.
//
// Fall through intended.
// }
File sourceFile = sourceFilePath.toFile();
if (hasCmakeHeaderFileExtensions(sourceFile)) {
nativeLibraryValue.headers.add(
new NativeHeaderFileValue(sourceFile, workingDirectoryOrdinal));
} else {
NativeSourceFileValue nativeSourceFileValue = new NativeSourceFileValue();
nativeSourceFileValue.workingDirectoryOrdinal = workingDirectoryOrdinal;
nativeSourceFileValue.src = sourceFile;
// We use flags from compile_commands.json if present. Otherwise, fall back
// to server model compile flags (which is known to not always return a
// complete set).
// Reference b/116237485
if (compilationDatabaseFlags.isEmpty()) {
compilationDatabaseFlags =
indexCompilationDatabase(compileCommandsJson, strings);
}
if (compilationDatabaseFlags.containsKey(sourceFilePath.toString())) {
nativeSourceFileValue.flagsOrdinal =
compilationDatabaseFlags.get(sourceFilePath.toString());
} else {
// TODO I think this path is always wrong because it won't have --targets
// I don't want to make it an exception this late in 3.3 cycle so I'm
// leaving it as-is for now.
String compileFlags = compileFlagsFromFileGroup(fileGroup);
if (!Strings.isNullOrEmpty(compileFlags)) {
nativeSourceFileValue.flagsOrdinal = strings.intern(compileFlags);
}
}
nativeLibraryValue.files.add(nativeSourceFileValue);
}
}
}
return nativeLibraryValue;
}
private static String compileFlagsFromFileGroup(FileGroup fileGroup) {
StringBuilder flags = new StringBuilder();
flags.append(fileGroup.compileFlags);
if (fileGroup.defines != null) {
for (String define : fileGroup.defines) {
flags.append(" -D").append(define);
}
}
if (fileGroup.includePath != null) {
for (IncludePath includePath : fileGroup.includePath) {
if (includePath == null || includePath.path == null) {
continue;
}
if (includePath.isSystem != null && includePath.isSystem) {
flags.append(" -system ");
} else {
flags.append(" -I ");
}
flags.append(includePath.path);
}
}
return flags.toString();
}
/**
* Helper function that returns true if the Target object is valid to be added to native
* library.
*/
private static boolean canAddTargetToNativeLibrary(@NonNull Target target) {
// If the target has no artifacts or file groups, the target will be get ignored, so mark
// it valid.
return (target.artifacts != null)
&& (target.fileGroups != null)
&& !target.type.equals("OBJECT_LIBRARY");
}
/**
* Returns the list of build files used by CMake as part of the build system. Temporary files
* are currently ignored.
*/
@NonNull
private List<File> getBuildFiles(@NonNull CxxAbiModel config, @NonNull Server cmakeServer)
throws IOException {
CmakeInputsResult cmakeInputsResult = cmakeServer.cmakeInputs();
if (!ServerUtils.isCmakeInputsResultValid(cmakeInputsResult)) {
throw new RuntimeException(
String.format(
"Invalid cmakeInputs result received from Cmake server: \n%s\n%s",
getObjectToString(cmakeInputsResult), getCmakeInfoString(cmakeServer)));
}
// Ideally we should see the build files within cmakeInputs response, but in the weird case
// that we don't, return the default make file.
if (cmakeInputsResult.buildFiles == null) {
List<File> buildFiles = Lists.newArrayList();
buildFiles.add(getMakefile());
return buildFiles;
}
// The sources listed might be duplicated, so remove the duplicates.
Set<String> buildSources = Sets.newHashSet();
for (BuildFiles buildFile : cmakeInputsResult.buildFiles) {
if (buildFile.isTemporary || buildFile.isCMake || buildFile.sources == null) {
continue;
}
Collections.addAll(buildSources, buildFile.sources);
}
// The path to the build file source might be relative, so use the absolute path using
// source directory information.
File sourceDirectory = null;
if (cmakeInputsResult.sourceDirectory != null) {
sourceDirectory = new File(cmakeInputsResult.sourceDirectory);
}
List<File> buildFiles = Lists.newArrayList();
for (String source : buildSources) {
// The source file can either be relative or absolute, if it's relative, use the source
// directory to get the absolute path.
File sourceFile = new File(source);
if (!sourceFile.isAbsolute()) {
if (sourceDirectory != null) {
sourceFile = new File(sourceDirectory, source).getCanonicalFile();
}
}
if (!sourceFile.exists()) {
ILogger logger =
LoggerWrapper.getLogger(CmakeServerExternalNativeJsonGenerator.class);
logger.error(
null,
"Build file "
+ sourceFile
+ " provided by CMake "
+ "does not exists. This might lead to incorrect Android Studio behavior.");
continue;
}
if (sourceFile
.getPath()
.startsWith(config.getCmake().getCmakeWrappingBaseFolder().getPath())) {
// Skip files in .cxx/cmake/x86
continue;
}
buildFiles.add(sourceFile);
}
return buildFiles;
}
/**
* Creates a default NativeBuildConfigValue.
*
* @return a default NativeBuildConfigValue.
*/
@NonNull
private static NativeBuildConfigValue createDefaultNativeBuildConfigValue() {
NativeBuildConfigValue nativeBuildConfigValue = new NativeBuildConfigValue();
nativeBuildConfigValue.buildFiles = new ArrayList<>();
nativeBuildConfigValue.cleanCommands = new ArrayList<>();
nativeBuildConfigValue.libraries = new HashMap<>();
nativeBuildConfigValue.toolchains = new HashMap<>();
nativeBuildConfigValue.cFileExtensions = new ArrayList<>();
nativeBuildConfigValue.cppFileExtensions = new ArrayList<>();
nativeBuildConfigValue.stringTable = Maps.newHashMap();
return nativeBuildConfigValue;
}
/**
* Returns the native toolchain for the given abi from the provided Cmake server. We ideally
* should get the toolchain information compile commands JSON file. If it's unavailable, we
* fallback to figuring this information out from the messages produced by Cmake server when
* configuring the project (though hacky, it works!).
*
* @param abi - ABI for which NativeToolchainValue needs to be created
* @param cmakeServer - Cmake server
* @param cppExtensionSet - CXX extensions
* @param cExtensionSet - C extensions
* @return a map of toolchain hash to toolchain value. The map will have only one entry.
*/
@NonNull
private static Map<String, NativeToolchainValue> getNativeToolchains(
@NonNull CxxAbiModel abi,
@NonNull Server cmakeServer,
@NonNull Collection<String> cppExtensionSet,
@NonNull Collection<String> cExtensionSet) {
NativeToolchainValue toolchainValue = new NativeToolchainValue();
File cCompilerExecutable = null;
File cppCompilerExecutable = null;
File compilationDatabase = getCompileCommandsJsonFile(abi.getCmake());
if (compilationDatabase.exists()) {
CompilationDatabaseToolchain toolchain =
populateCompilationDatabaseToolchains(
compilationDatabase, cppExtensionSet, cExtensionSet);
cppCompilerExecutable = toolchain.getCppCompilerExecutable();
cCompilerExecutable = toolchain.getCCompilerExecutable();
} else {
if (!cmakeServer.getCCompilerExecutable().isEmpty()) {
cCompilerExecutable = new File(cmakeServer.getCCompilerExecutable());
}
if (!cmakeServer.getCppCompilerExecutable().isEmpty()) {
cppCompilerExecutable = new File(cmakeServer.getCppCompilerExecutable());
}
}
if (cCompilerExecutable != null) {
toolchainValue.cCompilerExecutable = cCompilerExecutable;
}
if (cppCompilerExecutable != null) {
toolchainValue.cppCompilerExecutable = cppCompilerExecutable;
}
int toolchainHash = CmakeUtils.getToolchainHash(toolchainValue);
String toolchainHashString = UnsignedInts.toString(toolchainHash);
Map<String, NativeToolchainValue> toolchains = new HashMap<>();
toolchains.put(toolchainHashString, toolchainValue);
return toolchains;
}
/**
* Returns the normalized path for the given file. The normalized path for Unix is the default
* string returned by getPath. For Microsoft Windows, getPath returns a path with "\\" (example:
* "C:\\Android\\Sdk") while Vanilla-CMake prefers a forward slash (example "C:/Android/Sdk"),
* without the forward slash, CMake would mix backward slash and forward slash causing compiler
* issues. This function replaces the backward slashes with forward slashes for Microsoft
* Windows.
*/
@NonNull
private static String normalizeFilePath(@NonNull File file) {
if (isWindows()) {
return (file.getPath().replace("\\", "/"));
}
return file.getPath();
}
}