cmake --build: Factor out a struct holding build parameters

This commit extracts the set of parameters required to build a CMake
project into a dedicated class.  While this class is currently just a
wrapper for the data, the goal is to eventually implement a validate()
method which verifies that a given configuration contains the minimum
set of information required to execute a build, and that any optional
options are compatible with one another.
This commit is contained in:
William Allen 2025-12-02 22:51:55 -05:00 committed by Brad King
parent de91859711
commit c71e3abbad
9 changed files with 136 additions and 95 deletions

View File

@ -115,6 +115,7 @@ add_library(
cmBinUtilsWindowsPELinker.h
cmBinUtilsWindowsPEObjdumpGetRuntimeDependenciesTool.cxx
cmBinUtilsWindowsPEObjdumpGetRuntimeDependenciesTool.h
cmBuildArgs.h
cmBuildDatabase.cxx
cmBuildDatabase.h
cmBuildOptions.h

View File

@ -8,8 +8,11 @@
#include <ratio>
#include <utility>
#include <cm/filesystem>
#include <cm3p/uv.h>
#include "cmBuildArgs.h"
#include "cmBuildOptions.h"
#include "cmCTest.h"
#include "cmCTestTestHandler.h"
@ -256,12 +259,17 @@ int cmCTestBuildAndTest::Run()
config = "Debug";
}
cmBuildArgs buildArgs;
buildArgs.jobs = cmake::NO_BUILD_PARALLEL_LEVEL;
buildArgs.binaryDir = this->BinaryDir;
buildArgs.projectName = this->BuildProject;
buildArgs.verbose = false;
cmBuildOptions buildOptions(!this->BuildNoClean, false,
PackageResolveMode::Disable);
int retVal = cm.GetGlobalGenerator()->Build(
cmake::NO_BUILD_PARALLEL_LEVEL, this->SourceDir, this->BinaryDir,
this->BuildProject, { tar }, std::cout, this->BuildMakeProgram, config,
buildOptions, false, remainingTime, cmSystemTools::OUTPUT_PASSTHROUGH);
buildArgs, { tar }, std::cout, this->BuildMakeProgram, config,
buildOptions, remainingTime, cmSystemTools::OUTPUT_PASSTHROUGH);
// if the build failed then return
if (retVal) {
return 1;

22
Source/cmBuildArgs.h Normal file
View File

@ -0,0 +1,22 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file LICENSE.rst or https://cmake.org/licensing for details. */
#pragma once
#include <cm/filesystem>
#include <cm/string>
#include "cmDuration.h"
class cmBuildArgs
{
public:
static constexpr int NO_BUILD_PARALLEL_LEVEL = -1;
static constexpr int DEFAULT_BUILD_PARALLEL_LEVEL = 0;
std::string projectName;
cm::filesystem::path binaryDir;
int jobs = NO_BUILD_PARALLEL_LEVEL;
bool verbose = false;
std::string config;
cmDuration timeout;
};

View File

@ -13,6 +13,7 @@
#include <sstream>
#include <utility>
#include <cm/filesystem>
#include <cm/memory>
#include <cm/optional>
#include <cmext/algorithm>
@ -25,6 +26,7 @@
#include "cm_codecvt_Encoding.hxx"
#include "cmAlgorithms.h"
#include "cmBuildArgs.h"
#include "cmCMakePath.h"
#include "cmCPackPropertiesGenerator.h"
#include "cmComputeTargetDepends.h"
@ -2143,12 +2145,17 @@ void cmGlobalGenerator::CheckTargetProperties()
}
}
int cmGlobalGenerator::TryCompile(int jobs, std::string const& srcdir,
std::string const& bindir,
int cmGlobalGenerator::TryCompile(int jobs, std::string const& bindir,
std::string const& projectName,
std::string const& target, bool fast,
std::string& output, cmMakefile* mf)
{
cmBuildArgs buildArgs;
buildArgs.jobs = jobs;
buildArgs.binaryDir = bindir;
buildArgs.projectName = projectName;
buildArgs.verbose = true;
// if this is not set, then this is a first time configure
// and there is a good chance that the try compile stuff will
// take the bulk of the time, so try and guess some progress
@ -2176,10 +2183,9 @@ int cmGlobalGenerator::TryCompile(int jobs, std::string const& srcdir,
cmBuildOptions defaultBuildOptions(false, fast, PackageResolveMode::Disable);
std::stringstream ostr;
auto ret =
this->Build(jobs, srcdir, bindir, projectName, newTarget, ostr, "", config,
defaultBuildOptions, true, this->TryCompileTimeout,
cmSystemTools::OUTPUT_NONE, {}, BuildTryCompile::Yes);
auto ret = this->Build(buildArgs, newTarget, ostr, "", config,
defaultBuildOptions, this->TryCompileTimeout,
cmSystemTools::OUTPUT_NONE, {}, BuildTryCompile::Yes);
output = ostr.str();
return ret;
}
@ -2204,22 +2210,23 @@ void cmGlobalGenerator::PrintBuildCommandAdvice(std::ostream& /*os*/,
// they do not support certain build command line options
}
int cmGlobalGenerator::Build(
int jobs, std::string const& /*unused*/, std::string const& bindir,
std::string const& projectName, std::vector<std::string> const& targets,
std::ostream& ostr, std::string const& makeCommandCSTR,
std::string const& config, cmBuildOptions buildOptions, bool verbose,
cmDuration timeout, cmSystemTools::OutputOption outputMode,
std::vector<std::string> const& nativeOptions,
BuildTryCompile isInTryCompile)
int cmGlobalGenerator::Build(cmBuildArgs const& buildArgs,
std::vector<std::string> const& targets,
std::ostream& ostr,
std::string const& makeCommandCSTR,
std::string const& config,
cmBuildOptions buildOptions, cmDuration timeout,
cmSystemTools::OutputOption outputMode,
std::vector<std::string> const& nativeOptions,
BuildTryCompile isInTryCompile)
{
bool hideconsole = cmSystemTools::GetRunCommandHideConsole();
/**
* Run an executable command and put the stdout in output.
*/
cmWorkingDirectory workdir(bindir);
ostr << "Change Dir: '" << bindir << '\'' << std::endl;
cmWorkingDirectory workdir(buildArgs.binaryDir.string());
ostr << "Change Dir: '" << buildArgs.binaryDir.string() << '\'' << std::endl;
if (workdir.Failed()) {
cmSystemTools::SetRunCommandHideConsole(hideconsole);
std::string const& err = workdir.GetError();
@ -2239,8 +2246,9 @@ int cmGlobalGenerator::Build(
std::string outputBuf;
std::vector<GeneratedMakeCommand> makeCommand = this->GenerateBuildCommand(
makeCommandCSTR, projectName, bindir, targets, realConfig, jobs, verbose,
buildOptions, nativeOptions, isInTryCompile);
makeCommandCSTR, buildArgs.projectName, buildArgs.binaryDir.string(),
targets, realConfig, buildArgs.jobs, buildArgs.verbose, buildOptions,
nativeOptions, isInTryCompile);
// Workaround to convince some commands to produce output.
if (outputMode == cmSystemTools::OUTPUT_PASSTHROUGH &&
@ -2251,8 +2259,9 @@ int cmGlobalGenerator::Build(
// should we do a clean first?
if (buildOptions.Clean) {
std::vector<GeneratedMakeCommand> cleanCommand =
this->GenerateBuildCommand(makeCommandCSTR, projectName, bindir,
{ "clean" }, realConfig, jobs, verbose,
this->GenerateBuildCommand(makeCommandCSTR, buildArgs.projectName,
buildArgs.binaryDir.string(), { "clean" },
realConfig, buildArgs.jobs, buildArgs.verbose,
buildOptions);
ostr << "\nRun Clean Command: " << cleanCommand.front().QuotedPrintable()
<< std::endl;

View File

@ -45,6 +45,7 @@
enum class cmDepfileFormat;
enum class codecvt_Encoding;
class cmBuildArgs;
class cmDirectoryId;
class cmExportBuildFileGenerator;
class cmExternalMakefileProjectGenerator;
@ -247,10 +248,9 @@ public:
* Try running cmake and building a file. This is used for dynamically
* loaded commands, not as part of the usual build process.
*/
int TryCompile(int jobs, std::string const& srcdir,
std::string const& bindir, std::string const& projectName,
std::string const& targetName, bool fast, std::string& output,
cmMakefile* mf);
int TryCompile(int jobs, std::string const& bindir,
std::string const& projectName, std::string const& targetName,
bool fast, std::string& output, cmMakefile* mf);
/**
* Build a file given the following information. This is a more direct call
@ -259,11 +259,9 @@ public:
* done first.
*/
int Build(
int jobs, std::string const& srcdir, std::string const& bindir,
std::string const& projectName,
std::vector<std::string> const& targetNames, std::ostream& ostr,
std::string const& makeProgram, std::string const& config,
cmBuildOptions buildOptions, bool verbose, cmDuration timeout,
cmBuildArgs const& buildArgs, std::vector<std::string> const& targetNames,
std::ostream& ostr, std::string const& makeProgram,
std::string const& config, cmBuildOptions buildOptions, cmDuration timeout,
cmSystemTools::OutputOption outputMode,
std::vector<std::string> const& nativeOptions = std::vector<std::string>(),
BuildTryCompile isInTryCompile = BuildTryCompile::No);

View File

@ -3330,7 +3330,7 @@ int cmMakefile::TryCompile(std::string const& srcdir,
// finally call the generator to actually build the resulting project
int ret = this->GetGlobalGenerator()->TryCompile(
jobs, srcdir, bindir, projectName, targetName, fast, output, this);
jobs, bindir, projectName, targetName, fast, output, this);
this->IsSourceFileTryCompile = false;
return ret;

View File

@ -3833,12 +3833,13 @@ std::vector<std::string> cmake::GetDebugConfigs()
return std::move(configs.data());
}
int cmake::Build(int jobs, std::string dir, std::vector<std::string> targets,
std::string config, std::vector<std::string> nativeOptions,
cmBuildOptions& buildOptions, bool verbose,
std::string const& presetName, bool listPresets,
std::vector<std::string> const& args)
int cmake::Build(cmBuildArgs buildArgs, std::vector<std::string> targets,
std::vector<std::string> nativeOptions,
cmBuildOptions& buildOptions, std::string const& presetName,
bool listPresets, std::vector<std::string> const& args)
{
buildArgs.timeout = cmDuration::zero();
#if !defined(CMAKE_BOOTSTRAP)
if (!presetName.empty() || listPresets) {
this->SetHomeDirectory(cmSystemTools::GetLogicalWorkingDirectory());
@ -3918,17 +3919,18 @@ int cmake::Build(int jobs, std::string dir, std::vector<std::string> targets,
return 1;
}
if (dir.empty() && !expandedConfigurePreset->BinaryDir.empty()) {
dir = expandedConfigurePreset->BinaryDir;
if (buildArgs.binaryDir.empty() &&
!expandedConfigurePreset->BinaryDir.empty()) {
buildArgs.binaryDir = expandedConfigurePreset->BinaryDir;
}
this->UnprocessedPresetEnvironment = expandedPreset->Environment;
this->ProcessPresetEnvironment();
if ((jobs == cmake::DEFAULT_BUILD_PARALLEL_LEVEL ||
jobs == cmake::NO_BUILD_PARALLEL_LEVEL) &&
if ((buildArgs.jobs == cmake::DEFAULT_BUILD_PARALLEL_LEVEL ||
buildArgs.jobs == cmake::NO_BUILD_PARALLEL_LEVEL) &&
expandedPreset->Jobs) {
jobs = *expandedPreset->Jobs;
buildArgs.jobs = *expandedPreset->Jobs;
}
if (targets.empty()) {
@ -3936,8 +3938,8 @@ int cmake::Build(int jobs, std::string dir, std::vector<std::string> targets,
expandedPreset->Targets.end());
}
if (config.empty()) {
config = expandedPreset->Configuration;
if (buildArgs.config.empty()) {
buildArgs.config = expandedPreset->Configuration;
}
if (!buildOptions.Clean && expandedPreset->CleanFirst) {
@ -3949,8 +3951,8 @@ int cmake::Build(int jobs, std::string dir, std::vector<std::string> targets,
buildOptions.ResolveMode = *expandedPreset->ResolvePackageReferences;
}
if (!verbose && expandedPreset->Verbose) {
verbose = *expandedPreset->Verbose;
if (!buildArgs.verbose && expandedPreset->Verbose) {
buildArgs.verbose = *expandedPreset->Verbose;
}
if (nativeOptions.empty()) {
@ -3961,12 +3963,13 @@ int cmake::Build(int jobs, std::string dir, std::vector<std::string> targets,
}
#endif
if (!cmSystemTools::FileIsDirectory(dir)) {
std::cerr << "Error: " << dir << " is not a directory\n";
if (!cmSystemTools::FileIsDirectory(buildArgs.binaryDir.string())) {
std::cerr << "Error: " << buildArgs.binaryDir.string()
<< " is not a directory\n";
return 1;
}
std::string cachePath = FindCacheFile(dir);
std::string cachePath = FindCacheFile(buildArgs.binaryDir.string());
if (!this->LoadCache(cachePath)) {
std::cerr
<< "Error: not a CMake build directory (missing CMakeCache.txt)\n";
@ -4011,17 +4014,16 @@ int cmake::Build(int jobs, std::string dir, std::vector<std::string> targets,
return 1;
}
}
std::string projName;
cmValue cachedProjectName =
this->State->GetCacheEntryValue("CMAKE_PROJECT_NAME");
if (!cachedProjectName) {
std::cerr << "Error: could not find CMAKE_PROJECT_NAME in Cache\n";
return 1;
}
projName = *cachedProjectName;
buildArgs.projectName = *cachedProjectName;
if (this->State->GetCacheEntryValue("CMAKE_VERBOSE_MAKEFILE").IsOn()) {
verbose = true;
buildArgs.verbose = true;
}
#ifdef CMAKE_HAVE_VS_GENERATORS
@ -4068,7 +4070,7 @@ int cmake::Build(int jobs, std::string dir, std::vector<std::string> targets,
}
#if !defined(CMAKE_BOOTSTRAP)
cmInstrumentation instrumentation(dir);
cmInstrumentation instrumentation(buildArgs.binaryDir.string());
if (instrumentation.HasErrors()) {
return 1;
}
@ -4076,18 +4078,17 @@ int cmake::Build(int jobs, std::string dir, std::vector<std::string> targets,
cmInstrumentationQuery::Hook::PreCMakeBuild);
#endif
this->GlobalGenerator->PrintBuildCommandAdvice(std::cerr, jobs);
this->GlobalGenerator->PrintBuildCommandAdvice(std::cerr, buildArgs.jobs);
std::stringstream ostr;
// `cmGlobalGenerator::Build` logs metadata about what directory and commands
// are being executed to the `output` parameter. If CMake is verbose, print
// this out.
std::ostream& verbose_ostr = verbose ? std::cout : ostr;
auto doBuild = [this, jobs, dir, projName, targets, &verbose_ostr, config,
buildOptions, verbose, nativeOptions]() -> int {
std::ostream& verbose_ostr = buildArgs.verbose ? std::cout : ostr;
auto doBuild = [this, targets, &verbose_ostr, buildOptions, buildArgs,
nativeOptions]() -> int {
return this->GlobalGenerator->Build(
jobs, "", dir, projName, targets, verbose_ostr, "", config, buildOptions,
verbose, cmDuration::zero(), cmSystemTools::OUTPUT_PASSTHROUGH,
nativeOptions);
buildArgs, targets, verbose_ostr, "", buildArgs.config, buildOptions,
buildArgs.timeout, cmSystemTools::OUTPUT_PASSTHROUGH, nativeOptions);
};
#if !defined(CMAKE_BOOTSTRAP)

View File

@ -18,9 +18,9 @@
#include <cm/string_view>
#include <cmext/string_view>
#include "cmBuildArgs.h"
#include "cmDocumentationEntry.h" // IWYU pragma: keep
#include "cmGeneratedFileStream.h"
#include "cmGlobalGeneratorFactory.h"
#include "cmInstalledFile.h"
#include "cmListFileCache.h"
#include "cmMessageType.h"
@ -54,6 +54,7 @@ class cmGlobalGenerator;
class cmMakefile;
class cmMessenger;
class cmVariableWatch;
class cmGlobalGeneratorFactory;
struct cmBuildOptions;
struct cmGlobCacheEntry;
@ -158,8 +159,10 @@ public:
using InstalledFilesMap = std::map<std::string, cmInstalledFile>;
static int const NO_BUILD_PARALLEL_LEVEL = -1;
static int const DEFAULT_BUILD_PARALLEL_LEVEL = 0;
static int const NO_BUILD_PARALLEL_LEVEL =
cmBuildArgs::NO_BUILD_PARALLEL_LEVEL;
static int const DEFAULT_BUILD_PARALLEL_LEVEL =
cmBuildArgs::DEFAULT_BUILD_PARALLEL_LEVEL;
/// Default constructor
cmake(cmState::Role role,
@ -653,11 +656,10 @@ public:
cmListFileBacktrace const& backtrace = cmListFileBacktrace()) const;
//! run the --build option
int Build(int jobs, std::string dir, std::vector<std::string> targets,
std::string config, std::vector<std::string> nativeOptions,
cmBuildOptions& buildOptions, bool verbose,
std::string const& presetName, bool listPresets,
std::vector<std::string> const& args);
int Build(cmBuildArgs buildArgs, std::vector<std::string> targets,
std::vector<std::string> nativeOptions,
cmBuildOptions& buildOptions, std::string const& presetName,
bool listPresets, std::vector<std::string> const& args);
enum class DryRun
{

View File

@ -15,11 +15,13 @@
#include <utility>
#include <vector>
#include <cm/filesystem>
#include <cm/optional>
#include <cmext/algorithm>
#include <cm3p/uv.h>
#include "cmBuildArgs.h"
#include "cmBuildOptions.h"
#include "cmCommandLineArgument.h"
#include "cmDocumentationEntry.h"
@ -447,7 +449,7 @@ int extract_job_number(std::string const& command,
return jobs;
}
std::function<bool(std::string const&)> extract_job_number_lambda_builder(
std::string& dir, int& jobs, std::string const& flag)
cm::filesystem::path& dir, int& jobs, std::string const& flag)
{
return [&dir, &jobs, flag](std::string const& value) -> bool {
jobs = extract_job_number(flag, value);
@ -465,23 +467,22 @@ int do_build(int ac, char const* const* av)
std::cerr << "This cmake does not support --build\n";
return -1;
#else
int jobs = cmake::NO_BUILD_PARALLEL_LEVEL;
cmBuildArgs buildArgs;
std::vector<std::string> targets;
std::string config;
std::string dir;
std::vector<std::string> nativeOptions;
bool nativeOptionsPassed = false;
bool cleanFirst = false;
bool foundClean = false;
bool foundNonClean = false;
PackageResolveMode resolveMode = PackageResolveMode::Default;
bool verbose = cmSystemTools::HasEnv("VERBOSE");
buildArgs.verbose = cmSystemTools::HasEnv("VERBOSE");
std::string presetName;
bool listPresets = false;
auto jLambda = extract_job_number_lambda_builder(dir, jobs, "-j");
auto parallelLambda =
extract_job_number_lambda_builder(dir, jobs, "--parallel");
auto jLambda = extract_job_number_lambda_builder(buildArgs.binaryDir,
buildArgs.jobs, "-j");
auto parallelLambda = extract_job_number_lambda_builder(
buildArgs.binaryDir, buildArgs.jobs, "--parallel");
auto targetLambda = [&](std::string const& value) -> bool {
if (!value.empty()) {
@ -515,7 +516,7 @@ int do_build(int ac, char const* const* av)
return true;
};
auto verboseLambda = [&](std::string const&) -> bool {
verbose = true;
buildArgs.verbose = true;
return true;
};
@ -535,7 +536,7 @@ int do_build(int ac, char const* const* av)
CommandArgument{ "--target", CommandArgument::Values::OneOrMore,
targetLambda },
CommandArgument{ "--config", CommandArgument::Values::One,
CommandArgument::setToValue(config) },
CommandArgument::setToValue(buildArgs.config) },
CommandArgument{ "--clean-first", CommandArgument::Values::Zero,
CommandArgument::setToTrue(cleanFirst) },
CommandArgument{ "--resolve-package-references",
@ -570,12 +571,12 @@ int do_build(int ac, char const* const* av)
}
}
if (!matched && i == 0) {
dir = cmSystemTools::ToNormalizedPathOnDisk(arg);
buildArgs.binaryDir = cmSystemTools::ToNormalizedPathOnDisk(arg);
matched = true;
parsed = true;
}
if (!(matched && parsed)) {
dir.clear();
buildArgs.binaryDir.clear();
if (!matched) {
std::cerr << "Unknown argument " << arg << std::endl;
}
@ -592,38 +593,38 @@ int do_build(int ac, char const* const* av)
std::cerr << "Error: Building 'clean' and other targets together "
"is not supported."
<< std::endl;
dir.clear();
buildArgs.binaryDir.clear();
}
if (jobs == cmake::NO_BUILD_PARALLEL_LEVEL) {
if (buildArgs.jobs == cmake::NO_BUILD_PARALLEL_LEVEL) {
std::string parallel;
if (cmSystemTools::GetEnv("CMAKE_BUILD_PARALLEL_LEVEL", parallel)) {
if (parallel.empty()) {
jobs = cmake::DEFAULT_BUILD_PARALLEL_LEVEL;
buildArgs.jobs = cmake::DEFAULT_BUILD_PARALLEL_LEVEL;
} else {
unsigned long numJobs = 0;
if (cmStrToULong(parallel, &numJobs)) {
if (numJobs == 0) {
std::cerr << "The CMAKE_BUILD_PARALLEL_LEVEL environment variable "
"requires a positive integer argument.\n\n";
dir.clear();
buildArgs.binaryDir.clear();
} else if (numJobs > INT_MAX) {
std::cerr << "The CMAKE_BUILD_PARALLEL_LEVEL environment variable "
"is too large.\n\n";
dir.clear();
buildArgs.binaryDir.clear();
} else {
jobs = static_cast<int>(numJobs);
buildArgs.jobs = static_cast<int>(numJobs);
}
} else {
std::cerr << "'CMAKE_BUILD_PARALLEL_LEVEL' environment variable\n"
<< "invalid number '" << parallel << "' given.\n\n";
dir.clear();
buildArgs.binaryDir.clear();
}
}
}
}
if (dir.empty() && presetName.empty() && !listPresets) {
if (buildArgs.binaryDir.empty() && presetName.empty() && !listPresets) {
/* clang-format off */
std::cerr <<
"Usage: cmake --build <dir> "
@ -672,9 +673,8 @@ int do_build(int ac, char const* const* av)
cmBuildOptions buildOptions(cleanFirst, false, resolveMode);
std::vector<std::string> cmd;
cm::append(cmd, av, av + ac);
return cm.Build(jobs, dir, std::move(targets), std::move(config),
std::move(nativeOptions), buildOptions, verbose, presetName,
listPresets, cmd);
return cm.Build(buildArgs, std::move(targets), std::move(nativeOptions),
buildOptions, presetName, listPresets, cmd);
#endif
}
@ -805,7 +805,7 @@ int do_install(int ac, char const* const* av)
std::string component;
std::string defaultDirectoryPermissions;
std::string prefix;
std::string dir;
cm::filesystem::path dir;
int jobs = 0;
bool strip = false;
bool verbose = cmSystemTools::HasEnv("VERBOSE");
@ -919,8 +919,8 @@ int do_install(int ac, char const* const* av)
args.emplace_back("-P");
cmInstrumentation instrumentation(dir);
auto handler = cmInstallScriptHandler(dir, component, config, args);
cmInstrumentation instrumentation(dir.string());
auto handler = cmInstallScriptHandler(dir.string(), component, config, args);
int ret = 0;
if (!jobs && handler.IsParallel()) {
jobs = 1;