blob: 6ca5fc5e587277e3e111da4e76e64a29818d1451 [file]
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.plugins.gradle.nativeplatform.tooling.builder;
import org.gradle.api.Project;
import org.gradle.api.component.SoftwareComponent;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.internal.project.DefaultProject;
import org.gradle.api.plugins.PluginContainer;
import org.gradle.api.provider.Provider;
import org.gradle.internal.impldep.org.apache.commons.lang.StringUtils;
import org.gradle.internal.os.OperatingSystem;
import org.gradle.language.cpp.*;
import org.gradle.language.cpp.plugins.CppBasePlugin;
import org.gradle.language.cpp.plugins.CppPlugin;
import org.gradle.language.cpp.tasks.CppCompile;
import org.gradle.language.nativeplatform.ComponentWithExecutable;
import org.gradle.language.nativeplatform.internal.ConfigurableComponentWithExecutable;
import org.gradle.nativeplatform.internal.CompilerOutputFileNamingScheme;
import org.gradle.nativeplatform.internal.CompilerOutputFileNamingSchemeFactory;
import org.gradle.nativeplatform.tasks.LinkExecutable;
import org.gradle.nativeplatform.test.cpp.CppTestSuite;
import org.gradle.nativeplatform.toolchain.Clang;
import org.gradle.nativeplatform.toolchain.Gcc;
import org.gradle.nativeplatform.toolchain.NativeToolChain;
import org.gradle.nativeplatform.toolchain.VisualCpp;
import org.gradle.nativeplatform.toolchain.internal.NativeLanguageTools;
import org.gradle.nativeplatform.toolchain.internal.PlatformToolProvider;
import org.gradle.nativeplatform.toolchain.internal.ToolType;
import org.gradle.nativeplatform.toolchain.internal.tools.CommandLineToolSearchResult;
import org.gradle.nativeplatform.toolchain.internal.tools.ToolSearchPath;
import org.gradle.platform.base.internal.toolchain.ToolSearchResult;
import org.gradle.util.GradleVersion;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.gradle.model.DefaultExternalTask;
import org.jetbrains.plugins.gradle.nativeplatform.tooling.model.CppProject;
import org.jetbrains.plugins.gradle.nativeplatform.tooling.model.SourceFile;
import org.jetbrains.plugins.gradle.nativeplatform.tooling.model.impl.*;
import org.jetbrains.plugins.gradle.tooling.ErrorMessageBuilder;
import org.jetbrains.plugins.gradle.tooling.ModelBuilderService;
import org.jetbrains.plugins.gradle.tooling.util.ReflectionUtil;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
/**
* The prototype of the C++ project gradle tooling model builder.
* This implementation should be moved or replaced with the similar model builder from the Gradle distribution.
*
* @author Vladislav.Soroka
* @deprecated to be removed in 2019.1, use built-in 'org.gradle.tooling.model.cpp.CppComponent' available since Gradle 4.10
*/
@Deprecated
public class CppModelBuilder implements ModelBuilderService {
private static final boolean IS_51_OR_BETTER = GradleVersion.current().getBaseVersion().compareTo(GradleVersion.version("5.1")) >= 0;
private static final boolean IS_410_OR_BETTER = IS_51_OR_BETTER ||
GradleVersion.current().getBaseVersion().compareTo(GradleVersion.version("4.10")) >= 0;
private static final boolean IS_48_OR_BETTER = IS_410_OR_BETTER ||
GradleVersion.current().getBaseVersion().compareTo(GradleVersion.version("4.8")) >= 0;
private static final boolean IS_47_OR_BETTER = IS_48_OR_BETTER ||
GradleVersion.current().getBaseVersion().compareTo(GradleVersion.version("4.7")) >= 0;
private static final boolean IS_41_OR_BETTER = IS_47_OR_BETTER ||
GradleVersion.current().getBaseVersion().compareTo(GradleVersion.version("4.1")) >= 0;
@Override
public boolean canBuild(String modelName) {
return CppProject.class.getName().equals(modelName);
}
@Nullable
@Override
public Object buildAll(final String modelName, final Project project) {
if (!IS_41_OR_BETTER || IS_410_OR_BETTER) {
return null;
}
PluginContainer pluginContainer = project.getPlugins();
if (!pluginContainer.hasPlugin(CppBasePlugin.class)) {
if (pluginContainer.hasPlugin(CppPlugin.class)) {
project.getLogger().error(
"[sync warning] The IDE doesn't support 'cpp' gradle plugin. " +
"Consider to use new gradle C++ plugins, see details at https://blog.gradle.org/introducing-the-new-cpp-plugins");
}
return null;
}
final CppProjectImpl cppProject = new CppProjectImpl();
for (SoftwareComponent component : project.getComponents()) {
if (component instanceof CppComponent) {
CppComponent cppComponent = (CppComponent)component;
String cppComponentName = cppComponent.getName();
String cppComponentBaseName = cppComponent.getBaseName().get();
Set<File> headerDirs = new LinkedHashSet<File>(cppComponent.getPrivateHeaderDirs().getFiles());
CppComponentImpl ideCppComponent;
if (cppComponent instanceof ProductionCppComponent) {
ideCppComponent = new CppComponentImpl(cppComponentName, cppComponentBaseName);
cppProject.setMainComponent(ideCppComponent);
}
else if (cppComponent instanceof CppTestSuite) {
CppTestSuiteImpl cppTestSuite = new CppTestSuiteImpl(cppComponentName, cppComponentBaseName);
ideCppComponent = cppTestSuite;
cppProject.setTestComponent(cppTestSuite);
}
else {
continue;
}
for (CppBinary cppBinary : cppComponent.getBinaries().get()) {
String binaryName = cppBinary.getName();
String baseName = cppBinary.getBaseName().getOrElse("");
String variantName = StringUtils.uncapitalize(StringUtils.removeStart(binaryName, "main"));
boolean isExecutable = cppBinary instanceof ComponentWithExecutable;
CppBinaryImpl binary;
if (cppBinary instanceof CppSharedLibrary) {
binary = new CppSharedLibraryImpl(binaryName, baseName, variantName);
}
else if (cppBinary instanceof CppStaticLibrary) {
binary = new CppStaticLibraryImpl(binaryName, baseName, variantName);
}
else if (isExecutable) {
binary = new CppExecutableImpl(binaryName, baseName, variantName);
}
else {
continue;
}
ideCppComponent.addBinary(binary);
Provider<CppCompile> cppCompileProvider = cppBinary.getCompileTask();
if (cppCompileProvider.isPresent()) {
CppCompile cppCompile = cppCompileProvider.get();
CompilationDetailsImpl compilationDetails = new CompilationDetailsImpl();
binary.setCompilationDetails(compilationDetails);
String compileTaskName = cppCompile.getPath();
DefaultExternalTask compileTask = new DefaultExternalTask();
compileTask.setName(compileTaskName);
compileTask.setQName(compileTaskName);
compilationDetails.setCompileTask(compileTask);
Set<File> compileIncludePath = new LinkedHashSet<File>(cppBinary.getCompileIncludePath().getFiles());
Set<File> systemIncludes = new LinkedHashSet<File>();
//Since Gradle 4.8, system header include directories should be accessed separately via the systemIncludes property
//see https://github.com/gradle/gradle-native/blob/master/docs/RELEASE-NOTES.md#better-control-over-system-include-path-for-native-compilation---583
if (IS_48_OR_BETTER) {
compileIncludePath.addAll(cppCompile.getIncludes().getFiles());
systemIncludes.addAll(cppCompile.getSystemIncludes().getFiles());
}
else {
systemIncludes.addAll(cppCompile.getIncludes().getFiles());
}
compilationDetails.setSystemHeaderSearchPaths(new ArrayList<File>(systemIncludes));
compilationDetails.setUserHeaderSearchPaths(new ArrayList<File>(compileIncludePath));
compilationDetails.setHeaderDirs(headerDirs);
Set<SourceFile> sources = getSources(project, cppBinary, cppCompile);
compilationDetails.setSources(sources);
File compilerWorkingDir = cppCompile.getObjectFileDir().get().getAsFile();
compilationDetails.setCompileWorkingDir(compilerWorkingDir);
File cppCompilerExecutable = findCppCompilerExecutable(project, cppBinary);
compilationDetails.setCompilerExecutable(cppCompilerExecutable);
List<String> compilerArgs = new ArrayList<String>(cppCompile.getCompilerArgs().getOrElse(Collections.<String>emptyList()));
compilationDetails.setAdditionalArgs(compilerArgs);
}
LinkageDetailsImpl linkageDetails = new LinkageDetailsImpl();
binary.setLinkageDetails(linkageDetails);
DefaultExternalTask linkTask = new DefaultExternalTask();
linkageDetails.setLinkTask(linkTask);
linkTask.setName("");
linkTask.setQName("");
if (isExecutable) {
Provider<? extends LinkExecutable> fileProvider = ((ComponentWithExecutable)cppBinary).getLinkTask();
if (fileProvider.isPresent()) {
LinkExecutable linkExecutable = fileProvider.get();
String linkTaskName = linkExecutable.getPath();
linkTask.setName(linkTaskName);
linkTask.setQName(linkTaskName);
File executableFile = getExecutableFile(linkExecutable);
linkageDetails.setOutputLocation(executableFile);
}
}
}
}
}
return cppProject;
}
private static Set<SourceFile> getSources(Project project, CppBinary cppBinary, CppCompile cppCompile) {
Set<SourceFile> sources = new LinkedHashSet<SourceFile>();
for (File file : cppBinary.getCppSource().getFiles()) {
sources.add(new SourceFileImpl(file, null));
}
if (cppCompile.getObjectFileDir().isPresent()) {
File objectFileDir = cppCompile.getObjectFileDir().get().getAsFile();
Iterator<CompilerOutputFileNamingSchemeFactory> it =
((DefaultProject)project).getServices().getAll(CompilerOutputFileNamingSchemeFactory.class).iterator();
if (it.hasNext()) {
CompilerOutputFileNamingSchemeFactory outputFileNamingSchemeFactory = it.next();
boolean isTargetWindows = IS_51_OR_BETTER
? cppBinary.getTargetPlatform().getTargetMachine().getOperatingSystemFamily().isWindows()
: isWindowsOld(cppBinary.getTargetPlatform());
String objectFileExtension = isTargetWindows ? ".obj" : ".o";
CompilerOutputFileNamingScheme outputFileNamingScheme = outputFileNamingSchemeFactory.create();
outputFileNamingScheme.withOutputBaseFolder(objectFileDir).withObjectFileNameSuffix(objectFileExtension);
for (SourceFile sourceFile : sources) {
File objectFile = outputFileNamingScheme.map(sourceFile.getSourceFile());
((SourceFileImpl)sourceFile).setObjectFile(objectFile);
}
}
}
return sources;
}
private static boolean isWindowsOld(CppPlatform platform) {
Object operatingSystem = ReflectionUtil.callByReflection(platform, "getOperatingSystem");
if (operatingSystem == null) {
return false;
}
Object isWindowsNullable = ReflectionUtil.callByReflection(operatingSystem, "isWindows");
return Boolean.TRUE.equals(isWindowsNullable);
}
@Nullable
private static File getExecutableFile(LinkExecutable linkExecutable) {
File executableFile;
RegularFileProperty binaryFile = null;
if (IS_47_OR_BETTER) {
binaryFile = linkExecutable.getLinkedFile();
}
else {
try {
Object linkedFile = linkExecutable.getClass().getMethod("getBinaryFile").invoke(linkExecutable);
if (linkedFile instanceof RegularFileProperty) {
binaryFile = (RegularFileProperty)linkedFile;
}
}
catch (Exception e) {
//noinspection CallToPrintStackTrace
e.printStackTrace();
}
}
executableFile = binaryFile != null ? binaryFile.getAsFile().getOrNull() : null;
return executableFile;
}
@Nullable
private static File findCppCompilerExecutable(Project project, CppBinary cppBinary) {
Throwable throwable = null;
try {
if (cppBinary instanceof ConfigurableComponentWithExecutable) {
PlatformToolProvider toolProvider = ((ConfigurableComponentWithExecutable)cppBinary).getPlatformToolProvider();
ToolSearchResult toolSearchResult;
if (IS_410_OR_BETTER) {
toolSearchResult = toolProvider.locateTool(ToolType.CPP_COMPILER);
}
else {
Method isToolAvailableMethod = toolProvider.getClass().getDeclaredMethod("isToolAvailable", ToolType.class);
isToolAvailableMethod.setAccessible(true);
toolSearchResult = (ToolSearchResult)isToolAvailableMethod.invoke(toolProvider, ToolType.CPP_COMPILER);
}
if (toolSearchResult.isAvailable()) {
if (toolSearchResult instanceof CommandLineToolSearchResult) {
return ((CommandLineToolSearchResult)toolSearchResult).getTool();
}
// dirty hack for gradle versions <= 4.9 because of dummy implementation of org.gradle.nativeplatform.toolchain.internal.msvcpp.VisualCppPlatformToolProvider.isToolAvailable
if (toolProvider.getClass().getSimpleName().equals("VisualCppPlatformToolProvider")) {
Field visualCppField = toolProvider.getClass().getDeclaredField("visualCpp");
visualCppField.setAccessible(true);
Object visualCpp = visualCppField.get(toolProvider);
if (visualCpp instanceof NativeLanguageTools) {
return ((NativeLanguageTools)visualCpp).getCompilerExecutable();
}
}
}
}
}
catch (Throwable t) {
throwable = t;
}
NativeToolChain toolChain = cppBinary.getToolChain();
String exeName;
if (toolChain instanceof Gcc) {
exeName = "g++";
}
else if (toolChain instanceof Clang) {
exeName = "clang++";
}
else if (toolChain instanceof VisualCpp) {
exeName = "cl";
}
else {
exeName = null;
}
if (exeName != null) {
ToolSearchPath toolSearchPath = new ToolSearchPath(OperatingSystem.current());
CommandLineToolSearchResult searchResult = toolSearchPath.locate(ToolType.CPP_COMPILER, exeName);
if (searchResult.isAvailable()) {
return searchResult.getTool();
}
}
if (!IS_47_OR_BETTER) {
project.getLogger().error(
"[sync error] Unable to resolve compiler executable. " +
"The project uses '" + GradleVersion.current() + "' try to update the gradle version");
}
else {
project.getLogger().error("[sync error] Unable to resolve compiler executable", throwable);
}
return null;
}
@NotNull
@Override
public ErrorMessageBuilder getErrorMessageBuilder(@NotNull Project project, @NotNull Exception e) {
return ErrorMessageBuilder.create(
project, e, "C++ project import errors"
).withDescription("Unable to import C++ project");
}
}