1
0
Fork 0
mirror of https://github.com/eclipse-cdt/cdt synced 2025-06-05 08:46:02 +02:00

Bug 567488: Use command-line options to pass to cmake ICMakeProperties object

Change-Id: I4d6383ce240e6f2b8d7079f281ef7c2e56ea93a6
Signed-off-by: Martin Weber <fifteenknots505@gmail.com>
This commit is contained in:
Martin Weber 2020-10-28 21:36:37 +01:00
parent 3a10f42019
commit ebf2d24c95
5 changed files with 311 additions and 109 deletions

View file

@ -20,3 +20,4 @@ Export-Package: org.eclipse.cdt.cmake.core,
org.eclipse.cdt.cmake.core.properties
Automatic-Module-Name: org.eclipse.cdt.cmake.core
Bundle-Localization: plugin
Import-Package: org.eclipse.core.variables

View file

@ -18,7 +18,6 @@ import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -27,6 +26,10 @@ import java.util.function.Consumer;
import org.eclipse.cdt.cmake.core.ICMakeToolChainFile;
import org.eclipse.cdt.cmake.core.ICMakeToolChainManager;
import org.eclipse.cdt.cmake.core.properties.CMakeGenerator;
import org.eclipse.cdt.cmake.core.properties.ICMakeProperties;
import org.eclipse.cdt.cmake.core.properties.ICMakePropertiesController;
import org.eclipse.cdt.cmake.core.properties.IOsOverrides;
import org.eclipse.cdt.cmake.is.core.CompileCommandsJsonParser;
import org.eclipse.cdt.cmake.is.core.IIndexerInfoConsumer;
import org.eclipse.cdt.cmake.is.core.ParseRequest;
@ -56,6 +59,8 @@ import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.variables.IStringVariableManager;
import org.eclipse.core.variables.VariablesPlugin;
public class CMakeBuildConfiguration extends CBuildConfiguration {
@ -66,6 +71,11 @@ public class CMakeBuildConfiguration extends CBuildConfiguration {
public static final String CLEAN_COMMAND = "cmake.command.clean"; //$NON-NLS-1$
private ICMakeToolChainFile toolChainFile;
private final CMakePropertiesController pc = new CMakePropertiesController(() -> {
deleteCMakeCache = true;
});
private Map<IResource, IScannerInfo> infoPerResource;
/** whether one of the CMakeLists.txt files in the project has been
* modified and saved by the user since the last build.<br>
@ -76,6 +86,9 @@ public class CMakeBuildConfiguration extends CBuildConfiguration {
* To work around that, we run cmake in advance with its dedicated working error parser.
*/
private boolean cmakeListsModified;
/** whether we have to delete file CMakeCache.txt to avoid complaints by cmake
*/
private boolean deleteCMakeCache;
public CMakeBuildConfiguration(IBuildConfiguration config, String name) throws CoreException {
super(config, name);
@ -94,10 +107,16 @@ public class CMakeBuildConfiguration extends CBuildConfiguration {
this.toolChainFile = toolChainFile;
}
/** Gets the tool-chain description file to pass to the cmake command-line.
*
* @return the tool-chain file or <code>null</code> if cmake should take the native (i.e. the tools first found on
* the executable search path aka $path)
*/
public ICMakeToolChainFile getToolChainFile() {
return toolChainFile;
}
@SuppressWarnings("unused") // kept for reference of the property names
private boolean isLocal() throws CoreException {
IToolChain toolchain = getToolChain();
return (Platform.getOS().equals(toolchain.getProperty(IToolChain.ATTR_OS))
@ -110,81 +129,36 @@ public class CMakeBuildConfiguration extends CBuildConfiguration {
throws CoreException {
IProject project = getProject();
try {
String generator = getProperty(CMAKE_GENERATOR);
if (generator == null) {
generator = "Ninja"; //$NON-NLS-1$
}
project.deleteMarkers(ICModelMarker.C_MODEL_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE);
infoPerResource = new HashMap<>();
project.deleteMarkers(ICModelMarker.C_MODEL_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE);
infoPerResource = new HashMap<>();
try {
ConsoleOutputStream infoStream = console.getInfoStream();
Path buildDir = getBuildDirectory();
infoStream.write(String.format(Messages.CMakeBuildConfiguration_BuildingIn, buildDir.toString()));
// Make sure we have a toolchain file if cross
if (toolChainFile == null && !isLocal()) {
ICMakeToolChainManager manager = Activator.getService(ICMakeToolChainManager.class);
toolChainFile = manager.getToolChainFileFor(getToolChain());
if (toolChainFile == null) {
// error
console.getErrorStream().write(Messages.CMakeBuildConfiguration_NoToolchainFile);
return null;
}
}
boolean runCMake = cmakeListsModified;
if (!runCMake) {
switch (generator) {
case "Ninja": //$NON-NLS-1$
runCMake = !Files.exists(buildDir.resolve("build.ninja")); //$NON-NLS-1$
break;
case "Unix Makefiles": //$NON-NLS-1$
runCMake = !Files.exists(buildDir.resolve("Makefile")); //$NON-NLS-1$
break;
default:
runCMake = !Files.exists(buildDir.resolve("CMakeFiles")); //$NON-NLS-1$
}
if (deleteCMakeCache) {
Files.deleteIfExists(buildDir.resolve("CMakeCache.txt")); //$NON-NLS-1$
deleteCMakeCache = false;
runCMake = true;
}
ICMakeProperties cmakeProperties = pc.load();
runCMake |= !Files.exists(buildDir.resolve("CMakeCache.txt")); //$NON-NLS-1$
IOsOverrides overrides = extractCMakeOsOverrides(cmakeProperties);
CMakeGenerator generator = overrides.getGenerator();
if (!runCMake) {
runCMake |= !Files.exists(buildDir.resolve(generator.getMakefileName()));
}
if (runCMake) {
CMakeBuildConfiguration.deleteCMakeErrorMarkers(project);
infoStream.write(String.format(Messages.CMakeBuildConfiguration_Configuring, buildDir));
// clean output to make sure there is no content
// incompatible with current settings (cmake config would fail)
cleanBuildDirectory(buildDir);
List<String> command = new ArrayList<>();
command.add("cmake"); //$NON-NLS-1$
command.add("-G"); //$NON-NLS-1$
command.add(generator);
if (toolChainFile != null) {
command.add("-DCMAKE_TOOLCHAIN_FILE=" + toolChainFile.getPath().toString()); //$NON-NLS-1$
}
switch (getLaunchMode()) {
// TODO what to do with other modes
case "debug": //$NON-NLS-1$
command.add("-DCMAKE_BUILD_TYPE=Debug"); //$NON-NLS-1$
break;
case "run": //$NON-NLS-1$
command.add("-DCMAKE_BUILD_TYPE=Release"); //$NON-NLS-1$
break;
}
command.add("-DCMAKE_EXPORT_COMPILE_COMMANDS=ON"); //$NON-NLS-1$
String userArgs = getProperty(CMAKE_ARGUMENTS);
if (userArgs != null) {
command.addAll(Arrays.asList(userArgs.trim().split("\\s+"))); //$NON-NLS-1$
}
List<String> command = makeCMakeCommandline(cmakeProperties, overrides);
// tell cmake where its script is located..
IContainer srcFolder = project;
command.add(new File(srcFolder.getLocationURI()).getAbsolutePath());
@ -197,6 +171,9 @@ public class CMakeBuildConfiguration extends CBuildConfiguration {
ParsingConsoleOutputStream errStream = new ParsingConsoleOutputStream(console.getErrorStream(),
errorParser);
IConsole errConsole = new CMakeConsoleWrapper(console, errStream);
// TODO startBuildProcess() calls java.lang.ProcessBuilder.
// Use org.eclipse.cdt.core.ICommandLauncher
// in order to run builds in a container.
Process p = startBuildProcess(command, new IEnvironmentVariable[0], workingDir, errConsole,
monitor);
if (p == null) {
@ -209,11 +186,16 @@ public class CMakeBuildConfiguration extends CBuildConfiguration {
cmakeListsModified = false;
}
// parse compile_commands.json file
processCompileCommandsFile(console, monitor);
infoStream.write(String.format(Messages.CMakeBuildConfiguration_BuildingIn, buildDir.toString()));
// run the build tool...
try (ErrorParserManager epm = new ErrorParserManager(project, getBuildDirectoryURI(), this,
getToolChain().getErrorParserIds())) {
epm.setOutputStream(console.getOutputStream());
List<String> command = new ArrayList<>();
List<String> command = makeCMakeBuildCommandline(cmakeProperties, overrides, "all"); //$NON-NLS-1$
String envStr = getProperty(CMAKE_ENV);
List<IEnvironmentVariable> envVars = new ArrayList<>();
@ -229,23 +211,12 @@ public class CMakeBuildConfiguration extends CBuildConfiguration {
}
}
String buildCommand = getProperty(BUILD_COMMAND);
if (buildCommand == null) {
command.add("cmake"); //$NON-NLS-1$
command.add("--build"); //$NON-NLS-1$
command.add("."); //$NON-NLS-1$
if ("Ninja".equals(generator)) { //$NON-NLS-1$
command.add("--"); //$NON-NLS-1$
command.add("-v"); //$NON-NLS-1$
}
} else {
command.addAll(Arrays.asList(buildCommand.split(" "))); //$NON-NLS-1$
}
infoStream.write(String.join(" ", command) + '\n'); //$NON-NLS-1$
org.eclipse.core.runtime.Path workingDir = new org.eclipse.core.runtime.Path(
getBuildDirectory().toString());
// TODO startBuildProcess() calls java.lang.ProcessBuilder. Use org.eclipse.cdt.core.ICommandLauncher
// in order to run builds in a container.
Process p = startBuildProcess(command, envVars.toArray(new IEnvironmentVariable[0]), workingDir,
console, monitor);
if (p == null) {
@ -257,11 +228,6 @@ public class CMakeBuildConfiguration extends CBuildConfiguration {
project.refreshLocal(IResource.DEPTH_INFINITE, monitor);
// parse compile_commands.json file
// built-ins detection output goes to the build console, if the user requested
// output
processCompileCommandsFile(console, monitor);
infoStream.write(String.format(Messages.CMakeBuildConfiguration_BuildingComplete, epm.getErrorCount(),
epm.getWarningCount(), buildDir.toString()));
}
@ -273,14 +239,34 @@ public class CMakeBuildConfiguration extends CBuildConfiguration {
}
}
/**
* @param cmakeProperties
* @return
*/
private IOsOverrides extractCMakeOsOverrides(ICMakeProperties cmakeProperties) {
IOsOverrides overrides;
// get overrides. Simplistic approach ATM, probably a strategy might fit better.
// see comment in CMakeIndexerInfoConsumer#getFileForCMakePath()
final String os = Platform.getOS();
if (Platform.OS_WIN32.equals(os)) {
overrides = cmakeProperties.getWindowsOverrides();
} else {
// fall back to linux, if OS is unknown
overrides = cmakeProperties.getLinuxOverrides();
}
return overrides;
}
@Override
public void clean(IConsole console, IProgressMonitor monitor) throws CoreException {
IProject project = getProject();
try {
String generator = getProperty(CMAKE_GENERATOR);
project.deleteMarkers(ICModelMarker.C_MODEL_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE);
ICMakeProperties cmakeProperties = pc.load();
IOsOverrides overrides = extractCMakeOsOverrides(cmakeProperties);
List<String> command = makeCMakeBuildCommandline(cmakeProperties, overrides, "clean"); //$NON-NLS-1$
ConsoleOutputStream outStream = console.getOutputStream();
Path buildDir = getBuildDirectory();
@ -290,27 +276,13 @@ public class CMakeBuildConfiguration extends CBuildConfiguration {
return;
}
List<String> command = new ArrayList<>();
String cleanCommand = getProperty(CLEAN_COMMAND);
if (cleanCommand == null) {
if (generator == null || generator.equals("Ninja")) { //$NON-NLS-1$
command.add("ninja"); //$NON-NLS-1$
command.add("clean"); //$NON-NLS-1$
} else {
command.add("make"); //$NON-NLS-1$
command.add("clean"); //$NON-NLS-1$
}
} else {
command.addAll(Arrays.asList(cleanCommand.split(" "))); //$NON-NLS-1$
}
IEnvironmentVariable[] env = new IEnvironmentVariable[0];
outStream.write(String.join(" ", command) + '\n'); //$NON-NLS-1$
org.eclipse.core.runtime.Path workingDir = new org.eclipse.core.runtime.Path(
getBuildDirectory().toString());
Process p = startBuildProcess(command, env, workingDir, console, monitor);
// TODO startBuildProcess() calls java.lang.ProcessBuilder. Use org.eclipse.cdt.core.ICommandLauncher
// in order to run builds in a container.
Process p = startBuildProcess(command, new IEnvironmentVariable[0], workingDir, console, monitor);
if (p == null) {
console.getErrorStream().write(String.format(Messages.CMakeBuildConfiguration_Failure, "")); //$NON-NLS-1$
return;
@ -327,6 +299,130 @@ public class CMakeBuildConfiguration extends CBuildConfiguration {
}
}
/**
* Build the command-line for cmake. The first argument will be the
* cmake-command.
*
* @throws CoreException
*/
private List<String> makeCMakeCommandline(ICMakeProperties cmakeProps, IOsOverrides osOverrideProps)
throws CoreException {
List<String> args = new ArrayList<>();
// default for all OSes (maybe replaced later)
args.add("cmake"); //$NON-NLS-1$
/* add general settings */
if (cmakeProps.isWarnNoDev())
args.add("-Wno-dev"); //$NON-NLS-1$
if (cmakeProps.isDebugTryCompile())
args.add("--debug-trycompile"); //$NON-NLS-1$
if (cmakeProps.isDebugOutput())
args.add("--debug-output"); //$NON-NLS-1$
if (cmakeProps.isTrace())
args.add("--trace"); //$NON-NLS-1$
if (cmakeProps.isWarnUnitialized())
args.add("--warn-unitialized"); //$NON-NLS-1$
if (cmakeProps.isWarnUnused())
args.add("--warn-unused"); //$NON-NLS-1$
{
String file = cmakeProps.getCacheFile();
if (!(file == null || file.isBlank())) {
args.add("-C"); //$NON-NLS-1$
args.add(file);
}
}
if (toolChainFile != null) {
args.add("-DCMAKE_TOOLCHAIN_FILE=" + toolChainFile.getPath().toString()); //$NON-NLS-1$
}
appendCMakeArguments(args, cmakeProps.getExtraArguments());
/* add settings for the operating system we are running under */
appendCMakeOsOverrideArgs(args, osOverrideProps);
/* add our requirements */
{
// set argument for build type..
String bt = cmakeProps.getBuildType();
if (!(bt == null || bt.isBlank())) {
args.add("-DCMAKE_BUILD_TYPE=" + bt); //$NON-NLS-1$
}
// tell cmake to write compile commands to a JSON file
args.add("-DCMAKE_EXPORT_COMPILE_COMMANDS=ON"); //$NON-NLS-1$
}
return args;
}
/**
* Build the command-line for cmake to build the project. The first argument will be the
* cmake-command.
*
* @throws CoreException
*/
private List<String> makeCMakeBuildCommandline(ICMakeProperties cmakeProps, IOsOverrides osOverrides,
String buildscriptTarget) throws CoreException {
List<String> args = new ArrayList<>();
if (osOverrides.getUseDefaultCommand()) {
args.add("cmake"); //$NON-NLS-1$
} else {
IStringVariableManager varManager = VariablesPlugin.getDefault().getStringVariableManager();
String cmd = varManager.performStringSubstitution(osOverrides.getCommand());
args.add(cmd);
}
args.add("--build"); //$NON-NLS-1$
args.add("."); //$NON-NLS-1$
args.add("--target"); //$NON-NLS-1$
args.add(buildscriptTarget);
// TODO parallel build: use CMAKE_BUILD_PARALLEL_LEVEL envvar (since cmake 3.12)
// TODO verbose build: use VERBOSE envvar (since cmake 3.14)
// TODO stop on first error: query CMakeGenerator object for argument
return args;
}
/**
* Appends the additional arguments to pass on the cmake command-line. Performs variable substitutions.
*
* @param argList
* the list to append cmake-arguments to
* @param moreArgs
* the arguments to substitute and append
* @throws CoreException
* if unable to resolve the value of one or more variables
*/
private void appendCMakeArguments(List<String> argList, final List<String> moreArgs) throws CoreException {
IStringVariableManager mgr = VariablesPlugin.getDefault().getStringVariableManager();
for (String arg : moreArgs) {
String expanded = mgr.performStringSubstitution(arg);
argList.add(expanded);
}
}
/**
* Appends arguments specific to the given OS preferences for build-script generation.
* The first argument in the list will be replaced by the cmake command from the specified preferences,
* if given.
*
* @param args
* the list to append cmake-arguments to.
* @param prefs
* the generic OS specific cmake build properties to convert and append.
* @throws CoreException
* if unable to resolve the value of one or more variables
*/
private void appendCMakeOsOverrideArgs(List<String> args, final IOsOverrides prefs) throws CoreException {
// replace cmake command, if given
if (!prefs.getUseDefaultCommand()) {
IStringVariableManager varManager = VariablesPlugin.getDefault().getStringVariableManager();
String cmd = varManager.performStringSubstitution(prefs.getCommand());
args.set(0, cmd);
}
args.add("-G"); //$NON-NLS-1$
final CMakeGenerator generator = prefs.getGenerator();
args.add(generator.getCMakeName());
appendCMakeArguments(args, prefs.getExtraArguments());
}
/**
* @param console the console to print the compiler output during built-ins
* detection to or <code>null</code> if no separate console is to
@ -365,12 +461,17 @@ public class CMakeBuildConfiguration extends CBuildConfiguration {
}
}
private void cleanBuildDirectory(Path buildDir) throws IOException {
if (!Files.exists(buildDir))
return;
if (Files.isDirectory(buildDir))
cleanDirectory(buildDir);
// TODO: not a directory should we do something?
// interface IAdaptable
@Override
@SuppressWarnings("unchecked")
public <T> T getAdapter(Class<T> adapter) {
T adapter0 = super.getAdapter(adapter);
if (adapter0 == null) {
if (ICMakePropertiesController.class.equals(adapter)) {
adapter0 = (T) pc;
}
}
return adapter0;
}
/**
@ -426,7 +527,8 @@ public class CMakeBuildConfiguration extends CBuildConfiguration {
IResource resource = resourceDelta.getResource();
if (resource.getType() == IResource.FILE) {
String name = resource.getName();
if (name.equals("CMakeLists.txt") || name.endsWith(".cmake")) { //$NON-NLS-1$ //$NON-NLS-2$
if (!resource.isDerived(IResource.CHECK_ANCESTORS)
&& (name.equals("CMakeLists.txt") || name.endsWith(".cmake"))) { //$NON-NLS-1$ //$NON-NLS-2$
cmakeListsModified = true;
return false; // stop processing
}

View file

@ -0,0 +1,101 @@
/*******************************************************************************
* Copyright (c) 2020 Martin Weber.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.cdt.cmake.core.internal;
import java.util.List;
import java.util.Objects;
import java.util.function.BinaryOperator;
import java.util.function.Predicate;
import org.eclipse.cdt.cmake.core.internal.properties.CMakePropertiesBean;
import org.eclipse.cdt.cmake.core.properties.CMakeGenerator;
import org.eclipse.cdt.cmake.core.properties.ICMakeProperties;
import org.eclipse.cdt.cmake.core.properties.ICMakePropertiesController;
/**
* A {@code ICMakePropertiesController} that monitors modifications to the project properties that force
* us to delete file CMakeCache.txt to avoid complaints by cmake.
* @author Martin Weber
*/
class CMakePropertiesController implements ICMakePropertiesController {
private final Runnable cmakeCacheDirtyMarker;
private String cacheFile;
private List<String> extraArguments;
private CMakeGenerator generatorLinux;
private List<String> extraArgumentsLinux;
private CMakeGenerator generatorWindows;
private List<String> extraArgumentsWindows;
private String buildType;
/** Creates a new CMakePropertiesController object.
*
* @param cmakeCacheDirtyMarker
* the object to notify when modifications to the project properties force
* us to delete file CMakeCache.txt to avoid complaints by cmake
*/
CMakePropertiesController(Runnable cmakeCacheDirtyMarker) {
this.cmakeCacheDirtyMarker = Objects.requireNonNull(cmakeCacheDirtyMarker);
}
@Override
public ICMakeProperties load() {
// TODO implement load()
CMakePropertiesBean props = new CMakePropertiesBean();
setupModifyDetection(props);
return props;
}
@Override
public void save(ICMakeProperties properties) {
// detect whether changes force us to delete file CMakeCache.txt to avoid complaints by cmake
if (!Objects.equals(buildType, properties.getBuildType())
|| !Objects.equals(cacheFile, properties.getCacheFile())
|| !Objects.equals(generatorLinux, properties.getLinuxOverrides().getGenerator())
|| !Objects.equals(generatorWindows, properties.getWindowsOverrides().getGenerator())) {
cmakeCacheDirtyMarker.run(); // must remove cmake cachefile
} else if (extraArgumentsChange(extraArguments, properties.getExtraArguments())
|| extraArgumentsChange(extraArgumentsLinux, properties.getLinuxOverrides().getExtraArguments())
|| extraArgumentsChange(extraArgumentsWindows, properties.getWindowsOverrides().getExtraArguments())) {
cmakeCacheDirtyMarker.run(); // must remove cmake cachefile
}
// TODO implement save()
setupModifyDetection(properties);
}
/** Sets up detection of modifications that force us to delete file CMakeCache.txt to avoid complaints by cmake
*/
private void setupModifyDetection(ICMakeProperties properties) {
buildType = properties.getBuildType();
cacheFile = properties.getCacheFile();
extraArguments = properties.getExtraArguments();
generatorLinux = properties.getLinuxOverrides().getGenerator();
extraArgumentsLinux = properties.getLinuxOverrides().getExtraArguments();
generatorWindows = properties.getWindowsOverrides().getGenerator();
extraArgumentsWindows = properties.getWindowsOverrides().getExtraArguments();
}
private boolean extraArgumentsChange(List<String> args1, List<String> args2) {
String wanted = "CMAKE_TOOLCHAIN_FILE"; //$NON-NLS-1$
// extract the last arguments that contain String wanted..
Predicate<? super String> predContains = a -> a.contains(wanted);
BinaryOperator<String> keepLast = (first, second) -> second;
String a1 = args1.stream().filter(predContains).reduce(keepLast).orElse(null);
String a2 = args2.stream().filter(predContains).reduce(keepLast).orElse(null);
if (!Objects.equals(a1, a2)) {
return true;
}
return false;
}
}

View file

@ -21,7 +21,6 @@ public class Messages extends NLS {
public static String CMakeBuildConfiguration_Cleaning;
public static String CMakeBuildConfiguration_Configuring;
public static String CMakeBuildConfiguration_NotFound;
public static String CMakeBuildConfiguration_NoToolchainFile;
public static String CMakeBuildConfiguration_ProcCompCmds;
public static String CMakeBuildConfiguration_ProcCompJson;
public static String CMakeBuildConfiguration_Failure;

View file

@ -15,7 +15,6 @@ CMakeBuildConfiguration_BuildComplete=Build complete\n
CMakeBuildConfiguration_Configuring=Configuring in: %s\n
CMakeBuildConfiguration_Cleaning=Cleaning %s
CMakeBuildConfiguration_NotFound=CMakeFiles not found. Assuming clean.
CMakeBuildConfiguration_NoToolchainFile=No CMake toolchain file found for this target.
CMakeBuildConfiguration_ProcCompCmds=Processing compile commands %s
CMakeBuildConfiguration_ProcCompJson=Processing compile_commands.json
CMakeBuildConfiguration_Failure=Failure running cmake: %s\n