diff --git a/Source/cmCommonTargetGenerator.cxx b/Source/cmCommonTargetGenerator.cxx index 11fb4c7c5f..6e3d9cde95 100644 --- a/Source/cmCommonTargetGenerator.cxx +++ b/Source/cmCommonTargetGenerator.cxx @@ -8,6 +8,7 @@ #include #include +#include #include #include "cmComputeLinkInformation.h" @@ -422,10 +423,10 @@ void cmCommonTargetGenerator::AppendOSXVerFlag(std::string& flags, } } -std::string cmCommonTargetGenerator::GetCompilerLauncher( +std::vector cmCommonTargetGenerator::GetCompilerLauncher( std::string const& lang, std::string const& config) { - std::string compilerLauncher; + std::vector compilerLauncher; if (lang == "C" || lang == "CXX" || lang == "Fortran" || lang == "CUDA" || lang == "HIP" || lang == "ISPC" || lang == "OBJC" || lang == "OBJCXX") { std::string const propName = cmStrCat(lang, "_COMPILER_LAUNCHER"); @@ -434,14 +435,15 @@ std::string cmCommonTargetGenerator::GetCompilerLauncher( *cLauncherValue, this->GeneratorTarget->GetLocalGenerator(), config, this->GeneratorTarget, nullptr, this->GeneratorTarget, lang); if (!evaluatedCLauncher.empty()) { - compilerLauncher = evaluatedCLauncher; + cm::append(compilerLauncher, + cmList{ evaluatedCLauncher, cmList::EmptyElements::Yes }); } } return compilerLauncher; } std::string cmCommonTargetGenerator::GenerateCodeCheckRules( - cmSourceFile const& source, std::string& compilerLauncher, + cmSourceFile const& source, std::vector& compilerLauncher, std::string const& cmakeCmd, std::string const& config, std::function const& pathConverter) { @@ -488,11 +490,14 @@ std::string cmCommonTargetGenerator::GenerateCodeCheckRules( std::string code_check = cmakeCmd + " -E __run_co_compile"; if (!compilerLauncher.empty()) { // In __run_co_compile case the launcher command is supplied - // via --launcher= and consumed + // via --launcher= and consumed + for (std::string& arg : compilerLauncher) { + cmSystemTools::ReplaceString(arg, ";", "\\;"); + } code_check = cmStrCat(std::move(code_check), " --launcher=", this->GeneratorTarget->GetLocalGenerator()->EscapeForShell( - compilerLauncher)); + cmJoin(compilerLauncher, ";"))); compilerLauncher.clear(); } if (cmNonempty(iwyu)) { @@ -639,32 +644,44 @@ std::string cmCommonTargetGenerator::GenerateCodeCheckRules( return ""; } -std::string cmCommonTargetGenerator::GetLinkerLauncher( +std::vector cmCommonTargetGenerator::GetLinkerLauncher( std::string const& config) { - std::string lang = this->GeneratorTarget->GetLinkerLanguage(config); - std::string propName = lang + "_LINKER_LAUNCHER"; + std::vector linkLauncher; + std::string const lang = this->GeneratorTarget->GetLinkerLanguage(config); + std::string const propName = cmStrCat(lang, "_LINKER_LAUNCHER"); cmValue launcherProp = this->GeneratorTarget->GetProperty(propName); if (cmNonempty(launcherProp)) { cm::GenEx::Context context(this->LocalCommonGenerator, config, lang); cmGeneratorExpressionDAGChecker dagChecker{ this->GeneratorTarget, propName, nullptr, nullptr, context }; - std::string evaluatedLinklauncher = cmGeneratorExpression::Evaluate( + + std::string const evaluatedLinkLauncher = cmGeneratorExpression::Evaluate( *launcherProp, context.LG, context.Config, this->GeneratorTarget, &dagChecker, this->GeneratorTarget, context.Language); - // Convert ;-delimited list to single string - cmList args{ evaluatedLinklauncher, cmList::EmptyElements::Yes }; - if (!args.empty()) { - args[0] = this->LocalCommonGenerator->ConvertToOutputFormat( - args[0], cmOutputConverter::SHELL); - for (std::string& i : cmMakeRange(args.begin() + 1, args.end())) { - i = this->LocalCommonGenerator->EscapeForShell(i); - } - return cmJoin(args, " "); + if (!evaluatedLinkLauncher.empty()) { + cm::append(linkLauncher, + cmList{ evaluatedLinkLauncher, cmList::EmptyElements::Yes }); } } - return std::string(); + return linkLauncher; +} + +std::string cmCommonTargetGenerator::ConvertLauncherToShell( + std::vector const& launcher) const +{ + if (launcher.empty()) { + return std::string{}; + } + + std::vector args = launcher; + args[0] = this->LocalCommonGenerator->ConvertToOutputFormat( + args[0], cmOutputConverter::SHELL); + for (std::string& arg : cmMakeRange(args.begin() + 1, args.end())) { + arg = this->LocalCommonGenerator->EscapeForShell(arg); + } + return cmJoin(args, " "); } bool cmCommonTargetGenerator::HaveRequiredLanguages( diff --git a/Source/cmCommonTargetGenerator.h b/Source/cmCommonTargetGenerator.h index d8fd69499d..74c4604f89 100644 --- a/Source/cmCommonTargetGenerator.h +++ b/Source/cmCommonTargetGenerator.h @@ -68,12 +68,12 @@ protected: std::string GetManifests(std::string const& config); std::string GetAIXExports(std::string const& config); std::string GenerateCodeCheckRules( - cmSourceFile const& source, std::string& compilerLauncher, + cmSourceFile const& source, std::vector& compilerLauncher, std::string const& cmakeCmd, std::string const& config, std::function const& pathConverter); - std::string GetCompilerLauncher(std::string const& lang, - std::string const& config); + std::vector GetCompilerLauncher(std::string const& lang, + std::string const& config); struct LinkedTargetDirs { @@ -85,7 +85,10 @@ protected: std::string const& config) const; std::string ComputeTargetCompilePDB(std::string const& config) const; - std::string GetLinkerLauncher(std::string const& config); + std::vector GetLinkerLauncher(std::string const& config); + + std::string ConvertLauncherToShell( + std::vector const& launcher) const; bool HaveRequiredLanguages(std::vector const& sources, std::set& languagesNeeded) const; diff --git a/Source/cmFastbuildNormalTargetGenerator.cxx b/Source/cmFastbuildNormalTargetGenerator.cxx index db1640c06f..6c4eb6d5dd 100644 --- a/Source/cmFastbuildNormalTargetGenerator.cxx +++ b/Source/cmFastbuildNormalTargetGenerator.cxx @@ -822,11 +822,12 @@ void cmFastbuildNormalTargetGenerator::AddCompilerLaunchersForLanguages() this->GetGeneratorTarget(), "RULE_LAUNCH_COMPILE", Config); // See if we need to use a compiler launcher like ccache or distcc for (std::string const& language : Languages) { - std::string const compilerLauncher = + std::vector expanded = cmCommonTargetGenerator::GetCompilerLauncher(language, Config); - LogMessage("compilerLauncher: " + compilerLauncher); - std::vector expanded; - cmExpandList(compilerLauncher, expanded); + LogMessage("compilerLauncher: " + cmJoin(expanded, ";")); + // FIXME(#27402): Empty arguments are not supported here. + expanded.erase(std::remove(expanded.begin(), expanded.end(), ""), + expanded.end()); if (!expanded.empty()) { std::string const exe = expanded[0]; @@ -844,14 +845,8 @@ void cmFastbuildNormalTargetGenerator::AddCompilerLaunchersForLanguages() } void cmFastbuildNormalTargetGenerator::AddLinkerLauncher() { - std::string const linkerLauncher = + std::vector args = cmCommonTargetGenerator::GetLinkerLauncher(Config); - std::vector args; -#ifdef _WIN32 - cmSystemTools::ParseWindowsCommandLine(linkerLauncher.c_str(), args); -#else - cmSystemTools::ParseUnixCommandLine(linkerLauncher.c_str(), args); -#endif if (!args.empty()) { std::string const exe = std::move(args[0]); args.erase(args.begin()); @@ -1191,7 +1186,7 @@ std::string cmFastbuildNormalTargetGenerator::ComputeCodeCheckOptions( if (skipCodeCheck) { return {}; } - std::string compilerLauncher; + std::vector compilerLauncher; std::string staticCheckRule = this->GenerateCodeCheckRules( srcFile, compilerLauncher, "", Config, nullptr); LogMessage(cmStrCat("CodeCheck: ", staticCheckRule)); diff --git a/Source/cmMakefileExecutableTargetGenerator.cxx b/Source/cmMakefileExecutableTargetGenerator.cxx index 77c040fef5..5bbaabe118 100644 --- a/Source/cmMakefileExecutableTargetGenerator.cxx +++ b/Source/cmMakefileExecutableTargetGenerator.cxx @@ -592,8 +592,8 @@ void cmMakefileExecutableTargetGenerator::WriteExecutableRule(bool relink) vars.LinkFlags = linkFlags.c_str(); vars.Manifests = manifests.c_str(); - std::string linkerLauncher = - this->GetLinkerLauncher(this->GetConfigName()); + std::string const linkerLauncher = this->ConvertLauncherToShell( + this->GetLinkerLauncher(this->GetConfigName())); if (cmNonempty(linkerLauncher)) { vars.Launcher = linkerLauncher.c_str(); } diff --git a/Source/cmMakefileLibraryTargetGenerator.cxx b/Source/cmMakefileLibraryTargetGenerator.cxx index 9991868351..133e18423d 100644 --- a/Source/cmMakefileLibraryTargetGenerator.cxx +++ b/Source/cmMakefileLibraryTargetGenerator.cxx @@ -876,8 +876,8 @@ void cmMakefileLibraryTargetGenerator::WriteLibraryRules( vars.LanguageCompileFlags = langFlags.c_str(); - std::string linkerLauncher = - this->GetLinkerLauncher(this->GetConfigName()); + std::string const linkerLauncher = this->ConvertLauncherToShell( + this->GetLinkerLauncher(this->GetConfigName())); if (cmNonempty(linkerLauncher)) { vars.Launcher = linkerLauncher.c_str(); } diff --git a/Source/cmMakefileTargetGenerator.cxx b/Source/cmMakefileTargetGenerator.cxx index 7cddd65f8f..4779ab1e34 100644 --- a/Source/cmMakefileTargetGenerator.cxx +++ b/Source/cmMakefileTargetGenerator.cxx @@ -1106,7 +1106,7 @@ void cmMakefileTargetGenerator::WriteObjectRuleFiles( } // See if we need to use a compiler launcher like ccache or distcc - std::string compilerLauncher; + std::vector compilerLauncher; if (!compileCommands.empty()) { compilerLauncher = GetCompilerLauncher(lang, config); } @@ -1131,15 +1131,8 @@ void cmMakefileTargetGenerator::WriteObjectRuleFiles( // If compiler launcher was specified and not consumed above, it // goes to the beginning of the command line. if (!compileCommands.empty() && !compilerLauncher.empty()) { - cmList args{ compilerLauncher, cmList::EmptyElements::Yes }; - if (!args.empty()) { - args[0] = this->LocalGenerator->ConvertToOutputFormat( - args[0], cmOutputConverter::SHELL); - for (std::string& i : cmMakeRange(args.begin() + 1, args.end())) { - i = this->LocalGenerator->EscapeForShell(i); - } - } - compileCommands.front().insert(0, args.join(" ") + " "); + compileCommands.front().insert( + 0, cmStrCat(this->ConvertLauncherToShell(compilerLauncher), ' ')); } std::string launcher; diff --git a/Source/cmNinjaNormalTargetGenerator.cxx b/Source/cmNinjaNormalTargetGenerator.cxx index 8a569188a3..c4c0b3db26 100644 --- a/Source/cmNinjaNormalTargetGenerator.cxx +++ b/Source/cmNinjaNormalTargetGenerator.cxx @@ -584,7 +584,8 @@ void cmNinjaNormalTargetGenerator::WriteLinkRule( vars.LanguageCompileFlags = langFlags.c_str(); } - std::string linkerLauncher = this->GetLinkerLauncher(config); + std::string const linkerLauncher = + this->ConvertLauncherToShell(this->GetLinkerLauncher(config)); if (cmNonempty(linkerLauncher)) { vars.Launcher = linkerLauncher.c_str(); } diff --git a/Source/cmNinjaTargetGenerator.cxx b/Source/cmNinjaTargetGenerator.cxx index dece6cb8c2..f985d395df 100644 --- a/Source/cmNinjaTargetGenerator.cxx +++ b/Source/cmNinjaTargetGenerator.cxx @@ -47,7 +47,6 @@ #include "cmNinjaUtilityTargetGenerator.h" #include "cmOutputConverter.h" #include "cmPolicies.h" -#include "cmRange.h" #include "cmRulePlaceholderExpander.h" #include "cmSourceFile.h" #include "cmSourceFileLocationKind.h" @@ -1598,15 +1597,8 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatement( // If compiler launcher was specified and not consumed above, it // goes to the beginning of the command line. if (!compilerLauncher.empty()) { - cmList args{ compilerLauncher, cmList::EmptyElements::Yes }; - if (!args.empty()) { - args[0] = this->LocalGenerator->ConvertToOutputFormat( - args[0], cmOutputConverter::SHELL); - for (std::string& i : cmMakeRange(args.begin() + 1, args.end())) { - i = this->LocalGenerator->EscapeForShell(i); - } - vars["LAUNCHER"] = args.join(" ") + " "; - } + vars["LAUNCHER"] = + cmStrCat(this->ConvertLauncherToShell(compilerLauncher), ' '); } if (this->GetMakefile()->GetSafeDefinition( diff --git a/Tests/RunCMake/CompilerLauncher/C-special-args-Build-stdout.txt b/Tests/RunCMake/CompilerLauncher/C-special-args-Build-stdout.txt new file mode 100644 index 0000000000..be1afe7a9c --- /dev/null +++ b/Tests/RunCMake/CompilerLauncher/C-special-args-Build-stdout.txt @@ -0,0 +1 @@ +special-args launcher ok: compile diff --git a/Tests/RunCMake/CompilerLauncher/C-special-args-cppcheck-Build-stdout.txt b/Tests/RunCMake/CompilerLauncher/C-special-args-cppcheck-Build-stdout.txt new file mode 100644 index 0000000000..be1afe7a9c --- /dev/null +++ b/Tests/RunCMake/CompilerLauncher/C-special-args-cppcheck-Build-stdout.txt @@ -0,0 +1 @@ +special-args launcher ok: compile diff --git a/Tests/RunCMake/CompilerLauncher/C-special-args-cppcheck.cmake b/Tests/RunCMake/CompilerLauncher/C-special-args-cppcheck.cmake new file mode 100644 index 0000000000..602bf0d73a --- /dev/null +++ b/Tests/RunCMake/CompilerLauncher/C-special-args-cppcheck.cmake @@ -0,0 +1,3 @@ +# Lint tools run the launcher through "cmake -E __run_co_compile --launcher=...". +set(CMAKE_C_CPPCHECK "${CMAKE_COMMAND};-E;true") +include(C-special-args.cmake) diff --git a/Tests/RunCMake/CompilerLauncher/C-special-args.cmake b/Tests/RunCMake/CompilerLauncher/C-special-args.cmake new file mode 100644 index 0000000000..c4521b6e45 --- /dev/null +++ b/Tests/RunCMake/CompilerLauncher/C-special-args.cmake @@ -0,0 +1,4 @@ +set(check "${CMAKE_CURRENT_LIST_DIR}/check-special-args.cmake") +set(CMAKE_C_COMPILER_LAUNCHER + "${CMAKE_COMMAND};-P;${check};--;compile;;semi\\;colon;with space") +include(C-common.cmake) diff --git a/Tests/RunCMake/CompilerLauncher/RunCMakeTest.cmake b/Tests/RunCMake/CompilerLauncher/RunCMakeTest.cmake index 16a42ce238..519f072f54 100644 --- a/Tests/RunCMake/CompilerLauncher/RunCMakeTest.cmake +++ b/Tests/RunCMake/CompilerLauncher/RunCMakeTest.cmake @@ -51,3 +51,10 @@ foreach(lang ${langs}) run_compiler_launcher_env(${lang}-launch-env) endif() endforeach() + +if(NOT RunCMake_GENERATOR MATCHES "FASTBuild") + # FIXME(#27402): FASTBuild builds its launcher command line without shell + # quoting, so it can't handle empty or special-character arguments. + run_compiler_launcher(C-special-args) + run_compiler_launcher(C-special-args-cppcheck) +endif() diff --git a/Tests/RunCMake/CompilerLauncher/check-special-args.cmake b/Tests/RunCMake/CompilerLauncher/check-special-args.cmake new file mode 100644 index 0000000000..930aa8bb06 --- /dev/null +++ b/Tests/RunCMake/CompilerLauncher/check-special-args.cmake @@ -0,0 +1,41 @@ +# Verifies that the launcher's own arguments arrive intact, then runs the +# launched command. + +set(base 4) # CMAKE_ARGV0..3 are: cmake -P