| /* |
| * 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.configure.CmakeAndroidGradleBuildExtensionsKt.wrapCmakeListsForCompilerSettingsCaching; |
| 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.tasks.ExternalNativeBuildTaskUtils.getOutputFolder; |
| |
| 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.CompileCommand; |
| 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.CmakeExecutionConfiguration; |
| import com.android.build.gradle.internal.cxx.configure.JsonGenerationAbiConfiguration; |
| import com.android.build.gradle.internal.cxx.configure.JsonGenerationVariantConfiguration; |
| 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.builder.core.AndroidBuilder; |
| import com.android.ide.common.process.ProcessException; |
| 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; |
| import org.apache.commons.io.FileUtils; |
| |
| /** |
| * 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 JsonGenerationVariantConfiguration config, |
| @NonNull Set<String> configurationFailures, |
| @NonNull AndroidBuilder androidBuilder, |
| @NonNull File cmakeFolder, |
| @NonNull GradleBuildVariant.Builder stats) { |
| super(config, configurationFailures, androidBuilder, cmakeFolder, 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 |
| List<String> getCacheArguments(@NonNull JsonGenerationAbiConfiguration abiConfig) { |
| List<String> cacheArguments = getCommonCacheArguments(abiConfig); |
| cacheArguments.add("-DCMAKE_SYSTEM_NAME=Android"); |
| cacheArguments.add(String.format("-DCMAKE_ANDROID_ARCH_ABI=%s", abiConfig.getAbiName())); |
| cacheArguments.add( |
| String.format("-DCMAKE_SYSTEM_VERSION=%s", abiConfig.getAbiPlatformVersion())); |
| // Generates the compile_commands json file that will help us get the compiler executable |
| // and flags. |
| cacheArguments.add("-DCMAKE_EXPORT_COMPILE_COMMANDS=ON"); |
| cacheArguments.add(String.format("-DCMAKE_ANDROID_NDK=%s", getNdkFolder())); |
| |
| cacheArguments.add( |
| String.format( |
| "-DCMAKE_TOOLCHAIN_FILE=%s", |
| getToolchainFile(abiConfig.getAbiName()).getAbsolutePath())); |
| |
| // By default, use the ninja generator. |
| cacheArguments.add("-G Ninja"); |
| |
| // To preserve backward compatibility with fork CMake look for ninja.exe next to cmake.exe |
| // and use it. If it's not there then normal CMake search logic will be used. |
| File possibleNinja = |
| isWindows() |
| ? new File(getCmakeBinFolder(), "ninja.exe") |
| : new File(getCmakeBinFolder(), "ninja"); |
| if (possibleNinja.isFile()) { |
| cacheArguments.add(String.format("-DCMAKE_MAKE_PROGRAM=%s", possibleNinja)); |
| } |
| return cacheArguments; |
| } |
| |
| @NonNull |
| @Override |
| public String executeProcessAndGetOutput(@NonNull JsonGenerationAbiConfiguration abiConfig) |
| 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. |
| try (PrintWriter serverLogWriter = |
| getCmakeServerLogWriter(getOutputFolder(getJsonFolder(), abiConfig.getAbiName()))) { |
| ILogger logger = LoggerWrapper.getLogger(CmakeServerExternalNativeJsonGenerator.class); |
| Server cmakeServer = createServerAndConnect(serverLogWriter, logger); |
| |
| List<String> cacheArgumentsList = getCacheArguments(abiConfig); |
| cacheArgumentsList.addAll(getBuildArguments()); |
| ConfigureCommandResult configureCommandResult; |
| File cmakeListsFolder = getMakefile().getParentFile(); |
| if (config.enableCmakeCompilerSettingsCache) { |
| // Configure extensions |
| CmakeExecutionConfiguration executableConfiguration = |
| wrapCmakeListsForCompilerSettingsCaching( |
| config.compilerSettingsCacheFolder, |
| abiConfig, |
| getMakefile().getParentFile(), |
| cacheArgumentsList); |
| |
| cacheArgumentsList = executableConfiguration.getArgs(); |
| cmakeListsFolder = executableConfiguration.getCmakeListsFolder(); |
| } |
| |
| // Handshake |
| doHandshake(cmakeListsFolder, abiConfig.getExternalNativeBuildFolder(), cmakeServer); |
| |
| // Configure |
| String argsArray[] = cacheArgumentsList.toArray(new String[cacheArgumentsList.size()]); |
| 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(abiConfig, cmakeServer); |
| return configureCommandResult.interactiveMessages; |
| } |
| } |
| |
| /** Returns PrintWriter object to write CMake server logs. */ |
| @NonNull |
| private static PrintWriter getCmakeServerLogWriter(@NonNull File outputFolder) |
| throws IOException { |
| return new PrintWriter(getCmakeServerLog(outputFolder).getAbsoluteFile(), "UTF-8"); |
| } |
| |
| /** Returns the CMake server log file using the given output folder. */ |
| @NonNull |
| private static File getCmakeServerLog(@NonNull File outputFolder) { |
| return new File(outputFolder, "cmake_server_log.txt"); |
| } |
| |
| /** |
| * Creates a Cmake server and connects to it. |
| * |
| * @return a Cmake Server object that's successfully connected to the Cmake server |
| * @throws IOException I/O failure. Note: The function throws RuntimeException if we are unable |
| * to create or connect to Cmake server. |
| */ |
| @NonNull |
| private Server createServerAndConnect( |
| @NonNull PrintWriter serverLogWriter, @NonNull ILogger logger) throws IOException { |
| // Create a new cmake server for the given Cmake and configure the given project. |
| ServerReceiver serverReceiver = |
| new ServerReceiver() |
| .setMessageReceiver( |
| message -> |
| receiveInteractiveMessage( |
| serverLogWriter, |
| logger, |
| message, |
| getMakefile().getParentFile())) |
| .setDiagnosticReceiver( |
| message -> |
| receiveDiagnosticMessage(serverLogWriter, logger, message)); |
| Server cmakeServer = ServerFactory.create(getCmakeBinFolder(), serverReceiver); |
| if (cmakeServer == null) { |
| throw new RuntimeException( |
| "Unable to create a Cmake server located at: " |
| + getCmakeBinFolder().getAbsolutePath()); |
| } |
| |
| if (!cmakeServer.connect()) { |
| throw new RuntimeException( |
| "Unable to connect to Cmake server located at: " |
| + getCmakeBinFolder().getAbsolutePath()); |
| } |
| |
| return cmakeServer; |
| } |
| |
| /** Processes an interactive message received from the CMake server. */ |
| static void receiveInteractiveMessage( |
| @NonNull PrintWriter writer, |
| @NonNull ILogger logger, |
| @NonNull InteractiveMessage message, |
| @NonNull File makeFileDirectory) { |
| writer.println(CMAKE_SERVER_LOG_PREFIX + message.message); |
| logInteractiveMessage(logger, message, makeFileDirectory); |
| } |
| |
| /** |
| * Logs info/warning/error for the given interactive message. Throws a RunTimeException in case |
| * of an 'error' message type. |
| */ |
| @VisibleForTesting |
| static void logInteractiveMessage( |
| @NonNull ILogger logger, |
| @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")) { |
| logger.error(null, correctMakefilePaths(message.errorMessage, makeFileDirectory)); |
| return; |
| } |
| |
| String correctedMessage = correctMakefilePaths(message.message, makeFileDirectory); |
| |
| if ((message.title != null && message.title.equals("Error")) |
| || message.message.startsWith(CMAKE_ERROR_PREFIX)) { |
| logger.error(null, correctedMessage); |
| return; |
| } |
| |
| if ((message.title != null && message.title.equals("Warning")) |
| || message.message.startsWith(CMAKE_WARNING_PREFIX)) { |
| logger.warning(correctedMessage); |
| return; |
| } |
| |
| logger.info(correctedMessage); |
| } |
| |
| /** Processes an diagnostic message received by/from the CMake server. */ |
| static void receiveDiagnosticMessage( |
| @NonNull PrintWriter writer, @NonNull ILogger logger, @NonNull String message) { |
| writer.println(CMAKE_SERVER_LOG_PREFIX + message); |
| logger.info(message); |
| } |
| |
| /** |
| * 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 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( |
| 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 File sourceDirectory, |
| @NonNull File buildDirectory, |
| @NonNull ProtocolVersion cmakeServerProtocolVersion) { |
| HandshakeRequest handshakeRequest = new HandshakeRequest(); |
| handshakeRequest.cookie = "gradle-cmake-cookie"; |
| handshakeRequest.generator = getGenerator(getBuildArguments()); |
| 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(); |
| } |
| |
| /** |
| * Gets the generator set explicitly by the user (overriding our default). |
| * |
| * @param buildArguments - build arguments |
| */ |
| @NonNull |
| private static String getGenerator(@NonNull List<String> buildArguments) { |
| String generatorArgument = "-G "; |
| for (String argument : buildArguments) { |
| if (!argument.startsWith(generatorArgument)) { |
| continue; |
| } |
| |
| int startIndex = argument.indexOf(generatorArgument) + generatorArgument.length(); |
| return argument.substring(startIndex, argument.length()); |
| } |
| // Return the default generator, i.e., "Ninja" |
| return "Ninja"; |
| } |
| |
| /** |
| * 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 JsonGenerationAbiConfiguration config, @NonNull Server cmakeServer) |
| throws IOException { |
| NativeBuildConfigValue nativeBuildConfigValue = |
| getNativeBuildConfigValue(config, cmakeServer); |
| AndroidBuildGradleJsons.writeNativeBuildConfigValueToJsonFile( |
| config.getJsonFile(), 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 JsonGenerationAbiConfiguration abiConfig, @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(abiConfig, cmakeServer)); |
| |
| // Clean commands |
| assert nativeBuildConfigValue.cleanCommands != null; |
| nativeBuildConfigValue.cleanCommands.add( |
| CmakeUtils.getCleanCommand( |
| getCmakeExecutable(), abiConfig.getExternalNativeBuildFolder())); |
| |
| 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( |
| abiConfig.getAbiName(), |
| 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( |
| abiConfig.getAbiName(), |
| project.buildDirectory, |
| target, |
| strings); |
| nativeLibraryValue.toolchain = toolchainHashString; |
| String libraryName = |
| target.name + "-" + config.name + "-" + abiConfig.getAbiName(); |
| assert nativeBuildConfigValue.libraries != null; |
| nativeBuildConfigValue.libraries.put(libraryName, nativeLibraryValue); |
| } // target |
| } // project |
| } |
| return nativeBuildConfigValue; |
| } |
| |
| @VisibleForTesting |
| protected NativeLibraryValue getNativeLibraryValue( |
| @NonNull String abi, |
| @NonNull String workingDirectory, |
| @NonNull Target target, |
| StringTable strings) |
| throws FileNotFoundException { |
| return getNativeLibraryValue( |
| getCmakeExecutable(), |
| getOutputFolder(getJsonFolder(), abi), |
| isDebuggable(), |
| new JsonReader(new FileReader(getCompileCommandsJson(abi))), |
| abi, |
| workingDirectory, |
| target, |
| strings); |
| } |
| |
| @VisibleForTesting |
| static NativeLibraryValue getNativeLibraryValue( |
| @NonNull File cmakeExecutable, |
| @NonNull File outputFolder, |
| boolean isDebuggable, |
| @NonNull JsonReader compileCommandsJson, |
| @NonNull String abi, |
| @NonNull String 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<>(); |
| Map<String, Integer> compilationDatabaseFlags = Maps.newHashMap(); |
| |
| int workingDirectoryOrdinal = strings.intern(workingDirectory); |
| for (FileGroup fileGroup : target.fileGroups) { |
| for (String source : fileGroup.sources) { |
| File sourceFile = new File(target.sourceDirectory, source); |
| 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(sourceFile.getPath())) { |
| nativeSourceFileValue.flagsOrdinal = |
| compilationDatabaseFlags.get(sourceFile.getPath()); |
| } 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); |
| } |
| |
| /** |
| * 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 JsonGenerationAbiConfiguration 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.getGradleBuildOutputFolder().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 Map<String, NativeToolchainValue> getNativeToolchains( |
| @NonNull String abi, |
| @NonNull Server cmakeServer, |
| @NonNull Collection<String> cppExtensionSet, |
| @NonNull Collection<String> cExtensionSet) { |
| NativeToolchainValue toolchainValue = new NativeToolchainValue(); |
| File cCompilerExecutable = null; |
| File cppCompilerExecutable = null; |
| |
| File compilationDatabase = getCompileCommandsJson(abi); |
| 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; |
| } |
| |
| /** Helper function that returns the flags used to compile a given file. */ |
| @VisibleForTesting |
| static String getAndroidGradleFileLibFlags( |
| @NonNull String fileName, @NonNull List<CompileCommand> compileCommands) { |
| String flags = null; |
| |
| // Get the path of the given file name so we can compare it with the file specified within |
| // CompileCommand. |
| Path fileNamePath = Paths.get(fileName); |
| |
| // Search for the CompileCommand for the given file and parse the flags used to compile the |
| // file. |
| for (CompileCommand compileCommand : compileCommands) { |
| if (compileCommand.command == null || compileCommand.file == null) { |
| continue; |
| } |
| |
| if (fileNamePath.compareTo(Paths.get(compileCommand.file)) != 0) { |
| continue; |
| } |
| |
| flags = |
| compileCommand.command.substring( |
| compileCommand.command.indexOf(' ') + 1, |
| compileCommand.command.indexOf(fileName)); |
| break; |
| } |
| return flags; |
| } |
| |
| /** Returns the toolchain file to be used. */ |
| @NonNull |
| private File getToolchainFile(@NonNull String abi) { |
| // NDK versions r15 and above have the fix in android.toolchain.cmake to work with CMake |
| // version 3.7+, but if the user has NDK r14 or below, we add the (hacky) fix |
| // programmatically. |
| if (config.ndkVersion.getMajor() >= 15) { |
| // Add our toolchain file. |
| // Note: When setting this flag, Cmake's android toolchain would end up calling our |
| // toolchain via ndk-cmake-hooks, but our toolchains will (ideally) be executed only |
| // once. |
| return getToolChainFile(); |
| } |
| return getPreNDKr15WrapperToolchainFile(getOutputFolder(getJsonFolder(), abi)); |
| } |
| |
| /** |
| * Returns a pre-ndk-r15-wrapper android toolchain cmake file for NDK r14 and below that has a |
| * fix to work with CMake versions 3.7+. Note: This is a hacky solution, ideally, the user |
| * should install NDK r15+ so it works with CMake 3.7+. |
| */ |
| @NonNull |
| private File getPreNDKr15WrapperToolchainFile(@NonNull File outputFolder) { |
| StringBuilder tempAndroidToolchain = |
| new StringBuilder( |
| "# This toolchain file was generated by Gradle to support NDK versions r14 and below.\n"); |
| |
| // Include the original android toolchain |
| tempAndroidToolchain |
| .append(String.format("include(%s)", normalizeFilePath(getToolChainFile()))) |
| .append(System.lineSeparator()); |
| // Overwrite the CMAKE_SYSTEM_VERSION to 1 so we skip CMake's Android toolchain. |
| tempAndroidToolchain.append("set(CMAKE_SYSTEM_VERSION 1)").append(System.lineSeparator()); |
| |
| File toolchainFile = getTempToolchainFile(outputFolder); |
| try { |
| FileUtils.writeStringToFile(toolchainFile, tempAndroidToolchain.toString()); |
| } catch (IOException e) { |
| throw new RuntimeException( |
| String.format( |
| "Unable to write to file: %s." |
| + "Please upgrade NDK to version 15 or above.", |
| toolchainFile.getAbsolutePath())); |
| } |
| |
| return toolchainFile; |
| } |
| |
| /** |
| * Returns a pre-ndk-r15-wrapper cmake toolchain file within the object folder for the project. |
| */ |
| @NonNull |
| private static File getTempToolchainFile(@NonNull File outputFolder) { |
| String tempAndroidToolchainFile = "pre-ndk-r15-wrapper-android.toolchain.cmake"; |
| return new File(outputFolder, tempAndroidToolchainFile); |
| } |
| |
| /** |
| * 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(); |
| } |
| } |