summaryrefslogtreecommitdiff
path: root/Source
diff options
context:
space:
mode:
Diffstat (limited to 'Source')
-rw-r--r--Source/CMakeInstallSignTool.cmake.in51
-rw-r--r--Source/CMakeVersion.rc.in28
-rw-r--r--Source/CPack/WiX/cmCMakeToWixPath.cxx39
-rw-r--r--Source/CPack/WiX/cmCMakeToWixPath.h12
-rw-r--r--Source/CPack/cmCPackExternalGenerator.cxx323
-rw-r--r--Source/CPack/cmCPackExternalGenerator.h89
-rw-r--r--Source/CPack/cmCPackFreeBSDGenerator.cxx345
-rw-r--r--Source/CPack/cmCPackFreeBSDGenerator.h37
-rw-r--r--Source/CPack/cmCPackNuGetGenerator.cxx141
-rw-r--r--Source/CPack/cmCPackNuGetGenerator.h37
-rw-r--r--Source/Checks/Curses.cmake41
-rw-r--r--Source/Checks/Curses/CMakeLists.txt25
-rw-r--r--Source/Checks/Curses/CheckCurses.c15
-rw-r--r--Source/Checks/cm_cxx14_check.cmake36
-rw-r--r--Source/Checks/cm_cxx14_check.cpp15
-rw-r--r--Source/Checks/cm_cxx17_check.cmake36
-rw-r--r--Source/Checks/cm_cxx17_check.cpp31
-rw-r--r--Source/Modules/CheckCXXLinkerFlag.cmake25
-rw-r--r--Source/Modules/FindLibUUID.cmake85
-rw-r--r--Source/Modules/OverrideC.cmake3
-rw-r--r--Source/Modules/OverrideCXX.cmake3
-rw-r--r--Source/cmAddCompileDefinitionsCommand.cxx20
-rw-r--r--Source/cmAddCompileDefinitionsCommand.h31
-rw-r--r--Source/cmAddLinkOptionsCommand.cxx20
-rw-r--r--Source/cmAddLinkOptionsCommand.h31
-rw-r--r--Source/cmAffinity.cxx64
-rw-r--r--Source/cmAffinity.h12
-rw-r--r--Source/cmArgumentParser.cxx93
-rw-r--r--Source/cmArgumentParser.h143
-rw-r--r--Source/cmConnection.cxx163
-rw-r--r--Source/cmConnection.h136
-rw-r--r--Source/cmDuration.cxx27
-rw-r--r--Source/cmDuration.h24
-rw-r--r--Source/cmFSPermissions.cxx34
-rw-r--r--Source/cmFSPermissions.h45
-rw-r--r--Source/cmFileAPI.cxx835
-rw-r--r--Source/cmFileAPI.h209
-rw-r--r--Source/cmFileAPICMakeFiles.cxx114
-rw-r--r--Source/cmFileAPICMakeFiles.h15
-rw-r--r--Source/cmFileAPICache.cxx106
-rw-r--r--Source/cmFileAPICache.h15
-rw-r--r--Source/cmFileAPICodemodel.cxx1250
-rw-r--r--Source/cmFileAPICodemodel.h15
-rw-r--r--Source/cmFileCopier.cxx713
-rw-r--r--Source/cmFileCopier.h122
-rw-r--r--Source/cmFileInstaller.cxx350
-rw-r--r--Source/cmFileInstaller.h55
-rw-r--r--Source/cmFileTime.cxx49
-rw-r--r--Source/cmFileTime.h130
-rw-r--r--Source/cmFileTimeCache.cxx62
-rw-r--r--Source/cmFileTimeCache.h56
-rw-r--r--Source/cmFileTimes.cxx127
-rw-r--r--Source/cmFileTimes.h40
-rw-r--r--Source/cmGetPipes.cxx48
-rw-r--r--Source/cmGetPipes.h8
-rw-r--r--Source/cmGlobVerificationManager.cxx178
-rw-r--r--Source/cmGlobVerificationManager.h85
-rw-r--r--Source/cmGlobalVisualStudioVersionedGenerator.cxx519
-rw-r--r--Source/cmGlobalVisualStudioVersionedGenerator.h65
-rw-r--r--Source/cmIncludeGuardCommand.cxx108
-rw-r--r--Source/cmIncludeGuardCommand.h37
-rw-r--r--Source/cmInstallSubdirectoryGenerator.cxx76
-rw-r--r--Source/cmInstallSubdirectoryGenerator.h41
-rw-r--r--Source/cmJsonObjectDictionary.h45
-rw-r--r--Source/cmJsonObjects.cxx693
-rw-r--r--Source/cmJsonObjects.h27
-rw-r--r--Source/cmLinkItem.cxx64
-rw-r--r--Source/cmMessageType.h21
-rw-r--r--Source/cmNinjaLinkLineDeviceComputer.cxx20
-rw-r--r--Source/cmNinjaLinkLineDeviceComputer.h34
-rw-r--r--Source/cmPipeConnection.cxx71
-rw-r--r--Source/cmPipeConnection.h28
-rw-r--r--Source/cmQtAutoGen.cxx410
-rw-r--r--Source/cmQtAutoGen.h138
-rw-r--r--Source/cmQtAutoGenGlobalInitializer.cxx303
-rw-r--r--Source/cmQtAutoGenGlobalInitializer.h86
-rw-r--r--Source/cmQtAutoGenInitializer.cxx1712
-rw-r--r--Source/cmQtAutoGenInitializer.h241
-rw-r--r--Source/cmQtAutoGenerator.cxx332
-rw-r--r--Source/cmQtAutoGenerator.h109
-rw-r--r--Source/cmQtAutoMocUic.cxx2193
-rw-r--r--Source/cmQtAutoMocUic.h576
-rw-r--r--Source/cmQtAutoRcc.cxx523
-rw-r--r--Source/cmQtAutoRcc.h81
-rw-r--r--Source/cmRange.h239
-rw-r--r--Source/cmSourceFileLocationKind.h15
-rw-r--r--Source/cmString.cxx152
-rw-r--r--Source/cmString.hxx815
-rw-r--r--Source/cmStringReplaceHelper.cxx120
-rw-r--r--Source/cmStringReplaceHelper.h68
-rw-r--r--Source/cmTargetLinkDirectoriesCommand.cxx61
-rw-r--r--Source/cmTargetLinkDirectoriesCommand.h41
-rw-r--r--Source/cmTargetLinkOptionsCommand.cxx41
-rw-r--r--Source/cmTargetLinkOptionsCommand.h41
-rw-r--r--Source/cmUVHandlePtr.cxx267
-rw-r--r--Source/cmUVHandlePtr.h271
-rw-r--r--Source/cmUVProcessChain.cxx395
-rw-r--r--Source/cmUVProcessChain.h100
-rw-r--r--Source/cmUVSignalHackRAII.h45
-rw-r--r--Source/cmUVStreambuf.h219
-rw-r--r--Source/cmWorkerPool.cxx763
-rw-r--r--Source/cmWorkerPool.h226
-rw-r--r--Source/cm_static_string_view.hxx41
-rw-r--r--Source/cm_string_view.cxx301
-rw-r--r--Source/cm_string_view.hxx217
-rw-r--r--Source/cm_thread.hxx48
-rw-r--r--Source/kwsys/testConfigure.cxx30
-rw-r--r--Source/kwsys/testDirectory.cxx110
-rw-r--r--Source/kwsys/testDynloadImpl.c10
-rw-r--r--Source/kwsys/testDynloadImpl.h15
-rw-r--r--Source/kwsys/testDynloadUse.c15
111 files changed, 20061 insertions, 0 deletions
diff --git a/Source/CMakeInstallSignTool.cmake.in b/Source/CMakeInstallSignTool.cmake.in
new file mode 100644
index 000000000..fca629c31
--- /dev/null
+++ b/Source/CMakeInstallSignTool.cmake.in
@@ -0,0 +1,51 @@
+# The signtool. Default to PATH.
+set(CMake_INSTALL_SIGNTOOL "@CMake_INSTALL_SIGNTOOL@")
+if(NOT CMake_INSTALL_SIGNTOOL)
+ set(CMake_INSTALL_SIGNTOOL signtool)
+endif()
+
+# Select a certificate by Subject Name. Default to automatic selection.
+set(CMake_INSTALL_SIGNTOOL_SUBJECT_NAME "@CMake_INSTALL_SIGNTOOL_SUBJECT_NAME@")
+if(CMake_INSTALL_SIGNTOOL_SUBJECT_NAME)
+ set(select_cert -n "${CMake_INSTALL_SIGNTOOL_SUBJECT_NAME}")
+else()
+ set(select_cert -a)
+endif()
+
+# Timestamp URL. Default to a common provider.
+set(CMake_INSTALL_SIGNTOOL_TIMESTAMP_URL "@CMake_INSTALL_SIGNTOOL_TIMESTAMP_URL@")
+if(NOT CMake_INSTALL_SIGNTOOL_TIMESTAMP_URL)
+ set(CMake_INSTALL_SIGNTOOL_TIMESTAMP_URL "http://timestamp.digicert.com")
+endif()
+
+# Glob files that need a signature.
+file(GLOB files "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/bin/*.exe")
+
+# Sign all files at once.
+if(files)
+ # Run the signtool through 'cmd /c' to enable password prompt popup.
+ # Some providers have trouble when signtool is invoked with SW_HIDE.
+ set(cmd cmd /c "${CMake_INSTALL_SIGNTOOL}" sign -v ${select_cert})
+
+ # Sign with SHA-1 for Windows 7 and below.
+ execute_process(
+ COMMAND ${cmd} -t "${CMake_INSTALL_SIGNTOOL_TIMESTAMP_URL}" ${files}
+ RESULT_VARIABLE result
+ ERROR_VARIABLE stderr
+ )
+ if(NOT result EQUAL 0)
+ string(REPLACE "\n" "\n " stderr " ${stderr}")
+ message(WARNING "signtool failed:\n${stderr}")
+ endif()
+
+ # Sign with SHA-256 for Windows 8 and above.
+ execute_process(
+ COMMAND ${cmd} -tr "${CMake_INSTALL_SIGNTOOL_TIMESTAMP_URL}" -fd sha256 -td sha256 -as ${files}
+ RESULT_VARIABLE result
+ ERROR_VARIABLE stderr
+ )
+ if(NOT result EQUAL 0)
+ string(REPLACE "\n" "\n " stderr " ${stderr}")
+ message(WARNING "signtool failed:\n${stderr}")
+ endif()
+endif()
diff --git a/Source/CMakeVersion.rc.in b/Source/CMakeVersion.rc.in
new file mode 100644
index 000000000..762d9bb62
--- /dev/null
+++ b/Source/CMakeVersion.rc.in
@@ -0,0 +1,28 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+
+1 VERSIONINFO
+FILEVERSION @CMake_RCVERSION@
+PRODUCTVERSION @CMake_RCVERSION@
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904E4"
+ BEGIN
+ VALUE "FileVersion", "@CMake_RCVERSION_STR@\0"
+ VALUE "ProductVersion", "@CMake_RCVERSION_STR@\0"
+ END
+ END
+
+ BLOCK "VarFileInfo"
+ BEGIN
+ /* The following line should only be modified for localized versions. */
+ /* It consists of any number of WORD,WORD pairs, with each pair */
+ /* describing a language,codepage combination supported by the file. */
+ /* */
+ /* For example, a file might have values "0x409,1252" indicating that it */
+ /* supports English language (0x409) in the Windows ANSI codepage (1252). */
+
+ VALUE "Translation", 0x409, 1252
+ END
+END
diff --git a/Source/CPack/WiX/cmCMakeToWixPath.cxx b/Source/CPack/WiX/cmCMakeToWixPath.cxx
new file mode 100644
index 000000000..b3889cf2e
--- /dev/null
+++ b/Source/CPack/WiX/cmCMakeToWixPath.cxx
@@ -0,0 +1,39 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmCMakeToWixPath.h"
+
+#include "cmSystemTools.h"
+
+#include <string>
+#include <vector>
+
+#ifdef __CYGWIN__
+# include <sys/cygwin.h>
+std::string CMakeToWixPath(const std::string& cygpath)
+{
+ std::vector<char> winpath_chars;
+ ssize_t winpath_size;
+
+ // Get the required buffer size.
+ winpath_size =
+ cygwin_conv_path(CCP_POSIX_TO_WIN_A, cygpath.c_str(), nullptr, 0);
+ if (winpath_size <= 0) {
+ return cygpath;
+ }
+
+ winpath_chars.assign(static_cast<size_t>(winpath_size) + 1, '\0');
+
+ winpath_size = cygwin_conv_path(CCP_POSIX_TO_WIN_A, cygpath.c_str(),
+ winpath_chars.data(), winpath_size);
+ if (winpath_size < 0) {
+ return cygpath;
+ }
+
+ return cmSystemTools::TrimWhitespace(winpath_chars.data());
+}
+#else
+std::string CMakeToWixPath(const std::string& path)
+{
+ return path;
+}
+#endif
diff --git a/Source/CPack/WiX/cmCMakeToWixPath.h b/Source/CPack/WiX/cmCMakeToWixPath.h
new file mode 100644
index 000000000..8bb9e0408
--- /dev/null
+++ b/Source/CPack/WiX/cmCMakeToWixPath.h
@@ -0,0 +1,12 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#ifndef cmCMakeToWixPath_h
+#define cmCMakeToWixPath_h
+
+#include "cmConfigure.h" //IWYU pragma: keep
+
+#include <string>
+
+std::string CMakeToWixPath(const std::string& cygpath);
+
+#endif // cmCMakeToWixPath_h
diff --git a/Source/CPack/cmCPackExternalGenerator.cxx b/Source/CPack/cmCPackExternalGenerator.cxx
new file mode 100644
index 000000000..9dc985331
--- /dev/null
+++ b/Source/CPack/cmCPackExternalGenerator.cxx
@@ -0,0 +1,323 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmCPackExternalGenerator.h"
+
+#include "cmAlgorithms.h"
+#include "cmCPackComponentGroup.h"
+#include "cmCPackLog.h"
+#include "cmMakefile.h"
+#include "cmSystemTools.h"
+
+#include "cm_jsoncpp_value.h"
+#include "cm_jsoncpp_writer.h"
+
+#include "cmsys/FStream.hxx"
+
+#include <map>
+#include <utility>
+#include <vector>
+
+int cmCPackExternalGenerator::InitializeInternal()
+{
+ this->SetOption("CPACK_EXTERNAL_KNOWN_VERSIONS", "1.0");
+
+ if (!this->ReadListFile("Internal/CPack/CPackExternal.cmake")) {
+ cmCPackLogger(cmCPackLog::LOG_ERROR,
+ "Error while executing CPackExternal.cmake" << std::endl);
+ return 0;
+ }
+
+ std::string major = this->GetOption("CPACK_EXTERNAL_SELECTED_MAJOR");
+ if (major == "1") {
+ this->Generator = cm::make_unique<cmCPackExternalVersion1Generator>(this);
+ }
+
+ return this->Superclass::InitializeInternal();
+}
+
+int cmCPackExternalGenerator::PackageFiles()
+{
+ Json::StreamWriterBuilder builder;
+ builder["indentation"] = " ";
+
+ std::string filename = "package.json";
+ if (!this->packageFileNames.empty()) {
+ filename = this->packageFileNames[0];
+ }
+
+ cmsys::ofstream fout(filename.c_str());
+ std::unique_ptr<Json::StreamWriter> jout(builder.newStreamWriter());
+
+ Json::Value root(Json::objectValue);
+
+ if (!this->Generator->WriteToJSON(root)) {
+ return 0;
+ }
+
+ if (jout->write(root, &fout)) {
+ return 0;
+ }
+
+ const char* packageScript = this->GetOption("CPACK_EXTERNAL_PACKAGE_SCRIPT");
+ if (packageScript && *packageScript) {
+ if (!cmSystemTools::FileIsFullPath(packageScript)) {
+ cmCPackLogger(
+ cmCPackLog::LOG_ERROR,
+ "CPACK_EXTERNAL_PACKAGE_SCRIPT does not contain a full file path"
+ << std::endl);
+ return 0;
+ }
+
+ bool res = this->MakefileMap->ReadListFile(packageScript);
+
+ if (cmSystemTools::GetErrorOccuredFlag() || !res) {
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+bool cmCPackExternalGenerator::SupportsComponentInstallation() const
+{
+ return true;
+}
+
+int cmCPackExternalGenerator::InstallProjectViaInstallCommands(
+ bool setDestDir, const std::string& tempInstallDirectory)
+{
+ if (this->StagingEnabled()) {
+ return cmCPackGenerator::InstallProjectViaInstallCommands(
+ setDestDir, tempInstallDirectory);
+ }
+
+ return 1;
+}
+
+int cmCPackExternalGenerator::InstallProjectViaInstallScript(
+ bool setDestDir, const std::string& tempInstallDirectory)
+{
+ if (this->StagingEnabled()) {
+ return cmCPackGenerator::InstallProjectViaInstallScript(
+ setDestDir, tempInstallDirectory);
+ }
+
+ return 1;
+}
+
+int cmCPackExternalGenerator::InstallProjectViaInstalledDirectories(
+ bool setDestDir, const std::string& tempInstallDirectory,
+ const mode_t* default_dir_mode)
+{
+ if (this->StagingEnabled()) {
+ return cmCPackGenerator::InstallProjectViaInstalledDirectories(
+ setDestDir, tempInstallDirectory, default_dir_mode);
+ }
+
+ return 1;
+}
+
+int cmCPackExternalGenerator::RunPreinstallTarget(
+ const std::string& installProjectName, const std::string& installDirectory,
+ cmGlobalGenerator* globalGenerator, const std::string& buildConfig)
+{
+ if (this->StagingEnabled()) {
+ return cmCPackGenerator::RunPreinstallTarget(
+ installProjectName, installDirectory, globalGenerator, buildConfig);
+ }
+
+ return 1;
+}
+
+int cmCPackExternalGenerator::InstallCMakeProject(
+ bool setDestDir, const std::string& installDirectory,
+ const std::string& baseTempInstallDirectory, const mode_t* default_dir_mode,
+ const std::string& component, bool componentInstall,
+ const std::string& installSubDirectory, const std::string& buildConfig,
+ std::string& absoluteDestFiles)
+{
+ if (this->StagingEnabled()) {
+ return cmCPackGenerator::InstallCMakeProject(
+ setDestDir, installDirectory, baseTempInstallDirectory, default_dir_mode,
+ component, componentInstall, installSubDirectory, buildConfig,
+ absoluteDestFiles);
+ }
+
+ return 1;
+}
+
+bool cmCPackExternalGenerator::StagingEnabled() const
+{
+ return !cmSystemTools::IsOff(
+ this->GetOption("CPACK_EXTERNAL_ENABLE_STAGING"));
+}
+
+cmCPackExternalGenerator::cmCPackExternalVersionGenerator::
+ cmCPackExternalVersionGenerator(cmCPackExternalGenerator* parent)
+ : Parent(parent)
+{
+}
+
+int cmCPackExternalGenerator::cmCPackExternalVersionGenerator::WriteVersion(
+ Json::Value& root)
+{
+ root["formatVersionMajor"] = this->GetVersionMajor();
+ root["formatVersionMinor"] = this->GetVersionMinor();
+
+ return 1;
+}
+
+int cmCPackExternalGenerator::cmCPackExternalVersionGenerator::WriteToJSON(
+ Json::Value& root)
+{
+ if (!this->WriteVersion(root)) {
+ return 0;
+ }
+
+ const char* packageName = this->Parent->GetOption("CPACK_PACKAGE_NAME");
+ if (packageName) {
+ root["packageName"] = packageName;
+ }
+
+ const char* packageVersion =
+ this->Parent->GetOption("CPACK_PACKAGE_VERSION");
+ if (packageVersion) {
+ root["packageVersion"] = packageVersion;
+ }
+
+ const char* packageDescriptionFile =
+ this->Parent->GetOption("CPACK_PACKAGE_DESCRIPTION_FILE");
+ if (packageDescriptionFile) {
+ root["packageDescriptionFile"] = packageDescriptionFile;
+ }
+
+ const char* packageDescriptionSummary =
+ this->Parent->GetOption("CPACK_PACKAGE_DESCRIPTION_SUMMARY");
+ if (packageDescriptionSummary) {
+ root["packageDescriptionSummary"] = packageDescriptionSummary;
+ }
+
+ const char* buildConfigCstr = this->Parent->GetOption("CPACK_BUILD_CONFIG");
+ if (buildConfigCstr) {
+ root["buildConfig"] = buildConfigCstr;
+ }
+
+ const char* defaultDirectoryPermissions =
+ this->Parent->GetOption("CPACK_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS");
+ if (defaultDirectoryPermissions && *defaultDirectoryPermissions) {
+ root["defaultDirectoryPermissions"] = defaultDirectoryPermissions;
+ }
+ if (cmSystemTools::IsInternallyOn(
+ this->Parent->GetOption("CPACK_SET_DESTDIR"))) {
+ root["setDestdir"] = true;
+ root["packagingInstallPrefix"] =
+ this->Parent->GetOption("CPACK_PACKAGING_INSTALL_PREFIX");
+ } else {
+ root["setDestdir"] = false;
+ }
+
+ root["stripFiles"] =
+ !cmSystemTools::IsOff(this->Parent->GetOption("CPACK_STRIP_FILES"));
+ root["warnOnAbsoluteInstallDestination"] =
+ this->Parent->IsOn("CPACK_WARN_ON_ABSOLUTE_INSTALL_DESTINATION");
+ root["errorOnAbsoluteInstallDestination"] =
+ this->Parent->IsOn("CPACK_ERROR_ON_ABSOLUTE_INSTALL_DESTINATION");
+
+ Json::Value& projects = root["projects"] = Json::Value(Json::arrayValue);
+ for (auto& project : this->Parent->CMakeProjects) {
+ Json::Value jsonProject(Json::objectValue);
+
+ jsonProject["projectName"] = project.ProjectName;
+ jsonProject["component"] = project.Component;
+ jsonProject["directory"] = project.Directory;
+ jsonProject["subDirectory"] = project.SubDirectory;
+
+ Json::Value& installationTypes = jsonProject["installationTypes"] =
+ Json::Value(Json::arrayValue);
+ for (auto& installationType : project.InstallationTypes) {
+ installationTypes.append(installationType->Name);
+ }
+
+ Json::Value& components = jsonProject["components"] =
+ Json::Value(Json::arrayValue);
+ for (auto& component : project.Components) {
+ components.append(component->Name);
+ }
+
+ projects.append(jsonProject);
+ }
+
+ Json::Value& installationTypes = root["installationTypes"] =
+ Json::Value(Json::objectValue);
+ for (auto& installationType : this->Parent->InstallationTypes) {
+ Json::Value& jsonInstallationType =
+ installationTypes[installationType.first] =
+ Json::Value(Json::objectValue);
+
+ jsonInstallationType["name"] = installationType.second.Name;
+ jsonInstallationType["displayName"] = installationType.second.DisplayName;
+ jsonInstallationType["index"] = installationType.second.Index;
+ }
+
+ Json::Value& components = root["components"] =
+ Json::Value(Json::objectValue);
+ for (auto& component : this->Parent->Components) {
+ Json::Value& jsonComponent = components[component.first] =
+ Json::Value(Json::objectValue);
+
+ jsonComponent["name"] = component.second.Name;
+ jsonComponent["displayName"] = component.second.DisplayName;
+ if (component.second.Group) {
+ jsonComponent["group"] = component.second.Group->Name;
+ }
+ jsonComponent["isRequired"] = component.second.IsRequired;
+ jsonComponent["isHidden"] = component.second.IsHidden;
+ jsonComponent["isDisabledByDefault"] =
+ component.second.IsDisabledByDefault;
+ jsonComponent["isDownloaded"] = component.second.IsDownloaded;
+ jsonComponent["description"] = component.second.Description;
+ jsonComponent["archiveFile"] = component.second.ArchiveFile;
+
+ Json::Value& cmpInstallationTypes = jsonComponent["installationTypes"] =
+ Json::Value(Json::arrayValue);
+ for (auto& installationType : component.second.InstallationTypes) {
+ cmpInstallationTypes.append(installationType->Name);
+ }
+
+ Json::Value& dependencies = jsonComponent["dependencies"] =
+ Json::Value(Json::arrayValue);
+ for (auto& dep : component.second.Dependencies) {
+ dependencies.append(dep->Name);
+ }
+ }
+
+ Json::Value& groups = root["componentGroups"] =
+ Json::Value(Json::objectValue);
+ for (auto& group : this->Parent->ComponentGroups) {
+ Json::Value& jsonGroup = groups[group.first] =
+ Json::Value(Json::objectValue);
+
+ jsonGroup["name"] = group.second.Name;
+ jsonGroup["displayName"] = group.second.DisplayName;
+ jsonGroup["description"] = group.second.Description;
+ jsonGroup["isBold"] = group.second.IsBold;
+ jsonGroup["isExpandedByDefault"] = group.second.IsExpandedByDefault;
+ if (group.second.ParentGroup) {
+ jsonGroup["parentGroup"] = group.second.ParentGroup->Name;
+ }
+
+ Json::Value& subgroups = jsonGroup["subgroups"] =
+ Json::Value(Json::arrayValue);
+ for (auto& subgroup : group.second.Subgroups) {
+ subgroups.append(subgroup->Name);
+ }
+
+ Json::Value& groupComponents = jsonGroup["components"] =
+ Json::Value(Json::arrayValue);
+ for (auto& component : group.second.Components) {
+ groupComponents.append(component->Name);
+ }
+ }
+
+ return 1;
+}
diff --git a/Source/CPack/cmCPackExternalGenerator.h b/Source/CPack/cmCPackExternalGenerator.h
new file mode 100644
index 000000000..176d6a9d9
--- /dev/null
+++ b/Source/CPack/cmCPackExternalGenerator.h
@@ -0,0 +1,89 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#ifndef cmCPackExternalGenerator_h
+#define cmCPackExternalGenerator_h
+
+#include "cmCPackGenerator.h"
+#include "cm_sys_stat.h"
+
+#include <memory>
+#include <string>
+
+class cmGlobalGenerator;
+namespace Json {
+class Value;
+}
+
+/** \class cmCPackExternalGenerator
+ * \brief A generator for CPack External packaging tools
+ */
+class cmCPackExternalGenerator : public cmCPackGenerator
+{
+public:
+ cmCPackTypeMacro(cmCPackExternalGenerator, cmCPackGenerator);
+
+ const char* GetOutputExtension() override { return ".json"; }
+
+protected:
+ int InitializeInternal() override;
+
+ int PackageFiles() override;
+
+ bool SupportsComponentInstallation() const override;
+
+ int InstallProjectViaInstallCommands(
+ bool setDestDir, const std::string& tempInstallDirectory) override;
+ int InstallProjectViaInstallScript(
+ bool setDestDir, const std::string& tempInstallDirectory) override;
+ int InstallProjectViaInstalledDirectories(
+ bool setDestDir, const std::string& tempInstallDirectory,
+ const mode_t* default_dir_mode) override;
+
+ int RunPreinstallTarget(const std::string& installProjectName,
+ const std::string& installDirectory,
+ cmGlobalGenerator* globalGenerator,
+ const std::string& buildConfig) override;
+ int InstallCMakeProject(bool setDestDir, const std::string& installDirectory,
+ const std::string& baseTempInstallDirectory,
+ const mode_t* default_dir_mode,
+ const std::string& component, bool componentInstall,
+ const std::string& installSubDirectory,
+ const std::string& buildConfig,
+ std::string& absoluteDestFiles) override;
+
+private:
+ bool StagingEnabled() const;
+
+ class cmCPackExternalVersionGenerator
+ {
+ public:
+ cmCPackExternalVersionGenerator(cmCPackExternalGenerator* parent);
+
+ virtual ~cmCPackExternalVersionGenerator() = default;
+
+ virtual int WriteToJSON(Json::Value& root);
+
+ protected:
+ virtual int GetVersionMajor() = 0;
+ virtual int GetVersionMinor() = 0;
+
+ int WriteVersion(Json::Value& root);
+
+ cmCPackExternalGenerator* Parent;
+ };
+
+ class cmCPackExternalVersion1Generator
+ : public cmCPackExternalVersionGenerator
+ {
+ public:
+ using cmCPackExternalVersionGenerator::cmCPackExternalVersionGenerator;
+
+ protected:
+ int GetVersionMajor() override { return 1; }
+ int GetVersionMinor() override { return 0; }
+ };
+
+ std::unique_ptr<cmCPackExternalVersionGenerator> Generator;
+};
+
+#endif
diff --git a/Source/CPack/cmCPackFreeBSDGenerator.cxx b/Source/CPack/cmCPackFreeBSDGenerator.cxx
new file mode 100644
index 000000000..9fdafa47b
--- /dev/null
+++ b/Source/CPack/cmCPackFreeBSDGenerator.cxx
@@ -0,0 +1,345 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmCPackFreeBSDGenerator.h"
+
+#include "cmArchiveWrite.h"
+#include "cmCPackArchiveGenerator.h"
+#include "cmCPackLog.h"
+#include "cmGeneratedFileStream.h"
+#include "cmSystemTools.h"
+
+// Needed for ::open() and ::stat()
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <pkg.h>
+
+#include <algorithm>
+#include <utility>
+
+cmCPackFreeBSDGenerator::cmCPackFreeBSDGenerator()
+ : cmCPackArchiveGenerator(cmArchiveWrite::CompressXZ, "paxr")
+{
+}
+
+int cmCPackFreeBSDGenerator::InitializeInternal()
+{
+ this->SetOptionIfNotSet("CPACK_PACKAGING_INSTALL_PREFIX", "/usr/local");
+ this->SetOption("CPACK_INCLUDE_TOPLEVEL_DIRECTORY", "0");
+ return this->Superclass::InitializeInternal();
+}
+
+cmCPackFreeBSDGenerator::~cmCPackFreeBSDGenerator() = default;
+
+// This is a wrapper, for use only in stream-based output,
+// that will output a string in UCL escaped fashion (in particular,
+// quotes and backslashes are escaped). The list of characters
+// to escape is taken from https://github.com/vstakhov/libucl
+// (which is the reference implementation pkg(8) refers to).
+class EscapeQuotes
+{
+public:
+ const std::string& value;
+
+ EscapeQuotes(const std::string& s)
+ : value(s)
+ {
+ }
+};
+
+// Output a string as "string" with escaping applied.
+cmGeneratedFileStream& operator<<(cmGeneratedFileStream& s,
+ const EscapeQuotes& v)
+{
+ s << '"';
+ for (char c : v.value) {
+ switch (c) {
+ case '\n':
+ s << "\\n";
+ break;
+ case '\r':
+ s << "\\r";
+ break;
+ case '\b':
+ s << "\\b";
+ break;
+ case '\t':
+ s << "\\t";
+ break;
+ case '\f':
+ s << "\\f";
+ break;
+ case '\\':
+ s << "\\\\";
+ break;
+ case '"':
+ s << "\\\"";
+ break;
+ default:
+ s << c;
+ break;
+ }
+ }
+ s << '"';
+ return s;
+}
+
+// The following classes are all helpers for writing out the UCL
+// manifest file (it also looks like JSON). ManifestKey just has
+// a (string-valued) key; subclasses add a specific kind of
+// value-type to the key, and implement write_value() to output
+// the corresponding UCL.
+class ManifestKey
+{
+public:
+ std::string key;
+
+ ManifestKey(std::string k)
+ : key(std::move(k))
+ {
+ }
+
+ virtual ~ManifestKey() = default;
+
+ // Output the value associated with this key to the stream @p s.
+ // Format is to be decided by subclasses.
+ virtual void write_value(cmGeneratedFileStream& s) const = 0;
+};
+
+// Basic string-value (e.g. "name": "cmake")
+class ManifestKeyValue : public ManifestKey
+{
+public:
+ std::string value;
+
+ ManifestKeyValue(const std::string& k, std::string v)
+ : ManifestKey(k)
+ , value(std::move(v))
+ {
+ }
+
+ void write_value(cmGeneratedFileStream& s) const override
+ {
+ s << EscapeQuotes(value);
+ }
+};
+
+// List-of-strings values (e.g. "licenses": ["GPLv2", "LGPLv2"])
+class ManifestKeyListValue : public ManifestKey
+{
+public:
+ typedef std::vector<std::string> VList;
+ VList value;
+
+ ManifestKeyListValue(const std::string& k)
+ : ManifestKey(k)
+ {
+ }
+
+ ManifestKeyListValue& operator<<(const std::string& v)
+ {
+ value.push_back(v);
+ return *this;
+ }
+
+ ManifestKeyListValue& operator<<(const std::vector<std::string>& v)
+ {
+ for (std::string const& e : v) {
+ (*this) << e;
+ }
+ return *this;
+ }
+
+ void write_value(cmGeneratedFileStream& s) const override
+ {
+ bool with_comma = false;
+
+ s << '[';
+ for (std::string const& elem : value) {
+ s << (with_comma ? ',' : ' ');
+ s << EscapeQuotes(elem);
+ with_comma = true;
+ }
+ s << " ]";
+ }
+};
+
+// Deps: actually a dictionary, but we'll treat it as a
+// list so we only name the deps, and produce dictionary-
+// like output via write_value()
+class ManifestKeyDepsValue : public ManifestKeyListValue
+{
+public:
+ ManifestKeyDepsValue(const std::string& k)
+ : ManifestKeyListValue(k)
+ {
+ }
+
+ void write_value(cmGeneratedFileStream& s) const override
+ {
+ s << "{\n";
+ for (std::string const& elem : value) {
+ s << " \"" << elem << R"(": {"origin": ")" << elem << "\"},\n";
+ }
+ s << '}';
+ }
+};
+
+// Write one of the key-value classes (above) to the stream @p s
+cmGeneratedFileStream& operator<<(cmGeneratedFileStream& s,
+ const ManifestKey& v)
+{
+ s << '"' << v.key << "\": ";
+ v.write_value(s);
+ s << ",\n";
+ return s;
+}
+
+// Look up variable; if no value is set, returns an empty string;
+// basically a wrapper that handles the NULL-ptr return from GetOption().
+std::string cmCPackFreeBSDGenerator::var_lookup(const char* var_name)
+{
+ const char* pv = this->GetOption(var_name);
+ if (!pv) {
+ return std::string();
+ }
+ return pv;
+}
+
+// Produce UCL in the given @p manifest file for the common
+// manifest fields (common to the compact and regular formats),
+// by reading the CPACK_FREEBSD_* variables.
+void cmCPackFreeBSDGenerator::write_manifest_fields(
+ cmGeneratedFileStream& manifest)
+{
+ manifest << ManifestKeyValue("name",
+ var_lookup("CPACK_FREEBSD_PACKAGE_NAME"));
+ manifest << ManifestKeyValue("origin",
+ var_lookup("CPACK_FREEBSD_PACKAGE_ORIGIN"));
+ manifest << ManifestKeyValue("version",
+ var_lookup("CPACK_FREEBSD_PACKAGE_VERSION"));
+ manifest << ManifestKeyValue("maintainer",
+ var_lookup("CPACK_FREEBSD_PACKAGE_MAINTAINER"));
+ manifest << ManifestKeyValue("comment",
+ var_lookup("CPACK_FREEBSD_PACKAGE_COMMENT"));
+ manifest << ManifestKeyValue(
+ "desc", var_lookup("CPACK_FREEBSD_PACKAGE_DESCRIPTION"));
+ manifest << ManifestKeyValue("www", var_lookup("CPACK_FREEBSD_PACKAGE_WWW"));
+ std::vector<std::string> licenses;
+ cmSystemTools::ExpandListArgument(
+ var_lookup("CPACK_FREEBSD_PACKAGE_LICENSE"), licenses);
+ std::string licenselogic("single");
+ if (licenses.empty()) {
+ cmSystemTools::SetFatalErrorOccured();
+ } else if (licenses.size() > 1) {
+ licenselogic = var_lookup("CPACK_FREEBSD_PACKAGE_LICENSE_LOGIC");
+ }
+ manifest << ManifestKeyValue("licenselogic", licenselogic);
+ manifest << (ManifestKeyListValue("licenses") << licenses);
+ std::vector<std::string> categories;
+ cmSystemTools::ExpandListArgument(
+ var_lookup("CPACK_FREEBSD_PACKAGE_CATEGORIES"), categories);
+ manifest << (ManifestKeyListValue("categories") << categories);
+ manifest << ManifestKeyValue("prefix", var_lookup("CMAKE_INSTALL_PREFIX"));
+ std::vector<std::string> deps;
+ cmSystemTools::ExpandListArgument(var_lookup("CPACK_FREEBSD_PACKAGE_DEPS"),
+ deps);
+ if (!deps.empty()) {
+ manifest << (ManifestKeyDepsValue("deps") << deps);
+ }
+}
+
+// Package only actual files; others are ignored (in particular,
+// intermediate subdirectories are ignored).
+static bool ignore_file(const std::string& filename)
+{
+ struct stat statbuf;
+ return stat(filename.c_str(), &statbuf) < 0 ||
+ (statbuf.st_mode & S_IFMT) != S_IFREG;
+}
+
+// Write the given list of @p files to the manifest stream @p s,
+// as the UCL field "files" (which is dictionary-valued, to
+// associate filenames with hashes). All the files are transformed
+// to paths relative to @p toplevel, with a leading / (since the paths
+// in FreeBSD package files are supposed to be absolute).
+void write_manifest_files(cmGeneratedFileStream& s,
+ const std::string& toplevel,
+ const std::vector<std::string>& files)
+{
+ s << "\"files\": {\n";
+ for (std::string const& file : files) {
+ s << " \"/" << cmSystemTools::RelativePath(toplevel, file) << "\": \""
+ << "<sha256>"
+ << "\",\n";
+ }
+ s << " },\n";
+}
+
+static bool has_suffix(const std::string& str, const std::string& suffix)
+{
+ return str.size() >= suffix.size() &&
+ str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0;
+}
+
+int cmCPackFreeBSDGenerator::PackageFiles()
+{
+ if (!this->ReadListFile("Internal/CPack/CPackFreeBSD.cmake")) {
+ cmCPackLogger(cmCPackLog::LOG_ERROR,
+ "Error while execution CPackFreeBSD.cmake" << std::endl);
+ return 0;
+ }
+
+ std::vector<std::string>::const_iterator fileIt;
+ std::string dir = cmSystemTools::GetCurrentWorkingDirectory();
+ cmSystemTools::ChangeDirectory(toplevel);
+
+ files.erase(std::remove_if(files.begin(), files.end(), ignore_file),
+ files.end());
+
+ std::string manifestname = toplevel + "/+MANIFEST";
+ {
+ cmGeneratedFileStream manifest(manifestname);
+ manifest << "{\n";
+ write_manifest_fields(manifest);
+ write_manifest_files(manifest, toplevel, files);
+ manifest << "}\n";
+ }
+
+ cmCPackLogger(cmCPackLog::LOG_DEBUG, "Toplevel: " << toplevel << std::endl);
+
+ if (WantsComponentInstallation()) {
+ // CASE 1 : COMPONENT ALL-IN-ONE package
+ // If ALL COMPONENTS in ONE package has been requested
+ // then the package file is unique and should be open here.
+ if (componentPackageMethod == ONE_PACKAGE) {
+ return PackageComponentsAllInOne();
+ }
+ // CASE 2 : COMPONENT CLASSICAL package(s) (i.e. not all-in-one)
+ // There will be 1 package for each component group
+ // however one may require to ignore component group and
+ // in this case you'll get 1 package for each component.
+ return PackageComponents(componentPackageMethod ==
+ ONE_PACKAGE_PER_COMPONENT);
+ }
+
+ std::string output_dir = cmSystemTools::CollapseFullPath("../", toplevel);
+ pkg_create_from_manifest(output_dir.c_str(), ::TXZ, toplevel.c_str(),
+ manifestname.c_str(), nullptr);
+
+ std::string broken_suffix = std::string("-") +
+ var_lookup("CPACK_TOPLEVEL_TAG") + std::string(GetOutputExtension());
+ for (std::string& name : packageFileNames) {
+ cmCPackLogger(cmCPackLog::LOG_DEBUG, "Packagefile " << name << std::endl);
+ if (has_suffix(name, broken_suffix)) {
+ name.replace(name.size() - broken_suffix.size(), std::string::npos,
+ GetOutputExtension());
+ break;
+ }
+ }
+
+ cmSystemTools::ChangeDirectory(dir);
+ return 1;
+}
diff --git a/Source/CPack/cmCPackFreeBSDGenerator.h b/Source/CPack/cmCPackFreeBSDGenerator.h
new file mode 100644
index 000000000..99d2e2421
--- /dev/null
+++ b/Source/CPack/cmCPackFreeBSDGenerator.h
@@ -0,0 +1,37 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#ifndef cmCPackFreeBSDGenerator_h
+#define cmCPackFreeBSDGenerator_h
+
+#include <cmConfigure.h>
+
+#include "cmCPackArchiveGenerator.h"
+#include "cmCPackGenerator.h"
+
+class cmGeneratedFileStream;
+
+/** \class cmCPackFreeBSDGenerator
+ * \brief A generator for FreeBSD package files (TXZ with a manifest)
+ *
+ */
+class cmCPackFreeBSDGenerator : public cmCPackArchiveGenerator
+{
+public:
+ cmCPackTypeMacro(cmCPackFreeBSDGenerator, cmCPackArchiveGenerator);
+ /**
+ * Construct generator
+ */
+ cmCPackFreeBSDGenerator();
+ ~cmCPackFreeBSDGenerator() override;
+
+ int InitializeInternal() override;
+ int PackageFiles() override;
+
+protected:
+ const char* GetOutputExtension() override { return ".txz"; }
+
+ std::string var_lookup(const char* var_name);
+ void write_manifest_fields(cmGeneratedFileStream&);
+};
+
+#endif
diff --git a/Source/CPack/cmCPackNuGetGenerator.cxx b/Source/CPack/cmCPackNuGetGenerator.cxx
new file mode 100644
index 000000000..76f06992b
--- /dev/null
+++ b/Source/CPack/cmCPackNuGetGenerator.cxx
@@ -0,0 +1,141 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmCPackNuGetGenerator.h"
+
+#include "cmAlgorithms.h"
+#include "cmCPackComponentGroup.h"
+#include "cmCPackLog.h"
+#include "cmSystemTools.h"
+
+#include <algorithm>
+#include <iterator>
+#include <map>
+#include <ostream>
+#include <string>
+#include <utility>
+#include <vector>
+
+bool cmCPackNuGetGenerator::SupportsComponentInstallation() const
+{
+ return IsOn("CPACK_NUGET_COMPONENT_INSTALL");
+}
+
+int cmCPackNuGetGenerator::PackageFiles()
+{
+ cmCPackLogger(cmCPackLog::LOG_DEBUG, "Toplevel: " << toplevel << std::endl);
+
+ /* Reset package file name list it will be populated after the
+ * `CPackNuGet.cmake` run */
+ packageFileNames.clear();
+
+ /* Are we in the component packaging case */
+ if (WantsComponentInstallation()) {
+ if (componentPackageMethod == ONE_PACKAGE) {
+ // CASE 1 : COMPONENT ALL-IN-ONE package
+ // Meaning that all per-component pre-installed files
+ // goes into the single package.
+ this->SetOption("CPACK_NUGET_ALL_IN_ONE", "TRUE");
+ SetupGroupComponentVariables(true);
+ } else {
+ // CASE 2 : COMPONENT CLASSICAL package(s) (i.e. not all-in-one)
+ // There will be 1 package for each component group
+ // however one may require to ignore component group and
+ // in this case you'll get 1 package for each component.
+ SetupGroupComponentVariables(componentPackageMethod ==
+ ONE_PACKAGE_PER_COMPONENT);
+ }
+ } else {
+ // CASE 3 : NON COMPONENT package.
+ this->SetOption("CPACK_NUGET_ORDINAL_MONOLITIC", "TRUE");
+ }
+
+ auto retval = this->ReadListFile("Internal/CPack/CPackNuGet.cmake");
+ if (retval) {
+ AddGeneratedPackageNames();
+ } else {
+ cmCPackLogger(cmCPackLog::LOG_ERROR,
+ "Error while execution CPackNuGet.cmake" << std::endl);
+ }
+
+ return retval;
+}
+
+void cmCPackNuGetGenerator::SetupGroupComponentVariables(bool ignoreGroup)
+{
+ // The default behavior is to have one package by component group
+ // unless CPACK_COMPONENTS_IGNORE_GROUP is specified.
+ if (!ignoreGroup) {
+ std::vector<std::string> groups;
+ for (auto const& compG : this->ComponentGroups) {
+ cmCPackLogger(cmCPackLog::LOG_VERBOSE,
+ "Packaging component group: " << compG.first << std::endl);
+ groups.push_back(compG.first);
+ auto compGUp =
+ cmSystemTools::UpperCase(cmSystemTools::MakeCidentifier(compG.first));
+
+ // Collect components for this group
+ std::vector<std::string> components;
+ std::transform(begin(compG.second.Components),
+ end(compG.second.Components),
+ std::back_inserter(components),
+ [](cmCPackComponent const* comp) { return comp->Name; });
+ this->SetOption("CPACK_NUGET_" + compGUp + "_GROUP_COMPONENTS",
+ cmJoin(components, ";").c_str());
+ }
+ if (!groups.empty()) {
+ this->SetOption("CPACK_NUGET_GROUPS", cmJoin(groups, ";").c_str());
+ }
+
+ // Handle Orphan components (components not belonging to any groups)
+ std::vector<std::string> components;
+ for (auto const& comp : this->Components) {
+ // Does the component belong to a group?
+ if (comp.second.Group == nullptr) {
+ cmCPackLogger(
+ cmCPackLog::LOG_VERBOSE,
+ "Component <"
+ << comp.second.Name
+ << "> does not belong to any group, package it separately."
+ << std::endl);
+ components.push_back(comp.first);
+ }
+ }
+ if (!components.empty()) {
+ this->SetOption("CPACK_NUGET_COMPONENTS",
+ cmJoin(components, ";").c_str());
+ }
+
+ } else {
+ std::vector<std::string> components;
+ components.reserve(this->Components.size());
+ std::transform(begin(this->Components), end(this->Components),
+ std::back_inserter(components),
+ [](std::pair<std::string, cmCPackComponent> const& comp) {
+ return comp.first;
+ });
+ this->SetOption("CPACK_NUGET_COMPONENTS", cmJoin(components, ";").c_str());
+ }
+}
+
+void cmCPackNuGetGenerator::AddGeneratedPackageNames()
+{
+ const char* const files_list = this->GetOption("GEN_CPACK_OUTPUT_FILES");
+ if (!files_list) {
+ cmCPackLogger(
+ cmCPackLog::LOG_ERROR,
+ "Error while execution CPackNuGet.cmake: No NuGet package has generated"
+ << std::endl);
+ return;
+ }
+ // add the generated packages to package file names list
+ std::string fileNames{ files_list };
+ const char sep = ';';
+ std::string::size_type pos1 = 0;
+ std::string::size_type pos2 = fileNames.find(sep, pos1 + 1);
+ while (pos2 != std::string::npos) {
+ packageFileNames.push_back(fileNames.substr(pos1, pos2 - pos1));
+ pos1 = pos2 + 1;
+ pos2 = fileNames.find(sep, pos1 + 1);
+ }
+ packageFileNames.push_back(fileNames.substr(pos1, pos2 - pos1));
+}
diff --git a/Source/CPack/cmCPackNuGetGenerator.h b/Source/CPack/cmCPackNuGetGenerator.h
new file mode 100644
index 000000000..a59db2d04
--- /dev/null
+++ b/Source/CPack/cmCPackNuGetGenerator.h
@@ -0,0 +1,37 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#ifndef cmCPackNuGetGenerator_h
+#define cmCPackNuGetGenerator_h
+
+#include "cmCPackGenerator.h"
+
+/** \class cmCPackNuGetGenerator
+ * \brief A generator for RPM packages
+ */
+class cmCPackNuGetGenerator : public cmCPackGenerator
+{
+public:
+ cmCPackTypeMacro(cmCPackNuGetGenerator, cmCPackGenerator);
+
+ // NOTE In fact, it is possible to have NuGet not only for Windows...
+ // https://docs.microsoft.com/en-us/nuget/install-nuget-client-tools
+ static bool CanGenerate() { return true; }
+
+protected:
+ bool SupportsComponentInstallation() const override;
+ int PackageFiles() override;
+
+ const char* GetOutputExtension() override { return ".nupkg"; }
+ bool SupportsAbsoluteDestination() const override { return false; }
+ /**
+ * The method used to prepare variables when component
+ * install is used.
+ */
+ void SetupGroupComponentVariables(bool ignoreGroup);
+ /**
+ * Populate \c packageFileNames vector of built packages.
+ */
+ void AddGeneratedPackageNames();
+};
+
+#endif
diff --git a/Source/Checks/Curses.cmake b/Source/Checks/Curses.cmake
new file mode 100644
index 000000000..2942b666a
--- /dev/null
+++ b/Source/Checks/Curses.cmake
@@ -0,0 +1,41 @@
+message(STATUS "Checking for curses support")
+
+# Try compiling a simple project using curses.
+# Pass in any cache entries that the user may have set.
+set(CMakeCheckCurses_ARGS "")
+foreach(v
+ CURSES_INCLUDE_PATH
+ CURSES_CURSES_LIBRARY
+ CURSES_NCURSES_LIBRARY
+ CURSES_EXTRA_LIBRARY
+ CURSES_FORM_LIBRARY
+ )
+ if(${v})
+ list(APPEND CMakeCheckCurses_ARGS -D${v}=${${v}})
+ endif()
+endforeach()
+file(REMOVE_RECURSE "${CMake_BINARY_DIR}/Source/Checks/Curses-build")
+try_compile(CMakeCheckCurses_COMPILED
+ ${CMake_BINARY_DIR}/Source/Checks/Curses-build
+ ${CMake_SOURCE_DIR}/Source/Checks/Curses
+ CheckCurses # project name
+ CheckCurses # target name
+ CMAKE_FLAGS
+ "-DCMAKE_C_FLAGS:STRING=${CMAKE_C_FLAGS}"
+ ${CMakeCheckCurses_ARGS}
+ OUTPUT_VARIABLE CMakeCheckCurses_OUTPUT
+ )
+
+# Convert result from cache entry to normal variable.
+set(CMakeCheckCurses_COMPILED "${CMakeCheckCurses_COMPILED}")
+unset(CMakeCheckCurses_COMPILED CACHE)
+
+if(CMakeCheckCurses_COMPILED)
+ message(STATUS "Checking for curses support - Success")
+ file(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeOutput.log
+ "Checking for curses support passed with the following output:\n${CMakeCheckCurses_OUTPUT}\n\n")
+else()
+ message(STATUS "Checking for curses support - Failed")
+ file(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeError.log
+ "Checking for curses support failed with the following output:\n${CMakeCheckCurses_OUTPUT}\n\n")
+endif()
diff --git a/Source/Checks/Curses/CMakeLists.txt b/Source/Checks/Curses/CMakeLists.txt
new file mode 100644
index 000000000..17318a33b
--- /dev/null
+++ b/Source/Checks/Curses/CMakeLists.txt
@@ -0,0 +1,25 @@
+cmake_minimum_required(VERSION 3.1)
+if(POLICY CMP0060)
+ cmake_policy(SET CMP0060 NEW)
+endif()
+project(CheckCurses C)
+
+set(CURSES_NEED_NCURSES TRUE)
+find_package(Curses)
+if(NOT CURSES_FOUND)
+ return()
+endif()
+include_directories(${CURSES_INCLUDE_DIRS})
+add_executable(CheckCurses CheckCurses.c)
+target_link_libraries(CheckCurses ${CURSES_LIBRARIES})
+
+foreach(h
+ CURSES_HAVE_CURSES_H
+ CURSES_HAVE_NCURSES_H
+ CURSES_HAVE_NCURSES_NCURSES_H
+ CURSES_HAVE_NCURSES_CURSES_H
+ )
+ if(${h})
+ target_compile_definitions(CheckCurses PRIVATE ${h})
+ endif()
+endforeach()
diff --git a/Source/Checks/Curses/CheckCurses.c b/Source/Checks/Curses/CheckCurses.c
new file mode 100644
index 000000000..7d827e6bf
--- /dev/null
+++ b/Source/Checks/Curses/CheckCurses.c
@@ -0,0 +1,15 @@
+#if defined(CURSES_HAVE_NCURSES_H)
+# include <ncurses.h>
+#elif defined(CURSES_HAVE_NCURSES_NCURSES_H)
+# include <ncurses/ncurses.h>
+#elif defined(CURSES_HAVE_NCURSES_CURSES_H)
+# include <ncurses/curses.h>
+#else
+# include <curses.h>
+#endif
+
+int main()
+{
+ curses_version();
+ return 0;
+}
diff --git a/Source/Checks/cm_cxx14_check.cmake b/Source/Checks/cm_cxx14_check.cmake
new file mode 100644
index 000000000..38606b9c9
--- /dev/null
+++ b/Source/Checks/cm_cxx14_check.cmake
@@ -0,0 +1,36 @@
+set(CMake_CXX14_BROKEN 0)
+if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|PGI")
+ if(NOT CMAKE_CXX14_STANDARD_COMPILE_OPTION)
+ set(CMake_CXX14_WORKS 0)
+ endif()
+ if(NOT DEFINED CMake_CXX14_WORKS)
+ message(STATUS "Checking if compiler supports needed C++14 constructs")
+ try_compile(CMake_CXX14_WORKS
+ ${CMAKE_CURRENT_BINARY_DIR}
+ ${CMAKE_CURRENT_LIST_DIR}/cm_cxx14_check.cpp
+ CMAKE_FLAGS -DCMAKE_CXX_STANDARD=14
+ OUTPUT_VARIABLE OUTPUT
+ )
+ if(CMake_CXX14_WORKS AND "${OUTPUT}" MATCHES "error: no member named.*gets.*in the global namespace")
+ set_property(CACHE CMake_CXX14_WORKS PROPERTY VALUE 0)
+ endif()
+ if(CMake_CXX14_WORKS)
+ message(STATUS "Checking if compiler supports needed C++14 constructs - yes")
+ file(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeOutput.log
+ "Determining if compiler supports needed C++14 constructs passed with the following output:\n"
+ "${OUTPUT}\n"
+ "\n"
+ )
+ else()
+ message(STATUS "Checking if compiler supports needed C++14 constructs - no")
+ file(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeError.log
+ "Determining if compiler supports needed C++14 constructs failed with the following output:\n"
+ "${OUTPUT}\n"
+ "\n"
+ )
+ endif()
+ endif()
+ if(NOT CMake_CXX14_WORKS)
+ set(CMake_CXX14_BROKEN 1)
+ endif()
+endif()
diff --git a/Source/Checks/cm_cxx14_check.cpp b/Source/Checks/cm_cxx14_check.cpp
new file mode 100644
index 000000000..fff36c916
--- /dev/null
+++ b/Source/Checks/cm_cxx14_check.cpp
@@ -0,0 +1,15 @@
+#include <cstdio>
+#include <iterator>
+#include <memory>
+
+int main()
+{
+ int a[] = { 0, 1, 2 };
+ auto ai = std::cbegin(a);
+
+ int b[] = { 2, 1, 0 };
+ auto bi = std::cend(b);
+
+ std::unique_ptr<int> u(new int(0));
+ return *u + *ai + *(bi - 1);
+}
diff --git a/Source/Checks/cm_cxx17_check.cmake b/Source/Checks/cm_cxx17_check.cmake
new file mode 100644
index 000000000..4da2fd7ca
--- /dev/null
+++ b/Source/Checks/cm_cxx17_check.cmake
@@ -0,0 +1,36 @@
+set(CMake_CXX17_BROKEN 0)
+if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|PGI")
+ if(NOT CMAKE_CXX17_STANDARD_COMPILE_OPTION)
+ set(CMake_CXX17_WORKS 0)
+ endif()
+ if(NOT DEFINED CMake_CXX17_WORKS)
+ message(STATUS "Checking if compiler supports needed C++17 constructs")
+ try_compile(CMake_CXX17_WORKS
+ ${CMAKE_CURRENT_BINARY_DIR}
+ ${CMAKE_CURRENT_LIST_DIR}/cm_cxx17_check.cpp
+ CMAKE_FLAGS -DCMAKE_CXX_STANDARD=17
+ OUTPUT_VARIABLE OUTPUT
+ )
+ if(CMake_CXX17_WORKS AND "${OUTPUT}" MATCHES "error: no member named.*gets.*in the global namespace")
+ set_property(CACHE CMake_CXX17_WORKS PROPERTY VALUE 0)
+ endif()
+ if(CMake_CXX17_WORKS)
+ message(STATUS "Checking if compiler supports needed C++17 constructs - yes")
+ file(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeOutput.log
+ "Determining if compiler supports needed C++17 constructs passed with the following output:\n"
+ "${OUTPUT}\n"
+ "\n"
+ )
+ else()
+ message(STATUS "Checking if compiler supports needed C++17 constructs - no")
+ file(APPEND ${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeError.log
+ "Determining if compiler supports needed C++17 constructs failed with the following output:\n"
+ "${OUTPUT}\n"
+ "\n"
+ )
+ endif()
+ endif()
+ if(NOT CMake_CXX17_WORKS)
+ set(CMake_CXX17_BROKEN 1)
+ endif()
+endif()
diff --git a/Source/Checks/cm_cxx17_check.cpp b/Source/Checks/cm_cxx17_check.cpp
new file mode 100644
index 000000000..593d9b263
--- /dev/null
+++ b/Source/Checks/cm_cxx17_check.cpp
@@ -0,0 +1,31 @@
+#include <cstdio>
+#include <iterator>
+#include <memory>
+#include <unordered_map>
+
+#ifdef _MSC_VER
+# include <comdef.h>
+#endif
+
+int main()
+{
+ int a[] = { 0, 1, 2 };
+ auto ai = std::cbegin(a);
+
+ int b[] = { 2, 1, 0 };
+ auto bi = std::cend(b);
+
+ auto ci = std::size(a);
+
+ std::unique_ptr<int> u(new int(0));
+
+#ifdef _MSC_VER
+ // clang-cl has problems instantiating this constructor in C++17 mode
+ // error: indirection requires pointer operand ('const _GUID' invalid)
+ // return *_IID;
+ IUnknownPtr ptr{};
+ IDispatchPtr disp(ptr);
+#endif
+
+ return *u + *ai + *(bi - 1) + (3 - static_cast<int>(ci));
+}
diff --git a/Source/Modules/CheckCXXLinkerFlag.cmake b/Source/Modules/CheckCXXLinkerFlag.cmake
new file mode 100644
index 000000000..9ad2ad630
--- /dev/null
+++ b/Source/Modules/CheckCXXLinkerFlag.cmake
@@ -0,0 +1,25 @@
+# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+# file Copyright.txt or https://cmake.org/licensing for details.
+
+include_guard(GLOBAL)
+include(CheckCXXSourceCompiles)
+include(CMakeCheckCompilerFlagCommonPatterns)
+
+function(check_cxx_linker_flag _flag _var)
+ set(CMAKE_REQUIRED_LIBRARIES "${_flag}")
+
+ # Normalize locale during test compilation.
+ set(_locale_vars LC_ALL LC_MESSAGES LANG)
+ foreach(v IN LISTS _locale_vars)
+ set(_locale_vars_saved_${v} "$ENV{${v}}")
+ set(ENV{${v}} C)
+ endforeach()
+ check_compiler_flag_common_patterns(_common_patterns)
+ check_cxx_source_compiles("int main() { return 0; }" ${_var}
+ ${_common_patterns}
+ )
+ foreach(v IN LISTS _locale_vars)
+ set(ENV{${v}} ${_locale_vars_saved_${v}})
+ endforeach()
+ set(${_var} "${${_var}}" PARENT_SCOPE)
+endfunction()
diff --git a/Source/Modules/FindLibUUID.cmake b/Source/Modules/FindLibUUID.cmake
new file mode 100644
index 000000000..17f11c1f5
--- /dev/null
+++ b/Source/Modules/FindLibUUID.cmake
@@ -0,0 +1,85 @@
+# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+# file Copyright.txt or https://cmake.org/licensing for details.
+
+#[=======================================================================[.rst:
+FindLibUUID
+------------
+
+Find LibUUID include directory and library.
+
+Imported Targets
+^^^^^^^^^^^^^^^^
+
+An :ref:`imported target <Imported targets>` named
+``LibUUID::LibUUID`` is provided if LibUUID has been found.
+
+Result Variables
+^^^^^^^^^^^^^^^^
+
+This module defines the following variables:
+
+``LibUUID_FOUND``
+ True if LibUUID was found, false otherwise.
+``LibUUID_INCLUDE_DIRS``
+ Include directories needed to include LibUUID headers.
+``LibUUID_LIBRARIES``
+ Libraries needed to link to LibUUID.
+
+Cache Variables
+^^^^^^^^^^^^^^^
+
+This module uses the following cache variables:
+
+``LibUUID_LIBRARY``
+ The location of the LibUUID library file.
+``LibUUID_INCLUDE_DIR``
+ The location of the LibUUID include directory containing ``uuid/uuid.h``.
+
+The cache variables should not be used by project code.
+They may be set by end users to point at LibUUID components.
+#]=======================================================================]
+
+#-----------------------------------------------------------------------------
+if(CYGWIN)
+ # Note: on current version of Cygwin, linking to libuuid.dll.a doesn't
+ # import the right symbols sometimes. Fix this by linking directly
+ # to the DLL that provides the symbols, instead.
+ set(old_suffixes ${CMAKE_FIND_LIBRARY_SUFFIXES})
+ set(CMAKE_FIND_LIBRARY_SUFFIXES .dll)
+ find_library(LibUUID_LIBRARY
+ NAMES cyguuid-1.dll
+ )
+ set(CMAKE_FIND_LIBRARY_SUFFIXES ${old_suffixes})
+else()
+ find_library(LibUUID_LIBRARY
+ NAMES uuid
+ )
+endif()
+mark_as_advanced(LibUUID_LIBRARY)
+
+find_path(LibUUID_INCLUDE_DIR
+ NAMES uuid/uuid.h
+ )
+mark_as_advanced(LibUUID_INCLUDE_DIR)
+
+#-----------------------------------------------------------------------------
+include(${CMAKE_CURRENT_LIST_DIR}/../../Modules/FindPackageHandleStandardArgs.cmake)
+FIND_PACKAGE_HANDLE_STANDARD_ARGS(LibUUID
+ FOUND_VAR LibUUID_FOUND
+ REQUIRED_VARS LibUUID_LIBRARY LibUUID_INCLUDE_DIR
+ )
+set(LIBUUID_FOUND ${LibUUID_FOUND})
+
+#-----------------------------------------------------------------------------
+# Provide documented result variables and targets.
+if(LibUUID_FOUND)
+ set(LibUUID_INCLUDE_DIRS ${LibUUID_INCLUDE_DIR})
+ set(LibUUID_LIBRARIES ${LibUUID_LIBRARY})
+ if(NOT TARGET LibUUID::LibUUID)
+ add_library(LibUUID::LibUUID UNKNOWN IMPORTED)
+ set_target_properties(LibUUID::LibUUID PROPERTIES
+ IMPORTED_LOCATION "${LibUUID_LIBRARY}"
+ INTERFACE_INCLUDE_DIRECTORIES "${LibUUID_INCLUDE_DIRS}"
+ )
+ endif()
+endif()
diff --git a/Source/Modules/OverrideC.cmake b/Source/Modules/OverrideC.cmake
new file mode 100644
index 000000000..f8299adb0
--- /dev/null
+++ b/Source/Modules/OverrideC.cmake
@@ -0,0 +1,3 @@
+if("${CMAKE_SYSTEM_NAME};${CMAKE_C_COMPILER_ID}" STREQUAL "AIX;GNU")
+ string(APPEND CMAKE_C_FLAGS_INIT " -pthread")
+endif()
diff --git a/Source/Modules/OverrideCXX.cmake b/Source/Modules/OverrideCXX.cmake
new file mode 100644
index 000000000..13689e2c5
--- /dev/null
+++ b/Source/Modules/OverrideCXX.cmake
@@ -0,0 +1,3 @@
+if("${CMAKE_SYSTEM_NAME};${CMAKE_CXX_COMPILER_ID}" STREQUAL "AIX;GNU")
+ string(APPEND CMAKE_CXX_FLAGS_INIT " -pthread")
+endif()
diff --git a/Source/cmAddCompileDefinitionsCommand.cxx b/Source/cmAddCompileDefinitionsCommand.cxx
new file mode 100644
index 000000000..04748192a
--- /dev/null
+++ b/Source/cmAddCompileDefinitionsCommand.cxx
@@ -0,0 +1,20 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmAddCompileDefinitionsCommand.h"
+
+#include "cmMakefile.h"
+
+class cmExecutionStatus;
+
+bool cmAddCompileDefinitionsCommand::InitialPass(
+ std::vector<std::string> const& args, cmExecutionStatus&)
+{
+ if (args.empty()) {
+ return true;
+ }
+
+ for (std::string const& i : args) {
+ this->Makefile->AddCompileDefinition(i);
+ }
+ return true;
+}
diff --git a/Source/cmAddCompileDefinitionsCommand.h b/Source/cmAddCompileDefinitionsCommand.h
new file mode 100644
index 000000000..e985dca8f
--- /dev/null
+++ b/Source/cmAddCompileDefinitionsCommand.h
@@ -0,0 +1,31 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#ifndef cmAddCompileDefinitionsCommand_h
+#define cmAddCompileDefinitionsCommand_h
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <string>
+#include <vector>
+
+#include "cmCommand.h"
+
+class cmExecutionStatus;
+
+class cmAddCompileDefinitionsCommand : public cmCommand
+{
+public:
+ /**
+ * This is a virtual constructor for the command.
+ */
+ cmCommand* Clone() override { return new cmAddCompileDefinitionsCommand; }
+
+ /**
+ * This is called when the command is first encountered in
+ * the CMakeLists.txt file.
+ */
+ bool InitialPass(std::vector<std::string> const& args,
+ cmExecutionStatus& status) override;
+};
+
+#endif
diff --git a/Source/cmAddLinkOptionsCommand.cxx b/Source/cmAddLinkOptionsCommand.cxx
new file mode 100644
index 000000000..10ebd1284
--- /dev/null
+++ b/Source/cmAddLinkOptionsCommand.cxx
@@ -0,0 +1,20 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmAddLinkOptionsCommand.h"
+
+#include "cmMakefile.h"
+
+class cmExecutionStatus;
+
+bool cmAddLinkOptionsCommand::InitialPass(std::vector<std::string> const& args,
+ cmExecutionStatus&)
+{
+ if (args.empty()) {
+ return true;
+ }
+
+ for (std::string const& i : args) {
+ this->Makefile->AddLinkOption(i);
+ }
+ return true;
+}
diff --git a/Source/cmAddLinkOptionsCommand.h b/Source/cmAddLinkOptionsCommand.h
new file mode 100644
index 000000000..30fff0058
--- /dev/null
+++ b/Source/cmAddLinkOptionsCommand.h
@@ -0,0 +1,31 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#ifndef cmAddLinkOptionsCommand_h
+#define cmAddLinkOptionsCommand_h
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <string>
+#include <vector>
+
+#include "cmCommand.h"
+
+class cmExecutionStatus;
+
+class cmAddLinkOptionsCommand : public cmCommand
+{
+public:
+ /**
+ * This is a virtual constructor for the command.
+ */
+ cmCommand* Clone() override { return new cmAddLinkOptionsCommand; }
+
+ /**
+ * This is called when the command is first encountered in
+ * the CMakeLists.txt file.
+ */
+ bool InitialPass(std::vector<std::string> const& args,
+ cmExecutionStatus& status) override;
+};
+
+#endif
diff --git a/Source/cmAffinity.cxx b/Source/cmAffinity.cxx
new file mode 100644
index 000000000..09b029866
--- /dev/null
+++ b/Source/cmAffinity.cxx
@@ -0,0 +1,64 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmAffinity.h"
+
+#include "cm_uv.h"
+
+#ifndef CMAKE_USE_SYSTEM_LIBUV
+# ifdef _WIN32
+# define CM_HAVE_CPU_AFFINITY
+# include <windows.h>
+# elif defined(__linux__) || defined(__FreeBSD__)
+# define CM_HAVE_CPU_AFFINITY
+# include <pthread.h>
+# include <sched.h>
+// On some platforms CPU_ZERO needs memset but sched.h forgets string.h
+# include <string.h> // IWYU pragma: keep
+# if defined(__FreeBSD__)
+# include <pthread_np.h>
+# include <sys/cpuset.h>
+# include <sys/param.h>
+# endif
+# if defined(__linux__)
+typedef cpu_set_t cm_cpuset_t;
+# else
+typedef cpuset_t cm_cpuset_t;
+# endif
+# endif
+#endif
+
+namespace cmAffinity {
+
+std::set<size_t> GetProcessorsAvailable()
+{
+ std::set<size_t> processorsAvailable;
+#ifdef CM_HAVE_CPU_AFFINITY
+ int cpumask_size = uv_cpumask_size();
+ if (cpumask_size > 0) {
+# ifdef _WIN32
+ DWORD_PTR procmask;
+ DWORD_PTR sysmask;
+ if (GetProcessAffinityMask(GetCurrentProcess(), &procmask, &sysmask) !=
+ 0) {
+ for (int i = 0; i < cpumask_size; ++i) {
+ if (procmask & (((DWORD_PTR)1) << i)) {
+ processorsAvailable.insert(i);
+ }
+ }
+ }
+# else
+ cm_cpuset_t cpuset;
+ CPU_ZERO(&cpuset); // NOLINT(clang-tidy)
+ if (pthread_getaffinity_np(pthread_self(), sizeof(cpuset), &cpuset) == 0) {
+ for (int i = 0; i < cpumask_size; ++i) {
+ if (CPU_ISSET(i, &cpuset)) {
+ processorsAvailable.insert(i);
+ }
+ }
+ }
+# endif
+ }
+#endif
+ return processorsAvailable;
+}
+}
diff --git a/Source/cmAffinity.h b/Source/cmAffinity.h
new file mode 100644
index 000000000..3775bae40
--- /dev/null
+++ b/Source/cmAffinity.h
@@ -0,0 +1,12 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#pragma once
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <cstddef>
+#include <set>
+
+namespace cmAffinity {
+
+std::set<size_t> GetProcessorsAvailable();
+}
diff --git a/Source/cmArgumentParser.cxx b/Source/cmArgumentParser.cxx
new file mode 100644
index 000000000..751d117f4
--- /dev/null
+++ b/Source/cmArgumentParser.cxx
@@ -0,0 +1,93 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmArgumentParser.h"
+
+#include <algorithm>
+#include <type_traits>
+
+namespace ArgumentParser {
+
+auto ActionMap::Emplace(cm::string_view name, Action action)
+ -> std::pair<iterator, bool>
+{
+ auto const it =
+ std::lower_bound(this->begin(), this->end(), name,
+ [](value_type const& elem, cm::string_view const& k) {
+ return elem.first < k;
+ });
+ return (it != this->end() && it->first == name)
+ ? std::make_pair(it, false)
+ : std::make_pair(this->emplace(it, name, std::move(action)), true);
+}
+
+auto ActionMap::Find(cm::string_view name) const -> const_iterator
+{
+ auto const it =
+ std::lower_bound(this->begin(), this->end(), name,
+ [](value_type const& elem, cm::string_view const& k) {
+ return elem.first < k;
+ });
+ return (it != this->end() && it->first == name) ? it : this->end();
+}
+
+void Instance::Bind(bool& val)
+{
+ val = true;
+ this->CurrentString = nullptr;
+ this->CurrentList = nullptr;
+ this->ExpectValue = false;
+}
+
+void Instance::Bind(std::string& val)
+{
+ this->CurrentString = &val;
+ this->CurrentList = nullptr;
+ this->ExpectValue = true;
+}
+
+void Instance::Bind(StringList& val)
+{
+ this->CurrentString = nullptr;
+ this->CurrentList = &val;
+ this->ExpectValue = true;
+}
+
+void Instance::Bind(MultiStringList& val)
+{
+ this->CurrentString = nullptr;
+ this->CurrentList = (static_cast<void>(val.emplace_back()), &val.back());
+ this->ExpectValue = false;
+}
+
+void Instance::Consume(cm::string_view arg, void* result,
+ std::vector<std::string>* unparsedArguments,
+ std::vector<std::string>* keywordsMissingValue)
+{
+ auto const it = this->Bindings.Find(arg);
+ if (it != this->Bindings.end()) {
+ it->second(*this, result);
+ if (this->ExpectValue && keywordsMissingValue != nullptr) {
+ keywordsMissingValue->emplace_back(arg);
+ }
+ return;
+ }
+
+ if (this->CurrentString != nullptr) {
+ this->CurrentString->assign(std::string(arg));
+ this->CurrentString = nullptr;
+ this->CurrentList = nullptr;
+ } else if (this->CurrentList != nullptr) {
+ this->CurrentList->emplace_back(arg);
+ } else if (unparsedArguments != nullptr) {
+ unparsedArguments->emplace_back(arg);
+ }
+
+ if (this->ExpectValue) {
+ if (keywordsMissingValue != nullptr) {
+ keywordsMissingValue->pop_back();
+ }
+ this->ExpectValue = false;
+ }
+}
+
+} // namespace ArgumentParser
diff --git a/Source/cmArgumentParser.h b/Source/cmArgumentParser.h
new file mode 100644
index 000000000..6cfe94633
--- /dev/null
+++ b/Source/cmArgumentParser.h
@@ -0,0 +1,143 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#ifndef cmArgumentParser_h
+#define cmArgumentParser_h
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include "cm_static_string_view.hxx"
+#include "cm_string_view.hxx"
+
+#include <cassert>
+#include <functional>
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace ArgumentParser {
+
+using StringList = std::vector<std::string>;
+using MultiStringList = std::vector<StringList>;
+
+class Instance;
+using Action = std::function<void(Instance&, void*)>;
+
+// using ActionMap = cm::flat_map<cm::string_view, Action>;
+class ActionMap : public std::vector<std::pair<cm::string_view, Action>>
+{
+public:
+ std::pair<iterator, bool> Emplace(cm::string_view name, Action action);
+ const_iterator Find(cm::string_view name) const;
+};
+
+class Instance
+{
+public:
+ Instance(ActionMap const& bindings)
+ : Bindings(bindings)
+ {
+ }
+
+ void Bind(bool& val);
+ void Bind(std::string& val);
+ void Bind(StringList& val);
+ void Bind(MultiStringList& val);
+
+ void Consume(cm::string_view arg, void* result,
+ std::vector<std::string>* unparsedArguments,
+ std::vector<std::string>* keywordsMissingValue);
+
+private:
+ ActionMap const& Bindings;
+ std::string* CurrentString = nullptr;
+ StringList* CurrentList = nullptr;
+ bool ExpectValue = false;
+};
+
+} // namespace ArgumentParser
+
+template <typename Result>
+class cmArgumentParser
+{
+public:
+ // I *think* this function could be made `constexpr` when the code is
+ // compiled as C++20. This would allow building a parser at compile time.
+ template <typename T>
+ cmArgumentParser& Bind(cm::static_string_view name, T Result::*member)
+ {
+ bool const inserted =
+ this->Bindings
+ .Emplace(name,
+ [member](ArgumentParser::Instance& instance, void* result) {
+ instance.Bind(static_cast<Result*>(result)->*member);
+ })
+ .second;
+ assert(inserted), (void)inserted;
+ return *this;
+ }
+
+ template <typename Range>
+ void Parse(Result& result, Range const& args,
+ std::vector<std::string>* unparsedArguments = nullptr,
+ std::vector<std::string>* keywordsMissingValue = nullptr) const
+ {
+ ArgumentParser::Instance instance(this->Bindings);
+ for (cm::string_view arg : args) {
+ instance.Consume(arg, &result, unparsedArguments, keywordsMissingValue);
+ }
+ }
+
+ template <typename Range>
+ Result Parse(Range const& args,
+ std::vector<std::string>* unparsedArguments = nullptr,
+ std::vector<std::string>* keywordsMissingValue = nullptr) const
+ {
+ Result result;
+ this->Parse(result, args, unparsedArguments, keywordsMissingValue);
+ return result;
+ }
+
+private:
+ ArgumentParser::ActionMap Bindings;
+};
+
+template <>
+class cmArgumentParser<void>
+{
+public:
+ template <typename T>
+ cmArgumentParser& Bind(cm::static_string_view name, T& ref)
+ {
+ bool const inserted = this->Bind(cm::string_view(name), ref);
+ assert(inserted), (void)inserted;
+ return *this;
+ }
+
+ template <typename Range>
+ void Parse(Range const& args,
+ std::vector<std::string>* unparsedArguments = nullptr,
+ std::vector<std::string>* keywordsMissingValue = nullptr) const
+ {
+ ArgumentParser::Instance instance(this->Bindings);
+ for (cm::string_view arg : args) {
+ instance.Consume(arg, nullptr, unparsedArguments, keywordsMissingValue);
+ }
+ }
+
+protected:
+ template <typename T>
+ bool Bind(cm::string_view name, T& ref)
+ {
+ return this->Bindings
+ .Emplace(name,
+ [&ref](ArgumentParser::Instance& instance, void*) {
+ instance.Bind(ref);
+ })
+ .second;
+ }
+
+private:
+ ArgumentParser::ActionMap Bindings;
+};
+
+#endif
diff --git a/Source/cmConnection.cxx b/Source/cmConnection.cxx
new file mode 100644
index 000000000..166426bfc
--- /dev/null
+++ b/Source/cmConnection.cxx
@@ -0,0 +1,163 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmConnection.h"
+
+#include "cmServer.h"
+#include "cm_uv.h"
+
+#include <cassert>
+#include <cstring>
+
+struct write_req_t
+{
+ uv_write_t req;
+ uv_buf_t buf;
+};
+
+void cmEventBasedConnection::on_alloc_buffer(uv_handle_t* handle,
+ size_t suggested_size,
+ uv_buf_t* buf)
+{
+ (void)(handle);
+ char* rawBuffer = new char[suggested_size];
+ *buf = uv_buf_init(rawBuffer, static_cast<unsigned int>(suggested_size));
+}
+
+void cmEventBasedConnection::on_read(uv_stream_t* stream, ssize_t nread,
+ const uv_buf_t* buf)
+{
+ auto conn = static_cast<cmEventBasedConnection*>(stream->data);
+ if (conn) {
+ if (nread >= 0) {
+ conn->ReadData(std::string(buf->base, buf->base + nread));
+ } else {
+ conn->OnDisconnect(static_cast<int>(nread));
+ }
+ }
+
+ delete[](buf->base);
+}
+
+void cmEventBasedConnection::on_close(uv_handle_t* /*handle*/)
+{
+}
+
+void cmEventBasedConnection::on_write(uv_write_t* req, int status)
+{
+ (void)(status);
+
+ // Free req and buffer
+ write_req_t* wr = reinterpret_cast<write_req_t*>(req);
+ delete[](wr->buf.base);
+ delete wr;
+}
+
+void cmEventBasedConnection::on_new_connection(uv_stream_t* stream, int status)
+{
+ (void)(status);
+ auto conn = static_cast<cmEventBasedConnection*>(stream->data);
+
+ if (conn) {
+ conn->Connect(stream);
+ }
+}
+
+bool cmEventBasedConnection::IsOpen() const
+{
+ return this->WriteStream != nullptr;
+}
+
+void cmEventBasedConnection::WriteData(const std::string& _data)
+{
+#ifndef NDEBUG
+ auto curr_thread_id = uv_thread_self();
+ assert(this->Server);
+ assert(uv_thread_equal(&curr_thread_id, &this->Server->ServeThreadId));
+#endif
+
+ auto data = _data;
+ assert(this->WriteStream.get());
+ if (BufferStrategy) {
+ data = BufferStrategy->BufferOutMessage(data);
+ }
+
+ auto ds = data.size();
+
+ write_req_t* req = new write_req_t;
+ req->req.data = this;
+ req->buf = uv_buf_init(new char[ds], static_cast<unsigned int>(ds));
+ memcpy(req->buf.base, data.c_str(), ds);
+ uv_write(reinterpret_cast<uv_write_t*>(req), this->WriteStream, &req->buf, 1,
+ on_write);
+}
+
+void cmEventBasedConnection::ReadData(const std::string& data)
+{
+ this->RawReadBuffer += data;
+ if (BufferStrategy) {
+ std::string packet = BufferStrategy->BufferMessage(this->RawReadBuffer);
+ while (!packet.empty()) {
+ ProcessRequest(packet);
+ packet = BufferStrategy->BufferMessage(this->RawReadBuffer);
+ }
+ } else {
+ ProcessRequest(this->RawReadBuffer);
+ this->RawReadBuffer.clear();
+ }
+}
+
+cmEventBasedConnection::cmEventBasedConnection(
+ cmConnectionBufferStrategy* bufferStrategy)
+ : BufferStrategy(bufferStrategy)
+{
+}
+
+void cmEventBasedConnection::Connect(uv_stream_t* server)
+{
+ (void)server;
+ Server->OnConnected(nullptr);
+}
+
+void cmEventBasedConnection::OnDisconnect(int onerror)
+{
+ (void)onerror;
+ this->OnConnectionShuttingDown();
+ if (this->Server) {
+ this->Server->OnDisconnect(this);
+ }
+}
+
+cmConnection::~cmConnection() = default;
+
+bool cmConnection::OnConnectionShuttingDown()
+{
+ this->Server = nullptr;
+ return true;
+}
+
+void cmConnection::SetServer(cmServerBase* s)
+{
+ Server = s;
+}
+
+void cmConnection::ProcessRequest(const std::string& request)
+{
+ Server->ProcessRequest(this, request);
+}
+
+bool cmConnection::OnServeStart(std::string* errString)
+{
+ (void)errString;
+ return true;
+}
+
+bool cmEventBasedConnection::OnConnectionShuttingDown()
+{
+ if (this->WriteStream.get()) {
+ this->WriteStream->data = nullptr;
+ }
+
+ WriteStream.reset();
+
+ return true;
+}
diff --git a/Source/cmConnection.h b/Source/cmConnection.h
new file mode 100644
index 000000000..092b91302
--- /dev/null
+++ b/Source/cmConnection.h
@@ -0,0 +1,136 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include "cmUVHandlePtr.h"
+#include "cm_uv.h"
+
+#include <cstddef>
+#include <memory>
+#include <string>
+
+class cmServerBase;
+
+/***
+ * Given a sequence of bytes with any kind of buffering, instances of this
+ * class arrange logical chunks according to whatever the use case is for
+ * the connection.
+ */
+class cmConnectionBufferStrategy
+{
+public:
+ virtual ~cmConnectionBufferStrategy();
+
+ /***
+ * Called whenever with an active raw buffer. If a logical chunk
+ * becomes available, that chunk is returned and that portion is
+ * removed from the rawBuffer
+ *
+ * @param rawBuffer in/out parameter. Receive buffer; the buffer strategy is
+ * free to manipulate this buffer anyway it needs to.
+ *
+ * @return Next chunk from the stream. Returns the empty string if a chunk
+ * isn't ready yet. Users of this interface should repeatedly call this
+ * function until an empty string is returned since its entirely possible
+ * multiple chunks come in a single raw buffer.
+ */
+ virtual std::string BufferMessage(std::string& rawBuffer) = 0;
+
+ /***
+ * Called to properly buffer an outgoing message.
+ *
+ * @param rawBuffer Message to format in the correct way
+ *
+ * @return Formatted message
+ */
+ virtual std::string BufferOutMessage(const std::string& rawBuffer) const
+ {
+ return rawBuffer;
+ };
+ /***
+ * Resets the internal state of the buffering
+ */
+ virtual void clear();
+
+ // TODO: There should be a callback / flag set for errors
+};
+
+class cmConnection
+{
+public:
+ cmConnection() = default;
+
+ cmConnection(cmConnection const&) = delete;
+ cmConnection& operator=(cmConnection const&) = delete;
+
+ virtual void WriteData(const std::string& data) = 0;
+
+ virtual ~cmConnection();
+
+ virtual bool OnConnectionShuttingDown();
+
+ virtual bool IsOpen() const = 0;
+
+ virtual void SetServer(cmServerBase* s);
+
+ virtual void ProcessRequest(const std::string& request);
+
+ virtual bool OnServeStart(std::string* pString);
+
+protected:
+ cmServerBase* Server = nullptr;
+};
+
+/***
+ * Abstraction of a connection; ties in event callbacks from libuv and notifies
+ * the server when appropriate
+ */
+class cmEventBasedConnection : public cmConnection
+{
+
+public:
+ /***
+ * @param bufferStrategy If no strategy is given, it will process the raw
+ * chunks as they come in. The connection
+ * owns the pointer given.
+ */
+ cmEventBasedConnection(cmConnectionBufferStrategy* bufferStrategy = nullptr);
+
+ virtual void Connect(uv_stream_t* server);
+
+ virtual void ReadData(const std::string& data);
+
+ bool IsOpen() const override;
+
+ void WriteData(const std::string& data) override;
+ bool OnConnectionShuttingDown() override;
+
+ virtual void OnDisconnect(int errorCode);
+
+ static void on_close(uv_handle_t* handle);
+
+ template <typename T>
+ static void on_close_delete(uv_handle_t* handle)
+ {
+ delete reinterpret_cast<T*>(handle);
+ }
+
+protected:
+ cm::uv_stream_ptr WriteStream;
+
+ std::string RawReadBuffer;
+
+ std::unique_ptr<cmConnectionBufferStrategy> BufferStrategy;
+
+ static void on_read(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf);
+
+ static void on_write(uv_write_t* req, int status);
+
+ static void on_new_connection(uv_stream_t* stream, int status);
+
+ static void on_alloc_buffer(uv_handle_t* handle, size_t suggested_size,
+ uv_buf_t* buf);
+};
diff --git a/Source/cmDuration.cxx b/Source/cmDuration.cxx
new file mode 100644
index 000000000..8ca5d8d12
--- /dev/null
+++ b/Source/cmDuration.cxx
@@ -0,0 +1,27 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#define CMDURATION_CPP
+#include "cmDuration.h"
+
+template <typename T>
+T cmDurationTo(const cmDuration& duration)
+{
+ /* This works because the comparison operators for duration rely on
+ * std::common_type.
+ * So for example duration<int>::max() gets promoted to a duration<double>,
+ * which can then be safely compared.
+ */
+ if (duration >= std::chrono::duration<T>::max()) {
+ return std::chrono::duration<T>::max().count();
+ }
+ if (duration <= std::chrono::duration<T>::min()) {
+ return std::chrono::duration<T>::min().count();
+ }
+ // Ensure number of seconds by defining ratio<1>
+ return std::chrono::duration_cast<std::chrono::duration<T, std::ratio<1>>>(
+ duration)
+ .count();
+}
+
+template int cmDurationTo<int>(const cmDuration&);
+template unsigned int cmDurationTo<unsigned int>(const cmDuration&);
diff --git a/Source/cmDuration.h b/Source/cmDuration.h
new file mode 100644
index 000000000..6df1455b4
--- /dev/null
+++ b/Source/cmDuration.h
@@ -0,0 +1,24 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#pragma once
+
+#include <chrono>
+#include <ratio>
+
+typedef std::chrono::duration<double, std::ratio<1>> cmDuration;
+
+/*
+ * This function will return number of seconds in the requested type T.
+ *
+ * A duration_cast from duration<double> to duration<T> will not yield what
+ * one might expect if the double representation does not fit into type T.
+ * This function aims to safely convert, by clamping the double value between
+ * the permissible valid values for T.
+ */
+template <typename T>
+T cmDurationTo(const cmDuration& duration);
+
+#ifndef CMDURATION_CPP
+extern template int cmDurationTo<int>(const cmDuration&);
+extern template unsigned int cmDurationTo<unsigned int>(const cmDuration&);
+#endif
diff --git a/Source/cmFSPermissions.cxx b/Source/cmFSPermissions.cxx
new file mode 100644
index 000000000..4015a5164
--- /dev/null
+++ b/Source/cmFSPermissions.cxx
@@ -0,0 +1,34 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmFSPermissions.h"
+
+bool cmFSPermissions::stringToModeT(std::string const& arg,
+ mode_t& permissions)
+{
+ if (arg == "OWNER_READ") {
+ permissions |= mode_owner_read;
+ } else if (arg == "OWNER_WRITE") {
+ permissions |= mode_owner_write;
+ } else if (arg == "OWNER_EXECUTE") {
+ permissions |= mode_owner_execute;
+ } else if (arg == "GROUP_READ") {
+ permissions |= mode_group_read;
+ } else if (arg == "GROUP_WRITE") {
+ permissions |= mode_group_write;
+ } else if (arg == "GROUP_EXECUTE") {
+ permissions |= mode_group_execute;
+ } else if (arg == "WORLD_READ") {
+ permissions |= mode_world_read;
+ } else if (arg == "WORLD_WRITE") {
+ permissions |= mode_world_write;
+ } else if (arg == "WORLD_EXECUTE") {
+ permissions |= mode_world_execute;
+ } else if (arg == "SETUID") {
+ permissions |= mode_setuid;
+ } else if (arg == "SETGID") {
+ permissions |= mode_setgid;
+ } else {
+ return false;
+ }
+ return true;
+}
diff --git a/Source/cmFSPermissions.h b/Source/cmFSPermissions.h
new file mode 100644
index 000000000..7a6e70866
--- /dev/null
+++ b/Source/cmFSPermissions.h
@@ -0,0 +1,45 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#ifndef cmFSPermissions_h
+#define cmFSPermissions_h
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include "cm_sys_stat.h"
+
+#include <string>
+
+namespace cmFSPermissions {
+
+// Table of permissions flags.
+#if defined(_WIN32) && !defined(__CYGWIN__)
+static const mode_t mode_owner_read = S_IREAD;
+static const mode_t mode_owner_write = S_IWRITE;
+static const mode_t mode_owner_execute = S_IEXEC;
+static const mode_t mode_group_read = 040;
+static const mode_t mode_group_write = 020;
+static const mode_t mode_group_execute = 010;
+static const mode_t mode_world_read = 04;
+static const mode_t mode_world_write = 02;
+static const mode_t mode_world_execute = 01;
+static const mode_t mode_setuid = 04000;
+static const mode_t mode_setgid = 02000;
+#else
+static const mode_t mode_owner_read = S_IRUSR;
+static const mode_t mode_owner_write = S_IWUSR;
+static const mode_t mode_owner_execute = S_IXUSR;
+static const mode_t mode_group_read = S_IRGRP;
+static const mode_t mode_group_write = S_IWGRP;
+static const mode_t mode_group_execute = S_IXGRP;
+static const mode_t mode_world_read = S_IROTH;
+static const mode_t mode_world_write = S_IWOTH;
+static const mode_t mode_world_execute = S_IXOTH;
+static const mode_t mode_setuid = S_ISUID;
+static const mode_t mode_setgid = S_ISGID;
+#endif
+
+bool stringToModeT(std::string const& arg, mode_t& permissions);
+
+} // ns
+
+#endif
diff --git a/Source/cmFileAPI.cxx b/Source/cmFileAPI.cxx
new file mode 100644
index 000000000..ba4266963
--- /dev/null
+++ b/Source/cmFileAPI.cxx
@@ -0,0 +1,835 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmFileAPI.h"
+
+#include "cmAlgorithms.h"
+#include "cmCryptoHash.h"
+#include "cmFileAPICMakeFiles.h"
+#include "cmFileAPICache.h"
+#include "cmFileAPICodemodel.h"
+#include "cmGlobalGenerator.h"
+#include "cmSystemTools.h"
+#include "cmTimestamp.h"
+#include "cmake.h"
+#include "cmsys/Directory.hxx"
+#include "cmsys/FStream.hxx"
+
+#include <algorithm>
+#include <cassert>
+#include <chrono>
+#include <ctime>
+#include <iomanip>
+#include <sstream>
+#include <utility>
+
+cmFileAPI::cmFileAPI(cmake* cm)
+ : CMakeInstance(cm)
+{
+ this->APIv1 =
+ this->CMakeInstance->GetHomeOutputDirectory() + "/.cmake/api/v1";
+
+ Json::CharReaderBuilder rbuilder;
+ rbuilder["collectComments"] = false;
+ rbuilder["failIfExtra"] = true;
+ rbuilder["rejectDupKeys"] = false;
+ rbuilder["strictRoot"] = true;
+ this->JsonReader =
+ std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
+
+ Json::StreamWriterBuilder wbuilder;
+ wbuilder["indentation"] = "\t";
+ this->JsonWriter =
+ std::unique_ptr<Json::StreamWriter>(wbuilder.newStreamWriter());
+}
+
+void cmFileAPI::ReadQueries()
+{
+ std::string const query_dir = this->APIv1 + "/query";
+ this->QueryExists = cmSystemTools::FileIsDirectory(query_dir);
+ if (!this->QueryExists) {
+ return;
+ }
+
+ // Load queries at the top level.
+ std::vector<std::string> queries = cmFileAPI::LoadDir(query_dir);
+
+ // Read the queries and save for later.
+ for (std::string& query : queries) {
+ if (cmHasLiteralPrefix(query, "client-")) {
+ this->ReadClient(query);
+ } else if (!cmFileAPI::ReadQuery(query, this->TopQuery.Known)) {
+ this->TopQuery.Unknown.push_back(std::move(query));
+ }
+ }
+}
+
+void cmFileAPI::WriteReplies()
+{
+ if (this->QueryExists) {
+ cmSystemTools::MakeDirectory(this->APIv1 + "/reply");
+ this->WriteJsonFile(this->BuildReplyIndex(), "index", ComputeSuffixTime);
+ }
+
+ this->RemoveOldReplyFiles();
+}
+
+std::vector<std::string> cmFileAPI::LoadDir(std::string const& dir)
+{
+ std::vector<std::string> files;
+ cmsys::Directory d;
+ d.Load(dir);
+ for (unsigned long i = 0; i < d.GetNumberOfFiles(); ++i) {
+ std::string f = d.GetFile(i);
+ if (f != "." && f != "..") {
+ files.push_back(std::move(f));
+ }
+ }
+ std::sort(files.begin(), files.end());
+ return files;
+}
+
+void cmFileAPI::RemoveOldReplyFiles()
+{
+ std::string const reply_dir = this->APIv1 + "/reply";
+ std::vector<std::string> files = this->LoadDir(reply_dir);
+ for (std::string const& f : files) {
+ if (this->ReplyFiles.find(f) == this->ReplyFiles.end()) {
+ std::string file = reply_dir + "/" + f;
+ cmSystemTools::RemoveFile(file);
+ }
+ }
+}
+
+bool cmFileAPI::ReadJsonFile(std::string const& file, Json::Value& value,
+ std::string& error)
+{
+ std::vector<char> content;
+
+ cmsys::ifstream fin;
+ if (!cmSystemTools::FileIsDirectory(file)) {
+ fin.open(file.c_str(), std::ios::binary);
+ }
+ auto finEnd = fin.rdbuf()->pubseekoff(0, std::ios::end);
+ if (finEnd > 0) {
+ size_t finSize = finEnd;
+ try {
+ // Allocate a buffer to read the whole file.
+ content.resize(finSize);
+
+ // Now read the file from the beginning.
+ fin.seekg(0, std::ios::beg);
+ fin.read(content.data(), finSize);
+ } catch (...) {
+ fin.setstate(std::ios::failbit);
+ }
+ }
+ fin.close();
+ if (!fin) {
+ value = Json::Value();
+ error = "failed to read from file";
+ return false;
+ }
+
+ // Parse our buffer as json.
+ if (!this->JsonReader->parse(content.data(), content.data() + content.size(),
+ &value, &error)) {
+ value = Json::Value();
+ return false;
+ }
+
+ return true;
+}
+
+std::string cmFileAPI::WriteJsonFile(
+ Json::Value const& value, std::string const& prefix,
+ std::string (*computeSuffix)(std::string const&))
+{
+ std::string fileName;
+
+ // Write the json file with a temporary name.
+ std::string const& tmpFile = this->APIv1 + "/tmp.json";
+ cmsys::ofstream ftmp(tmpFile.c_str());
+ this->JsonWriter->write(value, &ftmp);
+ ftmp << "\n";
+ ftmp.close();
+ if (!ftmp) {
+ cmSystemTools::RemoveFile(tmpFile);
+ return fileName;
+ }
+
+ // Compute the final name for the file.
+ fileName = prefix + "-" + computeSuffix(tmpFile) + ".json";
+
+ // Create the destination.
+ std::string file = this->APIv1 + "/reply";
+ cmSystemTools::MakeDirectory(file);
+ file += "/";
+ file += fileName;
+
+ // If the final name already exists then assume it has proper content.
+ // Otherwise, atomically place the reply file at its final name
+ if (cmSystemTools::FileExists(file, true) ||
+ !cmSystemTools::RenameFile(tmpFile, file)) {
+ cmSystemTools::RemoveFile(tmpFile);
+ }
+
+ // Record this among files we have just written.
+ this->ReplyFiles.insert(fileName);
+
+ return fileName;
+}
+
+Json::Value cmFileAPI::MaybeJsonFile(Json::Value in, std::string const& prefix)
+{
+ Json::Value out;
+ if (in.isObject() || in.isArray()) {
+ out = Json::objectValue;
+ out["jsonFile"] = this->WriteJsonFile(in, prefix);
+ } else {
+ out = std::move(in);
+ }
+ return out;
+}
+
+std::string cmFileAPI::ComputeSuffixHash(std::string const& file)
+{
+ cmCryptoHash hasher(cmCryptoHash::AlgoSHA3_256);
+ std::string hash = hasher.HashFile(file);
+ hash.resize(20, '0');
+ return hash;
+}
+
+std::string cmFileAPI::ComputeSuffixTime(std::string const&)
+{
+ std::chrono::milliseconds ms =
+ std::chrono::duration_cast<std::chrono::milliseconds>(
+ std::chrono::system_clock::now().time_since_epoch());
+ std::chrono::seconds s =
+ std::chrono::duration_cast<std::chrono::seconds>(ms);
+
+ std::time_t ts = s.count();
+ std::size_t tms = ms.count() % 1000;
+
+ cmTimestamp cmts;
+ std::ostringstream ss;
+ ss << cmts.CreateTimestampFromTimeT(ts, "%Y-%m-%dT%H-%M-%S", true) << '-'
+ << std::setfill('0') << std::setw(4) << tms;
+ return ss.str();
+}
+
+bool cmFileAPI::ReadQuery(std::string const& query,
+ std::vector<Object>& objects)
+{
+ // Parse the "<kind>-" syntax.
+ std::string::size_type sep_pos = query.find('-');
+ if (sep_pos == std::string::npos) {
+ return false;
+ }
+ std::string kindName = query.substr(0, sep_pos);
+ std::string verStr = query.substr(sep_pos + 1);
+ if (kindName == ObjectKindName(ObjectKind::CodeModel)) {
+ Object o;
+ o.Kind = ObjectKind::CodeModel;
+ if (verStr == "v2") {
+ o.Version = 2;
+ } else {
+ return false;
+ }
+ objects.push_back(o);
+ return true;
+ }
+ if (kindName == ObjectKindName(ObjectKind::Cache)) {
+ Object o;
+ o.Kind = ObjectKind::Cache;
+ if (verStr == "v2") {
+ o.Version = 2;
+ } else {
+ return false;
+ }
+ objects.push_back(o);
+ return true;
+ }
+ if (kindName == ObjectKindName(ObjectKind::CMakeFiles)) {
+ Object o;
+ o.Kind = ObjectKind::CMakeFiles;
+ if (verStr == "v1") {
+ o.Version = 1;
+ } else {
+ return false;
+ }
+ objects.push_back(o);
+ return true;
+ }
+ if (kindName == ObjectKindName(ObjectKind::InternalTest)) {
+ Object o;
+ o.Kind = ObjectKind::InternalTest;
+ if (verStr == "v1") {
+ o.Version = 1;
+ } else if (verStr == "v2") {
+ o.Version = 2;
+ } else {
+ return false;
+ }
+ objects.push_back(o);
+ return true;
+ }
+ return false;
+}
+
+void cmFileAPI::ReadClient(std::string const& client)
+{
+ // Load queries for the client.
+ std::string clientDir = this->APIv1 + "/query/" + client;
+ std::vector<std::string> queries = this->LoadDir(clientDir);
+
+ // Read the queries and save for later.
+ ClientQuery& clientQuery = this->ClientQueries[client];
+ for (std::string& query : queries) {
+ if (query == "query.json") {
+ clientQuery.HaveQueryJson = true;
+ this->ReadClientQuery(client, clientQuery.QueryJson);
+ } else if (!this->ReadQuery(query, clientQuery.DirQuery.Known)) {
+ clientQuery.DirQuery.Unknown.push_back(std::move(query));
+ }
+ }
+}
+
+void cmFileAPI::ReadClientQuery(std::string const& client, ClientQueryJson& q)
+{
+ // Read the query.json file.
+ std::string queryFile = this->APIv1 + "/query/" + client + "/query.json";
+ Json::Value query;
+ if (!this->ReadJsonFile(queryFile, query, q.Error)) {
+ return;
+ }
+ if (!query.isObject()) {
+ q.Error = "query root is not an object";
+ return;
+ }
+
+ Json::Value const& clientValue = query["client"];
+ if (!clientValue.isNull()) {
+ q.ClientValue = clientValue;
+ }
+ q.RequestsValue = std::move(query["requests"]);
+ q.Requests = this->BuildClientRequests(q.RequestsValue);
+}
+
+Json::Value cmFileAPI::BuildReplyIndex()
+{
+ Json::Value index(Json::objectValue);
+
+ // Report information about this version of CMake.
+ index["cmake"] = this->BuildCMake();
+
+ // Reply to all queries that we loaded.
+ Json::Value& reply = index["reply"] = this->BuildReply(this->TopQuery);
+ for (auto const& client : this->ClientQueries) {
+ std::string const& clientName = client.first;
+ ClientQuery const& clientQuery = client.second;
+ reply[clientName] = this->BuildClientReply(clientQuery);
+ }
+
+ // Move our index of generated objects into its field.
+ Json::Value& objects = index["objects"] = Json::arrayValue;
+ for (auto& entry : this->ReplyIndexObjects) {
+ objects.append(std::move(entry.second)); // NOLINT(*)
+ }
+
+ return index;
+}
+
+Json::Value cmFileAPI::BuildCMake()
+{
+ Json::Value cmake = Json::objectValue;
+ cmake["version"] = this->CMakeInstance->ReportVersionJson();
+ Json::Value& cmake_paths = cmake["paths"] = Json::objectValue;
+ cmake_paths["cmake"] = cmSystemTools::GetCMakeCommand();
+ cmake_paths["ctest"] = cmSystemTools::GetCTestCommand();
+ cmake_paths["cpack"] = cmSystemTools::GetCPackCommand();
+ cmake_paths["root"] = cmSystemTools::GetCMakeRoot();
+ cmake["generator"] = this->CMakeInstance->GetGlobalGenerator()->GetJson();
+ return cmake;
+}
+
+Json::Value cmFileAPI::BuildReply(Query const& q)
+{
+ Json::Value reply = Json::objectValue;
+ for (Object const& o : q.Known) {
+ std::string const& name = ObjectName(o);
+ reply[name] = this->AddReplyIndexObject(o);
+ }
+
+ for (std::string const& name : q.Unknown) {
+ reply[name] = cmFileAPI::BuildReplyError("unknown query file");
+ }
+ return reply;
+}
+
+Json::Value cmFileAPI::BuildReplyError(std::string const& error)
+{
+ Json::Value e = Json::objectValue;
+ e["error"] = error;
+ return e;
+}
+
+Json::Value const& cmFileAPI::AddReplyIndexObject(Object const& o)
+{
+ Json::Value& indexEntry = this->ReplyIndexObjects[o];
+ if (!indexEntry.isNull()) {
+ // The reply object has already been generated.
+ return indexEntry;
+ }
+
+ // Generate this reply object.
+ Json::Value const& object = this->BuildObject(o);
+ assert(object.isObject());
+
+ // Populate this index entry.
+ indexEntry = Json::objectValue;
+ indexEntry["kind"] = object["kind"];
+ indexEntry["version"] = object["version"];
+ indexEntry["jsonFile"] = this->WriteJsonFile(object, ObjectName(o));
+ return indexEntry;
+}
+
+const char* cmFileAPI::ObjectKindName(ObjectKind kind)
+{
+ // Keep in sync with ObjectKind enum.
+ static const char* objectKindNames[] = {
+ "codemodel", //
+ "cache", //
+ "cmakeFiles", //
+ "__test" //
+ };
+ return objectKindNames[size_t(kind)];
+}
+
+std::string cmFileAPI::ObjectName(Object const& o)
+{
+ std::string name = ObjectKindName(o.Kind);
+ name += "-v";
+ name += std::to_string(o.Version);
+ return name;
+}
+
+Json::Value cmFileAPI::BuildVersion(unsigned int major, unsigned int minor)
+{
+ Json::Value version;
+ version["major"] = major;
+ version["minor"] = minor;
+ return version;
+}
+
+Json::Value cmFileAPI::BuildObject(Object const& object)
+{
+ Json::Value value;
+
+ switch (object.Kind) {
+ case ObjectKind::CodeModel:
+ value = this->BuildCodeModel(object);
+ break;
+ case ObjectKind::Cache:
+ value = this->BuildCache(object);
+ break;
+ case ObjectKind::CMakeFiles:
+ value = this->BuildCMakeFiles(object);
+ break;
+ case ObjectKind::InternalTest:
+ value = this->BuildInternalTest(object);
+ break;
+ }
+
+ return value;
+}
+
+cmFileAPI::ClientRequests cmFileAPI::BuildClientRequests(
+ Json::Value const& requests)
+{
+ ClientRequests result;
+ if (requests.isNull()) {
+ result.Error = "'requests' member missing";
+ return result;
+ }
+ if (!requests.isArray()) {
+ result.Error = "'requests' member is not an array";
+ return result;
+ }
+
+ result.reserve(requests.size());
+ for (Json::Value const& request : requests) {
+ result.emplace_back(this->BuildClientRequest(request));
+ }
+
+ return result;
+}
+
+cmFileAPI::ClientRequest cmFileAPI::BuildClientRequest(
+ Json::Value const& request)
+{
+ ClientRequest r;
+
+ if (!request.isObject()) {
+ r.Error = "request is not an object";
+ return r;
+ }
+
+ Json::Value const& kind = request["kind"];
+ if (kind.isNull()) {
+ r.Error = "'kind' member missing";
+ return r;
+ }
+ if (!kind.isString()) {
+ r.Error = "'kind' member is not a string";
+ return r;
+ }
+ std::string const& kindName = kind.asString();
+
+ if (kindName == this->ObjectKindName(ObjectKind::CodeModel)) {
+ r.Kind = ObjectKind::CodeModel;
+ } else if (kindName == this->ObjectKindName(ObjectKind::Cache)) {
+ r.Kind = ObjectKind::Cache;
+ } else if (kindName == this->ObjectKindName(ObjectKind::CMakeFiles)) {
+ r.Kind = ObjectKind::CMakeFiles;
+ } else if (kindName == this->ObjectKindName(ObjectKind::InternalTest)) {
+ r.Kind = ObjectKind::InternalTest;
+ } else {
+ r.Error = "unknown request kind '" + kindName + "'";
+ return r;
+ }
+
+ Json::Value const& version = request["version"];
+ if (version.isNull()) {
+ r.Error = "'version' member missing";
+ return r;
+ }
+ std::vector<RequestVersion> versions;
+ if (!cmFileAPI::ReadRequestVersions(version, versions, r.Error)) {
+ return r;
+ }
+
+ switch (r.Kind) {
+ case ObjectKind::CodeModel:
+ this->BuildClientRequestCodeModel(r, versions);
+ break;
+ case ObjectKind::Cache:
+ this->BuildClientRequestCache(r, versions);
+ break;
+ case ObjectKind::CMakeFiles:
+ this->BuildClientRequestCMakeFiles(r, versions);
+ break;
+ case ObjectKind::InternalTest:
+ this->BuildClientRequestInternalTest(r, versions);
+ break;
+ }
+
+ return r;
+}
+
+Json::Value cmFileAPI::BuildClientReply(ClientQuery const& q)
+{
+ Json::Value reply = this->BuildReply(q.DirQuery);
+
+ if (!q.HaveQueryJson) {
+ return reply;
+ }
+
+ Json::Value& reply_query_json = reply["query.json"];
+ ClientQueryJson const& qj = q.QueryJson;
+
+ if (!qj.Error.empty()) {
+ reply_query_json = this->BuildReplyError(qj.Error);
+ return reply;
+ }
+
+ if (!qj.ClientValue.isNull()) {
+ reply_query_json["client"] = qj.ClientValue;
+ }
+
+ if (!qj.RequestsValue.isNull()) {
+ reply_query_json["requests"] = qj.RequestsValue;
+ }
+
+ reply_query_json["responses"] = this->BuildClientReplyResponses(qj.Requests);
+
+ return reply;
+}
+
+Json::Value cmFileAPI::BuildClientReplyResponses(
+ ClientRequests const& requests)
+{
+ Json::Value responses;
+
+ if (!requests.Error.empty()) {
+ responses = this->BuildReplyError(requests.Error);
+ return responses;
+ }
+
+ responses = Json::arrayValue;
+ for (ClientRequest const& request : requests) {
+ responses.append(this->BuildClientReplyResponse(request));
+ }
+
+ return responses;
+}
+
+Json::Value cmFileAPI::BuildClientReplyResponse(ClientRequest const& request)
+{
+ Json::Value response;
+ if (!request.Error.empty()) {
+ response = this->BuildReplyError(request.Error);
+ return response;
+ }
+ response = this->AddReplyIndexObject(request);
+ return response;
+}
+
+bool cmFileAPI::ReadRequestVersions(Json::Value const& version,
+ std::vector<RequestVersion>& versions,
+ std::string& error)
+{
+ if (version.isArray()) {
+ for (Json::Value const& v : version) {
+ if (!ReadRequestVersion(v, /*inArray=*/true, versions, error)) {
+ return false;
+ }
+ }
+ } else {
+ if (!ReadRequestVersion(version, /*inArray=*/false, versions, error)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool cmFileAPI::ReadRequestVersion(Json::Value const& version, bool inArray,
+ std::vector<RequestVersion>& result,
+ std::string& error)
+{
+ if (version.isUInt()) {
+ RequestVersion v;
+ v.Major = version.asUInt();
+ result.push_back(v);
+ return true;
+ }
+
+ if (!version.isObject()) {
+ if (inArray) {
+ error = "'version' array entry is not a non-negative integer or object";
+ } else {
+ error =
+ "'version' member is not a non-negative integer, object, or array";
+ }
+ return false;
+ }
+
+ Json::Value const& major = version["major"];
+ if (major.isNull()) {
+ error = "'version' object 'major' member missing";
+ return false;
+ }
+ if (!major.isUInt()) {
+ error = "'version' object 'major' member is not a non-negative integer";
+ return false;
+ }
+
+ RequestVersion v;
+ v.Major = major.asUInt();
+
+ Json::Value const& minor = version["minor"];
+ if (minor.isUInt()) {
+ v.Minor = minor.asUInt();
+ } else if (!minor.isNull()) {
+ error = "'version' object 'minor' member is not a non-negative integer";
+ return false;
+ }
+
+ result.push_back(v);
+
+ return true;
+}
+
+std::string cmFileAPI::NoSupportedVersion(
+ std::vector<RequestVersion> const& versions)
+{
+ std::ostringstream msg;
+ msg << "no supported version specified";
+ if (!versions.empty()) {
+ msg << " among:";
+ for (RequestVersion const& v : versions) {
+ msg << " " << v.Major << "." << v.Minor;
+ }
+ }
+ return msg.str();
+}
+
+// The "codemodel" object kind.
+
+static unsigned int const CodeModelV2Minor = 0;
+
+void cmFileAPI::BuildClientRequestCodeModel(
+ ClientRequest& r, std::vector<RequestVersion> const& versions)
+{
+ // Select a known version from those requested.
+ for (RequestVersion const& v : versions) {
+ if ((v.Major == 2 && v.Minor <= CodeModelV2Minor)) {
+ r.Version = v.Major;
+ break;
+ }
+ }
+ if (!r.Version) {
+ r.Error = NoSupportedVersion(versions);
+ }
+}
+
+Json::Value cmFileAPI::BuildCodeModel(Object const& object)
+{
+ using namespace std::placeholders;
+ Json::Value codemodel = cmFileAPICodemodelDump(*this, object.Version);
+ codemodel["kind"] = this->ObjectKindName(object.Kind);
+
+ Json::Value& version = codemodel["version"];
+ if (object.Version == 2) {
+ version = BuildVersion(2, CodeModelV2Minor);
+ } else {
+ return codemodel; // should be unreachable
+ }
+
+ return codemodel;
+}
+
+// The "cache" object kind.
+
+static unsigned int const CacheV2Minor = 0;
+
+void cmFileAPI::BuildClientRequestCache(
+ ClientRequest& r, std::vector<RequestVersion> const& versions)
+{
+ // Select a known version from those requested.
+ for (RequestVersion const& v : versions) {
+ if ((v.Major == 2 && v.Minor <= CacheV2Minor)) {
+ r.Version = v.Major;
+ break;
+ }
+ }
+ if (!r.Version) {
+ r.Error = NoSupportedVersion(versions);
+ }
+}
+
+Json::Value cmFileAPI::BuildCache(Object const& object)
+{
+ using namespace std::placeholders;
+ Json::Value cache = cmFileAPICacheDump(*this, object.Version);
+ cache["kind"] = this->ObjectKindName(object.Kind);
+
+ Json::Value& version = cache["version"];
+ if (object.Version == 2) {
+ version = BuildVersion(2, CacheV2Minor);
+ } else {
+ return cache; // should be unreachable
+ }
+
+ return cache;
+}
+
+// The "cmakeFiles" object kind.
+
+static unsigned int const CMakeFilesV1Minor = 0;
+
+void cmFileAPI::BuildClientRequestCMakeFiles(
+ ClientRequest& r, std::vector<RequestVersion> const& versions)
+{
+ // Select a known version from those requested.
+ for (RequestVersion const& v : versions) {
+ if ((v.Major == 1 && v.Minor <= CMakeFilesV1Minor)) {
+ r.Version = v.Major;
+ break;
+ }
+ }
+ if (!r.Version) {
+ r.Error = NoSupportedVersion(versions);
+ }
+}
+
+Json::Value cmFileAPI::BuildCMakeFiles(Object const& object)
+{
+ using namespace std::placeholders;
+ Json::Value cmakeFiles = cmFileAPICMakeFilesDump(*this, object.Version);
+ cmakeFiles["kind"] = this->ObjectKindName(object.Kind);
+
+ Json::Value& version = cmakeFiles["version"];
+ if (object.Version == 1) {
+ version = BuildVersion(1, CMakeFilesV1Minor);
+ } else {
+ return cmakeFiles; // should be unreachable
+ }
+
+ return cmakeFiles;
+}
+
+// The "__test" object kind is for internal testing of CMake.
+
+static unsigned int const InternalTestV1Minor = 3;
+static unsigned int const InternalTestV2Minor = 0;
+
+void cmFileAPI::BuildClientRequestInternalTest(
+ ClientRequest& r, std::vector<RequestVersion> const& versions)
+{
+ // Select a known version from those requested.
+ for (RequestVersion const& v : versions) {
+ if ((v.Major == 1 && v.Minor <= InternalTestV1Minor) || //
+ (v.Major == 2 && v.Minor <= InternalTestV2Minor)) {
+ r.Version = v.Major;
+ break;
+ }
+ }
+ if (!r.Version) {
+ r.Error = NoSupportedVersion(versions);
+ }
+}
+
+Json::Value cmFileAPI::BuildInternalTest(Object const& object)
+{
+ Json::Value test = Json::objectValue;
+ test["kind"] = this->ObjectKindName(object.Kind);
+ Json::Value& version = test["version"];
+ if (object.Version == 2) {
+ version = BuildVersion(2, InternalTestV2Minor);
+ } else {
+ version = BuildVersion(1, InternalTestV1Minor);
+ }
+ return test;
+}
+
+Json::Value cmFileAPI::ReportCapabilities()
+{
+ Json::Value capabilities = Json::objectValue;
+ Json::Value& requests = capabilities["requests"] = Json::arrayValue;
+
+ {
+ Json::Value request = Json::objectValue;
+ request["kind"] = ObjectKindName(ObjectKind::CodeModel);
+ Json::Value& versions = request["version"] = Json::arrayValue;
+ versions.append(BuildVersion(2, CodeModelV2Minor));
+ requests.append(std::move(request)); // NOLINT(*)
+ }
+
+ {
+ Json::Value request = Json::objectValue;
+ request["kind"] = ObjectKindName(ObjectKind::Cache);
+ Json::Value& versions = request["version"] = Json::arrayValue;
+ versions.append(BuildVersion(2, CacheV2Minor));
+ requests.append(std::move(request)); // NOLINT(*)
+ }
+
+ {
+ Json::Value request = Json::objectValue;
+ request["kind"] = ObjectKindName(ObjectKind::CMakeFiles);
+ Json::Value& versions = request["version"] = Json::arrayValue;
+ versions.append(BuildVersion(1, CMakeFilesV1Minor));
+ requests.append(std::move(request)); // NOLINT(*)
+ }
+
+ return capabilities;
+}
diff --git a/Source/cmFileAPI.h b/Source/cmFileAPI.h
new file mode 100644
index 000000000..602efa8ed
--- /dev/null
+++ b/Source/cmFileAPI.h
@@ -0,0 +1,209 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#ifndef cmFileAPI_h
+#define cmFileAPI_h
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include "cm_jsoncpp_reader.h"
+#include "cm_jsoncpp_value.h"
+#include "cm_jsoncpp_writer.h"
+
+#include <map>
+#include <memory> // IWYU pragma: keep
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+class cmake;
+
+class cmFileAPI
+{
+public:
+ cmFileAPI(cmake* cm);
+
+ /** Read fileapi queries from disk. */
+ void ReadQueries();
+
+ /** Write fileapi replies to disk. */
+ void WriteReplies();
+
+ /** Get the "cmake" instance with which this was constructed. */
+ cmake* GetCMakeInstance() const { return this->CMakeInstance; }
+
+ /** Convert a JSON object or array into an object with a single
+ "jsonFile" member specifying a file named with the given prefix
+ and holding the original object. Other JSON types are unchanged. */
+ Json::Value MaybeJsonFile(Json::Value in, std::string const& prefix);
+
+ /** Report file-api capabilities for cmake -E capabilities. */
+ static Json::Value ReportCapabilities();
+
+private:
+ cmake* CMakeInstance;
+
+ /** The api/v1 directory location. */
+ std::string APIv1;
+
+ /** The set of files we have just written to the reply directory. */
+ std::unordered_set<std::string> ReplyFiles;
+
+ static std::vector<std::string> LoadDir(std::string const& dir);
+ void RemoveOldReplyFiles();
+
+ // Keep in sync with ObjectKindName.
+ enum class ObjectKind
+ {
+ CodeModel,
+ Cache,
+ CMakeFiles,
+ InternalTest
+ };
+
+ /** Identify one object kind and major version. */
+ struct Object
+ {
+ ObjectKind Kind;
+ unsigned long Version = 0;
+ friend bool operator<(Object const& l, Object const& r)
+ {
+ if (l.Kind != r.Kind) {
+ return l.Kind < r.Kind;
+ }
+ return l.Version < r.Version;
+ }
+ };
+
+ /** Represent content of a query directory. */
+ struct Query
+ {
+ /** Known object kind-version pairs. */
+ std::vector<Object> Known;
+ /** Unknown object kind names. */
+ std::vector<std::string> Unknown;
+ };
+
+ /** Represent one request in a client 'query.json'. */
+ struct ClientRequest : public Object
+ {
+ /** Empty if request is valid, else the error string. */
+ std::string Error;
+ };
+
+ /** Represent the "requests" in a client 'query.json'. */
+ struct ClientRequests : public std::vector<ClientRequest>
+ {
+ /** Empty if requests field is valid, else the error string. */
+ std::string Error;
+ };
+
+ /** Represent the content of a client query.json file. */
+ struct ClientQueryJson
+ {
+ /** The error string if parsing failed, else empty. */
+ std::string Error;
+
+ /** The 'query.json' object "client" member if it exists, else null. */
+ Json::Value ClientValue;
+
+ /** The 'query.json' object "requests" member if it exists, else null. */
+ Json::Value RequestsValue;
+
+ /** Requests extracted from 'query.json'. */
+ ClientRequests Requests;
+ };
+
+ /** Represent content of a client query directory. */
+ struct ClientQuery
+ {
+ /** The content of the client query directory except 'query.json'. */
+ Query DirQuery;
+
+ /** True if 'query.json' exists. */
+ bool HaveQueryJson = false;
+
+ /** The 'query.json' content. */
+ ClientQueryJson QueryJson;
+ };
+
+ /** Whether the top-level query directory exists at all. */
+ bool QueryExists = false;
+
+ /** The content of the top-level query directory. */
+ Query TopQuery;
+
+ /** The content of each "client-$client" query directory. */
+ std::map<std::string, ClientQuery> ClientQueries;
+
+ /** Reply index object generated for object kind/version.
+ This populates the "objects" field of the reply index. */
+ std::map<Object, Json::Value> ReplyIndexObjects;
+
+ std::unique_ptr<Json::CharReader> JsonReader;
+ std::unique_ptr<Json::StreamWriter> JsonWriter;
+
+ bool ReadJsonFile(std::string const& file, Json::Value& value,
+ std::string& error);
+
+ std::string WriteJsonFile(
+ Json::Value const& value, std::string const& prefix,
+ std::string (*computeSuffix)(std::string const&) = ComputeSuffixHash);
+ static std::string ComputeSuffixHash(std::string const&);
+ static std::string ComputeSuffixTime(std::string const&);
+
+ static bool ReadQuery(std::string const& query,
+ std::vector<Object>& objects);
+ void ReadClient(std::string const& client);
+ void ReadClientQuery(std::string const& client, ClientQueryJson& q);
+
+ Json::Value BuildReplyIndex();
+ Json::Value BuildCMake();
+ Json::Value BuildReply(Query const& q);
+ static Json::Value BuildReplyError(std::string const& error);
+ Json::Value const& AddReplyIndexObject(Object const& o);
+
+ static const char* ObjectKindName(ObjectKind kind);
+ static std::string ObjectName(Object const& o);
+
+ static Json::Value BuildVersion(unsigned int major, unsigned int minor);
+
+ Json::Value BuildObject(Object const& object);
+
+ ClientRequests BuildClientRequests(Json::Value const& requests);
+ ClientRequest BuildClientRequest(Json::Value const& request);
+ Json::Value BuildClientReply(ClientQuery const& q);
+ Json::Value BuildClientReplyResponses(ClientRequests const& requests);
+ Json::Value BuildClientReplyResponse(ClientRequest const& request);
+
+ struct RequestVersion
+ {
+ unsigned int Major = 0;
+ unsigned int Minor = 0;
+ };
+ static bool ReadRequestVersions(Json::Value const& version,
+ std::vector<RequestVersion>& versions,
+ std::string& error);
+ static bool ReadRequestVersion(Json::Value const& version, bool inArray,
+ std::vector<RequestVersion>& result,
+ std::string& error);
+ static std::string NoSupportedVersion(
+ std::vector<RequestVersion> const& versions);
+
+ void BuildClientRequestCodeModel(
+ ClientRequest& r, std::vector<RequestVersion> const& versions);
+ Json::Value BuildCodeModel(Object const& object);
+
+ void BuildClientRequestCache(ClientRequest& r,
+ std::vector<RequestVersion> const& versions);
+ Json::Value BuildCache(Object const& object);
+
+ void BuildClientRequestCMakeFiles(
+ ClientRequest& r, std::vector<RequestVersion> const& versions);
+ Json::Value BuildCMakeFiles(Object const& object);
+
+ void BuildClientRequestInternalTest(
+ ClientRequest& r, std::vector<RequestVersion> const& versions);
+ Json::Value BuildInternalTest(Object const& object);
+};
+
+#endif
diff --git a/Source/cmFileAPICMakeFiles.cxx b/Source/cmFileAPICMakeFiles.cxx
new file mode 100644
index 000000000..5590bc2b3
--- /dev/null
+++ b/Source/cmFileAPICMakeFiles.cxx
@@ -0,0 +1,114 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmFileAPICMakeFiles.h"
+
+#include "cmFileAPI.h"
+#include "cmGlobalGenerator.h"
+#include "cmLocalGenerator.h"
+#include "cmMakefile.h"
+#include "cmSystemTools.h"
+#include "cmake.h"
+
+#include "cm_jsoncpp_value.h"
+
+#include <string>
+#include <vector>
+
+namespace {
+
+class CMakeFiles
+{
+ cmFileAPI& FileAPI;
+ unsigned long Version;
+ std::string CMakeModules;
+ std::string const& TopSource;
+ std::string const& TopBuild;
+ bool OutOfSource;
+
+ Json::Value DumpPaths();
+ Json::Value DumpInputs();
+ Json::Value DumpInput(std::string const& file);
+
+public:
+ CMakeFiles(cmFileAPI& fileAPI, unsigned long version);
+ Json::Value Dump();
+};
+
+CMakeFiles::CMakeFiles(cmFileAPI& fileAPI, unsigned long version)
+ : FileAPI(fileAPI)
+ , Version(version)
+ , CMakeModules(cmSystemTools::GetCMakeRoot() + "/Modules")
+ , TopSource(this->FileAPI.GetCMakeInstance()->GetHomeDirectory())
+ , TopBuild(this->FileAPI.GetCMakeInstance()->GetHomeOutputDirectory())
+ , OutOfSource(TopBuild != TopSource)
+{
+ static_cast<void>(this->Version);
+}
+
+Json::Value CMakeFiles::Dump()
+{
+ Json::Value cmakeFiles = Json::objectValue;
+ cmakeFiles["paths"] = this->DumpPaths();
+ cmakeFiles["inputs"] = DumpInputs();
+ return cmakeFiles;
+}
+
+Json::Value CMakeFiles::DumpPaths()
+{
+ Json::Value paths = Json::objectValue;
+ paths["source"] = this->TopSource;
+ paths["build"] = this->TopBuild;
+ return paths;
+}
+
+Json::Value CMakeFiles::DumpInputs()
+{
+ Json::Value inputs = Json::arrayValue;
+
+ cmGlobalGenerator* gg =
+ this->FileAPI.GetCMakeInstance()->GetGlobalGenerator();
+ for (cmLocalGenerator const* lg : gg->GetLocalGenerators()) {
+ cmMakefile const* mf = lg->GetMakefile();
+ for (std::string const& file : mf->GetListFiles()) {
+ inputs.append(this->DumpInput(file));
+ }
+ }
+
+ return inputs;
+}
+
+Json::Value CMakeFiles::DumpInput(std::string const& file)
+{
+ Json::Value input = Json::objectValue;
+
+ bool const isCMake = cmSystemTools::IsSubDirectory(file, this->CMakeModules);
+ if (isCMake) {
+ input["isCMake"] = true;
+ }
+
+ if (!cmSystemTools::IsSubDirectory(file, this->TopSource) &&
+ !cmSystemTools::IsSubDirectory(file, this->TopBuild)) {
+ input["isExternal"] = true;
+ }
+
+ if (this->OutOfSource &&
+ cmSystemTools::IsSubDirectory(file, this->TopBuild)) {
+ input["isGenerated"] = true;
+ }
+
+ std::string path = file;
+ if (!isCMake && cmSystemTools::IsSubDirectory(path, this->TopSource)) {
+ // Use a relative path within the source directory.
+ path = cmSystemTools::RelativePath(this->TopSource, path);
+ }
+ input["path"] = path;
+
+ return input;
+}
+}
+
+Json::Value cmFileAPICMakeFilesDump(cmFileAPI& fileAPI, unsigned long version)
+{
+ CMakeFiles cmakeFiles(fileAPI, version);
+ return cmakeFiles.Dump();
+}
diff --git a/Source/cmFileAPICMakeFiles.h b/Source/cmFileAPICMakeFiles.h
new file mode 100644
index 000000000..a851c32f4
--- /dev/null
+++ b/Source/cmFileAPICMakeFiles.h
@@ -0,0 +1,15 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#ifndef cmFileAPICMakeFiles_h
+#define cmFileAPICMakeFiles_h
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include "cm_jsoncpp_value.h"
+
+class cmFileAPI;
+
+extern Json::Value cmFileAPICMakeFilesDump(cmFileAPI& fileAPI,
+ unsigned long version);
+
+#endif
diff --git a/Source/cmFileAPICache.cxx b/Source/cmFileAPICache.cxx
new file mode 100644
index 000000000..f96bc90e5
--- /dev/null
+++ b/Source/cmFileAPICache.cxx
@@ -0,0 +1,106 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmFileAPICache.h"
+
+#include "cmFileAPI.h"
+#include "cmState.h"
+#include "cmake.h"
+
+#include "cm_jsoncpp_value.h"
+
+#include <algorithm>
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace {
+
+class Cache
+{
+ cmFileAPI& FileAPI;
+ unsigned long Version;
+ cmState* State;
+
+ Json::Value DumpEntries();
+ Json::Value DumpEntry(std::string const& name);
+ Json::Value DumpEntryProperties(std::string const& name);
+ Json::Value DumpEntryProperty(std::string const& name,
+ std::string const& prop);
+
+public:
+ Cache(cmFileAPI& fileAPI, unsigned long version);
+ Json::Value Dump();
+};
+
+Cache::Cache(cmFileAPI& fileAPI, unsigned long version)
+ : FileAPI(fileAPI)
+ , Version(version)
+ , State(this->FileAPI.GetCMakeInstance()->GetState())
+{
+ static_cast<void>(this->Version);
+}
+
+Json::Value Cache::Dump()
+{
+ Json::Value cache = Json::objectValue;
+ cache["entries"] = DumpEntries();
+ return cache;
+}
+
+Json::Value Cache::DumpEntries()
+{
+ Json::Value entries = Json::arrayValue;
+
+ std::vector<std::string> names = this->State->GetCacheEntryKeys();
+ std::sort(names.begin(), names.end());
+
+ for (std::string const& name : names) {
+ entries.append(this->DumpEntry(name));
+ }
+
+ return entries;
+}
+
+Json::Value Cache::DumpEntry(std::string const& name)
+{
+ Json::Value entry = Json::objectValue;
+ entry["name"] = name;
+ entry["type"] =
+ cmState::CacheEntryTypeToString(this->State->GetCacheEntryType(name));
+ entry["value"] = this->State->GetCacheEntryValue(name);
+
+ Json::Value properties = this->DumpEntryProperties(name);
+ if (!properties.empty()) {
+ entry["properties"] = std::move(properties);
+ }
+
+ return entry;
+}
+
+Json::Value Cache::DumpEntryProperties(std::string const& name)
+{
+ Json::Value properties = Json::arrayValue;
+ std::vector<std::string> props =
+ this->State->GetCacheEntryPropertyList(name);
+ std::sort(props.begin(), props.end());
+ for (std::string const& prop : props) {
+ properties.append(this->DumpEntryProperty(name, prop));
+ }
+ return properties;
+}
+
+Json::Value Cache::DumpEntryProperty(std::string const& name,
+ std::string const& prop)
+{
+ Json::Value property = Json::objectValue;
+ property["name"] = prop;
+ property["value"] = this->State->GetCacheEntryProperty(name, prop);
+ return property;
+}
+}
+
+Json::Value cmFileAPICacheDump(cmFileAPI& fileAPI, unsigned long version)
+{
+ Cache cache(fileAPI, version);
+ return cache.Dump();
+}
diff --git a/Source/cmFileAPICache.h b/Source/cmFileAPICache.h
new file mode 100644
index 000000000..09d9e1ce9
--- /dev/null
+++ b/Source/cmFileAPICache.h
@@ -0,0 +1,15 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#ifndef cmFileAPICache_h
+#define cmFileAPICache_h
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include "cm_jsoncpp_value.h"
+
+class cmFileAPI;
+
+extern Json::Value cmFileAPICacheDump(cmFileAPI& fileAPI,
+ unsigned long version);
+
+#endif
diff --git a/Source/cmFileAPICodemodel.cxx b/Source/cmFileAPICodemodel.cxx
new file mode 100644
index 000000000..fecbf63ca
--- /dev/null
+++ b/Source/cmFileAPICodemodel.cxx
@@ -0,0 +1,1250 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmFileAPICodemodel.h"
+
+#include "cmAlgorithms.h"
+#include "cmCryptoHash.h"
+#include "cmFileAPI.h"
+#include "cmGeneratorExpression.h"
+#include "cmGeneratorTarget.h"
+#include "cmGlobalGenerator.h"
+#include "cmInstallGenerator.h"
+#include "cmInstallSubdirectoryGenerator.h"
+#include "cmInstallTargetGenerator.h"
+#include "cmLinkLineComputer.h"
+#include "cmListFileCache.h"
+#include "cmLocalGenerator.h"
+#include "cmMakefile.h"
+#include "cmSourceFile.h"
+#include "cmSourceGroup.h"
+#include "cmState.h"
+#include "cmStateDirectory.h"
+#include "cmStateSnapshot.h"
+#include "cmStateTypes.h"
+#include "cmSystemTools.h"
+#include "cmTarget.h"
+#include "cmTargetDepend.h"
+#include "cmake.h"
+
+#include "cm_jsoncpp_value.h"
+
+#include <algorithm>
+#include <cassert>
+#include <map>
+#include <set>
+#include <string>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+namespace {
+
+class Codemodel
+{
+ cmFileAPI& FileAPI;
+ unsigned long Version;
+
+ Json::Value DumpPaths();
+ Json::Value DumpConfigurations();
+ Json::Value DumpConfiguration(std::string const& config);
+
+public:
+ Codemodel(cmFileAPI& fileAPI, unsigned long version);
+ Json::Value Dump();
+};
+
+class CodemodelConfig
+{
+ cmFileAPI& FileAPI;
+ unsigned long Version;
+ std::string const& Config;
+ std::string TopSource;
+ std::string TopBuild;
+
+ struct Directory
+ {
+ cmStateSnapshot Snapshot;
+ cmLocalGenerator const* LocalGenerator = nullptr;
+ Json::Value TargetIndexes = Json::arrayValue;
+ Json::ArrayIndex ProjectIndex;
+ bool HasInstallRule = false;
+ };
+ std::map<cmStateSnapshot, Json::ArrayIndex, cmStateSnapshot::StrictWeakOrder>
+ DirectoryMap;
+ std::vector<Directory> Directories;
+
+ struct Project
+ {
+ cmStateSnapshot Snapshot;
+ static const Json::ArrayIndex NoParentIndex =
+ static_cast<Json::ArrayIndex>(-1);
+ Json::ArrayIndex ParentIndex = NoParentIndex;
+ Json::Value ChildIndexes = Json::arrayValue;
+ Json::Value DirectoryIndexes = Json::arrayValue;
+ Json::Value TargetIndexes = Json::arrayValue;
+ };
+ std::map<cmStateSnapshot, Json::ArrayIndex, cmStateSnapshot::StrictWeakOrder>
+ ProjectMap;
+ std::vector<Project> Projects;
+
+ void ProcessDirectories();
+
+ Json::ArrayIndex GetDirectoryIndex(cmLocalGenerator const* lg);
+ Json::ArrayIndex GetDirectoryIndex(cmStateSnapshot s);
+
+ Json::ArrayIndex AddProject(cmStateSnapshot s);
+
+ Json::Value DumpTargets();
+ Json::Value DumpTarget(cmGeneratorTarget* gt, Json::ArrayIndex ti);
+
+ Json::Value DumpDirectories();
+ Json::Value DumpDirectory(Directory& d);
+
+ Json::Value DumpProjects();
+ Json::Value DumpProject(Project& p);
+
+ Json::Value DumpMinimumCMakeVersion(cmStateSnapshot s);
+
+public:
+ CodemodelConfig(cmFileAPI& fileAPI, unsigned long version,
+ std::string const& config);
+ Json::Value Dump();
+};
+
+std::string RelativeIfUnder(std::string const& top, std::string const& in)
+{
+ std::string out;
+ if (in == top) {
+ out = ".";
+ } else if (cmSystemTools::IsSubDirectory(in, top)) {
+ out = in.substr(top.size() + 1);
+ } else {
+ out = in;
+ }
+ return out;
+}
+
+std::string TargetId(cmGeneratorTarget const* gt, std::string const& topBuild)
+{
+ cmCryptoHash hasher(cmCryptoHash::AlgoSHA3_256);
+ std::string path = RelativeIfUnder(
+ topBuild, gt->GetLocalGenerator()->GetCurrentBinaryDirectory());
+ std::string hash = hasher.HashString(path);
+ hash.resize(20, '0');
+ return gt->GetName() + CMAKE_DIRECTORY_ID_SEP + hash;
+}
+
+class BacktraceData
+{
+ std::string TopSource;
+ std::unordered_map<std::string, Json::ArrayIndex> CommandMap;
+ std::unordered_map<std::string, Json::ArrayIndex> FileMap;
+ std::unordered_map<cmListFileContext const*, Json::ArrayIndex> NodeMap;
+ Json::Value Commands = Json::arrayValue;
+ Json::Value Files = Json::arrayValue;
+ Json::Value Nodes = Json::arrayValue;
+
+ Json::ArrayIndex AddCommand(std::string const& command)
+ {
+ auto i = this->CommandMap.find(command);
+ if (i == this->CommandMap.end()) {
+ auto cmdIndex = static_cast<Json::ArrayIndex>(this->Commands.size());
+ i = this->CommandMap.emplace(command, cmdIndex).first;
+ this->Commands.append(command);
+ }
+ return i->second;
+ }
+
+ Json::ArrayIndex AddFile(std::string const& file)
+ {
+ auto i = this->FileMap.find(file);
+ if (i == this->FileMap.end()) {
+ auto fileIndex = static_cast<Json::ArrayIndex>(this->Files.size());
+ i = this->FileMap.emplace(file, fileIndex).first;
+ this->Files.append(RelativeIfUnder(this->TopSource, file));
+ }
+ return i->second;
+ }
+
+public:
+ BacktraceData(std::string topSource);
+ bool Add(cmListFileBacktrace const& bt, Json::ArrayIndex& index);
+ Json::Value Dump();
+};
+
+BacktraceData::BacktraceData(std::string topSource)
+ : TopSource(std::move(topSource))
+{
+}
+
+bool BacktraceData::Add(cmListFileBacktrace const& bt, Json::ArrayIndex& index)
+{
+ if (bt.Empty()) {
+ return false;
+ }
+ cmListFileContext const* top = &bt.Top();
+ auto found = this->NodeMap.find(top);
+ if (found != this->NodeMap.end()) {
+ index = found->second;
+ return true;
+ }
+ Json::Value entry = Json::objectValue;
+ entry["file"] = this->AddFile(top->FilePath);
+ if (top->Line) {
+ entry["line"] = static_cast<int>(top->Line);
+ }
+ if (!top->Name.empty()) {
+ entry["command"] = this->AddCommand(top->Name);
+ }
+ Json::ArrayIndex parent;
+ if (this->Add(bt.Pop(), parent)) {
+ entry["parent"] = parent;
+ }
+ index = this->NodeMap[top] = this->Nodes.size();
+ this->Nodes.append(std::move(entry)); // NOLINT(*)
+ return true;
+}
+
+Json::Value BacktraceData::Dump()
+{
+ Json::Value backtraceGraph;
+ this->CommandMap.clear();
+ this->FileMap.clear();
+ this->NodeMap.clear();
+ backtraceGraph["commands"] = std::move(this->Commands);
+ backtraceGraph["files"] = std::move(this->Files);
+ backtraceGraph["nodes"] = std::move(this->Nodes);
+ return backtraceGraph;
+}
+
+struct CompileData
+{
+ struct IncludeEntry
+ {
+ BT<std::string> Path;
+ bool IsSystem = false;
+ IncludeEntry(BT<std::string> path, bool isSystem)
+ : Path(std::move(path))
+ , IsSystem(isSystem)
+ {
+ }
+ };
+
+ void SetDefines(std::set<BT<std::string>> const& defines);
+
+ std::string Language;
+ std::string Sysroot;
+ std::vector<BT<std::string>> Flags;
+ std::vector<BT<std::string>> Defines;
+ std::vector<IncludeEntry> Includes;
+};
+
+void CompileData::SetDefines(std::set<BT<std::string>> const& defines)
+{
+ this->Defines.reserve(defines.size());
+ for (BT<std::string> const& d : defines) {
+ this->Defines.push_back(d);
+ }
+}
+
+class Target
+{
+ cmGeneratorTarget* GT;
+ std::string const& Config;
+ std::string TopSource;
+ std::string TopBuild;
+ std::vector<cmSourceGroup> SourceGroupsLocal;
+ BacktraceData Backtraces;
+
+ std::map<std::string, CompileData> CompileDataMap;
+
+ std::unordered_map<cmSourceFile const*, Json::ArrayIndex> SourceMap;
+ Json::Value Sources = Json::arrayValue;
+
+ struct SourceGroup
+ {
+ std::string Name;
+ Json::Value SourceIndexes = Json::arrayValue;
+ };
+ std::unordered_map<cmSourceGroup const*, Json::ArrayIndex> SourceGroupsMap;
+ std::vector<SourceGroup> SourceGroups;
+
+ struct CompileGroup
+ {
+ std::map<Json::Value, Json::ArrayIndex>::iterator Entry;
+ Json::Value SourceIndexes = Json::arrayValue;
+ };
+ std::map<Json::Value, Json::ArrayIndex> CompileGroupMap;
+ std::vector<CompileGroup> CompileGroups;
+
+ void ProcessLanguages();
+ void ProcessLanguage(std::string const& lang);
+
+ Json::ArrayIndex AddSourceGroup(cmSourceGroup* sg, Json::ArrayIndex si);
+ CompileData BuildCompileData(cmSourceFile* sf);
+ Json::ArrayIndex AddSourceCompileGroup(cmSourceFile* sf,
+ Json::ArrayIndex si);
+ void AddBacktrace(Json::Value& object, cmListFileBacktrace const& bt);
+ Json::Value DumpPaths();
+ Json::Value DumpCompileData(CompileData cd);
+ Json::Value DumpInclude(CompileData::IncludeEntry const& inc);
+ Json::Value DumpDefine(BT<std::string> const& def);
+ Json::Value DumpSources();
+ Json::Value DumpSource(cmGeneratorTarget::SourceAndKind const& sk,
+ Json::ArrayIndex si);
+ Json::Value DumpSourceGroups();
+ Json::Value DumpSourceGroup(SourceGroup& sg);
+ Json::Value DumpCompileGroups();
+ Json::Value DumpCompileGroup(CompileGroup& cg);
+ Json::Value DumpSysroot(std::string const& path);
+ Json::Value DumpInstall();
+ Json::Value DumpInstallPrefix();
+ Json::Value DumpInstallDestinations();
+ Json::Value DumpInstallDestination(cmInstallTargetGenerator* itGen);
+ Json::Value DumpArtifacts();
+ Json::Value DumpLink();
+ Json::Value DumpArchive();
+ Json::Value DumpLinkCommandFragments();
+ Json::Value DumpCommandFragments(std::vector<BT<std::string>> const& frags);
+ Json::Value DumpCommandFragment(BT<std::string> const& frag,
+ std::string const& role = std::string());
+ Json::Value DumpDependencies();
+ Json::Value DumpDependency(cmTargetDepend const& td);
+ Json::Value DumpFolder();
+
+public:
+ Target(cmGeneratorTarget* gt, std::string const& config);
+ Json::Value Dump();
+};
+
+Codemodel::Codemodel(cmFileAPI& fileAPI, unsigned long version)
+ : FileAPI(fileAPI)
+ , Version(version)
+{
+}
+
+Json::Value Codemodel::Dump()
+{
+ Json::Value codemodel = Json::objectValue;
+
+ codemodel["paths"] = this->DumpPaths();
+ codemodel["configurations"] = this->DumpConfigurations();
+
+ return codemodel;
+}
+
+Json::Value Codemodel::DumpPaths()
+{
+ Json::Value paths = Json::objectValue;
+ paths["source"] = this->FileAPI.GetCMakeInstance()->GetHomeDirectory();
+ paths["build"] = this->FileAPI.GetCMakeInstance()->GetHomeOutputDirectory();
+ return paths;
+}
+
+Json::Value Codemodel::DumpConfigurations()
+{
+ std::vector<std::string> configs;
+ cmGlobalGenerator* gg =
+ this->FileAPI.GetCMakeInstance()->GetGlobalGenerator();
+ auto makefiles = gg->GetMakefiles();
+ if (!makefiles.empty()) {
+ makefiles[0]->GetConfigurations(configs);
+ if (configs.empty()) {
+ configs.emplace_back();
+ }
+ }
+ Json::Value configurations = Json::arrayValue;
+ for (std::string const& config : configs) {
+ configurations.append(this->DumpConfiguration(config));
+ }
+ return configurations;
+}
+
+Json::Value Codemodel::DumpConfiguration(std::string const& config)
+{
+ CodemodelConfig configuration(this->FileAPI, this->Version, config);
+ return configuration.Dump();
+}
+
+CodemodelConfig::CodemodelConfig(cmFileAPI& fileAPI, unsigned long version,
+ std::string const& config)
+ : FileAPI(fileAPI)
+ , Version(version)
+ , Config(config)
+ , TopSource(this->FileAPI.GetCMakeInstance()->GetHomeDirectory())
+ , TopBuild(this->FileAPI.GetCMakeInstance()->GetHomeOutputDirectory())
+{
+ static_cast<void>(this->Version);
+}
+
+Json::Value CodemodelConfig::Dump()
+{
+ Json::Value configuration = Json::objectValue;
+ configuration["name"] = this->Config;
+ this->ProcessDirectories();
+ configuration["targets"] = this->DumpTargets();
+ configuration["directories"] = this->DumpDirectories();
+ configuration["projects"] = this->DumpProjects();
+ return configuration;
+}
+
+void CodemodelConfig::ProcessDirectories()
+{
+ cmGlobalGenerator* gg =
+ this->FileAPI.GetCMakeInstance()->GetGlobalGenerator();
+ std::vector<cmLocalGenerator*> const& localGens = gg->GetLocalGenerators();
+
+ // Add directories in forward order to process parents before children.
+ this->Directories.reserve(localGens.size());
+ for (cmLocalGenerator* lg : localGens) {
+ auto directoryIndex =
+ static_cast<Json::ArrayIndex>(this->Directories.size());
+ this->Directories.emplace_back();
+ Directory& d = this->Directories[directoryIndex];
+ d.Snapshot = lg->GetStateSnapshot().GetBuildsystemDirectory();
+ d.LocalGenerator = lg;
+ this->DirectoryMap[d.Snapshot] = directoryIndex;
+
+ d.ProjectIndex = this->AddProject(d.Snapshot);
+ this->Projects[d.ProjectIndex].DirectoryIndexes.append(directoryIndex);
+ }
+
+ // Update directories in reverse order to process children before parents.
+ for (auto di = this->Directories.rbegin(); di != this->Directories.rend();
+ ++di) {
+ Directory& d = *di;
+
+ // Accumulate the presence of install rules on the way up.
+ for (auto gen : d.LocalGenerator->GetMakefile()->GetInstallGenerators()) {
+ if (!dynamic_cast<cmInstallSubdirectoryGenerator*>(gen)) {
+ d.HasInstallRule = true;
+ break;
+ }
+ }
+ if (!d.HasInstallRule) {
+ for (cmStateSnapshot const& child : d.Snapshot.GetChildren()) {
+ cmStateSnapshot childDir = child.GetBuildsystemDirectory();
+ Json::ArrayIndex const childIndex = this->GetDirectoryIndex(childDir);
+ if (this->Directories[childIndex].HasInstallRule) {
+ d.HasInstallRule = true;
+ break;
+ }
+ }
+ }
+ }
+}
+
+Json::ArrayIndex CodemodelConfig::GetDirectoryIndex(cmLocalGenerator const* lg)
+{
+ return this->GetDirectoryIndex(
+ lg->GetStateSnapshot().GetBuildsystemDirectory());
+}
+
+Json::ArrayIndex CodemodelConfig::GetDirectoryIndex(cmStateSnapshot s)
+{
+ auto i = this->DirectoryMap.find(s);
+ assert(i != this->DirectoryMap.end());
+ return i->second;
+}
+
+Json::ArrayIndex CodemodelConfig::AddProject(cmStateSnapshot s)
+{
+ cmStateSnapshot ps = s.GetBuildsystemDirectoryParent();
+ if (ps.IsValid() && ps.GetProjectName() == s.GetProjectName()) {
+ // This directory is part of its parent directory project.
+ Json::ArrayIndex const parentDirIndex = this->GetDirectoryIndex(ps);
+ return this->Directories[parentDirIndex].ProjectIndex;
+ }
+
+ // This directory starts a new project.
+ auto projectIndex = static_cast<Json::ArrayIndex>(this->Projects.size());
+ this->Projects.emplace_back();
+ Project& p = this->Projects[projectIndex];
+ p.Snapshot = s;
+ this->ProjectMap[s] = projectIndex;
+ if (ps.IsValid()) {
+ Json::ArrayIndex const parentDirIndex = this->GetDirectoryIndex(ps);
+ p.ParentIndex = this->Directories[parentDirIndex].ProjectIndex;
+ this->Projects[p.ParentIndex].ChildIndexes.append(projectIndex);
+ }
+ return projectIndex;
+}
+
+Json::Value CodemodelConfig::DumpTargets()
+{
+ Json::Value targets = Json::arrayValue;
+
+ std::vector<cmGeneratorTarget*> targetList;
+ cmGlobalGenerator* gg =
+ this->FileAPI.GetCMakeInstance()->GetGlobalGenerator();
+ for (cmLocalGenerator const* lg : gg->GetLocalGenerators()) {
+ cmAppend(targetList, lg->GetGeneratorTargets());
+ }
+ std::sort(targetList.begin(), targetList.end(),
+ [](cmGeneratorTarget* l, cmGeneratorTarget* r) {
+ return l->GetName() < r->GetName();
+ });
+
+ for (cmGeneratorTarget* gt : targetList) {
+ if (gt->GetType() == cmStateEnums::GLOBAL_TARGET ||
+ gt->GetType() == cmStateEnums::INTERFACE_LIBRARY) {
+ continue;
+ }
+
+ targets.append(this->DumpTarget(gt, targets.size()));
+ }
+
+ return targets;
+}
+
+Json::Value CodemodelConfig::DumpTarget(cmGeneratorTarget* gt,
+ Json::ArrayIndex ti)
+{
+ Target t(gt, this->Config);
+ std::string prefix = "target-" + gt->GetName();
+ for (char& c : prefix) {
+ // CMP0037 OLD behavior allows slashes in target names. Remove them.
+ if (c == '/' || c == '\\') {
+ c = '_';
+ }
+ }
+ if (!this->Config.empty()) {
+ prefix += "-" + this->Config;
+ }
+ Json::Value target = this->FileAPI.MaybeJsonFile(t.Dump(), prefix);
+ target["name"] = gt->GetName();
+ target["id"] = TargetId(gt, this->TopBuild);
+
+ // Cross-reference directory containing target.
+ Json::ArrayIndex di = this->GetDirectoryIndex(gt->GetLocalGenerator());
+ target["directoryIndex"] = di;
+ this->Directories[di].TargetIndexes.append(ti);
+
+ // Cross-reference project containing target.
+ Json::ArrayIndex pi = this->Directories[di].ProjectIndex;
+ target["projectIndex"] = pi;
+ this->Projects[pi].TargetIndexes.append(ti);
+
+ return target;
+}
+
+Json::Value CodemodelConfig::DumpDirectories()
+{
+ Json::Value directories = Json::arrayValue;
+ for (Directory& d : this->Directories) {
+ directories.append(this->DumpDirectory(d));
+ }
+ return directories;
+}
+
+Json::Value CodemodelConfig::DumpDirectory(Directory& d)
+{
+ Json::Value directory = Json::objectValue;
+
+ std::string sourceDir = d.Snapshot.GetDirectory().GetCurrentSource();
+ directory["source"] = RelativeIfUnder(this->TopSource, sourceDir);
+
+ std::string buildDir = d.Snapshot.GetDirectory().GetCurrentBinary();
+ directory["build"] = RelativeIfUnder(this->TopBuild, buildDir);
+
+ cmStateSnapshot parentDir = d.Snapshot.GetBuildsystemDirectoryParent();
+ if (parentDir.IsValid()) {
+ directory["parentIndex"] = this->GetDirectoryIndex(parentDir);
+ }
+
+ Json::Value childIndexes = Json::arrayValue;
+ for (cmStateSnapshot const& child : d.Snapshot.GetChildren()) {
+ childIndexes.append(
+ this->GetDirectoryIndex(child.GetBuildsystemDirectory()));
+ }
+ if (!childIndexes.empty()) {
+ directory["childIndexes"] = std::move(childIndexes);
+ }
+
+ directory["projectIndex"] = d.ProjectIndex;
+
+ if (!d.TargetIndexes.empty()) {
+ directory["targetIndexes"] = std::move(d.TargetIndexes);
+ }
+
+ Json::Value minimumCMakeVersion = this->DumpMinimumCMakeVersion(d.Snapshot);
+ if (!minimumCMakeVersion.isNull()) {
+ directory["minimumCMakeVersion"] = std::move(minimumCMakeVersion);
+ }
+
+ if (d.HasInstallRule) {
+ directory["hasInstallRule"] = true;
+ }
+
+ return directory;
+}
+
+Json::Value CodemodelConfig::DumpProjects()
+{
+ Json::Value projects = Json::arrayValue;
+ for (Project& p : this->Projects) {
+ projects.append(this->DumpProject(p));
+ }
+ return projects;
+}
+
+Json::Value CodemodelConfig::DumpProject(Project& p)
+{
+ Json::Value project = Json::objectValue;
+
+ project["name"] = p.Snapshot.GetProjectName();
+
+ if (p.ParentIndex != Project::NoParentIndex) {
+ project["parentIndex"] = p.ParentIndex;
+ }
+
+ if (!p.ChildIndexes.empty()) {
+ project["childIndexes"] = std::move(p.ChildIndexes);
+ }
+
+ project["directoryIndexes"] = std::move(p.DirectoryIndexes);
+
+ if (!p.TargetIndexes.empty()) {
+ project["targetIndexes"] = std::move(p.TargetIndexes);
+ }
+
+ return project;
+}
+
+Json::Value CodemodelConfig::DumpMinimumCMakeVersion(cmStateSnapshot s)
+{
+ Json::Value minimumCMakeVersion;
+ if (std::string const* def =
+ s.GetDefinition("CMAKE_MINIMUM_REQUIRED_VERSION")) {
+ minimumCMakeVersion = Json::objectValue;
+ minimumCMakeVersion["string"] = *def;
+ }
+ return minimumCMakeVersion;
+}
+
+Target::Target(cmGeneratorTarget* gt, std::string const& config)
+ : GT(gt)
+ , Config(config)
+ , TopSource(gt->GetGlobalGenerator()->GetCMakeInstance()->GetHomeDirectory())
+ , TopBuild(
+ gt->GetGlobalGenerator()->GetCMakeInstance()->GetHomeOutputDirectory())
+ , SourceGroupsLocal(this->GT->Makefile->GetSourceGroups())
+ , Backtraces(this->TopSource)
+{
+}
+
+Json::Value Target::Dump()
+{
+ Json::Value target = Json::objectValue;
+
+ cmStateEnums::TargetType const type = this->GT->GetType();
+
+ target["name"] = this->GT->GetName();
+ target["type"] = cmState::GetTargetTypeName(type);
+ target["id"] = TargetId(this->GT, this->TopBuild);
+ target["paths"] = this->DumpPaths();
+ if (this->GT->Target->GetIsGeneratorProvided()) {
+ target["isGeneratorProvided"] = true;
+ }
+
+ this->AddBacktrace(target, this->GT->GetBacktrace());
+
+ if (this->GT->Target->GetHaveInstallRule()) {
+ target["install"] = this->DumpInstall();
+ }
+
+ if (this->GT->HaveWellDefinedOutputFiles()) {
+ Json::Value artifacts = this->DumpArtifacts();
+ if (!artifacts.empty()) {
+ target["artifacts"] = std::move(artifacts);
+ }
+ }
+
+ if (type == cmStateEnums::EXECUTABLE ||
+ type == cmStateEnums::SHARED_LIBRARY ||
+ type == cmStateEnums::MODULE_LIBRARY) {
+ target["nameOnDisk"] = this->GT->GetFullName(this->Config);
+ target["link"] = this->DumpLink();
+ } else if (type == cmStateEnums::STATIC_LIBRARY) {
+ target["nameOnDisk"] = this->GT->GetFullName(this->Config);
+ target["archive"] = this->DumpArchive();
+ }
+
+ Json::Value dependencies = this->DumpDependencies();
+ if (!dependencies.empty()) {
+ target["dependencies"] = dependencies;
+ }
+
+ {
+ this->ProcessLanguages();
+
+ target["sources"] = this->DumpSources();
+
+ Json::Value folder = this->DumpFolder();
+ if (!folder.isNull()) {
+ target["folder"] = std::move(folder);
+ }
+
+ Json::Value sourceGroups = this->DumpSourceGroups();
+ if (!sourceGroups.empty()) {
+ target["sourceGroups"] = std::move(sourceGroups);
+ }
+
+ Json::Value compileGroups = this->DumpCompileGroups();
+ if (!compileGroups.empty()) {
+ target["compileGroups"] = std::move(compileGroups);
+ }
+ }
+
+ target["backtraceGraph"] = this->Backtraces.Dump();
+
+ return target;
+}
+
+void Target::ProcessLanguages()
+{
+ std::set<std::string> languages;
+ this->GT->GetLanguages(languages, this->Config);
+ for (std::string const& lang : languages) {
+ this->ProcessLanguage(lang);
+ }
+}
+
+void Target::ProcessLanguage(std::string const& lang)
+{
+ CompileData& cd = this->CompileDataMap[lang];
+ cd.Language = lang;
+ if (const char* sysrootCompile =
+ this->GT->Makefile->GetDefinition("CMAKE_SYSROOT_COMPILE")) {
+ cd.Sysroot = sysrootCompile;
+ } else if (const char* sysroot =
+ this->GT->Makefile->GetDefinition("CMAKE_SYSROOT")) {
+ cd.Sysroot = sysroot;
+ }
+ cmLocalGenerator* lg = this->GT->GetLocalGenerator();
+ {
+ // FIXME: Add flags from end section of ExpandRuleVariable,
+ // which may need to be factored out.
+ std::string flags;
+ lg->GetTargetCompileFlags(this->GT, this->Config, lang, flags);
+ cd.Flags.emplace_back(std::move(flags), cmListFileBacktrace());
+ }
+ std::set<BT<std::string>> defines =
+ lg->GetTargetDefines(this->GT, this->Config, lang);
+ cd.SetDefines(defines);
+ std::vector<BT<std::string>> includePathList =
+ lg->GetIncludeDirectories(this->GT, lang, this->Config);
+ for (BT<std::string> const& i : includePathList) {
+ cd.Includes.emplace_back(
+ i, this->GT->IsSystemIncludeDirectory(i.Value, this->Config, lang));
+ }
+}
+
+Json::ArrayIndex Target::AddSourceGroup(cmSourceGroup* sg, Json::ArrayIndex si)
+{
+ std::unordered_map<cmSourceGroup const*, Json::ArrayIndex>::iterator i =
+ this->SourceGroupsMap.find(sg);
+ if (i == this->SourceGroupsMap.end()) {
+ auto sgIndex = static_cast<Json::ArrayIndex>(this->SourceGroups.size());
+ i = this->SourceGroupsMap.emplace(sg, sgIndex).first;
+ SourceGroup g;
+ g.Name = sg->GetFullName();
+ this->SourceGroups.push_back(std::move(g));
+ }
+ this->SourceGroups[i->second].SourceIndexes.append(si);
+ return i->second;
+}
+
+CompileData Target::BuildCompileData(cmSourceFile* sf)
+{
+ CompileData fd;
+
+ fd.Language = sf->GetLanguage();
+ if (fd.Language.empty()) {
+ return fd;
+ }
+ CompileData const& cd = this->CompileDataMap.at(fd.Language);
+
+ fd.Sysroot = cd.Sysroot;
+
+ cmLocalGenerator* lg = this->GT->GetLocalGenerator();
+ cmGeneratorExpressionInterpreter genexInterpreter(lg, this->Config, this->GT,
+ fd.Language);
+
+ fd.Flags = cd.Flags;
+ const std::string COMPILE_FLAGS("COMPILE_FLAGS");
+ if (const char* cflags = sf->GetProperty(COMPILE_FLAGS)) {
+ std::string flags = genexInterpreter.Evaluate(cflags, COMPILE_FLAGS);
+ fd.Flags.emplace_back(std::move(flags), cmListFileBacktrace());
+ }
+ const std::string COMPILE_OPTIONS("COMPILE_OPTIONS");
+ if (const char* coptions = sf->GetProperty(COMPILE_OPTIONS)) {
+ std::string flags;
+ lg->AppendCompileOptions(
+ flags, genexInterpreter.Evaluate(coptions, COMPILE_OPTIONS));
+ fd.Flags.emplace_back(std::move(flags), cmListFileBacktrace());
+ }
+
+ // Add include directories from source file properties.
+ {
+ std::vector<std::string> includes;
+ const std::string INCLUDE_DIRECTORIES("INCLUDE_DIRECTORIES");
+ if (const char* cincludes = sf->GetProperty(INCLUDE_DIRECTORIES)) {
+ const std::string& evaluatedIncludes =
+ genexInterpreter.Evaluate(cincludes, INCLUDE_DIRECTORIES);
+ lg->AppendIncludeDirectories(includes, evaluatedIncludes, *sf);
+
+ for (std::string const& include : includes) {
+ bool const isSystemInclude = this->GT->IsSystemIncludeDirectory(
+ include, this->Config, fd.Language);
+ fd.Includes.emplace_back(include, isSystemInclude);
+ }
+ }
+ }
+ fd.Includes.insert(fd.Includes.end(), cd.Includes.begin(),
+ cd.Includes.end());
+
+ const std::string COMPILE_DEFINITIONS("COMPILE_DEFINITIONS");
+ std::set<std::string> fileDefines;
+ if (const char* defs = sf->GetProperty(COMPILE_DEFINITIONS)) {
+ lg->AppendDefines(fileDefines,
+ genexInterpreter.Evaluate(defs, COMPILE_DEFINITIONS));
+ }
+
+ const std::string defPropName =
+ "COMPILE_DEFINITIONS_" + cmSystemTools::UpperCase(this->Config);
+ if (const char* config_defs = sf->GetProperty(defPropName)) {
+ lg->AppendDefines(
+ fileDefines,
+ genexInterpreter.Evaluate(config_defs, COMPILE_DEFINITIONS));
+ }
+
+ std::set<BT<std::string>> defines;
+ defines.insert(fileDefines.begin(), fileDefines.end());
+ defines.insert(cd.Defines.begin(), cd.Defines.end());
+
+ fd.SetDefines(defines);
+
+ return fd;
+}
+
+Json::ArrayIndex Target::AddSourceCompileGroup(cmSourceFile* sf,
+ Json::ArrayIndex si)
+{
+ Json::Value compileDataJson =
+ this->DumpCompileData(this->BuildCompileData(sf));
+ std::map<Json::Value, Json::ArrayIndex>::iterator i =
+ this->CompileGroupMap.find(compileDataJson);
+ if (i == this->CompileGroupMap.end()) {
+ Json::ArrayIndex cgIndex =
+ static_cast<Json::ArrayIndex>(this->CompileGroups.size());
+ i =
+ this->CompileGroupMap.emplace(std::move(compileDataJson), cgIndex).first;
+ CompileGroup g;
+ g.Entry = i;
+ this->CompileGroups.push_back(std::move(g));
+ }
+ this->CompileGroups[i->second].SourceIndexes.append(si);
+ return i->second;
+}
+
+void Target::AddBacktrace(Json::Value& object, cmListFileBacktrace const& bt)
+{
+ Json::ArrayIndex backtrace;
+ if (this->Backtraces.Add(bt, backtrace)) {
+ object["backtrace"] = backtrace;
+ }
+}
+
+Json::Value Target::DumpPaths()
+{
+ Json::Value paths = Json::objectValue;
+ cmLocalGenerator* lg = this->GT->GetLocalGenerator();
+
+ std::string const& sourceDir = lg->GetCurrentSourceDirectory();
+ paths["source"] = RelativeIfUnder(this->TopSource, sourceDir);
+
+ std::string const& buildDir = lg->GetCurrentBinaryDirectory();
+ paths["build"] = RelativeIfUnder(this->TopBuild, buildDir);
+
+ return paths;
+}
+
+Json::Value Target::DumpSources()
+{
+ Json::Value sources = Json::arrayValue;
+ cmGeneratorTarget::KindedSources const& kinded =
+ this->GT->GetKindedSources(this->Config);
+ for (cmGeneratorTarget::SourceAndKind const& sk : kinded.Sources) {
+ sources.append(this->DumpSource(sk, sources.size()));
+ }
+ return sources;
+}
+
+Json::Value Target::DumpSource(cmGeneratorTarget::SourceAndKind const& sk,
+ Json::ArrayIndex si)
+{
+ Json::Value source = Json::objectValue;
+
+ std::string const path = sk.Source.Value->GetFullPath();
+ source["path"] = RelativeIfUnder(this->TopSource, path);
+ if (sk.Source.Value->GetIsGenerated()) {
+ source["isGenerated"] = true;
+ }
+ this->AddBacktrace(source, sk.Source.Backtrace);
+
+ if (cmSourceGroup* sg =
+ this->GT->Makefile->FindSourceGroup(path, this->SourceGroupsLocal)) {
+ source["sourceGroupIndex"] = this->AddSourceGroup(sg, si);
+ }
+
+ switch (sk.Kind) {
+ case cmGeneratorTarget::SourceKindObjectSource: {
+ source["compileGroupIndex"] =
+ this->AddSourceCompileGroup(sk.Source.Value, si);
+ } break;
+ case cmGeneratorTarget::SourceKindAppManifest:
+ case cmGeneratorTarget::SourceKindCertificate:
+ case cmGeneratorTarget::SourceKindCustomCommand:
+ case cmGeneratorTarget::SourceKindExternalObject:
+ case cmGeneratorTarget::SourceKindExtra:
+ case cmGeneratorTarget::SourceKindHeader:
+ case cmGeneratorTarget::SourceKindIDL:
+ case cmGeneratorTarget::SourceKindManifest:
+ case cmGeneratorTarget::SourceKindModuleDefinition:
+ case cmGeneratorTarget::SourceKindResx:
+ case cmGeneratorTarget::SourceKindXaml:
+ break;
+ }
+
+ return source;
+}
+
+Json::Value Target::DumpCompileData(CompileData cd)
+{
+ Json::Value result = Json::objectValue;
+
+ if (!cd.Language.empty()) {
+ result["language"] = cd.Language;
+ }
+ if (!cd.Sysroot.empty()) {
+ result["sysroot"] = this->DumpSysroot(cd.Sysroot);
+ }
+ if (!cd.Flags.empty()) {
+ result["compileCommandFragments"] = this->DumpCommandFragments(cd.Flags);
+ }
+ if (!cd.Includes.empty()) {
+ Json::Value includes = Json::arrayValue;
+ for (auto const& i : cd.Includes) {
+ includes.append(this->DumpInclude(i));
+ }
+ result["includes"] = includes;
+ }
+ if (!cd.Defines.empty()) {
+ Json::Value defines = Json::arrayValue;
+ for (BT<std::string> const& d : cd.Defines) {
+ defines.append(this->DumpDefine(d));
+ }
+ result["defines"] = std::move(defines);
+ }
+
+ return result;
+}
+
+Json::Value Target::DumpInclude(CompileData::IncludeEntry const& inc)
+{
+ Json::Value include = Json::objectValue;
+ include["path"] = inc.Path.Value;
+ if (inc.IsSystem) {
+ include["isSystem"] = true;
+ }
+ this->AddBacktrace(include, inc.Path.Backtrace);
+ return include;
+}
+
+Json::Value Target::DumpDefine(BT<std::string> const& def)
+{
+ Json::Value define = Json::objectValue;
+ define["define"] = def.Value;
+ this->AddBacktrace(define, def.Backtrace);
+ return define;
+}
+
+Json::Value Target::DumpSourceGroups()
+{
+ Json::Value sourceGroups = Json::arrayValue;
+ for (auto& sg : this->SourceGroups) {
+ sourceGroups.append(this->DumpSourceGroup(sg));
+ }
+ return sourceGroups;
+}
+
+Json::Value Target::DumpSourceGroup(SourceGroup& sg)
+{
+ Json::Value group = Json::objectValue;
+ group["name"] = sg.Name;
+ group["sourceIndexes"] = std::move(sg.SourceIndexes);
+ return group;
+}
+
+Json::Value Target::DumpCompileGroups()
+{
+ Json::Value compileGroups = Json::arrayValue;
+ for (auto& cg : this->CompileGroups) {
+ compileGroups.append(this->DumpCompileGroup(cg));
+ }
+ return compileGroups;
+}
+
+Json::Value Target::DumpCompileGroup(CompileGroup& cg)
+{
+ Json::Value group = cg.Entry->first;
+ group["sourceIndexes"] = std::move(cg.SourceIndexes);
+ return group;
+}
+
+Json::Value Target::DumpSysroot(std::string const& path)
+{
+ Json::Value sysroot = Json::objectValue;
+ sysroot["path"] = path;
+ return sysroot;
+}
+
+Json::Value Target::DumpInstall()
+{
+ Json::Value install = Json::objectValue;
+ install["prefix"] = this->DumpInstallPrefix();
+ install["destinations"] = this->DumpInstallDestinations();
+ return install;
+}
+
+Json::Value Target::DumpInstallPrefix()
+{
+ Json::Value prefix = Json::objectValue;
+ std::string p =
+ this->GT->Makefile->GetSafeDefinition("CMAKE_INSTALL_PREFIX");
+ cmSystemTools::ConvertToUnixSlashes(p);
+ prefix["path"] = p;
+ return prefix;
+}
+
+Json::Value Target::DumpInstallDestinations()
+{
+ Json::Value destinations = Json::arrayValue;
+ auto installGens = this->GT->Target->GetInstallGenerators();
+ for (auto itGen : installGens) {
+ destinations.append(this->DumpInstallDestination(itGen));
+ }
+ return destinations;
+}
+
+Json::Value Target::DumpInstallDestination(cmInstallTargetGenerator* itGen)
+{
+ Json::Value destination = Json::objectValue;
+ destination["path"] = itGen->GetDestination(this->Config);
+ this->AddBacktrace(destination, itGen->GetBacktrace());
+ return destination;
+}
+
+Json::Value Target::DumpArtifacts()
+{
+ Json::Value artifacts = Json::arrayValue;
+
+ // Object libraries have only object files as artifacts.
+ if (this->GT->GetType() == cmStateEnums::OBJECT_LIBRARY) {
+ if (!this->GT->GetGlobalGenerator()->HasKnownObjectFileLocation(nullptr)) {
+ return artifacts;
+ }
+ std::vector<cmSourceFile const*> objectSources;
+ this->GT->GetObjectSources(objectSources, this->Config);
+ std::string const obj_dir = this->GT->GetObjectDirectory(this->Config);
+ for (cmSourceFile const* sf : objectSources) {
+ const std::string& obj = this->GT->GetObjectName(sf);
+ Json::Value artifact = Json::objectValue;
+ artifact["path"] = RelativeIfUnder(this->TopBuild, obj_dir + obj);
+ artifacts.append(std::move(artifact)); // NOLINT(*)
+ }
+ return artifacts;
+ }
+
+ // Other target types always have a "main" artifact.
+ {
+ Json::Value artifact = Json::objectValue;
+ artifact["path"] =
+ RelativeIfUnder(this->TopBuild,
+ this->GT->GetFullPath(
+ this->Config, cmStateEnums::RuntimeBinaryArtifact));
+ artifacts.append(std::move(artifact)); // NOLINT(*)
+ }
+
+ // Add Windows-specific artifacts produced by the linker.
+ if (this->GT->IsDLLPlatform() &&
+ this->GT->GetType() != cmStateEnums::STATIC_LIBRARY) {
+ if (this->GT->GetType() == cmStateEnums::SHARED_LIBRARY ||
+ this->GT->IsExecutableWithExports()) {
+ Json::Value artifact = Json::objectValue;
+ artifact["path"] =
+ RelativeIfUnder(this->TopBuild,
+ this->GT->GetFullPath(
+ this->Config, cmStateEnums::ImportLibraryArtifact));
+ artifacts.append(std::move(artifact)); // NOLINT(*)
+ }
+ cmGeneratorTarget::OutputInfo const* output =
+ this->GT->GetOutputInfo(this->Config);
+ if (output && !output->PdbDir.empty()) {
+ Json::Value artifact = Json::objectValue;
+ artifact["path"] = RelativeIfUnder(this->TopBuild,
+ output->PdbDir + '/' +
+ this->GT->GetPDBName(this->Config));
+ artifacts.append(std::move(artifact)); // NOLINT(*)
+ }
+ }
+ return artifacts;
+}
+
+Json::Value Target::DumpLink()
+{
+ Json::Value link = Json::objectValue;
+ std::string lang = this->GT->GetLinkerLanguage(this->Config);
+ link["language"] = lang;
+ {
+ Json::Value commandFragments = this->DumpLinkCommandFragments();
+ if (!commandFragments.empty()) {
+ link["commandFragments"] = std::move(commandFragments);
+ }
+ }
+ if (const char* sysrootLink =
+ this->GT->Makefile->GetDefinition("CMAKE_SYSROOT_LINK")) {
+ link["sysroot"] = this->DumpSysroot(sysrootLink);
+ } else if (const char* sysroot =
+ this->GT->Makefile->GetDefinition("CMAKE_SYSROOT")) {
+ link["sysroot"] = this->DumpSysroot(sysroot);
+ }
+ if (this->GT->IsIPOEnabled(lang, this->Config)) {
+ link["lto"] = true;
+ }
+ return link;
+}
+
+Json::Value Target::DumpArchive()
+{
+ Json::Value archive = Json::objectValue;
+ {
+ // The "link" fragments not relevant to static libraries are empty.
+ Json::Value commandFragments = this->DumpLinkCommandFragments();
+ if (!commandFragments.empty()) {
+ archive["commandFragments"] = std::move(commandFragments);
+ }
+ }
+ std::string lang = this->GT->GetLinkerLanguage(this->Config);
+ if (this->GT->IsIPOEnabled(lang, this->Config)) {
+ archive["lto"] = true;
+ }
+ return archive;
+}
+
+Json::Value Target::DumpLinkCommandFragments()
+{
+ Json::Value linkFragments = Json::arrayValue;
+
+ std::string linkLanguageFlags;
+ std::string linkFlags;
+ std::string frameworkPath;
+ std::string linkPath;
+ std::string linkLibs;
+ cmLocalGenerator* lg = this->GT->GetLocalGenerator();
+ cmLinkLineComputer linkLineComputer(lg,
+ lg->GetStateSnapshot().GetDirectory());
+ lg->GetTargetFlags(&linkLineComputer, this->Config, linkLibs,
+ linkLanguageFlags, linkFlags, frameworkPath, linkPath,
+ this->GT);
+ linkLanguageFlags = cmSystemTools::TrimWhitespace(linkLanguageFlags);
+ linkFlags = cmSystemTools::TrimWhitespace(linkFlags);
+ frameworkPath = cmSystemTools::TrimWhitespace(frameworkPath);
+ linkPath = cmSystemTools::TrimWhitespace(linkPath);
+ linkLibs = cmSystemTools::TrimWhitespace(linkLibs);
+
+ if (!linkLanguageFlags.empty()) {
+ linkFragments.append(
+ this->DumpCommandFragment(std::move(linkLanguageFlags), "flags"));
+ }
+
+ if (!linkFlags.empty()) {
+ linkFragments.append(
+ this->DumpCommandFragment(std::move(linkFlags), "flags"));
+ }
+
+ if (!frameworkPath.empty()) {
+ linkFragments.append(
+ this->DumpCommandFragment(std::move(frameworkPath), "frameworkPath"));
+ }
+
+ if (!linkPath.empty()) {
+ linkFragments.append(
+ this->DumpCommandFragment(std::move(linkPath), "libraryPath"));
+ }
+
+ if (!linkLibs.empty()) {
+ linkFragments.append(
+ this->DumpCommandFragment(std::move(linkLibs), "libraries"));
+ }
+
+ return linkFragments;
+}
+
+Json::Value Target::DumpCommandFragments(
+ std::vector<BT<std::string>> const& frags)
+{
+ Json::Value commandFragments = Json::arrayValue;
+ for (BT<std::string> const& f : frags) {
+ commandFragments.append(this->DumpCommandFragment(f));
+ }
+ return commandFragments;
+}
+
+Json::Value Target::DumpCommandFragment(BT<std::string> const& frag,
+ std::string const& role)
+{
+ Json::Value fragment = Json::objectValue;
+ fragment["fragment"] = frag.Value;
+ if (!role.empty()) {
+ fragment["role"] = role;
+ }
+ this->AddBacktrace(fragment, frag.Backtrace);
+ return fragment;
+}
+
+Json::Value Target::DumpDependencies()
+{
+ Json::Value dependencies = Json::arrayValue;
+ cmGlobalGenerator* gg = this->GT->GetGlobalGenerator();
+ for (cmTargetDepend const& td : gg->GetTargetDirectDepends(this->GT)) {
+ dependencies.append(this->DumpDependency(td));
+ }
+ return dependencies;
+}
+
+Json::Value Target::DumpDependency(cmTargetDepend const& td)
+{
+ Json::Value dependency = Json::objectValue;
+ dependency["id"] = TargetId(td, this->TopBuild);
+ this->AddBacktrace(dependency, td.GetBacktrace());
+ return dependency;
+}
+
+Json::Value Target::DumpFolder()
+{
+ Json::Value folder;
+ if (const char* f = this->GT->GetProperty("FOLDER")) {
+ folder = Json::objectValue;
+ folder["name"] = f;
+ }
+ return folder;
+}
+}
+
+Json::Value cmFileAPICodemodelDump(cmFileAPI& fileAPI, unsigned long version)
+{
+ Codemodel codemodel(fileAPI, version);
+ return codemodel.Dump();
+}
diff --git a/Source/cmFileAPICodemodel.h b/Source/cmFileAPICodemodel.h
new file mode 100644
index 000000000..ffbd92863
--- /dev/null
+++ b/Source/cmFileAPICodemodel.h
@@ -0,0 +1,15 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#ifndef cmFileAPICodemodel_h
+#define cmFileAPICodemodel_h
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include "cm_jsoncpp_value.h"
+
+class cmFileAPI;
+
+extern Json::Value cmFileAPICodemodelDump(cmFileAPI& fileAPI,
+ unsigned long version);
+
+#endif
diff --git a/Source/cmFileCopier.cxx b/Source/cmFileCopier.cxx
new file mode 100644
index 000000000..49e8cd5b3
--- /dev/null
+++ b/Source/cmFileCopier.cxx
@@ -0,0 +1,713 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+
+#include "cmFileCopier.h"
+
+#include "cmFSPermissions.h"
+#include "cmFileCommand.h"
+#include "cmFileTimes.h"
+#include "cmMakefile.h"
+#include "cmSystemTools.h"
+#include "cmsys/Directory.hxx"
+#include "cmsys/Glob.hxx"
+
+#ifdef _WIN32
+# include "cmsys/FStream.hxx"
+#endif
+
+#include <sstream>
+#include <string.h>
+
+using namespace cmFSPermissions;
+
+cmFileCopier::cmFileCopier(cmFileCommand* command, const char* name)
+ : FileCommand(command)
+ , Makefile(command->GetMakefile())
+ , Name(name)
+ , Always(false)
+ , MatchlessFiles(true)
+ , FilePermissions(0)
+ , DirPermissions(0)
+ , CurrentMatchRule(nullptr)
+ , UseGivenPermissionsFile(false)
+ , UseGivenPermissionsDir(false)
+ , UseSourcePermissions(true)
+ , FollowSymlinkChain(false)
+ , Doing(DoingNone)
+{
+}
+
+cmFileCopier::~cmFileCopier() = default;
+
+cmFileCopier::MatchProperties cmFileCopier::CollectMatchProperties(
+ const std::string& file)
+{
+ // Match rules are case-insensitive on some platforms.
+#if defined(_WIN32) || defined(__APPLE__) || defined(__CYGWIN__)
+ const std::string file_to_match = cmSystemTools::LowerCase(file);
+#else
+ const std::string& file_to_match = file;
+#endif
+
+ // Collect properties from all matching rules.
+ bool matched = false;
+ MatchProperties result;
+ for (MatchRule& mr : this->MatchRules) {
+ if (mr.Regex.find(file_to_match)) {
+ matched = true;
+ result.Exclude |= mr.Properties.Exclude;
+ result.Permissions |= mr.Properties.Permissions;
+ }
+ }
+ if (!matched && !this->MatchlessFiles) {
+ result.Exclude = !cmSystemTools::FileIsDirectory(file);
+ }
+ return result;
+}
+
+bool cmFileCopier::SetPermissions(const std::string& toFile,
+ mode_t permissions)
+{
+ if (permissions) {
+#ifdef WIN32
+ if (Makefile->IsOn("CMAKE_CROSSCOMPILING")) {
+ // Store the mode in an NTFS alternate stream.
+ std::string mode_t_adt_filename = toFile + ":cmake_mode_t";
+
+ // Writing to an NTFS alternate stream changes the modification
+ // time, so we need to save and restore its original value.
+ cmFileTimes file_time_orig(toFile);
+ {
+ cmsys::ofstream permissionStream(mode_t_adt_filename.c_str());
+ if (permissionStream) {
+ permissionStream << std::oct << permissions << std::endl;
+ }
+ permissionStream.close();
+ }
+ file_time_orig.Store(toFile);
+ }
+#endif
+
+ if (!cmSystemTools::SetPermissions(toFile, permissions)) {
+ std::ostringstream e;
+ e << this->Name << " cannot set permissions on \"" << toFile << "\"";
+ this->FileCommand->SetError(e.str());
+ return false;
+ }
+ }
+ return true;
+}
+
+// Translate an argument to a permissions bit.
+bool cmFileCopier::CheckPermissions(std::string const& arg,
+ mode_t& permissions)
+{
+ if (!cmFSPermissions::stringToModeT(arg, permissions)) {
+ std::ostringstream e;
+ e << this->Name << " given invalid permission \"" << arg << "\".";
+ this->FileCommand->SetError(e.str());
+ return false;
+ }
+ return true;
+}
+
+std::string const& cmFileCopier::ToName(std::string const& fromName)
+{
+ return fromName;
+}
+
+bool cmFileCopier::ReportMissing(const std::string& fromFile)
+{
+ // The input file does not exist and installation is not optional.
+ std::ostringstream e;
+ e << this->Name << " cannot find \"" << fromFile << "\".";
+ this->FileCommand->SetError(e.str());
+ return false;
+}
+
+void cmFileCopier::NotBeforeMatch(std::string const& arg)
+{
+ std::ostringstream e;
+ e << "option " << arg << " may not appear before PATTERN or REGEX.";
+ this->FileCommand->SetError(e.str());
+ this->Doing = DoingError;
+}
+
+void cmFileCopier::NotAfterMatch(std::string const& arg)
+{
+ std::ostringstream e;
+ e << "option " << arg << " may not appear after PATTERN or REGEX.";
+ this->FileCommand->SetError(e.str());
+ this->Doing = DoingError;
+}
+
+void cmFileCopier::DefaultFilePermissions()
+{
+ // Use read/write permissions.
+ this->FilePermissions = 0;
+ this->FilePermissions |= mode_owner_read;
+ this->FilePermissions |= mode_owner_write;
+ this->FilePermissions |= mode_group_read;
+ this->FilePermissions |= mode_world_read;
+}
+
+void cmFileCopier::DefaultDirectoryPermissions()
+{
+ // Use read/write/executable permissions.
+ this->DirPermissions = 0;
+ this->DirPermissions |= mode_owner_read;
+ this->DirPermissions |= mode_owner_write;
+ this->DirPermissions |= mode_owner_execute;
+ this->DirPermissions |= mode_group_read;
+ this->DirPermissions |= mode_group_execute;
+ this->DirPermissions |= mode_world_read;
+ this->DirPermissions |= mode_world_execute;
+}
+
+bool cmFileCopier::GetDefaultDirectoryPermissions(mode_t** mode)
+{
+ // check if default dir creation permissions were set
+ const char* default_dir_install_permissions = this->Makefile->GetDefinition(
+ "CMAKE_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS");
+ if (default_dir_install_permissions && *default_dir_install_permissions) {
+ std::vector<std::string> items;
+ cmSystemTools::ExpandListArgument(default_dir_install_permissions, items);
+ for (const auto& arg : items) {
+ if (!this->CheckPermissions(arg, **mode)) {
+ std::ostringstream e;
+ e << this->FileCommand->GetError()
+ << " Set with CMAKE_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS "
+ "variable.";
+ this->FileCommand->SetError(e.str());
+ return false;
+ }
+ }
+ } else {
+ *mode = nullptr;
+ }
+
+ return true;
+}
+
+bool cmFileCopier::Parse(std::vector<std::string> const& args)
+{
+ this->Doing = DoingFiles;
+ for (unsigned int i = 1; i < args.size(); ++i) {
+ // Check this argument.
+ if (!this->CheckKeyword(args[i]) && !this->CheckValue(args[i])) {
+ std::ostringstream e;
+ e << "called with unknown argument \"" << args[i] << "\".";
+ this->FileCommand->SetError(e.str());
+ return false;
+ }
+
+ // Quit if an argument is invalid.
+ if (this->Doing == DoingError) {
+ return false;
+ }
+ }
+
+ // Require a destination.
+ if (this->Destination.empty()) {
+ std::ostringstream e;
+ e << this->Name << " given no DESTINATION";
+ this->FileCommand->SetError(e.str());
+ return false;
+ }
+
+ // If file permissions were not specified set default permissions.
+ if (!this->UseGivenPermissionsFile && !this->UseSourcePermissions) {
+ this->DefaultFilePermissions();
+ }
+
+ // If directory permissions were not specified set default permissions.
+ if (!this->UseGivenPermissionsDir && !this->UseSourcePermissions) {
+ this->DefaultDirectoryPermissions();
+ }
+
+ return true;
+}
+
+bool cmFileCopier::CheckKeyword(std::string const& arg)
+{
+ if (arg == "DESTINATION") {
+ if (this->CurrentMatchRule) {
+ this->NotAfterMatch(arg);
+ } else {
+ this->Doing = DoingDestination;
+ }
+ } else if (arg == "FILES_FROM_DIR") {
+ if (this->CurrentMatchRule) {
+ this->NotAfterMatch(arg);
+ } else {
+ this->Doing = DoingFilesFromDir;
+ }
+ } else if (arg == "PATTERN") {
+ this->Doing = DoingPattern;
+ } else if (arg == "REGEX") {
+ this->Doing = DoingRegex;
+ } else if (arg == "FOLLOW_SYMLINK_CHAIN") {
+ this->FollowSymlinkChain = true;
+ this->Doing = DoingNone;
+ } else if (arg == "EXCLUDE") {
+ // Add this property to the current match rule.
+ if (this->CurrentMatchRule) {
+ this->CurrentMatchRule->Properties.Exclude = true;
+ this->Doing = DoingNone;
+ } else {
+ this->NotBeforeMatch(arg);
+ }
+ } else if (arg == "PERMISSIONS") {
+ if (this->CurrentMatchRule) {
+ this->Doing = DoingPermissionsMatch;
+ } else {
+ this->NotBeforeMatch(arg);
+ }
+ } else if (arg == "FILE_PERMISSIONS") {
+ if (this->CurrentMatchRule) {
+ this->NotAfterMatch(arg);
+ } else {
+ this->Doing = DoingPermissionsFile;
+ this->UseGivenPermissionsFile = true;
+ }
+ } else if (arg == "DIRECTORY_PERMISSIONS") {
+ if (this->CurrentMatchRule) {
+ this->NotAfterMatch(arg);
+ } else {
+ this->Doing = DoingPermissionsDir;
+ this->UseGivenPermissionsDir = true;
+ }
+ } else if (arg == "USE_SOURCE_PERMISSIONS") {
+ if (this->CurrentMatchRule) {
+ this->NotAfterMatch(arg);
+ } else {
+ this->Doing = DoingNone;
+ this->UseSourcePermissions = true;
+ }
+ } else if (arg == "NO_SOURCE_PERMISSIONS") {
+ if (this->CurrentMatchRule) {
+ this->NotAfterMatch(arg);
+ } else {
+ this->Doing = DoingNone;
+ this->UseSourcePermissions = false;
+ }
+ } else if (arg == "FILES_MATCHING") {
+ if (this->CurrentMatchRule) {
+ this->NotAfterMatch(arg);
+ } else {
+ this->Doing = DoingNone;
+ this->MatchlessFiles = false;
+ }
+ } else {
+ return false;
+ }
+ return true;
+}
+
+bool cmFileCopier::CheckValue(std::string const& arg)
+{
+ switch (this->Doing) {
+ case DoingFiles:
+ this->Files.push_back(arg);
+ break;
+ case DoingDestination:
+ if (arg.empty() || cmSystemTools::FileIsFullPath(arg)) {
+ this->Destination = arg;
+ } else {
+ this->Destination = this->Makefile->GetCurrentBinaryDirectory();
+ this->Destination += "/" + arg;
+ }
+ this->Doing = DoingNone;
+ break;
+ case DoingFilesFromDir:
+ if (cmSystemTools::FileIsFullPath(arg)) {
+ this->FilesFromDir = arg;
+ } else {
+ this->FilesFromDir = this->Makefile->GetCurrentSourceDirectory();
+ this->FilesFromDir += "/" + arg;
+ }
+ cmSystemTools::ConvertToUnixSlashes(this->FilesFromDir);
+ this->Doing = DoingNone;
+ break;
+ case DoingPattern: {
+ // Convert the pattern to a regular expression. Require a
+ // leading slash and trailing end-of-string in the matched
+ // string to make sure the pattern matches only whole file
+ // names.
+ std::string regex = "/";
+ regex += cmsys::Glob::PatternToRegex(arg, false);
+ regex += "$";
+ this->MatchRules.emplace_back(regex);
+ this->CurrentMatchRule = &*(this->MatchRules.end() - 1);
+ if (this->CurrentMatchRule->Regex.is_valid()) {
+ this->Doing = DoingNone;
+ } else {
+ std::ostringstream e;
+ e << "could not compile PATTERN \"" << arg << "\".";
+ this->FileCommand->SetError(e.str());
+ this->Doing = DoingError;
+ }
+ } break;
+ case DoingRegex:
+ this->MatchRules.emplace_back(arg);
+ this->CurrentMatchRule = &*(this->MatchRules.end() - 1);
+ if (this->CurrentMatchRule->Regex.is_valid()) {
+ this->Doing = DoingNone;
+ } else {
+ std::ostringstream e;
+ e << "could not compile REGEX \"" << arg << "\".";
+ this->FileCommand->SetError(e.str());
+ this->Doing = DoingError;
+ }
+ break;
+ case DoingPermissionsFile:
+ if (!this->CheckPermissions(arg, this->FilePermissions)) {
+ this->Doing = DoingError;
+ }
+ break;
+ case DoingPermissionsDir:
+ if (!this->CheckPermissions(arg, this->DirPermissions)) {
+ this->Doing = DoingError;
+ }
+ break;
+ case DoingPermissionsMatch:
+ if (!this->CheckPermissions(
+ arg, this->CurrentMatchRule->Properties.Permissions)) {
+ this->Doing = DoingError;
+ }
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+
+bool cmFileCopier::Run(std::vector<std::string> const& args)
+{
+ if (!this->Parse(args)) {
+ return false;
+ }
+
+ for (std::string const& f : this->Files) {
+ std::string file;
+ if (!f.empty() && !cmSystemTools::FileIsFullPath(f)) {
+ if (!this->FilesFromDir.empty()) {
+ file = this->FilesFromDir;
+ } else {
+ file = this->Makefile->GetCurrentSourceDirectory();
+ }
+ file += "/";
+ file += f;
+ } else if (!this->FilesFromDir.empty()) {
+ this->FileCommand->SetError("option FILES_FROM_DIR requires all files "
+ "to be specified as relative paths.");
+ return false;
+ } else {
+ file = f;
+ }
+
+ // Split the input file into its directory and name components.
+ std::vector<std::string> fromPathComponents;
+ cmSystemTools::SplitPath(file, fromPathComponents);
+ std::string fromName = *(fromPathComponents.end() - 1);
+ std::string fromDir = cmSystemTools::JoinPath(
+ fromPathComponents.begin(), fromPathComponents.end() - 1);
+
+ // Compute the full path to the destination file.
+ std::string toFile = this->Destination;
+ if (!this->FilesFromDir.empty()) {
+ std::string dir = cmSystemTools::GetFilenamePath(f);
+ if (!dir.empty()) {
+ toFile += "/";
+ toFile += dir;
+ }
+ }
+ std::string const& toName = this->ToName(fromName);
+ if (!toName.empty()) {
+ toFile += "/";
+ toFile += toName;
+ }
+
+ // Construct the full path to the source file. The file name may
+ // have been changed above.
+ std::string fromFile = fromDir;
+ if (!fromName.empty()) {
+ fromFile += "/";
+ fromFile += fromName;
+ }
+
+ if (!this->Install(fromFile, toFile)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool cmFileCopier::Install(const std::string& fromFile,
+ const std::string& toFile)
+{
+ if (fromFile.empty()) {
+ std::ostringstream e;
+ e << "INSTALL encountered an empty string input file name.";
+ this->FileCommand->SetError(e.str());
+ return false;
+ }
+
+ // Collect any properties matching this file name.
+ MatchProperties match_properties = this->CollectMatchProperties(fromFile);
+
+ // Skip the file if it is excluded.
+ if (match_properties.Exclude) {
+ return true;
+ }
+
+ if (cmSystemTools::SameFile(fromFile, toFile)) {
+ return true;
+ }
+
+ std::string newFromFile = fromFile;
+ std::string newToFile = toFile;
+
+ if (this->FollowSymlinkChain &&
+ !this->InstallSymlinkChain(newFromFile, newToFile)) {
+ return false;
+ }
+
+ if (cmSystemTools::FileIsSymlink(newFromFile)) {
+ return this->InstallSymlink(newFromFile, newToFile);
+ }
+ if (cmSystemTools::FileIsDirectory(newFromFile)) {
+ return this->InstallDirectory(newFromFile, newToFile, match_properties);
+ }
+ if (cmSystemTools::FileExists(newFromFile)) {
+ return this->InstallFile(newFromFile, newToFile, match_properties);
+ }
+ return this->ReportMissing(newFromFile);
+}
+
+bool cmFileCopier::InstallSymlinkChain(std::string& fromFile,
+ std::string& toFile)
+{
+ std::string newFromFile;
+ std::string toFilePath = cmSystemTools::GetFilenamePath(toFile);
+ while (cmSystemTools::ReadSymlink(fromFile, newFromFile)) {
+ if (!cmSystemTools::FileIsFullPath(newFromFile)) {
+ std::string fromFilePath = cmSystemTools::GetFilenamePath(fromFile);
+ newFromFile = fromFilePath + "/" + newFromFile;
+ }
+
+ std::string symlinkTarget = cmSystemTools::GetFilenameName(newFromFile);
+
+ bool copy = true;
+ if (!this->Always) {
+ std::string oldSymlinkTarget;
+ if (cmSystemTools::ReadSymlink(toFile, oldSymlinkTarget)) {
+ if (symlinkTarget == oldSymlinkTarget) {
+ copy = false;
+ }
+ }
+ }
+
+ this->ReportCopy(toFile, TypeLink, copy);
+
+ if (copy) {
+ cmSystemTools::RemoveFile(toFile);
+ cmSystemTools::MakeDirectory(toFilePath);
+
+ if (!cmSystemTools::CreateSymlink(symlinkTarget, toFile)) {
+ std::ostringstream e;
+ e << this->Name << " cannot create symlink \"" << toFile << "\".";
+ this->FileCommand->SetError(e.str());
+ return false;
+ }
+ }
+
+ fromFile = newFromFile;
+ toFile = toFilePath + "/" + symlinkTarget;
+ }
+
+ return true;
+}
+
+bool cmFileCopier::InstallSymlink(const std::string& fromFile,
+ const std::string& toFile)
+{
+ // Read the original symlink.
+ std::string symlinkTarget;
+ if (!cmSystemTools::ReadSymlink(fromFile, symlinkTarget)) {
+ std::ostringstream e;
+ e << this->Name << " cannot read symlink \"" << fromFile
+ << "\" to duplicate at \"" << toFile << "\".";
+ this->FileCommand->SetError(e.str());
+ return false;
+ }
+
+ // Compare the symlink value to that at the destination if not
+ // always installing.
+ bool copy = true;
+ if (!this->Always) {
+ std::string oldSymlinkTarget;
+ if (cmSystemTools::ReadSymlink(toFile, oldSymlinkTarget)) {
+ if (symlinkTarget == oldSymlinkTarget) {
+ copy = false;
+ }
+ }
+ }
+
+ // Inform the user about this file installation.
+ this->ReportCopy(toFile, TypeLink, copy);
+
+ if (copy) {
+ // Remove the destination file so we can always create the symlink.
+ cmSystemTools::RemoveFile(toFile);
+
+ // Create destination directory if it doesn't exist
+ cmSystemTools::MakeDirectory(cmSystemTools::GetFilenamePath(toFile));
+
+ // Create the symlink.
+ if (!cmSystemTools::CreateSymlink(symlinkTarget, toFile)) {
+ std::ostringstream e;
+ e << this->Name << " cannot duplicate symlink \"" << fromFile
+ << "\" at \"" << toFile << "\".";
+ this->FileCommand->SetError(e.str());
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool cmFileCopier::InstallFile(const std::string& fromFile,
+ const std::string& toFile,
+ MatchProperties match_properties)
+{
+ // Determine whether we will copy the file.
+ bool copy = true;
+ if (!this->Always) {
+ // If both files exist with the same time do not copy.
+ if (!this->FileTimes.DifferS(fromFile, toFile)) {
+ copy = false;
+ }
+ }
+
+ // Inform the user about this file installation.
+ this->ReportCopy(toFile, TypeFile, copy);
+
+ // Copy the file.
+ if (copy && !cmSystemTools::CopyAFile(fromFile, toFile, true)) {
+ std::ostringstream e;
+ e << this->Name << " cannot copy file \"" << fromFile << "\" to \""
+ << toFile << "\".";
+ this->FileCommand->SetError(e.str());
+ return false;
+ }
+
+ // Set the file modification time of the destination file.
+ if (copy && !this->Always) {
+ // Add write permission so we can set the file time.
+ // Permissions are set unconditionally below anyway.
+ mode_t perm = 0;
+ if (cmSystemTools::GetPermissions(toFile, perm)) {
+ cmSystemTools::SetPermissions(toFile, perm | mode_owner_write);
+ }
+ if (!cmFileTimes::Copy(fromFile, toFile)) {
+ std::ostringstream e;
+ e << this->Name << " cannot set modification time on \"" << toFile
+ << "\"";
+ this->FileCommand->SetError(e.str());
+ return false;
+ }
+ }
+
+ // Set permissions of the destination file.
+ mode_t permissions =
+ (match_properties.Permissions ? match_properties.Permissions
+ : this->FilePermissions);
+ if (!permissions) {
+ // No permissions were explicitly provided but the user requested
+ // that the source file permissions be used.
+ cmSystemTools::GetPermissions(fromFile, permissions);
+ }
+ return this->SetPermissions(toFile, permissions);
+}
+
+bool cmFileCopier::InstallDirectory(const std::string& source,
+ const std::string& destination,
+ MatchProperties match_properties)
+{
+ // Inform the user about this directory installation.
+ this->ReportCopy(destination, TypeDir,
+ !cmSystemTools::FileIsDirectory(destination));
+
+ // check if default dir creation permissions were set
+ mode_t default_dir_mode_v = 0;
+ mode_t* default_dir_mode = &default_dir_mode_v;
+ if (!this->GetDefaultDirectoryPermissions(&default_dir_mode)) {
+ return false;
+ }
+
+ // Make sure the destination directory exists.
+ if (!cmSystemTools::MakeDirectory(destination, default_dir_mode)) {
+ std::ostringstream e;
+ e << this->Name << " cannot make directory \"" << destination
+ << "\": " << cmSystemTools::GetLastSystemError();
+ this->FileCommand->SetError(e.str());
+ return false;
+ }
+
+ // Compute the requested permissions for the destination directory.
+ mode_t permissions =
+ (match_properties.Permissions ? match_properties.Permissions
+ : this->DirPermissions);
+ if (!permissions) {
+ // No permissions were explicitly provided but the user requested
+ // that the source directory permissions be used.
+ cmSystemTools::GetPermissions(source, permissions);
+ }
+
+ // Compute the set of permissions required on this directory to
+ // recursively install files and subdirectories safely.
+ mode_t required_permissions =
+ mode_owner_read | mode_owner_write | mode_owner_execute;
+
+ // If the required permissions are specified it is safe to set the
+ // final permissions now. Otherwise we must add the required
+ // permissions temporarily during file installation.
+ mode_t permissions_before = 0;
+ mode_t permissions_after = 0;
+ if ((permissions & required_permissions) == required_permissions) {
+ permissions_before = permissions;
+ } else {
+ permissions_before = permissions | required_permissions;
+ permissions_after = permissions;
+ }
+
+ // Set the required permissions of the destination directory.
+ if (!this->SetPermissions(destination, permissions_before)) {
+ return false;
+ }
+
+ // Load the directory contents to traverse it recursively.
+ cmsys::Directory dir;
+ if (!source.empty()) {
+ dir.Load(source);
+ }
+ unsigned long numFiles = static_cast<unsigned long>(dir.GetNumberOfFiles());
+ for (unsigned long fileNum = 0; fileNum < numFiles; ++fileNum) {
+ if (!(strcmp(dir.GetFile(fileNum), ".") == 0 ||
+ strcmp(dir.GetFile(fileNum), "..") == 0)) {
+ std::string fromPath = source;
+ fromPath += "/";
+ fromPath += dir.GetFile(fileNum);
+ std::string toPath = destination;
+ toPath += "/";
+ toPath += dir.GetFile(fileNum);
+ if (!this->Install(fromPath, toPath)) {
+ return false;
+ }
+ }
+ }
+
+ // Set the requested permissions of the destination directory.
+ return this->SetPermissions(destination, permissions_after);
+}
diff --git a/Source/cmFileCopier.h b/Source/cmFileCopier.h
new file mode 100644
index 000000000..a79a60b90
--- /dev/null
+++ b/Source/cmFileCopier.h
@@ -0,0 +1,122 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#ifndef cmFileCopier_h
+#define cmFileCopier_h
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include "cmFileTimeCache.h"
+#include "cm_sys_stat.h"
+#include "cmsys/RegularExpression.hxx"
+
+#include <string>
+#include <vector>
+
+class cmFileCommand;
+class cmMakefile;
+
+// File installation helper class.
+struct cmFileCopier
+{
+ cmFileCopier(cmFileCommand* command, const char* name = "COPY");
+ virtual ~cmFileCopier();
+
+ bool Run(std::vector<std::string> const& args);
+
+protected:
+ cmFileCommand* FileCommand;
+ cmMakefile* Makefile;
+ const char* Name;
+ bool Always;
+ cmFileTimeCache FileTimes;
+
+ // Whether to install a file not matching any expression.
+ bool MatchlessFiles;
+
+ // Permissions for files and directories installed by this object.
+ mode_t FilePermissions;
+ mode_t DirPermissions;
+
+ // Properties set by pattern and regex match rules.
+ struct MatchProperties
+ {
+ bool Exclude = false;
+ mode_t Permissions = 0;
+ };
+ struct MatchRule
+ {
+ cmsys::RegularExpression Regex;
+ MatchProperties Properties;
+ std::string RegexString;
+ MatchRule(std::string const& regex)
+ : Regex(regex)
+ , RegexString(regex)
+ {
+ }
+ };
+ std::vector<MatchRule> MatchRules;
+
+ // Get the properties from rules matching this input file.
+ MatchProperties CollectMatchProperties(const std::string& file);
+
+ bool SetPermissions(const std::string& toFile, mode_t permissions);
+
+ // Translate an argument to a permissions bit.
+ bool CheckPermissions(std::string const& arg, mode_t& permissions);
+
+ bool InstallSymlinkChain(std::string& fromFile, std::string& toFile);
+ bool InstallSymlink(const std::string& fromFile, const std::string& toFile);
+ bool InstallFile(const std::string& fromFile, const std::string& toFile,
+ MatchProperties match_properties);
+ bool InstallDirectory(const std::string& source,
+ const std::string& destination,
+ MatchProperties match_properties);
+ virtual bool Install(const std::string& fromFile, const std::string& toFile);
+ virtual std::string const& ToName(std::string const& fromName);
+
+ enum Type
+ {
+ TypeFile,
+ TypeDir,
+ TypeLink
+ };
+ virtual void ReportCopy(const std::string&, Type, bool) {}
+ virtual bool ReportMissing(const std::string& fromFile);
+
+ MatchRule* CurrentMatchRule;
+ bool UseGivenPermissionsFile;
+ bool UseGivenPermissionsDir;
+ bool UseSourcePermissions;
+ bool FollowSymlinkChain;
+ std::string Destination;
+ std::string FilesFromDir;
+ std::vector<std::string> Files;
+ int Doing;
+
+ virtual bool Parse(std::vector<std::string> const& args);
+ enum
+ {
+ DoingNone,
+ DoingError,
+ DoingDestination,
+ DoingFilesFromDir,
+ DoingFiles,
+ DoingPattern,
+ DoingRegex,
+ DoingPermissionsFile,
+ DoingPermissionsDir,
+ DoingPermissionsMatch,
+ DoingLast1
+ };
+ virtual bool CheckKeyword(std::string const& arg);
+ virtual bool CheckValue(std::string const& arg);
+
+ void NotBeforeMatch(std::string const& arg);
+ void NotAfterMatch(std::string const& arg);
+ virtual void DefaultFilePermissions();
+ virtual void DefaultDirectoryPermissions();
+
+ bool GetDefaultDirectoryPermissions(mode_t** mode);
+};
+
+#endif
diff --git a/Source/cmFileInstaller.cxx b/Source/cmFileInstaller.cxx
new file mode 100644
index 000000000..d4f76fd86
--- /dev/null
+++ b/Source/cmFileInstaller.cxx
@@ -0,0 +1,350 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+
+#include "cmFileInstaller.h"
+
+#include "cmFSPermissions.h"
+#include "cmFileCommand.h"
+#include "cmMakefile.h"
+#include "cmSystemTools.h"
+
+#include "cm_sys_stat.h"
+
+#include <sstream>
+
+using namespace cmFSPermissions;
+
+cmFileInstaller::cmFileInstaller(cmFileCommand* command)
+ : cmFileCopier(command, "INSTALL")
+ , InstallType(cmInstallType_FILES)
+ , Optional(false)
+ , MessageAlways(false)
+ , MessageLazy(false)
+ , MessageNever(false)
+ , DestDirLength(0)
+{
+ // Installation does not use source permissions by default.
+ this->UseSourcePermissions = false;
+ // Check whether to copy files always or only if they have changed.
+ std::string install_always;
+ if (cmSystemTools::GetEnv("CMAKE_INSTALL_ALWAYS", install_always)) {
+ this->Always = cmSystemTools::IsOn(install_always);
+ }
+ // Get the current manifest.
+ this->Manifest =
+ this->Makefile->GetSafeDefinition("CMAKE_INSTALL_MANIFEST_FILES");
+}
+cmFileInstaller::~cmFileInstaller()
+{
+ // Save the updated install manifest.
+ this->Makefile->AddDefinition("CMAKE_INSTALL_MANIFEST_FILES",
+ this->Manifest.c_str());
+}
+
+void cmFileInstaller::ManifestAppend(std::string const& file)
+{
+ if (!this->Manifest.empty()) {
+ this->Manifest += ";";
+ }
+ this->Manifest += file.substr(this->DestDirLength);
+}
+
+std::string const& cmFileInstaller::ToName(std::string const& fromName)
+{
+ return this->Rename.empty() ? fromName : this->Rename;
+}
+
+void cmFileInstaller::ReportCopy(const std::string& toFile, Type type,
+ bool copy)
+{
+ if (!this->MessageNever && (copy || !this->MessageLazy)) {
+ std::string message = (copy ? "Installing: " : "Up-to-date: ");
+ message += toFile;
+ this->Makefile->DisplayStatus(message, -1);
+ }
+ if (type != TypeDir) {
+ // Add the file to the manifest.
+ this->ManifestAppend(toFile);
+ }
+}
+bool cmFileInstaller::ReportMissing(const std::string& fromFile)
+{
+ return (this->Optional || this->cmFileCopier::ReportMissing(fromFile));
+}
+bool cmFileInstaller::Install(const std::string& fromFile,
+ const std::string& toFile)
+{
+ // Support installing from empty source to make a directory.
+ if (this->InstallType == cmInstallType_DIRECTORY && fromFile.empty()) {
+ return this->InstallDirectory(fromFile, toFile, MatchProperties());
+ }
+ return this->cmFileCopier::Install(fromFile, toFile);
+}
+
+void cmFileInstaller::DefaultFilePermissions()
+{
+ this->cmFileCopier::DefaultFilePermissions();
+ // Add execute permissions based on the target type.
+ switch (this->InstallType) {
+ case cmInstallType_SHARED_LIBRARY:
+ case cmInstallType_MODULE_LIBRARY:
+ if (this->Makefile->IsOn("CMAKE_INSTALL_SO_NO_EXE")) {
+ break;
+ }
+ CM_FALLTHROUGH;
+ case cmInstallType_EXECUTABLE:
+ case cmInstallType_PROGRAMS:
+ this->FilePermissions |= mode_owner_execute;
+ this->FilePermissions |= mode_group_execute;
+ this->FilePermissions |= mode_world_execute;
+ break;
+ default:
+ break;
+ }
+}
+
+bool cmFileInstaller::Parse(std::vector<std::string> const& args)
+{
+ if (!this->cmFileCopier::Parse(args)) {
+ return false;
+ }
+
+ if (!this->Rename.empty()) {
+ if (!this->FilesFromDir.empty()) {
+ this->FileCommand->SetError("INSTALL option RENAME may not be "
+ "combined with FILES_FROM_DIR.");
+ return false;
+ }
+ if (this->InstallType != cmInstallType_FILES &&
+ this->InstallType != cmInstallType_PROGRAMS) {
+ this->FileCommand->SetError("INSTALL option RENAME may be used "
+ "only with FILES or PROGRAMS.");
+ return false;
+ }
+ if (this->Files.size() > 1) {
+ this->FileCommand->SetError("INSTALL option RENAME may be used "
+ "only with one file.");
+ return false;
+ }
+ }
+
+ if (!this->HandleInstallDestination()) {
+ return false;
+ }
+
+ if (((this->MessageAlways ? 1 : 0) + (this->MessageLazy ? 1 : 0) +
+ (this->MessageNever ? 1 : 0)) > 1) {
+ this->FileCommand->SetError("INSTALL options MESSAGE_ALWAYS, "
+ "MESSAGE_LAZY, and MESSAGE_NEVER "
+ "are mutually exclusive.");
+ return false;
+ }
+
+ return true;
+}
+
+bool cmFileInstaller::CheckKeyword(std::string const& arg)
+{
+ if (arg == "TYPE") {
+ if (this->CurrentMatchRule) {
+ this->NotAfterMatch(arg);
+ } else {
+ this->Doing = DoingType;
+ }
+ } else if (arg == "FILES") {
+ if (this->CurrentMatchRule) {
+ this->NotAfterMatch(arg);
+ } else {
+ this->Doing = DoingFiles;
+ }
+ } else if (arg == "RENAME") {
+ if (this->CurrentMatchRule) {
+ this->NotAfterMatch(arg);
+ } else {
+ this->Doing = DoingRename;
+ }
+ } else if (arg == "OPTIONAL") {
+ if (this->CurrentMatchRule) {
+ this->NotAfterMatch(arg);
+ } else {
+ this->Doing = DoingNone;
+ this->Optional = true;
+ }
+ } else if (arg == "MESSAGE_ALWAYS") {
+ if (this->CurrentMatchRule) {
+ this->NotAfterMatch(arg);
+ } else {
+ this->Doing = DoingNone;
+ this->MessageAlways = true;
+ }
+ } else if (arg == "MESSAGE_LAZY") {
+ if (this->CurrentMatchRule) {
+ this->NotAfterMatch(arg);
+ } else {
+ this->Doing = DoingNone;
+ this->MessageLazy = true;
+ }
+ } else if (arg == "MESSAGE_NEVER") {
+ if (this->CurrentMatchRule) {
+ this->NotAfterMatch(arg);
+ } else {
+ this->Doing = DoingNone;
+ this->MessageNever = true;
+ }
+ } else if (arg == "PERMISSIONS") {
+ if (this->CurrentMatchRule) {
+ this->Doing = DoingPermissionsMatch;
+ } else {
+ // file(INSTALL) aliases PERMISSIONS to FILE_PERMISSIONS
+ this->Doing = DoingPermissionsFile;
+ this->UseGivenPermissionsFile = true;
+ }
+ } else if (arg == "DIR_PERMISSIONS") {
+ if (this->CurrentMatchRule) {
+ this->NotAfterMatch(arg);
+ } else {
+ // file(INSTALL) aliases DIR_PERMISSIONS to DIRECTORY_PERMISSIONS
+ this->Doing = DoingPermissionsDir;
+ this->UseGivenPermissionsDir = true;
+ }
+ } else if (arg == "COMPONENTS" || arg == "CONFIGURATIONS" ||
+ arg == "PROPERTIES") {
+ std::ostringstream e;
+ e << "INSTALL called with old-style " << arg << " argument. "
+ << "This script was generated with an older version of CMake. "
+ << "Re-run this cmake version on your build tree.";
+ this->FileCommand->SetError(e.str());
+ this->Doing = DoingError;
+ } else {
+ return this->cmFileCopier::CheckKeyword(arg);
+ }
+ return true;
+}
+
+bool cmFileInstaller::CheckValue(std::string const& arg)
+{
+ switch (this->Doing) {
+ case DoingType:
+ if (!this->GetTargetTypeFromString(arg)) {
+ this->Doing = DoingError;
+ }
+ break;
+ case DoingRename:
+ this->Rename = arg;
+ break;
+ default:
+ return this->cmFileCopier::CheckValue(arg);
+ }
+ return true;
+}
+
+bool cmFileInstaller::GetTargetTypeFromString(const std::string& stype)
+{
+ if (stype == "EXECUTABLE") {
+ this->InstallType = cmInstallType_EXECUTABLE;
+ } else if (stype == "FILE") {
+ this->InstallType = cmInstallType_FILES;
+ } else if (stype == "PROGRAM") {
+ this->InstallType = cmInstallType_PROGRAMS;
+ } else if (stype == "STATIC_LIBRARY") {
+ this->InstallType = cmInstallType_STATIC_LIBRARY;
+ } else if (stype == "SHARED_LIBRARY") {
+ this->InstallType = cmInstallType_SHARED_LIBRARY;
+ } else if (stype == "MODULE") {
+ this->InstallType = cmInstallType_MODULE_LIBRARY;
+ } else if (stype == "DIRECTORY") {
+ this->InstallType = cmInstallType_DIRECTORY;
+ } else {
+ std::ostringstream e;
+ e << "Option TYPE given unknown value \"" << stype << "\".";
+ this->FileCommand->SetError(e.str());
+ return false;
+ }
+ return true;
+}
+
+bool cmFileInstaller::HandleInstallDestination()
+{
+ std::string& destination = this->Destination;
+
+ // allow for / to be a valid destination
+ if (destination.size() < 2 && destination != "/") {
+ this->FileCommand->SetError("called with inappropriate arguments. "
+ "No DESTINATION provided or .");
+ return false;
+ }
+
+ std::string sdestdir;
+ if (cmSystemTools::GetEnv("DESTDIR", sdestdir) && !sdestdir.empty()) {
+ cmSystemTools::ConvertToUnixSlashes(sdestdir);
+ char ch1 = destination[0];
+ char ch2 = destination[1];
+ char ch3 = 0;
+ if (destination.size() > 2) {
+ ch3 = destination[2];
+ }
+ int skip = 0;
+ if (ch1 != '/') {
+ int relative = 0;
+ if (((ch1 >= 'a' && ch1 <= 'z') || (ch1 >= 'A' && ch1 <= 'Z')) &&
+ ch2 == ':') {
+ // Assume windows
+ // let's do some destdir magic:
+ skip = 2;
+ if (ch3 != '/') {
+ relative = 1;
+ }
+ } else {
+ relative = 1;
+ }
+ if (relative) {
+ // This is relative path on unix or windows. Since we are doing
+ // destdir, this case does not make sense.
+ this->FileCommand->SetError(
+ "called with relative DESTINATION. This "
+ "does not make sense when using DESTDIR. Specify "
+ "absolute path or remove DESTDIR environment variable.");
+ return false;
+ }
+ } else {
+ if (ch2 == '/') {
+ // looks like a network path.
+ std::string message =
+ "called with network path DESTINATION. This "
+ "does not make sense when using DESTDIR. Specify local "
+ "absolute path or remove DESTDIR environment variable."
+ "\nDESTINATION=\n";
+ message += destination;
+ this->FileCommand->SetError(message);
+ return false;
+ }
+ }
+ destination = sdestdir + destination.substr(skip);
+ this->DestDirLength = int(sdestdir.size());
+ }
+
+ // check if default dir creation permissions were set
+ mode_t default_dir_mode_v = 0;
+ mode_t* default_dir_mode = &default_dir_mode_v;
+ if (!this->GetDefaultDirectoryPermissions(&default_dir_mode)) {
+ return false;
+ }
+
+ if (this->InstallType != cmInstallType_DIRECTORY) {
+ if (!cmSystemTools::FileExists(destination)) {
+ if (!cmSystemTools::MakeDirectory(destination, default_dir_mode)) {
+ std::string errstring = "cannot create directory: " + destination +
+ ". Maybe need administrative privileges.";
+ this->FileCommand->SetError(errstring);
+ return false;
+ }
+ }
+ if (!cmSystemTools::FileIsDirectory(destination)) {
+ std::string errstring =
+ "INSTALL destination: " + destination + " is not a directory.";
+ this->FileCommand->SetError(errstring);
+ return false;
+ }
+ }
+ return true;
+}
diff --git a/Source/cmFileInstaller.h b/Source/cmFileInstaller.h
new file mode 100644
index 000000000..312529aa8
--- /dev/null
+++ b/Source/cmFileInstaller.h
@@ -0,0 +1,55 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#ifndef cmFileInstaller_h
+#define cmFileInstaller_h
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include "cmFileCopier.h"
+
+#include "cmInstallType.h"
+
+#include <string>
+#include <vector>
+
+class cmFileCommand;
+
+struct cmFileInstaller : public cmFileCopier
+{
+ cmFileInstaller(cmFileCommand* command);
+ ~cmFileInstaller() override;
+
+protected:
+ cmInstallType InstallType;
+ bool Optional;
+ bool MessageAlways;
+ bool MessageLazy;
+ bool MessageNever;
+ int DestDirLength;
+ std::string Rename;
+
+ std::string Manifest;
+ void ManifestAppend(std::string const& file);
+
+ std::string const& ToName(std::string const& fromName) override;
+
+ void ReportCopy(const std::string& toFile, Type type, bool copy) override;
+ bool ReportMissing(const std::string& fromFile) override;
+ bool Install(const std::string& fromFile,
+ const std::string& toFile) override;
+
+ bool Parse(std::vector<std::string> const& args) override;
+ enum
+ {
+ DoingType = DoingLast1,
+ DoingRename,
+ DoingLast2
+ };
+ bool CheckKeyword(std::string const& arg) override;
+ bool CheckValue(std::string const& arg) override;
+ void DefaultFilePermissions() override;
+ bool GetTargetTypeFromString(const std::string& stype);
+ bool HandleInstallDestination();
+};
+
+#endif
diff --git a/Source/cmFileTime.cxx b/Source/cmFileTime.cxx
new file mode 100644
index 000000000..253457ff2
--- /dev/null
+++ b/Source/cmFileTime.cxx
@@ -0,0 +1,49 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmFileTime.h"
+
+#include <string>
+#include <time.h>
+
+// Use a platform-specific API to get file times efficiently.
+#if !defined(_WIN32) || defined(__CYGWIN__)
+# include "cm_sys_stat.h"
+#else
+# include "cmsys/Encoding.hxx"
+# include <windows.h>
+#endif
+
+bool cmFileTime::Load(std::string const& fileName)
+{
+#if !defined(_WIN32) || defined(__CYGWIN__)
+ // POSIX version. Use the stat function.
+ struct stat fst;
+ if (::stat(fileName.c_str(), &fst) != 0) {
+ return false;
+ }
+# if CMake_STAT_HAS_ST_MTIM
+ // Nanosecond resolution
+ this->NS = fst.st_mtim.tv_sec * NsPerS + fst.st_mtim.tv_nsec;
+# elif CMake_STAT_HAS_ST_MTIMESPEC
+ // Nanosecond resolution
+ this->NS = fst.st_mtimespec.tv_sec * NsPerS + fst.st_mtimespec.tv_nsec;
+# else
+ // Second resolution
+ this->NS = fst.st_mtime * NsPerS;
+# endif
+#else
+ // Windows version. Get the modification time from extended file attributes.
+ WIN32_FILE_ATTRIBUTE_DATA fdata;
+ if (!GetFileAttributesExW(cmsys::Encoding::ToWide(fileName).c_str(),
+ GetFileExInfoStandard, &fdata)) {
+ return false;
+ }
+
+ // Copy the file time to the output location.
+ this->NS = (static_cast<NSC>(fdata.ftLastWriteTime.dwHighDateTime) << 32) |
+ static_cast<NSC>(fdata.ftLastWriteTime.dwLowDateTime);
+ // The file time resolution is 100 ns.
+ this->NS *= 100;
+#endif
+ return true;
+}
diff --git a/Source/cmFileTime.h b/Source/cmFileTime.h
new file mode 100644
index 000000000..d4de4e033
--- /dev/null
+++ b/Source/cmFileTime.h
@@ -0,0 +1,130 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#ifndef cmFileTime_h
+#define cmFileTime_h
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <string>
+
+/** \class cmFileTime
+ * \brief Abstract file modification time with support for comparison with
+ * other file modification times.
+ */
+class cmFileTime
+{
+public:
+ typedef long long NSC;
+ static constexpr NSC NsPerS = 1000000000;
+
+ cmFileTime() = default;
+ ~cmFileTime() = default;
+
+ /**
+ * @brief Loads the file time of fileName from the file system
+ * @return true on success
+ */
+ bool Load(std::string const& fileName);
+
+ /**
+ * @brief Return true if this is older than ftm
+ */
+ bool Older(cmFileTime const& ftm) const { return (this->NS - ftm.NS) < 0; }
+
+ /**
+ * @brief Return true if this is newer than ftm
+ */
+ bool Newer(cmFileTime const& ftm) const { return (ftm.NS - this->NS) < 0; }
+
+ /**
+ * @brief Return true if this is the same as ftm
+ */
+ bool Equal(cmFileTime const& ftm) const { return this->NS == ftm.NS; }
+
+ /**
+ * @brief Return true if this is not the same as ftm
+ */
+ bool Differ(cmFileTime const& ftm) const { return this->NS != ftm.NS; }
+
+ /**
+ * @brief Compare file modification times.
+ * @return -1, 0, +1 for this older, same, or newer than ftm.
+ */
+ int Compare(cmFileTime const& ftm) const
+ {
+ NSC const diff = this->NS - ftm.NS;
+ if (diff == 0) {
+ return 0;
+ }
+ return (diff < 0) ? -1 : 1;
+ }
+
+ // -- Comparison in second resolution
+
+ /**
+ * @brief Return true if this is at least a second older than ftm
+ */
+ bool OlderS(cmFileTime const& ftm) const
+ {
+ return (ftm.NS - this->NS) >= cmFileTime::NsPerS;
+ }
+
+ /**
+ * @brief Return true if this is at least a second newer than ftm
+ */
+ bool NewerS(cmFileTime const& ftm) const
+ {
+ return (this->NS - ftm.NS) >= cmFileTime::NsPerS;
+ }
+
+ /**
+ * @brief Return true if this is within the same second as ftm
+ */
+ bool EqualS(cmFileTime const& ftm) const
+ {
+ NSC diff = this->NS - ftm.NS;
+ if (diff < 0) {
+ diff = -diff;
+ }
+ return (diff < cmFileTime::NsPerS);
+ }
+
+ /**
+ * @brief Return true if this is older or newer than ftm by at least a second
+ */
+ bool DifferS(cmFileTime const& ftm) const
+ {
+ NSC diff = this->NS - ftm.NS;
+ if (diff < 0) {
+ diff = -diff;
+ }
+ return (diff >= cmFileTime::NsPerS);
+ }
+
+ /**
+ * @brief Compare file modification times.
+ * @return -1: this at least a second older, 0: this within the same second
+ * as ftm, +1: this at least a second newer than ftm.
+ */
+ int CompareS(cmFileTime const& ftm) const
+ {
+ NSC const diff = this->NS - ftm.NS;
+ if (diff <= -cmFileTime::NsPerS) {
+ return -1;
+ }
+ if (diff >= cmFileTime::NsPerS) {
+ return 1;
+ }
+ return 0;
+ }
+
+ /**
+ * @brief The file modification time in nanoseconds
+ */
+ NSC GetNS() const { return this->NS; }
+
+private:
+ NSC NS = 0;
+};
+
+#endif
diff --git a/Source/cmFileTimeCache.cxx b/Source/cmFileTimeCache.cxx
new file mode 100644
index 000000000..24d6bf6e1
--- /dev/null
+++ b/Source/cmFileTimeCache.cxx
@@ -0,0 +1,62 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmFileTimeCache.h"
+
+#include <string>
+#include <unordered_map>
+#include <utility>
+
+cmFileTimeCache::cmFileTimeCache() = default;
+
+cmFileTimeCache::~cmFileTimeCache() = default;
+
+bool cmFileTimeCache::Load(std::string const& fileName, cmFileTime& fileTime)
+{
+ // Use the stored time if available.
+ {
+ auto fit = this->Cache.find(fileName);
+ if (fit != this->Cache.end()) {
+ fileTime = fit->second;
+ return true;
+ }
+ }
+ // Read file time from OS
+ if (!fileTime.Load(fileName)) {
+ return false;
+ }
+ // Store file time in cache
+ this->Cache[fileName] = fileTime;
+ return true;
+}
+
+bool cmFileTimeCache::Remove(std::string const& fileName)
+{
+ return (this->Cache.erase(fileName) != 0);
+}
+
+bool cmFileTimeCache::Compare(std::string const& f1, std::string const& f2,
+ int* result)
+{
+ // Get the modification time for each file.
+ cmFileTime ft1, ft2;
+ if (this->Load(f1, ft1) && this->Load(f2, ft2)) {
+ // Compare the two modification times.
+ *result = ft1.Compare(ft2);
+ return true;
+ }
+ // No comparison available. Default to the same time.
+ *result = 0;
+ return false;
+}
+
+bool cmFileTimeCache::DifferS(std::string const& f1, std::string const& f2)
+{
+ // Get the modification time for each file.
+ cmFileTime ft1, ft2;
+ if (this->Load(f1, ft1) && this->Load(f2, ft2)) {
+ // Compare the two modification times.
+ return ft1.DifferS(ft2);
+ }
+ // No comparison available. Default to different times.
+ return true;
+}
diff --git a/Source/cmFileTimeCache.h b/Source/cmFileTimeCache.h
new file mode 100644
index 000000000..4f1a3a253
--- /dev/null
+++ b/Source/cmFileTimeCache.h
@@ -0,0 +1,56 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#ifndef cmFileTimeCache_h
+#define cmFileTimeCache_h
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include "cmFileTime.h" // IWYU pragma: keep
+#include <string>
+#include <unordered_map>
+
+/** \class cmFileTimeCache
+ * \brief Caches file modification times in an internal map for fast lookups.
+ */
+class cmFileTimeCache
+{
+public:
+ cmFileTimeCache();
+ ~cmFileTimeCache();
+
+ cmFileTimeCache(const cmFileTimeCache&) = delete;
+ cmFileTimeCache& operator=(const cmFileTimeCache&) = delete;
+
+ /**
+ * @brief Loads the file time from the cache or the file system.
+ * @return true on success
+ */
+ bool Load(std::string const& fileName, cmFileTime& fileTime);
+
+ /**
+ * @brief Removes a file time from the cache
+ * @return true if the file was found in the cache and removed
+ */
+ bool Remove(std::string const& fileName);
+
+ /**
+ * @brief Compare file modification times.
+ * @return true for successful comparison and false for error.
+ *
+ * When true is returned, result has -1, 0, +1 for
+ * f1 older, same, or newer than f2.
+ */
+ bool Compare(std::string const& f1, std::string const& f2, int* result);
+
+ /**
+ * @brief Compare file modification times.
+ * @return true unless both files exist and have modification times less
+ * than 1 second apart.
+ */
+ bool DifferS(std::string const& f1, std::string const& f2);
+
+private:
+ std::unordered_map<std::string, cmFileTime> Cache;
+};
+
+#endif
diff --git a/Source/cmFileTimes.cxx b/Source/cmFileTimes.cxx
new file mode 100644
index 000000000..fd4f6797f
--- /dev/null
+++ b/Source/cmFileTimes.cxx
@@ -0,0 +1,127 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmFileTimes.h"
+
+#include "cmAlgorithms.h"
+#include "cm_sys_stat.h"
+
+#include <utility>
+
+#if defined(_WIN32)
+# include "cmSystemTools.h"
+# include <windows.h>
+#else
+# include <utime.h>
+#endif
+
+#if defined(_WIN32) && \
+ (defined(_MSC_VER) || defined(__WATCOMC__) || defined(__MINGW32__))
+# include <io.h>
+#endif
+
+#ifdef _WIN32
+class cmFileTimes::WindowsHandle
+{
+public:
+ WindowsHandle(HANDLE h)
+ : handle_(h)
+ {
+ }
+ ~WindowsHandle()
+ {
+ if (this->handle_ != INVALID_HANDLE_VALUE) {
+ CloseHandle(this->handle_);
+ }
+ }
+ explicit operator bool() const
+ {
+ return this->handle_ != INVALID_HANDLE_VALUE;
+ }
+ bool operator!() const { return this->handle_ == INVALID_HANDLE_VALUE; }
+ operator HANDLE() const { return this->handle_; }
+
+private:
+ HANDLE handle_;
+};
+#endif
+
+class cmFileTimes::Times
+{
+public:
+#if defined(_WIN32) && !defined(__CYGWIN__)
+ FILETIME timeCreation;
+ FILETIME timeLastAccess;
+ FILETIME timeLastWrite;
+#else
+ struct utimbuf timeBuf;
+#endif
+};
+
+cmFileTimes::cmFileTimes() = default;
+cmFileTimes::cmFileTimes(std::string const& fileName)
+{
+ Load(fileName);
+}
+cmFileTimes::~cmFileTimes() = default;
+
+bool cmFileTimes::Load(std::string const& fileName)
+{
+ std::unique_ptr<Times> ptr;
+ if (IsValid()) {
+ // Invalidate this and re-use times
+ ptr.swap(this->times);
+ } else {
+ ptr = cm::make_unique<Times>();
+ }
+
+#if defined(_WIN32) && !defined(__CYGWIN__)
+ cmFileTimes::WindowsHandle handle =
+ CreateFileW(cmSystemTools::ConvertToWindowsExtendedPath(fileName).c_str(),
+ GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS, 0);
+ if (!handle) {
+ return false;
+ }
+ if (!GetFileTime(handle, &ptr->timeCreation, &ptr->timeLastAccess,
+ &ptr->timeLastWrite)) {
+ return false;
+ }
+#else
+ struct stat st;
+ if (stat(fileName.c_str(), &st) < 0) {
+ return false;
+ }
+ ptr->timeBuf.actime = st.st_atime;
+ ptr->timeBuf.modtime = st.st_mtime;
+#endif
+ // Accept times
+ this->times = std::move(ptr);
+ return true;
+}
+
+bool cmFileTimes::Store(std::string const& fileName) const
+{
+ if (!IsValid()) {
+ return false;
+ }
+
+#if defined(_WIN32) && !defined(__CYGWIN__)
+ cmFileTimes::WindowsHandle handle = CreateFileW(
+ cmSystemTools::ConvertToWindowsExtendedPath(fileName).c_str(),
+ FILE_WRITE_ATTRIBUTES, 0, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
+ if (!handle) {
+ return false;
+ }
+ return SetFileTime(handle, &this->times->timeCreation,
+ &this->times->timeLastAccess,
+ &this->times->timeLastWrite) != 0;
+#else
+ return utime(fileName.c_str(), &this->times->timeBuf) >= 0;
+#endif
+}
+
+bool cmFileTimes::Copy(std::string const& fromFile, std::string const& toFile)
+{
+ cmFileTimes fileTimes;
+ return (fileTimes.Load(fromFile) && fileTimes.Store(toFile));
+}
diff --git a/Source/cmFileTimes.h b/Source/cmFileTimes.h
new file mode 100644
index 000000000..cbf0fe20a
--- /dev/null
+++ b/Source/cmFileTimes.h
@@ -0,0 +1,40 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#ifndef cmFileTimes_h
+#define cmFileTimes_h
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <memory> // IWYU pragma: keep
+#include <string>
+
+/** \class cmFileTimes
+ * \brief Loads and stores file times.
+ */
+class cmFileTimes
+{
+public:
+ cmFileTimes();
+ //! Calls Load()
+ cmFileTimes(std::string const& fileName);
+ ~cmFileTimes();
+
+ //! @return true, if file times were loaded successfully
+ bool IsValid() const { return (times != nullptr); }
+ //! Try to load the file times from @a fileName and @return IsValid()
+ bool Load(std::string const& fileName);
+ //! Stores the file times at @a fileName (if IsValid())
+ bool Store(std::string const& fileName) const;
+
+ //! Copies the file times of @a fromFile to @a toFile
+ static bool Copy(std::string const& fromFile, std::string const& toFile);
+
+private:
+#ifdef _WIN32
+ class WindowsHandle;
+#endif
+ class Times;
+ std::unique_ptr<Times> times;
+};
+
+#endif
diff --git a/Source/cmGetPipes.cxx b/Source/cmGetPipes.cxx
new file mode 100644
index 000000000..ad323f7ee
--- /dev/null
+++ b/Source/cmGetPipes.cxx
@@ -0,0 +1,48 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmGetPipes.h"
+
+#include "cm_uv.h"
+
+#include <fcntl.h>
+
+#if defined(_WIN32) && !defined(__CYGWIN__)
+# include <io.h>
+
+int cmGetPipes(int* fds)
+{
+ SECURITY_ATTRIBUTES attr;
+ HANDLE readh, writeh;
+ attr.nLength = sizeof(attr);
+ attr.lpSecurityDescriptor = nullptr;
+ attr.bInheritHandle = FALSE;
+ if (!CreatePipe(&readh, &writeh, &attr, 0))
+ return uv_translate_sys_error(GetLastError());
+ fds[0] = _open_osfhandle((intptr_t)readh, 0);
+ fds[1] = _open_osfhandle((intptr_t)writeh, 0);
+ if (fds[0] == -1 || fds[1] == -1) {
+ CloseHandle(readh);
+ CloseHandle(writeh);
+ return uv_translate_sys_error(GetLastError());
+ }
+ return 0;
+}
+#else
+# include <errno.h>
+# include <unistd.h>
+
+int cmGetPipes(int* fds)
+{
+ if (pipe(fds) == -1) {
+ return uv_translate_sys_error(errno);
+ }
+
+ if (fcntl(fds[0], F_SETFD, FD_CLOEXEC) == -1 ||
+ fcntl(fds[1], F_SETFD, FD_CLOEXEC) == -1) {
+ close(fds[0]);
+ close(fds[1]);
+ return uv_translate_sys_error(errno);
+ }
+ return 0;
+}
+#endif
diff --git a/Source/cmGetPipes.h b/Source/cmGetPipes.h
new file mode 100644
index 000000000..2a46b51b3
--- /dev/null
+++ b/Source/cmGetPipes.h
@@ -0,0 +1,8 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#ifndef cmGetPipes_h
+#define cmGetPipes_h
+
+int cmGetPipes(int* fds);
+
+#endif
diff --git a/Source/cmGlobVerificationManager.cxx b/Source/cmGlobVerificationManager.cxx
new file mode 100644
index 000000000..9fb417098
--- /dev/null
+++ b/Source/cmGlobVerificationManager.cxx
@@ -0,0 +1,178 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmGlobVerificationManager.h"
+
+#include "cmsys/FStream.hxx"
+#include <sstream>
+
+#include "cmGeneratedFileStream.h"
+#include "cmListFileCache.h"
+#include "cmSystemTools.h"
+#include "cmVersion.h"
+
+bool cmGlobVerificationManager::SaveVerificationScript(const std::string& path)
+{
+ if (this->Cache.empty()) {
+ return true;
+ }
+
+ std::string scriptFile = path;
+ scriptFile += "/CMakeFiles";
+ std::string stampFile = scriptFile;
+ cmSystemTools::MakeDirectory(scriptFile);
+ scriptFile += "/VerifyGlobs.cmake";
+ stampFile += "/cmake.verify_globs";
+ cmGeneratedFileStream verifyScriptFile(scriptFile);
+ verifyScriptFile.SetCopyIfDifferent(true);
+ if (!verifyScriptFile) {
+ cmSystemTools::Error("Unable to open verification script file for save. " +
+ scriptFile);
+ cmSystemTools::ReportLastSystemError("");
+ return false;
+ }
+
+ verifyScriptFile << std::boolalpha;
+ verifyScriptFile << "# CMAKE generated file: DO NOT EDIT!\n"
+ << "# Generated by CMake Version "
+ << cmVersion::GetMajorVersion() << "."
+ << cmVersion::GetMinorVersion() << "\n";
+
+ for (auto const& i : this->Cache) {
+ CacheEntryKey k = std::get<0>(i);
+ CacheEntryValue v = std::get<1>(i);
+
+ if (!v.Initialized) {
+ continue;
+ }
+
+ verifyScriptFile << "\n";
+
+ for (auto const& bt : v.Backtraces) {
+ verifyScriptFile << "# " << std::get<0>(bt);
+ std::get<1>(bt).PrintTitle(verifyScriptFile);
+ verifyScriptFile << "\n";
+ }
+
+ k.PrintGlobCommand(verifyScriptFile, "NEW_GLOB");
+ verifyScriptFile << "\n";
+
+ verifyScriptFile << "set(OLD_GLOB\n";
+ for (const std::string& file : v.Files) {
+ verifyScriptFile << " \"" << file << "\"\n";
+ }
+ verifyScriptFile << " )\n";
+
+ verifyScriptFile << "if(NOT \"${NEW_GLOB}\" STREQUAL \"${OLD_GLOB}\")\n"
+ << " message(\"-- GLOB mismatch!\")\n"
+ << " file(TOUCH_NOCREATE \"" << stampFile << "\")\n"
+ << "endif()\n";
+ }
+ verifyScriptFile.Close();
+
+ cmsys::ofstream verifyStampFile(stampFile.c_str());
+ if (!verifyStampFile) {
+ cmSystemTools::Error("Unable to open verification stamp file for write. " +
+ stampFile);
+ return false;
+ }
+ verifyStampFile << "# This file is generated by CMake for checking of the "
+ "VerifyGlobs.cmake file\n";
+ this->VerifyScript = scriptFile;
+ this->VerifyStamp = stampFile;
+ return true;
+}
+
+bool cmGlobVerificationManager::DoWriteVerifyTarget() const
+{
+ return !this->VerifyScript.empty() && !this->VerifyStamp.empty();
+}
+
+bool cmGlobVerificationManager::CacheEntryKey::operator<(
+ const CacheEntryKey& r) const
+{
+ if (this->Recurse < r.Recurse) {
+ return true;
+ }
+ if (this->Recurse > r.Recurse) {
+ return false;
+ }
+ if (this->ListDirectories < r.ListDirectories) {
+ return true;
+ }
+ if (this->ListDirectories > r.ListDirectories) {
+ return false;
+ }
+ if (this->FollowSymlinks < r.FollowSymlinks) {
+ return true;
+ }
+ if (this->FollowSymlinks > r.FollowSymlinks) {
+ return false;
+ }
+ if (this->Relative < r.Relative) {
+ return true;
+ }
+ if (this->Relative > r.Relative) {
+ return false;
+ }
+ if (this->Expression < r.Expression) {
+ return true;
+ }
+ if (this->Expression > r.Expression) {
+ return false;
+ }
+ return false;
+}
+
+void cmGlobVerificationManager::CacheEntryKey::PrintGlobCommand(
+ std::ostream& out, const std::string& cmdVar)
+{
+ out << "file(GLOB" << (this->Recurse ? "_RECURSE " : " ");
+ out << cmdVar << " ";
+ if (this->Recurse && this->FollowSymlinks) {
+ out << "FOLLOW_SYMLINKS ";
+ }
+ out << "LIST_DIRECTORIES " << this->ListDirectories << " ";
+ if (!this->Relative.empty()) {
+ out << "RELATIVE \"" << this->Relative << "\" ";
+ }
+ out << "\"" << this->Expression << "\")";
+}
+
+void cmGlobVerificationManager::AddCacheEntry(
+ const bool recurse, const bool listDirectories, const bool followSymlinks,
+ const std::string& relative, const std::string& expression,
+ const std::vector<std::string>& files, const std::string& variable,
+ const cmListFileBacktrace& backtrace)
+{
+ CacheEntryKey key = CacheEntryKey(recurse, listDirectories, followSymlinks,
+ relative, expression);
+ CacheEntryValue& value = this->Cache[key];
+ if (!value.Initialized) {
+ value.Files = files;
+ value.Initialized = true;
+ value.Backtraces.emplace_back(variable, backtrace);
+ } else if (value.Initialized && value.Files != files) {
+ std::ostringstream message;
+ message << std::boolalpha;
+ message << "The glob expression\n";
+ key.PrintGlobCommand(message, variable);
+ backtrace.PrintTitle(message);
+ message << "\nwas already present in the glob cache but the directory\n"
+ "contents have changed during the configuration run.\n";
+ message << "Matching glob expressions:";
+ for (auto const& bt : value.Backtraces) {
+ message << "\n " << std::get<0>(bt);
+ std::get<1>(bt).PrintTitle(message);
+ }
+ cmSystemTools::Error(message.str());
+ } else {
+ value.Backtraces.emplace_back(variable, backtrace);
+ }
+}
+
+void cmGlobVerificationManager::Reset()
+{
+ this->Cache.clear();
+ this->VerifyScript.clear();
+ this->VerifyStamp.clear();
+}
diff --git a/Source/cmGlobVerificationManager.h b/Source/cmGlobVerificationManager.h
new file mode 100644
index 000000000..48606d3f3
--- /dev/null
+++ b/Source/cmGlobVerificationManager.h
@@ -0,0 +1,85 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#ifndef cmGlobVerificationManager_h
+#define cmGlobVerificationManager_h
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include "cmListFileCache.h"
+
+#include <iosfwd>
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+/** \class cmGlobVerificationManager
+ * \brief Class for expressing build-time dependencies on glob expressions.
+ *
+ * Generates a CMake script which verifies glob outputs during prebuild.
+ *
+ */
+class cmGlobVerificationManager
+{
+protected:
+ //! Save verification script for given makefile.
+ //! Saves to output <path>/<CMakeFilesDirectory>/VerifyGlobs.cmake
+ bool SaveVerificationScript(const std::string& path);
+
+ //! Add an entry into the glob cache
+ void AddCacheEntry(bool recurse, bool listDirectories, bool followSymlinks,
+ const std::string& relative,
+ const std::string& expression,
+ const std::vector<std::string>& files,
+ const std::string& variable,
+ const cmListFileBacktrace& bt);
+
+ //! Clear the glob cache for state reset.
+ void Reset();
+
+ //! Check targets should be written in generated build system.
+ bool DoWriteVerifyTarget() const;
+
+ //! Get the paths to the generated script and stamp files
+ std::string const& GetVerifyScript() const { return this->VerifyScript; }
+ std::string const& GetVerifyStamp() const { return this->VerifyStamp; }
+
+private:
+ struct CacheEntryKey
+ {
+ const bool Recurse;
+ const bool ListDirectories;
+ const bool FollowSymlinks;
+ const std::string Relative;
+ const std::string Expression;
+ CacheEntryKey(const bool rec, const bool l, const bool s, std::string rel,
+ std::string e)
+ : Recurse(rec)
+ , ListDirectories(l)
+ , FollowSymlinks(s)
+ , Relative(std::move(rel))
+ , Expression(std::move(e))
+ {
+ }
+ bool operator<(const CacheEntryKey& r) const;
+ void PrintGlobCommand(std::ostream& out, const std::string& cmdVar);
+ };
+
+ struct CacheEntryValue
+ {
+ bool Initialized = false;
+ std::vector<std::string> Files;
+ std::vector<std::pair<std::string, cmListFileBacktrace>> Backtraces;
+ };
+
+ typedef std::map<CacheEntryKey, CacheEntryValue> CacheEntryMap;
+ CacheEntryMap Cache;
+ std::string VerifyScript;
+ std::string VerifyStamp;
+
+ // Only cmState should be able to add cache values.
+ // cmGlobVerificationManager should never be used directly.
+ friend class cmState; // allow access to add cache values
+};
+
+#endif
diff --git a/Source/cmGlobalVisualStudioVersionedGenerator.cxx b/Source/cmGlobalVisualStudioVersionedGenerator.cxx
new file mode 100644
index 000000000..66a57eb10
--- /dev/null
+++ b/Source/cmGlobalVisualStudioVersionedGenerator.cxx
@@ -0,0 +1,519 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmGlobalVisualStudioVersionedGenerator.h"
+
+#include "cmAlgorithms.h"
+#include "cmDocumentationEntry.h"
+#include "cmLocalVisualStudio10Generator.h"
+#include "cmMakefile.h"
+#include "cmVSSetupHelper.h"
+#include "cmake.h"
+
+#if defined(_M_ARM64)
+# define HOST_PLATFORM_NAME "ARM64"
+# define HOST_TOOLS_ARCH ""
+#elif defined(_M_ARM)
+# define HOST_PLATFORM_NAME "ARM"
+# define HOST_TOOLS_ARCH ""
+#elif defined(_M_IA64)
+# define HOST_PLATFORM_NAME "Itanium"
+# define HOST_TOOLS_ARCH ""
+#elif defined(_WIN64)
+# define HOST_PLATFORM_NAME "x64"
+# define HOST_TOOLS_ARCH "x64"
+#else
+static bool VSIsWow64()
+{
+ BOOL isWow64 = false;
+ return IsWow64Process(GetCurrentProcess(), &isWow64) && isWow64;
+}
+#endif
+
+static std::string VSHostPlatformName()
+{
+#ifdef HOST_PLATFORM_NAME
+ return HOST_PLATFORM_NAME;
+#else
+ if (VSIsWow64()) {
+ return "x64";
+ } else {
+ return "Win32";
+ }
+#endif
+}
+
+static std::string VSHostArchitecture()
+{
+#ifdef HOST_TOOLS_ARCH
+ return HOST_TOOLS_ARCH;
+#else
+ if (VSIsWow64()) {
+ return "x64";
+ } else {
+ return "x86";
+ }
+#endif
+}
+
+static unsigned int VSVersionToMajor(
+ cmGlobalVisualStudioGenerator::VSVersion v)
+{
+ switch (v) {
+ case cmGlobalVisualStudioGenerator::VS9:
+ return 9;
+ case cmGlobalVisualStudioGenerator::VS10:
+ return 10;
+ case cmGlobalVisualStudioGenerator::VS11:
+ return 11;
+ case cmGlobalVisualStudioGenerator::VS12:
+ return 12;
+ case cmGlobalVisualStudioGenerator::VS14:
+ return 14;
+ case cmGlobalVisualStudioGenerator::VS15:
+ return 15;
+ case cmGlobalVisualStudioGenerator::VS16:
+ return 16;
+ }
+ return 0;
+}
+
+static const char* VSVersionToToolset(
+ cmGlobalVisualStudioGenerator::VSVersion v)
+{
+ switch (v) {
+ case cmGlobalVisualStudioGenerator::VS9:
+ return "v90";
+ case cmGlobalVisualStudioGenerator::VS10:
+ return "v100";
+ case cmGlobalVisualStudioGenerator::VS11:
+ return "v110";
+ case cmGlobalVisualStudioGenerator::VS12:
+ return "v120";
+ case cmGlobalVisualStudioGenerator::VS14:
+ return "v140";
+ case cmGlobalVisualStudioGenerator::VS15:
+ return "v141";
+ case cmGlobalVisualStudioGenerator::VS16:
+ return "v142";
+ }
+ return "";
+}
+
+static const char vs15generatorName[] = "Visual Studio 15 2017";
+
+// Map generator name without year to name with year.
+static const char* cmVS15GenName(const std::string& name, std::string& genName)
+{
+ if (strncmp(name.c_str(), vs15generatorName,
+ sizeof(vs15generatorName) - 6) != 0) {
+ return 0;
+ }
+ const char* p = name.c_str() + sizeof(vs15generatorName) - 6;
+ if (cmHasLiteralPrefix(p, " 2017")) {
+ p += 5;
+ }
+ genName = std::string(vs15generatorName) + p;
+ return p;
+}
+
+class cmGlobalVisualStudioVersionedGenerator::Factory15
+ : public cmGlobalGeneratorFactory
+{
+public:
+ cmGlobalGenerator* CreateGlobalGenerator(const std::string& name,
+ cmake* cm) const override
+ {
+ std::string genName;
+ const char* p = cmVS15GenName(name, genName);
+ if (!p) {
+ return 0;
+ }
+ if (!*p) {
+ return new cmGlobalVisualStudioVersionedGenerator(
+ cmGlobalVisualStudioGenerator::VS15, cm, genName, "");
+ }
+ if (*p++ != ' ') {
+ return 0;
+ }
+ if (strcmp(p, "Win64") == 0) {
+ return new cmGlobalVisualStudioVersionedGenerator(
+ cmGlobalVisualStudioGenerator::VS15, cm, genName, "x64");
+ }
+ if (strcmp(p, "ARM") == 0) {
+ return new cmGlobalVisualStudioVersionedGenerator(
+ cmGlobalVisualStudioGenerator::VS15, cm, genName, "ARM");
+ }
+ return 0;
+ }
+
+ void GetDocumentation(cmDocumentationEntry& entry) const override
+ {
+ entry.Name = std::string(vs15generatorName) + " [arch]";
+ entry.Brief = "Generates Visual Studio 2017 project files. "
+ "Optional [arch] can be \"Win64\" or \"ARM\".";
+ }
+
+ std::vector<std::string> GetGeneratorNames() const override
+ {
+ std::vector<std::string> names;
+ names.push_back(vs15generatorName);
+ return names;
+ }
+
+ std::vector<std::string> GetGeneratorNamesWithPlatform() const override
+ {
+ std::vector<std::string> names;
+ names.push_back(vs15generatorName + std::string(" ARM"));
+ names.push_back(vs15generatorName + std::string(" Win64"));
+ return names;
+ }
+
+ bool SupportsToolset() const override { return true; }
+ bool SupportsPlatform() const override { return true; }
+
+ std::vector<std::string> GetKnownPlatforms() const override
+ {
+ std::vector<std::string> platforms;
+ platforms.emplace_back("x64");
+ platforms.emplace_back("Win32");
+ platforms.emplace_back("ARM");
+ platforms.emplace_back("ARM64");
+ return platforms;
+ }
+
+ std::string GetDefaultPlatformName() const override { return "Win32"; }
+};
+
+cmGlobalGeneratorFactory*
+cmGlobalVisualStudioVersionedGenerator::NewFactory15()
+{
+ return new Factory15;
+}
+
+static const char vs16generatorName[] = "Visual Studio 16 2019";
+
+// Map generator name without year to name with year.
+static const char* cmVS16GenName(const std::string& name, std::string& genName)
+{
+ if (strncmp(name.c_str(), vs16generatorName,
+ sizeof(vs16generatorName) - 6) != 0) {
+ return 0;
+ }
+ const char* p = name.c_str() + sizeof(vs16generatorName) - 6;
+ if (cmHasLiteralPrefix(p, " 2019")) {
+ p += 5;
+ }
+ genName = std::string(vs16generatorName) + p;
+ return p;
+}
+
+class cmGlobalVisualStudioVersionedGenerator::Factory16
+ : public cmGlobalGeneratorFactory
+{
+public:
+ cmGlobalGenerator* CreateGlobalGenerator(const std::string& name,
+ cmake* cm) const override
+ {
+ std::string genName;
+ const char* p = cmVS16GenName(name, genName);
+ if (!p) {
+ return 0;
+ }
+ if (!*p) {
+ return new cmGlobalVisualStudioVersionedGenerator(
+ cmGlobalVisualStudioGenerator::VS16, cm, genName, "");
+ }
+ return 0;
+ }
+
+ void GetDocumentation(cmDocumentationEntry& entry) const override
+ {
+ entry.Name = std::string(vs16generatorName);
+ entry.Brief = "Generates Visual Studio 2019 project files. "
+ "Use -A option to specify architecture.";
+ }
+
+ std::vector<std::string> GetGeneratorNames() const override
+ {
+ std::vector<std::string> names;
+ names.push_back(vs16generatorName);
+ return names;
+ }
+
+ std::vector<std::string> GetGeneratorNamesWithPlatform() const override
+ {
+ return std::vector<std::string>();
+ }
+
+ bool SupportsToolset() const override { return true; }
+ bool SupportsPlatform() const override { return true; }
+
+ std::vector<std::string> GetKnownPlatforms() const override
+ {
+ std::vector<std::string> platforms;
+ platforms.emplace_back("x64");
+ platforms.emplace_back("Win32");
+ platforms.emplace_back("ARM");
+ platforms.emplace_back("ARM64");
+ return platforms;
+ }
+
+ std::string GetDefaultPlatformName() const override
+ {
+ return VSHostPlatformName();
+ }
+};
+
+cmGlobalGeneratorFactory*
+cmGlobalVisualStudioVersionedGenerator::NewFactory16()
+{
+ return new Factory16;
+}
+
+cmGlobalVisualStudioVersionedGenerator::cmGlobalVisualStudioVersionedGenerator(
+ VSVersion version, cmake* cm, const std::string& name,
+ std::string const& platformInGeneratorName)
+ : cmGlobalVisualStudio14Generator(cm, name, platformInGeneratorName)
+ , vsSetupAPIHelper(VSVersionToMajor(version))
+{
+ this->Version = version;
+ this->ExpressEdition = false;
+ this->DefaultPlatformToolset = VSVersionToToolset(this->Version);
+ this->DefaultCLFlagTableName = VSVersionToToolset(this->Version);
+ this->DefaultCSharpFlagTableName = VSVersionToToolset(this->Version);
+ this->DefaultLinkFlagTableName = VSVersionToToolset(this->Version);
+ if (this->Version >= cmGlobalVisualStudioGenerator::VS16) {
+ this->DefaultPlatformName = VSHostPlatformName();
+ this->DefaultPlatformToolsetHostArchitecture = VSHostArchitecture();
+ }
+}
+
+bool cmGlobalVisualStudioVersionedGenerator::MatchesGeneratorName(
+ const std::string& name) const
+{
+ std::string genName;
+ switch (this->Version) {
+ case cmGlobalVisualStudioGenerator::VS9:
+ case cmGlobalVisualStudioGenerator::VS10:
+ case cmGlobalVisualStudioGenerator::VS11:
+ case cmGlobalVisualStudioGenerator::VS12:
+ case cmGlobalVisualStudioGenerator::VS14:
+ break;
+ case cmGlobalVisualStudioGenerator::VS15:
+ if (cmVS15GenName(name, genName)) {
+ return genName == this->GetName();
+ }
+ break;
+ case cmGlobalVisualStudioGenerator::VS16:
+ if (cmVS16GenName(name, genName)) {
+ return genName == this->GetName();
+ }
+ break;
+ }
+ return false;
+}
+
+bool cmGlobalVisualStudioVersionedGenerator::SetGeneratorInstance(
+ std::string const& i, cmMakefile* mf)
+{
+ if (!i.empty()) {
+ if (!this->vsSetupAPIHelper.SetVSInstance(i)) {
+ std::ostringstream e;
+ /* clang-format off */
+ e <<
+ "Generator\n"
+ " " << this->GetName() << "\n"
+ "could not find specified instance of Visual Studio:\n"
+ " " << i;
+ /* clang-format on */
+ mf->IssueMessage(MessageType::FATAL_ERROR, e.str());
+ return false;
+ }
+ }
+
+ std::string vsInstance;
+ if (!this->vsSetupAPIHelper.GetVSInstanceInfo(vsInstance)) {
+ std::ostringstream e;
+ /* clang-format off */
+ e <<
+ "Generator\n"
+ " " << this->GetName() << "\n"
+ "could not find any instance of Visual Studio.\n";
+ /* clang-format on */
+ mf->IssueMessage(MessageType::FATAL_ERROR, e.str());
+ return false;
+ }
+
+ // Save the selected instance persistently.
+ std::string genInstance = mf->GetSafeDefinition("CMAKE_GENERATOR_INSTANCE");
+ if (vsInstance != genInstance) {
+ this->CMakeInstance->AddCacheEntry(
+ "CMAKE_GENERATOR_INSTANCE", vsInstance.c_str(),
+ "Generator instance identifier.", cmStateEnums::INTERNAL);
+ }
+
+ return true;
+}
+
+bool cmGlobalVisualStudioVersionedGenerator::GetVSInstance(
+ std::string& dir) const
+{
+ return vsSetupAPIHelper.GetVSInstanceInfo(dir);
+}
+
+bool cmGlobalVisualStudioVersionedGenerator::IsDefaultToolset(
+ const std::string& version) const
+{
+ if (version.empty()) {
+ return true;
+ }
+
+ std::string vcToolsetVersion;
+ if (this->vsSetupAPIHelper.GetVCToolsetVersion(vcToolsetVersion)) {
+
+ cmsys::RegularExpression regex("[0-9][0-9]\\.[0-9]+");
+ if (regex.find(version) && regex.find(vcToolsetVersion)) {
+ const auto majorMinorEnd = vcToolsetVersion.find('.', 3);
+ const auto majorMinor = vcToolsetVersion.substr(0, majorMinorEnd);
+ return version == majorMinor;
+ }
+ }
+
+ return false;
+}
+
+std::string cmGlobalVisualStudioVersionedGenerator::GetAuxiliaryToolset() const
+{
+ const char* version = this->GetPlatformToolsetVersion();
+ if (version) {
+ std::string instancePath;
+ GetVSInstance(instancePath);
+ std::string toolsetDir = instancePath + "/VC/Auxiliary/Build";
+ char sep = '/';
+ if (cmSystemTools::VersionCompareGreaterEq(version, "14.20")) {
+ std::string toolsetDot = toolsetDir + "." + version +
+ "/Microsoft.VCToolsVersion." + version + ".props";
+ if (cmSystemTools::PathExists(toolsetDot)) {
+ sep = '.';
+ }
+ }
+ std::string toolsetPath = toolsetDir + sep + version +
+ "/Microsoft.VCToolsVersion." + version + ".props";
+ cmSystemTools::ConvertToUnixSlashes(toolsetPath);
+ return toolsetPath;
+ }
+ return {};
+}
+
+bool cmGlobalVisualStudioVersionedGenerator::InitializeWindows(cmMakefile* mf)
+{
+ // If the Win 8.1 SDK is installed then we can select a SDK matching
+ // the target Windows version.
+ if (this->IsWin81SDKInstalled()) {
+ // VS 2019 does not default to 8.1 so specify it explicitly when needed.
+ if (this->Version >= cmGlobalVisualStudioGenerator::VS16 &&
+ !cmSystemTools::VersionCompareGreater(this->SystemVersion, "8.1")) {
+ this->SetWindowsTargetPlatformVersion("8.1", mf);
+ return true;
+ }
+ return cmGlobalVisualStudio14Generator::InitializeWindows(mf);
+ }
+ // Otherwise we must choose a Win 10 SDK even if we are not targeting
+ // Windows 10.
+ return this->SelectWindows10SDK(mf, false);
+}
+
+bool cmGlobalVisualStudioVersionedGenerator::SelectWindowsStoreToolset(
+ std::string& toolset) const
+{
+ if (cmHasLiteralPrefix(this->SystemVersion, "10.0")) {
+ if (this->IsWindowsStoreToolsetInstalled() &&
+ this->IsWindowsDesktopToolsetInstalled()) {
+ toolset = VSVersionToToolset(this->Version);
+ return true;
+ } else {
+ return false;
+ }
+ }
+ return this->cmGlobalVisualStudio14Generator::SelectWindowsStoreToolset(
+ toolset);
+}
+
+bool cmGlobalVisualStudioVersionedGenerator::IsWindowsDesktopToolsetInstalled()
+ const
+{
+ return vsSetupAPIHelper.IsVSInstalled();
+}
+
+bool cmGlobalVisualStudioVersionedGenerator::IsWindowsStoreToolsetInstalled()
+ const
+{
+ return vsSetupAPIHelper.IsWin10SDKInstalled();
+}
+
+bool cmGlobalVisualStudioVersionedGenerator::IsWin81SDKInstalled() const
+{
+ // Does the VS installer tool know about one?
+ if (vsSetupAPIHelper.IsWin81SDKInstalled()) {
+ return true;
+ }
+
+ // Does the registry know about one (e.g. from VS 2015)?
+ std::string win81Root;
+ if (cmSystemTools::ReadRegistryValue(
+ "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\"
+ "Windows Kits\\Installed Roots;KitsRoot81",
+ win81Root, cmSystemTools::KeyWOW64_32) ||
+ cmSystemTools::ReadRegistryValue(
+ "HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\"
+ "Windows Kits\\Installed Roots;KitsRoot81",
+ win81Root, cmSystemTools::KeyWOW64_32)) {
+ return cmSystemTools::FileExists(win81Root + "/include/um/windows.h",
+ true);
+ }
+ return false;
+}
+
+std::string cmGlobalVisualStudioVersionedGenerator::GetWindows10SDKMaxVersion()
+ const
+{
+ return std::string();
+}
+
+std::string cmGlobalVisualStudioVersionedGenerator::FindMSBuildCommand()
+{
+ std::string msbuild;
+
+ // Ask Visual Studio Installer tool.
+ std::string vs;
+ if (vsSetupAPIHelper.GetVSInstanceInfo(vs)) {
+ msbuild = vs + "/MSBuild/Current/Bin/MSBuild.exe";
+ if (cmSystemTools::FileExists(msbuild)) {
+ return msbuild;
+ }
+ msbuild = vs + "/MSBuild/15.0/Bin/MSBuild.exe";
+ if (cmSystemTools::FileExists(msbuild)) {
+ return msbuild;
+ }
+ }
+
+ msbuild = "MSBuild.exe";
+ return msbuild;
+}
+
+std::string cmGlobalVisualStudioVersionedGenerator::FindDevEnvCommand()
+{
+ std::string devenv;
+
+ // Ask Visual Studio Installer tool.
+ std::string vs;
+ if (vsSetupAPIHelper.GetVSInstanceInfo(vs)) {
+ devenv = vs + "/Common7/IDE/devenv.com";
+ if (cmSystemTools::FileExists(devenv)) {
+ return devenv;
+ }
+ }
+
+ devenv = "devenv.com";
+ return devenv;
+}
diff --git a/Source/cmGlobalVisualStudioVersionedGenerator.h b/Source/cmGlobalVisualStudioVersionedGenerator.h
new file mode 100644
index 000000000..466816ba8
--- /dev/null
+++ b/Source/cmGlobalVisualStudioVersionedGenerator.h
@@ -0,0 +1,65 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#ifndef cmGlobalVisualStudioVersionedGenerator_h
+#define cmGlobalVisualStudioVersionedGenerator_h
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <iosfwd>
+#include <string>
+
+#include "cmGlobalVisualStudio14Generator.h"
+#include "cmVSSetupHelper.h"
+
+class cmGlobalGeneratorFactory;
+class cmake;
+
+/** \class cmGlobalVisualStudioVersionedGenerator */
+class cmGlobalVisualStudioVersionedGenerator
+ : public cmGlobalVisualStudio14Generator
+{
+public:
+ static cmGlobalGeneratorFactory* NewFactory15();
+ static cmGlobalGeneratorFactory* NewFactory16();
+
+ bool MatchesGeneratorName(const std::string& name) const override;
+
+ bool SetGeneratorInstance(std::string const& i, cmMakefile* mf) override;
+
+ bool GetVSInstance(std::string& dir) const;
+
+ bool IsDefaultToolset(const std::string& version) const override;
+ std::string GetAuxiliaryToolset() const override;
+
+protected:
+ cmGlobalVisualStudioVersionedGenerator(
+ VSVersion version, cmake* cm, const std::string& name,
+ std::string const& platformInGeneratorName);
+
+ bool InitializeWindows(cmMakefile* mf) override;
+ bool SelectWindowsStoreToolset(std::string& toolset) const override;
+
+ // Used to verify that the Desktop toolset for the current generator is
+ // installed on the machine.
+ bool IsWindowsDesktopToolsetInstalled() const override;
+
+ // These aren't virtual because we need to check if the selected version
+ // of the toolset is installed
+ bool IsWindowsStoreToolsetInstalled() const;
+
+ // Check for a Win 8 SDK known to the registry or VS installer tool.
+ bool IsWin81SDKInstalled() const;
+
+ std::string GetWindows10SDKMaxVersion() const override;
+
+ std::string FindMSBuildCommand() override;
+ std::string FindDevEnvCommand() override;
+
+private:
+ class Factory15;
+ friend class Factory15;
+ class Factory16;
+ friend class Factory16;
+ mutable cmVSSetupAPIHelper vsSetupAPIHelper;
+};
+#endif
diff --git a/Source/cmIncludeGuardCommand.cxx b/Source/cmIncludeGuardCommand.cxx
new file mode 100644
index 000000000..505b07cad
--- /dev/null
+++ b/Source/cmIncludeGuardCommand.cxx
@@ -0,0 +1,108 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmIncludeGuardCommand.h"
+
+#include "cmExecutionStatus.h"
+#include "cmMakefile.h"
+#include "cmStateDirectory.h"
+#include "cmStateSnapshot.h"
+#include "cmSystemTools.h"
+#include "cmake.h"
+
+namespace {
+
+enum IncludeGuardScope
+{
+ VARIABLE,
+ DIRECTORY,
+ GLOBAL
+};
+
+std::string GetIncludeGuardVariableName(std::string const& filePath)
+{
+ std::string result = "__INCGUARD_";
+#ifdef CMAKE_BUILD_WITH_CMAKE
+ result += cmSystemTools::ComputeStringMD5(filePath);
+#else
+ result += cmSystemTools::MakeCidentifier(filePath);
+#endif
+ result += "__";
+ return result;
+}
+
+bool CheckIncludeGuardIsSet(cmMakefile* mf, std::string const& includeGuardVar)
+{
+ if (mf->GetProperty(includeGuardVar)) {
+ return true;
+ }
+ cmStateSnapshot dirSnapshot =
+ mf->GetStateSnapshot().GetBuildsystemDirectoryParent();
+ while (dirSnapshot.GetState()) {
+ cmStateDirectory stateDir = dirSnapshot.GetDirectory();
+ if (stateDir.GetProperty(includeGuardVar)) {
+ return true;
+ }
+ dirSnapshot = dirSnapshot.GetBuildsystemDirectoryParent();
+ }
+ return false;
+}
+
+} // anonymous namespace
+
+// cmIncludeGuardCommand
+bool cmIncludeGuardCommand::InitialPass(std::vector<std::string> const& args,
+ cmExecutionStatus& status)
+{
+ if (args.size() > 1) {
+ this->SetError(
+ "given an invalid number of arguments. The command takes at "
+ "most 1 argument.");
+ return false;
+ }
+
+ IncludeGuardScope scope = VARIABLE;
+
+ if (!args.empty()) {
+ std::string const& arg = args[0];
+ if (arg == "DIRECTORY") {
+ scope = DIRECTORY;
+ } else if (arg == "GLOBAL") {
+ scope = GLOBAL;
+ } else {
+ this->SetError("given an invalid scope: " + arg);
+ return false;
+ }
+ }
+
+ std::string includeGuardVar = GetIncludeGuardVariableName(
+ this->Makefile->GetDefinition("CMAKE_CURRENT_LIST_FILE"));
+
+ cmMakefile* const mf = this->Makefile;
+
+ switch (scope) {
+ case VARIABLE:
+ if (mf->IsDefinitionSet(includeGuardVar)) {
+ status.SetReturnInvoked();
+ return true;
+ }
+ mf->AddDefinition(includeGuardVar, true);
+ break;
+ case DIRECTORY:
+ if (CheckIncludeGuardIsSet(mf, includeGuardVar)) {
+ status.SetReturnInvoked();
+ return true;
+ }
+ mf->SetProperty(includeGuardVar, "TRUE");
+ break;
+ case GLOBAL:
+ cmake* const cm = mf->GetCMakeInstance();
+ if (cm->GetProperty(includeGuardVar)) {
+ status.SetReturnInvoked();
+ return true;
+ }
+ cm->SetProperty(includeGuardVar, "TRUE");
+ break;
+ }
+
+ return true;
+}
diff --git a/Source/cmIncludeGuardCommand.h b/Source/cmIncludeGuardCommand.h
new file mode 100644
index 000000000..eaad9b89a
--- /dev/null
+++ b/Source/cmIncludeGuardCommand.h
@@ -0,0 +1,37 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#ifndef cmIncludeGuardCommand_h
+#define cmIncludeGuardCommand_h
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <string>
+#include <vector>
+
+#include "cmCommand.h"
+
+class cmExecutionStatus;
+
+/** \class cmIncludeGuardCommand
+ * \brief cmIncludeGuardCommand identical to C++ #pragma_once command
+ * Can work in 3 modes: GLOBAL (works on global properties),
+ * DIRECTORY(use directory property), VARIABLE(unnamed overload without
+ * arguments) define an ordinary variable to be used as include guard checker
+ */
+class cmIncludeGuardCommand : public cmCommand
+{
+public:
+ /**
+ * This is a virtual constructor for the command.
+ */
+ cmCommand* Clone() override { return new cmIncludeGuardCommand; }
+
+ /**
+ * This is called when the command is first encountered in
+ * the CMakeLists.txt file.
+ */
+ bool InitialPass(std::vector<std::string> const& args,
+ cmExecutionStatus& status) override;
+};
+
+#endif
diff --git a/Source/cmInstallSubdirectoryGenerator.cxx b/Source/cmInstallSubdirectoryGenerator.cxx
new file mode 100644
index 000000000..1c0512ce5
--- /dev/null
+++ b/Source/cmInstallSubdirectoryGenerator.cxx
@@ -0,0 +1,76 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmInstallSubdirectoryGenerator.h"
+
+#include "cmLocalGenerator.h"
+#include "cmMakefile.h"
+#include "cmPolicies.h"
+#include "cmScriptGenerator.h"
+#include "cmSystemTools.h"
+
+#include <sstream>
+#include <vector>
+
+cmInstallSubdirectoryGenerator::cmInstallSubdirectoryGenerator(
+ cmMakefile* makefile, const char* binaryDirectory, bool excludeFromAll)
+ : cmInstallGenerator(nullptr, std::vector<std::string>(), nullptr,
+ MessageDefault, excludeFromAll)
+ , Makefile(makefile)
+ , BinaryDirectory(binaryDirectory)
+{
+}
+
+cmInstallSubdirectoryGenerator::~cmInstallSubdirectoryGenerator() = default;
+
+bool cmInstallSubdirectoryGenerator::HaveInstall()
+{
+ for (auto generator : this->Makefile->GetInstallGenerators()) {
+ if (generator->HaveInstall()) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void cmInstallSubdirectoryGenerator::CheckCMP0082(
+ bool& haveSubdirectoryInstall, bool& /*unused*/)
+{
+ if (this->HaveInstall()) {
+ haveSubdirectoryInstall = true;
+ }
+}
+
+bool cmInstallSubdirectoryGenerator::Compute(cmLocalGenerator* lg)
+{
+ this->LocalGenerator = lg;
+ return true;
+}
+
+void cmInstallSubdirectoryGenerator::GenerateScript(std::ostream& os)
+{
+ if (!this->ExcludeFromAll) {
+ cmPolicies::PolicyStatus status =
+ this->LocalGenerator->GetPolicyStatus(cmPolicies::CMP0082);
+ switch (status) {
+ case cmPolicies::WARN:
+ case cmPolicies::OLD:
+ // OLD behavior is handled in cmLocalGenerator::GenerateInstallRules()
+ break;
+
+ case cmPolicies::NEW:
+ case cmPolicies::REQUIRED_IF_USED:
+ case cmPolicies::REQUIRED_ALWAYS: {
+ Indent indent;
+ std::string odir = this->BinaryDirectory;
+ cmSystemTools::ConvertToUnixSlashes(odir);
+ os << indent << "if(NOT CMAKE_INSTALL_LOCAL_ONLY)\n"
+ << indent.Next()
+ << "# Include the install script for the subdirectory.\n"
+ << indent.Next() << "include(\"" << odir
+ << "/cmake_install.cmake\")\n"
+ << indent << "endif()\n\n";
+ } break;
+ }
+ }
+}
diff --git a/Source/cmInstallSubdirectoryGenerator.h b/Source/cmInstallSubdirectoryGenerator.h
new file mode 100644
index 000000000..22759d948
--- /dev/null
+++ b/Source/cmInstallSubdirectoryGenerator.h
@@ -0,0 +1,41 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#ifndef cmInstallSubdirectoryGenerator_h
+#define cmInstallSubdirectoryGenerator_h
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include "cmInstallGenerator.h"
+
+#include <iosfwd>
+#include <string>
+
+class cmLocalGenerator;
+class cmMakefile;
+
+/** \class cmInstallSubdirectoryGenerator
+ * \brief Generate target installation rules.
+ */
+class cmInstallSubdirectoryGenerator : public cmInstallGenerator
+{
+public:
+ cmInstallSubdirectoryGenerator(cmMakefile* makefile,
+ const char* binaryDirectory,
+ bool excludeFromAll);
+ ~cmInstallSubdirectoryGenerator() override;
+
+ bool HaveInstall() override;
+ void CheckCMP0082(bool& haveSubdirectoryInstall,
+ bool& haveInstallAfterSubdirectory) override;
+
+ bool Compute(cmLocalGenerator* lg) override;
+
+protected:
+ void GenerateScript(std::ostream& os) override;
+
+ cmMakefile* Makefile;
+ std::string BinaryDirectory;
+ cmLocalGenerator* LocalGenerator;
+};
+
+#endif
diff --git a/Source/cmJsonObjectDictionary.h b/Source/cmJsonObjectDictionary.h
new file mode 100644
index 000000000..8a2b52995
--- /dev/null
+++ b/Source/cmJsonObjectDictionary.h
@@ -0,0 +1,45 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#pragma once
+
+#include <string>
+
+// Vocabulary:
+
+static const std::string kARTIFACTS_KEY = "artifacts";
+static const std::string kBUILD_DIRECTORY_KEY = "buildDirectory";
+static const std::string kCOMPILE_FLAGS_KEY = "compileFlags";
+static const std::string kCONFIGURATIONS_KEY = "configurations";
+static const std::string kDEFINES_KEY = "defines";
+static const std::string kFILE_GROUPS_KEY = "fileGroups";
+static const std::string kFRAMEWORK_PATH_KEY = "frameworkPath";
+static const std::string kFULL_NAME_KEY = "fullName";
+static const std::string kINCLUDE_PATH_KEY = "includePath";
+static const std::string kIS_CMAKE_KEY = "isCMake";
+static const std::string kIS_GENERATED_KEY = "isGenerated";
+static const std::string kIS_SYSTEM_KEY = "isSystem";
+static const std::string kIS_TEMPORARY_KEY = "isTemporary";
+static const std::string kKEY_KEY = "key";
+static const std::string kLANGUAGE_KEY = "language";
+static const std::string kLINKER_LANGUAGE_KEY = "linkerLanguage";
+static const std::string kLINK_FLAGS_KEY = "linkFlags";
+static const std::string kLINK_LANGUAGE_FLAGS_KEY = "linkLanguageFlags";
+static const std::string kLINK_LIBRARIES_KEY = "linkLibraries";
+static const std::string kLINK_PATH_KEY = "linkPath";
+static const std::string kNAME_KEY = "name";
+static const std::string kPATH_KEY = "path";
+static const std::string kPROJECTS_KEY = "projects";
+static const std::string kPROPERTIES_KEY = "properties";
+static const std::string kSOURCE_DIRECTORY_KEY = "sourceDirectory";
+static const std::string kSOURCES_KEY = "sources";
+static const std::string kSYSROOT_KEY = "sysroot";
+static const std::string kTARGETS_KEY = "targets";
+static const std::string kTYPE_KEY = "type";
+static const std::string kVALUE_KEY = "value";
+static const std::string kHAS_INSTALL_RULE = "hasInstallRule";
+static const std::string kINSTALL_PATHS = "installPaths";
+static const std::string kCTEST_NAME = "ctestName";
+static const std::string kCTEST_COMMAND = "ctestCommand";
+static const std::string kCTEST_INFO = "ctestInfo";
+static const std::string kMINIMUM_CMAKE_VERSION = "minimumCMakeVersion";
+static const std::string kIS_GENERATOR_PROVIDED_KEY = "isGeneratorProvided";
diff --git a/Source/cmJsonObjects.cxx b/Source/cmJsonObjects.cxx
new file mode 100644
index 000000000..636a8e1c2
--- /dev/null
+++ b/Source/cmJsonObjects.cxx
@@ -0,0 +1,693 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmJsonObjects.h" // IWYU pragma: keep
+
+#include "cmAlgorithms.h"
+#include "cmGeneratorExpression.h"
+#include "cmGeneratorTarget.h"
+#include "cmGlobalGenerator.h"
+#include "cmInstallGenerator.h"
+#include "cmInstallSubdirectoryGenerator.h"
+#include "cmInstallTargetGenerator.h"
+#include "cmJsonObjectDictionary.h"
+#include "cmJsonObjects.h"
+#include "cmLinkLineComputer.h"
+#include "cmLocalGenerator.h"
+#include "cmMakefile.h"
+#include "cmProperty.h"
+#include "cmPropertyMap.h"
+#include "cmSourceFile.h"
+#include "cmState.h"
+#include "cmStateDirectory.h"
+#include "cmStateSnapshot.h"
+#include "cmStateTypes.h"
+#include "cmSystemTools.h"
+#include "cmTarget.h"
+#include "cmTest.h"
+#include "cmake.h"
+
+#include <algorithm>
+#include <cassert>
+#include <cstddef>
+#include <functional>
+#include <limits>
+#include <map>
+#include <set>
+#include <string>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+namespace {
+
+std::vector<std::string> getConfigurations(const cmake* cm)
+{
+ std::vector<std::string> configurations;
+ auto makefiles = cm->GetGlobalGenerator()->GetMakefiles();
+ if (makefiles.empty()) {
+ return configurations;
+ }
+
+ makefiles[0]->GetConfigurations(configurations);
+ if (configurations.empty()) {
+ configurations.emplace_back();
+ }
+ return configurations;
+}
+
+bool hasString(const Json::Value& v, const std::string& s)
+{
+ return !v.isNull() &&
+ std::any_of(v.begin(), v.end(),
+ [s](const Json::Value& i) { return i.asString() == s; });
+}
+
+template <class T>
+Json::Value fromStringList(const T& in)
+{
+ Json::Value result = Json::arrayValue;
+ for (std::string const& i : in) {
+ result.append(i);
+ }
+ return result;
+}
+
+} // namespace
+
+void cmGetCMakeInputs(const cmGlobalGenerator* gg,
+ const std::string& sourceDir,
+ const std::string& buildDir,
+ std::vector<std::string>* internalFiles,
+ std::vector<std::string>* explicitFiles,
+ std::vector<std::string>* tmpFiles)
+{
+ const std::string cmakeRootDir = cmSystemTools::GetCMakeRoot() + '/';
+ std::vector<cmMakefile*> const& makefiles = gg->GetMakefiles();
+ for (cmMakefile const* mf : makefiles) {
+ for (std::string const& lf : mf->GetListFiles()) {
+
+ const std::string startOfFile = lf.substr(0, cmakeRootDir.size());
+ const bool isInternal = (startOfFile == cmakeRootDir);
+ const bool isTemporary = !isInternal && (lf.find(buildDir + '/') == 0);
+
+ std::string toAdd = lf;
+ if (!sourceDir.empty()) {
+ const std::string& relative =
+ cmSystemTools::RelativePath(sourceDir, lf);
+ if (toAdd.size() > relative.size()) {
+ toAdd = relative;
+ }
+ }
+
+ if (isInternal) {
+ if (internalFiles) {
+ internalFiles->push_back(std::move(toAdd));
+ }
+ } else {
+ if (isTemporary) {
+ if (tmpFiles) {
+ tmpFiles->push_back(std::move(toAdd));
+ }
+ } else {
+ if (explicitFiles) {
+ explicitFiles->push_back(std::move(toAdd));
+ }
+ }
+ }
+ }
+ }
+}
+
+Json::Value cmDumpCMakeInputs(const cmake* cm)
+{
+ const cmGlobalGenerator* gg = cm->GetGlobalGenerator();
+ const std::string& buildDir = cm->GetHomeOutputDirectory();
+ const std::string& sourceDir = cm->GetHomeDirectory();
+
+ std::vector<std::string> internalFiles;
+ std::vector<std::string> explicitFiles;
+ std::vector<std::string> tmpFiles;
+ cmGetCMakeInputs(gg, sourceDir, buildDir, &internalFiles, &explicitFiles,
+ &tmpFiles);
+
+ Json::Value array = Json::arrayValue;
+
+ Json::Value tmp = Json::objectValue;
+ tmp[kIS_CMAKE_KEY] = true;
+ tmp[kIS_TEMPORARY_KEY] = false;
+ tmp[kSOURCES_KEY] = fromStringList(internalFiles);
+ array.append(tmp);
+
+ tmp = Json::objectValue;
+ tmp[kIS_CMAKE_KEY] = false;
+ tmp[kIS_TEMPORARY_KEY] = false;
+ tmp[kSOURCES_KEY] = fromStringList(explicitFiles);
+ array.append(tmp);
+
+ tmp = Json::objectValue;
+ tmp[kIS_CMAKE_KEY] = false;
+ tmp[kIS_TEMPORARY_KEY] = true;
+ tmp[kSOURCES_KEY] = fromStringList(tmpFiles);
+ array.append(tmp);
+
+ return array;
+}
+
+class LanguageData
+{
+public:
+ bool operator==(const LanguageData& other) const;
+
+ void SetDefines(const std::set<std::string>& defines);
+
+ bool IsGenerated = false;
+ std::string Language;
+ std::string Flags;
+ std::vector<std::string> Defines;
+ std::vector<std::pair<std::string, bool>> IncludePathList;
+};
+
+bool LanguageData::operator==(const LanguageData& other) const
+{
+ return Language == other.Language && Defines == other.Defines &&
+ Flags == other.Flags && IncludePathList == other.IncludePathList &&
+ IsGenerated == other.IsGenerated;
+}
+
+void LanguageData::SetDefines(const std::set<std::string>& defines)
+{
+ std::vector<std::string> result;
+ result.reserve(defines.size());
+ for (std::string const& i : defines) {
+ result.push_back(i);
+ }
+ std::sort(result.begin(), result.end());
+ Defines = std::move(result);
+}
+
+namespace std {
+
+template <>
+struct hash<LanguageData>
+{
+ std::size_t operator()(const LanguageData& in) const
+ {
+ using std::hash;
+ size_t result =
+ hash<std::string>()(in.Language) ^ hash<std::string>()(in.Flags);
+ for (auto const& i : in.IncludePathList) {
+ result = result ^
+ (hash<std::string>()(i.first) ^
+ (i.second ? std::numeric_limits<size_t>::max() : 0));
+ }
+ for (auto const& i : in.Defines) {
+ result = result ^ hash<std::string>()(i);
+ }
+ result =
+ result ^ (in.IsGenerated ? std::numeric_limits<size_t>::max() : 0);
+ return result;
+ }
+};
+
+} // namespace std
+
+static Json::Value DumpSourceFileGroup(const LanguageData& data,
+ const std::vector<std::string>& files,
+ const std::string& baseDir)
+{
+ Json::Value result = Json::objectValue;
+
+ if (!data.Language.empty()) {
+ result[kLANGUAGE_KEY] = data.Language;
+ if (!data.Flags.empty()) {
+ result[kCOMPILE_FLAGS_KEY] = data.Flags;
+ }
+ if (!data.IncludePathList.empty()) {
+ Json::Value includes = Json::arrayValue;
+ for (auto const& i : data.IncludePathList) {
+ Json::Value tmp = Json::objectValue;
+ tmp[kPATH_KEY] = i.first;
+ if (i.second) {
+ tmp[kIS_SYSTEM_KEY] = i.second;
+ }
+ includes.append(tmp);
+ }
+ result[kINCLUDE_PATH_KEY] = includes;
+ }
+ if (!data.Defines.empty()) {
+ result[kDEFINES_KEY] = fromStringList(data.Defines);
+ }
+ }
+
+ result[kIS_GENERATED_KEY] = data.IsGenerated;
+
+ Json::Value sourcesValue = Json::arrayValue;
+ for (auto const& i : files) {
+ const std::string relPath = cmSystemTools::RelativePath(baseDir, i);
+ sourcesValue.append(relPath.size() < i.size() ? relPath : i);
+ }
+
+ result[kSOURCES_KEY] = sourcesValue;
+ return result;
+}
+
+static Json::Value DumpSourceFilesList(
+ cmGeneratorTarget* target, const std::string& config,
+ const std::map<std::string, LanguageData>& languageDataMap)
+{
+ // Collect sourcefile groups:
+
+ std::vector<cmSourceFile*> files;
+ target->GetSourceFiles(files, config);
+
+ std::unordered_map<LanguageData, std::vector<std::string>> fileGroups;
+ for (cmSourceFile* file : files) {
+ LanguageData fileData;
+ fileData.Language = file->GetLanguage();
+ if (!fileData.Language.empty()) {
+ const LanguageData& ld = languageDataMap.at(fileData.Language);
+ cmLocalGenerator* lg = target->GetLocalGenerator();
+ cmGeneratorExpressionInterpreter genexInterpreter(lg, config, target,
+ fileData.Language);
+
+ std::string compileFlags = ld.Flags;
+ const std::string COMPILE_FLAGS("COMPILE_FLAGS");
+ if (const char* cflags = file->GetProperty(COMPILE_FLAGS)) {
+ lg->AppendFlags(compileFlags,
+ genexInterpreter.Evaluate(cflags, COMPILE_FLAGS));
+ }
+ const std::string COMPILE_OPTIONS("COMPILE_OPTIONS");
+ if (const char* coptions = file->GetProperty(COMPILE_OPTIONS)) {
+ lg->AppendCompileOptions(
+ compileFlags, genexInterpreter.Evaluate(coptions, COMPILE_OPTIONS));
+ }
+ fileData.Flags = compileFlags;
+
+ // Add include directories from source file properties.
+ std::vector<std::string> includes;
+
+ const std::string INCLUDE_DIRECTORIES("INCLUDE_DIRECTORIES");
+ if (const char* cincludes = file->GetProperty(INCLUDE_DIRECTORIES)) {
+ const std::string& evaluatedIncludes =
+ genexInterpreter.Evaluate(cincludes, INCLUDE_DIRECTORIES);
+ lg->AppendIncludeDirectories(includes, evaluatedIncludes, *file);
+
+ for (const auto& include : includes) {
+ fileData.IncludePathList.emplace_back(
+ include,
+ target->IsSystemIncludeDirectory(include, config,
+ fileData.Language));
+ }
+ }
+
+ fileData.IncludePathList.insert(fileData.IncludePathList.end(),
+ ld.IncludePathList.begin(),
+ ld.IncludePathList.end());
+
+ const std::string COMPILE_DEFINITIONS("COMPILE_DEFINITIONS");
+ std::set<std::string> defines;
+ if (const char* defs = file->GetProperty(COMPILE_DEFINITIONS)) {
+ lg->AppendDefines(
+ defines, genexInterpreter.Evaluate(defs, COMPILE_DEFINITIONS));
+ }
+
+ const std::string defPropName =
+ "COMPILE_DEFINITIONS_" + cmSystemTools::UpperCase(config);
+ if (const char* config_defs = file->GetProperty(defPropName)) {
+ lg->AppendDefines(
+ defines,
+ genexInterpreter.Evaluate(config_defs, COMPILE_DEFINITIONS));
+ }
+
+ defines.insert(ld.Defines.begin(), ld.Defines.end());
+
+ fileData.SetDefines(defines);
+ }
+
+ fileData.IsGenerated = file->GetIsGenerated();
+ std::vector<std::string>& groupFileList = fileGroups[fileData];
+ groupFileList.push_back(file->GetFullPath());
+ }
+
+ const std::string& baseDir = target->Makefile->GetCurrentSourceDirectory();
+ Json::Value result = Json::arrayValue;
+ for (auto const& it : fileGroups) {
+ Json::Value group = DumpSourceFileGroup(it.first, it.second, baseDir);
+ if (!group.isNull()) {
+ result.append(group);
+ }
+ }
+
+ return result;
+}
+
+static Json::Value DumpCTestInfo(cmLocalGenerator* lg, cmTest* testInfo,
+ const std::string& config)
+{
+ Json::Value result = Json::objectValue;
+ result[kCTEST_NAME] = testInfo->GetName();
+
+ // Concat command entries together. After the first should be the arguments
+ // for the command
+ std::string command;
+ for (auto const& cmd : testInfo->GetCommand()) {
+ command.append(cmd);
+ command.append(" ");
+ }
+
+ // Remove any config specific variables from the output.
+ cmGeneratorExpression ge;
+ auto cge = ge.Parse(command);
+ const std::string& processed = cge->Evaluate(lg, config);
+ result[kCTEST_COMMAND] = processed;
+
+ // Build up the list of properties that may have been specified
+ Json::Value properties = Json::arrayValue;
+ for (auto& prop : testInfo->GetProperties()) {
+ Json::Value entry = Json::objectValue;
+ entry[kKEY_KEY] = prop.first;
+
+ // Remove config variables from the value too.
+ auto cge_value = ge.Parse(prop.second.GetValue());
+ const std::string& processed_value = cge_value->Evaluate(lg, config);
+ entry[kVALUE_KEY] = processed_value;
+ properties.append(entry);
+ }
+ result[kPROPERTIES_KEY] = properties;
+
+ return result;
+}
+
+static void DumpMakefileTests(cmLocalGenerator* lg, const std::string& config,
+ Json::Value* result)
+{
+ auto mf = lg->GetMakefile();
+ std::vector<cmTest*> tests;
+ mf->GetTests(config, tests);
+ for (auto test : tests) {
+ Json::Value tmp = DumpCTestInfo(lg, test, config);
+ if (!tmp.isNull()) {
+ result->append(tmp);
+ }
+ }
+}
+
+static Json::Value DumpCTestProjectList(const cmake* cm,
+ std::string const& config)
+{
+ Json::Value result = Json::arrayValue;
+
+ auto globalGen = cm->GetGlobalGenerator();
+
+ for (const auto& projectIt : globalGen->GetProjectMap()) {
+ Json::Value pObj = Json::objectValue;
+ pObj[kNAME_KEY] = projectIt.first;
+
+ Json::Value tests = Json::arrayValue;
+
+ // Gather tests for every generator
+ for (const auto& lg : projectIt.second) {
+ // Make sure they're generated.
+ lg->GenerateTestFiles();
+ DumpMakefileTests(lg, config, &tests);
+ }
+
+ pObj[kCTEST_INFO] = tests;
+
+ result.append(pObj);
+ }
+
+ return result;
+}
+
+static Json::Value DumpCTestConfiguration(const cmake* cm,
+ const std::string& config)
+{
+ Json::Value result = Json::objectValue;
+ result[kNAME_KEY] = config;
+
+ result[kPROJECTS_KEY] = DumpCTestProjectList(cm, config);
+
+ return result;
+}
+
+static Json::Value DumpCTestConfigurationsList(const cmake* cm)
+{
+ Json::Value result = Json::arrayValue;
+
+ for (const std::string& c : getConfigurations(cm)) {
+ result.append(DumpCTestConfiguration(cm, c));
+ }
+
+ return result;
+}
+
+Json::Value cmDumpCTestInfo(const cmake* cm)
+{
+ Json::Value result = Json::objectValue;
+ result[kCONFIGURATIONS_KEY] = DumpCTestConfigurationsList(cm);
+ return result;
+}
+
+static Json::Value DumpTarget(cmGeneratorTarget* target,
+ const std::string& config)
+{
+ cmLocalGenerator* lg = target->GetLocalGenerator();
+
+ const cmStateEnums::TargetType type = target->GetType();
+ const std::string typeName = cmState::GetTargetTypeName(type);
+
+ Json::Value ttl = Json::arrayValue;
+ ttl.append("EXECUTABLE");
+ ttl.append("STATIC_LIBRARY");
+ ttl.append("SHARED_LIBRARY");
+ ttl.append("MODULE_LIBRARY");
+ ttl.append("OBJECT_LIBRARY");
+ ttl.append("UTILITY");
+ ttl.append("INTERFACE_LIBRARY");
+
+ if (!hasString(ttl, typeName) || target->IsImported()) {
+ return Json::Value();
+ }
+
+ Json::Value result = Json::objectValue;
+ result[kNAME_KEY] = target->GetName();
+ result[kIS_GENERATOR_PROVIDED_KEY] =
+ target->Target->GetIsGeneratorProvided();
+ result[kTYPE_KEY] = typeName;
+ result[kSOURCE_DIRECTORY_KEY] = lg->GetCurrentSourceDirectory();
+ result[kBUILD_DIRECTORY_KEY] = lg->GetCurrentBinaryDirectory();
+
+ if (type == cmStateEnums::INTERFACE_LIBRARY) {
+ return result;
+ }
+
+ result[kFULL_NAME_KEY] = target->GetFullName(config);
+
+ if (target->Target->GetHaveInstallRule()) {
+ result[kHAS_INSTALL_RULE] = true;
+
+ Json::Value installPaths = Json::arrayValue;
+ auto targetGenerators = target->Makefile->GetInstallGenerators();
+ for (auto installGenerator : targetGenerators) {
+ auto installTargetGenerator =
+ dynamic_cast<cmInstallTargetGenerator*>(installGenerator);
+ if (installTargetGenerator != nullptr &&
+ installTargetGenerator->GetTarget()->Target == target->Target) {
+ auto dest = installTargetGenerator->GetDestination(config);
+
+ std::string installPath;
+ if (!dest.empty() && cmSystemTools::FileIsFullPath(dest)) {
+ installPath = dest;
+ } else {
+ std::string installPrefix =
+ target->Makefile->GetSafeDefinition("CMAKE_INSTALL_PREFIX");
+ installPath = installPrefix + '/' + dest;
+ }
+
+ installPaths.append(installPath);
+ }
+ }
+
+ result[kINSTALL_PATHS] = installPaths;
+ }
+
+ if (target->HaveWellDefinedOutputFiles()) {
+ Json::Value artifacts = Json::arrayValue;
+ artifacts.append(
+ target->GetFullPath(config, cmStateEnums::RuntimeBinaryArtifact));
+ if (target->IsDLLPlatform()) {
+ artifacts.append(
+ target->GetFullPath(config, cmStateEnums::ImportLibraryArtifact));
+ const cmGeneratorTarget::OutputInfo* output =
+ target->GetOutputInfo(config);
+ if (output && !output->PdbDir.empty()) {
+ artifacts.append(output->PdbDir + '/' + target->GetPDBName(config));
+ }
+ }
+ result[kARTIFACTS_KEY] = artifacts;
+
+ result[kLINKER_LANGUAGE_KEY] = target->GetLinkerLanguage(config);
+
+ std::string linkLibs;
+ std::string linkFlags;
+ std::string linkLanguageFlags;
+ std::string frameworkPath;
+ std::string linkPath;
+ cmLinkLineComputer linkLineComputer(lg,
+ lg->GetStateSnapshot().GetDirectory());
+ lg->GetTargetFlags(&linkLineComputer, config, linkLibs, linkLanguageFlags,
+ linkFlags, frameworkPath, linkPath, target);
+
+ linkLibs = cmSystemTools::TrimWhitespace(linkLibs);
+ linkFlags = cmSystemTools::TrimWhitespace(linkFlags);
+ linkLanguageFlags = cmSystemTools::TrimWhitespace(linkLanguageFlags);
+ frameworkPath = cmSystemTools::TrimWhitespace(frameworkPath);
+ linkPath = cmSystemTools::TrimWhitespace(linkPath);
+
+ if (!cmSystemTools::TrimWhitespace(linkLibs).empty()) {
+ result[kLINK_LIBRARIES_KEY] = linkLibs;
+ }
+ if (!cmSystemTools::TrimWhitespace(linkFlags).empty()) {
+ result[kLINK_FLAGS_KEY] = linkFlags;
+ }
+ if (!cmSystemTools::TrimWhitespace(linkLanguageFlags).empty()) {
+ result[kLINK_LANGUAGE_FLAGS_KEY] = linkLanguageFlags;
+ }
+ if (!frameworkPath.empty()) {
+ result[kFRAMEWORK_PATH_KEY] = frameworkPath;
+ }
+ if (!linkPath.empty()) {
+ result[kLINK_PATH_KEY] = linkPath;
+ }
+ const std::string sysroot =
+ lg->GetMakefile()->GetSafeDefinition("CMAKE_SYSROOT");
+ if (!sysroot.empty()) {
+ result[kSYSROOT_KEY] = sysroot;
+ }
+ }
+
+ std::set<std::string> languages;
+ target->GetLanguages(languages, config);
+ std::map<std::string, LanguageData> languageDataMap;
+
+ for (std::string const& lang : languages) {
+ LanguageData& ld = languageDataMap[lang];
+ ld.Language = lang;
+ lg->GetTargetCompileFlags(target, config, lang, ld.Flags);
+ std::set<std::string> defines;
+ lg->GetTargetDefines(target, config, lang, defines);
+ ld.SetDefines(defines);
+ std::vector<std::string> includePathList;
+ lg->GetIncludeDirectories(includePathList, target, lang, config);
+ for (std::string const& i : includePathList) {
+ ld.IncludePathList.emplace_back(
+ i, target->IsSystemIncludeDirectory(i, config, lang));
+ }
+ }
+
+ Json::Value sourceGroupsValue =
+ DumpSourceFilesList(target, config, languageDataMap);
+ if (!sourceGroupsValue.empty()) {
+ result[kFILE_GROUPS_KEY] = sourceGroupsValue;
+ }
+
+ return result;
+}
+
+static Json::Value DumpTargetsList(
+ const std::vector<cmLocalGenerator*>& generators, const std::string& config)
+{
+ Json::Value result = Json::arrayValue;
+
+ std::vector<cmGeneratorTarget*> targetList;
+ for (auto const& lgIt : generators) {
+ cmAppend(targetList, lgIt->GetGeneratorTargets());
+ }
+ std::sort(targetList.begin(), targetList.end());
+
+ for (cmGeneratorTarget* target : targetList) {
+ Json::Value tmp = DumpTarget(target, config);
+ if (!tmp.isNull()) {
+ result.append(tmp);
+ }
+ }
+
+ return result;
+}
+
+static Json::Value DumpProjectList(const cmake* cm, std::string const& config)
+{
+ Json::Value result = Json::arrayValue;
+
+ auto globalGen = cm->GetGlobalGenerator();
+
+ for (auto const& projectIt : globalGen->GetProjectMap()) {
+ Json::Value pObj = Json::objectValue;
+ pObj[kNAME_KEY] = projectIt.first;
+
+ // All Projects must have at least one local generator
+ assert(!projectIt.second.empty());
+ const cmLocalGenerator* lg = projectIt.second.at(0);
+
+ // Project structure information:
+ const cmMakefile* mf = lg->GetMakefile();
+ auto minVersion = mf->GetDefinition("CMAKE_MINIMUM_REQUIRED_VERSION");
+ pObj[kMINIMUM_CMAKE_VERSION] = minVersion ? minVersion : "";
+ pObj[kSOURCE_DIRECTORY_KEY] = mf->GetCurrentSourceDirectory();
+ pObj[kBUILD_DIRECTORY_KEY] = mf->GetCurrentBinaryDirectory();
+ pObj[kTARGETS_KEY] = DumpTargetsList(projectIt.second, config);
+
+ // For a project-level install rule it might be defined in any of its
+ // associated generators.
+ bool hasInstallRule = false;
+ for (const auto generator : projectIt.second) {
+ for (const auto installGen :
+ generator->GetMakefile()->GetInstallGenerators()) {
+ if (!dynamic_cast<cmInstallSubdirectoryGenerator*>(installGen)) {
+ hasInstallRule = true;
+ break;
+ }
+ }
+
+ if (hasInstallRule) {
+ break;
+ }
+ }
+
+ pObj[kHAS_INSTALL_RULE] = hasInstallRule;
+
+ result.append(pObj);
+ }
+
+ return result;
+}
+
+static Json::Value DumpConfiguration(const cmake* cm,
+ const std::string& config)
+{
+ Json::Value result = Json::objectValue;
+ result[kNAME_KEY] = config;
+
+ result[kPROJECTS_KEY] = DumpProjectList(cm, config);
+
+ return result;
+}
+
+static Json::Value DumpConfigurationsList(const cmake* cm)
+{
+ Json::Value result = Json::arrayValue;
+
+ for (std::string const& c : getConfigurations(cm)) {
+ result.append(DumpConfiguration(cm, c));
+ }
+
+ return result;
+}
+
+Json::Value cmDumpCodeModel(const cmake* cm)
+{
+ Json::Value result = Json::objectValue;
+ result[kCONFIGURATIONS_KEY] = DumpConfigurationsList(cm);
+ return result;
+}
diff --git a/Source/cmJsonObjects.h b/Source/cmJsonObjects.h
new file mode 100644
index 000000000..cd4da94f4
--- /dev/null
+++ b/Source/cmJsonObjects.h
@@ -0,0 +1,27 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#ifndef cmJsonObjects_h
+#define cmJsonObjects_h
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include "cm_jsoncpp_value.h"
+
+#include <string>
+#include <vector>
+
+class cmake;
+class cmGlobalGenerator;
+
+extern void cmGetCMakeInputs(const cmGlobalGenerator* gg,
+ const std::string& sourceDir,
+ const std::string& buildDir,
+ std::vector<std::string>* internalFiles,
+ std::vector<std::string>* explicitFiles,
+ std::vector<std::string>* tmpFiles);
+
+extern Json::Value cmDumpCodeModel(const cmake* cm);
+extern Json::Value cmDumpCTestInfo(const cmake* cm);
+extern Json::Value cmDumpCMakeInputs(const cmake* cm);
+
+#endif
diff --git a/Source/cmLinkItem.cxx b/Source/cmLinkItem.cxx
new file mode 100644
index 000000000..9b03ad011
--- /dev/null
+++ b/Source/cmLinkItem.cxx
@@ -0,0 +1,64 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmLinkItem.h"
+
+#include "cmGeneratorTarget.h"
+
+#include <utility> // IWYU pragma: keep
+
+cmLinkItem::cmLinkItem() = default;
+
+cmLinkItem::cmLinkItem(std::string n, cmListFileBacktrace bt)
+ : String(std::move(n))
+ , Backtrace(std::move(bt))
+{
+}
+
+cmLinkItem::cmLinkItem(cmGeneratorTarget const* t, cmListFileBacktrace bt)
+ : Target(t)
+ , Backtrace(std::move(bt))
+{
+}
+
+std::string const& cmLinkItem::AsStr() const
+{
+ return this->Target ? this->Target->GetName() : this->String;
+}
+
+bool operator<(cmLinkItem const& l, cmLinkItem const& r)
+{
+ // Order among targets.
+ if (l.Target && r.Target) {
+ return l.Target < r.Target;
+ }
+ // Order targets before strings.
+ if (l.Target) {
+ return true;
+ }
+ if (r.Target) {
+ return false;
+ }
+ // Order among strings.
+ return l.String < r.String;
+}
+
+bool operator==(cmLinkItem const& l, cmLinkItem const& r)
+{
+ return l.Target == r.Target && l.String == r.String;
+}
+
+std::ostream& operator<<(std::ostream& os, cmLinkItem const& item)
+{
+ return os << item.AsStr();
+}
+
+cmLinkImplItem::cmLinkImplItem()
+ : cmLinkItem()
+{
+}
+
+cmLinkImplItem::cmLinkImplItem(cmLinkItem item, bool fromGenex)
+ : cmLinkItem(std::move(item))
+ , FromGenex(fromGenex)
+{
+}
diff --git a/Source/cmMessageType.h b/Source/cmMessageType.h
new file mode 100644
index 000000000..b57b86bee
--- /dev/null
+++ b/Source/cmMessageType.h
@@ -0,0 +1,21 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#ifndef cmMessageType_h
+#define cmMessageType_h
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+enum class MessageType
+{
+ AUTHOR_WARNING,
+ AUTHOR_ERROR,
+ FATAL_ERROR,
+ INTERNAL_ERROR,
+ MESSAGE,
+ WARNING,
+ LOG,
+ DEPRECATION_ERROR,
+ DEPRECATION_WARNING
+};
+
+#endif
diff --git a/Source/cmNinjaLinkLineDeviceComputer.cxx b/Source/cmNinjaLinkLineDeviceComputer.cxx
new file mode 100644
index 000000000..84c1b37d3
--- /dev/null
+++ b/Source/cmNinjaLinkLineDeviceComputer.cxx
@@ -0,0 +1,20 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+
+#include "cmNinjaLinkLineDeviceComputer.h"
+
+#include "cmGlobalNinjaGenerator.h"
+
+cmNinjaLinkLineDeviceComputer::cmNinjaLinkLineDeviceComputer(
+ cmOutputConverter* outputConverter, cmStateDirectory const& stateDir,
+ cmGlobalNinjaGenerator const* gg)
+ : cmLinkLineDeviceComputer(outputConverter, stateDir)
+ , GG(gg)
+{
+}
+
+std::string cmNinjaLinkLineDeviceComputer::ConvertToLinkReference(
+ std::string const& lib) const
+{
+ return GG->ConvertToNinjaPath(lib);
+}
diff --git a/Source/cmNinjaLinkLineDeviceComputer.h b/Source/cmNinjaLinkLineDeviceComputer.h
new file mode 100644
index 000000000..84ced5b35
--- /dev/null
+++ b/Source/cmNinjaLinkLineDeviceComputer.h
@@ -0,0 +1,34 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+
+#ifndef cmNinjaLinkLineDeviceComputer_h
+#define cmNinjaLinkLineDeviceComputer_h
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <string>
+
+#include "cmLinkLineDeviceComputer.h"
+
+class cmGlobalNinjaGenerator;
+class cmOutputConverter;
+class cmStateDirectory;
+
+class cmNinjaLinkLineDeviceComputer : public cmLinkLineDeviceComputer
+{
+public:
+ cmNinjaLinkLineDeviceComputer(cmOutputConverter* outputConverter,
+ cmStateDirectory const& stateDir,
+ cmGlobalNinjaGenerator const* gg);
+
+ cmNinjaLinkLineDeviceComputer(cmNinjaLinkLineDeviceComputer const&) = delete;
+ cmNinjaLinkLineDeviceComputer& operator=(
+ cmNinjaLinkLineDeviceComputer const&) = delete;
+
+ std::string ConvertToLinkReference(std::string const& input) const override;
+
+private:
+ cmGlobalNinjaGenerator const* GG;
+};
+
+#endif
diff --git a/Source/cmPipeConnection.cxx b/Source/cmPipeConnection.cxx
new file mode 100644
index 000000000..1eede13e0
--- /dev/null
+++ b/Source/cmPipeConnection.cxx
@@ -0,0 +1,71 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmPipeConnection.h"
+
+#include <utility>
+
+#include "cmServer.h"
+
+cmPipeConnection::cmPipeConnection(std::string name,
+ cmConnectionBufferStrategy* bufferStrategy)
+ : cmEventBasedConnection(bufferStrategy)
+ , PipeName(std::move(name))
+{
+}
+
+void cmPipeConnection::Connect(uv_stream_t* server)
+{
+ if (this->WriteStream.get()) {
+ // Accept and close all pipes but the first:
+ cm::uv_pipe_ptr rejectPipe;
+
+ rejectPipe.init(*this->Server->GetLoop(), 0);
+ uv_accept(server, rejectPipe);
+
+ return;
+ }
+
+ cm::uv_pipe_ptr ClientPipe;
+ ClientPipe.init(*this->Server->GetLoop(), 0,
+ static_cast<cmEventBasedConnection*>(this));
+
+ if (uv_accept(server, ClientPipe) != 0) {
+ return;
+ }
+
+ uv_read_start(ClientPipe, on_alloc_buffer, on_read);
+ WriteStream = std::move(ClientPipe);
+ Server->OnConnected(this);
+}
+
+bool cmPipeConnection::OnServeStart(std::string* errorMessage)
+{
+ this->ServerPipe.init(*this->Server->GetLoop(), 0,
+ static_cast<cmEventBasedConnection*>(this));
+
+ int r;
+ if ((r = uv_pipe_bind(this->ServerPipe, this->PipeName.c_str())) != 0) {
+ *errorMessage = std::string("Internal Error with ") + this->PipeName +
+ ": " + uv_err_name(r);
+ return false;
+ }
+
+ if ((r = uv_listen(this->ServerPipe, 1, on_new_connection)) != 0) {
+ *errorMessage = std::string("Internal Error listening on ") +
+ this->PipeName + ": " + uv_err_name(r);
+ return false;
+ }
+
+ return cmConnection::OnServeStart(errorMessage);
+}
+
+bool cmPipeConnection::OnConnectionShuttingDown()
+{
+ if (this->WriteStream.get()) {
+ this->WriteStream->data = nullptr;
+ }
+
+ this->ServerPipe.reset();
+
+ return cmEventBasedConnection::OnConnectionShuttingDown();
+}
diff --git a/Source/cmPipeConnection.h b/Source/cmPipeConnection.h
new file mode 100644
index 000000000..e67f15c57
--- /dev/null
+++ b/Source/cmPipeConnection.h
@@ -0,0 +1,28 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#pragma once
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include "cmUVHandlePtr.h"
+#include <string>
+
+#include "cmConnection.h"
+#include "cm_uv.h"
+
+class cmPipeConnection : public cmEventBasedConnection
+{
+public:
+ cmPipeConnection(std::string name,
+ cmConnectionBufferStrategy* bufferStrategy = nullptr);
+
+ bool OnServeStart(std::string* pString) override;
+
+ bool OnConnectionShuttingDown() override;
+
+ void Connect(uv_stream_t* server) override;
+
+private:
+ const std::string PipeName;
+ cm::uv_pipe_ptr ServerPipe;
+};
diff --git a/Source/cmQtAutoGen.cxx b/Source/cmQtAutoGen.cxx
new file mode 100644
index 000000000..3683eddda
--- /dev/null
+++ b/Source/cmQtAutoGen.cxx
@@ -0,0 +1,410 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmQtAutoGen.h"
+
+#include "cmAlgorithms.h"
+#include "cmDuration.h"
+#include "cmProcessOutput.h"
+#include "cmSystemTools.h"
+#include "cmsys/FStream.hxx"
+#include "cmsys/RegularExpression.hxx"
+
+#include <algorithm>
+#include <array>
+#include <sstream>
+#include <utility>
+
+// - Static functions
+
+/// @brief Merges newOpts into baseOpts
+/// @arg valueOpts list of options that accept a value
+void MergeOptions(std::vector<std::string>& baseOpts,
+ std::vector<std::string> const& newOpts,
+ std::vector<std::string> const& valueOpts, bool isQt5)
+{
+ typedef std::vector<std::string>::iterator Iter;
+ typedef std::vector<std::string>::const_iterator CIter;
+ if (newOpts.empty()) {
+ return;
+ }
+ if (baseOpts.empty()) {
+ baseOpts = newOpts;
+ return;
+ }
+
+ std::vector<std::string> extraOpts;
+ for (CIter fit = newOpts.begin(), fitEnd = newOpts.end(); fit != fitEnd;
+ ++fit) {
+ std::string const& newOpt = *fit;
+ Iter existIt = std::find(baseOpts.begin(), baseOpts.end(), newOpt);
+ if (existIt != baseOpts.end()) {
+ if (newOpt.size() >= 2) {
+ // Acquire the option name
+ std::string optName;
+ {
+ auto oit = newOpt.begin();
+ if (*oit == '-') {
+ ++oit;
+ if (isQt5 && (*oit == '-')) {
+ ++oit;
+ }
+ optName.assign(oit, newOpt.end());
+ }
+ }
+ // Test if this is a value option and change the existing value
+ if (!optName.empty() &&
+ (std::find(valueOpts.begin(), valueOpts.end(), optName) !=
+ valueOpts.end())) {
+ const Iter existItNext(existIt + 1);
+ const CIter fitNext(fit + 1);
+ if ((existItNext != baseOpts.end()) && (fitNext != fitEnd)) {
+ *existItNext = *fitNext;
+ ++fit;
+ }
+ }
+ }
+ } else {
+ extraOpts.push_back(newOpt);
+ }
+ }
+ // Append options
+ cmAppend(baseOpts, extraOpts);
+}
+
+// - Class definitions
+
+unsigned int const cmQtAutoGen::ParallelMax = 64;
+std::string const cmQtAutoGen::ListSep = "<<<S>>>";
+
+std::string const& cmQtAutoGen::GeneratorName(GenT genType)
+{
+ static const std::string AutoGen("AutoGen");
+ static const std::string AutoMoc("AutoMoc");
+ static const std::string AutoUic("AutoUic");
+ static const std::string AutoRcc("AutoRcc");
+
+ switch (genType) {
+ case GenT::GEN:
+ return AutoGen;
+ case GenT::MOC:
+ return AutoMoc;
+ case GenT::UIC:
+ return AutoUic;
+ case GenT::RCC:
+ return AutoRcc;
+ }
+ return AutoGen;
+}
+
+std::string const& cmQtAutoGen::GeneratorNameUpper(GenT genType)
+{
+ static const std::string AUTOGEN("AUTOGEN");
+ static const std::string AUTOMOC("AUTOMOC");
+ static const std::string AUTOUIC("AUTOUIC");
+ static const std::string AUTORCC("AUTORCC");
+
+ switch (genType) {
+ case GenT::GEN:
+ return AUTOGEN;
+ case GenT::MOC:
+ return AUTOMOC;
+ case GenT::UIC:
+ return AUTOUIC;
+ case GenT::RCC:
+ return AUTORCC;
+ }
+ return AUTOGEN;
+}
+
+std::string cmQtAutoGen::Tools(bool moc, bool uic, bool rcc)
+{
+ std::string res;
+ std::vector<std::string> lst;
+ if (moc) {
+ lst.emplace_back("AUTOMOC");
+ }
+ if (uic) {
+ lst.emplace_back("AUTOUIC");
+ }
+ if (rcc) {
+ lst.emplace_back("AUTORCC");
+ }
+ switch (lst.size()) {
+ case 1:
+ res += lst.at(0);
+ break;
+ case 2:
+ res += lst.at(0);
+ res += " and ";
+ res += lst.at(1);
+ break;
+ case 3:
+ res += lst.at(0);
+ res += ", ";
+ res += lst.at(1);
+ res += " and ";
+ res += lst.at(2);
+ break;
+ default:
+ break;
+ }
+ return res;
+}
+
+std::string cmQtAutoGen::Quoted(std::string const& text)
+{
+ const std::array<std::pair<const char*, const char*>, 9> replaces = {
+ { { "\\", "\\\\" },
+ { "\"", "\\\"" },
+ { "\a", "\\a" },
+ { "\b", "\\b" },
+ { "\f", "\\f" },
+ { "\n", "\\n" },
+ { "\r", "\\r" },
+ { "\t", "\\t" },
+ { "\v", "\\v" } }
+ };
+
+ std::string res = text;
+ for (auto const& pair : replaces) {
+ cmSystemTools::ReplaceString(res, pair.first, pair.second);
+ }
+ res = '"' + res;
+ res += '"';
+ return res;
+}
+
+std::string cmQtAutoGen::QuotedCommand(std::vector<std::string> const& command)
+{
+ std::string res;
+ for (std::string const& item : command) {
+ if (!res.empty()) {
+ res.push_back(' ');
+ }
+ std::string const cesc = cmQtAutoGen::Quoted(item);
+ if (item.empty() || (cesc.size() > (item.size() + 2)) ||
+ (cesc.find(' ') != std::string::npos)) {
+ res += cesc;
+ } else {
+ res += item;
+ }
+ }
+ return res;
+}
+
+std::string cmQtAutoGen::SubDirPrefix(std::string const& filename)
+{
+ std::string::size_type slash_pos = filename.rfind('/');
+ if (slash_pos == std::string::npos) {
+ return std::string();
+ }
+ return filename.substr(0, slash_pos + 1);
+}
+
+std::string cmQtAutoGen::AppendFilenameSuffix(std::string const& filename,
+ std::string const& suffix)
+{
+ std::string res;
+ auto pos = filename.rfind('.');
+ if (pos != std::string::npos) {
+ const auto it_dot = filename.begin() + pos;
+ res.assign(filename.begin(), it_dot);
+ res.append(suffix);
+ res.append(it_dot, filename.end());
+ } else {
+ res = filename;
+ res.append(suffix);
+ }
+ return res;
+}
+
+void cmQtAutoGen::UicMergeOptions(std::vector<std::string>& baseOpts,
+ std::vector<std::string> const& newOpts,
+ bool isQt5)
+{
+ static std::vector<std::string> const valueOpts = {
+ "tr", "translate", "postfix", "generator",
+ "include", // Since Qt 5.3
+ "g"
+ };
+ MergeOptions(baseOpts, newOpts, valueOpts, isQt5);
+}
+
+void cmQtAutoGen::RccMergeOptions(std::vector<std::string>& baseOpts,
+ std::vector<std::string> const& newOpts,
+ bool isQt5)
+{
+ static std::vector<std::string> const valueOpts = { "name", "root",
+ "compress",
+ "threshold" };
+ MergeOptions(baseOpts, newOpts, valueOpts, isQt5);
+}
+
+static void RccListParseContent(std::string const& content,
+ std::vector<std::string>& files)
+{
+ cmsys::RegularExpression fileMatchRegex("(<file[^<]+)");
+ cmsys::RegularExpression fileReplaceRegex("(^<file[^>]*>)");
+
+ const char* contentChars = content.c_str();
+ while (fileMatchRegex.find(contentChars)) {
+ std::string const qrcEntry = fileMatchRegex.match(1);
+ contentChars += qrcEntry.size();
+ {
+ fileReplaceRegex.find(qrcEntry);
+ std::string const tag = fileReplaceRegex.match(1);
+ files.push_back(qrcEntry.substr(tag.size()));
+ }
+ }
+}
+
+static bool RccListParseOutput(std::string const& rccStdOut,
+ std::string const& rccStdErr,
+ std::vector<std::string>& files,
+ std::string& error)
+{
+ // Lambda to strip CR characters
+ auto StripCR = [](std::string& line) {
+ std::string::size_type cr = line.find('\r');
+ if (cr != std::string::npos) {
+ line = line.substr(0, cr);
+ }
+ };
+
+ // Parse rcc std output
+ {
+ std::istringstream ostr(rccStdOut);
+ std::string oline;
+ while (std::getline(ostr, oline)) {
+ StripCR(oline);
+ if (!oline.empty()) {
+ files.push_back(oline);
+ }
+ }
+ }
+ // Parse rcc error output
+ {
+ std::istringstream estr(rccStdErr);
+ std::string eline;
+ while (std::getline(estr, eline)) {
+ StripCR(eline);
+ if (cmHasLiteralPrefix(eline, "RCC: Error in")) {
+ static std::string const searchString = "Cannot find file '";
+
+ std::string::size_type pos = eline.find(searchString);
+ if (pos == std::string::npos) {
+ error = "rcc lists unparsable output:\n";
+ error += cmQtAutoGen::Quoted(eline);
+ error += "\n";
+ return false;
+ }
+ pos += searchString.length();
+ std::string::size_type sz = eline.size() - pos - 1;
+ files.push_back(eline.substr(pos, sz));
+ }
+ }
+ }
+
+ return true;
+}
+
+cmQtAutoGen::RccLister::RccLister() = default;
+
+cmQtAutoGen::RccLister::RccLister(std::string rccExecutable,
+ std::vector<std::string> listOptions)
+ : RccExcutable_(std::move(rccExecutable))
+ , ListOptions_(std::move(listOptions))
+{
+}
+
+bool cmQtAutoGen::RccLister::list(std::string const& qrcFile,
+ std::vector<std::string>& files,
+ std::string& error, bool verbose) const
+{
+ error.clear();
+
+ if (!cmSystemTools::FileExists(qrcFile, true)) {
+ error = "The resource file ";
+ error += Quoted(qrcFile);
+ error += " does not exist.";
+ return false;
+ }
+
+ // Run rcc list command in the directory of the qrc file with the pathless
+ // qrc file name argument. This way rcc prints relative paths.
+ // This avoids issues on Windows when the qrc file is in a path that
+ // contains non-ASCII characters.
+ std::string const fileDir = cmSystemTools::GetFilenamePath(qrcFile);
+
+ if (!this->RccExcutable_.empty() &&
+ cmSystemTools::FileExists(this->RccExcutable_, true) &&
+ !this->ListOptions_.empty()) {
+
+ bool result = false;
+ int retVal = 0;
+ std::string rccStdOut;
+ std::string rccStdErr;
+ {
+ std::vector<std::string> cmd;
+ cmd.emplace_back(this->RccExcutable_);
+ cmAppend(cmd, this->ListOptions_);
+ cmd.emplace_back(cmSystemTools::GetFilenameName(qrcFile));
+
+ // Log command
+ if (verbose) {
+ std::string msg = "Running command:\n";
+ msg += QuotedCommand(cmd);
+ msg += '\n';
+ cmSystemTools::Stdout(msg);
+ }
+
+ result = cmSystemTools::RunSingleCommand(
+ cmd, &rccStdOut, &rccStdErr, &retVal, fileDir.c_str(),
+ cmSystemTools::OUTPUT_NONE, cmDuration::zero(), cmProcessOutput::Auto);
+ }
+ if (!result || retVal) {
+ error = "The rcc list process failed for ";
+ error += Quoted(qrcFile);
+ error += "\n";
+ if (!rccStdOut.empty()) {
+ error += rccStdOut;
+ error += "\n";
+ }
+ if (!rccStdErr.empty()) {
+ error += rccStdErr;
+ error += "\n";
+ }
+ return false;
+ }
+ if (!RccListParseOutput(rccStdOut, rccStdErr, files, error)) {
+ return false;
+ }
+ } else {
+ // We can't use rcc for the file listing.
+ // Read the qrc file content into string and parse it.
+ {
+ std::string qrcContents;
+ {
+ cmsys::ifstream ifs(qrcFile.c_str());
+ if (ifs) {
+ std::ostringstream osst;
+ osst << ifs.rdbuf();
+ qrcContents = osst.str();
+ } else {
+ error = "The resource file ";
+ error += Quoted(qrcFile);
+ error += " is not readable\n";
+ return false;
+ }
+ }
+ // Parse string content
+ RccListParseContent(qrcContents, files);
+ }
+ }
+
+ // Convert relative paths to absolute paths
+ for (std::string& entry : files) {
+ entry = cmSystemTools::CollapseFullPath(entry, fileDir);
+ }
+ return true;
+}
diff --git a/Source/cmQtAutoGen.h b/Source/cmQtAutoGen.h
new file mode 100644
index 000000000..9c5212952
--- /dev/null
+++ b/Source/cmQtAutoGen.h
@@ -0,0 +1,138 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#ifndef cmQtAutoGen_h
+#define cmQtAutoGen_h
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <memory> // IWYU pragma: keep
+#include <string>
+#include <vector>
+
+/** \class cmQtAutoGen
+ * \brief Common base class for QtAutoGen classes
+ */
+class cmQtAutoGen
+{
+public:
+ /// @brief Integer version
+ struct IntegerVersion
+ {
+ unsigned int Major = 0;
+ unsigned int Minor = 0;
+
+ IntegerVersion() = default;
+ IntegerVersion(unsigned int major, unsigned int minor)
+ : Major(major)
+ , Minor(minor)
+ {
+ }
+
+ bool operator>(IntegerVersion const version)
+ {
+ return (this->Major > version.Major) ||
+ ((this->Major == version.Major) && (this->Minor > version.Minor));
+ }
+
+ bool operator>=(IntegerVersion const version)
+ {
+ return (this->Major > version.Major) ||
+ ((this->Major == version.Major) && (this->Minor >= version.Minor));
+ }
+ };
+
+ class CompilerFeatures
+ {
+ public:
+ bool Evaluated = false;
+ std::string HelpOutput;
+ std::vector<std::string> ListOptions;
+ };
+ typedef std::shared_ptr<CompilerFeatures> CompilerFeaturesHandle;
+
+ /// @brief AutoGen generator type
+ enum class GenT
+ {
+ GEN, // AUTOGEN
+ MOC, // AUTOMOC
+ UIC, // AUTOUIC
+ RCC // AUTORCC
+ };
+
+ /// @brief Nested lists separator
+ static std::string const ListSep;
+ /// @brief Maximum number of parallel threads/processes in a generator
+ static unsigned int const ParallelMax;
+
+public:
+ /// @brief Returns the generator name
+ static std::string const& GeneratorName(GenT genType);
+ /// @brief Returns the generator name in upper case
+ static std::string const& GeneratorNameUpper(GenT genType);
+
+ /// @brief Returns a string with the requested tool names
+ static std::string Tools(bool moc, bool uic, bool rcc);
+
+ /// @brief Returns the string escaped and enclosed in quotes
+ static std::string Quoted(std::string const& text);
+
+ static std::string QuotedCommand(std::vector<std::string> const& command);
+
+ /// @brief Returns the parent directory of the file with a "/" suffix
+ static std::string SubDirPrefix(std::string const& filename);
+
+ /// @brief Appends the suffix to the filename before the last dot
+ static std::string AppendFilenameSuffix(std::string const& filename,
+ std::string const& suffix);
+
+ /// @brief Merges newOpts into baseOpts
+ static void UicMergeOptions(std::vector<std::string>& baseOpts,
+ std::vector<std::string> const& newOpts,
+ bool isQt5);
+
+ /// @brief Merges newOpts into baseOpts
+ static void RccMergeOptions(std::vector<std::string>& baseOpts,
+ std::vector<std::string> const& newOpts,
+ bool isQt5);
+
+ /** @class RccLister
+ * @brief Lists files in qrc resource files
+ */
+ class RccLister
+ {
+ public:
+ RccLister();
+ RccLister(std::string rccExecutable, std::vector<std::string> listOptions);
+
+ //! The rcc executable
+ std::string const& RccExcutable() const { return RccExcutable_; }
+ void SetRccExecutable(std::string const& rccExecutable)
+ {
+ RccExcutable_ = rccExecutable;
+ }
+
+ //! The rcc executable list options
+ std::vector<std::string> const& ListOptions() const
+ {
+ return ListOptions_;
+ }
+ void SetListOptions(std::vector<std::string> const& listOptions)
+ {
+ ListOptions_ = listOptions;
+ }
+
+ /**
+ * @brief Lists a files in the qrcFile
+ * @arg files The file names are appended to this list
+ * @arg error contains the error message when the function fails
+ */
+ bool list(std::string const& qrcFile, std::vector<std::string>& files,
+ std::string& error, bool verbose = false) const;
+
+ private:
+ std::string RccExcutable_;
+ std::vector<std::string> ListOptions_;
+ };
+};
+
+#endif
diff --git a/Source/cmQtAutoGenGlobalInitializer.cxx b/Source/cmQtAutoGenGlobalInitializer.cxx
new file mode 100644
index 000000000..ef8a56b3b
--- /dev/null
+++ b/Source/cmQtAutoGenGlobalInitializer.cxx
@@ -0,0 +1,303 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmQtAutoGenGlobalInitializer.h"
+#include "cmQtAutoGen.h"
+#include "cmQtAutoGenInitializer.h"
+
+#include "cmAlgorithms.h"
+#include "cmCustomCommandLines.h"
+#include "cmDuration.h"
+#include "cmGeneratorTarget.h"
+#include "cmLocalGenerator.h"
+#include "cmMakefile.h"
+#include "cmMessageType.h"
+#include "cmProcessOutput.h"
+#include "cmState.h"
+#include "cmStateTypes.h"
+#include "cmSystemTools.h"
+#include "cmTarget.h"
+
+#include <memory>
+#include <utility>
+
+cmQtAutoGenGlobalInitializer::Keywords::Keywords()
+ : AUTOMOC("AUTOMOC")
+ , AUTOUIC("AUTOUIC")
+ , AUTORCC("AUTORCC")
+ , AUTOMOC_EXECUTABLE("AUTOMOC_EXECUTABLE")
+ , AUTOUIC_EXECUTABLE("AUTOUIC_EXECUTABLE")
+ , AUTORCC_EXECUTABLE("AUTORCC_EXECUTABLE")
+ , SKIP_AUTOGEN("SKIP_AUTOGEN")
+ , SKIP_AUTOMOC("SKIP_AUTOMOC")
+ , SKIP_AUTOUIC("SKIP_AUTOUIC")
+ , SKIP_AUTORCC("SKIP_AUTORCC")
+ , AUTOUIC_OPTIONS("AUTOUIC_OPTIONS")
+ , AUTORCC_OPTIONS("AUTORCC_OPTIONS")
+ , qrc("qrc")
+ , ui("ui")
+{
+}
+
+cmQtAutoGenGlobalInitializer::cmQtAutoGenGlobalInitializer(
+ std::vector<cmLocalGenerator*> const& localGenerators)
+{
+ for (cmLocalGenerator* localGen : localGenerators) {
+ // Detect global autogen and autorcc target names
+ bool globalAutoGenTarget = false;
+ bool globalAutoRccTarget = false;
+ {
+ cmMakefile* makefile = localGen->GetMakefile();
+ // Detect global autogen target name
+ if (cmSystemTools::IsOn(
+ makefile->GetSafeDefinition("CMAKE_GLOBAL_AUTOGEN_TARGET"))) {
+ std::string targetName =
+ makefile->GetSafeDefinition("CMAKE_GLOBAL_AUTOGEN_TARGET_NAME");
+ if (targetName.empty()) {
+ targetName = "autogen";
+ }
+ GlobalAutoGenTargets_.emplace(localGen, std::move(targetName));
+ globalAutoGenTarget = true;
+ }
+
+ // Detect global autorcc target name
+ if (cmSystemTools::IsOn(
+ makefile->GetSafeDefinition("CMAKE_GLOBAL_AUTORCC_TARGET"))) {
+ std::string targetName =
+ makefile->GetSafeDefinition("CMAKE_GLOBAL_AUTORCC_TARGET_NAME");
+ if (targetName.empty()) {
+ targetName = "autorcc";
+ }
+ GlobalAutoRccTargets_.emplace(localGen, std::move(targetName));
+ globalAutoRccTarget = true;
+ }
+ }
+
+ // Find targets that require AUTOMOC/UIC/RCC processing
+ for (cmGeneratorTarget* target : localGen->GetGeneratorTargets()) {
+ // Process only certain target types
+ switch (target->GetType()) {
+ case cmStateEnums::EXECUTABLE:
+ case cmStateEnums::STATIC_LIBRARY:
+ case cmStateEnums::SHARED_LIBRARY:
+ case cmStateEnums::MODULE_LIBRARY:
+ case cmStateEnums::OBJECT_LIBRARY:
+ // Process target
+ break;
+ default:
+ // Don't process target
+ continue;
+ }
+ if (target->IsImported()) {
+ // Don't process target
+ continue;
+ }
+
+ bool const moc = target->GetPropertyAsBool(kw().AUTOMOC);
+ bool const uic = target->GetPropertyAsBool(kw().AUTOUIC);
+ bool const rcc = target->GetPropertyAsBool(kw().AUTORCC);
+ if (moc || uic || rcc) {
+ std::string const mocExec =
+ target->GetSafeProperty(kw().AUTOMOC_EXECUTABLE);
+ std::string const uicExec =
+ target->GetSafeProperty(kw().AUTOUIC_EXECUTABLE);
+ std::string const rccExec =
+ target->GetSafeProperty(kw().AUTORCC_EXECUTABLE);
+
+ // We support Qt4, Qt5 and Qt6
+ auto qtVersion = cmQtAutoGenInitializer::GetQtVersion(target);
+ bool const validQt = (qtVersion.first.Major == 4) ||
+ (qtVersion.first.Major == 5) || (qtVersion.first.Major == 6);
+
+ bool const mocAvailable = (validQt || !mocExec.empty());
+ bool const uicAvailable = (validQt || !uicExec.empty());
+ bool const rccAvailable = (validQt || !rccExec.empty());
+ bool const mocIsValid = (moc && mocAvailable);
+ bool const uicIsValid = (uic && uicAvailable);
+ bool const rccIsValid = (rcc && rccAvailable);
+ // Disabled AUTOMOC/UIC/RCC warning
+ bool const mocDisabled = (moc && !mocAvailable);
+ bool const uicDisabled = (uic && !uicAvailable);
+ bool const rccDisabled = (rcc && !rccAvailable);
+ if (mocDisabled || uicDisabled || rccDisabled) {
+ std::string msg = "AUTOGEN: No valid Qt version found for target ";
+ msg += target->GetName();
+ msg += ". ";
+ msg += cmQtAutoGen::Tools(mocDisabled, uicDisabled, rccDisabled);
+ msg += " disabled. Consider adding:\n";
+ {
+ std::string version = (qtVersion.second == 0)
+ ? std::string("<QTVERSION>")
+ : std::to_string(qtVersion.second);
+ std::string comp = uicDisabled ? "Widgets" : "Core";
+ msg += " find_package(Qt";
+ msg += version;
+ msg += " COMPONENTS ";
+ msg += comp;
+ msg += ")\n";
+ }
+ msg += "to your CMakeLists.txt file.";
+ target->Makefile->IssueMessage(MessageType::AUTHOR_WARNING, msg);
+ }
+ if (mocIsValid || uicIsValid || rccIsValid) {
+ // Create autogen target initializer
+ Initializers_.emplace_back(cm::make_unique<cmQtAutoGenInitializer>(
+ this, target, qtVersion.first, mocIsValid, uicIsValid, rccIsValid,
+ globalAutoGenTarget, globalAutoRccTarget));
+ }
+ }
+ }
+ }
+}
+
+cmQtAutoGenGlobalInitializer::~cmQtAutoGenGlobalInitializer() = default;
+
+void cmQtAutoGenGlobalInitializer::GetOrCreateGlobalTarget(
+ cmLocalGenerator* localGen, std::string const& name,
+ std::string const& comment)
+{
+ // Test if the target already exists
+ if (localGen->FindGeneratorTargetToUse(name) == nullptr) {
+ cmMakefile* makefile = localGen->GetMakefile();
+
+ // Create utility target
+ cmTarget* target = makefile->AddUtilityCommand(
+ name, cmMakefile::TargetOrigin::Generator, true,
+ makefile->GetHomeOutputDirectory().c_str() /*work dir*/,
+ std::vector<std::string>() /*output*/,
+ std::vector<std::string>() /*depends*/, cmCustomCommandLines(), false,
+ comment.c_str());
+ localGen->AddGeneratorTarget(new cmGeneratorTarget(target, localGen));
+
+ // Set FOLDER property in the target
+ {
+ char const* folder =
+ makefile->GetState()->GetGlobalProperty("AUTOGEN_TARGETS_FOLDER");
+ if (folder != nullptr) {
+ target->SetProperty("FOLDER", folder);
+ }
+ }
+ }
+}
+
+void cmQtAutoGenGlobalInitializer::AddToGlobalAutoGen(
+ cmLocalGenerator* localGen, std::string const& targetName)
+{
+ auto it = GlobalAutoGenTargets_.find(localGen);
+ if (it != GlobalAutoGenTargets_.end()) {
+ cmGeneratorTarget* target = localGen->FindGeneratorTargetToUse(it->second);
+ if (target != nullptr) {
+ target->Target->AddUtility(targetName, localGen->GetMakefile());
+ }
+ }
+}
+
+void cmQtAutoGenGlobalInitializer::AddToGlobalAutoRcc(
+ cmLocalGenerator* localGen, std::string const& targetName)
+{
+ auto it = GlobalAutoRccTargets_.find(localGen);
+ if (it != GlobalAutoRccTargets_.end()) {
+ cmGeneratorTarget* target = localGen->FindGeneratorTargetToUse(it->second);
+ if (target != nullptr) {
+ target->Target->AddUtility(targetName, localGen->GetMakefile());
+ }
+ }
+}
+
+cmQtAutoGen::CompilerFeaturesHandle
+cmQtAutoGenGlobalInitializer::GetCompilerFeatures(
+ std::string const& generator, std::string const& executable,
+ std::string& error)
+{
+ // Check if we have cached features
+ {
+ auto it = this->CompilerFeatures_.find(executable);
+ if (it != this->CompilerFeatures_.end()) {
+ return it->second;
+ }
+ }
+
+ // Check if the executable exists
+ if (!cmSystemTools::FileExists(executable, true)) {
+ error = "The \"";
+ error += generator;
+ error += "\" executable ";
+ error += cmQtAutoGen::Quoted(executable);
+ error += " does not exist.";
+ return cmQtAutoGen::CompilerFeaturesHandle();
+ }
+
+ // Test the executable
+ std::string stdOut;
+ {
+ std::string stdErr;
+ std::vector<std::string> command;
+ command.emplace_back(executable);
+ command.emplace_back("-h");
+ int retVal = 0;
+ const bool runResult = cmSystemTools::RunSingleCommand(
+ command, &stdOut, &stdErr, &retVal, nullptr, cmSystemTools::OUTPUT_NONE,
+ cmDuration::zero(), cmProcessOutput::Auto);
+ if (!runResult) {
+ error = "Test run of \"";
+ error += generator;
+ error += "\" executable ";
+ error += cmQtAutoGen::Quoted(executable) + " failed.\n";
+ error += cmQtAutoGen::QuotedCommand(command);
+ error += "\n";
+ error += stdOut;
+ error += "\n";
+ error += stdErr;
+ return cmQtAutoGen::CompilerFeaturesHandle();
+ }
+ }
+
+ // Create valid handle
+ cmQtAutoGen::CompilerFeaturesHandle res =
+ std::make_shared<cmQtAutoGen::CompilerFeatures>();
+ res->HelpOutput = std::move(stdOut);
+
+ // Register compiler features
+ this->CompilerFeatures_.emplace(executable, res);
+
+ return res;
+}
+
+bool cmQtAutoGenGlobalInitializer::generate()
+{
+ return (InitializeCustomTargets() && SetupCustomTargets());
+}
+
+bool cmQtAutoGenGlobalInitializer::InitializeCustomTargets()
+{
+ // Initialize global autogen targets
+ {
+ std::string const comment = "Global AUTOGEN target";
+ for (auto const& pair : GlobalAutoGenTargets_) {
+ GetOrCreateGlobalTarget(pair.first, pair.second, comment);
+ }
+ }
+ // Initialize global autorcc targets
+ {
+ std::string const comment = "Global AUTORCC target";
+ for (auto const& pair : GlobalAutoRccTargets_) {
+ GetOrCreateGlobalTarget(pair.first, pair.second, comment);
+ }
+ }
+ // Initialize per target autogen targets
+ for (auto& initializer : Initializers_) {
+ if (!initializer->InitCustomTargets()) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool cmQtAutoGenGlobalInitializer::SetupCustomTargets()
+{
+ for (auto& initializer : Initializers_) {
+ if (!initializer->SetupCustomTargets()) {
+ return false;
+ }
+ }
+ return true;
+}
diff --git a/Source/cmQtAutoGenGlobalInitializer.h b/Source/cmQtAutoGenGlobalInitializer.h
new file mode 100644
index 000000000..d56153ab7
--- /dev/null
+++ b/Source/cmQtAutoGenGlobalInitializer.h
@@ -0,0 +1,86 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#ifndef cmQtAutoGenGlobalInitializer_h
+#define cmQtAutoGenGlobalInitializer_h
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include "cmQtAutoGen.h"
+
+#include <map>
+#include <memory> // IWYU pragma: keep
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+class cmLocalGenerator;
+class cmQtAutoGenInitializer;
+
+/// @brief Initializes the QtAutoGen generators
+class cmQtAutoGenGlobalInitializer
+{
+public:
+ /// @brief Collection of QtAutogen related keywords
+ class Keywords
+ {
+ public:
+ Keywords();
+
+ std::string AUTOMOC;
+ std::string AUTOUIC;
+ std::string AUTORCC;
+
+ std::string AUTOMOC_EXECUTABLE;
+ std::string AUTOUIC_EXECUTABLE;
+ std::string AUTORCC_EXECUTABLE;
+
+ std::string SKIP_AUTOGEN;
+ std::string SKIP_AUTOMOC;
+ std::string SKIP_AUTOUIC;
+ std::string SKIP_AUTORCC;
+
+ std::string AUTOUIC_OPTIONS;
+ std::string AUTORCC_OPTIONS;
+
+ std::string qrc;
+ std::string ui;
+ };
+
+public:
+ cmQtAutoGenGlobalInitializer(
+ std::vector<cmLocalGenerator*> const& localGenerators);
+ ~cmQtAutoGenGlobalInitializer();
+
+ Keywords const& kw() const { return Keywords_; };
+
+ bool generate();
+
+private:
+ friend class cmQtAutoGenInitializer;
+
+ bool InitializeCustomTargets();
+ bool SetupCustomTargets();
+
+ void GetOrCreateGlobalTarget(cmLocalGenerator* localGen,
+ std::string const& name,
+ std::string const& comment);
+
+ void AddToGlobalAutoGen(cmLocalGenerator* localGen,
+ std::string const& targetName);
+ void AddToGlobalAutoRcc(cmLocalGenerator* localGen,
+ std::string const& targetName);
+
+ cmQtAutoGen::CompilerFeaturesHandle GetCompilerFeatures(
+ std::string const& generator, std::string const& executable,
+ std::string& error);
+
+private:
+ std::vector<std::unique_ptr<cmQtAutoGenInitializer>> Initializers_;
+ std::map<cmLocalGenerator*, std::string> GlobalAutoGenTargets_;
+ std::map<cmLocalGenerator*, std::string> GlobalAutoRccTargets_;
+ std::unordered_map<std::string, cmQtAutoGen::CompilerFeaturesHandle>
+ CompilerFeatures_;
+ Keywords const Keywords_;
+};
+
+#endif
diff --git a/Source/cmQtAutoGenInitializer.cxx b/Source/cmQtAutoGenInitializer.cxx
new file mode 100644
index 000000000..9985f93eb
--- /dev/null
+++ b/Source/cmQtAutoGenInitializer.cxx
@@ -0,0 +1,1712 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmQtAutoGenInitializer.h"
+#include "cmQtAutoGen.h"
+#include "cmQtAutoGenGlobalInitializer.h"
+
+#include "cmAlgorithms.h"
+#include "cmCustomCommand.h"
+#include "cmCustomCommandLines.h"
+#include "cmFilePathChecksum.h"
+#include "cmGeneratorExpression.h"
+#include "cmGeneratorTarget.h"
+#include "cmGlobalGenerator.h"
+#include "cmLinkItem.h"
+#include "cmListFileCache.h"
+#include "cmLocalGenerator.h"
+#include "cmMakefile.h"
+#include "cmMessageType.h"
+#include "cmOutputConverter.h"
+#include "cmPolicies.h"
+#include "cmSourceFile.h"
+#include "cmSourceFileLocationKind.h"
+#include "cmSourceGroup.h"
+#include "cmState.h"
+#include "cmStateTypes.h"
+#include "cmSystemTools.h"
+#include "cmTarget.h"
+#include "cmake.h"
+#include "cmsys/SystemInformation.hxx"
+
+#include <algorithm>
+#include <array>
+#include <deque>
+#include <map>
+#include <set>
+#include <string>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+static std::size_t GetParallelCPUCount()
+{
+ static std::size_t count = 0;
+ // Detect only on the first call
+ if (count == 0) {
+ cmsys::SystemInformation info;
+ info.RunCPUCheck();
+ count = info.GetNumberOfPhysicalCPU();
+ count = std::max<std::size_t>(count, 1);
+ count = std::min<std::size_t>(count, cmQtAutoGen::ParallelMax);
+ }
+ return count;
+}
+
+static std::string FileProjectRelativePath(cmMakefile* makefile,
+ std::string const& fileName)
+{
+ std::string res;
+ {
+ std::string pSource = cmSystemTools::RelativePath(
+ makefile->GetCurrentSourceDirectory(), fileName);
+ std::string pBinary = cmSystemTools::RelativePath(
+ makefile->GetCurrentBinaryDirectory(), fileName);
+ if (pSource.size() < pBinary.size()) {
+ res = std::move(pSource);
+ } else if (pBinary.size() < fileName.size()) {
+ res = std::move(pBinary);
+ } else {
+ res = fileName;
+ }
+ }
+ return res;
+}
+
+/**
+ * Tests if targetDepend is a STATIC_LIBRARY and if any of its
+ * recursive STATIC_LIBRARY dependencies depends on targetOrigin
+ * (STATIC_LIBRARY cycle).
+ */
+static bool StaticLibraryCycle(cmGeneratorTarget const* targetOrigin,
+ cmGeneratorTarget const* targetDepend,
+ std::string const& config)
+{
+ bool cycle = false;
+ if ((targetOrigin->GetType() == cmStateEnums::STATIC_LIBRARY) &&
+ (targetDepend->GetType() == cmStateEnums::STATIC_LIBRARY)) {
+ std::set<cmGeneratorTarget const*> knownLibs;
+ std::deque<cmGeneratorTarget const*> testLibs;
+
+ // Insert initial static_library dependency
+ knownLibs.insert(targetDepend);
+ testLibs.push_back(targetDepend);
+
+ while (!testLibs.empty()) {
+ cmGeneratorTarget const* testTarget = testLibs.front();
+ testLibs.pop_front();
+ // Check if the test target is the origin target (cycle)
+ if (testTarget == targetOrigin) {
+ cycle = true;
+ break;
+ }
+ // Collect all static_library dependencies from the test target
+ cmLinkImplementationLibraries const* libs =
+ testTarget->GetLinkImplementationLibraries(config);
+ if (libs != nullptr) {
+ for (cmLinkItem const& item : libs->Libraries) {
+ cmGeneratorTarget const* depTarget = item.Target;
+ if ((depTarget != nullptr) &&
+ (depTarget->GetType() == cmStateEnums::STATIC_LIBRARY) &&
+ knownLibs.insert(depTarget).second) {
+ testLibs.push_back(depTarget);
+ }
+ }
+ }
+ }
+ }
+ return cycle;
+}
+
+cmQtAutoGenInitializer::InfoWriter::InfoWriter(std::string const& filename)
+{
+ Ofs_.SetCopyIfDifferent(true);
+ Ofs_.Open(filename, false, true);
+}
+
+template <class IT>
+std::string cmQtAutoGenInitializer::InfoWriter::ListJoin(IT it_begin,
+ IT it_end)
+{
+ std::string res;
+ for (IT it = it_begin; it != it_end; ++it) {
+ if (it != it_begin) {
+ res += ';';
+ }
+ for (const char* c = it->c_str(); *c; ++c) {
+ if (*c == '"') {
+ // Escape the double quote to avoid ending the argument.
+ res += "\\\"";
+ } else if (*c == '$') {
+ // Escape the dollar to avoid expanding variables.
+ res += "\\$";
+ } else if (*c == '\\') {
+ // Escape the backslash to avoid other escapes.
+ res += "\\\\";
+ } else if (*c == ';') {
+ // Escape the semicolon to avoid list expansion.
+ res += "\\;";
+ } else {
+ // Other characters will be parsed correctly.
+ res += *c;
+ }
+ }
+ }
+ return res;
+}
+
+std::string cmQtAutoGenInitializer::InfoWriter::ConfigKey(
+ const char* key, std::string const& config)
+{
+ std::string ckey = key;
+ ckey += '_';
+ ckey += config;
+ return ckey;
+}
+
+void cmQtAutoGenInitializer::InfoWriter::Write(const char* key,
+ std::string const& value)
+{
+ Ofs_ << "set(" << key << " " << cmOutputConverter::EscapeForCMake(value)
+ << ")\n";
+};
+
+void cmQtAutoGenInitializer::InfoWriter::WriteUInt(const char* key,
+ unsigned int value)
+{
+ Ofs_ << "set(" << key << " " << value << ")\n";
+};
+
+template <class C>
+void cmQtAutoGenInitializer::InfoWriter::WriteStrings(const char* key,
+ C const& container)
+{
+ Ofs_ << "set(" << key << " \""
+ << ListJoin(container.begin(), container.end()) << "\")\n";
+}
+
+void cmQtAutoGenInitializer::InfoWriter::WriteConfig(
+ const char* key, std::map<std::string, std::string> const& map)
+{
+ for (auto const& item : map) {
+ Write(ConfigKey(key, item.first).c_str(), item.second);
+ }
+};
+
+template <class C>
+void cmQtAutoGenInitializer::InfoWriter::WriteConfigStrings(
+ const char* key, std::map<std::string, C> const& map)
+{
+ for (auto const& item : map) {
+ WriteStrings(ConfigKey(key, item.first).c_str(), item.second);
+ }
+}
+
+void cmQtAutoGenInitializer::InfoWriter::WriteNestedLists(
+ const char* key, std::vector<std::vector<std::string>> const& lists)
+{
+ std::vector<std::string> seplist;
+ for (const std::vector<std::string>& list : lists) {
+ std::string blist = "{";
+ blist += ListJoin(list.begin(), list.end());
+ blist += "}";
+ seplist.push_back(std::move(blist));
+ }
+ Write(key, cmJoin(seplist, cmQtAutoGen::ListSep));
+};
+
+cmQtAutoGenInitializer::cmQtAutoGenInitializer(
+ cmQtAutoGenGlobalInitializer* globalInitializer, cmGeneratorTarget* target,
+ IntegerVersion const& qtVersion, bool mocEnabled, bool uicEnabled,
+ bool rccEnabled, bool globalAutogenTarget, bool globalAutoRccTarget)
+ : GlobalInitializer(globalInitializer)
+ , Target(target)
+ , QtVersion(qtVersion)
+{
+ AutogenTarget.GlobalTarget = globalAutogenTarget;
+ Moc.Enabled = mocEnabled;
+ Uic.Enabled = uicEnabled;
+ Rcc.Enabled = rccEnabled;
+ Rcc.GlobalTarget = globalAutoRccTarget;
+}
+
+bool cmQtAutoGenInitializer::InitCustomTargets()
+{
+ cmMakefile* makefile = this->Target->Target->GetMakefile();
+ cmLocalGenerator* localGen = this->Target->GetLocalGenerator();
+ cmGlobalGenerator* globalGen = localGen->GetGlobalGenerator();
+
+ // Configurations
+ this->MultiConfig = globalGen->IsMultiConfig();
+ this->ConfigDefault = makefile->GetConfigurations(this->ConfigsList);
+ if (this->ConfigsList.empty()) {
+ this->ConfigsList.push_back(this->ConfigDefault);
+ }
+
+ // Verbosity
+ this->Verbosity = makefile->GetSafeDefinition("CMAKE_AUTOGEN_VERBOSE");
+ if (!this->Verbosity.empty()) {
+ unsigned long iVerb = 0;
+ if (!cmSystemTools::StringToULong(this->Verbosity.c_str(), &iVerb)) {
+ // Non numeric verbosity
+ this->Verbosity = cmSystemTools::IsOn(this->Verbosity) ? "1" : "0";
+ }
+ }
+
+ // Targets FOLDER
+ {
+ const char* folder =
+ makefile->GetState()->GetGlobalProperty("AUTOMOC_TARGETS_FOLDER");
+ if (folder == nullptr) {
+ folder =
+ makefile->GetState()->GetGlobalProperty("AUTOGEN_TARGETS_FOLDER");
+ }
+ // Inherit FOLDER property from target (#13688)
+ if (folder == nullptr) {
+ folder = this->Target->GetProperty("FOLDER");
+ }
+ if (folder != nullptr) {
+ this->TargetsFolder = folder;
+ }
+ }
+
+ // Check status of policy CMP0071
+ {
+ cmPolicies::PolicyStatus const CMP0071_status =
+ makefile->GetPolicyStatus(cmPolicies::CMP0071);
+ switch (CMP0071_status) {
+ case cmPolicies::WARN:
+ this->CMP0071Warn = true;
+ CM_FALLTHROUGH;
+ case cmPolicies::OLD:
+ // Ignore GENERATED file
+ break;
+ case cmPolicies::REQUIRED_IF_USED:
+ case cmPolicies::REQUIRED_ALWAYS:
+ case cmPolicies::NEW:
+ // Process GENERATED file
+ this->CMP0071Accept = true;
+ break;
+ }
+ }
+
+ // Common directories
+ {
+ // Collapsed current binary directory
+ std::string const cbd = cmSystemTools::CollapseFullPath(
+ std::string(), makefile->GetCurrentBinaryDirectory());
+
+ // Info directory
+ this->Dir.Info = cbd;
+ this->Dir.Info += "/CMakeFiles";
+ this->Dir.Info += '/';
+ this->Dir.Info += this->Target->GetName();
+ this->Dir.Info += "_autogen";
+ this->Dir.Info += ".dir";
+ cmSystemTools::ConvertToUnixSlashes(this->Dir.Info);
+
+ // Build directory
+ this->Dir.Build = this->Target->GetSafeProperty("AUTOGEN_BUILD_DIR");
+ if (this->Dir.Build.empty()) {
+ this->Dir.Build = cbd;
+ this->Dir.Build += '/';
+ this->Dir.Build += this->Target->GetName();
+ this->Dir.Build += "_autogen";
+ }
+ cmSystemTools::ConvertToUnixSlashes(this->Dir.Build);
+ // Cleanup build directory
+ this->AddCleanFile(this->Dir.Build);
+
+ // Working directory
+ this->Dir.Work = cbd;
+ cmSystemTools::ConvertToUnixSlashes(this->Dir.Work);
+
+ // Include directory
+ this->Dir.Include = this->Dir.Build;
+ this->Dir.Include += "/include";
+ if (this->MultiConfig) {
+ this->Dir.Include += "_$<CONFIG>";
+ }
+ // Per config include directories
+ if (this->MultiConfig) {
+ for (std::string const& cfg : this->ConfigsList) {
+ std::string& dir = this->Dir.ConfigInclude[cfg];
+ dir = this->Dir.Build;
+ dir += "/include_";
+ dir += cfg;
+ }
+ }
+ }
+
+ // Moc, Uic and _autogen target settings
+ if (this->MocOrUicEnabled()) {
+ // Init moc specific settings
+ if (this->Moc.Enabled && !InitMoc()) {
+ return false;
+ }
+
+ // Init uic specific settings
+ if (this->Uic.Enabled && !InitUic()) {
+ return false;
+ }
+
+ // Autogen target name
+ this->AutogenTarget.Name = this->Target->GetName();
+ this->AutogenTarget.Name += "_autogen";
+
+ // Autogen target parallel processing
+ this->AutogenTarget.Parallel =
+ this->Target->GetSafeProperty("AUTOGEN_PARALLEL");
+ if (this->AutogenTarget.Parallel.empty() ||
+ (this->AutogenTarget.Parallel == "AUTO")) {
+ // Autodetect number of CPUs
+ this->AutogenTarget.Parallel = std::to_string(GetParallelCPUCount());
+ }
+
+ // Autogen target info and settings files
+ {
+ this->AutogenTarget.InfoFile = this->Dir.Info;
+ this->AutogenTarget.InfoFile += "/AutogenInfo.cmake";
+
+ this->AutogenTarget.SettingsFile = this->Dir.Info;
+ this->AutogenTarget.SettingsFile += "/AutogenOldSettings.txt";
+
+ if (this->MultiConfig) {
+ for (std::string const& cfg : this->ConfigsList) {
+ std::string& filename = this->AutogenTarget.ConfigSettingsFile[cfg];
+ filename =
+ AppendFilenameSuffix(this->AutogenTarget.SettingsFile, "_" + cfg);
+ this->AddCleanFile(filename);
+ }
+ } else {
+ this->AddCleanFile(this->AutogenTarget.SettingsFile);
+ }
+
+ this->AutogenTarget.ParseCacheFile = this->Dir.Info;
+ this->AutogenTarget.ParseCacheFile += "/ParseCache.txt";
+ this->AddCleanFile(this->AutogenTarget.ParseCacheFile);
+ }
+
+ // Autogen target: Compute user defined dependencies
+ {
+ this->AutogenTarget.DependOrigin =
+ this->Target->GetPropertyAsBool("AUTOGEN_ORIGIN_DEPENDS");
+
+ std::string const deps =
+ this->Target->GetSafeProperty("AUTOGEN_TARGET_DEPENDS");
+ if (!deps.empty()) {
+ std::vector<std::string> extraDeps;
+ cmSystemTools::ExpandListArgument(deps, extraDeps);
+ for (std::string const& depName : extraDeps) {
+ // Allow target and file dependencies
+ auto* depTarget = makefile->FindTargetToUse(depName);
+ if (depTarget != nullptr) {
+ this->AutogenTarget.DependTargets.insert(depTarget);
+ } else {
+ this->AutogenTarget.DependFiles.insert(depName);
+ }
+ }
+ }
+ }
+
+ // CMAKE_AUTOMOC_RELAXED_MODE deprecation warning
+ if (this->Moc.Enabled) {
+ if (cmSystemTools::IsOn(
+ makefile->GetDefinition("CMAKE_AUTOMOC_RELAXED_MODE"))) {
+ std::string msg = "AUTOMOC: CMAKE_AUTOMOC_RELAXED_MODE is "
+ "deprecated an will be removed in the future. ";
+ msg += "Consider disabling it and converting the target ";
+ msg += this->Target->GetName();
+ msg += " to regular mode.";
+ makefile->IssueMessage(MessageType::AUTHOR_WARNING, msg);
+ }
+ }
+ }
+
+ // Init rcc specific settings
+ if (this->Rcc.Enabled && !InitRcc()) {
+ return false;
+ }
+
+ // Add autogen include directory to the origin target INCLUDE_DIRECTORIES
+ if (this->MocOrUicEnabled() || (this->Rcc.Enabled && this->MultiConfig)) {
+ this->Target->AddIncludeDirectory(this->Dir.Include, true);
+ }
+
+ // Scan files
+ if (!this->InitScanFiles()) {
+ return false;
+ }
+
+ // Create autogen target
+ if (this->MocOrUicEnabled() && !this->InitAutogenTarget()) {
+ return false;
+ }
+
+ // Create rcc targets
+ if (this->Rcc.Enabled && !this->InitRccTargets()) {
+ return false;
+ }
+
+ return true;
+}
+
+bool cmQtAutoGenInitializer::InitMoc()
+{
+ cmMakefile* makefile = this->Target->Target->GetMakefile();
+ cmLocalGenerator* localGen = this->Target->GetLocalGenerator();
+
+ // Mocs compilation file
+ this->Moc.MocsCompilation = this->Dir.Build;
+ this->Moc.MocsCompilation += "/mocs_compilation.cpp";
+
+ // Moc predefs command
+ if (this->Target->GetPropertyAsBool("AUTOMOC_COMPILER_PREDEFINES") &&
+ (this->QtVersion >= IntegerVersion(5, 8))) {
+ this->Moc.PredefsCmd =
+ makefile->GetSafeDefinition("CMAKE_CXX_COMPILER_PREDEFINES_COMMAND");
+ }
+
+ // Moc includes
+ {
+ bool const appendImplicit = (this->QtVersion.Major >= 5);
+ auto GetIncludeDirs =
+ [this, localGen,
+ appendImplicit](std::string const& cfg) -> std::vector<std::string> {
+ // Get the include dirs for this target, without stripping the implicit
+ // include dirs off, see
+ // https://gitlab.kitware.com/cmake/cmake/issues/13667
+ std::vector<std::string> dirs;
+ localGen->GetIncludeDirectoriesImplicit(dirs, this->Target, "CXX", cfg,
+ false, appendImplicit);
+ return dirs;
+ };
+
+ // Default configuration include directories
+ this->Moc.Includes = GetIncludeDirs(this->ConfigDefault);
+ // Other configuration settings
+ if (this->MultiConfig) {
+ for (std::string const& cfg : this->ConfigsList) {
+ std::vector<std::string> dirs = GetIncludeDirs(cfg);
+ if (dirs != this->Moc.Includes) {
+ this->Moc.ConfigIncludes[cfg] = std::move(dirs);
+ }
+ }
+ }
+ }
+
+ // Moc compile definitions
+ {
+ auto GetCompileDefinitions =
+ [this, localGen](std::string const& cfg) -> std::set<std::string> {
+ std::set<std::string> defines;
+ localGen->GetTargetDefines(this->Target, cfg, "CXX", defines);
+#ifdef _WIN32
+ if (this->Moc.PredefsCmd.empty()) {
+ // Add WIN32 definition if we don't have a moc_predefs.h
+ defines.insert("WIN32");
+ }
+#endif
+ return defines;
+ };
+
+ // Default configuration defines
+ this->Moc.Defines = GetCompileDefinitions(this->ConfigDefault);
+ // Other configuration defines
+ if (this->MultiConfig) {
+ for (std::string const& cfg : this->ConfigsList) {
+ std::set<std::string> defines = GetCompileDefinitions(cfg);
+ if (defines != this->Moc.Defines) {
+ this->Moc.ConfigDefines[cfg] = std::move(defines);
+ }
+ }
+ }
+ }
+
+ // Moc executable
+ {
+ if (!this->GetQtExecutable(this->Moc, "moc", false)) {
+ return false;
+ }
+ // Let the _autogen target depend on the moc executable
+ if (this->Moc.ExecutableTarget != nullptr) {
+ this->AutogenTarget.DependTargets.insert(
+ this->Moc.ExecutableTarget->Target);
+ }
+ }
+
+ return true;
+}
+
+bool cmQtAutoGenInitializer::InitUic()
+{
+ cmMakefile* makefile = this->Target->Target->GetMakefile();
+
+ // Uic search paths
+ {
+ std::string const usp =
+ this->Target->GetSafeProperty("AUTOUIC_SEARCH_PATHS");
+ if (!usp.empty()) {
+ cmSystemTools::ExpandListArgument(usp, this->Uic.SearchPaths);
+ std::string const& srcDir = makefile->GetCurrentSourceDirectory();
+ for (std::string& path : this->Uic.SearchPaths) {
+ path = cmSystemTools::CollapseFullPath(path, srcDir);
+ }
+ }
+ }
+ // Uic target options
+ {
+ auto UicGetOpts =
+ [this](std::string const& cfg) -> std::vector<std::string> {
+ std::vector<std::string> opts;
+ this->Target->GetAutoUicOptions(opts, cfg);
+ return opts;
+ };
+
+ // Default settings
+ this->Uic.Options = UicGetOpts(this->ConfigDefault);
+
+ // Configuration specific settings
+ if (this->MultiConfig) {
+ for (std::string const& cfg : this->ConfigsList) {
+ std::vector<std::string> options = UicGetOpts(cfg);
+ if (options != this->Uic.Options) {
+ this->Uic.ConfigOptions[cfg] = std::move(options);
+ }
+ }
+ }
+ }
+
+ // Uic executable
+ {
+ if (!this->GetQtExecutable(this->Uic, "uic", true)) {
+ return false;
+ }
+ // Let the _autogen target depend on the uic executable
+ if (this->Uic.ExecutableTarget != nullptr) {
+ this->AutogenTarget.DependTargets.insert(
+ this->Uic.ExecutableTarget->Target);
+ }
+ }
+
+ return true;
+}
+
+bool cmQtAutoGenInitializer::InitRcc()
+{
+ // Rcc executable
+ {
+ if (!this->GetQtExecutable(this->Rcc, "rcc", false)) {
+ return false;
+ }
+ // Evaluate test output on demand
+ CompilerFeatures& features = *this->Rcc.ExecutableFeatures;
+ if (!features.Evaluated) {
+ // Look for list options
+ if (this->QtVersion.Major == 5 || this->QtVersion.Major == 6) {
+ if (features.HelpOutput.find("--list") != std::string::npos) {
+ features.ListOptions.emplace_back("--list");
+ } else if (features.HelpOutput.find("-list") != std::string::npos) {
+ features.ListOptions.emplace_back("-list");
+ }
+ }
+ // Evaluation finished
+ features.Evaluated = true;
+ }
+ }
+
+ return true;
+}
+
+bool cmQtAutoGenInitializer::InitScanFiles()
+{
+ cmMakefile* makefile = this->Target->Target->GetMakefile();
+ auto const& kw = this->GlobalInitializer->kw();
+
+ auto makeMUFile = [this, &kw](cmSourceFile* sf, std::string const& fullPath,
+ bool muIt) -> MUFileHandle {
+ MUFileHandle muf = cm::make_unique<MUFile>();
+ muf->RealPath = cmSystemTools::GetRealPath(fullPath);
+ muf->SF = sf;
+ muf->Generated = sf->GetIsGenerated();
+ bool const skipAutogen = sf->GetPropertyAsBool(kw.SKIP_AUTOGEN);
+ muf->SkipMoc = this->Moc.Enabled &&
+ (skipAutogen || sf->GetPropertyAsBool(kw.SKIP_AUTOMOC));
+ muf->SkipUic = this->Uic.Enabled &&
+ (skipAutogen || sf->GetPropertyAsBool(kw.SKIP_AUTOUIC));
+ if (muIt) {
+ muf->MocIt = this->Moc.Enabled && !muf->SkipMoc;
+ muf->UicIt = this->Uic.Enabled && !muf->SkipUic;
+ }
+ return muf;
+ };
+
+ auto addMUFile = [&](MUFileHandle&& muf, bool isHeader) {
+ if ((muf->MocIt || muf->UicIt) && muf->Generated) {
+ this->AutogenTarget.FilesGenerated.emplace_back(muf.get());
+ }
+ if (isHeader) {
+ this->AutogenTarget.Headers.emplace(muf->SF, std::move(muf));
+ } else {
+ this->AutogenTarget.Sources.emplace(muf->SF, std::move(muf));
+ }
+ };
+
+ // Scan through target files
+ {
+ // Scan through target files
+ std::vector<cmSourceFile*> srcFiles;
+ this->Target->GetConfigCommonSourceFiles(srcFiles);
+ for (cmSourceFile* sf : srcFiles) {
+ // sf->GetExtension() is only valid after sf->GetFullPath() ...
+ // Since we're iterating over source files that might be not in the
+ // target we need to check for path errors (not existing files).
+ std::string pathError;
+ std::string const& fullPath = sf->GetFullPath(&pathError);
+ if (!pathError.empty() || fullPath.empty()) {
+ continue;
+ }
+ std::string const& ext = sf->GetExtension();
+
+ // Register files that will be scanned by moc or uic
+ if (this->MocOrUicEnabled()) {
+ switch (cmSystemTools::GetFileFormat(ext)) {
+ case cmSystemTools::HEADER_FILE_FORMAT:
+ addMUFile(makeMUFile(sf, fullPath, true), true);
+ break;
+ case cmSystemTools::CXX_FILE_FORMAT:
+ addMUFile(makeMUFile(sf, fullPath, true), false);
+ break;
+ default:
+ break;
+ }
+ }
+
+ // Register rcc enabled files
+ if (this->Rcc.Enabled) {
+ if ((ext == kw.qrc) && !sf->GetPropertyAsBool(kw.SKIP_AUTOGEN) &&
+ !sf->GetPropertyAsBool(kw.SKIP_AUTORCC)) {
+ // Register qrc file
+ Qrc qrc;
+ qrc.QrcFile = cmSystemTools::GetRealPath(fullPath);
+ qrc.QrcName =
+ cmSystemTools::GetFilenameWithoutLastExtension(qrc.QrcFile);
+ qrc.Generated = sf->GetIsGenerated();
+ // RCC options
+ {
+ std::string const opts = sf->GetSafeProperty(kw.AUTORCC_OPTIONS);
+ if (!opts.empty()) {
+ cmSystemTools::ExpandListArgument(opts, qrc.Options);
+ }
+ }
+ this->Rcc.Qrcs.push_back(std::move(qrc));
+ }
+ }
+ }
+ }
+ // cmGeneratorTarget::GetConfigCommonSourceFiles computes the target's
+ // sources meta data cache. Clear it so that OBJECT library targets that
+ // are AUTOGEN initialized after this target get their added
+ // mocs_compilation.cpp source acknowledged by this target.
+ this->Target->ClearSourcesCache();
+
+ // For source files find additional headers and private headers
+ if (this->MocOrUicEnabled()) {
+ std::vector<MUFileHandle> extraHeaders;
+ extraHeaders.reserve(this->AutogenTarget.Sources.size() * 2);
+ // Header search suffixes and extensions
+ std::array<std::string, 2> const suffixes{ { "", "_p" } };
+ auto const& exts = makefile->GetCMakeInstance()->GetHeaderExtensions();
+ // Scan through sources
+ for (auto const& pair : this->AutogenTarget.Sources) {
+ MUFile const& muf = *pair.second;
+ if (muf.MocIt || muf.UicIt) {
+ // Search for the default header file and a private header
+ std::string const& srcPath = muf.SF->GetFullPath();
+ std::string basePath = cmQtAutoGen::SubDirPrefix(srcPath);
+ basePath += cmSystemTools::GetFilenameWithoutLastExtension(srcPath);
+ for (auto const& suffix : suffixes) {
+ std::string const suffixedPath = basePath + suffix;
+ for (auto const& ext : exts) {
+ std::string fullPath = suffixedPath;
+ fullPath += '.';
+ fullPath += ext;
+
+ auto constexpr locationKind = cmSourceFileLocationKind::Known;
+ cmSourceFile* sf = makefile->GetSource(fullPath, locationKind);
+ if (sf != nullptr) {
+ // Check if we know about this header already
+ if (this->AutogenTarget.Headers.find(sf) !=
+ this->AutogenTarget.Headers.end()) {
+ continue;
+ }
+ // We only accept not-GENERATED files that do exist.
+ if (!sf->GetIsGenerated() &&
+ !cmSystemTools::FileExists(fullPath)) {
+ continue;
+ }
+ } else if (cmSystemTools::FileExists(fullPath)) {
+ // Create a new source file for the existing file
+ sf = makefile->CreateSource(fullPath, false, locationKind);
+ }
+
+ if (sf != nullptr) {
+ auto eMuf = makeMUFile(sf, fullPath, true);
+ // Ony process moc/uic when the parent is processed as well
+ if (!muf.MocIt) {
+ eMuf->MocIt = false;
+ }
+ if (!muf.UicIt) {
+ eMuf->UicIt = false;
+ }
+ extraHeaders.emplace_back(std::move(eMuf));
+ }
+ }
+ }
+ }
+ }
+ // Move generated files to main headers list
+ for (auto& eMuf : extraHeaders) {
+ addMUFile(std::move(eMuf), true);
+ }
+ }
+
+ // Scan through all source files in the makefile to extract moc and uic
+ // parameters. Historically we support non target source file parameters.
+ // The reason is that their file names might be discovered from source files
+ // at generation time.
+ if (this->MocOrUicEnabled()) {
+ for (cmSourceFile* sf : makefile->GetSourceFiles()) {
+ // sf->GetExtension() is only valid after sf->GetFullPath() ...
+ // Since we're iterating over source files that might be not in the
+ // target we need to check for path errors (not existing files).
+ std::string pathError;
+ std::string const& fullPath = sf->GetFullPath(&pathError);
+ if (!pathError.empty() || fullPath.empty()) {
+ continue;
+ }
+ std::string const& ext = sf->GetExtension();
+
+ auto const fileFormat = cmSystemTools::GetFileFormat(ext);
+ if (fileFormat == cmSystemTools::HEADER_FILE_FORMAT) {
+ if (this->AutogenTarget.Headers.find(sf) ==
+ this->AutogenTarget.Headers.end()) {
+ auto muf = makeMUFile(sf, fullPath, false);
+ if (muf->SkipMoc || muf->SkipUic) {
+ this->AutogenTarget.Headers.emplace(sf, std::move(muf));
+ }
+ }
+ } else if (fileFormat == cmSystemTools::CXX_FILE_FORMAT) {
+ if (this->AutogenTarget.Sources.find(sf) ==
+ this->AutogenTarget.Sources.end()) {
+ auto muf = makeMUFile(sf, fullPath, false);
+ if (muf->SkipMoc || muf->SkipUic) {
+ this->AutogenTarget.Sources.emplace(sf, std::move(muf));
+ }
+ }
+ } else if (this->Uic.Enabled && (ext == kw.ui)) {
+ // .ui file
+ std::string realPath = cmSystemTools::GetRealPath(fullPath);
+ bool const skipAutogen = sf->GetPropertyAsBool(kw.SKIP_AUTOGEN);
+ bool const skipUic =
+ (skipAutogen || sf->GetPropertyAsBool(kw.SKIP_AUTOUIC));
+ if (!skipUic) {
+ // Check if the .ui file has uic options
+ std::string const uicOpts = sf->GetSafeProperty(kw.AUTOUIC_OPTIONS);
+ if (!uicOpts.empty()) {
+ this->Uic.FileFiles.push_back(std::move(realPath));
+ std::vector<std::string> optsVec;
+ cmSystemTools::ExpandListArgument(uicOpts, optsVec);
+ this->Uic.FileOptions.push_back(std::move(optsVec));
+ }
+ } else {
+ // Register skipped .ui file
+ this->Uic.SkipUi.insert(std::move(realPath));
+ }
+ }
+ }
+ }
+
+ // Process GENERATED sources and headers
+ if (this->MocOrUicEnabled() && !this->AutogenTarget.FilesGenerated.empty()) {
+ if (this->CMP0071Accept) {
+ // Let the autogen target depend on the GENERATED files
+ for (MUFile* muf : this->AutogenTarget.FilesGenerated) {
+ this->AutogenTarget.DependFiles.insert(muf->RealPath);
+ }
+ } else if (this->CMP0071Warn) {
+ std::string msg;
+ msg += cmPolicies::GetPolicyWarning(cmPolicies::CMP0071);
+ msg += '\n';
+ std::string property;
+ if (this->Moc.Enabled && this->Uic.Enabled) {
+ property = kw.SKIP_AUTOGEN;
+ } else if (this->Moc.Enabled) {
+ property = kw.SKIP_AUTOMOC;
+ } else if (this->Uic.Enabled) {
+ property = kw.SKIP_AUTOUIC;
+ }
+ msg += "For compatibility, CMake is excluding the GENERATED source "
+ "file(s):\n";
+ for (MUFile* muf : this->AutogenTarget.FilesGenerated) {
+ msg += " ";
+ msg += Quoted(muf->RealPath);
+ msg += '\n';
+ }
+ msg += "from processing by ";
+ msg += cmQtAutoGen::Tools(this->Moc.Enabled, this->Uic.Enabled, false);
+ msg += ". If any of the files should be processed, set CMP0071 to NEW. "
+ "If any of the files should not be processed, "
+ "explicitly exclude them by setting the source file property ";
+ msg += property;
+ msg += ":\n set_property(SOURCE file.h PROPERTY ";
+ msg += property;
+ msg += " ON)\n";
+ makefile->IssueMessage(MessageType::AUTHOR_WARNING, msg);
+ }
+ }
+
+ // Process qrc files
+ if (!this->Rcc.Qrcs.empty()) {
+ const bool modernQt = (this->QtVersion.Major >= 5);
+ // Target rcc options
+ std::vector<std::string> optionsTarget;
+ cmSystemTools::ExpandListArgument(
+ this->Target->GetSafeProperty(kw.AUTORCC_OPTIONS), optionsTarget);
+
+ // Check if file name is unique
+ for (Qrc& qrc : this->Rcc.Qrcs) {
+ qrc.Unique = true;
+ for (Qrc const& qrc2 : this->Rcc.Qrcs) {
+ if ((&qrc != &qrc2) && (qrc.QrcName == qrc2.QrcName)) {
+ qrc.Unique = false;
+ break;
+ }
+ }
+ }
+ // Path checksum and file names
+ {
+ cmFilePathChecksum const fpathCheckSum(makefile);
+ for (Qrc& qrc : this->Rcc.Qrcs) {
+ qrc.PathChecksum = fpathCheckSum.getPart(qrc.QrcFile);
+ // RCC output file name
+ {
+ std::string rccFile = this->Dir.Build + "/";
+ rccFile += qrc.PathChecksum;
+ rccFile += "/qrc_";
+ rccFile += qrc.QrcName;
+ rccFile += ".cpp";
+ qrc.RccFile = std::move(rccFile);
+ }
+ {
+ std::string base = this->Dir.Info;
+ base += "/RCC";
+ base += qrc.QrcName;
+ if (!qrc.Unique) {
+ base += qrc.PathChecksum;
+ }
+
+ qrc.LockFile = base;
+ qrc.LockFile += ".lock";
+
+ qrc.InfoFile = base;
+ qrc.InfoFile += "Info.cmake";
+
+ qrc.SettingsFile = base;
+ qrc.SettingsFile += "Settings.txt";
+
+ if (this->MultiConfig) {
+ for (std::string const& cfg : this->ConfigsList) {
+ qrc.ConfigSettingsFile[cfg] =
+ AppendFilenameSuffix(qrc.SettingsFile, "_" + cfg);
+ }
+ }
+ }
+ }
+ }
+ // RCC options
+ for (Qrc& qrc : this->Rcc.Qrcs) {
+ // Target options
+ std::vector<std::string> opts = optionsTarget;
+ // Merge computed "-name XYZ" option
+ {
+ std::string name = qrc.QrcName;
+ // Replace '-' with '_'. The former is not valid for symbol names.
+ std::replace(name.begin(), name.end(), '-', '_');
+ if (!qrc.Unique) {
+ name += "_";
+ name += qrc.PathChecksum;
+ }
+ std::vector<std::string> nameOpts;
+ nameOpts.emplace_back("-name");
+ nameOpts.emplace_back(std::move(name));
+ RccMergeOptions(opts, nameOpts, modernQt);
+ }
+ // Merge file option
+ RccMergeOptions(opts, qrc.Options, modernQt);
+ qrc.Options = std::move(opts);
+ }
+ // RCC resources
+ for (Qrc& qrc : this->Rcc.Qrcs) {
+ if (!qrc.Generated) {
+ std::string error;
+ RccLister const lister(this->Rcc.Executable,
+ this->Rcc.ExecutableFeatures->ListOptions);
+ if (!lister.list(qrc.QrcFile, qrc.Resources, error)) {
+ cmSystemTools::Error(error);
+ return false;
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+bool cmQtAutoGenInitializer::InitAutogenTarget()
+{
+ cmMakefile* makefile = this->Target->Target->GetMakefile();
+ cmLocalGenerator* localGen = this->Target->GetLocalGenerator();
+ cmGlobalGenerator* globalGen = localGen->GetGlobalGenerator();
+
+ // Register info file as generated by CMake
+ makefile->AddCMakeOutputFile(this->AutogenTarget.InfoFile);
+
+ // Files provided by the autogen target
+ std::vector<std::string> autogenProvides;
+ if (this->Moc.Enabled) {
+ this->AddGeneratedSource(this->Moc.MocsCompilation, this->Moc, true);
+ autogenProvides.push_back(this->Moc.MocsCompilation);
+ }
+
+ // Compose target comment
+ std::string autogenComment;
+ {
+ std::string tools;
+ if (this->Moc.Enabled) {
+ tools += "MOC";
+ }
+ if (this->Uic.Enabled) {
+ if (!tools.empty()) {
+ tools += " and ";
+ }
+ tools += "UIC";
+ }
+ autogenComment = "Automatic ";
+ autogenComment += tools;
+ autogenComment += " for target ";
+ autogenComment += this->Target->GetName();
+ }
+
+ // Compose command lines
+ cmCustomCommandLines commandLines;
+ {
+ cmCustomCommandLine currentLine;
+ currentLine.push_back(cmSystemTools::GetCMakeCommand());
+ currentLine.push_back("-E");
+ currentLine.push_back("cmake_autogen");
+ currentLine.push_back(this->AutogenTarget.InfoFile);
+ currentLine.push_back("$<CONFIGURATION>");
+ commandLines.push_back(std::move(currentLine));
+ }
+
+ // Use PRE_BUILD on demand
+ bool usePRE_BUILD = false;
+ if (globalGen->GetName().find("Visual Studio") != std::string::npos) {
+ // Under VS use a PRE_BUILD event instead of a separate target to
+ // reduce the number of targets loaded into the IDE.
+ // This also works around a VS 11 bug that may skip updating the target:
+ // https://connect.microsoft.com/VisualStudio/feedback/details/769495
+ usePRE_BUILD = true;
+ }
+ // Disable PRE_BUILD in some cases
+ if (usePRE_BUILD) {
+ // Cannot use PRE_BUILD with file depends
+ if (!this->AutogenTarget.DependFiles.empty()) {
+ usePRE_BUILD = false;
+ }
+ // Cannot use PRE_BUILD when a global autogen target is in place
+ if (AutogenTarget.GlobalTarget) {
+ usePRE_BUILD = false;
+ }
+ }
+ // Create the autogen target/command
+ if (usePRE_BUILD) {
+ // Add additional autogen target dependencies to origin target
+ for (cmTarget* depTarget : this->AutogenTarget.DependTargets) {
+ this->Target->Target->AddUtility(depTarget->GetName(), makefile);
+ }
+
+ // Add the pre-build command directly to bypass the OBJECT_LIBRARY
+ // rejection in cmMakefile::AddCustomCommandToTarget because we know
+ // PRE_BUILD will work for an OBJECT_LIBRARY in this specific case.
+ //
+ // PRE_BUILD does not support file dependencies!
+ const std::vector<std::string> no_output;
+ const std::vector<std::string> no_deps;
+ cmCustomCommand cc(makefile, no_output, autogenProvides, no_deps,
+ commandLines, autogenComment.c_str(),
+ this->Dir.Work.c_str());
+ cc.SetEscapeOldStyle(false);
+ cc.SetEscapeAllowMakeVars(true);
+ this->Target->Target->AddPreBuildCommand(cc);
+ } else {
+
+ // Add link library target dependencies to the autogen target
+ // dependencies
+ if (this->AutogenTarget.DependOrigin) {
+ // add_dependencies/addUtility do not support generator expressions.
+ // We depend only on the libraries found in all configs therefore.
+ std::map<cmGeneratorTarget const*, std::size_t> commonTargets;
+ for (std::string const& config : this->ConfigsList) {
+ cmLinkImplementationLibraries const* libs =
+ this->Target->GetLinkImplementationLibraries(config);
+ if (libs != nullptr) {
+ for (cmLinkItem const& item : libs->Libraries) {
+ cmGeneratorTarget const* libTarget = item.Target;
+ if ((libTarget != nullptr) &&
+ !StaticLibraryCycle(this->Target, libTarget, config)) {
+ // Increment target config count
+ commonTargets[libTarget]++;
+ }
+ }
+ }
+ }
+ for (auto const& item : commonTargets) {
+ if (item.second == this->ConfigsList.size()) {
+ this->AutogenTarget.DependTargets.insert(item.first->Target);
+ }
+ }
+ }
+
+ // Create autogen target
+ cmTarget* autogenTarget = makefile->AddUtilityCommand(
+ this->AutogenTarget.Name, cmMakefile::TargetOrigin::Generator, true,
+ this->Dir.Work.c_str(), /*byproducts=*/autogenProvides,
+ std::vector<std::string>(this->AutogenTarget.DependFiles.begin(),
+ this->AutogenTarget.DependFiles.end()),
+ commandLines, false, autogenComment.c_str());
+ // Create autogen generator target
+ localGen->AddGeneratorTarget(
+ new cmGeneratorTarget(autogenTarget, localGen));
+
+ // Forward origin utilities to autogen target
+ if (this->AutogenTarget.DependOrigin) {
+ for (BT<std::string> const& depName : this->Target->GetUtilities()) {
+ autogenTarget->AddUtility(depName.Value, makefile);
+ }
+ }
+ // Add additional autogen target dependencies to autogen target
+ for (cmTarget* depTarget : this->AutogenTarget.DependTargets) {
+ autogenTarget->AddUtility(depTarget->GetName(), makefile);
+ }
+
+ // Set FOLDER property in autogen target
+ if (!this->TargetsFolder.empty()) {
+ autogenTarget->SetProperty("FOLDER", this->TargetsFolder.c_str());
+ }
+
+ // Add autogen target to the origin target dependencies
+ this->Target->Target->AddUtility(this->AutogenTarget.Name, makefile);
+
+ // Add autogen target to the global autogen target dependencies
+ if (this->AutogenTarget.GlobalTarget) {
+ this->GlobalInitializer->AddToGlobalAutoGen(localGen,
+ this->AutogenTarget.Name);
+ }
+ }
+
+ return true;
+}
+
+bool cmQtAutoGenInitializer::InitRccTargets()
+{
+ cmMakefile* makefile = this->Target->Target->GetMakefile();
+ cmLocalGenerator* localGen = this->Target->GetLocalGenerator();
+
+ for (Qrc const& qrc : this->Rcc.Qrcs) {
+ // Register info file as generated by CMake
+ makefile->AddCMakeOutputFile(qrc.InfoFile);
+ // Register file at target
+ this->AddGeneratedSource(qrc.RccFile, this->Rcc);
+
+ std::vector<std::string> ccOutput;
+ ccOutput.push_back(qrc.RccFile);
+
+ std::vector<std::string> ccDepends;
+ // Add the .qrc and info file to the custom command dependencies
+ ccDepends.push_back(qrc.QrcFile);
+ ccDepends.push_back(qrc.InfoFile);
+
+ cmCustomCommandLines commandLines;
+ if (this->MultiConfig) {
+ // Build for all configurations
+ for (std::string const& config : this->ConfigsList) {
+ cmCustomCommandLine currentLine;
+ currentLine.push_back(cmSystemTools::GetCMakeCommand());
+ currentLine.push_back("-E");
+ currentLine.push_back("cmake_autorcc");
+ currentLine.push_back(qrc.InfoFile);
+ currentLine.push_back(config);
+ commandLines.push_back(std::move(currentLine));
+ }
+ } else {
+ cmCustomCommandLine currentLine;
+ currentLine.push_back(cmSystemTools::GetCMakeCommand());
+ currentLine.push_back("-E");
+ currentLine.push_back("cmake_autorcc");
+ currentLine.push_back(qrc.InfoFile);
+ currentLine.push_back("$<CONFIG>");
+ commandLines.push_back(std::move(currentLine));
+ }
+ std::string ccComment = "Automatic RCC for ";
+ ccComment += FileProjectRelativePath(makefile, qrc.QrcFile);
+
+ if (qrc.Generated || this->Rcc.GlobalTarget) {
+ // Create custom rcc target
+ std::string ccName;
+ {
+ ccName = this->Target->GetName();
+ ccName += "_arcc_";
+ ccName += qrc.QrcName;
+ if (!qrc.Unique) {
+ ccName += "_";
+ ccName += qrc.PathChecksum;
+ }
+
+ cmTarget* autoRccTarget = makefile->AddUtilityCommand(
+ ccName, cmMakefile::TargetOrigin::Generator, true,
+ this->Dir.Work.c_str(), ccOutput, ccDepends, commandLines, false,
+ ccComment.c_str());
+
+ // Create autogen generator target
+ localGen->AddGeneratorTarget(
+ new cmGeneratorTarget(autoRccTarget, localGen));
+
+ // Set FOLDER property in autogen target
+ if (!this->TargetsFolder.empty()) {
+ autoRccTarget->SetProperty("FOLDER", this->TargetsFolder.c_str());
+ }
+ if (!this->Rcc.ExecutableTargetName.empty()) {
+ autoRccTarget->AddUtility(this->Rcc.ExecutableTargetName, makefile);
+ }
+ }
+ // Add autogen target to the origin target dependencies
+ this->Target->Target->AddUtility(ccName, makefile);
+
+ // Add autogen target to the global autogen target dependencies
+ if (this->Rcc.GlobalTarget) {
+ this->GlobalInitializer->AddToGlobalAutoRcc(localGen, ccName);
+ }
+ } else {
+ // Create custom rcc command
+ {
+ std::vector<std::string> ccByproducts;
+
+ // Add the resource files to the dependencies
+ for (std::string const& fileName : qrc.Resources) {
+ // Add resource file to the custom command dependencies
+ ccDepends.push_back(fileName);
+ }
+ if (!this->Rcc.ExecutableTargetName.empty()) {
+ ccDepends.push_back(this->Rcc.ExecutableTargetName);
+ }
+ makefile->AddCustomCommandToOutput(ccOutput, ccByproducts, ccDepends,
+ /*main_dependency*/ std::string(),
+ commandLines, ccComment.c_str(),
+ this->Dir.Work.c_str());
+ }
+ // Reconfigure when .qrc file changes
+ makefile->AddCMakeDependFile(qrc.QrcFile);
+ }
+ }
+
+ return true;
+}
+
+bool cmQtAutoGenInitializer::SetupCustomTargets()
+{
+ // Create info directory on demand
+ if (!cmSystemTools::MakeDirectory(this->Dir.Info)) {
+ std::string emsg = ("AutoGen: Could not create directory: ");
+ emsg += Quoted(this->Dir.Info);
+ cmSystemTools::Error(emsg);
+ return false;
+ }
+
+ // Generate autogen target info file
+ if (this->MocOrUicEnabled()) {
+ // Write autogen target info files
+ if (!this->SetupWriteAutogenInfo()) {
+ return false;
+ }
+ }
+
+ // Write AUTORCC info files
+ return !this->Rcc.Enabled || this->SetupWriteRccInfo();
+}
+
+bool cmQtAutoGenInitializer::SetupWriteAutogenInfo()
+{
+ InfoWriter ofs(this->AutogenTarget.InfoFile);
+ if (ofs) {
+ // Utility lambdas
+ cmMakefile* makefile = this->Target->Target->GetMakefile();
+ auto MfDef = [makefile](const char* key) {
+ return makefile->GetSafeDefinition(key);
+ };
+
+ // Write common settings
+ ofs.Write("# Meta\n");
+ ofs.Write("AM_MULTI_CONFIG", this->MultiConfig ? "TRUE" : "FALSE");
+ ofs.Write("AM_PARALLEL", this->AutogenTarget.Parallel);
+ ofs.Write("AM_VERBOSITY", this->Verbosity);
+
+ ofs.Write("# Directories\n");
+ ofs.Write("AM_CMAKE_SOURCE_DIR", MfDef("CMAKE_SOURCE_DIR"));
+ ofs.Write("AM_CMAKE_BINARY_DIR", MfDef("CMAKE_BINARY_DIR"));
+ ofs.Write("AM_CMAKE_CURRENT_SOURCE_DIR",
+ MfDef("CMAKE_CURRENT_SOURCE_DIR"));
+ ofs.Write("AM_CMAKE_CURRENT_BINARY_DIR",
+ MfDef("CMAKE_CURRENT_BINARY_DIR"));
+ ofs.Write("AM_CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE",
+ MfDef("CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE"));
+ ofs.Write("AM_BUILD_DIR", this->Dir.Build);
+ ofs.Write("AM_INCLUDE_DIR", this->Dir.Include);
+ ofs.WriteConfig("AM_INCLUDE_DIR", this->Dir.ConfigInclude);
+
+ std::vector<std::string> headers;
+ std::vector<std::string> headersFlags;
+ std::vector<std::string> headersBuildPaths;
+ std::vector<std::string> sources;
+ std::vector<std::string> sourcesFlags;
+ std::set<std::string> moc_skip;
+ std::set<std::string> uic_skip;
+
+ // Filter headers
+ {
+ auto headerCount = this->AutogenTarget.Headers.size();
+ headers.reserve(headerCount);
+ headersFlags.reserve(headerCount);
+
+ std::vector<MUFile const*> sortedHeaders;
+ {
+ sortedHeaders.reserve(headerCount);
+ for (auto const& pair : this->AutogenTarget.Headers) {
+ sortedHeaders.emplace_back(pair.second.get());
+ }
+ std::sort(sortedHeaders.begin(), sortedHeaders.end(),
+ [](MUFile const* a, MUFile const* b) {
+ return (a->RealPath < b->RealPath);
+ });
+ }
+
+ for (MUFile const* const muf : sortedHeaders) {
+ if (muf->Generated && !this->CMP0071Accept) {
+ continue;
+ }
+ if (muf->SkipMoc) {
+ moc_skip.insert(muf->RealPath);
+ }
+ if (muf->SkipUic) {
+ uic_skip.insert(muf->RealPath);
+ }
+ if (muf->MocIt || muf->UicIt) {
+ headers.emplace_back(muf->RealPath);
+ std::string flags;
+ flags += muf->MocIt ? 'M' : 'm';
+ flags += muf->UicIt ? 'U' : 'u';
+ headersFlags.emplace_back(std::move(flags));
+ }
+ }
+ }
+ // Header build paths
+ {
+ cmFilePathChecksum const fpathCheckSum(makefile);
+ std::unordered_set<std::string> emitted;
+ for (std::string const& hdr : headers) {
+ std::string basePath = fpathCheckSum.getPart(hdr);
+ basePath += "/moc_";
+ basePath += cmSystemTools::GetFilenameWithoutLastExtension(hdr);
+ for (unsigned int ii = 1; ii != 1024; ++ii) {
+ std::string path = basePath;
+ if (ii > 1) {
+ path += '_';
+ path += std::to_string(ii);
+ }
+ path += ".cpp";
+ if (emitted.emplace(path).second) {
+ headersBuildPaths.emplace_back(std::move(path));
+ break;
+ }
+ }
+ }
+ }
+
+ // Filter sources
+ {
+ auto sourcesCount = this->AutogenTarget.Sources.size();
+ sources.reserve(sourcesCount);
+ sourcesFlags.reserve(sourcesCount);
+
+ std::vector<MUFile const*> sorted;
+ sorted.reserve(sourcesCount);
+ for (auto const& pair : this->AutogenTarget.Sources) {
+ sorted.emplace_back(pair.second.get());
+ }
+ std::sort(sorted.begin(), sorted.end(),
+ [](MUFile const* a, MUFile const* b) {
+ return (a->RealPath < b->RealPath);
+ });
+
+ for (MUFile const* const muf : sorted) {
+ if (muf->Generated && !this->CMP0071Accept) {
+ continue;
+ }
+ if (muf->SkipMoc) {
+ moc_skip.insert(muf->RealPath);
+ }
+ if (muf->SkipUic) {
+ uic_skip.insert(muf->RealPath);
+ }
+ if (muf->MocIt || muf->UicIt) {
+ sources.emplace_back(muf->RealPath);
+ std::string flags;
+ flags += muf->MocIt ? 'M' : 'm';
+ flags += muf->UicIt ? 'U' : 'u';
+ sourcesFlags.emplace_back(std::move(flags));
+ }
+ }
+ }
+
+ ofs.Write("# Qt\n");
+ ofs.WriteUInt("AM_QT_VERSION_MAJOR", this->QtVersion.Major);
+ ofs.Write("AM_QT_MOC_EXECUTABLE", this->Moc.Executable);
+ ofs.Write("AM_QT_UIC_EXECUTABLE", this->Uic.Executable);
+
+ ofs.Write("# Files\n");
+ ofs.Write("AM_CMAKE_EXECUTABLE", cmSystemTools::GetCMakeCommand());
+ ofs.Write("AM_SETTINGS_FILE", this->AutogenTarget.SettingsFile);
+ ofs.WriteConfig("AM_SETTINGS_FILE",
+ this->AutogenTarget.ConfigSettingsFile);
+ ofs.Write("AM_PARSE_CACHE_FILE", this->AutogenTarget.ParseCacheFile);
+ ofs.WriteStrings("AM_HEADERS", headers);
+ ofs.WriteStrings("AM_HEADERS_FLAGS", headersFlags);
+ ofs.WriteStrings("AM_HEADERS_BUILD_PATHS", headersBuildPaths);
+ ofs.WriteStrings("AM_SOURCES", sources);
+ ofs.WriteStrings("AM_SOURCES_FLAGS", sourcesFlags);
+
+ // Write moc settings
+ if (this->Moc.Enabled) {
+ ofs.Write("# MOC settings\n");
+ ofs.WriteStrings("AM_MOC_SKIP", moc_skip);
+ ofs.WriteStrings("AM_MOC_DEFINITIONS", this->Moc.Defines);
+ ofs.WriteConfigStrings("AM_MOC_DEFINITIONS", this->Moc.ConfigDefines);
+ ofs.WriteStrings("AM_MOC_INCLUDES", this->Moc.Includes);
+ ofs.WriteConfigStrings("AM_MOC_INCLUDES", this->Moc.ConfigIncludes);
+ ofs.Write("AM_MOC_OPTIONS",
+ this->Target->GetSafeProperty("AUTOMOC_MOC_OPTIONS"));
+ ofs.Write("AM_MOC_RELAXED_MODE", MfDef("CMAKE_AUTOMOC_RELAXED_MODE"));
+ ofs.Write("AM_MOC_MACRO_NAMES",
+ this->Target->GetSafeProperty("AUTOMOC_MACRO_NAMES"));
+ ofs.Write("AM_MOC_DEPEND_FILTERS",
+ this->Target->GetSafeProperty("AUTOMOC_DEPEND_FILTERS"));
+ ofs.Write("AM_MOC_PREDEFS_CMD", this->Moc.PredefsCmd);
+ }
+
+ // Write uic settings
+ if (this->Uic.Enabled) {
+ // Add skipped .ui files
+ uic_skip.insert(this->Uic.SkipUi.begin(), this->Uic.SkipUi.end());
+
+ ofs.Write("# UIC settings\n");
+ ofs.WriteStrings("AM_UIC_SKIP", uic_skip);
+ ofs.WriteStrings("AM_UIC_TARGET_OPTIONS", this->Uic.Options);
+ ofs.WriteConfigStrings("AM_UIC_TARGET_OPTIONS", this->Uic.ConfigOptions);
+ ofs.WriteStrings("AM_UIC_OPTIONS_FILES", this->Uic.FileFiles);
+ ofs.WriteNestedLists("AM_UIC_OPTIONS_OPTIONS", this->Uic.FileOptions);
+ ofs.WriteStrings("AM_UIC_SEARCH_PATHS", this->Uic.SearchPaths);
+ }
+ } else {
+ std::string err = "AutoGen: Could not write file ";
+ err += this->AutogenTarget.InfoFile;
+ cmSystemTools::Error(err);
+ return false;
+ }
+
+ return true;
+}
+
+bool cmQtAutoGenInitializer::SetupWriteRccInfo()
+{
+ for (Qrc const& qrc : this->Rcc.Qrcs) {
+ InfoWriter ofs(qrc.InfoFile);
+ if (ofs) {
+ // Write
+ ofs.Write("# Configurations\n");
+ ofs.Write("ARCC_MULTI_CONFIG", this->MultiConfig ? "TRUE" : "FALSE");
+ ofs.Write("ARCC_VERBOSITY", this->Verbosity);
+ ofs.Write("# Settings file\n");
+ ofs.Write("ARCC_SETTINGS_FILE", qrc.SettingsFile);
+ ofs.WriteConfig("ARCC_SETTINGS_FILE", qrc.ConfigSettingsFile);
+
+ ofs.Write("# Directories\n");
+ ofs.Write("ARCC_BUILD_DIR", this->Dir.Build);
+ ofs.Write("ARCC_INCLUDE_DIR", this->Dir.Include);
+ ofs.WriteConfig("ARCC_INCLUDE_DIR", this->Dir.ConfigInclude);
+
+ ofs.Write("# Rcc executable\n");
+ ofs.Write("ARCC_RCC_EXECUTABLE", this->Rcc.Executable);
+ ofs.WriteStrings("ARCC_RCC_LIST_OPTIONS",
+ this->Rcc.ExecutableFeatures->ListOptions);
+
+ ofs.Write("# Rcc job\n");
+ ofs.Write("ARCC_LOCK_FILE", qrc.LockFile);
+ ofs.Write("ARCC_SOURCE", qrc.QrcFile);
+ ofs.Write("ARCC_OUTPUT_CHECKSUM", qrc.PathChecksum);
+ ofs.Write("ARCC_OUTPUT_NAME",
+ cmSystemTools::GetFilenameName(qrc.RccFile));
+ ofs.WriteStrings("ARCC_OPTIONS", qrc.Options);
+ ofs.WriteStrings("ARCC_INPUTS", qrc.Resources);
+ } else {
+ std::string err = "AutoRcc: Could not write file ";
+ err += qrc.InfoFile;
+ cmSystemTools::Error(err);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void cmQtAutoGenInitializer::RegisterGeneratedSource(
+ std::string const& filename)
+{
+ cmMakefile* makefile = this->Target->Target->GetMakefile();
+ cmSourceFile* gFile = makefile->GetOrCreateSource(filename, true);
+ gFile->SetProperty("GENERATED", "1");
+ gFile->SetProperty("SKIP_AUTOGEN", "1");
+}
+
+bool cmQtAutoGenInitializer::AddGeneratedSource(std::string const& filename,
+ GenVarsT const& genVars,
+ bool prepend)
+{
+ // Register source at makefile
+ this->RegisterGeneratedSource(filename);
+ // Add source file to target
+ this->Target->AddSource(filename, prepend);
+ // Add source file to source group
+ return this->AddToSourceGroup(filename, genVars.GenNameUpper);
+}
+
+bool cmQtAutoGenInitializer::AddToSourceGroup(std::string const& fileName,
+ std::string const& genNameUpper)
+{
+ cmMakefile* makefile = this->Target->Target->GetMakefile();
+ cmSourceGroup* sourceGroup = nullptr;
+ // Acquire source group
+ {
+ std::string property;
+ std::string groupName;
+ {
+ // Prefer generator specific source group name
+ std::array<std::string, 2> props{ { genNameUpper + "_SOURCE_GROUP",
+ "AUTOGEN_SOURCE_GROUP" } };
+ for (std::string& prop : props) {
+ const char* propName = makefile->GetState()->GetGlobalProperty(prop);
+ if ((propName != nullptr) && (*propName != '\0')) {
+ groupName = propName;
+ property = std::move(prop);
+ break;
+ }
+ }
+ }
+ // Generate a source group on demand
+ if (!groupName.empty()) {
+ sourceGroup = makefile->GetOrCreateSourceGroup(groupName);
+ if (sourceGroup == nullptr) {
+ std::string err;
+ err += genNameUpper;
+ err += " error in ";
+ err += property;
+ err += ": Could not find or create the source group ";
+ err += cmQtAutoGen::Quoted(groupName);
+ cmSystemTools::Error(err);
+ return false;
+ }
+ }
+ }
+ if (sourceGroup != nullptr) {
+ sourceGroup->AddGroupFile(fileName);
+ }
+ return true;
+}
+
+void cmQtAutoGenInitializer::AddCleanFile(std::string const& fileName)
+{
+ Target->Target->AppendProperty("ADDITIONAL_CLEAN_FILES", fileName.c_str(),
+ false);
+}
+
+static unsigned int CharPtrToUInt(const char* const input)
+{
+ unsigned long tmp = 0;
+ if (input != nullptr && cmSystemTools::StringToULong(input, &tmp)) {
+ return static_cast<unsigned int>(tmp);
+ }
+ return 0;
+}
+
+static std::vector<cmQtAutoGen::IntegerVersion> GetKnownQtVersions(
+ cmGeneratorTarget const* target)
+{
+ // Qt version variable prefixes
+ static std::array<std::string, 3> const prefixes{ { "Qt6Core", "Qt5Core",
+ "QT" } };
+
+ std::vector<cmQtAutoGen::IntegerVersion> result;
+ result.reserve(prefixes.size() * 2);
+ // Adds a version to the result (nullptr safe)
+ auto addVersion = [&result](const char* major, const char* minor) {
+ cmQtAutoGen::IntegerVersion ver(CharPtrToUInt(major),
+ CharPtrToUInt(minor));
+ if (ver.Major != 0) {
+ result.emplace_back(ver);
+ }
+ };
+ cmMakefile* makefile = target->Target->GetMakefile();
+
+ // Read versions from variables
+ for (const std::string& prefix : prefixes) {
+ addVersion(makefile->GetDefinition(prefix + "_VERSION_MAJOR"),
+ makefile->GetDefinition(prefix + "_VERSION_MINOR"));
+ }
+
+ // Read versions from directory properties
+ for (const std::string& prefix : prefixes) {
+ addVersion(makefile->GetProperty(prefix + "_VERSION_MAJOR"),
+ makefile->GetProperty(prefix + "_VERSION_MINOR"));
+ }
+
+ return result;
+}
+
+std::pair<cmQtAutoGen::IntegerVersion, unsigned int>
+cmQtAutoGenInitializer::GetQtVersion(cmGeneratorTarget const* target)
+{
+ std::pair<IntegerVersion, unsigned int> res(
+ IntegerVersion(),
+ CharPtrToUInt(target->GetLinkInterfaceDependentStringProperty(
+ "QT_MAJOR_VERSION", "")));
+
+ auto knownQtVersions = GetKnownQtVersions(target);
+ if (!knownQtVersions.empty()) {
+ if (res.second == 0) {
+ // No specific version was requested by the target:
+ // Use highest known Qt version.
+ res.first = knownQtVersions.at(0);
+ } else {
+ // Pick a version from the known versions:
+ for (auto it : knownQtVersions) {
+ if (it.Major == res.second) {
+ res.first = it;
+ break;
+ }
+ }
+ }
+ }
+ return res;
+}
+
+bool cmQtAutoGenInitializer::GetQtExecutable(GenVarsT& genVars,
+ const std::string& executable,
+ bool ignoreMissingTarget) const
+{
+ auto print_err = [this, &genVars](std::string const& err) {
+ std::string msg = genVars.GenNameUpper;
+ msg += " for target ";
+ msg += this->Target->GetName();
+ msg += ": ";
+ msg += err;
+ cmSystemTools::Error(msg);
+ };
+
+ // Custom executable
+ {
+ std::string const prop = genVars.GenNameUpper + "_EXECUTABLE";
+ std::string const val = this->Target->Target->GetSafeProperty(prop);
+ if (!val.empty()) {
+ // Evaluate generator expression
+ {
+ cmListFileBacktrace lfbt =
+ this->Target->Target->GetMakefile()->GetBacktrace();
+ cmGeneratorExpression ge(lfbt);
+ std::unique_ptr<cmCompiledGeneratorExpression> cge = ge.Parse(val);
+ genVars.Executable =
+ cge->Evaluate(this->Target->GetLocalGenerator(), "");
+ }
+ if (genVars.Executable.empty() && !ignoreMissingTarget) {
+ print_err(prop + " evaluates to an empty value");
+ return false;
+ }
+
+ // Create empty compiler features.
+ genVars.ExecutableFeatures =
+ std::make_shared<cmQtAutoGen::CompilerFeatures>();
+ return true;
+ }
+ }
+
+ // Find executable target
+ {
+ // Find executable target name
+ std::string targetName;
+ if (this->QtVersion.Major == 4) {
+ targetName = "Qt4::";
+ } else if (this->QtVersion.Major == 5) {
+ targetName = "Qt5::";
+ } else if (this->QtVersion.Major == 6) {
+ targetName = "Qt6::";
+ }
+ targetName += executable;
+
+ // Find target
+ cmLocalGenerator* localGen = this->Target->GetLocalGenerator();
+ cmGeneratorTarget* target = localGen->FindGeneratorTargetToUse(targetName);
+ if (target != nullptr) {
+ genVars.ExecutableTargetName = targetName;
+ genVars.ExecutableTarget = target;
+ if (target->IsImported()) {
+ genVars.Executable = target->ImportedGetLocation("");
+ } else {
+ genVars.Executable = target->GetLocation("");
+ }
+ } else {
+ if (ignoreMissingTarget) {
+ // Create empty compiler features.
+ genVars.ExecutableFeatures =
+ std::make_shared<cmQtAutoGen::CompilerFeatures>();
+ return true;
+ }
+ std::string err = "Could not find ";
+ err += executable;
+ err += " executable target ";
+ err += targetName;
+ print_err(err);
+ return false;
+ }
+ }
+
+ // Get executable features
+ {
+ std::string err;
+ genVars.ExecutableFeatures = this->GlobalInitializer->GetCompilerFeatures(
+ executable, genVars.Executable, err);
+ if (!genVars.ExecutableFeatures) {
+ print_err(err);
+ return false;
+ }
+ }
+
+ return true;
+}
diff --git a/Source/cmQtAutoGenInitializer.h b/Source/cmQtAutoGenInitializer.h
new file mode 100644
index 000000000..aa073d1fd
--- /dev/null
+++ b/Source/cmQtAutoGenInitializer.h
@@ -0,0 +1,241 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#ifndef cmQtAutoGenInitializer_h
+#define cmQtAutoGenInitializer_h
+
+#include "cmConfigure.h" // IWYU pragma: keep
+#include "cmGeneratedFileStream.h"
+#include "cmQtAutoGen.h"
+
+#include <map>
+#include <memory> // IWYU pragma: keep
+#include <ostream>
+#include <set>
+#include <string>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+class cmGeneratorTarget;
+class cmTarget;
+class cmQtAutoGenGlobalInitializer;
+class cmSourceFile;
+
+/// @brief Initializes the QtAutoGen generators
+class cmQtAutoGenInitializer : public cmQtAutoGen
+{
+public:
+ /// @brief Rcc job information
+ class Qrc
+ {
+ public:
+ std::string LockFile;
+ std::string QrcFile;
+ std::string QrcName;
+ std::string PathChecksum;
+ std::string InfoFile;
+ std::string SettingsFile;
+ std::map<std::string, std::string> ConfigSettingsFile;
+ std::string RccFile;
+ bool Generated = false;
+ bool Unique = false;
+ std::vector<std::string> Options;
+ std::vector<std::string> Resources;
+ };
+
+ /// @brief Moc/Uic file
+ struct MUFile
+ {
+ std::string RealPath;
+ cmSourceFile* SF = nullptr;
+ bool Generated = false;
+ bool SkipMoc = false;
+ bool SkipUic = false;
+ bool MocIt = false;
+ bool UicIt = false;
+ };
+ typedef std::unique_ptr<MUFile> MUFileHandle;
+
+ /// @brief Abstract moc/uic/rcc generator variables base class
+ struct GenVarsT
+ {
+ bool Enabled = false;
+ // Generator type/name
+ GenT Gen;
+ std::string const& GenNameUpper;
+ // Executable
+ std::string ExecutableTargetName;
+ cmGeneratorTarget* ExecutableTarget = nullptr;
+ std::string Executable;
+ CompilerFeaturesHandle ExecutableFeatures;
+
+ /// @brief Constructor
+ GenVarsT(GenT gen)
+ : Gen(gen)
+ , GenNameUpper(cmQtAutoGen::GeneratorNameUpper(gen)){};
+ };
+
+ /// @brief Writes a CMake info file
+ class InfoWriter
+ {
+ public:
+ /// @brief Open the given file
+ InfoWriter(std::string const& filename);
+
+ /// @return True if the file is open
+ explicit operator bool() const { return static_cast<bool>(Ofs_); }
+
+ void Write(const char* text) { Ofs_ << text; }
+ void Write(const char* key, std::string const& value);
+ void WriteUInt(const char* key, unsigned int value);
+
+ template <class C>
+ void WriteStrings(const char* key, C const& container);
+ void WriteConfig(const char* key,
+ std::map<std::string, std::string> const& map);
+ template <class C>
+ void WriteConfigStrings(const char* key,
+ std::map<std::string, C> const& map);
+ void WriteNestedLists(const char* key,
+ std::vector<std::vector<std::string>> const& lists);
+
+ private:
+ template <class IT>
+ static std::string ListJoin(IT it_begin, IT it_end);
+ static std::string ConfigKey(const char* key, std::string const& config);
+
+ private:
+ cmGeneratedFileStream Ofs_;
+ };
+
+public:
+ /// @return The detected Qt version and the required Qt major version
+ static std::pair<IntegerVersion, unsigned int> GetQtVersion(
+ cmGeneratorTarget const* target);
+
+ cmQtAutoGenInitializer(cmQtAutoGenGlobalInitializer* globalInitializer,
+ cmGeneratorTarget* target,
+ IntegerVersion const& qtVersion, bool mocEnabled,
+ bool uicEnabled, bool rccEnabled,
+ bool globalAutogenTarget, bool globalAutoRccTarget);
+
+ bool InitCustomTargets();
+ bool SetupCustomTargets();
+
+private:
+ /// @brief If moc or uic is enabled, the autogen target will be generated
+ bool MocOrUicEnabled() const
+ {
+ return (this->Moc.Enabled || this->Uic.Enabled);
+ }
+
+ bool InitMoc();
+ bool InitUic();
+ bool InitRcc();
+
+ bool InitScanFiles();
+ bool InitAutogenTarget();
+ bool InitRccTargets();
+
+ bool SetupWriteAutogenInfo();
+ bool SetupWriteRccInfo();
+
+ void RegisterGeneratedSource(std::string const& filename);
+ bool AddGeneratedSource(std::string const& filename, GenVarsT const& genVars,
+ bool prepend = false);
+ bool AddToSourceGroup(std::string const& fileName,
+ std::string const& genNameUpper);
+ void AddCleanFile(std::string const& fileName);
+
+ bool GetQtExecutable(GenVarsT& genVars, const std::string& executable,
+ bool ignoreMissingTarget) const;
+
+private:
+ cmQtAutoGenGlobalInitializer* GlobalInitializer;
+ cmGeneratorTarget* Target;
+
+ // Configuration
+ IntegerVersion QtVersion;
+ bool MultiConfig = false;
+ std::string ConfigDefault;
+ std::vector<std::string> ConfigsList;
+ std::string Verbosity;
+ std::string TargetsFolder;
+ bool CMP0071Accept = false;
+ bool CMP0071Warn = false;
+
+ /// @brief Common directories
+ struct
+ {
+ std::string Info;
+ std::string Build;
+ std::string Work;
+ std::string Include;
+ std::map<std::string, std::string> ConfigInclude;
+ } Dir;
+
+ /// @brief Autogen target variables
+ struct
+ {
+ std::string Name;
+ bool GlobalTarget = false;
+ // Settings
+ std::string Parallel;
+ // Configuration files
+ std::string InfoFile;
+ std::string SettingsFile;
+ std::string ParseCacheFile;
+ std::map<std::string, std::string> ConfigSettingsFile;
+ // Dependencies
+ bool DependOrigin = false;
+ std::set<std::string> DependFiles;
+ std::set<cmTarget*> DependTargets;
+ // Sources to process
+ std::unordered_map<cmSourceFile*, MUFileHandle> Headers;
+ std::unordered_map<cmSourceFile*, MUFileHandle> Sources;
+ std::vector<MUFile*> FilesGenerated;
+ } AutogenTarget;
+
+ /// @brief Moc only variables
+ struct MocT : public GenVarsT
+ {
+ std::string PredefsCmd;
+ std::vector<std::string> Includes;
+ std::map<std::string, std::vector<std::string>> ConfigIncludes;
+ std::set<std::string> Defines;
+ std::map<std::string, std::set<std::string>> ConfigDefines;
+ std::string MocsCompilation;
+
+ /// @brief Constructor
+ MocT()
+ : GenVarsT(GenT::MOC){};
+ } Moc;
+
+ /// @brief Uic only variables
+ struct UicT : public GenVarsT
+ {
+ std::set<std::string> SkipUi;
+ std::vector<std::string> SearchPaths;
+ std::vector<std::string> Options;
+ std::map<std::string, std::vector<std::string>> ConfigOptions;
+ std::vector<std::string> FileFiles;
+ std::vector<std::vector<std::string>> FileOptions;
+
+ /// @brief Constructor
+ UicT()
+ : GenVarsT(GenT::UIC){};
+ } Uic;
+
+ /// @brief Rcc only variables
+ struct RccT : public GenVarsT
+ {
+ bool GlobalTarget = false;
+ std::vector<Qrc> Qrcs;
+
+ /// @brief Constructor
+ RccT()
+ : GenVarsT(GenT::RCC){};
+ } Rcc;
+};
+
+#endif
diff --git a/Source/cmQtAutoGenerator.cxx b/Source/cmQtAutoGenerator.cxx
new file mode 100644
index 000000000..e1c435be0
--- /dev/null
+++ b/Source/cmQtAutoGenerator.cxx
@@ -0,0 +1,332 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmQtAutoGenerator.h"
+#include "cmQtAutoGen.h"
+
+#include "cmsys/FStream.hxx"
+
+#include "cmAlgorithms.h"
+#include "cmGlobalGenerator.h"
+#include "cmMakefile.h"
+#include "cmState.h"
+#include "cmStateDirectory.h"
+#include "cmStateSnapshot.h"
+#include "cmSystemTools.h"
+#include "cmake.h"
+
+cmQtAutoGenerator::Logger::Logger()
+{
+ // Initialize logger
+ {
+ std::string verbose;
+ if (cmSystemTools::GetEnv("VERBOSE", verbose) && !verbose.empty()) {
+ unsigned long iVerbose = 0;
+ if (cmSystemTools::StringToULong(verbose.c_str(), &iVerbose)) {
+ SetVerbosity(static_cast<unsigned int>(iVerbose));
+ } else {
+ // Non numeric verbosity
+ SetVerbose(cmSystemTools::IsOn(verbose));
+ }
+ }
+ }
+ {
+ std::string colorEnv;
+ cmSystemTools::GetEnv("COLOR", colorEnv);
+ if (!colorEnv.empty()) {
+ SetColorOutput(cmSystemTools::IsOn(colorEnv));
+ } else {
+ SetColorOutput(true);
+ }
+ }
+}
+
+cmQtAutoGenerator::Logger::~Logger() = default;
+
+void cmQtAutoGenerator::Logger::RaiseVerbosity(std::string const& value)
+{
+ unsigned long verbosity = 0;
+ if (cmSystemTools::StringToULong(value.c_str(), &verbosity)) {
+ if (this->Verbosity_ < verbosity) {
+ this->Verbosity_ = static_cast<unsigned int>(verbosity);
+ }
+ }
+}
+
+void cmQtAutoGenerator::Logger::SetColorOutput(bool value)
+{
+ ColorOutput_ = value;
+}
+
+std::string cmQtAutoGenerator::Logger::HeadLine(std::string const& title)
+{
+ std::string head = title;
+ head += '\n';
+ head.append(head.size() - 1, '-');
+ head += '\n';
+ return head;
+}
+
+void cmQtAutoGenerator::Logger::Info(GenT genType,
+ std::string const& message) const
+{
+ std::string msg = GeneratorName(genType);
+ msg += ": ";
+ msg += message;
+ if (msg.back() != '\n') {
+ msg.push_back('\n');
+ }
+ {
+ std::lock_guard<std::mutex> lock(Mutex_);
+ cmSystemTools::Stdout(msg);
+ }
+}
+
+void cmQtAutoGenerator::Logger::Warning(GenT genType,
+ std::string const& message) const
+{
+ std::string msg;
+ if (message.find('\n') == std::string::npos) {
+ // Single line message
+ msg += GeneratorName(genType);
+ msg += " warning: ";
+ } else {
+ // Multi line message
+ msg += HeadLine(GeneratorName(genType) + " warning");
+ }
+ // Message
+ msg += message;
+ if (msg.back() != '\n') {
+ msg.push_back('\n');
+ }
+ msg.push_back('\n');
+ {
+ std::lock_guard<std::mutex> lock(Mutex_);
+ cmSystemTools::Stdout(msg);
+ }
+}
+
+void cmQtAutoGenerator::Logger::WarningFile(GenT genType,
+ std::string const& filename,
+ std::string const& message) const
+{
+ std::string msg = " ";
+ msg += Quoted(filename);
+ msg.push_back('\n');
+ // Message
+ msg += message;
+ Warning(genType, msg);
+}
+
+void cmQtAutoGenerator::Logger::Error(GenT genType,
+ std::string const& message) const
+{
+ std::string msg;
+ msg += HeadLine(GeneratorName(genType) + " error");
+ // Message
+ msg += message;
+ if (msg.back() != '\n') {
+ msg.push_back('\n');
+ }
+ msg.push_back('\n');
+ {
+ std::lock_guard<std::mutex> lock(Mutex_);
+ cmSystemTools::Stderr(msg);
+ }
+}
+
+void cmQtAutoGenerator::Logger::ErrorFile(GenT genType,
+ std::string const& filename,
+ std::string const& message) const
+{
+ std::string emsg = " ";
+ emsg += Quoted(filename);
+ emsg += '\n';
+ // Message
+ emsg += message;
+ Error(genType, emsg);
+}
+
+void cmQtAutoGenerator::Logger::ErrorCommand(
+ GenT genType, std::string const& message,
+ std::vector<std::string> const& command, std::string const& output) const
+{
+ std::string msg;
+ msg.push_back('\n');
+ msg += HeadLine(GeneratorName(genType) + " subprocess error");
+ msg += message;
+ if (msg.back() != '\n') {
+ msg.push_back('\n');
+ }
+ msg.push_back('\n');
+ msg += HeadLine("Command");
+ msg += QuotedCommand(command);
+ if (msg.back() != '\n') {
+ msg.push_back('\n');
+ }
+ msg.push_back('\n');
+ msg += HeadLine("Output");
+ msg += output;
+ if (msg.back() != '\n') {
+ msg.push_back('\n');
+ }
+ msg.push_back('\n');
+ {
+ std::lock_guard<std::mutex> lock(Mutex_);
+ cmSystemTools::Stderr(msg);
+ }
+}
+
+bool cmQtAutoGenerator::MakeParentDirectory(std::string const& filename)
+{
+ bool success = true;
+ std::string const dirName = cmSystemTools::GetFilenamePath(filename);
+ if (!dirName.empty()) {
+ success = cmSystemTools::MakeDirectory(dirName);
+ }
+ return success;
+}
+
+bool cmQtAutoGenerator::FileRead(std::string& content,
+ std::string const& filename,
+ std::string* error)
+{
+ content.clear();
+ if (!cmSystemTools::FileExists(filename, true)) {
+ if (error != nullptr) {
+ *error = "Not a file.";
+ }
+ return false;
+ }
+
+ unsigned long const length = cmSystemTools::FileLength(filename);
+ cmsys::ifstream ifs(filename.c_str(), (std::ios::in | std::ios::binary));
+
+ // Use lambda to save destructor calls of ifs
+ return [&ifs, length, &content, error]() -> bool {
+ if (!ifs) {
+ if (error != nullptr) {
+ *error = "Opening the file for reading failed.";
+ }
+ return false;
+ }
+ content.reserve(length);
+ typedef std::istreambuf_iterator<char> IsIt;
+ content.assign(IsIt{ ifs }, IsIt{});
+ if (!ifs) {
+ content.clear();
+ if (error != nullptr) {
+ *error = "Reading from the file failed.";
+ }
+ return false;
+ }
+ return true;
+ }();
+}
+
+bool cmQtAutoGenerator::FileWrite(std::string const& filename,
+ std::string const& content,
+ std::string* error)
+{
+ // Make sure the parent directory exists
+ if (!cmQtAutoGenerator::MakeParentDirectory(filename)) {
+ if (error != nullptr) {
+ *error = "Could not create parent directory.";
+ }
+ return false;
+ }
+ cmsys::ofstream ofs;
+ ofs.open(filename.c_str(),
+ (std::ios::out | std::ios::binary | std::ios::trunc));
+
+ // Use lambda to save destructor calls of ofs
+ return [&ofs, &content, error]() -> bool {
+ if (!ofs) {
+ if (error != nullptr) {
+ *error = "Opening file for writing failed.";
+ }
+ return false;
+ }
+ ofs << content;
+ if (!ofs.good()) {
+ if (error != nullptr) {
+ *error = "File writing failed.";
+ }
+ return false;
+ }
+ return true;
+ }();
+}
+
+bool cmQtAutoGenerator::FileDiffers(std::string const& filename,
+ std::string const& content)
+{
+ bool differs = true;
+ std::string oldContents;
+ if (FileRead(oldContents, filename) && (oldContents == content)) {
+ differs = false;
+ }
+ return differs;
+}
+
+cmQtAutoGenerator::cmQtAutoGenerator() = default;
+
+cmQtAutoGenerator::~cmQtAutoGenerator() = default;
+
+bool cmQtAutoGenerator::Run(std::string const& infoFile,
+ std::string const& config)
+{
+ // Info settings
+ InfoFile_ = infoFile;
+ cmSystemTools::ConvertToUnixSlashes(InfoFile_);
+ if (!InfoFileTime_.Load(InfoFile_)) {
+ std::string msg = "AutoGen: The info file ";
+ msg += Quoted(InfoFile_);
+ msg += " is not readable\n";
+ cmSystemTools::Stderr(msg);
+ return false;
+ }
+ InfoDir_ = cmSystemTools::GetFilenamePath(infoFile);
+ InfoConfig_ = config;
+
+ bool success = false;
+ {
+ cmake cm(cmake::RoleScript, cmState::Unknown);
+ cm.SetHomeOutputDirectory(InfoDir());
+ cm.SetHomeDirectory(InfoDir());
+ cm.GetCurrentSnapshot().SetDefaultDefinitions();
+ cmGlobalGenerator gg(&cm);
+
+ cmStateSnapshot snapshot = cm.GetCurrentSnapshot();
+ snapshot.GetDirectory().SetCurrentBinary(InfoDir());
+ snapshot.GetDirectory().SetCurrentSource(InfoDir());
+
+ auto makefile = cm::make_unique<cmMakefile>(&gg, snapshot);
+ // The OLD/WARN behavior for policy CMP0053 caused a speed regression.
+ // https://gitlab.kitware.com/cmake/cmake/issues/17570
+ makefile->SetPolicyVersion("3.9", std::string());
+ gg.SetCurrentMakefile(makefile.get());
+ success = this->Init(makefile.get());
+ }
+ if (success) {
+ success = this->Process();
+ }
+ return success;
+}
+
+std::string cmQtAutoGenerator::SettingsFind(std::string const& content,
+ const char* key)
+{
+ std::string prefix(key);
+ prefix += ':';
+ std::string::size_type pos = content.find(prefix);
+ if (pos != std::string::npos) {
+ pos += prefix.size();
+ if (pos < content.size()) {
+ std::string::size_type posE = content.find('\n', pos);
+ if ((posE != std::string::npos) && (posE != pos)) {
+ return content.substr(pos, posE - pos);
+ }
+ }
+ }
+ return std::string();
+}
diff --git a/Source/cmQtAutoGenerator.h b/Source/cmQtAutoGenerator.h
new file mode 100644
index 000000000..ff4c4c942
--- /dev/null
+++ b/Source/cmQtAutoGenerator.h
@@ -0,0 +1,109 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#ifndef cmQtAutoGenerator_h
+#define cmQtAutoGenerator_h
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include "cmFileTime.h"
+#include "cmQtAutoGen.h"
+
+#include <mutex>
+#include <string>
+#include <vector>
+
+class cmMakefile;
+
+/** \class cmQtAutoGenerator
+ * \brief Base class for QtAutoGen generators
+ */
+class cmQtAutoGenerator : public cmQtAutoGen
+{
+public:
+ // -- Types
+
+ /**
+ * Thread safe logger
+ */
+ class Logger
+ {
+ public:
+ // -- Construction
+ Logger();
+ ~Logger();
+ // -- Verbosity
+ unsigned int Verbosity() const { return this->Verbosity_; }
+ void SetVerbosity(unsigned int value) { this->Verbosity_ = value; }
+ void RaiseVerbosity(std::string const& value);
+ bool Verbose() const { return (this->Verbosity_ != 0); }
+ void SetVerbose(bool value) { this->Verbosity_ = value ? 1 : 0; }
+ // -- Color output
+ bool ColorOutput() const { return this->ColorOutput_; }
+ void SetColorOutput(bool value);
+ // -- Log info
+ void Info(GenT genType, std::string const& message) const;
+ // -- Log warning
+ void Warning(GenT genType, std::string const& message) const;
+ void WarningFile(GenT genType, std::string const& filename,
+ std::string const& message) const;
+ // -- Log error
+ void Error(GenT genType, std::string const& message) const;
+ void ErrorFile(GenT genType, std::string const& filename,
+ std::string const& message) const;
+ void ErrorCommand(GenT genType, std::string const& message,
+ std::vector<std::string> const& command,
+ std::string const& output) const;
+
+ private:
+ static std::string HeadLine(std::string const& title);
+
+ private:
+ mutable std::mutex Mutex_;
+ unsigned int Verbosity_ = 0;
+ bool ColorOutput_ = false;
+ };
+
+ // -- File system methods
+ static bool MakeParentDirectory(std::string const& filename);
+ static bool FileRead(std::string& content, std::string const& filename,
+ std::string* error = nullptr);
+ static bool FileWrite(std::string const& filename,
+ std::string const& content,
+ std::string* error = nullptr);
+ static bool FileDiffers(std::string const& filename,
+ std::string const& content);
+
+public:
+ // -- Constructors
+ cmQtAutoGenerator();
+ virtual ~cmQtAutoGenerator();
+
+ cmQtAutoGenerator(cmQtAutoGenerator const&) = delete;
+ cmQtAutoGenerator& operator=(cmQtAutoGenerator const&) = delete;
+
+ // -- Run
+ bool Run(std::string const& infoFile, std::string const& config);
+
+ // -- InfoFile
+ std::string const& InfoFile() const { return InfoFile_; }
+ cmFileTime const& InfoFileTime() const { return InfoFileTime_; }
+ std::string const& InfoDir() const { return InfoDir_; }
+ std::string const& InfoConfig() const { return InfoConfig_; }
+
+ // -- Utility
+ static std::string SettingsFind(std::string const& content, const char* key);
+
+protected:
+ // -- Abstract processing interface
+ virtual bool Init(cmMakefile* makefile) = 0;
+ virtual bool Process() = 0;
+
+private:
+ // -- Info settings
+ std::string InfoFile_;
+ cmFileTime InfoFileTime_;
+ std::string InfoDir_;
+ std::string InfoConfig_;
+};
+
+#endif
diff --git a/Source/cmQtAutoMocUic.cxx b/Source/cmQtAutoMocUic.cxx
new file mode 100644
index 000000000..641d8aa3f
--- /dev/null
+++ b/Source/cmQtAutoMocUic.cxx
@@ -0,0 +1,2193 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmQtAutoMocUic.h"
+
+#include <algorithm>
+#include <array>
+#include <list>
+#include <memory>
+#include <set>
+#include <sstream>
+#include <utility>
+
+#include "cmAlgorithms.h"
+#include "cmCryptoHash.h"
+#include "cmGeneratedFileStream.h"
+#include "cmMakefile.h"
+#include "cmQtAutoGen.h"
+#include "cmSystemTools.h"
+#include "cmake.h"
+#include "cmsys/FStream.hxx"
+
+#if defined(__APPLE__)
+# include <unistd.h>
+#endif
+
+static constexpr std::size_t MocUnderscoreLength = 4; // Length of "moc_"
+static constexpr std::size_t UiUnderscoreLength = 3; // Length of "ui_"
+
+cmQtAutoMocUic::IncludeKeyT::IncludeKeyT(std::string const& key,
+ std::size_t basePrefixLength)
+ : Key(key)
+ , Dir(SubDirPrefix(key))
+ , Base(cmSystemTools::GetFilenameWithoutLastExtension(key))
+{
+ if (basePrefixLength != 0) {
+ Base = Base.substr(basePrefixLength);
+ }
+}
+
+void cmQtAutoMocUic::ParseCacheT::FileT::Clear()
+{
+ Moc.Macro.clear();
+ Moc.Include.Underscore.clear();
+ Moc.Include.Dot.clear();
+ Moc.Depends.clear();
+
+ Uic.Include.clear();
+ Uic.Depends.clear();
+}
+
+cmQtAutoMocUic::ParseCacheT::FileHandleT cmQtAutoMocUic::ParseCacheT::Get(
+ std::string const& fileName) const
+{
+ auto it = Map_.find(fileName);
+ if (it != Map_.end()) {
+ return it->second;
+ }
+ return FileHandleT();
+}
+
+cmQtAutoMocUic::ParseCacheT::GetOrInsertT
+cmQtAutoMocUic::ParseCacheT::GetOrInsert(std::string const& fileName)
+{
+ // Find existing entry
+ {
+ auto it = Map_.find(fileName);
+ if (it != Map_.end()) {
+ return GetOrInsertT{ it->second, false };
+ }
+ }
+
+ // Insert new entry
+ return GetOrInsertT{
+ Map_.emplace(fileName, std::make_shared<FileT>()).first->second, true
+ };
+}
+
+cmQtAutoMocUic::ParseCacheT::ParseCacheT() = default;
+cmQtAutoMocUic::ParseCacheT::~ParseCacheT() = default;
+
+void cmQtAutoMocUic::ParseCacheT::Clear()
+{
+ Map_.clear();
+}
+
+bool cmQtAutoMocUic::ParseCacheT::ReadFromFile(std::string const& fileName)
+{
+ cmsys::ifstream fin(fileName.c_str());
+ if (!fin) {
+ return false;
+ }
+ FileHandleT fileHandle;
+
+ std::string line;
+ while (std::getline(fin, line)) {
+ // Check if this an empty or a comment line
+ if (line.empty() || line.front() == '#') {
+ continue;
+ }
+ // Drop carriage return character at the end
+ if (line.back() == '\r') {
+ line.pop_back();
+ if (line.empty()) {
+ continue;
+ }
+ }
+ // Check if this a file name line
+ if (line.front() != ' ') {
+ fileHandle = GetOrInsert(line).first;
+ continue;
+ }
+
+ // Bad line or bad file handle
+ if (!fileHandle || (line.size() < 6)) {
+ continue;
+ }
+
+ constexpr std::size_t offset = 5;
+ if (cmHasLiteralPrefix(line, " mmc:")) {
+ fileHandle->Moc.Macro = line.substr(offset);
+ continue;
+ }
+ if (cmHasLiteralPrefix(line, " miu:")) {
+ fileHandle->Moc.Include.Underscore.emplace_back(line.substr(offset),
+ MocUnderscoreLength);
+ continue;
+ }
+ if (cmHasLiteralPrefix(line, " mid:")) {
+ fileHandle->Moc.Include.Dot.emplace_back(line.substr(offset), 0);
+ continue;
+ }
+ if (cmHasLiteralPrefix(line, " mdp:")) {
+ fileHandle->Moc.Depends.emplace_back(line.substr(offset));
+ continue;
+ }
+ if (cmHasLiteralPrefix(line, " uic:")) {
+ fileHandle->Uic.Include.emplace_back(line.substr(offset),
+ UiUnderscoreLength);
+ continue;
+ }
+ if (cmHasLiteralPrefix(line, " udp:")) {
+ fileHandle->Uic.Depends.emplace_back(line.substr(offset));
+ continue;
+ }
+ }
+ return true;
+}
+
+bool cmQtAutoMocUic::ParseCacheT::WriteToFile(std::string const& fileName)
+{
+ cmGeneratedFileStream ofs(fileName);
+ if (!ofs) {
+ return false;
+ }
+ ofs << "# Generated by CMake. Changes will be overwritten." << std::endl;
+ for (auto const& pair : Map_) {
+ ofs << pair.first << std::endl;
+ FileT const& file = *pair.second;
+ if (!file.Moc.Macro.empty()) {
+ ofs << " mmc:" << file.Moc.Macro << std::endl;
+ }
+ for (IncludeKeyT const& item : file.Moc.Include.Underscore) {
+ ofs << " miu:" << item.Key << std::endl;
+ }
+ for (IncludeKeyT const& item : file.Moc.Include.Dot) {
+ ofs << " mid:" << item.Key << std::endl;
+ }
+ for (std::string const& item : file.Moc.Depends) {
+ ofs << " mdp:" << item << std::endl;
+ }
+ for (IncludeKeyT const& item : file.Uic.Include) {
+ ofs << " uic:" << item.Key << std::endl;
+ }
+ for (std::string const& item : file.Uic.Depends) {
+ ofs << " udp:" << item << std::endl;
+ }
+ }
+ return ofs.Close();
+}
+
+cmQtAutoMocUic::BaseSettingsT::BaseSettingsT() = default;
+cmQtAutoMocUic::BaseSettingsT::~BaseSettingsT() = default;
+
+cmQtAutoMocUic::MocSettingsT::MocSettingsT()
+{
+ RegExpInclude.compile(
+ "(^|\n)[ \t]*#[ \t]*include[ \t]+"
+ "[\"<](([^ \">]+/)?moc_[^ \">/]+\\.cpp|[^ \">]+\\.moc)[\">]");
+}
+
+cmQtAutoMocUic::MocSettingsT::~MocSettingsT() = default;
+
+bool cmQtAutoMocUic::MocSettingsT::skipped(std::string const& fileName) const
+{
+ return (!Enabled || (SkipList.find(fileName) != SkipList.end()));
+}
+
+std::string cmQtAutoMocUic::MocSettingsT::MacrosString() const
+{
+ std::string res;
+ const auto itB = MacroFilters.cbegin();
+ const auto itE = MacroFilters.cend();
+ const auto itL = itE - 1;
+ auto itC = itB;
+ for (; itC != itE; ++itC) {
+ // Separator
+ if (itC != itB) {
+ if (itC != itL) {
+ res += ", ";
+ } else {
+ res += " or ";
+ }
+ }
+ // Key
+ res += itC->Key;
+ }
+ return res;
+}
+
+cmQtAutoMocUic::UicSettingsT::UicSettingsT()
+{
+ RegExpInclude.compile("(^|\n)[ \t]*#[ \t]*include[ \t]+"
+ "[\"<](([^ \">]+/)?ui_[^ \">/]+\\.h)[\">]");
+}
+
+cmQtAutoMocUic::UicSettingsT::~UicSettingsT() = default;
+
+bool cmQtAutoMocUic::UicSettingsT::skipped(std::string const& fileName) const
+{
+ return (!Enabled || (SkipList.find(fileName) != SkipList.end()));
+}
+
+void cmQtAutoMocUic::JobT::LogError(GenT genType,
+ std::string const& message) const
+{
+ Gen()->AbortError();
+ Gen()->Log().Error(genType, message);
+}
+
+void cmQtAutoMocUic::JobT::LogFileError(GenT genType,
+ std::string const& filename,
+ std::string const& message) const
+{
+ Gen()->AbortError();
+ Gen()->Log().ErrorFile(genType, filename, message);
+}
+
+void cmQtAutoMocUic::JobT::LogCommandError(
+ GenT genType, std::string const& message,
+ std::vector<std::string> const& command, std::string const& output) const
+{
+ Gen()->AbortError();
+ Gen()->Log().ErrorCommand(genType, message, command, output);
+}
+
+bool cmQtAutoMocUic::JobT::RunProcess(GenT genType,
+ cmWorkerPool::ProcessResultT& result,
+ std::vector<std::string> const& command,
+ std::string* infoMessage)
+{
+ // Log command
+ if (Log().Verbose()) {
+ std::string msg;
+ if ((infoMessage != nullptr) && !infoMessage->empty()) {
+ msg = *infoMessage;
+ if (msg.back() != '\n') {
+ msg += '\n';
+ }
+ }
+ msg += QuotedCommand(command);
+ msg += '\n';
+ Log().Info(genType, msg);
+ }
+ return cmWorkerPool::JobT::RunProcess(result, command,
+ BaseConst().AutogenBuildDir);
+}
+
+void cmQtAutoMocUic::JobMocPredefsT::Process()
+{
+ // (Re)generate moc_predefs.h on demand
+ std::unique_ptr<std::string> reason;
+ if (Log().Verbose()) {
+ reason = cm::make_unique<std::string>();
+ }
+ if (!Update(reason.get())) {
+ return;
+ }
+ std::string const& predefsFileRel = MocConst().PredefsFileRel;
+ std::string const& predefsFileAbs = MocConst().PredefsFileAbs;
+ {
+ cmWorkerPool::ProcessResultT result;
+ {
+ // Compose command
+ std::vector<std::string> cmd = MocConst().PredefsCmd;
+ // Add includes
+ cmAppend(cmd, MocConst().Includes);
+ // Add definitions
+ for (std::string const& def : MocConst().Definitions) {
+ cmd.emplace_back("-D" + def);
+ }
+ // Execute command
+ if (!RunProcess(GenT::MOC, result, cmd, reason.get())) {
+ std::string msg = "The content generation command for ";
+ msg += Quoted(predefsFileRel);
+ msg += " failed.\n";
+ msg += result.ErrorMessage;
+ LogCommandError(GenT::MOC, msg, cmd, result.StdOut);
+ return;
+ }
+ }
+
+ // (Re)write predefs file only on demand
+ if (cmQtAutoGenerator::FileDiffers(predefsFileAbs, result.StdOut)) {
+ if (!cmQtAutoGenerator::FileWrite(predefsFileAbs, result.StdOut)) {
+ std::string msg = "Writing ";
+ msg += Quoted(predefsFileRel);
+ msg += " failed.";
+ LogFileError(GenT::MOC, predefsFileAbs, msg);
+ return;
+ }
+ } else {
+ // Touch to update the time stamp
+ if (Log().Verbose()) {
+ Log().Info(GenT::MOC, "Touching " + Quoted(predefsFileRel));
+ }
+ if (!cmSystemTools::Touch(predefsFileAbs, false)) {
+ std::string msg = "Touching ";
+ msg += Quoted(predefsFileAbs);
+ msg += " failed.";
+ LogFileError(GenT::MOC, predefsFileAbs, msg);
+ return;
+ }
+ }
+ }
+
+ // Read file time afterwards
+ if (!MocEval().PredefsTime.Load(predefsFileAbs)) {
+ LogFileError(GenT::MOC, predefsFileAbs, "File time reading failed.");
+ return;
+ }
+}
+
+bool cmQtAutoMocUic::JobMocPredefsT::Update(std::string* reason) const
+{
+ // Test if the file exists
+ if (!MocEval().PredefsTime.Load(MocConst().PredefsFileAbs)) {
+ if (reason != nullptr) {
+ *reason = "Generating ";
+ *reason += Quoted(MocConst().PredefsFileRel);
+ *reason += ", because it doesn't exist.";
+ }
+ return true;
+ }
+
+ // Test if the settings changed
+ if (MocConst().SettingsChanged) {
+ if (reason != nullptr) {
+ *reason = "Generating ";
+ *reason += Quoted(MocConst().PredefsFileRel);
+ *reason += ", because the moc settings changed.";
+ }
+ return true;
+ }
+
+ // Test if the executable is newer
+ {
+ std::string const& exec = MocConst().PredefsCmd.at(0);
+ cmFileTime execTime;
+ if (execTime.Load(exec)) {
+ if (MocEval().PredefsTime.Older(execTime)) {
+ if (reason != nullptr) {
+ *reason = "Generating ";
+ *reason += Quoted(MocConst().PredefsFileRel);
+ *reason += " because it is older than ";
+ *reason += Quoted(exec);
+ *reason += ".";
+ }
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+bool cmQtAutoMocUic::JobParseT::ReadFile()
+{
+ // Clear old parse information
+ FileHandle->ParseData->Clear();
+ std::string const& fileName = FileHandle->FileName;
+ // Write info
+ if (Log().Verbose()) {
+ Log().Info(GenT::GEN, "Parsing " + Quoted(fileName));
+ }
+ // Read file content
+ {
+ std::string error;
+ if (!cmQtAutoGenerator::FileRead(Content, fileName, &error)) {
+ LogFileError(GenT::GEN, fileName, "Could not read the file: " + error);
+ return false;
+ }
+ }
+ // Warn if empty
+ if (Content.empty()) {
+ Log().WarningFile(GenT::GEN, fileName, "The file is empty.");
+ return false;
+ }
+ return true;
+}
+
+void cmQtAutoMocUic::JobParseT::CreateKeys(std::vector<IncludeKeyT>& container,
+ std::set<std::string> const& source,
+ std::size_t basePrefixLength)
+{
+ if (source.empty()) {
+ return;
+ }
+ container.reserve(source.size());
+ for (std::string const& src : source) {
+ container.emplace_back(src, basePrefixLength);
+ }
+}
+
+void cmQtAutoMocUic::JobParseT::MocMacro()
+{
+ for (KeyExpT const& filter : MocConst().MacroFilters) {
+ // Run a simple find string check
+ if (Content.find(filter.Key) == std::string::npos) {
+ continue;
+ }
+ // Run the expensive regular expression check loop
+ cmsys::RegularExpressionMatch match;
+ if (filter.Exp.find(Content.c_str(), match)) {
+ // Keep detected macro name
+ FileHandle->ParseData->Moc.Macro = filter.Key;
+ return;
+ }
+ }
+}
+
+void cmQtAutoMocUic::JobParseT::MocDependecies()
+{
+ if (MocConst().DependFilters.empty()) {
+ return;
+ }
+
+ // Find dependency strings
+ std::set<std::string> parseDepends;
+ for (KeyExpT const& filter : MocConst().DependFilters) {
+ // Run a simple find string check
+ if (Content.find(filter.Key) == std::string::npos) {
+ continue;
+ }
+ // Run the expensive regular expression check loop
+ const char* contentChars = Content.c_str();
+ cmsys::RegularExpressionMatch match;
+ while (filter.Exp.find(contentChars, match)) {
+ {
+ std::string dep = match.match(1);
+ if (!dep.empty()) {
+ parseDepends.emplace(std::move(dep));
+ }
+ }
+ contentChars += match.end();
+ }
+ }
+
+ // Store dependency strings
+ {
+ auto& Depends = FileHandle->ParseData->Moc.Depends;
+ Depends.reserve(parseDepends.size());
+ for (std::string const& item : parseDepends) {
+ Depends.emplace_back(item);
+ // Replace end of line characters in filenames
+ std::string& path = Depends.back();
+ std::replace(path.begin(), path.end(), '\n', ' ');
+ std::replace(path.begin(), path.end(), '\r', ' ');
+ }
+ }
+}
+
+void cmQtAutoMocUic::JobParseT::MocIncludes()
+{
+ if (Content.find("moc") == std::string::npos) {
+ return;
+ }
+
+ std::set<std::string> underscore;
+ std::set<std::string> dot;
+ {
+ const char* contentChars = Content.c_str();
+ cmsys::RegularExpression const& regExp = MocConst().RegExpInclude;
+ cmsys::RegularExpressionMatch match;
+ while (regExp.find(contentChars, match)) {
+ std::string incString = match.match(2);
+ std::string const incBase =
+ cmSystemTools::GetFilenameWithoutLastExtension(incString);
+ if (cmHasLiteralPrefix(incBase, "moc_")) {
+ // moc_<BASE>.cpp
+ // Remove the moc_ part from the base name
+ underscore.emplace(std::move(incString));
+ } else {
+ // <BASE>.moc
+ dot.emplace(std::move(incString));
+ }
+ // Forward content pointer
+ contentChars += match.end();
+ }
+ }
+ auto& Include = FileHandle->ParseData->Moc.Include;
+ CreateKeys(Include.Underscore, underscore, MocUnderscoreLength);
+ CreateKeys(Include.Dot, dot, 0);
+}
+
+void cmQtAutoMocUic::JobParseT::UicIncludes()
+{
+ if (Content.find("ui_") == std::string::npos) {
+ return;
+ }
+
+ std::set<std::string> includes;
+ {
+ const char* contentChars = Content.c_str();
+ cmsys::RegularExpression const& regExp = UicConst().RegExpInclude;
+ cmsys::RegularExpressionMatch match;
+ while (regExp.find(contentChars, match)) {
+ includes.emplace(match.match(2));
+ // Forward content pointer
+ contentChars += match.end();
+ }
+ }
+ CreateKeys(FileHandle->ParseData->Uic.Include, includes, UiUnderscoreLength);
+}
+
+void cmQtAutoMocUic::JobParseHeaderT::Process()
+{
+ if (!ReadFile()) {
+ return;
+ }
+ // Moc parsing
+ if (FileHandle->Moc) {
+ MocMacro();
+ MocDependecies();
+ }
+ // Uic parsing
+ if (FileHandle->Uic) {
+ UicIncludes();
+ }
+}
+
+void cmQtAutoMocUic::JobParseSourceT::Process()
+{
+ if (!ReadFile()) {
+ return;
+ }
+ // Moc parsing
+ if (FileHandle->Moc) {
+ MocMacro();
+ MocDependecies();
+ MocIncludes();
+ }
+ // Uic parsing
+ if (FileHandle->Uic) {
+ UicIncludes();
+ }
+}
+
+void cmQtAutoMocUic::JobEvaluateT::Process()
+{
+ // Evaluate for moc
+ if (MocConst().Enabled) {
+ // Evaluate headers
+ for (auto const& pair : BaseEval().Headers) {
+ if (!MocEvalHeader(pair.second)) {
+ return;
+ }
+ }
+ // Evaluate sources
+ for (auto const& pair : BaseEval().Sources) {
+ if (!MocEvalSource(pair.second)) {
+ return;
+ }
+ }
+ }
+ // Evaluate for uic
+ if (UicConst().Enabled) {
+ if (!UicEval(BaseEval().Headers) || !UicEval(BaseEval().Sources)) {
+ return;
+ }
+ }
+
+ // Add discovered header parse jobs
+ Gen()->CreateParseJobs<JobParseHeaderT>(MocEval().HeadersDiscovered);
+ // Add generate job after
+ Gen()->WorkerPool().EmplaceJob<JobGenerateT>();
+}
+
+bool cmQtAutoMocUic::JobEvaluateT::MocEvalHeader(SourceFileHandleT source)
+{
+ SourceFileT const& sourceFile = *source;
+ auto const& parseData = sourceFile.ParseData->Moc;
+ if (!source->Moc) {
+ return true;
+ }
+
+ if (!parseData.Macro.empty()) {
+ // Create a new mapping
+ MappingHandleT handle = std::make_shared<MappingT>();
+ handle->SourceFile = std::move(source);
+
+ // Absolute build path
+ if (BaseConst().MultiConfig) {
+ handle->OutputFile = Gen()->AbsoluteIncludePath(sourceFile.BuildPath);
+ } else {
+ handle->OutputFile = Gen()->AbsoluteBuildPath(sourceFile.BuildPath);
+ }
+
+ // Register mapping in headers map
+ MocRegisterMapping(handle, true);
+ }
+
+ return true;
+}
+
+bool cmQtAutoMocUic::JobEvaluateT::MocEvalSource(
+ SourceFileHandleT const& source)
+{
+ SourceFileT const& sourceFile = *source;
+ auto const& parseData = sourceFile.ParseData->Moc;
+ if (!sourceFile.Moc ||
+ (parseData.Macro.empty() && parseData.Include.Underscore.empty() &&
+ parseData.Include.Dot.empty())) {
+ return true;
+ }
+
+ std::string const sourceDir = SubDirPrefix(sourceFile.FileName);
+ std::string const sourceBase =
+ cmSystemTools::GetFilenameWithoutLastExtension(sourceFile.FileName);
+
+ // For relaxed mode check if the own "moc_" or ".moc" file is included
+ bool const relaxedMode = MocConst().RelaxedMode;
+ bool sourceIncludesMocUnderscore = false;
+ bool sourceIncludesDotMoc = false;
+ // Check if the sources own "moc_" or ".moc" file is included
+ if (relaxedMode) {
+ for (IncludeKeyT const& incKey : parseData.Include.Underscore) {
+ if (incKey.Base == sourceBase) {
+ sourceIncludesMocUnderscore = true;
+ break;
+ }
+ }
+ }
+ for (IncludeKeyT const& incKey : parseData.Include.Dot) {
+ if (incKey.Base == sourceBase) {
+ sourceIncludesDotMoc = true;
+ break;
+ }
+ }
+
+ // Check if this source needs to be moc processed but doesn't.
+ if (!sourceIncludesDotMoc && !parseData.Macro.empty() &&
+ !(relaxedMode && sourceIncludesMocUnderscore)) {
+ {
+ std::string emsg = "The file contains a ";
+ emsg += Quoted(parseData.Macro);
+ emsg += " macro, but does not include ";
+ emsg += Quoted(sourceBase + ".moc");
+ emsg += "!\nConsider to\n - add #include \"";
+ emsg += sourceBase;
+ emsg += ".moc\"\n - enable SKIP_AUTOMOC for this file";
+ LogFileError(GenT::MOC, sourceFile.FileName, emsg);
+ }
+ return false;
+ }
+
+ // Evaluate "moc_" includes
+ for (IncludeKeyT const& incKey : parseData.Include.Underscore) {
+ std::string const headerBase = incKey.Dir + incKey.Base;
+ SourceFileHandleT header = MocFindIncludedHeader(sourceDir, headerBase);
+ if (!header) {
+ {
+ std::string msg = "The file includes the moc file ";
+ msg += Quoted(incKey.Key);
+ msg += ",\nbut the header could not be found "
+ "in the following locations\n";
+ msg += MocMessageTestHeaders(headerBase);
+ LogFileError(GenT::MOC, sourceFile.FileName, msg);
+ }
+ return false;
+ }
+ // The include might be handled differently in relaxed mode
+ if (relaxedMode && !sourceIncludesDotMoc && !parseData.Macro.empty() &&
+ (incKey.Base == sourceBase)) {
+ // The <BASE>.cpp file includes a Qt macro but does not include the
+ // <BASE>.moc file. In this case, the moc_<BASE>.cpp should probably
+ // be generated from <BASE>.cpp instead of <BASE>.h, because otherwise
+ // it won't build. But warn, since this is not how it is supposed to be
+ // used. This is for KDE4 compatibility.
+ {
+ // Issue a warning
+ std::string msg = "The file contains a ";
+ msg += Quoted(parseData.Macro);
+ msg += " macro, but does not include ";
+ msg += Quoted(sourceBase + ".moc");
+ msg += ".\nInstead it includes ";
+ msg += Quoted(incKey.Key);
+ msg += ".\nRunning moc on the source\n ";
+ msg += Quoted(sourceFile.FileName);
+ msg += "!\nBetter include ";
+ msg += Quoted(sourceBase + ".moc");
+ msg += " for compatibility with regular mode.\n";
+ msg += "This is a CMAKE_AUTOMOC_RELAXED_MODE warning.\n";
+ Log().WarningFile(GenT::MOC, sourceFile.FileName, msg);
+ }
+ // Create mapping
+ if (!MocRegisterIncluded(incKey.Key, source, source, false)) {
+ return false;
+ }
+ continue;
+ }
+
+ // Check if header is skipped
+ if (MocConst().skipped(header->FileName)) {
+ continue;
+ }
+ // Create mapping
+ if (!MocRegisterIncluded(incKey.Key, source, std::move(header), true)) {
+ return false;
+ }
+ }
+
+ // Evaluate ".moc" includes
+ if (relaxedMode) {
+ // Relaxed mode
+ for (IncludeKeyT const& incKey : parseData.Include.Dot) {
+ // Check if this is the sources own .moc file
+ bool const ownMoc = (incKey.Base == sourceBase);
+ if (ownMoc && !parseData.Macro.empty()) {
+ // Create mapping for the regular use case
+ if (!MocRegisterIncluded(incKey.Key, source, source, false)) {
+ return false;
+ }
+ continue;
+ }
+ // Try to find a header instead but issue a warning.
+ // This is for KDE4 compatibility.
+ std::string const headerBase = incKey.Dir + incKey.Base;
+ SourceFileHandleT header = MocFindIncludedHeader(sourceDir, headerBase);
+ if (!header) {
+ std::string msg = "The file includes the moc file ";
+ msg += Quoted(incKey.Key);
+ msg += ",\nwhich seems to be the moc file from a different source "
+ "file.\nCMAKE_AUTOMOC_RELAXED_MODE: Also a matching header"
+ "could not be found in the following locations\n";
+ msg += MocMessageTestHeaders(headerBase);
+ LogFileError(GenT::MOC, sourceFile.FileName, msg);
+ return false;
+ }
+ // Check if header is skipped
+ if (MocConst().skipped(header->FileName)) {
+ continue;
+ }
+ // Issue a warning
+ if (ownMoc && parseData.Macro.empty()) {
+ std::string msg = "The file includes the moc file ";
+ msg += Quoted(incKey.Key);
+ msg += ", but does not contain a\n";
+ msg += MocConst().MacrosString();
+ msg += " macro.\nRunning moc on the header\n ";
+ msg += Quoted(header->FileName);
+ msg += "!\nBetter include ";
+ msg += Quoted("moc_" + incKey.Base + ".cpp");
+ msg += " for a compatibility with regular mode.\n";
+ msg += "This is a CMAKE_AUTOMOC_RELAXED_MODE warning.\n";
+ Log().WarningFile(GenT::MOC, sourceFile.FileName, msg);
+ } else {
+ std::string msg = "The file includes the moc file ";
+ msg += Quoted(incKey.Key);
+ msg += " instead of ";
+ msg += Quoted("moc_" + incKey.Base + ".cpp");
+ msg += ".\nRunning moc on the header\n ";
+ msg += Quoted(header->FileName);
+ msg += "!\nBetter include ";
+ msg += Quoted("moc_" + incKey.Base + ".cpp");
+ msg += " for compatibility with regular mode.\n";
+ msg += "This is a CMAKE_AUTOMOC_RELAXED_MODE warning.\n";
+ Log().WarningFile(GenT::MOC, sourceFile.FileName, msg);
+ }
+ // Create mapping
+ if (!MocRegisterIncluded(incKey.Key, source, std::move(header), true)) {
+ return false;
+ }
+ }
+ } else {
+ // Strict mode
+ for (IncludeKeyT const& incKey : parseData.Include.Dot) {
+ // Check if this is the sources own .moc file
+ bool const ownMoc = (incKey.Base == sourceBase);
+ if (!ownMoc) {
+ // Don't allow <BASE>.moc include other than own in regular mode
+ std::string msg = "The file includes the moc file ";
+ msg += Quoted(incKey.Key);
+ msg += ",\nwhich seems to be the moc file from a different "
+ "source file.\nThis is not supported. Include ";
+ msg += Quoted(sourceBase + ".moc");
+ msg += " to run moc on this source file.";
+ LogFileError(GenT::MOC, sourceFile.FileName, msg);
+ return false;
+ }
+ // Accept but issue a warning if moc isn't required
+ if (parseData.Macro.empty()) {
+ std::string msg = "The file includes the moc file ";
+ msg += Quoted(incKey.Key);
+ msg += ", but does not contain a ";
+ msg += MocConst().MacrosString();
+ msg += " macro.";
+ Log().WarningFile(GenT::MOC, sourceFile.FileName, msg);
+ }
+ // Create mapping
+ if (!MocRegisterIncluded(incKey.Key, source, source, false)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+cmQtAutoMocUic::SourceFileHandleT
+cmQtAutoMocUic::JobEvaluateT::MocFindIncludedHeader(
+ std::string const& includerDir, std::string const& includeBase) const
+{
+ // Search in vicinity of the source
+ {
+ SourceFileHandleT res = MocFindHeader(includerDir + includeBase);
+ if (res) {
+ return res;
+ }
+ }
+ // Search in include directories
+ for (std::string const& path : MocConst().IncludePaths) {
+ std::string testPath = path;
+ testPath += '/';
+ testPath += includeBase;
+ SourceFileHandleT res = MocFindHeader(testPath);
+ if (res) {
+ return res;
+ }
+ }
+ // Return without success
+ return SourceFileHandleT();
+}
+
+cmQtAutoMocUic::SourceFileHandleT cmQtAutoMocUic::JobEvaluateT::MocFindHeader(
+ std::string const& basePath) const
+{
+ std::string testPath;
+ testPath.reserve(basePath.size() + 8);
+ for (std::string const& ext : BaseConst().HeaderExtensions) {
+ testPath.clear();
+ testPath += basePath;
+ testPath += '.';
+ testPath += ext;
+ cmFileTime fileTime;
+ if (fileTime.Load(testPath)) {
+ // Compute real path of the file
+ testPath = cmSystemTools::GetRealPath(testPath);
+ // Return a known file if it exists already
+ {
+ auto it = BaseEval().Headers.find(testPath);
+ if (it != BaseEval().Headers.end()) {
+ return it->second;
+ }
+ }
+ // Created and return discovered file entry
+ SourceFileHandleT& res = MocEval().HeadersDiscovered[testPath];
+ if (!res) {
+ res = std::make_shared<SourceFileT>(testPath);
+ res->FileTime = fileTime;
+ res->Moc = true;
+ }
+ return res;
+ }
+ }
+ // Return without success
+ return SourceFileHandleT();
+}
+
+std::string cmQtAutoMocUic::JobEvaluateT::MocMessageTestHeaders(
+ std::string const& fileBase) const
+{
+ std::ostringstream res;
+ {
+ std::string exts = ".{";
+ exts += cmJoin(BaseConst().HeaderExtensions, ",");
+ exts += '}';
+ // Compose result string
+ res << " " << fileBase << exts << '\n';
+ for (std::string const& path : MocConst().IncludePaths) {
+ res << " " << path << '/' << fileBase << exts << '\n';
+ }
+ }
+ return res.str();
+}
+
+bool cmQtAutoMocUic::JobEvaluateT::MocRegisterIncluded(
+ std::string const& includeString, SourceFileHandleT includerFileHandle,
+ SourceFileHandleT sourceFileHandle, bool sourceIsHeader) const
+{
+ // Check if this file is already included
+ MappingHandleT& handle = MocEval().Includes[includeString];
+ if (handle) {
+ // Check if the output file would be generated from different source files
+ if (handle->SourceFile != sourceFileHandle) {
+ std::string msg = "The source files\n ";
+ msg += Quoted(includerFileHandle->FileName);
+ msg += '\n';
+ for (auto const& item : handle->IncluderFiles) {
+ msg += " ";
+ msg += Quoted(item->FileName);
+ msg += '\n';
+ }
+ msg += "contain the same include string ";
+ msg += Quoted(includeString);
+ msg += ", but\nthe moc file would be generated from different "
+ "source files\n ";
+ msg += Quoted(sourceFileHandle->FileName);
+ msg += " and\n ";
+ msg += Quoted(handle->SourceFile->FileName);
+ msg += ".\nConsider to\n"
+ " - not include the \"moc_<NAME>.cpp\" file\n"
+ " - add a directory prefix to a \"<NAME>.moc\" include "
+ "(e.g \"sub/<NAME>.moc\")\n"
+ " - rename the source file(s)\n";
+ LogError(GenT::MOC, msg);
+ return false;
+ }
+
+ // The same mapping already exists. Just add to the includers list.
+ handle->IncluderFiles.emplace_back(std::move(includerFileHandle));
+ return true;
+ }
+
+ // Create a new mapping
+ handle = std::make_shared<MappingT>();
+ handle->IncludeString = includeString;
+ handle->IncluderFiles.emplace_back(std::move(includerFileHandle));
+ handle->SourceFile = std::move(sourceFileHandle);
+ handle->OutputFile += Gen()->AbsoluteIncludePath(includeString);
+
+ // Register mapping in sources/headers map
+ MocRegisterMapping(handle, sourceIsHeader);
+ return true;
+}
+
+void cmQtAutoMocUic::JobEvaluateT::MocRegisterMapping(
+ MappingHandleT mappingHandle, bool sourceIsHeader) const
+{
+ auto& regMap =
+ sourceIsHeader ? MocEval().HeaderMappings : MocEval().SourceMappings;
+ // Check if source file already gets mapped
+ auto& regHandle = regMap[mappingHandle->SourceFile->FileName];
+ if (!regHandle) {
+ // Yet unknown mapping
+ regHandle = std::move(mappingHandle);
+ } else {
+ // Mappings with include string override those without
+ if (!mappingHandle->IncludeString.empty()) {
+ regHandle = std::move(mappingHandle);
+ }
+ }
+}
+
+bool cmQtAutoMocUic::JobEvaluateT::UicEval(SourceFileMapT const& fileMap)
+{
+ for (auto const& pair : fileMap) {
+ if (!UicEvalFile(pair.second)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool cmQtAutoMocUic::JobEvaluateT::UicEvalFile(
+ SourceFileHandleT const& sourceFileHandle)
+{
+ SourceFileT const& sourceFile = *sourceFileHandle;
+ auto const& Include = sourceFile.ParseData->Uic.Include;
+ if (!sourceFile.Uic || Include.empty()) {
+ return true;
+ }
+
+ std::string const sourceDir = SubDirPrefix(sourceFile.FileName);
+ for (IncludeKeyT const& incKey : Include) {
+ // Find .ui file name
+ SourceFileHandleT uiFileHandle =
+ UicFindIncludedUi(sourceFile.FileName, sourceDir, incKey);
+ if (!uiFileHandle || UicConst().skipped(uiFileHandle->FileName)) {
+ continue;
+ }
+ // Register mapping
+ if (!UicRegisterMapping(incKey.Key, std::move(uiFileHandle),
+ sourceFileHandle)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool cmQtAutoMocUic::JobEvaluateT::UicRegisterMapping(
+ std::string const& includeString, SourceFileHandleT uiFileHandle,
+ SourceFileHandleT includerFileHandle)
+{
+ auto& Includes = Gen()->UicEval().Includes;
+ auto it = Includes.find(includeString);
+ if (it != Includes.end()) {
+ MappingHandleT const& handle = it->second;
+ if (handle->SourceFile != uiFileHandle) {
+ // The output file already gets generated - from a different .ui file!
+ std::string msg = "The source files\n ";
+ msg += Quoted(includerFileHandle->FileName);
+ msg += '\n';
+ for (auto const& item : handle->IncluderFiles) {
+ msg += " ";
+ msg += Quoted(item->FileName);
+ msg += '\n';
+ }
+ msg += "contain the same include string ";
+ msg += Quoted(includeString);
+ msg += ", but\nthe uic file would be generated from different "
+ "user interface files\n ";
+ msg += Quoted(uiFileHandle->FileName);
+ msg += " and\n ";
+ msg += Quoted(handle->SourceFile->FileName);
+ msg += ".\nConsider to\n"
+ " - add a directory prefix to a \"ui_<NAME>.h\" include "
+ "(e.g \"sub/ui_<NAME>.h\")\n"
+ " - rename the <NAME>.ui file(s) and adjust the \"ui_<NAME>.h\" "
+ "include(s)\n";
+ LogError(GenT::UIC, msg);
+ return false;
+ }
+ // Add includer file to existing mapping
+ handle->IncluderFiles.emplace_back(std::move(includerFileHandle));
+ } else {
+ // New mapping handle
+ MappingHandleT handle = std::make_shared<MappingT>();
+ handle->IncludeString = includeString;
+ handle->IncluderFiles.emplace_back(std::move(includerFileHandle));
+ handle->SourceFile = std::move(uiFileHandle);
+ handle->OutputFile += Gen()->AbsoluteIncludePath(includeString);
+ // Register mapping
+ Includes.emplace(includeString, std::move(handle));
+ }
+ return true;
+}
+
+cmQtAutoMocUic::SourceFileHandleT
+cmQtAutoMocUic::JobEvaluateT::UicFindIncludedUi(
+ std::string const& sourceFile, std::string const& sourceDir,
+ IncludeKeyT const& incKey) const
+{
+ std::string searchFileName = incKey.Base;
+ searchFileName += ".ui";
+ // Collect search paths list
+ std::vector<std::string> testFiles;
+ {
+ auto& searchPaths = UicConst().SearchPaths;
+ testFiles.reserve((searchPaths.size() + 1) * 2);
+
+ // Vicinity of the source
+ testFiles.emplace_back(sourceDir + searchFileName);
+ if (!incKey.Dir.empty()) {
+ std::string path = sourceDir;
+ path += incKey.Dir;
+ path += searchFileName;
+ testFiles.emplace_back(path);
+ }
+ // AUTOUIC search paths
+ if (!searchPaths.empty()) {
+ for (std::string const& sPath : searchPaths) {
+ std::string path = sPath;
+ path += '/';
+ path += searchFileName;
+ testFiles.emplace_back(std::move(path));
+ }
+ if (!incKey.Dir.empty()) {
+ for (std::string const& sPath : searchPaths) {
+ std::string path = sPath;
+ path += '/';
+ path += incKey.Dir;
+ path += searchFileName;
+ testFiles.emplace_back(std::move(path));
+ }
+ }
+ }
+ }
+
+ // Search for the .ui file!
+ for (std::string const& testFile : testFiles) {
+ cmFileTime fileTime;
+ if (fileTime.Load(testFile)) {
+ // .ui file found in files system!
+ std::string realPath = cmSystemTools::GetRealPath(testFile);
+ // Get or create .ui file handle
+ SourceFileHandleT& handle = Gen()->UicEval().UiFiles[realPath];
+ if (!handle) {
+ // The file wasn't registered, yet
+ handle = std::make_shared<SourceFileT>(realPath);
+ handle->FileTime = fileTime;
+ }
+ return handle;
+ }
+ }
+
+ // Log error
+ {
+ std::string msg = "The file includes the uic file ";
+ msg += Quoted(incKey.Key);
+ msg += ",\nbut the user interface file ";
+ msg += Quoted(searchFileName);
+ msg += "\ncould not be found in the following locations\n";
+ for (std::string const& testFile : testFiles) {
+ msg += " ";
+ msg += Quoted(testFile);
+ msg += '\n';
+ }
+ LogFileError(GenT::UIC, sourceFile, msg);
+ }
+
+ return SourceFileHandleT();
+}
+
+void cmQtAutoMocUic::JobGenerateT::Process()
+{
+ // Add moc compile jobs
+ if (MocConst().Enabled) {
+ for (auto const& pair : MocEval().HeaderMappings) {
+ // Register if this mapping is a candidate for mocs_compilation.cpp
+ bool const compFile = pair.second->IncludeString.empty();
+ if (compFile) {
+ MocEval().CompFiles.emplace_back(pair.second->SourceFile->BuildPath);
+ }
+ if (!MocGenerate(pair.second, compFile)) {
+ return;
+ }
+ }
+ for (auto const& pair : MocEval().SourceMappings) {
+ if (!MocGenerate(pair.second, false)) {
+ return;
+ }
+ }
+
+ // Add mocs compilations job on demand
+ Gen()->WorkerPool().EmplaceJob<JobMocsCompilationT>();
+ }
+
+ // Add uic compile jobs
+ if (UicConst().Enabled) {
+ for (auto const& pair : Gen()->UicEval().Includes) {
+ if (!UicGenerate(pair.second)) {
+ return;
+ }
+ }
+ }
+
+ // Add finish job
+ Gen()->WorkerPool().EmplaceJob<JobFinishT>();
+}
+
+bool cmQtAutoMocUic::JobGenerateT::MocGenerate(MappingHandleT const& mapping,
+ bool compFile) const
+{
+ std::unique_ptr<std::string> reason;
+ if (Log().Verbose()) {
+ reason = cm::make_unique<std::string>();
+ }
+ if (MocUpdate(*mapping, reason.get())) {
+ // Create the parent directory
+ if (!MakeParentDirectory(mapping->OutputFile)) {
+ LogFileError(GenT::MOC, mapping->OutputFile,
+ "Could not create parent directory.");
+ return false;
+ }
+ // Add moc job
+ Gen()->WorkerPool().EmplaceJob<JobMocT>(mapping, std::move(reason));
+ // Check if a moc job for a mocs_compilation.cpp entry was generated
+ if (compFile) {
+ MocEval().CompUpdated = true;
+ }
+ }
+ return true;
+}
+
+bool cmQtAutoMocUic::JobGenerateT::MocUpdate(MappingT const& mapping,
+ std::string* reason) const
+{
+ std::string const& sourceFile = mapping.SourceFile->FileName;
+ std::string const& outputFile = mapping.OutputFile;
+
+ // Test if the output file exists
+ cmFileTime outputFileTime;
+ if (!outputFileTime.Load(outputFile)) {
+ if (reason != nullptr) {
+ *reason = "Generating ";
+ *reason += Quoted(outputFile);
+ *reason += ", because it doesn't exist, from ";
+ *reason += Quoted(sourceFile);
+ }
+ return true;
+ }
+
+ // Test if any setting changed
+ if (MocConst().SettingsChanged) {
+ if (reason != nullptr) {
+ *reason = "Generating ";
+ *reason += Quoted(outputFile);
+ *reason += ", because the uic settings changed, from ";
+ *reason += Quoted(sourceFile);
+ }
+ return true;
+ }
+
+ // Test if the source file is newer
+ if (outputFileTime.Older(mapping.SourceFile->FileTime)) {
+ if (reason != nullptr) {
+ *reason = "Generating ";
+ *reason += Quoted(outputFile);
+ *reason += ", because it's older than its source file, from ";
+ *reason += Quoted(sourceFile);
+ }
+ return true;
+ }
+
+ // Test if the moc_predefs file is newer
+ if (!MocConst().PredefsFileAbs.empty()) {
+ if (outputFileTime.Older(MocEval().PredefsTime)) {
+ if (reason != nullptr) {
+ *reason = "Generating ";
+ *reason += Quoted(outputFile);
+ *reason += ", because it's older than ";
+ *reason += Quoted(MocConst().PredefsFileAbs);
+ *reason += ", from ";
+ *reason += Quoted(sourceFile);
+ }
+ return true;
+ }
+ }
+
+ // Test if the moc executable is newer
+ if (outputFileTime.Older(MocConst().ExecutableTime)) {
+ if (reason != nullptr) {
+ *reason = "Generating ";
+ *reason += Quoted(outputFile);
+ *reason += ", because it's older than the moc executable, from ";
+ *reason += Quoted(sourceFile);
+ }
+ return true;
+ }
+
+ // Test if a dependency file is newer
+ {
+ // Check dependency timestamps
+ std::string const sourceDir = SubDirPrefix(sourceFile);
+ for (std::string const& dep : mapping.SourceFile->ParseData->Moc.Depends) {
+ // Find dependency file
+ auto const depMatch = MocFindDependency(sourceDir, dep);
+ if (depMatch.first.empty()) {
+ Log().WarningFile(GenT::MOC, sourceFile,
+ "Could not find dependency file " + Quoted(dep));
+ continue;
+ }
+ // Test if dependency file is older
+ if (outputFileTime.Older(depMatch.second)) {
+ if (reason != nullptr) {
+ *reason = "Generating ";
+ *reason += Quoted(outputFile);
+ *reason += ", because it's older than its dependency file ";
+ *reason += Quoted(depMatch.first);
+ *reason += ", from ";
+ *reason += Quoted(sourceFile);
+ }
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+std::pair<std::string, cmFileTime>
+cmQtAutoMocUic::JobGenerateT::MocFindDependency(
+ std::string const& sourceDir, std::string const& includeString) const
+{
+ typedef std::pair<std::string, cmFileTime> ResPair;
+ // Search in vicinity of the source
+ {
+ ResPair res{ sourceDir + includeString, {} };
+ if (res.second.Load(res.first)) {
+ return res;
+ }
+ }
+ // Search in include directories
+ for (std::string const& includePath : MocConst().IncludePaths) {
+ ResPair res{ includePath, {} };
+ res.first += '/';
+ res.first += includeString;
+ if (res.second.Load(res.first)) {
+ return res;
+ }
+ }
+ // Return empty
+ return ResPair();
+}
+
+bool cmQtAutoMocUic::JobGenerateT::UicGenerate(
+ MappingHandleT const& mapping) const
+{
+ std::unique_ptr<std::string> reason;
+ if (Log().Verbose()) {
+ reason = cm::make_unique<std::string>();
+ }
+ if (UicUpdate(*mapping, reason.get())) {
+ // Create the parent directory
+ if (!MakeParentDirectory(mapping->OutputFile)) {
+ LogFileError(GenT::UIC, mapping->OutputFile,
+ "Could not create parent directory.");
+ return false;
+ }
+ // Add uic job
+ Gen()->WorkerPool().EmplaceJob<JobUicT>(mapping, std::move(reason));
+ }
+ return true;
+}
+
+bool cmQtAutoMocUic::JobGenerateT::UicUpdate(MappingT const& mapping,
+ std::string* reason) const
+{
+ std::string const& sourceFile = mapping.SourceFile->FileName;
+ std::string const& outputFile = mapping.OutputFile;
+
+ // Test if the build file exists
+ cmFileTime outputFileTime;
+ if (!outputFileTime.Load(outputFile)) {
+ if (reason != nullptr) {
+ *reason = "Generating ";
+ *reason += Quoted(outputFile);
+ *reason += ", because it doesn't exist, from ";
+ *reason += Quoted(sourceFile);
+ }
+ return true;
+ }
+
+ // Test if the uic settings changed
+ if (UicConst().SettingsChanged) {
+ if (reason != nullptr) {
+ *reason = "Generating ";
+ *reason += Quoted(outputFile);
+ *reason += ", because the uic settings changed, from ";
+ *reason += Quoted(sourceFile);
+ }
+ return true;
+ }
+
+ // Test if the source file is newer
+ if (outputFileTime.Older(mapping.SourceFile->FileTime)) {
+ if (reason != nullptr) {
+ *reason = "Generating ";
+ *reason += Quoted(outputFile);
+ *reason += " because it's older than the source file ";
+ *reason += Quoted(sourceFile);
+ }
+ return true;
+ }
+
+ // Test if the uic executable is newer
+ if (outputFileTime.Older(UicConst().ExecutableTime)) {
+ if (reason != nullptr) {
+ *reason = "Generating ";
+ *reason += Quoted(outputFile);
+ *reason += ", because it's older than the uic executable, from ";
+ *reason += Quoted(sourceFile);
+ }
+ return true;
+ }
+
+ return false;
+}
+
+void cmQtAutoMocUic::JobMocT::Process()
+{
+ std::string const& sourceFile = Mapping->SourceFile->FileName;
+ std::string const& outputFile = Mapping->OutputFile;
+
+ // Compose moc command
+ std::vector<std::string> cmd;
+ cmd.push_back(MocConst().Executable);
+ // Add options
+ cmAppend(cmd, MocConst().AllOptions);
+ // Add predefs include
+ if (!MocConst().PredefsFileAbs.empty()) {
+ cmd.emplace_back("--include");
+ cmd.push_back(MocConst().PredefsFileAbs);
+ }
+ cmd.emplace_back("-o");
+ cmd.push_back(outputFile);
+ cmd.push_back(sourceFile);
+
+ // Execute moc command
+ cmWorkerPool::ProcessResultT result;
+ if (RunProcess(GenT::MOC, result, cmd, Reason.get())) {
+ // Moc command success. Print moc output.
+ if (!result.StdOut.empty()) {
+ Log().Info(GenT::MOC, result.StdOut);
+ }
+ } else {
+ // Moc command failed
+ std::string msg = "The moc process failed to compile\n ";
+ msg += Quoted(sourceFile);
+ msg += "\ninto\n ";
+ msg += Quoted(outputFile);
+ if (Mapping->IncluderFiles.empty()) {
+ msg += ".\n";
+ } else {
+ msg += "\nincluded by\n";
+ for (auto const& item : Mapping->IncluderFiles) {
+ msg += " ";
+ msg += Quoted(item->FileName);
+ msg += '\n';
+ }
+ }
+ msg += result.ErrorMessage;
+ LogCommandError(GenT::MOC, msg, cmd, result.StdOut);
+ }
+}
+
+void cmQtAutoMocUic::JobUicT::Process()
+{
+ std::string const& sourceFile = Mapping->SourceFile->FileName;
+ std::string const& outputFile = Mapping->OutputFile;
+
+ // Compose uic command
+ std::vector<std::string> cmd;
+ cmd.push_back(UicConst().Executable);
+ {
+ std::vector<std::string> allOpts = UicConst().TargetOptions;
+ auto optionIt = UicConst().Options.find(sourceFile);
+ if (optionIt != UicConst().Options.end()) {
+ UicMergeOptions(allOpts, optionIt->second,
+ (BaseConst().QtVersionMajor == 5));
+ }
+ cmAppend(cmd, allOpts);
+ }
+ cmd.emplace_back("-o");
+ cmd.emplace_back(outputFile);
+ cmd.emplace_back(sourceFile);
+
+ cmWorkerPool::ProcessResultT result;
+ if (RunProcess(GenT::UIC, result, cmd, Reason.get())) {
+ // Uic command success
+ // Print uic output
+ if (!result.StdOut.empty()) {
+ Log().Info(GenT::UIC, result.StdOut);
+ }
+ } else {
+ // Uic command failed
+ std::string msg = "The uic process failed to compile\n ";
+ msg += Quoted(sourceFile);
+ msg += "\ninto\n ";
+ msg += Quoted(outputFile);
+ msg += "\nincluded by\n";
+ for (auto const& item : Mapping->IncluderFiles) {
+ msg += " ";
+ msg += Quoted(item->FileName);
+ msg += '\n';
+ }
+ msg += result.ErrorMessage;
+ LogCommandError(GenT::UIC, msg, cmd, result.StdOut);
+ }
+}
+
+void cmQtAutoMocUic::JobMocsCompilationT::Process()
+{
+ // Compose mocs compilation file content
+ std::string content =
+ "// This file is autogenerated. Changes will be overwritten.\n";
+
+ if (MocEval().CompFiles.empty()) {
+ // Placeholder content
+ content += "// No files found that require moc or the moc files are "
+ "included\n";
+ content += "enum some_compilers { need_more_than_nothing };\n";
+ } else {
+ // Valid content
+ char const clampB = BaseConst().MultiConfig ? '<' : '"';
+ char const clampE = BaseConst().MultiConfig ? '>' : '"';
+ for (std::string const& mocfile : MocEval().CompFiles) {
+ content += "#include ";
+ content += clampB;
+ content += mocfile;
+ content += clampE;
+ content += '\n';
+ }
+ }
+
+ std::string const& compAbs = MocConst().CompFileAbs;
+ if (cmQtAutoGenerator::FileDiffers(compAbs, content)) {
+ // Actually write mocs compilation file
+ if (Log().Verbose()) {
+ Log().Info(GenT::MOC, "Generating MOC compilation " + compAbs);
+ }
+ if (!FileWrite(compAbs, content)) {
+ LogFileError(GenT::MOC, compAbs,
+ "mocs compilation file writing failed.");
+ }
+ } else if (MocEval().CompUpdated) {
+ // Only touch mocs compilation file
+ if (Log().Verbose()) {
+ Log().Info(GenT::MOC, "Touching mocs compilation " + compAbs);
+ }
+ if (!cmSystemTools::Touch(compAbs, false)) {
+ LogFileError(GenT::MOC, compAbs,
+ "mocs compilation file touching failed.");
+ }
+ }
+}
+
+void cmQtAutoMocUic::JobFinishT::Process()
+{
+ Gen()->AbortSuccess();
+}
+
+cmQtAutoMocUic::cmQtAutoMocUic() = default;
+cmQtAutoMocUic::~cmQtAutoMocUic() = default;
+
+bool cmQtAutoMocUic::Init(cmMakefile* makefile)
+{
+ // Utility lambdas
+ auto InfoGet = [makefile](const char* key) {
+ return makefile->GetSafeDefinition(key);
+ };
+ auto InfoGetBool = [makefile](const char* key) {
+ return makefile->IsOn(key);
+ };
+ auto InfoGetList = [makefile](const char* key) -> std::vector<std::string> {
+ std::vector<std::string> list;
+ cmSystemTools::ExpandListArgument(makefile->GetSafeDefinition(key), list);
+ return list;
+ };
+ auto InfoGetLists =
+ [makefile](const char* key) -> std::vector<std::vector<std::string>> {
+ std::vector<std::vector<std::string>> lists;
+ {
+ std::string const value = makefile->GetSafeDefinition(key);
+ std::string::size_type pos = 0;
+ while (pos < value.size()) {
+ std::string::size_type next = value.find(ListSep, pos);
+ std::string::size_type length =
+ (next != std::string::npos) ? next - pos : value.size() - pos;
+ // Remove enclosing braces
+ if (length >= 2) {
+ std::string::const_iterator itBeg = value.begin() + (pos + 1);
+ std::string::const_iterator itEnd = itBeg + (length - 2);
+ {
+ std::string subValue(itBeg, itEnd);
+ std::vector<std::string> list;
+ cmSystemTools::ExpandListArgument(subValue, list);
+ lists.push_back(std::move(list));
+ }
+ }
+ pos += length;
+ pos += ListSep.size();
+ }
+ }
+ return lists;
+ };
+ auto InfoGetConfig = [makefile, this](const char* key) -> std::string {
+ const char* valueConf = nullptr;
+ {
+ std::string keyConf = key;
+ keyConf += '_';
+ keyConf += InfoConfig();
+ valueConf = makefile->GetDefinition(keyConf);
+ }
+ if (valueConf == nullptr) {
+ return makefile->GetSafeDefinition(key);
+ }
+ return std::string(valueConf);
+ };
+ auto InfoGetConfigList =
+ [&InfoGetConfig](const char* key) -> std::vector<std::string> {
+ std::vector<std::string> list;
+ cmSystemTools::ExpandListArgument(InfoGetConfig(key), list);
+ return list;
+ };
+ auto LogInfoError = [this](std::string const& msg) -> bool {
+ std::ostringstream err;
+ err << "In " << Quoted(this->InfoFile()) << ":\n" << msg;
+ this->Log().Error(GenT::GEN, err.str());
+ return false;
+ };
+ auto MatchSizes = [&LogInfoError](const char* keyA, const char* keyB,
+ std::size_t sizeA,
+ std::size_t sizeB) -> bool {
+ if (sizeA == sizeB) {
+ return true;
+ }
+ std::ostringstream err;
+ err << "Lists sizes mismatch " << keyA << '(' << sizeA << ") " << keyB
+ << '(' << sizeB << ')';
+ return LogInfoError(err.str());
+ };
+
+ // -- Read info file
+ if (!makefile->ReadListFile(InfoFile())) {
+ return LogInfoError("File processing failed");
+ }
+
+ // -- Meta
+ Logger_.RaiseVerbosity(InfoGet("AM_VERBOSITY"));
+ BaseConst_.MultiConfig = InfoGetBool("AM_MULTI_CONFIG");
+ {
+ unsigned long num = 1;
+ if (cmSystemTools::StringToULong(InfoGet("AM_PARALLEL").c_str(), &num)) {
+ num = std::max<unsigned long>(num, 1);
+ num = std::min<unsigned long>(num, ParallelMax);
+ }
+ WorkerPool_.SetThreadCount(static_cast<unsigned int>(num));
+ }
+ BaseConst_.HeaderExtensions =
+ makefile->GetCMakeInstance()->GetHeaderExtensions();
+
+ // - Files and directories
+ BaseConst_.IncludeProjectDirsBefore =
+ InfoGetBool("AM_CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE");
+ BaseConst_.ProjectSourceDir = InfoGet("AM_CMAKE_SOURCE_DIR");
+ BaseConst_.ProjectBinaryDir = InfoGet("AM_CMAKE_BINARY_DIR");
+ BaseConst_.CurrentSourceDir = InfoGet("AM_CMAKE_CURRENT_SOURCE_DIR");
+ BaseConst_.CurrentBinaryDir = InfoGet("AM_CMAKE_CURRENT_BINARY_DIR");
+ BaseConst_.AutogenBuildDir = InfoGet("AM_BUILD_DIR");
+ if (BaseConst_.AutogenBuildDir.empty()) {
+ return LogInfoError("Autogen build directory missing.");
+ }
+ BaseConst_.AutogenIncludeDir = InfoGetConfig("AM_INCLUDE_DIR");
+ if (BaseConst_.AutogenIncludeDir.empty()) {
+ return LogInfoError("Autogen include directory missing.");
+ }
+ BaseConst_.CMakeExecutable = InfoGetConfig("AM_CMAKE_EXECUTABLE");
+ if (BaseConst_.CMakeExecutable.empty()) {
+ return LogInfoError("CMake executable file name missing.");
+ }
+ if (!BaseConst_.CMakeExecutableTime.Load(BaseConst_.CMakeExecutable)) {
+ std::string error = "The CMake executable ";
+ error += Quoted(BaseConst_.CMakeExecutable);
+ error += " does not exist.";
+ return LogInfoError(error);
+ }
+ BaseConst_.ParseCacheFile = InfoGetConfig("AM_PARSE_CACHE_FILE");
+ if (BaseConst_.ParseCacheFile.empty()) {
+ return LogInfoError("Parse cache file name missing.");
+ }
+
+ // - Settings file
+ SettingsFile_ = InfoGetConfig("AM_SETTINGS_FILE");
+ if (SettingsFile_.empty()) {
+ return LogInfoError("Settings file name missing.");
+ }
+
+ // - Qt environment
+ {
+ unsigned long qtv = BaseConst_.QtVersionMajor;
+ if (cmSystemTools::StringToULong(InfoGet("AM_QT_VERSION_MAJOR").c_str(),
+ &qtv)) {
+ BaseConst_.QtVersionMajor = static_cast<unsigned int>(qtv);
+ }
+ }
+
+ // - Moc
+ MocConst_.Executable = InfoGet("AM_QT_MOC_EXECUTABLE");
+ if (!MocConst().Executable.empty()) {
+ MocConst_.Enabled = true;
+ // Load the executable file time
+ if (!MocConst_.ExecutableTime.Load(MocConst_.Executable)) {
+ std::string error = "The moc executable ";
+ error += Quoted(MocConst_.Executable);
+ error += " does not exist.";
+ return LogInfoError(error);
+ }
+ for (std::string& sfl : InfoGetList("AM_MOC_SKIP")) {
+ MocConst_.SkipList.insert(std::move(sfl));
+ }
+ MocConst_.Definitions = InfoGetConfigList("AM_MOC_DEFINITIONS");
+ MocConst_.IncludePaths = InfoGetConfigList("AM_MOC_INCLUDES");
+ MocConst_.Options = InfoGetList("AM_MOC_OPTIONS");
+ MocConst_.RelaxedMode = InfoGetBool("AM_MOC_RELAXED_MODE");
+ for (std::string const& item : InfoGetList("AM_MOC_MACRO_NAMES")) {
+ MocConst_.MacroFilters.emplace_back(
+ item, ("[\n][ \t]*{?[ \t]*" + item).append("[^a-zA-Z0-9_]"));
+ }
+ {
+ auto addFilter = [this, &LogInfoError](std::string const& key,
+ std::string const& exp) -> bool {
+ auto filterErr = [&LogInfoError, &key, &exp](const char* err) -> bool {
+ std::ostringstream ferr;
+ ferr << "AUTOMOC_DEPEND_FILTERS: " << err << '\n';
+ ferr << " Key: " << Quoted(key) << '\n';
+ ferr << " Exp: " << Quoted(exp) << '\n';
+ return LogInfoError(ferr.str());
+ };
+ if (key.empty()) {
+ return filterErr("Key is empty");
+ }
+ if (exp.empty()) {
+ return filterErr("Regular expression is empty");
+ }
+ this->MocConst_.DependFilters.emplace_back(key, exp);
+ if (!this->MocConst_.DependFilters.back().Exp.is_valid()) {
+ return filterErr("Regular expression compiling failed");
+ }
+ return true;
+ };
+
+ // Insert default filter for Q_PLUGIN_METADATA
+ if (BaseConst().QtVersionMajor != 4) {
+ if (!addFilter("Q_PLUGIN_METADATA",
+ "[\n][ \t]*Q_PLUGIN_METADATA[ \t]*\\("
+ "[^\\)]*FILE[ \t]*\"([^\"]+)\"")) {
+ return false;
+ }
+ }
+ // Insert user defined dependency filters
+ std::vector<std::string> flts = InfoGetList("AM_MOC_DEPEND_FILTERS");
+ if ((flts.size() % 2) != 0) {
+ return LogInfoError(
+ "AUTOMOC_DEPEND_FILTERS list size is not a multiple of 2");
+ }
+ for (auto itC = flts.begin(), itE = flts.end(); itC != itE; itC += 2) {
+ if (!addFilter(*itC, *(itC + 1))) {
+ return false;
+ }
+ }
+ }
+ MocConst_.PredefsCmd = InfoGetList("AM_MOC_PREDEFS_CMD");
+ }
+
+ // - Uic
+ UicConst_.Executable = InfoGet("AM_QT_UIC_EXECUTABLE");
+ if (!UicConst().Executable.empty()) {
+ UicConst_.Enabled = true;
+ // Load the executable file time
+ if (!UicConst_.ExecutableTime.Load(UicConst_.Executable)) {
+ std::string error = "The uic executable ";
+ error += Quoted(UicConst_.Executable);
+ error += " does not exist.";
+ return LogInfoError(error);
+ }
+ for (std::string& sfl : InfoGetList("AM_UIC_SKIP")) {
+ UicConst_.SkipList.insert(std::move(sfl));
+ }
+ UicConst_.SearchPaths = InfoGetList("AM_UIC_SEARCH_PATHS");
+ UicConst_.TargetOptions = InfoGetConfigList("AM_UIC_TARGET_OPTIONS");
+ {
+ const char* keyFiles = "AM_UIC_OPTIONS_FILES";
+ const char* keyOpts = "AM_UIC_OPTIONS_OPTIONS";
+ auto sources = InfoGetList(keyFiles);
+ auto options = InfoGetLists(keyOpts);
+ if (!MatchSizes(keyFiles, keyOpts, sources.size(), options.size())) {
+ return false;
+ }
+ auto fitEnd = sources.cend();
+ auto fit = sources.begin();
+ auto oit = options.begin();
+ while (fit != fitEnd) {
+ UicConst_.Options[*fit] = std::move(*oit);
+ ++fit;
+ ++oit;
+ }
+ }
+ }
+
+ // - Headers and sources
+ {
+ auto makeSource =
+ [&LogInfoError](std::string const& fileName,
+ std::string const& fileFlags) -> SourceFileHandleT {
+ if (fileFlags.size() != 2) {
+ LogInfoError("Invalid file flags string size");
+ return SourceFileHandleT();
+ }
+ cmFileTime fileTime;
+ if (!fileTime.Load(fileName)) {
+ LogInfoError("The source file " + cmQtAutoGen::Quoted(fileName) +
+ " does not exist.");
+ return SourceFileHandleT();
+ }
+ SourceFileHandleT sfh = std::make_shared<SourceFileT>(fileName);
+ sfh->FileTime = fileTime;
+ sfh->Moc = (fileFlags[0] == 'M');
+ sfh->Uic = (fileFlags[1] == 'U');
+ return sfh;
+ };
+
+ // Headers
+ {
+ // Get file lists
+ const char *keyFiles = "AM_HEADERS", *keyFlags = "AM_HEADERS_FLAGS";
+ std::vector<std::string> files = InfoGetList(keyFiles);
+ std::vector<std::string> flags = InfoGetList(keyFlags);
+ std::vector<std::string> builds;
+ if (!MatchSizes(keyFiles, keyFlags, files.size(), flags.size())) {
+ return false;
+ }
+ if (MocConst().Enabled) {
+ const char* keyPaths = "AM_HEADERS_BUILD_PATHS";
+ builds = InfoGetList(keyPaths);
+ if (!MatchSizes(keyFiles, keyPaths, files.size(), builds.size())) {
+ return false;
+ }
+ }
+ // Process file lists
+ for (std::size_t ii = 0; ii != files.size(); ++ii) {
+ std::string& fileName(files[ii]);
+ SourceFileHandleT sfh = makeSource(fileName, flags[ii]);
+ if (!sfh) {
+ return false;
+ }
+ if (MocConst().Enabled) {
+ sfh->BuildPath = std::move(builds[ii]);
+ if (sfh->BuildPath.empty()) {
+ Log().ErrorFile(GenT::GEN, this->InfoFile(),
+ "Header file build path is empty");
+ return false;
+ }
+ }
+ BaseEval().Headers.emplace(std::move(fileName), std::move(sfh));
+ }
+ }
+
+ // Sources
+ {
+ const char *keyFiles = "AM_SOURCES", *keyFlags = "AM_SOURCES_FLAGS";
+ std::vector<std::string> files = InfoGetList(keyFiles);
+ std::vector<std::string> flags = InfoGetList(keyFlags);
+ if (!MatchSizes(keyFiles, keyFlags, files.size(), flags.size())) {
+ return false;
+ }
+ // Process file lists
+ for (std::size_t ii = 0; ii != files.size(); ++ii) {
+ std::string& fileName(files[ii]);
+ SourceFileHandleT sfh = makeSource(fileName, flags[ii]);
+ if (!sfh) {
+ return false;
+ }
+ BaseEval().Sources.emplace(std::move(fileName), std::move(sfh));
+ }
+ }
+ }
+
+ // Init derived information
+ // ------------------------
+
+ // Moc variables
+ if (MocConst().Enabled) {
+ // Mocs compilation file
+ MocConst_.CompFileAbs = AbsoluteBuildPath("mocs_compilation.cpp");
+
+ // Moc predefs file
+ if (!MocConst_.PredefsCmd.empty()) {
+ MocConst_.PredefsFileRel = "moc_predefs";
+ if (BaseConst_.MultiConfig) {
+ MocConst_.PredefsFileRel += '_';
+ MocConst_.PredefsFileRel += InfoConfig();
+ }
+ MocConst_.PredefsFileRel += ".h";
+ MocConst_.PredefsFileAbs = AbsoluteBuildPath(MocConst().PredefsFileRel);
+ }
+
+ // Sort include directories on demand
+ if (BaseConst().IncludeProjectDirsBefore) {
+ // Move strings to temporary list
+ std::list<std::string> includes(MocConst().IncludePaths.begin(),
+ MocConst().IncludePaths.end());
+ MocConst_.IncludePaths.clear();
+ MocConst_.IncludePaths.reserve(includes.size());
+ // Append project directories only
+ {
+ std::array<std::string const*, 2> const movePaths = {
+ { &BaseConst().ProjectBinaryDir, &BaseConst().ProjectSourceDir }
+ };
+ for (std::string const* ppath : movePaths) {
+ std::list<std::string>::iterator it = includes.begin();
+ while (it != includes.end()) {
+ std::string const& path = *it;
+ if (cmSystemTools::StringStartsWith(path, ppath->c_str())) {
+ MocConst_.IncludePaths.push_back(path);
+ it = includes.erase(it);
+ } else {
+ ++it;
+ }
+ }
+ }
+ }
+ // Append remaining directories
+ MocConst_.IncludePaths.insert(MocConst_.IncludePaths.end(),
+ includes.begin(), includes.end());
+ }
+ // Compose moc includes list
+ {
+ std::set<std::string> frameworkPaths;
+ for (std::string const& path : MocConst().IncludePaths) {
+ MocConst_.Includes.push_back("-I" + path);
+ // Extract framework path
+ if (cmHasLiteralSuffix(path, ".framework/Headers")) {
+ // Go up twice to get to the framework root
+ std::vector<std::string> pathComponents;
+ cmSystemTools::SplitPath(path, pathComponents);
+ frameworkPaths.emplace(cmSystemTools::JoinPath(
+ pathComponents.begin(), pathComponents.end() - 2));
+ }
+ }
+ // Append framework includes
+ for (std::string const& path : frameworkPaths) {
+ MocConst_.Includes.emplace_back("-F");
+ MocConst_.Includes.push_back(path);
+ }
+ }
+ // Setup single list with all options
+ {
+ // Add includes
+ MocConst_.AllOptions.insert(MocConst_.AllOptions.end(),
+ MocConst().Includes.begin(),
+ MocConst().Includes.end());
+ // Add definitions
+ for (std::string const& def : MocConst().Definitions) {
+ MocConst_.AllOptions.push_back("-D" + def);
+ }
+ // Add options
+ MocConst_.AllOptions.insert(MocConst_.AllOptions.end(),
+ MocConst().Options.begin(),
+ MocConst().Options.end());
+ }
+ }
+
+ return true;
+}
+
+template <class JOBTYPE>
+void cmQtAutoMocUic::CreateParseJobs(SourceFileMapT const& sourceMap)
+{
+ cmFileTime const parseCacheTime = BaseEval().ParseCacheTime;
+ ParseCacheT& parseCache = BaseEval().ParseCache;
+ for (auto& src : sourceMap) {
+ // Get or create the file parse data reference
+ ParseCacheT::GetOrInsertT cacheEntry = parseCache.GetOrInsert(src.first);
+ src.second->ParseData = std::move(cacheEntry.first);
+ // Create a parse job if the cache file was missing or is older
+ if (cacheEntry.second || src.second->FileTime.Newer(parseCacheTime)) {
+ BaseEval().ParseCacheChanged = true;
+ WorkerPool().EmplaceJob<JOBTYPE>(src.second);
+ }
+ }
+}
+
+void cmQtAutoMocUic::InitJobs()
+{
+ // Add moc_predefs.h job
+ if (MocConst().Enabled && !MocConst().PredefsCmd.empty()) {
+ WorkerPool().EmplaceJob<JobMocPredefsT>();
+ }
+ // Add header parse jobs
+ CreateParseJobs<JobParseHeaderT>(BaseEval().Headers);
+ // Add source parse jobs
+ CreateParseJobs<JobParseSourceT>(BaseEval().Sources);
+ // Add evaluate job
+ WorkerPool().EmplaceJob<JobEvaluateT>();
+}
+
+bool cmQtAutoMocUic::Process()
+{
+ SettingsFileRead();
+ ParseCacheRead();
+ if (!CreateDirectories()) {
+ return false;
+ }
+ InitJobs();
+ if (!WorkerPool_.Process(this)) {
+ return false;
+ }
+ if (JobError_) {
+ return false;
+ }
+ if (!ParseCacheWrite()) {
+ return false;
+ }
+ if (!SettingsFileWrite()) {
+ return false;
+ }
+ return true;
+}
+
+void cmQtAutoMocUic::SettingsFileRead()
+{
+ // Compose current settings strings
+ {
+ cmCryptoHash cryptoHash(cmCryptoHash::AlgoSHA256);
+ std::string const sep(";");
+ auto cha = [&cryptoHash, &sep](std::string const& value) {
+ cryptoHash.Append(value);
+ cryptoHash.Append(sep);
+ };
+
+ if (MocConst_.Enabled) {
+ cryptoHash.Initialize();
+ cha(MocConst().Executable);
+ for (auto const& value : MocConst().AllOptions) {
+ cha(value);
+ }
+ cha(BaseConst().IncludeProjectDirsBefore ? "TRUE" : "FALSE");
+ for (auto const& value : MocConst().PredefsCmd) {
+ cha(value);
+ }
+ for (auto const& filter : MocConst().DependFilters) {
+ cha(filter.Key);
+ }
+ for (auto const& filter : MocConst().MacroFilters) {
+ cha(filter.Key);
+ }
+ SettingsStringMoc_ = cryptoHash.FinalizeHex();
+ }
+
+ if (UicConst().Enabled) {
+ cryptoHash.Initialize();
+ cha(UicConst().Executable);
+ for (auto const& value : UicConst().TargetOptions) {
+ cha(value);
+ }
+ for (const auto& item : UicConst().Options) {
+ cha(item.first);
+ for (auto const& svalue : item.second) {
+ cha(svalue);
+ }
+ }
+ SettingsStringUic_ = cryptoHash.FinalizeHex();
+ }
+ }
+
+ // Read old settings and compare
+ {
+ std::string content;
+ if (cmQtAutoGenerator::FileRead(content, SettingsFile_)) {
+ if (MocConst().Enabled) {
+ if (SettingsStringMoc_ != SettingsFind(content, "moc")) {
+ MocConst_.SettingsChanged = true;
+ }
+ }
+ if (UicConst().Enabled) {
+ if (SettingsStringUic_ != SettingsFind(content, "uic")) {
+ UicConst_.SettingsChanged = true;
+ }
+ }
+ // In case any setting changed remove the old settings file.
+ // This triggers a full rebuild on the next run if the current
+ // build is aborted before writing the current settings in the end.
+ if (MocConst().SettingsChanged || UicConst().SettingsChanged) {
+ cmSystemTools::RemoveFile(SettingsFile_);
+ }
+ } else {
+ // Settings file read failed
+ if (MocConst().Enabled) {
+ MocConst_.SettingsChanged = true;
+ }
+ if (UicConst().Enabled) {
+ UicConst_.SettingsChanged = true;
+ }
+ }
+ }
+}
+
+bool cmQtAutoMocUic::SettingsFileWrite()
+{
+ // Only write if any setting changed
+ if (MocConst().SettingsChanged || UicConst().SettingsChanged) {
+ if (Log().Verbose()) {
+ Log().Info(GenT::GEN, "Writing settings file " + Quoted(SettingsFile_));
+ }
+ // Compose settings file content
+ std::string content;
+ {
+ auto SettingAppend = [&content](const char* key,
+ std::string const& value) {
+ if (!value.empty()) {
+ content += key;
+ content += ':';
+ content += value;
+ content += '\n';
+ }
+ };
+ SettingAppend("moc", SettingsStringMoc_);
+ SettingAppend("uic", SettingsStringUic_);
+ }
+ // Write settings file
+ std::string error;
+ if (!cmQtAutoGenerator::FileWrite(SettingsFile_, content, &error)) {
+ Log().ErrorFile(GenT::GEN, SettingsFile_,
+ "Settings file writing failed. " + error);
+ // Remove old settings file to trigger a full rebuild on the next run
+ cmSystemTools::RemoveFile(SettingsFile_);
+ return false;
+ }
+ }
+ return true;
+}
+
+void cmQtAutoMocUic::ParseCacheRead()
+{
+ const char* reason = nullptr;
+ // Don't read the cache if it is invalid
+ if (!BaseEval().ParseCacheTime.Load(BaseConst().ParseCacheFile)) {
+ reason = "Refreshing parse cache because it doesn't exist.";
+ } else if (MocConst().SettingsChanged || UicConst().SettingsChanged) {
+ reason = "Refreshing parse cache because the settings changed.";
+ } else if (BaseEval().ParseCacheTime.Older(
+ BaseConst().CMakeExecutableTime)) {
+ reason =
+ "Refreshing parse cache because it is older than the CMake executable.";
+ }
+
+ if (reason != nullptr) {
+ // Don't read but refresh the complete parse cache
+ if (Log().Verbose()) {
+ Log().Info(GenT::GEN, reason);
+ }
+ BaseEval().ParseCacheChanged = true;
+ } else {
+ // Read parse cache
+ BaseEval().ParseCache.ReadFromFile(BaseConst().ParseCacheFile);
+ }
+}
+
+bool cmQtAutoMocUic::ParseCacheWrite()
+{
+ if (BaseEval().ParseCacheChanged) {
+ if (Log().Verbose()) {
+ Log().Info(GenT::GEN,
+ "Writing parse cache file " +
+ Quoted(BaseConst().ParseCacheFile));
+ }
+ if (!BaseEval().ParseCache.WriteToFile(BaseConst().ParseCacheFile)) {
+ Log().ErrorFile(GenT::GEN, BaseConst().ParseCacheFile,
+ "Parse cache file writing failed.");
+ return false;
+ }
+ }
+ return true;
+}
+
+bool cmQtAutoMocUic::CreateDirectories()
+{
+ // Create AUTOGEN include directory
+ if (!cmSystemTools::MakeDirectory(BaseConst().AutogenIncludeDir)) {
+ Log().ErrorFile(GenT::GEN, BaseConst().AutogenIncludeDir,
+ "Could not create directory.");
+ return false;
+ }
+ return true;
+}
+
+void cmQtAutoMocUic::Abort(bool error)
+{
+ if (error) {
+ JobError_.store(true);
+ }
+ WorkerPool_.Abort();
+}
+
+std::string cmQtAutoMocUic::AbsoluteBuildPath(
+ std::string const& relativePath) const
+{
+ std::string res(BaseConst().AutogenBuildDir);
+ res += '/';
+ res += relativePath;
+ return res;
+}
+
+std::string cmQtAutoMocUic::AbsoluteIncludePath(
+ std::string const& relativePath) const
+{
+ std::string res(BaseConst().AutogenIncludeDir);
+ res += '/';
+ res += relativePath;
+ return res;
+}
diff --git a/Source/cmQtAutoMocUic.h b/Source/cmQtAutoMocUic.h
new file mode 100644
index 000000000..81546cc18
--- /dev/null
+++ b/Source/cmQtAutoMocUic.h
@@ -0,0 +1,576 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#ifndef cmQtAutoMocUic_h
+#define cmQtAutoMocUic_h
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include "cmFileTime.h"
+#include "cmQtAutoGen.h"
+#include "cmQtAutoGenerator.h"
+#include "cmWorkerPool.h"
+#include "cmsys/RegularExpression.hxx"
+
+#include <atomic>
+#include <cstddef>
+#include <map>
+#include <memory> // IWYU pragma: keep
+#include <set>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+class cmMakefile;
+
+/** \class cmQtAutoMocUic
+ * \brief AUTOMOC and AUTOUIC generator
+ */
+class cmQtAutoMocUic : public cmQtAutoGenerator
+{
+public:
+ cmQtAutoMocUic();
+ ~cmQtAutoMocUic() override;
+
+ cmQtAutoMocUic(cmQtAutoMocUic const&) = delete;
+ cmQtAutoMocUic& operator=(cmQtAutoMocUic const&) = delete;
+
+public:
+ // -- Types
+
+ /**
+ * Search key plus regular expression pair
+ */
+ struct KeyExpT
+ {
+ KeyExpT() = default;
+
+ KeyExpT(const char* key, const char* exp)
+ : Key(key)
+ , Exp(exp)
+ {
+ }
+
+ KeyExpT(std::string key, std::string const& exp)
+ : Key(std::move(key))
+ , Exp(exp)
+ {
+ }
+
+ std::string Key;
+ cmsys::RegularExpression Exp;
+ };
+
+ /**
+ * Include string with sub parts
+ */
+ struct IncludeKeyT
+ {
+ IncludeKeyT(std::string const& key, std::size_t basePrefixLength);
+
+ std::string Key; // Full include string
+ std::string Dir; // Include directory
+ std::string Base; // Base part of the include file name
+ };
+
+ /**
+ * Source file parsing cache
+ */
+ class ParseCacheT
+ {
+ public:
+ // -- Types
+ /**
+ * Entry of the file parsing cache
+ */
+ struct FileT
+ {
+ void Clear();
+
+ struct MocT
+ {
+ std::string Macro;
+ struct IncludeT
+ {
+ std::vector<IncludeKeyT> Underscore;
+ std::vector<IncludeKeyT> Dot;
+ } Include;
+ std::vector<std::string> Depends;
+ } Moc;
+
+ struct UicT
+ {
+ std::vector<IncludeKeyT> Include;
+ std::vector<std::string> Depends;
+ } Uic;
+ };
+ typedef std::shared_ptr<FileT> FileHandleT;
+ typedef std::pair<FileHandleT, bool> GetOrInsertT;
+
+ public:
+ ParseCacheT();
+ ~ParseCacheT();
+
+ void Clear();
+
+ bool ReadFromFile(std::string const& fileName);
+ bool WriteToFile(std::string const& fileName);
+
+ //! Might return an invalid handle
+ FileHandleT Get(std::string const& fileName) const;
+ //! Always returns a valid handle
+ GetOrInsertT GetOrInsert(std::string const& fileName);
+
+ private:
+ std::unordered_map<std::string, FileHandleT> Map_;
+ };
+
+ /**
+ * Source file data
+ */
+ class SourceFileT
+ {
+ public:
+ SourceFileT(std::string fileName)
+ : FileName(std::move(fileName))
+ {
+ }
+
+ public:
+ std::string FileName;
+ cmFileTime FileTime;
+ ParseCacheT::FileHandleT ParseData;
+ std::string BuildPath;
+ bool Moc = false;
+ bool Uic = false;
+ };
+ typedef std::shared_ptr<SourceFileT> SourceFileHandleT;
+ typedef std::map<std::string, SourceFileHandleT> SourceFileMapT;
+
+ /**
+ * Meta compiler file mapping information
+ */
+ struct MappingT
+ {
+ SourceFileHandleT SourceFile;
+ std::string OutputFile;
+ std::string IncludeString;
+ std::vector<SourceFileHandleT> IncluderFiles;
+ };
+ typedef std::shared_ptr<MappingT> MappingHandleT;
+ typedef std::map<std::string, MappingHandleT> MappingMapT;
+
+ /**
+ * Common settings
+ */
+ class BaseSettingsT
+ {
+ public:
+ // -- Constructors
+ BaseSettingsT();
+ ~BaseSettingsT();
+
+ BaseSettingsT(BaseSettingsT const&) = delete;
+ BaseSettingsT& operator=(BaseSettingsT const&) = delete;
+
+ // -- Attributes
+ // - Config
+ bool MultiConfig = false;
+ bool IncludeProjectDirsBefore = false;
+ unsigned int QtVersionMajor = 4;
+ // - Directories
+ std::string ProjectSourceDir;
+ std::string ProjectBinaryDir;
+ std::string CurrentSourceDir;
+ std::string CurrentBinaryDir;
+ std::string AutogenBuildDir;
+ std::string AutogenIncludeDir;
+ // - Files
+ std::string CMakeExecutable;
+ cmFileTime CMakeExecutableTime;
+ std::string ParseCacheFile;
+ std::vector<std::string> HeaderExtensions;
+ };
+
+ /**
+ * Shared common variables
+ */
+ class BaseEvalT
+ {
+ public:
+ // -- Parse Cache
+ bool ParseCacheChanged = false;
+ cmFileTime ParseCacheTime;
+ ParseCacheT ParseCache;
+
+ // -- Sources
+ SourceFileMapT Headers;
+ SourceFileMapT Sources;
+ };
+
+ /**
+ * Moc settings
+ */
+ class MocSettingsT
+ {
+ public:
+ // -- Constructors
+ MocSettingsT();
+ ~MocSettingsT();
+
+ MocSettingsT(MocSettingsT const&) = delete;
+ MocSettingsT& operator=(MocSettingsT const&) = delete;
+
+ // -- Const methods
+ bool skipped(std::string const& fileName) const;
+ std::string MacrosString() const;
+
+ // -- Attributes
+ bool Enabled = false;
+ bool SettingsChanged = false;
+ bool RelaxedMode = false;
+ cmFileTime ExecutableTime;
+ std::string Executable;
+ std::string CompFileAbs;
+ std::string PredefsFileRel;
+ std::string PredefsFileAbs;
+ std::unordered_set<std::string> SkipList;
+ std::vector<std::string> IncludePaths;
+ std::vector<std::string> Includes;
+ std::vector<std::string> Definitions;
+ std::vector<std::string> Options;
+ std::vector<std::string> AllOptions;
+ std::vector<std::string> PredefsCmd;
+ std::vector<KeyExpT> DependFilters;
+ std::vector<KeyExpT> MacroFilters;
+ cmsys::RegularExpression RegExpInclude;
+ };
+
+ /**
+ * Moc shared variables
+ */
+ class MocEvalT
+ {
+ public:
+ // -- predefines file
+ cmFileTime PredefsTime;
+ // -- Mappings
+ MappingMapT HeaderMappings;
+ MappingMapT SourceMappings;
+ MappingMapT Includes;
+ // -- Discovered files
+ SourceFileMapT HeadersDiscovered;
+ // -- Mocs compilation
+ bool CompUpdated = false;
+ std::vector<std::string> CompFiles;
+ };
+
+ /**
+ * Uic settings
+ */
+ class UicSettingsT
+ {
+ public:
+ UicSettingsT();
+ ~UicSettingsT();
+
+ UicSettingsT(UicSettingsT const&) = delete;
+ UicSettingsT& operator=(UicSettingsT const&) = delete;
+
+ // -- Const methods
+ bool skipped(std::string const& fileName) const;
+
+ // -- Attributes
+ bool Enabled = false;
+ bool SettingsChanged = false;
+ cmFileTime ExecutableTime;
+ std::string Executable;
+ std::unordered_set<std::string> SkipList;
+ std::vector<std::string> TargetOptions;
+ std::map<std::string, std::vector<std::string>> Options;
+ std::vector<std::string> SearchPaths;
+ cmsys::RegularExpression RegExpInclude;
+ };
+
+ /**
+ * Uic shared variables
+ */
+ class UicEvalT
+ {
+ public:
+ SourceFileMapT UiFiles;
+ MappingMapT Includes;
+ };
+
+ /**
+ * Abstract job class for concurrent job processing
+ */
+ class JobT : public cmWorkerPool::JobT
+ {
+ protected:
+ /**
+ * @brief Protected default constructor
+ */
+ JobT(bool fence = false)
+ : cmWorkerPool::JobT(fence)
+ {
+ }
+
+ //! Get the generator. Only valid during Process() call!
+ cmQtAutoMocUic* Gen() const
+ {
+ return static_cast<cmQtAutoMocUic*>(UserData());
+ };
+
+ // -- Accessors. Only valid during Process() call!
+ Logger const& Log() const { return Gen()->Log(); }
+ BaseSettingsT const& BaseConst() const { return Gen()->BaseConst(); }
+ BaseEvalT& BaseEval() const { return Gen()->BaseEval(); }
+ MocSettingsT const& MocConst() const { return Gen()->MocConst(); }
+ MocEvalT& MocEval() const { return Gen()->MocEval(); }
+ UicSettingsT const& UicConst() const { return Gen()->UicConst(); }
+ UicEvalT& UicEval() const { return Gen()->UicEval(); }
+
+ // -- Error logging with automatic abort
+ void LogError(GenT genType, std::string const& message) const;
+ void LogFileError(GenT genType, std::string const& filename,
+ std::string const& message) const;
+ void LogCommandError(GenT genType, std::string const& message,
+ std::vector<std::string> const& command,
+ std::string const& output) const;
+
+ /**
+ * @brief Run an external process. Use only during Process() call!
+ */
+ bool RunProcess(GenT genType, cmWorkerPool::ProcessResultT& result,
+ std::vector<std::string> const& command,
+ std::string* infoMessage = nullptr);
+ };
+
+ /**
+ * Fence job utility class
+ */
+ class JobFenceT : public JobT
+ {
+ public:
+ JobFenceT()
+ : JobT(true)
+ {
+ }
+ void Process() override{};
+ };
+
+ /**
+ * Generate moc_predefs.h
+ */
+ class JobMocPredefsT : public JobFenceT
+ {
+ void Process() override;
+ bool Update(std::string* reason) const;
+ };
+
+ /**
+ * File parse job base class
+ */
+ class JobParseT : public JobT
+ {
+ public:
+ JobParseT(SourceFileHandleT fileHandle)
+ : FileHandle(std::move(fileHandle))
+ {
+ }
+
+ protected:
+ bool ReadFile();
+ void CreateKeys(std::vector<IncludeKeyT>& container,
+ std::set<std::string> const& source,
+ std::size_t basePrefixLength);
+ void MocMacro();
+ void MocDependecies();
+ void MocIncludes();
+ void UicIncludes();
+
+ protected:
+ SourceFileHandleT FileHandle;
+ std::string Content;
+ };
+
+ /**
+ * Header file parse job
+ */
+ class JobParseHeaderT : public JobParseT
+ {
+ public:
+ using JobParseT::JobParseT;
+ void Process() override;
+ };
+
+ /**
+ * Source file parse job
+ */
+ class JobParseSourceT : public JobParseT
+ {
+ public:
+ using JobParseT::JobParseT;
+ void Process() override;
+ };
+
+ /**
+ * Evaluate parsed files
+ */
+ class JobEvaluateT : public JobFenceT
+ {
+ void Process() override;
+
+ // -- Moc
+ bool MocEvalHeader(SourceFileHandleT source);
+ bool MocEvalSource(SourceFileHandleT const& source);
+ SourceFileHandleT MocFindIncludedHeader(
+ std::string const& includerDir, std::string const& includeBase) const;
+ SourceFileHandleT MocFindHeader(std::string const& basePath) const;
+ std::string MocMessageTestHeaders(std::string const& fileBase) const;
+ bool MocRegisterIncluded(std::string const& includeString,
+ SourceFileHandleT includerFileHandle,
+ SourceFileHandleT sourceFileHandle,
+ bool sourceIsHeader) const;
+ void MocRegisterMapping(MappingHandleT mappingHandle,
+ bool sourceIsHeader) const;
+
+ // -- Uic
+ bool UicEval(SourceFileMapT const& fileMap);
+ bool UicEvalFile(SourceFileHandleT const& sourceFileHandle);
+ SourceFileHandleT UicFindIncludedUi(std::string const& sourceFile,
+ std::string const& sourceDir,
+ IncludeKeyT const& incKey) const;
+ bool UicRegisterMapping(std::string const& includeString,
+ SourceFileHandleT uiFileHandle,
+ SourceFileHandleT includerFileHandle);
+ };
+
+ /**
+ * Generates moc/uic jobs
+ */
+ class JobGenerateT : public JobFenceT
+ {
+ void Process() override;
+ // -- Moc
+ bool MocGenerate(MappingHandleT const& mapping, bool compFile) const;
+ bool MocUpdate(MappingT const& mapping, std::string* reason) const;
+ std::pair<std::string, cmFileTime> MocFindDependency(
+ std::string const& sourceDir, std::string const& includeString) const;
+ // -- Uic
+ bool UicGenerate(MappingHandleT const& mapping) const;
+ bool UicUpdate(MappingT const& mapping, std::string* reason) const;
+ };
+
+ /**
+ * File compiling base job
+ */
+ class JobCompileT : public JobT
+ {
+ public:
+ JobCompileT(MappingHandleT uicMapping, std::unique_ptr<std::string> reason)
+ : Mapping(std::move(uicMapping))
+ , Reason(std::move(reason))
+ {
+ }
+
+ protected:
+ MappingHandleT Mapping;
+ std::unique_ptr<std::string> Reason;
+ };
+
+ /**
+ * moc compiles a file
+ */
+ class JobMocT : public JobCompileT
+ {
+ public:
+ using JobCompileT::JobCompileT;
+ void Process() override;
+ };
+
+ /**
+ * uic compiles a file
+ */
+ class JobUicT : public JobCompileT
+ {
+ public:
+ using JobCompileT::JobCompileT;
+ void Process() override;
+ };
+
+ /// @brief Generate mocs_compilation.cpp
+ ///
+ class JobMocsCompilationT : public JobFenceT
+ {
+ private:
+ void Process() override;
+ };
+
+ /// @brief The last job
+ ///
+ class JobFinishT : public JobFenceT
+ {
+ private:
+ void Process() override;
+ };
+
+ // -- Const settings interface
+ BaseSettingsT const& BaseConst() const { return this->BaseConst_; }
+ BaseEvalT& BaseEval() { return this->BaseEval_; }
+ MocSettingsT const& MocConst() const { return this->MocConst_; }
+ MocEvalT& MocEval() { return this->MocEval_; }
+ UicSettingsT const& UicConst() const { return this->UicConst_; }
+ UicEvalT& UicEval() { return this->UicEval_; }
+
+ // -- Parallel job processing interface
+ cmWorkerPool& WorkerPool() { return WorkerPool_; }
+ void AbortError() { Abort(true); }
+ void AbortSuccess() { Abort(false); }
+
+ // -- Utility
+ std::string AbsoluteBuildPath(std::string const& relativePath) const;
+ std::string AbsoluteIncludePath(std::string const& relativePath) const;
+ template <class JOBTYPE>
+ void CreateParseJobs(SourceFileMapT const& sourceMap);
+
+private:
+ // -- Utility accessors
+ Logger const& Log() const { return Logger_; }
+ // -- Abstract processing interface
+ bool Init(cmMakefile* makefile) override;
+ void InitJobs();
+ bool Process() override;
+ // -- Settings file
+ void SettingsFileRead();
+ bool SettingsFileWrite();
+ // -- Parse cache
+ void ParseCacheRead();
+ bool ParseCacheWrite();
+ // -- Thread processing
+ void Abort(bool error);
+ // -- Generation
+ bool CreateDirectories();
+
+private:
+ // -- Utility
+ Logger Logger_;
+ // -- Settings
+ BaseSettingsT BaseConst_;
+ BaseEvalT BaseEval_;
+ MocSettingsT MocConst_;
+ MocEvalT MocEval_;
+ UicSettingsT UicConst_;
+ UicEvalT UicEval_;
+ // -- Settings file
+ std::string SettingsFile_;
+ std::string SettingsStringMoc_;
+ std::string SettingsStringUic_;
+ // -- Worker thread pool
+ std::atomic<bool> JobError_ = ATOMIC_VAR_INIT(false);
+ cmWorkerPool WorkerPool_;
+};
+
+#endif
diff --git a/Source/cmQtAutoRcc.cxx b/Source/cmQtAutoRcc.cxx
new file mode 100644
index 000000000..20885df93
--- /dev/null
+++ b/Source/cmQtAutoRcc.cxx
@@ -0,0 +1,523 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmQtAutoRcc.h"
+#include "cmQtAutoGen.h"
+
+#include <sstream>
+
+#include "cmAlgorithms.h"
+#include "cmCryptoHash.h"
+#include "cmDuration.h"
+#include "cmFileLockResult.h"
+#include "cmMakefile.h"
+#include "cmProcessOutput.h"
+#include "cmSystemTools.h"
+
+// -- Class methods
+
+cmQtAutoRcc::cmQtAutoRcc() = default;
+
+cmQtAutoRcc::~cmQtAutoRcc() = default;
+
+bool cmQtAutoRcc::Init(cmMakefile* makefile)
+{
+ // -- Utility lambdas
+ auto InfoGet = [makefile](std::string const& key) {
+ return makefile->GetSafeDefinition(key);
+ };
+ auto InfoGetList =
+ [makefile](std::string const& key) -> std::vector<std::string> {
+ std::vector<std::string> list;
+ cmSystemTools::ExpandListArgument(makefile->GetSafeDefinition(key), list);
+ return list;
+ };
+ auto InfoGetConfig = [makefile,
+ this](std::string const& key) -> std::string {
+ const char* valueConf = nullptr;
+ {
+ std::string keyConf = key;
+ keyConf += '_';
+ keyConf += InfoConfig();
+ valueConf = makefile->GetDefinition(keyConf);
+ }
+ if (valueConf == nullptr) {
+ return makefile->GetSafeDefinition(key);
+ }
+ return std::string(valueConf);
+ };
+ auto InfoGetConfigList =
+ [&InfoGetConfig](std::string const& key) -> std::vector<std::string> {
+ std::vector<std::string> list;
+ cmSystemTools::ExpandListArgument(InfoGetConfig(key), list);
+ return list;
+ };
+ auto LogInfoError = [this](std::string const& msg) -> bool {
+ std::ostringstream err;
+ err << "In " << Quoted(this->InfoFile()) << ":\n" << msg;
+ this->Log().Error(GenT::RCC, err.str());
+ return false;
+ };
+
+ // -- Read info file
+ if (!makefile->ReadListFile(InfoFile())) {
+ return LogInfoError("File processing failed.");
+ }
+
+ // - Configurations
+ Logger_.RaiseVerbosity(InfoGet("ARCC_VERBOSITY"));
+ MultiConfig_ = makefile->IsOn("ARCC_MULTI_CONFIG");
+
+ // - Directories
+ AutogenBuildDir_ = InfoGet("ARCC_BUILD_DIR");
+ if (AutogenBuildDir_.empty()) {
+ return LogInfoError("Build directory empty.");
+ }
+
+ IncludeDir_ = InfoGetConfig("ARCC_INCLUDE_DIR");
+ if (IncludeDir_.empty()) {
+ return LogInfoError("Include directory empty.");
+ }
+
+ // - Rcc executable
+ RccExecutable_ = InfoGet("ARCC_RCC_EXECUTABLE");
+ if (!RccExecutableTime_.Load(RccExecutable_)) {
+ std::string error = "The rcc executable ";
+ error += Quoted(RccExecutable_);
+ error += " does not exist.";
+ return LogInfoError(error);
+ }
+ RccListOptions_ = InfoGetList("ARCC_RCC_LIST_OPTIONS");
+
+ // - Job
+ LockFile_ = InfoGet("ARCC_LOCK_FILE");
+ QrcFile_ = InfoGet("ARCC_SOURCE");
+ QrcFileName_ = cmSystemTools::GetFilenameName(QrcFile_);
+ QrcFileDir_ = cmSystemTools::GetFilenamePath(QrcFile_);
+ RccPathChecksum_ = InfoGet("ARCC_OUTPUT_CHECKSUM");
+ RccFileName_ = InfoGet("ARCC_OUTPUT_NAME");
+ Options_ = InfoGetConfigList("ARCC_OPTIONS");
+ Inputs_ = InfoGetList("ARCC_INPUTS");
+
+ // - Settings file
+ SettingsFile_ = InfoGetConfig("ARCC_SETTINGS_FILE");
+
+ // - Validity checks
+ if (LockFile_.empty()) {
+ return LogInfoError("Lock file name missing.");
+ }
+ if (SettingsFile_.empty()) {
+ return LogInfoError("Settings file name missing.");
+ }
+ if (AutogenBuildDir_.empty()) {
+ return LogInfoError("Autogen build directory missing.");
+ }
+ if (RccExecutable_.empty()) {
+ return LogInfoError("rcc executable missing.");
+ }
+ if (QrcFile_.empty()) {
+ return LogInfoError("rcc input file missing.");
+ }
+ if (RccFileName_.empty()) {
+ return LogInfoError("rcc output file missing.");
+ }
+
+ // Init derived information
+ // ------------------------
+
+ RccFilePublic_ = AutogenBuildDir_;
+ RccFilePublic_ += '/';
+ RccFilePublic_ += RccPathChecksum_;
+ RccFilePublic_ += '/';
+ RccFilePublic_ += RccFileName_;
+
+ // Compute rcc output file name
+ if (IsMultiConfig()) {
+ RccFileOutput_ = IncludeDir_;
+ RccFileOutput_ += '/';
+ RccFileOutput_ += MultiConfigOutput();
+ } else {
+ RccFileOutput_ = RccFilePublic_;
+ }
+
+ return true;
+}
+
+bool cmQtAutoRcc::Process()
+{
+ if (!SettingsFileRead()) {
+ return false;
+ }
+
+ // Test if the rcc output needs to be regenerated
+ bool generate = false;
+ if (!TestQrcRccFiles(generate)) {
+ return false;
+ }
+ if (!generate && !TestResources(generate)) {
+ return false;
+ }
+ // Generate on demand
+ if (generate) {
+ if (!GenerateRcc()) {
+ return false;
+ }
+ } else {
+ // Test if the info file is newer than the output file
+ if (!TestInfoFile()) {
+ return false;
+ }
+ }
+
+ if (!GenerateWrapper()) {
+ return false;
+ }
+
+ return SettingsFileWrite();
+}
+
+std::string cmQtAutoRcc::MultiConfigOutput() const
+{
+ static std::string const suffix = "_CMAKE_";
+ std::string res;
+ res += RccPathChecksum_;
+ res += '/';
+ res += AppendFilenameSuffix(RccFileName_, suffix);
+ return res;
+}
+
+bool cmQtAutoRcc::SettingsFileRead()
+{
+ // Compose current settings strings
+ {
+ cmCryptoHash crypt(cmCryptoHash::AlgoSHA256);
+ std::string const sep(" ~~~ ");
+ {
+ std::string str;
+ str += RccExecutable_;
+ str += sep;
+ str += cmJoin(RccListOptions_, ";");
+ str += sep;
+ str += QrcFile_;
+ str += sep;
+ str += RccPathChecksum_;
+ str += sep;
+ str += RccFileName_;
+ str += sep;
+ str += cmJoin(Options_, ";");
+ str += sep;
+ str += cmJoin(Inputs_, ";");
+ str += sep;
+ SettingsString_ = crypt.HashString(str);
+ }
+ }
+
+ // Make sure the settings file exists
+ if (!cmSystemTools::FileExists(SettingsFile_, true)) {
+ // Touch the settings file to make sure it exists
+ if (!cmSystemTools::Touch(SettingsFile_, true)) {
+ Log().ErrorFile(GenT::RCC, SettingsFile_,
+ "Settings file creation failed.");
+ return false;
+ }
+ }
+
+ // Lock the lock file
+ {
+ // Make sure the lock file exists
+ if (!cmSystemTools::FileExists(LockFile_, true)) {
+ if (!cmSystemTools::Touch(LockFile_, true)) {
+ Log().ErrorFile(GenT::RCC, LockFile_, "Lock file creation failed.");
+ return false;
+ }
+ }
+ // Lock the lock file
+ cmFileLockResult lockResult =
+ LockFileLock_.Lock(LockFile_, static_cast<unsigned long>(-1));
+ if (!lockResult.IsOk()) {
+ Log().ErrorFile(GenT::RCC, LockFile_,
+ "File lock failed: " + lockResult.GetOutputMessage());
+ return false;
+ }
+ }
+
+ // Read old settings
+ {
+ std::string content;
+ if (FileRead(content, SettingsFile_)) {
+ SettingsChanged_ = (SettingsString_ != SettingsFind(content, "rcc"));
+ // In case any setting changed clear the old settings file.
+ // This triggers a full rebuild on the next run if the current
+ // build is aborted before writing the current settings in the end.
+ if (SettingsChanged_) {
+ std::string error;
+ if (!FileWrite(SettingsFile_, "", &error)) {
+ Log().ErrorFile(GenT::RCC, SettingsFile_,
+ "Settings file clearing failed. " + error);
+ return false;
+ }
+ }
+ } else {
+ SettingsChanged_ = true;
+ }
+ }
+
+ return true;
+}
+
+bool cmQtAutoRcc::SettingsFileWrite()
+{
+ // Only write if any setting changed
+ if (SettingsChanged_) {
+ if (Log().Verbose()) {
+ Log().Info(GenT::RCC, "Writing settings file " + Quoted(SettingsFile_));
+ }
+ // Write settings file
+ std::string content = "rcc:";
+ content += SettingsString_;
+ content += '\n';
+ std::string error;
+ if (!FileWrite(SettingsFile_, content, &error)) {
+ Log().ErrorFile(GenT::RCC, SettingsFile_,
+ "Settings file writing failed. " + error);
+ // Remove old settings file to trigger a full rebuild on the next run
+ cmSystemTools::RemoveFile(SettingsFile_);
+ return false;
+ }
+ }
+
+ // Unlock the lock file
+ LockFileLock_.Release();
+ return true;
+}
+
+/// Do basic checks if rcc generation is required
+bool cmQtAutoRcc::TestQrcRccFiles(bool& generate)
+{
+ // Test if the rcc input file exists
+ if (!QrcFileTime_.Load(QrcFile_)) {
+ std::string error;
+ error = "The resources file ";
+ error += Quoted(QrcFile_);
+ error += " does not exist";
+ Log().ErrorFile(GenT::RCC, QrcFile_, error);
+ return false;
+ }
+
+ // Test if the rcc output file exists
+ if (!RccFileTime_.Load(RccFileOutput_)) {
+ if (Log().Verbose()) {
+ Reason = "Generating ";
+ Reason += Quoted(RccFileOutput_);
+ Reason += ", because it doesn't exist, from ";
+ Reason += Quoted(QrcFile_);
+ }
+ generate = true;
+ return true;
+ }
+
+ // Test if the settings changed
+ if (SettingsChanged_) {
+ if (Log().Verbose()) {
+ Reason = "Generating ";
+ Reason += Quoted(RccFileOutput_);
+ Reason += ", because the rcc settings changed, from ";
+ Reason += Quoted(QrcFile_);
+ }
+ generate = true;
+ return true;
+ }
+
+ // Test if the rcc output file is older than the .qrc file
+ if (RccFileTime_.Older(QrcFileTime_)) {
+ if (Log().Verbose()) {
+ Reason = "Generating ";
+ Reason += Quoted(RccFileOutput_);
+ Reason += ", because it is older than ";
+ Reason += Quoted(QrcFile_);
+ Reason += ", from ";
+ Reason += Quoted(QrcFile_);
+ }
+ generate = true;
+ return true;
+ }
+
+ // Test if the rcc output file is older than the rcc executable
+ if (RccFileTime_.Older(RccExecutableTime_)) {
+ if (Log().Verbose()) {
+ Reason = "Generating ";
+ Reason += Quoted(RccFileOutput_);
+ Reason += ", because it is older than the rcc executable, from ";
+ Reason += Quoted(QrcFile_);
+ }
+ generate = true;
+ return true;
+ }
+
+ return true;
+}
+
+bool cmQtAutoRcc::TestResources(bool& generate)
+{
+ // Read resource files list
+ if (Inputs_.empty()) {
+ std::string error;
+ RccLister const lister(RccExecutable_, RccListOptions_);
+ if (!lister.list(QrcFile_, Inputs_, error, Log().Verbose())) {
+ Log().ErrorFile(GenT::RCC, QrcFile_, error);
+ return false;
+ }
+ }
+
+ // Check if any resource file is newer than the rcc output file
+ for (std::string const& resFile : Inputs_) {
+ // Check if the resource file exists
+ cmFileTime fileTime;
+ if (!fileTime.Load(resFile)) {
+ std::string error;
+ error = "Could not find the resource file\n ";
+ error += Quoted(resFile);
+ error += '\n';
+ Log().ErrorFile(GenT::RCC, QrcFile_, error);
+ return false;
+ }
+ // Check if the resource file is newer than the rcc output file
+ if (RccFileTime_.Older(fileTime)) {
+ if (Log().Verbose()) {
+ Reason = "Generating ";
+ Reason += Quoted(RccFileOutput_);
+ Reason += ", because it is older than ";
+ Reason += Quoted(resFile);
+ Reason += ", from ";
+ Reason += Quoted(QrcFile_);
+ }
+ generate = true;
+ break;
+ }
+ }
+ return true;
+}
+
+bool cmQtAutoRcc::TestInfoFile()
+{
+ // Test if the rcc output file is older than the info file
+ if (RccFileTime_.Older(InfoFileTime())) {
+ if (Log().Verbose()) {
+ std::string reason = "Touching ";
+ reason += Quoted(RccFileOutput_);
+ reason += " because it is older than ";
+ reason += Quoted(InfoFile());
+ Log().Info(GenT::RCC, reason);
+ }
+ // Touch build file
+ if (!cmSystemTools::Touch(RccFileOutput_, false)) {
+ Log().ErrorFile(GenT::RCC, RccFileOutput_, "Build file touch failed");
+ return false;
+ }
+ BuildFileChanged_ = true;
+ }
+
+ return true;
+}
+
+bool cmQtAutoRcc::GenerateRcc()
+{
+ // Make parent directory
+ if (!MakeParentDirectory(RccFileOutput_)) {
+ Log().ErrorFile(GenT::RCC, RccFileOutput_,
+ "Could not create parent directory");
+ return false;
+ }
+
+ // Compose rcc command
+ std::vector<std::string> cmd;
+ cmd.push_back(RccExecutable_);
+ cmAppend(cmd, Options_);
+ cmd.emplace_back("-o");
+ cmd.push_back(RccFileOutput_);
+ cmd.push_back(QrcFile_);
+
+ // Log reason and command
+ if (Log().Verbose()) {
+ std::string msg = Reason;
+ if (!msg.empty() && (msg.back() != '\n')) {
+ msg += '\n';
+ }
+ msg += QuotedCommand(cmd);
+ msg += '\n';
+ Log().Info(GenT::RCC, msg);
+ }
+
+ std::string rccStdOut;
+ std::string rccStdErr;
+ int retVal = 0;
+ bool result = cmSystemTools::RunSingleCommand(
+ cmd, &rccStdOut, &rccStdErr, &retVal, AutogenBuildDir_.c_str(),
+ cmSystemTools::OUTPUT_NONE, cmDuration::zero(), cmProcessOutput::Auto);
+ if (!result || (retVal != 0)) {
+ // rcc process failed
+ {
+ std::string err = "The rcc process failed to compile\n ";
+ err += Quoted(QrcFile_);
+ err += "\ninto\n ";
+ err += Quoted(RccFileOutput_);
+ Log().ErrorCommand(GenT::RCC, err, cmd, rccStdOut + rccStdErr);
+ }
+ cmSystemTools::RemoveFile(RccFileOutput_);
+ return false;
+ }
+
+ // rcc process success
+ // Print rcc output
+ if (!rccStdOut.empty()) {
+ Log().Info(GenT::RCC, rccStdOut);
+ }
+ BuildFileChanged_ = true;
+
+ return true;
+}
+
+bool cmQtAutoRcc::GenerateWrapper()
+{
+ // Generate a wrapper source file on demand
+ if (IsMultiConfig()) {
+ // Wrapper file content
+ std::string content;
+ content += "// This is an autogenerated configuration wrapper file.\n";
+ content += "// Changes will be overwritten.\n";
+ content += "#include <";
+ content += MultiConfigOutput();
+ content += ">\n";
+
+ // Compare with existing file content
+ bool fileDiffers = true;
+ {
+ std::string oldContents;
+ if (FileRead(oldContents, RccFilePublic_)) {
+ fileDiffers = (oldContents != content);
+ }
+ }
+ if (fileDiffers) {
+ // Write new wrapper file
+ if (Log().Verbose()) {
+ Log().Info(GenT::RCC, "Generating RCC wrapper file " + RccFilePublic_);
+ }
+ std::string error;
+ if (!FileWrite(RccFilePublic_, content, &error)) {
+ Log().ErrorFile(GenT::RCC, RccFilePublic_,
+ "RCC wrapper file writing failed. " + error);
+ return false;
+ }
+ } else if (BuildFileChanged_) {
+ // Just touch the wrapper file
+ if (Log().Verbose()) {
+ Log().Info(GenT::RCC, "Touching RCC wrapper file " + RccFilePublic_);
+ }
+ if (!cmSystemTools::Touch(RccFilePublic_, false)) {
+ Log().ErrorFile(GenT::RCC, RccFilePublic_,
+ "RCC wrapper file touch failed.");
+ return false;
+ }
+ }
+ }
+ return true;
+}
diff --git a/Source/cmQtAutoRcc.h b/Source/cmQtAutoRcc.h
new file mode 100644
index 000000000..636a6672d
--- /dev/null
+++ b/Source/cmQtAutoRcc.h
@@ -0,0 +1,81 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#ifndef cmQtAutoRcc_h
+#define cmQtAutoRcc_h
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include "cmFileLock.h"
+#include "cmFileTime.h"
+#include "cmQtAutoGenerator.h"
+
+#include <string>
+#include <vector>
+
+class cmMakefile;
+
+// @brief AUTORCC generator
+class cmQtAutoRcc : public cmQtAutoGenerator
+{
+public:
+ cmQtAutoRcc();
+ ~cmQtAutoRcc() override;
+
+ cmQtAutoRcc(cmQtAutoRcc const&) = delete;
+ cmQtAutoRcc& operator=(cmQtAutoRcc const&) = delete;
+
+private:
+ // -- Utility
+ Logger const& Log() const { return Logger_; }
+ bool IsMultiConfig() const { return MultiConfig_; }
+ std::string MultiConfigOutput() const;
+
+ // -- Abstract processing interface
+ bool Init(cmMakefile* makefile) override;
+ bool Process() override;
+ // -- Settings file
+ bool SettingsFileRead();
+ bool SettingsFileWrite();
+ // -- Tests
+ bool TestQrcRccFiles(bool& generate);
+ bool TestResources(bool& generate);
+ bool TestInfoFile();
+ // -- Generation
+ bool GenerateRcc();
+ bool GenerateWrapper();
+
+private:
+ // -- Logging
+ Logger Logger_;
+ // -- Config settings
+ bool MultiConfig_ = false;
+ // -- Directories
+ std::string AutogenBuildDir_;
+ std::string IncludeDir_;
+ // -- Qt environment
+ std::string RccExecutable_;
+ cmFileTime RccExecutableTime_;
+ std::vector<std::string> RccListOptions_;
+ // -- Job
+ std::string LockFile_;
+ cmFileLock LockFileLock_;
+ std::string QrcFile_;
+ std::string QrcFileName_;
+ std::string QrcFileDir_;
+ cmFileTime QrcFileTime_;
+ std::string RccPathChecksum_;
+ std::string RccFileName_;
+ std::string RccFileOutput_;
+ std::string RccFilePublic_;
+ cmFileTime RccFileTime_;
+ std::string Reason;
+ std::vector<std::string> Options_;
+ std::vector<std::string> Inputs_;
+ // -- Settings file
+ std::string SettingsFile_;
+ std::string SettingsString_;
+ bool SettingsChanged_ = false;
+ bool BuildFileChanged_ = false;
+};
+
+#endif
diff --git a/Source/cmRange.h b/Source/cmRange.h
new file mode 100644
index 000000000..3be5193e7
--- /dev/null
+++ b/Source/cmRange.h
@@ -0,0 +1,239 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#ifndef cmRange_h
+#define cmRange_h
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <algorithm>
+#include <functional>
+#include <iterator>
+
+namespace RangeIterators {
+
+template <typename Iter, typename UnaryPredicate>
+class FilterIterator
+{
+public:
+ using iterator_category = std::bidirectional_iterator_tag;
+ using value_type = typename std::iterator_traits<Iter>::value_type;
+ using difference_type = typename std::iterator_traits<Iter>::difference_type;
+ using pointer = typename std::iterator_traits<Iter>::pointer;
+ using reference = typename std::iterator_traits<Iter>::reference;
+
+ FilterIterator(Iter b, Iter e, UnaryPredicate p)
+ : Cur(std::move(b))
+ , End(std::move(e))
+ , Pred(std::move(p))
+ {
+ this->SatisfyPredicate();
+ }
+
+ FilterIterator& operator++()
+ {
+ ++this->Cur;
+ this->SatisfyPredicate();
+ return *this;
+ }
+
+ FilterIterator& operator--()
+ {
+ do {
+ --this->Cur;
+ } while (!this->Pred(*this->Cur));
+ return *this;
+ }
+
+ bool operator==(FilterIterator const& other) const
+ {
+ return this->Cur == other.Cur;
+ }
+
+ bool operator!=(FilterIterator const& other) const
+ {
+ return !this->operator==(other);
+ }
+
+ auto operator*() const -> decltype(*std::declval<Iter>())
+ {
+ return *this->Cur;
+ }
+
+private:
+ void SatisfyPredicate()
+ {
+ while (this->Cur != this->End && !this->Pred(*this->Cur)) {
+ ++this->Cur;
+ }
+ }
+
+ Iter Cur;
+ Iter End;
+ UnaryPredicate Pred;
+};
+
+template <typename Iter, typename UnaryFunction>
+class TransformIterator
+{
+public:
+ using iterator_category = std::bidirectional_iterator_tag;
+ using value_type =
+ typename std::remove_cv<typename std::remove_reference<decltype(
+ std::declval<UnaryFunction>()(*std::declval<Iter>()))>::type>::type;
+ using difference_type = typename std::iterator_traits<Iter>::difference_type;
+ using pointer = value_type const*;
+ using reference = value_type const&;
+
+ TransformIterator(Iter i, UnaryFunction f)
+ : Base(std::move(i))
+ , Func(std::move(f))
+ {
+ }
+
+ TransformIterator& operator++()
+ {
+ ++this->Base;
+ return *this;
+ }
+
+ TransformIterator& operator--()
+ {
+ --this->Base;
+ return *this;
+ }
+
+ bool operator==(TransformIterator const& other) const
+ {
+ return this->Base == other.Base;
+ }
+
+ bool operator!=(TransformIterator const& other) const
+ {
+ return !this->operator==(other);
+ }
+
+ auto operator*() const
+ -> decltype(std::declval<UnaryFunction>()(*std::declval<Iter>()))
+ {
+ return this->Func(*this->Base);
+ }
+
+private:
+ Iter Base;
+ UnaryFunction Func;
+};
+
+} // namespace RangeIterators
+
+template <typename Iter>
+class cmRange
+{
+public:
+ using const_iterator = Iter;
+ using value_type = typename std::iterator_traits<Iter>::value_type;
+ using difference_type = typename std::iterator_traits<Iter>::difference_type;
+
+ cmRange(Iter b, Iter e)
+ : Begin(std::move(b))
+ , End(std::move(e))
+ {
+ }
+
+ Iter begin() const { return this->Begin; }
+ Iter end() const { return this->End; }
+ bool empty() const { return this->Begin == this->End; }
+
+ difference_type size() const
+ {
+ return std::distance(this->Begin, this->End);
+ }
+
+ cmRange& advance(difference_type amount) &
+ {
+ std::advance(this->Begin, amount);
+ return *this;
+ }
+
+ cmRange advance(difference_type amount) &&
+ {
+ std::advance(this->Begin, amount);
+ return std::move(*this);
+ }
+
+ cmRange& retreat(difference_type amount) &
+ {
+ std::advance(this->End, -amount);
+ return *this;
+ }
+
+ cmRange retreat(difference_type amount) &&
+ {
+ std::advance(this->End, -amount);
+ return std::move(*this);
+ }
+
+ template <typename UnaryPredicate>
+ bool all_of(UnaryPredicate p) const
+ {
+ return std::all_of(this->Begin, this->End, std::ref(p));
+ }
+
+ template <typename UnaryPredicate>
+ bool any_of(UnaryPredicate p) const
+ {
+ return std::any_of(this->Begin, this->End, std::ref(p));
+ }
+
+ template <typename UnaryPredicate>
+ bool none_of(UnaryPredicate p) const
+ {
+ return std::none_of(this->Begin, this->End, std::ref(p));
+ }
+
+ template <typename UnaryPredicate>
+ auto filter(UnaryPredicate p) const
+ -> cmRange<RangeIterators::FilterIterator<Iter, UnaryPredicate>>
+ {
+ using It = RangeIterators::FilterIterator<Iter, UnaryPredicate>;
+ return { It(this->Begin, this->End, p), It(this->End, this->End, p) };
+ }
+
+ template <typename UnaryFunction>
+ auto transform(UnaryFunction f) const
+ -> cmRange<RangeIterators::TransformIterator<Iter, UnaryFunction>>
+ {
+ using It = RangeIterators::TransformIterator<Iter, UnaryFunction>;
+ return { It(this->Begin, f), It(this->End, f) };
+ }
+
+private:
+ Iter Begin;
+ Iter End;
+};
+
+template <typename Iter1, typename Iter2>
+bool operator==(cmRange<Iter1> const& left, cmRange<Iter2> const& right)
+{
+ return left.size() == right.size() &&
+ std::equal(left.begin(), left.end(), right.begin());
+}
+
+template <typename Iter1, typename Iter2>
+auto cmMakeRange(Iter1 begin, Iter2 end) -> cmRange<Iter1>
+{
+ return { begin, end };
+}
+
+template <typename Range>
+auto cmMakeRange(Range const& range) -> cmRange<decltype(range.begin())>
+{
+ return { range.begin(), range.end() };
+}
+
+template <typename Range>
+auto cmReverseRange(Range const& range) -> cmRange<decltype(range.rbegin())>
+{
+ return { range.rbegin(), range.rend() };
+}
+
+#endif
diff --git a/Source/cmSourceFileLocationKind.h b/Source/cmSourceFileLocationKind.h
new file mode 100644
index 000000000..dd4c6dd4f
--- /dev/null
+++ b/Source/cmSourceFileLocationKind.h
@@ -0,0 +1,15 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#ifndef cmSourceFileLocationKind_h
+#define cmSourceFileLocationKind_h
+
+enum class cmSourceFileLocationKind
+{
+ // The location is user-specified and may be ambiguous.
+ Ambiguous,
+ // The location is known to be at the given location; do not try to guess at
+ // extensions or absolute path.
+ Known
+};
+
+#endif
diff --git a/Source/cmString.cxx b/Source/cmString.cxx
new file mode 100644
index 000000000..2a0c125e6
--- /dev/null
+++ b/Source/cmString.cxx
@@ -0,0 +1,152 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#define _SCL_SECURE_NO_WARNINGS
+
+#include "cmString.hxx"
+
+#include <memory>
+#include <ostream>
+#include <stdexcept>
+#include <string>
+#include <type_traits>
+
+namespace cm {
+
+static std::string const empty_string_;
+
+void String::internally_mutate_to_stable_string()
+{
+ // We assume that only one thread mutates this instance at
+ // a time even if we point to a shared string buffer refernced
+ // by other threads.
+ *this = String(data(), size());
+}
+
+bool String::is_stable() const
+{
+ return str_if_stable() != nullptr;
+}
+
+void String::stabilize()
+{
+ if (is_stable()) {
+ return;
+ }
+ this->internally_mutate_to_stable_string();
+}
+
+std::string const* String::str_if_stable() const
+{
+ if (!data()) {
+ // We view no string.
+ // This is stable for the lifetime of our current value.
+ return &empty_string_;
+ }
+
+ if (string_ && data() == string_->data() && size() == string_->size()) {
+ // We view an entire string.
+ // This is stable for the lifetime of our current value.
+ return string_.get();
+ }
+
+ return nullptr;
+}
+
+std::string const& String::str()
+{
+ if (std::string const* s = str_if_stable()) {
+ return *s;
+ }
+ // Mutate to hold a std::string that is stable for the lifetime
+ // of our current value.
+ this->internally_mutate_to_stable_string();
+ return *string_;
+}
+
+const char* String::c_str()
+{
+ const char* c = data();
+ if (c == nullptr) {
+ return c;
+ }
+
+ // We always point into a null-terminated string so it is safe to
+ // access one past the end. If it is a null byte then we can use
+ // the pointer directly.
+ if (c[size()] == '\0') {
+ return c;
+ }
+
+ // Mutate to hold a std::string so we can get a null terminator.
+ this->internally_mutate_to_stable_string();
+ c = string_->c_str();
+ return c;
+}
+
+String& String::insert(size_type index, size_type count, char ch)
+{
+ std::string s;
+ s.reserve(size() + count);
+ s.assign(data(), size());
+ s.insert(index, count, ch);
+ return *this = std::move(s);
+}
+
+String& String::erase(size_type index, size_type count)
+{
+ if (index > size()) {
+ throw std::out_of_range("Index out of range in String::erase");
+ }
+ size_type const rcount = std::min(count, size() - index);
+ size_type const rindex = index + rcount;
+ std::string s;
+ s.reserve(size() - rcount);
+ s.assign(data(), index);
+ s.append(data() + rindex, size() - rindex);
+ return *this = std::move(s);
+}
+
+String String::substr(size_type pos, size_type count) const
+{
+ if (pos > size()) {
+ throw std::out_of_range("Index out of range in String::substr");
+ }
+ return String(*this, pos, count);
+}
+
+String::String(std::string&& s, Private)
+ : string_(std::make_shared<std::string>(std::move(s)))
+ , view_(string_->data(), string_->size())
+{
+}
+
+String::size_type String::copy(char* dest, size_type count,
+ size_type pos) const
+{
+ return view_.copy(dest, count, pos);
+}
+
+std::ostream& operator<<(std::ostream& os, String const& s)
+{
+ return os.write(s.data(), s.size());
+}
+
+std::string& operator+=(std::string& self, String const& s)
+{
+ return self += s.view();
+}
+
+String IntoString<char*>::into_string(const char* s)
+{
+ if (!s) {
+ return String();
+ }
+ return std::string(s);
+}
+
+string_view AsStringView<String>::view(String const& s)
+{
+ return s.view();
+}
+
+} // namespace cm
diff --git a/Source/cmString.hxx b/Source/cmString.hxx
new file mode 100644
index 000000000..49bad7870
--- /dev/null
+++ b/Source/cmString.hxx
@@ -0,0 +1,815 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#ifndef cmString_hxx
+#define cmString_hxx
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include "cm_static_string_view.hxx"
+#include "cm_string_view.hxx"
+
+#include <algorithm>
+#include <functional>
+#include <initializer_list>
+#include <memory>
+#include <ostream>
+#include <string>
+#include <type_traits>
+#include <utility>
+
+namespace cm {
+
+class String;
+
+/**
+ * Trait to convert type T into a String.
+ * Implementations must derive from 'std::true_type'
+ * and define an 'into_string' member that accepts
+ * type T (by value or reference) and returns one of:
+ *
+ * - 'std::string' to construct an owned instance.
+ * - 'cm::string_view' to construct a borrowed or null instances.
+ * The buffer from which the view is borrowed must outlive
+ * all copies of the resulting String, e.g. static storage.
+ * - 'cm::String' for already-constructed instances.
+ */
+template <typename T>
+struct IntoString : std::false_type
+{
+};
+
+template <typename T>
+struct IntoString<T&> : IntoString<T>
+{
+};
+
+template <typename T>
+struct IntoString<T const> : IntoString<T>
+{
+};
+
+template <typename T>
+struct IntoString<T const*> : IntoString<T*>
+{
+};
+
+template <typename T, std::string::size_type N>
+struct IntoString<T const[N]> : IntoString<T[N]>
+{
+};
+
+template <>
+struct IntoString<char*> : std::true_type
+{
+ static String into_string(const char* s);
+};
+
+template <>
+struct IntoString<std::nullptr_t> : std::true_type
+{
+ static string_view into_string(std::nullptr_t) { return string_view(); }
+};
+
+template <std::string::size_type N>
+struct IntoString<char[N]> : std::true_type
+{
+ static std::string into_string(char const (&s)[N])
+ {
+ return std::string(s, N - 1);
+ }
+};
+
+template <>
+struct IntoString<std::string> : std::true_type
+{
+ static std::string into_string(std::string s) { return s; }
+};
+
+template <>
+struct IntoString<string_view> : std::true_type
+{
+ static std::string into_string(string_view s) { return std::string(s); }
+};
+
+template <>
+struct IntoString<static_string_view> : std::true_type
+{
+ static string_view into_string(static_string_view s) { return s; }
+};
+
+template <>
+struct IntoString<char> : std::true_type
+{
+ static std::string into_string(char const& c) { return std::string(1, c); }
+};
+
+/**
+ * Trait to convert type T into a 'cm::string_view'.
+ * Implementations must derive from 'std::true_type' and
+ * define a 'view' member that accepts type T (by reference)
+ * and returns a 'cm::string_view'.
+ */
+template <typename T>
+struct AsStringView : std::false_type
+{
+};
+
+template <typename T>
+struct AsStringView<T&> : AsStringView<T>
+{
+};
+
+template <typename T>
+struct AsStringView<T const> : AsStringView<T>
+{
+};
+
+template <typename T>
+struct AsStringView<T const*> : AsStringView<T*>
+{
+};
+
+template <typename T, std::string::size_type N>
+struct AsStringView<T const[N]> : AsStringView<T[N]>
+{
+};
+
+template <>
+struct AsStringView<char*> : std::true_type
+{
+ static string_view view(const char* s) { return s; }
+};
+
+template <std::string::size_type N>
+struct AsStringView<char[N]> : std::true_type
+{
+ static string_view view(char const (&s)[N]) { return string_view(s, N - 1); }
+};
+
+template <>
+struct AsStringView<std::string> : std::true_type
+{
+ static string_view view(std::string const& s) { return s; }
+};
+
+template <>
+struct AsStringView<char> : std::true_type
+{
+ static string_view view(const char& s) { return string_view(&s, 1); }
+};
+
+template <>
+struct AsStringView<string_view> : std::true_type
+{
+ static string_view view(string_view const& s) { return s; }
+};
+
+template <>
+struct AsStringView<static_string_view> : std::true_type
+{
+ static string_view view(static_string_view const& s) { return s; }
+};
+
+template <>
+struct AsStringView<String> : std::true_type
+{
+ static string_view view(String const& s);
+};
+
+/**
+ * \class String
+ *
+ * A custom string type that holds a view of a string buffer
+ * and optionally shares ownership of the buffer. Instances
+ * may have one of the following states:
+ *
+ * - null: views and owns nothing.
+ * Conversion to 'bool' is 'false'.
+ * 'data()' and 'c_str()' return nullptr.
+ * 'size()' returns 0.
+ * 'str()' returns an empty string.
+ *
+ * - borrowed: views a string but does not own it. This is used
+ * to bind to static storage (e.g. string literals) or for
+ * temporary instances that do not outlive the borrowed buffer.
+ * Copies and substrings still borrow the original buffer.
+ * Mutation allocates a new internal string and converts to
+ * the 'owned' state.
+ * Conversion to 'bool' is 'true'.
+ * 'c_str()' may internally mutate to the 'owned' state.
+ * 'str()' internally mutates to the 'owned' state.
+ *
+ * - owned: views an immutable 'std::string' instance owned internally.
+ * Copies and substrings share ownership of the internal string.
+ * Mutation allocates a new internal string.
+ * Conversion to 'bool' is 'true'.
+ */
+class String
+{
+ enum class Private
+ {
+ };
+
+public:
+ using traits_type = std::string::traits_type;
+ using value_type = string_view::value_type;
+ using pointer = string_view::pointer;
+ using const_pointer = string_view::const_pointer;
+ using reference = string_view::reference;
+ using const_reference = string_view::const_reference;
+ using const_iterator = string_view::const_iterator;
+ using iterator = string_view::const_iterator;
+ using const_reverse_iterator = string_view::const_reverse_iterator;
+ using reverse_iterator = string_view::const_reverse_iterator;
+ using difference_type = string_view::difference_type;
+ using size_type = string_view::size_type;
+
+ static size_type const npos = string_view::npos;
+
+ /** Construct a null string. */
+ String() = default;
+
+ /** Construct from any type implementing the IntoString trait. */
+ template <typename T,
+ typename = typename std::enable_if<IntoString<T>::value>::type>
+ String(T&& s)
+ : String(IntoString<T>::into_string(std::forward<T>(s)), Private())
+ {
+ }
+
+ /** Construct via std::string initializer list constructor. */
+ String(std::initializer_list<char> il)
+ : String(std::string(il))
+ {
+ }
+
+ /** Construct by copying the specified buffer. */
+ String(const char* d, size_type s)
+ : String(std::string(d, s))
+ {
+ }
+
+ /** Construct by copying from input iterator range. */
+ template <typename InputIterator>
+ String(InputIterator first, InputIterator last)
+ : String(std::string(first, last))
+ {
+ }
+
+ /** Construct a string with 'n' copies of character 'c'. */
+ String(size_type n, char c)
+ : String(std::string(n, c))
+ {
+ }
+
+ /** Construct from a substring of another String instance.
+ This shares ownership of the other string's buffer
+ but views only a substring. */
+ String(String const& s, size_type pos, size_type count = npos)
+ : string_(s.string_)
+ , view_(s.data() + pos, std::min(count, s.size() - pos))
+ {
+ }
+
+ /** Construct by moving from another String instance.
+ The other instance is left as a null string. */
+ String(String&& s) noexcept
+ : string_(std::move(s.string_))
+ , view_(s.view_)
+ {
+ s.view_ = string_view();
+ }
+
+ /** Construct by copying from another String instance.
+ This shares ownership of the other string's buffer. */
+ String(String const&) noexcept = default;
+
+ ~String() = default;
+
+ /** Construct by borrowing an externally-owned buffer. The buffer
+ must outlive the returned instance and all copies of it. */
+ static String borrow(string_view v) { return String(v, Private()); }
+
+ /** Assign by moving from another String instance.
+ The other instance is left as a null string. */
+ String& operator=(String&& s) noexcept
+ {
+ string_ = std::move(s.string_);
+ view_ = s.view_;
+ s.view_ = string_view();
+ return *this;
+ }
+
+ /** Assign by copying from another String instance.
+ This shares ownership of the other string's buffer. */
+ String& operator=(String const&) noexcept = default;
+
+ /** Assign from any type implementing the IntoString trait. */
+ template <typename T>
+ typename // NOLINT(*)
+ std::enable_if<IntoString<T>::value, String&>::type
+ operator=(T&& s)
+ {
+ *this = String(std::forward<T>(s));
+ return *this;
+ }
+
+ /** Assign via std::string initializer list constructor. */
+ String& operator=(std::initializer_list<char> il)
+ {
+ *this = String(il);
+ return *this;
+ }
+
+ /** Return true if the instance is not a null string. */
+ explicit operator bool() const noexcept { return data() != nullptr; }
+
+ /** Return a view of the string. */
+ string_view view() const noexcept { return view_; }
+
+ /** Return true if the instance is an empty stringn or null string. */
+ bool empty() const noexcept { return view_.empty(); }
+
+ /** Return a pointer to the start of the string. */
+ const char* data() const noexcept { return view_.data(); }
+
+ /** Return the length of the string in bytes. */
+ size_type size() const noexcept { return view_.size(); }
+ size_type length() const noexcept { return view_.length(); }
+
+ /** Return the character at the given position.
+ No bounds checking is performed. */
+ char operator[](size_type pos) const noexcept { return view_[pos]; }
+
+ /** Return the character at the given position.
+ If the position is out of bounds, throws std::out_of_range. */
+ char at(size_type pos) const { return view_.at(pos); }
+
+ char front() const noexcept { return view_.front(); }
+
+ char back() const noexcept { return view_.back(); }
+
+ /** Return true if this instance is stable and otherwise false.
+ An instance is stable if it is in the 'null' state or if it is
+ an 'owned' state not produced by substring operations, or
+ after a call to 'stabilize()' or 'str()'. */
+ bool is_stable() const;
+
+ /** If 'is_stable()' does not return true, mutate so it does. */
+ void stabilize();
+
+ /** Get a pointer to a normal std::string if 'is_stable()' returns
+ true and otherwise nullptr. The pointer is valid until this
+ instance is mutated or destroyed. */
+ std::string const* str_if_stable() const;
+
+ /** Get a refernce to a normal std::string. The reference
+ is valid until this instance is mutated or destroyed. */
+ std::string const& str();
+
+ /** Get a pointer to a C-style null-terminated string
+ containing the same value as this instance. The pointer
+ is valid until this instance is mutated, destroyed,
+ or str() is called. */
+ const char* c_str();
+
+ const_iterator begin() const noexcept { return view_.begin(); }
+ const_iterator end() const noexcept { return view_.end(); }
+ const_iterator cbegin() const noexcept { return begin(); }
+ const_iterator cend() const noexcept { return end(); }
+
+ const_reverse_iterator rbegin() const noexcept { return view_.rbegin(); }
+ const_reverse_iterator rend() const noexcept { return view_.rend(); }
+ const_reverse_iterator crbegin() const noexcept { return rbegin(); }
+ const_reverse_iterator crend() const noexcept { return rend(); }
+
+ /** Append to the string using any type that implements the
+ AsStringView trait. */
+ template <typename T>
+ typename std::enable_if<AsStringView<T>::value, String&>::type operator+=(
+ T&& s)
+ {
+ string_view v = AsStringView<T>::view(std::forward<T>(s));
+ std::string r;
+ r.reserve(size() + v.size());
+ r.assign(data(), size());
+ r.append(v.data(), v.size());
+ return *this = std::move(r);
+ }
+
+ /** Assign to an empty string. */
+ void clear() { *this = ""_s; }
+
+ /** Insert 'count' copies of 'ch' at position 'index'. */
+ String& insert(size_type index, size_type count, char ch);
+
+ /** Erase 'count' characters starting at position 'index'. */
+ String& erase(size_type index = 0, size_type count = npos);
+
+ void push_back(char ch)
+ {
+ std::string s;
+ s.reserve(size() + 1);
+ s.assign(data(), size());
+ s.push_back(ch);
+ *this = std::move(s);
+ }
+
+ void pop_back() { *this = String(*this, 0, size() - 1); }
+
+ template <typename T>
+ typename std::enable_if<AsStringView<T>::value, String&>::type replace(
+ size_type pos, size_type count, T&& s)
+ {
+ const_iterator first = begin() + pos;
+ const_iterator last = first + count;
+ return replace(first, last, std::forward<T>(s));
+ }
+
+ template <typename InputIterator>
+ String& replace(const_iterator first, const_iterator last,
+ InputIterator first2, InputIterator last2)
+ {
+ std::string out;
+ out.append(view_.begin(), first);
+ out.append(first2, last2);
+ out.append(last, view_.end());
+ return *this = std::move(out);
+ }
+
+ template <typename T>
+ typename std::enable_if<AsStringView<T>::value, String&>::type replace(
+ const_iterator first, const_iterator last, T&& s)
+ {
+ string_view v = AsStringView<T>::view(std::forward<T>(s));
+ std::string out;
+ out.reserve((first - view_.begin()) + v.size() + (view_.end() - last));
+ out.append(view_.begin(), first);
+ out.append(v.data(), v.size());
+ out.append(last, view_.end());
+ return *this = std::move(out);
+ }
+
+ template <typename T>
+ typename std::enable_if<AsStringView<T>::value, String&>::type replace(
+ size_type pos, size_type count, T&& s, size_type pos2,
+ size_type count2 = npos)
+ {
+ string_view v = AsStringView<T>::view(std::forward<T>(s));
+ v = v.substr(pos2, count2);
+ return replace(pos, count, v);
+ }
+
+ String& replace(size_type pos, size_type count, size_type count2, char ch)
+ {
+ const_iterator first = begin() + pos;
+ const_iterator last = first + count;
+ return replace(first, last, count2, ch);
+ }
+
+ String& replace(const_iterator first, const_iterator last, size_type count2,
+ char ch)
+ {
+ std::string out;
+ out.reserve((first - view_.begin()) + count2 + (view_.end() - last));
+ out.append(view_.begin(), first);
+ out.append(count2, ch);
+ out.append(last, view_.end());
+ return *this = std::move(out);
+ }
+
+ size_type copy(char* dest, size_type count, size_type pos = 0) const;
+
+ void resize(size_type count) { resize(count, char()); }
+
+ void resize(size_type count, char ch)
+ {
+ std::string s;
+ s.reserve(count);
+ if (count <= size()) {
+ s.assign(data(), count);
+ } else {
+ s.assign(data(), size());
+ s.resize(count, ch);
+ }
+ *this = std::move(s);
+ }
+
+ void swap(String& other)
+ {
+ std::swap(string_, other.string_);
+ std::swap(view_, other.view_);
+ }
+
+ /** Return a substring starting at position 'pos' and
+ consisting of at most 'count' characters. */
+ String substr(size_type pos = 0, size_type count = npos) const;
+
+ template <typename T>
+ typename std::enable_if<AsStringView<T>::value, int>::type compare(
+ T&& s) const
+ {
+ return view_.compare(AsStringView<T>::view(std::forward<T>(s)));
+ }
+
+ int compare(size_type pos1, size_type count1, string_view v) const
+ {
+ return view_.compare(pos1, count1, v);
+ }
+
+ int compare(size_type pos1, size_type count1, string_view v, size_type pos2,
+ size_type count2) const
+ {
+ return view_.compare(pos1, count1, v, pos2, count2);
+ }
+
+ int compare(size_type pos1, size_type count1, const char* s) const
+ {
+ return view_.compare(pos1, count1, s);
+ }
+
+ int compare(size_type pos1, size_type count1, const char* s,
+ size_type count2) const
+ {
+ return view_.compare(pos1, count1, s, count2);
+ }
+
+ template <typename T>
+ typename std::enable_if<AsStringView<T>::value, size_type>::type find(
+ T&& s, size_type pos = 0) const
+ {
+ string_view v = AsStringView<T>::view(std::forward<T>(s));
+ return view_.find(v, pos);
+ }
+
+ size_type find(const char* s, size_type pos, size_type count) const
+ {
+ return view_.find(s, pos, count);
+ }
+
+ template <typename T>
+ typename std::enable_if<AsStringView<T>::value, size_type>::type rfind(
+ T&& s, size_type pos = npos) const
+ {
+ string_view v = AsStringView<T>::view(std::forward<T>(s));
+ return view_.rfind(v, pos);
+ }
+
+ size_type rfind(const char* s, size_type pos, size_type count) const
+ {
+ return view_.rfind(s, pos, count);
+ }
+
+ template <typename T>
+ typename std::enable_if<AsStringView<T>::value, size_type>::type
+ find_first_of(T&& s, size_type pos = 0) const
+ {
+ string_view v = AsStringView<T>::view(std::forward<T>(s));
+ return view_.find_first_of(v, pos);
+ }
+
+ size_type find_first_of(const char* s, size_type pos, size_type count) const
+ {
+ return view_.find_first_of(s, pos, count);
+ }
+
+ template <typename T>
+ typename std::enable_if<AsStringView<T>::value, size_type>::type
+ find_first_not_of(T&& s, size_type pos = 0) const
+ {
+ string_view v = AsStringView<T>::view(std::forward<T>(s));
+ return view_.find_first_not_of(v, pos);
+ }
+
+ size_type find_first_not_of(const char* s, size_type pos,
+ size_type count) const
+ {
+ return view_.find_first_not_of(s, pos, count);
+ }
+
+ template <typename T>
+ typename std::enable_if<AsStringView<T>::value, size_type>::type
+ find_last_of(T&& s, size_type pos = npos) const
+ {
+ string_view v = AsStringView<T>::view(std::forward<T>(s));
+ return view_.find_last_of(v, pos);
+ }
+
+ size_type find_last_of(const char* s, size_type pos, size_type count) const
+ {
+ return view_.find_last_of(s, pos, count);
+ }
+
+ template <typename T>
+ typename std::enable_if<AsStringView<T>::value, size_type>::type
+ find_last_not_of(T&& s, size_type pos = npos) const
+ {
+ string_view v = AsStringView<T>::view(std::forward<T>(s));
+ return view_.find_last_not_of(v, pos);
+ }
+
+ size_type find_last_not_of(const char* s, size_type pos,
+ size_type count) const
+ {
+ return view_.find_last_not_of(s, pos, count);
+ }
+
+private:
+ // Internal constructor to move from existing String.
+ String(String&& s, Private) noexcept
+ : String(std::move(s))
+ {
+ }
+
+ // Internal constructor for dynamically allocated string.
+ String(std::string&& s, Private);
+
+ // Internal constructor for view of statically allocated string.
+ String(string_view v, Private)
+ : view_(v)
+ {
+ }
+
+ void internally_mutate_to_stable_string();
+
+ std::shared_ptr<std::string const> string_;
+ string_view view_;
+};
+
+template <typename L, typename R>
+typename std::enable_if<AsStringView<L>::value && AsStringView<R>::value,
+ bool>::type
+operator==(L&& l, R&& r)
+{
+ return (AsStringView<L>::view(std::forward<L>(l)) ==
+ AsStringView<R>::view(std::forward<R>(r)));
+}
+
+template <typename L, typename R>
+typename std::enable_if<AsStringView<L>::value && AsStringView<R>::value,
+ bool>::type
+operator!=(L&& l, R&& r)
+{
+ return (AsStringView<L>::view(std::forward<L>(l)) !=
+ AsStringView<R>::view(std::forward<R>(r)));
+}
+
+template <typename L, typename R>
+typename std::enable_if<AsStringView<L>::value && AsStringView<R>::value,
+ bool>::type
+operator<(L&& l, R&& r)
+{
+ return (AsStringView<L>::view(std::forward<L>(l)) <
+ AsStringView<R>::view(std::forward<R>(r)));
+}
+
+template <typename L, typename R>
+typename std::enable_if<AsStringView<L>::value && AsStringView<R>::value,
+ bool>::type
+operator<=(L&& l, R&& r)
+{
+ return (AsStringView<L>::view(std::forward<L>(l)) <=
+ AsStringView<R>::view(std::forward<R>(r)));
+}
+
+template <typename L, typename R>
+typename std::enable_if<AsStringView<L>::value && AsStringView<R>::value,
+ bool>::type
+operator>(L&& l, R&& r)
+{
+ return (AsStringView<L>::view(std::forward<L>(l)) >
+ AsStringView<R>::view(std::forward<R>(r)));
+}
+
+template <typename L, typename R>
+typename std::enable_if<AsStringView<L>::value && AsStringView<R>::value,
+ bool>::type
+operator>=(L&& l, R&& r)
+{
+ return (AsStringView<L>::view(std::forward<L>(l)) >=
+ AsStringView<R>::view(std::forward<R>(r)));
+}
+
+std::ostream& operator<<(std::ostream& os, String const& s);
+std::string& operator+=(std::string& self, String const& s);
+
+template <typename L, typename R>
+struct StringOpPlus
+{
+ L l;
+ R r;
+#if defined(__SUNPRO_CC)
+ StringOpPlus(L in_l, R in_r)
+ : l(in_l)
+ , r(in_r)
+ {
+ }
+#endif
+ operator std::string() const;
+ std::string::size_type size() const { return l.size() + r.size(); }
+};
+
+template <typename T>
+struct StringAdd
+{
+ static const bool value = AsStringView<T>::value;
+ typedef string_view temp_type;
+ template <typename S>
+ static temp_type temp(S&& s)
+ {
+ return AsStringView<T>::view(std::forward<S>(s));
+ }
+};
+
+template <typename L, typename R>
+struct StringAdd<StringOpPlus<L, R>> : std::true_type
+{
+ typedef StringOpPlus<L, R> const& temp_type;
+ static temp_type temp(temp_type s) { return s; }
+};
+
+template <typename L, typename R>
+StringOpPlus<L, R>::operator std::string() const
+{
+ std::string s;
+ s.reserve(size());
+ s += *this;
+ return s;
+}
+
+template <typename L, typename R>
+std::string& operator+=(std::string& s, StringOpPlus<L, R> const& a)
+{
+ s.reserve(s.size() + a.size());
+ s += a.l;
+ s += a.r;
+ return s;
+}
+
+template <typename L, typename R>
+String& operator+=(String& s, StringOpPlus<L, R> const& a)
+{
+ std::string r;
+ r.reserve(s.size() + a.size());
+ r.assign(s.data(), s.size());
+ r += a.l;
+ r += a.r;
+ s = std::move(r);
+ return s;
+}
+
+template <typename L, typename R>
+std::ostream& operator<<(std::ostream& os, StringOpPlus<L, R> const& a)
+{
+ return os << a.l << a.r;
+}
+
+template <typename L, typename R>
+struct IntoString<StringOpPlus<L, R>> : std::true_type
+{
+ static std::string into_string(StringOpPlus<L, R> const& a) { return a; }
+};
+
+template <typename L, typename R>
+typename std::enable_if<StringAdd<L>::value && StringAdd<R>::value,
+ StringOpPlus<typename StringAdd<L>::temp_type,
+ typename StringAdd<R>::temp_type>>::type
+operator+(L&& l, R&& r)
+{
+ return { StringAdd<L>::temp(std::forward<L>(l)),
+ StringAdd<R>::temp(std::forward<R>(r)) };
+}
+
+template <typename LL, typename LR, typename R>
+typename std::enable_if<AsStringView<R>::value, bool>::type operator==(
+ StringOpPlus<LL, LR> const& l, R&& r)
+{
+ return std::string(l) == AsStringView<R>::view(std::forward<R>(r));
+}
+
+template <typename L, typename RL, typename RR>
+typename std::enable_if<AsStringView<L>::value, bool>::type operator==(
+ L&& l, StringOpPlus<RL, RR> const& r)
+{
+ return AsStringView<L>::view(std::forward<L>(l)) == std::string(r);
+}
+
+} // namespace cm
+
+namespace std {
+
+template <>
+struct hash<cm::String>
+{
+ typedef cm::String argument_type;
+ typedef size_t result_type;
+
+ result_type operator()(argument_type const& s) const noexcept
+ {
+ result_type const h(std::hash<cm::string_view>{}(s.view()));
+ return h;
+ }
+};
+}
+
+#endif
diff --git a/Source/cmStringReplaceHelper.cxx b/Source/cmStringReplaceHelper.cxx
new file mode 100644
index 000000000..4a6298782
--- /dev/null
+++ b/Source/cmStringReplaceHelper.cxx
@@ -0,0 +1,120 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+
+#include "cmStringReplaceHelper.h"
+
+#include "cmMakefile.h"
+#include <sstream>
+#include <utility>
+
+cmStringReplaceHelper::cmStringReplaceHelper(const std::string& regex,
+ std::string replace_expr,
+ cmMakefile* makefile)
+ : RegExString(regex)
+ , RegularExpression(regex)
+ , ReplaceExpression(std::move(replace_expr))
+ , Makefile(makefile)
+{
+ this->ParseReplaceExpression();
+}
+
+bool cmStringReplaceHelper::Replace(const std::string& input,
+ std::string& output)
+{
+ output.clear();
+
+ // Scan through the input for all matches.
+ std::string::size_type base = 0;
+ while (this->RegularExpression.find(input.c_str() + base)) {
+ if (this->Makefile != nullptr) {
+ this->Makefile->ClearMatches();
+ this->Makefile->StoreMatches(this->RegularExpression);
+ }
+ auto l2 = this->RegularExpression.start();
+ auto r = this->RegularExpression.end();
+
+ // Concatenate the part of the input that was not matched.
+ output += input.substr(base, l2);
+
+ // Make sure the match had some text.
+ if (r - l2 == 0) {
+ std::ostringstream error;
+ error << "regex \"" << this->RegExString << "\" matched an empty string";
+ this->ErrorString = error.str();
+ return false;
+ }
+
+ // Concatenate the replacement for the match.
+ for (const auto& replacement : this->Replacements) {
+ if (replacement.Number < 0) {
+ // This is just a plain-text part of the replacement.
+ output += replacement.Value;
+ } else {
+ // Replace with part of the match.
+ auto n = replacement.Number;
+ auto start = this->RegularExpression.start(n);
+ auto end = this->RegularExpression.end(n);
+ auto len = input.length() - base;
+ if ((start != std::string::npos) && (end != std::string::npos) &&
+ (start <= len) && (end <= len)) {
+ output += input.substr(base + start, end - start);
+ } else {
+ std::ostringstream error;
+ error << "replace expression \"" << this->ReplaceExpression
+ << "\" contains an out-of-range escape for regex \""
+ << this->RegExString << "\"";
+ this->ErrorString = error.str();
+ return false;
+ }
+ }
+ }
+
+ // Move past the match.
+ base += r;
+ }
+
+ // Concatenate the text after the last match.
+ output += input.substr(base, input.length() - base);
+
+ return true;
+}
+
+void cmStringReplaceHelper::ParseReplaceExpression()
+{
+ std::string::size_type l = 0;
+ while (l < this->ReplaceExpression.length()) {
+ auto r = this->ReplaceExpression.find('\\', l);
+ if (r == std::string::npos) {
+ r = this->ReplaceExpression.length();
+ this->Replacements.emplace_back(
+ this->ReplaceExpression.substr(l, r - l));
+ } else {
+ if (r - l > 0) {
+ this->Replacements.emplace_back(
+ this->ReplaceExpression.substr(l, r - l));
+ }
+ if (r == (this->ReplaceExpression.length() - 1)) {
+ this->ValidReplaceExpression = false;
+ this->ErrorString = "replace-expression ends in a backslash";
+ return;
+ }
+ if ((this->ReplaceExpression[r + 1] >= '0') &&
+ (this->ReplaceExpression[r + 1] <= '9')) {
+ this->Replacements.emplace_back(this->ReplaceExpression[r + 1] - '0');
+ } else if (this->ReplaceExpression[r + 1] == 'n') {
+ this->Replacements.emplace_back("\n");
+ } else if (this->ReplaceExpression[r + 1] == '\\') {
+ this->Replacements.emplace_back("\\");
+ } else {
+ this->ValidReplaceExpression = false;
+ std::ostringstream error;
+ error << "Unknown escape \"" << this->ReplaceExpression.substr(r, 2)
+ << "\" in replace-expression";
+ this->ErrorString = error.str();
+ return;
+ }
+ r += 2;
+ }
+ l = r;
+ }
+}
diff --git a/Source/cmStringReplaceHelper.h b/Source/cmStringReplaceHelper.h
new file mode 100644
index 000000000..b3e470416
--- /dev/null
+++ b/Source/cmStringReplaceHelper.h
@@ -0,0 +1,68 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#ifndef cmStringReplaceHelper_h
+#define cmStringReplaceHelper_h
+
+#include "cmsys/RegularExpression.hxx"
+
+#include <string>
+#include <utility>
+#include <vector>
+
+class cmMakefile;
+
+class cmStringReplaceHelper
+{
+public:
+ cmStringReplaceHelper(const std::string& regex, std::string replace_expr,
+ cmMakefile* makefile = nullptr);
+
+ bool IsRegularExpressionValid() const
+ {
+ return this->RegularExpression.is_valid();
+ }
+ bool IsReplaceExpressionValid() const
+ {
+ return this->ValidReplaceExpression;
+ }
+
+ bool Replace(const std::string& input, std::string& output);
+
+ const std::string& GetError() { return this->ErrorString; }
+
+private:
+ class RegexReplacement
+ {
+ public:
+ RegexReplacement(const char* s)
+ : Number(-1)
+ , Value(s)
+ {
+ }
+ RegexReplacement(std::string s)
+ : Number(-1)
+ , Value(std::move(s))
+ {
+ }
+ RegexReplacement(int n)
+ : Number(n)
+ {
+ }
+ RegexReplacement() = default;
+
+ int Number;
+ std::string Value;
+ };
+
+ void ParseReplaceExpression();
+
+ std::string ErrorString;
+ std::string RegExString;
+ cmsys::RegularExpression RegularExpression;
+ bool ValidReplaceExpression = true;
+ std::string ReplaceExpression;
+ std::vector<RegexReplacement> Replacements;
+ cmMakefile* Makefile = nullptr;
+};
+
+#endif
diff --git a/Source/cmTargetLinkDirectoriesCommand.cxx b/Source/cmTargetLinkDirectoriesCommand.cxx
new file mode 100644
index 000000000..269f7513e
--- /dev/null
+++ b/Source/cmTargetLinkDirectoriesCommand.cxx
@@ -0,0 +1,61 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmTargetLinkDirectoriesCommand.h"
+
+#include <sstream>
+
+#include "cmAlgorithms.h"
+#include "cmGeneratorExpression.h"
+#include "cmListFileCache.h"
+#include "cmMakefile.h"
+#include "cmMessageType.h"
+#include "cmSystemTools.h"
+#include "cmTarget.h"
+
+class cmExecutionStatus;
+
+bool cmTargetLinkDirectoriesCommand::InitialPass(
+ std::vector<std::string> const& args, cmExecutionStatus&)
+{
+ return this->HandleArguments(args, "LINK_DIRECTORIES", PROCESS_BEFORE);
+}
+
+void cmTargetLinkDirectoriesCommand::HandleMissingTarget(
+ const std::string& name)
+{
+ std::ostringstream e;
+ e << "Cannot specify link directories for target \"" << name
+ << "\" which is not built by this project.";
+ this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
+}
+
+std::string cmTargetLinkDirectoriesCommand::Join(
+ const std::vector<std::string>& content)
+{
+ std::vector<std::string> directories;
+
+ for (const auto& dir : content) {
+ auto unixPath = dir;
+ cmSystemTools::ConvertToUnixSlashes(unixPath);
+ if (!cmSystemTools::FileIsFullPath(unixPath) &&
+ !cmGeneratorExpression::StartsWithGeneratorExpression(unixPath)) {
+ auto tmp = this->Makefile->GetCurrentSourceDirectory();
+ tmp += "/";
+ tmp += unixPath;
+ unixPath = tmp;
+ }
+ directories.push_back(unixPath);
+ }
+
+ return cmJoin(directories, ";");
+}
+
+bool cmTargetLinkDirectoriesCommand::HandleDirectContent(
+ cmTarget* tgt, const std::vector<std::string>& content, bool prepend, bool)
+{
+ cmListFileBacktrace lfbt = this->Makefile->GetBacktrace();
+
+ tgt->InsertLinkDirectory(this->Join(content), lfbt, prepend);
+
+ return true; // Successfully handled.
+}
diff --git a/Source/cmTargetLinkDirectoriesCommand.h b/Source/cmTargetLinkDirectoriesCommand.h
new file mode 100644
index 000000000..52c75a0b9
--- /dev/null
+++ b/Source/cmTargetLinkDirectoriesCommand.h
@@ -0,0 +1,41 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#ifndef cmTargetLinkDirectoriesCommand_h
+#define cmTargetLinkDirectoriesCommand_h
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <string>
+#include <vector>
+
+#include "cmTargetPropCommandBase.h"
+
+class cmCommand;
+class cmExecutionStatus;
+class cmTarget;
+
+class cmTargetLinkDirectoriesCommand : public cmTargetPropCommandBase
+{
+public:
+ /**
+ * This is a virtual constructor for the command.
+ */
+ cmCommand* Clone() override { return new cmTargetLinkDirectoriesCommand; }
+
+ /**
+ * This is called when the command is first encountered in
+ * the CMakeLists.txt file.
+ */
+ bool InitialPass(std::vector<std::string> const& args,
+ cmExecutionStatus& status) override;
+
+private:
+ void HandleMissingTarget(const std::string& name) override;
+
+ std::string Join(const std::vector<std::string>& content) override;
+ bool HandleDirectContent(cmTarget* tgt,
+ const std::vector<std::string>& content,
+ bool prepend, bool system) override;
+};
+
+#endif
diff --git a/Source/cmTargetLinkOptionsCommand.cxx b/Source/cmTargetLinkOptionsCommand.cxx
new file mode 100644
index 000000000..536648638
--- /dev/null
+++ b/Source/cmTargetLinkOptionsCommand.cxx
@@ -0,0 +1,41 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmTargetLinkOptionsCommand.h"
+
+#include <sstream>
+
+#include "cmAlgorithms.h"
+#include "cmListFileCache.h"
+#include "cmMakefile.h"
+#include "cmMessageType.h"
+#include "cmTarget.h"
+
+class cmExecutionStatus;
+
+bool cmTargetLinkOptionsCommand::InitialPass(
+ std::vector<std::string> const& args, cmExecutionStatus&)
+{
+ return this->HandleArguments(args, "LINK_OPTIONS", PROCESS_BEFORE);
+}
+
+void cmTargetLinkOptionsCommand::HandleMissingTarget(const std::string& name)
+{
+ std::ostringstream e;
+ e << "Cannot specify link options for target \"" << name
+ << "\" which is not built by this project.";
+ this->Makefile->IssueMessage(MessageType::FATAL_ERROR, e.str());
+}
+
+std::string cmTargetLinkOptionsCommand::Join(
+ const std::vector<std::string>& content)
+{
+ return cmJoin(content, ";");
+}
+
+bool cmTargetLinkOptionsCommand::HandleDirectContent(
+ cmTarget* tgt, const std::vector<std::string>& content, bool prepend, bool)
+{
+ cmListFileBacktrace lfbt = this->Makefile->GetBacktrace();
+ tgt->InsertLinkOption(this->Join(content), lfbt, prepend);
+ return true; // Successfully handled.
+}
diff --git a/Source/cmTargetLinkOptionsCommand.h b/Source/cmTargetLinkOptionsCommand.h
new file mode 100644
index 000000000..a1fc9fc2e
--- /dev/null
+++ b/Source/cmTargetLinkOptionsCommand.h
@@ -0,0 +1,41 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#ifndef cmTargetLinkOptionsCommand_h
+#define cmTargetLinkOptionsCommand_h
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <string>
+#include <vector>
+
+#include "cmTargetPropCommandBase.h"
+
+class cmCommand;
+class cmExecutionStatus;
+class cmTarget;
+
+class cmTargetLinkOptionsCommand : public cmTargetPropCommandBase
+{
+public:
+ /**
+ * This is a virtual constructor for the command.
+ */
+ cmCommand* Clone() override { return new cmTargetLinkOptionsCommand; }
+
+ /**
+ * This is called when the command is first encountered in
+ * the CMakeLists.txt file.
+ */
+ bool InitialPass(std::vector<std::string> const& args,
+ cmExecutionStatus& status) override;
+
+private:
+ void HandleMissingTarget(const std::string& name) override;
+
+ bool HandleDirectContent(cmTarget* tgt,
+ const std::vector<std::string>& content,
+ bool prepend, bool system) override;
+ std::string Join(const std::vector<std::string>& content) override;
+};
+
+#endif
diff --git a/Source/cmUVHandlePtr.cxx b/Source/cmUVHandlePtr.cxx
new file mode 100644
index 000000000..db674636c
--- /dev/null
+++ b/Source/cmUVHandlePtr.cxx
@@ -0,0 +1,267 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#define cmUVHandlePtr_cxx
+#include "cmUVHandlePtr.h"
+
+#include <assert.h>
+#include <mutex>
+#include <stdlib.h>
+
+#include "cm_uv.h"
+
+namespace cm {
+
+struct uv_loop_deleter
+{
+ void operator()(uv_loop_t* loop) const;
+};
+
+void uv_loop_deleter::operator()(uv_loop_t* loop) const
+{
+ uv_run(loop, UV_RUN_DEFAULT);
+ int result = uv_loop_close(loop);
+ (void)result;
+ assert(result >= 0);
+ free(loop);
+}
+
+int uv_loop_ptr::init(void* data)
+{
+ this->reset();
+
+ this->loop.reset(static_cast<uv_loop_t*>(calloc(1, sizeof(uv_loop_t))),
+ uv_loop_deleter());
+ this->loop->data = data;
+
+ return uv_loop_init(this->loop.get());
+}
+
+void uv_loop_ptr::reset()
+{
+ this->loop.reset();
+}
+
+uv_loop_ptr::operator uv_loop_t*()
+{
+ return this->loop.get();
+}
+
+uv_loop_t* uv_loop_ptr::operator->() const noexcept
+{
+ return this->loop.get();
+}
+
+uv_loop_t* uv_loop_ptr::get() const
+{
+ return this->loop.get();
+}
+
+template <typename T>
+static void handle_default_delete(T* type_handle)
+{
+ auto handle = reinterpret_cast<uv_handle_t*>(type_handle);
+ if (handle) {
+ assert(!uv_is_closing(handle));
+ if (!uv_is_closing(handle)) {
+ uv_close(handle, [](uv_handle_t* h) { free(h); });
+ }
+ }
+}
+
+/**
+ * Encapsulates delete logic for a given handle type T
+ */
+template <typename T>
+struct uv_handle_deleter
+{
+ void operator()(T* type_handle) const { handle_default_delete(type_handle); }
+};
+
+template <typename T>
+void uv_handle_ptr_base_<T>::allocate(void* data)
+{
+ reset();
+
+ /*
+ We use calloc since we know all these types are c structs
+ and we just want to 0 init them. New would do the same thing;
+ but casting from uv_handle_t to certain other types -- namely
+ uv_timer_t -- triggers a cast_align warning on certain systems.
+ */
+ handle.reset(static_cast<T*>(calloc(1, sizeof(T))), uv_handle_deleter<T>());
+ handle->data = data;
+}
+
+template <typename T>
+void uv_handle_ptr_base_<T>::reset()
+{
+ handle.reset();
+}
+
+template <typename T>
+uv_handle_ptr_base_<T>::operator uv_handle_t*()
+{
+ return reinterpret_cast<uv_handle_t*>(handle.get());
+}
+
+template <typename T>
+T* uv_handle_ptr_base_<T>::operator->() const noexcept
+{
+ return handle.get();
+}
+
+template <typename T>
+T* uv_handle_ptr_base_<T>::get() const
+{
+ return handle.get();
+}
+
+template <typename T>
+uv_handle_ptr_<T>::operator T*() const
+{
+ return this->handle.get();
+}
+
+#ifdef CMAKE_BUILD_WITH_CMAKE
+template <>
+struct uv_handle_deleter<uv_async_t>
+{
+ /***
+ * Wile uv_async_send is itself thread-safe, there are
+ * no strong guarantees that close hasn't already been
+ * called on the handle; and that it might be deleted
+ * as the send call goes through. This mutex guards
+ * against that.
+ *
+ * The shared_ptr here is to allow for copy construction
+ * which is mandated by the standard for Deleter on
+ * shared_ptrs.
+ */
+ std::shared_ptr<std::mutex> handleMutex;
+
+ uv_handle_deleter()
+ : handleMutex(std::make_shared<std::mutex>())
+ {
+ }
+
+ void operator()(uv_async_t* handle)
+ {
+ std::lock_guard<std::mutex> lock(*handleMutex);
+ handle_default_delete(handle);
+ }
+};
+
+void uv_async_ptr::send()
+{
+ auto deleter = std::get_deleter<uv_handle_deleter<uv_async_t>>(this->handle);
+ assert(deleter);
+
+ std::lock_guard<std::mutex> lock(*deleter->handleMutex);
+ if (this->handle) {
+ uv_async_send(*this);
+ }
+}
+
+int uv_async_ptr::init(uv_loop_t& loop, uv_async_cb async_cb, void* data)
+{
+ allocate(data);
+ return uv_async_init(&loop, handle.get(), async_cb);
+}
+#endif
+
+template <>
+struct uv_handle_deleter<uv_signal_t>
+{
+ void operator()(uv_signal_t* handle) const
+ {
+ if (handle) {
+ uv_signal_stop(handle);
+ handle_default_delete(handle);
+ }
+ }
+};
+
+int uv_signal_ptr::init(uv_loop_t& loop, void* data)
+{
+ allocate(data);
+ return uv_signal_init(&loop, handle.get());
+}
+
+int uv_signal_ptr::start(uv_signal_cb cb, int signum)
+{
+ assert(handle);
+ return uv_signal_start(*this, cb, signum);
+}
+
+void uv_signal_ptr::stop()
+{
+ if (handle) {
+ uv_signal_stop(*this);
+ }
+}
+
+int uv_pipe_ptr::init(uv_loop_t& loop, int ipc, void* data)
+{
+ allocate(data);
+ return uv_pipe_init(&loop, *this, ipc);
+}
+
+uv_pipe_ptr::operator uv_stream_t*() const
+{
+ return reinterpret_cast<uv_stream_t*>(handle.get());
+}
+
+int uv_process_ptr::spawn(uv_loop_t& loop, uv_process_options_t const& options,
+ void* data)
+{
+ allocate(data);
+ return uv_spawn(&loop, *this, &options);
+}
+
+int uv_timer_ptr::init(uv_loop_t& loop, void* data)
+{
+ allocate(data);
+ return uv_timer_init(&loop, *this);
+}
+
+int uv_timer_ptr::start(uv_timer_cb cb, uint64_t timeout, uint64_t repeat)
+{
+ assert(handle);
+ return uv_timer_start(*this, cb, timeout, repeat);
+}
+
+#ifdef CMAKE_BUILD_WITH_CMAKE
+uv_tty_ptr::operator uv_stream_t*() const
+{
+ return reinterpret_cast<uv_stream_t*>(handle.get());
+}
+
+int uv_tty_ptr::init(uv_loop_t& loop, int fd, int readable, void* data)
+{
+ allocate(data);
+ return uv_tty_init(&loop, *this, fd, readable);
+}
+#endif
+
+template class uv_handle_ptr_base_<uv_handle_t>;
+
+#define UV_HANDLE_PTR_INSTANTIATE_EXPLICIT(NAME) \
+ template class uv_handle_ptr_base_<uv_##NAME##_t>; \
+ template class uv_handle_ptr_<uv_##NAME##_t>;
+
+UV_HANDLE_PTR_INSTANTIATE_EXPLICIT(signal)
+
+UV_HANDLE_PTR_INSTANTIATE_EXPLICIT(pipe)
+
+UV_HANDLE_PTR_INSTANTIATE_EXPLICIT(stream)
+
+UV_HANDLE_PTR_INSTANTIATE_EXPLICIT(process)
+
+UV_HANDLE_PTR_INSTANTIATE_EXPLICIT(timer)
+
+#ifdef CMAKE_BUILD_WITH_CMAKE
+UV_HANDLE_PTR_INSTANTIATE_EXPLICIT(async)
+
+UV_HANDLE_PTR_INSTANTIATE_EXPLICIT(tty)
+#endif
+}
diff --git a/Source/cmUVHandlePtr.h b/Source/cmUVHandlePtr.h
new file mode 100644
index 000000000..c09e4bfdf
--- /dev/null
+++ b/Source/cmUVHandlePtr.h
@@ -0,0 +1,271 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#pragma once
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <type_traits>
+
+#include "cm_uv.h"
+
+#if defined(__SUNPRO_CC)
+
+# include <utility>
+
+# define CM_INHERIT_CTOR(Class, Base, Tpl) \
+ template <typename... Args> \
+ Class(Args&&... args) \
+ : Base Tpl(std::forward<Args>(args)...) \
+ { \
+ }
+
+#else
+
+# define CM_INHERIT_CTOR(Class, Base, Tpl) using Base Tpl ::Base;
+
+#endif
+
+namespace cm {
+
+/***
+ * RAII class to simplify and ensure the safe usage of uv_loop_t. This includes
+ * making sure resources are properly freed.
+ */
+class uv_loop_ptr
+{
+protected:
+ std::shared_ptr<uv_loop_t> loop;
+
+public:
+ uv_loop_ptr(uv_loop_ptr const&) = delete;
+ uv_loop_ptr& operator=(uv_loop_ptr const&) = delete;
+ uv_loop_ptr(uv_loop_ptr&&) noexcept;
+ uv_loop_ptr& operator=(uv_loop_ptr&&) noexcept;
+
+ // Dtor and ctor need to be inline defined like this for default ctors and
+ // dtors to work. Some compilers do not like '= default' here.
+ uv_loop_ptr() {} // NOLINT(modernize-use-equals-default)
+ uv_loop_ptr(std::nullptr_t) {}
+ ~uv_loop_ptr() { this->reset(); }
+
+ int init(void* data = nullptr);
+
+ /**
+ * Properly close the handle if needed and sets the inner handle to nullptr
+ */
+ void reset();
+
+ /**
+ * Allow less verbose calling of uv_loop_* functions
+ * @return reinterpreted handle
+ */
+ operator uv_loop_t*();
+
+ uv_loop_t* get() const;
+ uv_loop_t* operator->() const noexcept;
+};
+
+/***
+ * RAII class to simplify and ensure the safe usage of uv_*_t types. This
+ * includes making sure resources are properly freed and contains casting
+ * operators which allow for passing into relevant uv_* functions.
+ *
+ *@tparam T actual uv_*_t type represented.
+ */
+template <typename T>
+class uv_handle_ptr_base_
+{
+protected:
+ template <typename _T>
+ friend class uv_handle_ptr_base_;
+
+ /**
+ * This must be a pointer type since the handle can outlive this class.
+ * When uv_close is eventually called on the handle, the memory the
+ * handle inhabits must be valid until the close callback is called
+ * which can be later on in the loop.
+ */
+ std::shared_ptr<T> handle;
+
+ /**
+ * Allocate memory for the type and optionally set it's 'data' pointer.
+ * Protected since this should only be called for an appropriate 'init'
+ * call.
+ *
+ * @param data data pointer to set
+ */
+ void allocate(void* data = nullptr);
+
+public:
+ uv_handle_ptr_base_(uv_handle_ptr_base_ const&) = delete;
+ uv_handle_ptr_base_& operator=(uv_handle_ptr_base_ const&) = delete;
+ uv_handle_ptr_base_(uv_handle_ptr_base_&&) noexcept;
+ uv_handle_ptr_base_& operator=(uv_handle_ptr_base_&&) noexcept;
+
+ /**
+ * This move constructor allows us to move out of a more specialized
+ * uv type into a less specialized one. The only constraint is that
+ * the right hand side is castable to T.
+ *
+ * This allows you to return uv_handle_ptr or uv_stream_ptr from a function
+ * that initializes something like uv_pipe_ptr or uv_tcp_ptr and interact
+ * and clean up after it without caring about the exact type.
+ */
+ template <typename S,
+ typename = typename std::enable_if<
+ std::is_rvalue_reference<S&&>::value>::type>
+ uv_handle_ptr_base_(S&& rhs)
+ {
+ // This will force a compiler error if rhs doesn't have a casting
+ // operator to get T*
+ this->handle = std::shared_ptr<T>(rhs.handle, rhs);
+ rhs.handle.reset();
+ }
+
+ // Dtor and ctor need to be inline defined like this for default ctors and
+ // dtors to work. Some compilers do not like '= default' here.
+ uv_handle_ptr_base_() {} // NOLINT(modernize-use-equals-default)
+ uv_handle_ptr_base_(std::nullptr_t) {}
+ ~uv_handle_ptr_base_() { reset(); }
+
+ /**
+ * Properly close the handle if needed and sets the inner handle to nullptr
+ */
+ void reset();
+
+ /**
+ * Allow less verbose calling of uv_handle_* functions
+ * @return reinterpreted handle
+ */
+ operator uv_handle_t*();
+
+ T* get() const;
+ T* operator->() const noexcept;
+};
+
+template <typename T>
+inline uv_handle_ptr_base_<T>::uv_handle_ptr_base_(
+ uv_handle_ptr_base_<T>&&) noexcept = default;
+template <typename T>
+inline uv_handle_ptr_base_<T>& uv_handle_ptr_base_<T>::operator=(
+ uv_handle_ptr_base_<T>&&) noexcept = default;
+
+/**
+ * While uv_handle_ptr_base_ only exposes uv_handle_t*, this exposes uv_T_t*
+ * too. It is broken out like this so we can reuse most of the code for the
+ * uv_handle_ptr class.
+ */
+template <typename T>
+class uv_handle_ptr_ : public uv_handle_ptr_base_<T>
+{
+ template <typename _T>
+ friend class uv_handle_ptr_;
+
+public:
+ CM_INHERIT_CTOR(uv_handle_ptr_, uv_handle_ptr_base_, <T>);
+
+ /***
+ * Allow less verbose calling of uv_<T> functions
+ * @return reinterpreted handle
+ */
+ operator T*() const;
+};
+
+/***
+ * This specialization is required to avoid duplicate 'operator uv_handle_t*()'
+ * declarations
+ */
+template <>
+class uv_handle_ptr_<uv_handle_t> : public uv_handle_ptr_base_<uv_handle_t>
+{
+public:
+ CM_INHERIT_CTOR(uv_handle_ptr_, uv_handle_ptr_base_, <uv_handle_t>);
+};
+
+class uv_async_ptr : public uv_handle_ptr_<uv_async_t>
+{
+public:
+ CM_INHERIT_CTOR(uv_async_ptr, uv_handle_ptr_, <uv_async_t>);
+
+ int init(uv_loop_t& loop, uv_async_cb async_cb, void* data = nullptr);
+
+ void send();
+};
+
+struct uv_signal_ptr : public uv_handle_ptr_<uv_signal_t>
+{
+ CM_INHERIT_CTOR(uv_signal_ptr, uv_handle_ptr_, <uv_signal_t>);
+
+ int init(uv_loop_t& loop, void* data = nullptr);
+
+ int start(uv_signal_cb cb, int signum);
+
+ void stop();
+};
+
+struct uv_pipe_ptr : public uv_handle_ptr_<uv_pipe_t>
+{
+ CM_INHERIT_CTOR(uv_pipe_ptr, uv_handle_ptr_, <uv_pipe_t>);
+
+ operator uv_stream_t*() const;
+
+ int init(uv_loop_t& loop, int ipc, void* data = nullptr);
+};
+
+struct uv_process_ptr : public uv_handle_ptr_<uv_process_t>
+{
+ CM_INHERIT_CTOR(uv_process_ptr, uv_handle_ptr_, <uv_process_t>);
+
+ int spawn(uv_loop_t& loop, uv_process_options_t const& options,
+ void* data = nullptr);
+};
+
+struct uv_timer_ptr : public uv_handle_ptr_<uv_timer_t>
+{
+ CM_INHERIT_CTOR(uv_timer_ptr, uv_handle_ptr_, <uv_timer_t>);
+
+ int init(uv_loop_t& loop, void* data = nullptr);
+
+ int start(uv_timer_cb cb, uint64_t timeout, uint64_t repeat);
+};
+
+struct uv_tty_ptr : public uv_handle_ptr_<uv_tty_t>
+{
+ CM_INHERIT_CTOR(uv_tty_ptr, uv_handle_ptr_, <uv_tty_t>);
+
+ operator uv_stream_t*() const;
+
+ int init(uv_loop_t& loop, int fd, int readable, void* data = nullptr);
+};
+
+typedef uv_handle_ptr_<uv_stream_t> uv_stream_ptr;
+typedef uv_handle_ptr_<uv_handle_t> uv_handle_ptr;
+
+#ifndef cmUVHandlePtr_cxx
+
+extern template class uv_handle_ptr_base_<uv_handle_t>;
+
+# define UV_HANDLE_PTR_INSTANTIATE_EXTERN(NAME) \
+ extern template class uv_handle_ptr_base_<uv_##NAME##_t>; \
+ extern template class uv_handle_ptr_<uv_##NAME##_t>;
+
+UV_HANDLE_PTR_INSTANTIATE_EXTERN(async)
+
+UV_HANDLE_PTR_INSTANTIATE_EXTERN(signal)
+
+UV_HANDLE_PTR_INSTANTIATE_EXTERN(pipe)
+
+UV_HANDLE_PTR_INSTANTIATE_EXTERN(process)
+
+UV_HANDLE_PTR_INSTANTIATE_EXTERN(stream)
+
+UV_HANDLE_PTR_INSTANTIATE_EXTERN(timer)
+
+UV_HANDLE_PTR_INSTANTIATE_EXTERN(tty)
+
+# undef UV_HANDLE_PTR_INSTANTIATE_EXTERN
+
+#endif
+}
diff --git a/Source/cmUVProcessChain.cxx b/Source/cmUVProcessChain.cxx
new file mode 100644
index 000000000..90ece0bc5
--- /dev/null
+++ b/Source/cmUVProcessChain.cxx
@@ -0,0 +1,395 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmUVProcessChain.h"
+
+#include "cmAlgorithms.h"
+#include "cmGetPipes.h"
+#include "cmUVHandlePtr.h"
+#include "cmUVStreambuf.h"
+#include "cm_uv.h"
+
+#include <assert.h>
+
+#include <iterator>
+#include <memory>
+#include <utility>
+
+struct cmUVProcessChain::InternalData
+{
+ struct BasicStreamData
+ {
+ cmUVStreambuf Streambuf;
+ cm::uv_pipe_ptr BuiltinStream;
+ uv_stdio_container_t Stdio;
+ };
+
+ template <typename IOStream>
+ struct StreamData : public BasicStreamData
+ {
+ StreamData()
+ : BuiltinIOStream(&this->Streambuf)
+ {
+ }
+
+ IOStream BuiltinIOStream;
+
+ IOStream* GetBuiltinStream()
+ {
+ if (this->BuiltinStream.get()) {
+ return &this->BuiltinIOStream;
+ }
+ return nullptr;
+ }
+ };
+
+ struct ProcessData
+ {
+ cmUVProcessChain::InternalData* Data;
+ cm::uv_process_ptr Process;
+ cm::uv_pipe_ptr OutputPipe;
+ bool Finished = false;
+ Status ProcessStatus;
+ };
+
+ const cmUVProcessChainBuilder* Builder = nullptr;
+
+ bool Valid = false;
+
+ cm::uv_loop_ptr Loop;
+
+ StreamData<std::istream> OutputStreamData;
+ StreamData<std::istream> ErrorStreamData;
+
+ unsigned int ProcessesCompleted = 0;
+ std::vector<std::unique_ptr<ProcessData>> Processes;
+
+ bool Prepare(const cmUVProcessChainBuilder* builder);
+ bool AddCommand(const cmUVProcessChainBuilder::ProcessConfiguration& config,
+ bool first, bool last);
+ bool Finish();
+
+ static const Status* GetStatus(const ProcessData& data);
+};
+
+cmUVProcessChainBuilder::cmUVProcessChainBuilder()
+{
+ this->SetNoStream(Stream_INPUT)
+ .SetNoStream(Stream_OUTPUT)
+ .SetNoStream(Stream_ERROR);
+}
+
+cmUVProcessChainBuilder& cmUVProcessChainBuilder::AddCommand(
+ const std::vector<std::string>& arguments)
+{
+ if (!arguments.empty()) {
+ this->Processes.emplace_back();
+ this->Processes.back().Arguments = arguments;
+ }
+ return *this;
+}
+
+cmUVProcessChainBuilder& cmUVProcessChainBuilder::SetNoStream(Stream stdio)
+{
+ switch (stdio) {
+ case Stream_INPUT:
+ case Stream_OUTPUT:
+ case Stream_ERROR: {
+ auto& streamData = this->Stdio[stdio];
+ streamData.Type = None;
+ break;
+ }
+ }
+ return *this;
+}
+
+cmUVProcessChainBuilder& cmUVProcessChainBuilder::SetBuiltinStream(
+ Stream stdio)
+{
+ switch (stdio) {
+ case Stream_INPUT:
+ // FIXME
+ break;
+
+ case Stream_OUTPUT:
+ case Stream_ERROR: {
+ auto& streamData = this->Stdio[stdio];
+ streamData.Type = Builtin;
+ break;
+ }
+ }
+ return *this;
+}
+
+cmUVProcessChainBuilder& cmUVProcessChainBuilder::SetExternalStream(
+ Stream stdio, int fd)
+{
+ switch (stdio) {
+ case Stream_INPUT:
+ // FIXME
+ break;
+
+ case Stream_OUTPUT:
+ case Stream_ERROR: {
+ auto& streamData = this->Stdio[stdio];
+ streamData.Type = External;
+ streamData.FileDescriptor = fd;
+ break;
+ }
+ }
+ return *this;
+}
+
+cmUVProcessChain cmUVProcessChainBuilder::Start() const
+{
+ cmUVProcessChain chain;
+
+ if (!chain.Data->Prepare(this)) {
+ return chain;
+ }
+
+ for (auto it = this->Processes.begin(); it != this->Processes.end(); ++it) {
+ if (!chain.Data->AddCommand(*it, it == this->Processes.begin(),
+ it == std::prev(this->Processes.end()))) {
+ return chain;
+ }
+ }
+
+ chain.Data->Finish();
+
+ return chain;
+}
+
+const cmUVProcessChain::Status* cmUVProcessChain::InternalData::GetStatus(
+ const cmUVProcessChain::InternalData::ProcessData& data)
+{
+ if (data.Finished) {
+ return &data.ProcessStatus;
+ }
+ return nullptr;
+}
+
+bool cmUVProcessChain::InternalData::Prepare(
+ const cmUVProcessChainBuilder* builder)
+{
+ this->Builder = builder;
+
+ auto const& output =
+ this->Builder->Stdio[cmUVProcessChainBuilder::Stream_OUTPUT];
+ auto& outputData = this->OutputStreamData;
+ switch (output.Type) {
+ case cmUVProcessChainBuilder::None:
+ outputData.Stdio.flags = UV_IGNORE;
+ break;
+
+ case cmUVProcessChainBuilder::Builtin:
+ outputData.BuiltinStream.init(*this->Loop, 0);
+ outputData.Stdio.flags =
+ static_cast<uv_stdio_flags>(UV_CREATE_PIPE | UV_WRITABLE_PIPE);
+ outputData.Stdio.data.stream = outputData.BuiltinStream;
+ break;
+
+ case cmUVProcessChainBuilder::External:
+ outputData.Stdio.flags = UV_INHERIT_FD;
+ outputData.Stdio.data.fd = output.FileDescriptor;
+ break;
+ }
+
+ auto const& error =
+ this->Builder->Stdio[cmUVProcessChainBuilder::Stream_ERROR];
+ auto& errorData = this->ErrorStreamData;
+ switch (error.Type) {
+ case cmUVProcessChainBuilder::None:
+ errorData.Stdio.flags = UV_IGNORE;
+ break;
+
+ case cmUVProcessChainBuilder::Builtin: {
+ int pipeFd[2];
+ if (cmGetPipes(pipeFd) < 0) {
+ return false;
+ }
+
+ errorData.BuiltinStream.init(*this->Loop, 0);
+ if (uv_pipe_open(errorData.BuiltinStream, pipeFd[0]) < 0) {
+ return false;
+ }
+ errorData.Stdio.flags = UV_INHERIT_FD;
+ errorData.Stdio.data.fd = pipeFd[1];
+ break;
+ }
+
+ case cmUVProcessChainBuilder::External:
+ errorData.Stdio.flags = UV_INHERIT_FD;
+ errorData.Stdio.data.fd = error.FileDescriptor;
+ break;
+ }
+
+ return true;
+}
+
+bool cmUVProcessChain::InternalData::AddCommand(
+ const cmUVProcessChainBuilder::ProcessConfiguration& config, bool first,
+ bool last)
+{
+ this->Processes.emplace_back(cm::make_unique<ProcessData>());
+ auto& process = *this->Processes.back();
+ process.Data = this;
+
+ auto options = uv_process_options_t();
+
+ // Bounds were checked at add time, first element is guaranteed to exist
+ options.file = config.Arguments[0].c_str();
+
+ std::vector<const char*> arguments;
+ for (auto const& arg : config.Arguments) {
+ arguments.push_back(arg.c_str());
+ }
+ arguments.push_back(nullptr);
+ options.args = const_cast<char**>(arguments.data());
+ options.flags = UV_PROCESS_WINDOWS_HIDE;
+
+ std::array<uv_stdio_container_t, 3> stdio;
+ stdio[0] = uv_stdio_container_t();
+ if (first) {
+ stdio[0].flags = UV_IGNORE;
+ } else {
+ assert(this->Processes.size() >= 2);
+ auto& prev = *this->Processes[this->Processes.size() - 2];
+ stdio[0].flags = UV_INHERIT_STREAM;
+ stdio[0].data.stream = prev.OutputPipe;
+ }
+ if (last) {
+ stdio[1] = this->OutputStreamData.Stdio;
+ } else {
+ if (process.OutputPipe.init(*this->Loop, 0) < 0) {
+ return false;
+ }
+ stdio[1] = uv_stdio_container_t();
+ stdio[1].flags =
+ static_cast<uv_stdio_flags>(UV_CREATE_PIPE | UV_WRITABLE_PIPE);
+ stdio[1].data.stream = process.OutputPipe;
+ }
+ stdio[2] = this->ErrorStreamData.Stdio;
+
+ options.stdio = stdio.data();
+ options.stdio_count = 3;
+ options.exit_cb = [](uv_process_t* handle, int64_t exitStatus,
+ int termSignal) {
+ auto* processData = static_cast<ProcessData*>(handle->data);
+ processData->Finished = true;
+ processData->ProcessStatus.ExitStatus = exitStatus;
+ processData->ProcessStatus.TermSignal = termSignal;
+ processData->Data->ProcessesCompleted++;
+ };
+
+ return process.Process.spawn(*this->Loop, options, &process) >= 0;
+}
+
+bool cmUVProcessChain::InternalData::Finish()
+{
+ if (this->Builder->Stdio[cmUVProcessChainBuilder::Stream_OUTPUT].Type ==
+ cmUVProcessChainBuilder::Builtin) {
+ this->OutputStreamData.Streambuf.open(
+ this->OutputStreamData.BuiltinStream);
+ }
+
+ if (this->Builder->Stdio[cmUVProcessChainBuilder::Stream_ERROR].Type ==
+ cmUVProcessChainBuilder::Builtin) {
+ cm::uv_pipe_ptr tmpPipe;
+ if (tmpPipe.init(*this->Loop, 0) < 0) {
+ return false;
+ }
+ if (uv_pipe_open(tmpPipe, this->ErrorStreamData.Stdio.data.fd) < 0) {
+ return false;
+ }
+ tmpPipe.reset();
+
+ this->ErrorStreamData.Streambuf.open(this->ErrorStreamData.BuiltinStream);
+ }
+
+ this->Valid = true;
+ return true;
+}
+
+cmUVProcessChain::cmUVProcessChain()
+ : Data(cm::make_unique<InternalData>())
+{
+ this->Data->Loop.init();
+}
+
+cmUVProcessChain::cmUVProcessChain(cmUVProcessChain&& other) noexcept
+ : Data(std::move(other.Data))
+{
+}
+
+cmUVProcessChain::~cmUVProcessChain() = default;
+
+cmUVProcessChain& cmUVProcessChain::operator=(
+ cmUVProcessChain&& other) noexcept
+{
+ this->Data = std::move(other.Data);
+ return *this;
+}
+
+uv_loop_t& cmUVProcessChain::GetLoop()
+{
+ return *this->Data->Loop;
+}
+
+std::istream* cmUVProcessChain::OutputStream()
+{
+ return this->Data->OutputStreamData.GetBuiltinStream();
+}
+
+std::istream* cmUVProcessChain::ErrorStream()
+{
+ return this->Data->ErrorStreamData.GetBuiltinStream();
+}
+
+bool cmUVProcessChain::Valid() const
+{
+ return this->Data->Valid;
+}
+
+bool cmUVProcessChain::Wait(int64_t milliseconds)
+{
+ bool timeout = false;
+ cm::uv_timer_ptr timer;
+
+ if (milliseconds >= 0) {
+ timer.init(*this->Data->Loop, &timeout);
+ timer.start(
+ [](uv_timer_t* handle) {
+ auto* timeoutPtr = static_cast<bool*>(handle->data);
+ *timeoutPtr = true;
+ },
+ milliseconds, 0);
+ }
+
+ while (!timeout &&
+ this->Data->ProcessesCompleted < this->Data->Processes.size()) {
+ uv_run(this->Data->Loop, UV_RUN_ONCE);
+ }
+
+ return !timeout;
+}
+
+std::vector<const cmUVProcessChain::Status*> cmUVProcessChain::GetStatus()
+ const
+{
+ std::vector<const cmUVProcessChain::Status*> statuses(
+ this->Data->Processes.size(), nullptr);
+ for (std::size_t i = 0; i < statuses.size(); i++) {
+ statuses[i] = this->GetStatus(i);
+ }
+ return statuses;
+}
+
+const cmUVProcessChain::Status* cmUVProcessChain::GetStatus(
+ std::size_t index) const
+{
+ auto const& process = *this->Data->Processes[index];
+ if (process.Finished) {
+ return &process.ProcessStatus;
+ }
+ return nullptr;
+}
diff --git a/Source/cmUVProcessChain.h b/Source/cmUVProcessChain.h
new file mode 100644
index 000000000..2b3352070
--- /dev/null
+++ b/Source/cmUVProcessChain.h
@@ -0,0 +1,100 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#ifndef cmUVProcessChain_h
+#define cmUVProcessChain_h
+
+#include "cm_uv.h"
+
+#include <array>
+#include <iosfwd>
+#include <memory> // IWYU pragma: keep
+#include <string>
+#include <vector>
+
+#include <stdint.h>
+
+class cmUVProcessChain;
+
+class cmUVProcessChainBuilder
+{
+public:
+ enum Stream
+ {
+ Stream_INPUT = 0,
+ Stream_OUTPUT = 1,
+ Stream_ERROR = 2,
+ };
+
+ cmUVProcessChainBuilder();
+
+ cmUVProcessChainBuilder& AddCommand(
+ const std::vector<std::string>& arguments);
+ cmUVProcessChainBuilder& SetNoStream(Stream stdio);
+ cmUVProcessChainBuilder& SetBuiltinStream(Stream stdio);
+ cmUVProcessChainBuilder& SetExternalStream(Stream stdio, int fd);
+
+ cmUVProcessChain Start() const;
+
+private:
+ enum StdioType
+ {
+ None,
+ Builtin,
+ External,
+ };
+
+ friend class cmUVProcessChain;
+
+ struct StdioConfiguration
+ {
+ StdioType Type;
+ int FileDescriptor;
+ };
+
+ struct ProcessConfiguration
+ {
+ std::vector<std::string> Arguments;
+ };
+
+ std::array<StdioConfiguration, 3> Stdio;
+ std::vector<ProcessConfiguration> Processes;
+};
+
+class cmUVProcessChain
+{
+public:
+ struct Status
+ {
+ int64_t ExitStatus;
+ int TermSignal;
+ };
+
+ cmUVProcessChain(const cmUVProcessChain& other) = delete;
+ cmUVProcessChain(cmUVProcessChain&& other) noexcept;
+
+ ~cmUVProcessChain();
+
+ cmUVProcessChain& operator=(const cmUVProcessChain& other) = delete;
+ cmUVProcessChain& operator=(cmUVProcessChain&& other) noexcept;
+
+ uv_loop_t& GetLoop();
+
+ // FIXME: Add stdin support
+ std::istream* OutputStream();
+ std::istream* ErrorStream();
+
+ bool Valid() const;
+ bool Wait(int64_t milliseconds = -1);
+ std::vector<const Status*> GetStatus() const;
+ const Status* GetStatus(std::size_t index) const;
+
+private:
+ friend class cmUVProcessChainBuilder;
+
+ cmUVProcessChain();
+
+ struct InternalData;
+ std::unique_ptr<InternalData> Data;
+};
+
+#endif
diff --git a/Source/cmUVSignalHackRAII.h b/Source/cmUVSignalHackRAII.h
new file mode 100644
index 000000000..63599db82
--- /dev/null
+++ b/Source/cmUVSignalHackRAII.h
@@ -0,0 +1,45 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#pragma once
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include "cm_uv.h"
+
+#if defined(CMAKE_USE_SYSTEM_LIBUV) && !defined(_WIN32) && \
+ UV_VERSION_MAJOR == 1 && UV_VERSION_MINOR < 19
+# define CMAKE_UV_SIGNAL_HACK
+# include "cmUVHandlePtr.h"
+/*
+ libuv does not use SA_RESTART on its signal handler, but C++ streams
+ depend on it for reliable i/o operations. This RAII helper convinces
+ libuv to install its handler, and then revises the handler to add the
+ SA_RESTART flag. We use a distinct uv loop that never runs to avoid
+ ever really getting a callback. libuv may fill the hack loop's signal
+ pipe and then stop writing, but that won't break any real loops.
+ */
+class cmUVSignalHackRAII
+{
+ uv_loop_t HackLoop;
+ cm::uv_signal_ptr HackSignal;
+ static void HackCB(uv_signal_t*, int) {}
+
+public:
+ cmUVSignalHackRAII()
+ {
+ uv_loop_init(&this->HackLoop);
+ this->HackSignal.init(this->HackLoop);
+ this->HackSignal.start(HackCB, SIGCHLD);
+ struct sigaction hack_sa;
+ sigaction(SIGCHLD, nullptr, &hack_sa);
+ if (!(hack_sa.sa_flags & SA_RESTART)) {
+ hack_sa.sa_flags |= SA_RESTART;
+ sigaction(SIGCHLD, &hack_sa, nullptr);
+ }
+ }
+ ~cmUVSignalHackRAII()
+ {
+ this->HackSignal.stop();
+ uv_loop_close(&this->HackLoop);
+ }
+};
+#endif
diff --git a/Source/cmUVStreambuf.h b/Source/cmUVStreambuf.h
new file mode 100644
index 000000000..873352b1a
--- /dev/null
+++ b/Source/cmUVStreambuf.h
@@ -0,0 +1,219 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#ifndef cmUVStreambuf_h
+#define cmUVStreambuf_h
+
+#include "cmUVHandlePtr.h"
+
+#include "cm_uv.h"
+
+#include <algorithm>
+#include <cstring>
+#include <streambuf>
+#include <vector>
+
+/*
+ * This file is based on example code from:
+ *
+ * http://www.voidcn.com/article/p-vjnlygmc-gy.html
+ *
+ * The example code was distributed under the following license:
+ *
+ * Copyright 2007 Edd Dawson.
+ * Distributed under the Boost Software License, Version 1.0.
+ *
+ * Boost Software License - Version 1.0 - August 17th, 2003
+ *
+ * Permission is hereby granted, free of charge, to any person or organization
+ * obtaining a copy of the software and accompanying documentation covered by
+ * this license (the "Software") to use, reproduce, display, distribute,
+ * execute, and transmit the Software, and to prepare derivative works of the
+ * Software, and to permit third-parties to whom the Software is furnished to
+ * do so, all subject to the following:
+ *
+ * The copyright notices in the Software and this entire statement, including
+ * the above license grant, this restriction and the following disclaimer,
+ * must be included in all copies of the Software, in whole or in part, and
+ * all derivative works of the Software, unless such copies or derivative
+ * works are solely in the form of machine-executable object code generated by
+ * a source language processor.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+ * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+ * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+template <typename CharT, typename Traits = std::char_traits<CharT>>
+class cmBasicUVStreambuf : public std::basic_streambuf<CharT, Traits>
+{
+public:
+ cmBasicUVStreambuf(std::size_t bufSize = 256, std::size_t putBack = 8);
+ ~cmBasicUVStreambuf() override;
+
+ bool is_open() const;
+
+ cmBasicUVStreambuf* open(uv_stream_t* stream);
+
+ cmBasicUVStreambuf* close();
+
+protected:
+ typename cmBasicUVStreambuf::int_type underflow() override;
+ std::streamsize showmanyc() override;
+
+ // FIXME: Add write support
+
+private:
+ uv_stream_t* Stream = nullptr;
+ void* OldStreamData = nullptr;
+ const std::size_t PutBack = 0;
+ std::vector<CharT> InputBuffer;
+ bool EndOfFile = false;
+
+ void StreamReadStartStop();
+
+ void StreamRead(ssize_t nread);
+ void HandleAlloc(uv_buf_t* buf);
+};
+
+template <typename CharT, typename Traits>
+cmBasicUVStreambuf<CharT, Traits>::cmBasicUVStreambuf(std::size_t bufSize,
+ std::size_t putBack)
+ : PutBack(std::max<std::size_t>(putBack, 1))
+ , InputBuffer(std::max<std::size_t>(this->PutBack, bufSize) + this->PutBack)
+{
+ this->close();
+}
+
+template <typename CharT, typename Traits>
+cmBasicUVStreambuf<CharT, Traits>::~cmBasicUVStreambuf()
+{
+ this->close();
+}
+
+template <typename CharT, typename Traits>
+bool cmBasicUVStreambuf<CharT, Traits>::is_open() const
+{
+ return this->Stream != nullptr;
+}
+
+template <typename CharT, typename Traits>
+cmBasicUVStreambuf<CharT, Traits>* cmBasicUVStreambuf<CharT, Traits>::open(
+ uv_stream_t* stream)
+{
+ this->close();
+ this->Stream = stream;
+ this->EndOfFile = false;
+ if (this->Stream) {
+ this->OldStreamData = this->Stream->data;
+ this->Stream->data = this;
+ }
+ this->StreamReadStartStop();
+ return this;
+}
+
+template <typename CharT, typename Traits>
+cmBasicUVStreambuf<CharT, Traits>* cmBasicUVStreambuf<CharT, Traits>::close()
+{
+ if (this->Stream) {
+ uv_read_stop(this->Stream);
+ this->Stream->data = this->OldStreamData;
+ }
+ this->Stream = nullptr;
+ CharT* readEnd = this->InputBuffer.data() + this->InputBuffer.size();
+ this->setg(readEnd, readEnd, readEnd);
+ return this;
+}
+
+template <typename CharT, typename Traits>
+typename cmBasicUVStreambuf<CharT, Traits>::int_type
+cmBasicUVStreambuf<CharT, Traits>::underflow()
+{
+ if (!this->is_open()) {
+ return Traits::eof();
+ }
+
+ if (this->gptr() < this->egptr()) {
+ return Traits::to_int_type(*this->gptr());
+ }
+
+ this->StreamReadStartStop();
+ while (this->in_avail() == 0) {
+ uv_run(this->Stream->loop, UV_RUN_ONCE);
+ }
+ if (this->in_avail() == -1) {
+ return Traits::eof();
+ }
+ return Traits::to_int_type(*this->gptr());
+}
+
+template <typename CharT, typename Traits>
+std::streamsize cmBasicUVStreambuf<CharT, Traits>::showmanyc()
+{
+ if (!this->is_open() || this->EndOfFile) {
+ return -1;
+ }
+ return 0;
+}
+
+template <typename CharT, typename Traits>
+void cmBasicUVStreambuf<CharT, Traits>::StreamReadStartStop()
+{
+ if (this->Stream) {
+ uv_read_stop(this->Stream);
+ if (this->gptr() >= this->egptr()) {
+ uv_read_start(
+ this->Stream,
+ [](uv_handle_t* handle, size_t /* unused */, uv_buf_t* buf) {
+ auto streambuf =
+ static_cast<cmBasicUVStreambuf<CharT, Traits>*>(handle->data);
+ streambuf->HandleAlloc(buf);
+ },
+ [](uv_stream_t* stream2, ssize_t nread, const uv_buf_t* /* unused */) {
+ auto streambuf =
+ static_cast<cmBasicUVStreambuf<CharT, Traits>*>(stream2->data);
+ streambuf->StreamRead(nread);
+ });
+ }
+ }
+}
+
+template <typename CharT, typename Traits>
+void cmBasicUVStreambuf<CharT, Traits>::HandleAlloc(uv_buf_t* buf)
+{
+ auto size = this->egptr() - this->gptr();
+ std::memmove(this->InputBuffer.data(), this->gptr(),
+ this->egptr() - this->gptr());
+ this->setg(this->InputBuffer.data(), this->InputBuffer.data(),
+ this->InputBuffer.data() + size);
+ buf->base = this->egptr();
+#ifdef _WIN32
+# define BUF_LEN_TYPE ULONG
+#else
+# define BUF_LEN_TYPE size_t
+#endif
+ buf->len = BUF_LEN_TYPE(
+ (this->InputBuffer.data() + this->InputBuffer.size() - this->egptr()) *
+ sizeof(CharT));
+#undef BUF_LEN_TYPE
+}
+
+template <typename CharT, typename Traits>
+void cmBasicUVStreambuf<CharT, Traits>::StreamRead(ssize_t nread)
+{
+ if (nread > 0) {
+ this->setg(this->eback(), this->gptr(),
+ this->egptr() + nread / sizeof(CharT));
+ uv_read_stop(this->Stream);
+ } else if (nread < 0 /*|| nread == UV_EOF*/) {
+ this->EndOfFile = true;
+ uv_read_stop(this->Stream);
+ }
+}
+
+using cmUVStreambuf = cmBasicUVStreambuf<char>;
+
+#endif
diff --git a/Source/cmWorkerPool.cxx b/Source/cmWorkerPool.cxx
new file mode 100644
index 000000000..cbf070e91
--- /dev/null
+++ b/Source/cmWorkerPool.cxx
@@ -0,0 +1,763 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#include "cmWorkerPool.h"
+
+#include "cmRange.h"
+#include "cmUVHandlePtr.h"
+#include "cmUVSignalHackRAII.h" // IWYU pragma: keep
+#include "cm_uv.h"
+
+#include <algorithm>
+#include <array>
+#include <condition_variable>
+#include <deque>
+#include <functional>
+#include <mutex>
+#include <stddef.h>
+#include <thread>
+
+/**
+ * @brief libuv pipe buffer class
+ */
+class cmUVPipeBuffer
+{
+public:
+ typedef cmRange<char const*> DataRange;
+ typedef std::function<void(DataRange)> DataFunction;
+ /// On error the ssize_t argument is a non zero libuv error code
+ typedef std::function<void(ssize_t)> EndFunction;
+
+public:
+ /**
+ * Reset to construction state
+ */
+ void reset();
+
+ /**
+ * Initializes uv_pipe(), uv_stream() and uv_handle()
+ * @return true on success
+ */
+ bool init(uv_loop_t* uv_loop);
+
+ /**
+ * Start reading
+ * @return true on success
+ */
+ bool startRead(DataFunction dataFunction, EndFunction endFunction);
+
+ //! libuv pipe
+ uv_pipe_t* uv_pipe() const { return UVPipe_.get(); }
+ //! uv_pipe() casted to libuv stream
+ uv_stream_t* uv_stream() const { return static_cast<uv_stream_t*>(UVPipe_); }
+ //! uv_pipe() casted to libuv handle
+ uv_handle_t* uv_handle() { return static_cast<uv_handle_t*>(UVPipe_); }
+
+private:
+ // -- Libuv callbacks
+ static void UVAlloc(uv_handle_t* handle, size_t suggestedSize,
+ uv_buf_t* buf);
+ static void UVData(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf);
+
+private:
+ cm::uv_pipe_ptr UVPipe_;
+ std::vector<char> Buffer_;
+ DataFunction DataFunction_;
+ EndFunction EndFunction_;
+};
+
+void cmUVPipeBuffer::reset()
+{
+ if (UVPipe_.get() != nullptr) {
+ EndFunction_ = nullptr;
+ DataFunction_ = nullptr;
+ Buffer_.clear();
+ Buffer_.shrink_to_fit();
+ UVPipe_.reset();
+ }
+}
+
+bool cmUVPipeBuffer::init(uv_loop_t* uv_loop)
+{
+ reset();
+ if (uv_loop == nullptr) {
+ return false;
+ }
+ int ret = UVPipe_.init(*uv_loop, 0, this);
+ return (ret == 0);
+}
+
+bool cmUVPipeBuffer::startRead(DataFunction dataFunction,
+ EndFunction endFunction)
+{
+ if (UVPipe_.get() == nullptr) {
+ return false;
+ }
+ if (!dataFunction || !endFunction) {
+ return false;
+ }
+ DataFunction_ = std::move(dataFunction);
+ EndFunction_ = std::move(endFunction);
+ int ret = uv_read_start(uv_stream(), &cmUVPipeBuffer::UVAlloc,
+ &cmUVPipeBuffer::UVData);
+ return (ret == 0);
+}
+
+void cmUVPipeBuffer::UVAlloc(uv_handle_t* handle, size_t suggestedSize,
+ uv_buf_t* buf)
+{
+ auto& pipe = *reinterpret_cast<cmUVPipeBuffer*>(handle->data);
+ pipe.Buffer_.resize(suggestedSize);
+ buf->base = pipe.Buffer_.data();
+ buf->len = static_cast<unsigned long>(pipe.Buffer_.size());
+}
+
+void cmUVPipeBuffer::UVData(uv_stream_t* stream, ssize_t nread,
+ const uv_buf_t* buf)
+{
+ auto& pipe = *reinterpret_cast<cmUVPipeBuffer*>(stream->data);
+ if (nread > 0) {
+ if (buf->base != nullptr) {
+ // Call data function
+ pipe.DataFunction_(DataRange(buf->base, buf->base + nread));
+ }
+ } else if (nread < 0) {
+ // Save the end function on the stack before resetting the pipe
+ EndFunction efunc;
+ efunc.swap(pipe.EndFunction_);
+ // Reset pipe before calling the end function
+ pipe.reset();
+ // Call end function
+ efunc((nread == UV_EOF) ? 0 : nread);
+ }
+}
+
+/**
+ * @brief External process management class
+ */
+class cmUVReadOnlyProcess
+{
+public:
+ // -- Types
+ //! @brief Process settings
+ struct SetupT
+ {
+ std::string WorkingDirectory;
+ std::vector<std::string> Command;
+ cmWorkerPool::ProcessResultT* Result = nullptr;
+ bool MergedOutput = false;
+ };
+
+public:
+ // -- Const accessors
+ SetupT const& Setup() const { return Setup_; }
+ cmWorkerPool::ProcessResultT* Result() const { return Setup_.Result; }
+ bool IsStarted() const { return IsStarted_; }
+ bool IsFinished() const { return IsFinished_; }
+
+ // -- Runtime
+ void setup(cmWorkerPool::ProcessResultT* result, bool mergedOutput,
+ std::vector<std::string> const& command,
+ std::string const& workingDirectory = std::string());
+ bool start(uv_loop_t* uv_loop, std::function<void()> finishedCallback);
+
+private:
+ // -- Libuv callbacks
+ static void UVExit(uv_process_t* handle, int64_t exitStatus, int termSignal);
+ void UVPipeOutData(cmUVPipeBuffer::DataRange data);
+ void UVPipeOutEnd(ssize_t error);
+ void UVPipeErrData(cmUVPipeBuffer::DataRange data);
+ void UVPipeErrEnd(ssize_t error);
+ void UVTryFinish();
+
+private:
+ // -- Setup
+ SetupT Setup_;
+ // -- Runtime
+ bool IsStarted_ = false;
+ bool IsFinished_ = false;
+ std::function<void()> FinishedCallback_;
+ std::vector<const char*> CommandPtr_;
+ std::array<uv_stdio_container_t, 3> UVOptionsStdIO_;
+ uv_process_options_t UVOptions_;
+ cm::uv_process_ptr UVProcess_;
+ cmUVPipeBuffer UVPipeOut_;
+ cmUVPipeBuffer UVPipeErr_;
+};
+
+void cmUVReadOnlyProcess::setup(cmWorkerPool::ProcessResultT* result,
+ bool mergedOutput,
+ std::vector<std::string> const& command,
+ std::string const& workingDirectory)
+{
+ Setup_.WorkingDirectory = workingDirectory;
+ Setup_.Command = command;
+ Setup_.Result = result;
+ Setup_.MergedOutput = mergedOutput;
+}
+
+bool cmUVReadOnlyProcess::start(uv_loop_t* uv_loop,
+ std::function<void()> finishedCallback)
+{
+ if (IsStarted() || (Result() == nullptr)) {
+ return false;
+ }
+
+ // Reset result before the start
+ Result()->reset();
+
+ // Fill command string pointers
+ if (!Setup().Command.empty()) {
+ CommandPtr_.reserve(Setup().Command.size() + 1);
+ for (std::string const& arg : Setup().Command) {
+ CommandPtr_.push_back(arg.c_str());
+ }
+ CommandPtr_.push_back(nullptr);
+ } else {
+ Result()->ErrorMessage = "Empty command";
+ }
+
+ if (!Result()->error()) {
+ if (!UVPipeOut_.init(uv_loop)) {
+ Result()->ErrorMessage = "libuv stdout pipe initialization failed";
+ }
+ }
+ if (!Result()->error()) {
+ if (!UVPipeErr_.init(uv_loop)) {
+ Result()->ErrorMessage = "libuv stderr pipe initialization failed";
+ }
+ }
+ if (!Result()->error()) {
+ // -- Setup process stdio options
+ // stdin
+ UVOptionsStdIO_[0].flags = UV_IGNORE;
+ UVOptionsStdIO_[0].data.stream = nullptr;
+ // stdout
+ UVOptionsStdIO_[1].flags =
+ static_cast<uv_stdio_flags>(UV_CREATE_PIPE | UV_WRITABLE_PIPE);
+ UVOptionsStdIO_[1].data.stream = UVPipeOut_.uv_stream();
+ // stderr
+ UVOptionsStdIO_[2].flags =
+ static_cast<uv_stdio_flags>(UV_CREATE_PIPE | UV_WRITABLE_PIPE);
+ UVOptionsStdIO_[2].data.stream = UVPipeErr_.uv_stream();
+
+ // -- Setup process options
+ std::fill_n(reinterpret_cast<char*>(&UVOptions_), sizeof(UVOptions_), 0);
+ UVOptions_.exit_cb = &cmUVReadOnlyProcess::UVExit;
+ UVOptions_.file = CommandPtr_[0];
+ UVOptions_.args = const_cast<char**>(CommandPtr_.data());
+ UVOptions_.cwd = Setup_.WorkingDirectory.c_str();
+ UVOptions_.flags = UV_PROCESS_WINDOWS_HIDE;
+ UVOptions_.stdio_count = static_cast<int>(UVOptionsStdIO_.size());
+ UVOptions_.stdio = UVOptionsStdIO_.data();
+
+ // -- Spawn process
+ int uvErrorCode = UVProcess_.spawn(*uv_loop, UVOptions_, this);
+ if (uvErrorCode != 0) {
+ Result()->ErrorMessage = "libuv process spawn failed";
+ if (const char* uvErr = uv_strerror(uvErrorCode)) {
+ Result()->ErrorMessage += ": ";
+ Result()->ErrorMessage += uvErr;
+ }
+ }
+ }
+ // -- Start reading from stdio streams
+ if (!Result()->error()) {
+ if (!UVPipeOut_.startRead(
+ [this](cmUVPipeBuffer::DataRange range) {
+ this->UVPipeOutData(range);
+ },
+ [this](ssize_t error) { this->UVPipeOutEnd(error); })) {
+ Result()->ErrorMessage = "libuv start reading from stdout pipe failed";
+ }
+ }
+ if (!Result()->error()) {
+ if (!UVPipeErr_.startRead(
+ [this](cmUVPipeBuffer::DataRange range) {
+ this->UVPipeErrData(range);
+ },
+ [this](ssize_t error) { this->UVPipeErrEnd(error); })) {
+ Result()->ErrorMessage = "libuv start reading from stderr pipe failed";
+ }
+ }
+
+ if (!Result()->error()) {
+ IsStarted_ = true;
+ FinishedCallback_ = std::move(finishedCallback);
+ } else {
+ // Clear libuv handles and finish
+ UVProcess_.reset();
+ UVPipeOut_.reset();
+ UVPipeErr_.reset();
+ CommandPtr_.clear();
+ }
+
+ return IsStarted();
+}
+
+void cmUVReadOnlyProcess::UVExit(uv_process_t* handle, int64_t exitStatus,
+ int termSignal)
+{
+ auto& proc = *reinterpret_cast<cmUVReadOnlyProcess*>(handle->data);
+ if (proc.IsStarted() && !proc.IsFinished()) {
+ // Set error message on demand
+ proc.Result()->ExitStatus = exitStatus;
+ proc.Result()->TermSignal = termSignal;
+ if (!proc.Result()->error()) {
+ if (termSignal != 0) {
+ proc.Result()->ErrorMessage = "Process was terminated by signal ";
+ proc.Result()->ErrorMessage +=
+ std::to_string(proc.Result()->TermSignal);
+ } else if (exitStatus != 0) {
+ proc.Result()->ErrorMessage = "Process failed with return value ";
+ proc.Result()->ErrorMessage +=
+ std::to_string(proc.Result()->ExitStatus);
+ }
+ }
+
+ // Reset process handle
+ proc.UVProcess_.reset();
+ // Try finish
+ proc.UVTryFinish();
+ }
+}
+
+void cmUVReadOnlyProcess::UVPipeOutData(cmUVPipeBuffer::DataRange data)
+{
+ Result()->StdOut.append(data.begin(), data.end());
+}
+
+void cmUVReadOnlyProcess::UVPipeOutEnd(ssize_t error)
+{
+ // Process pipe error
+ if ((error != 0) && !Result()->error()) {
+ Result()->ErrorMessage =
+ "Reading from stdout pipe failed with libuv error code ";
+ Result()->ErrorMessage += std::to_string(error);
+ }
+ // Try finish
+ UVTryFinish();
+}
+
+void cmUVReadOnlyProcess::UVPipeErrData(cmUVPipeBuffer::DataRange data)
+{
+ std::string* str =
+ Setup_.MergedOutput ? &Result()->StdOut : &Result()->StdErr;
+ str->append(data.begin(), data.end());
+}
+
+void cmUVReadOnlyProcess::UVPipeErrEnd(ssize_t error)
+{
+ // Process pipe error
+ if ((error != 0) && !Result()->error()) {
+ Result()->ErrorMessage =
+ "Reading from stderr pipe failed with libuv error code ";
+ Result()->ErrorMessage += std::to_string(error);
+ }
+ // Try finish
+ UVTryFinish();
+}
+
+void cmUVReadOnlyProcess::UVTryFinish()
+{
+ // There still might be data in the pipes after the process has finished.
+ // Therefore check if the process is finished AND all pipes are closed
+ // before signaling the worker thread to continue.
+ if ((UVProcess_.get() != nullptr) || (UVPipeOut_.uv_pipe() != nullptr) ||
+ (UVPipeErr_.uv_pipe() != nullptr)) {
+ return;
+ }
+ IsFinished_ = true;
+ FinishedCallback_();
+}
+
+/**
+ * @brief Worker pool worker thread
+ */
+class cmWorkerPoolWorker
+{
+public:
+ cmWorkerPoolWorker(uv_loop_t& uvLoop);
+ ~cmWorkerPoolWorker();
+
+ cmWorkerPoolWorker(cmWorkerPoolWorker const&) = delete;
+ cmWorkerPoolWorker& operator=(cmWorkerPoolWorker const&) = delete;
+
+ /**
+ * Set the internal thread
+ */
+ void SetThread(std::thread&& aThread) { Thread_ = std::move(aThread); }
+
+ /**
+ * Run an external process
+ */
+ bool RunProcess(cmWorkerPool::ProcessResultT& result,
+ std::vector<std::string> const& command,
+ std::string const& workingDirectory);
+
+private:
+ // -- Libuv callbacks
+ static void UVProcessStart(uv_async_t* handle);
+ void UVProcessFinished();
+
+private:
+ // -- Process management
+ struct
+ {
+ std::mutex Mutex;
+ cm::uv_async_ptr Request;
+ std::condition_variable Condition;
+ std::unique_ptr<cmUVReadOnlyProcess> ROP;
+ } Proc_;
+ // -- System thread
+ std::thread Thread_;
+};
+
+cmWorkerPoolWorker::cmWorkerPoolWorker(uv_loop_t& uvLoop)
+{
+ Proc_.Request.init(uvLoop, &cmWorkerPoolWorker::UVProcessStart, this);
+}
+
+cmWorkerPoolWorker::~cmWorkerPoolWorker()
+{
+ if (Thread_.joinable()) {
+ Thread_.join();
+ }
+}
+
+bool cmWorkerPoolWorker::RunProcess(cmWorkerPool::ProcessResultT& result,
+ std::vector<std::string> const& command,
+ std::string const& workingDirectory)
+{
+ if (command.empty()) {
+ return false;
+ }
+ // Create process instance
+ {
+ std::lock_guard<std::mutex> lock(Proc_.Mutex);
+ Proc_.ROP = cm::make_unique<cmUVReadOnlyProcess>();
+ Proc_.ROP->setup(&result, true, command, workingDirectory);
+ }
+ // Send asynchronous process start request to libuv loop
+ Proc_.Request.send();
+ // Wait until the process has been finished and destroyed
+ {
+ std::unique_lock<std::mutex> ulock(Proc_.Mutex);
+ while (Proc_.ROP) {
+ Proc_.Condition.wait(ulock);
+ }
+ }
+ return !result.error();
+}
+
+void cmWorkerPoolWorker::UVProcessStart(uv_async_t* handle)
+{
+ auto* wrk = reinterpret_cast<cmWorkerPoolWorker*>(handle->data);
+ bool startFailed = false;
+ {
+ auto& Proc = wrk->Proc_;
+ std::lock_guard<std::mutex> lock(Proc.Mutex);
+ if (Proc.ROP && !Proc.ROP->IsStarted()) {
+ startFailed =
+ !Proc.ROP->start(handle->loop, [wrk] { wrk->UVProcessFinished(); });
+ }
+ }
+ // Clean up if starting of the process failed
+ if (startFailed) {
+ wrk->UVProcessFinished();
+ }
+}
+
+void cmWorkerPoolWorker::UVProcessFinished()
+{
+ {
+ std::lock_guard<std::mutex> lock(Proc_.Mutex);
+ if (Proc_.ROP && (Proc_.ROP->IsFinished() || !Proc_.ROP->IsStarted())) {
+ Proc_.ROP.reset();
+ }
+ }
+ // Notify idling thread
+ Proc_.Condition.notify_one();
+}
+
+/**
+ * @brief Private worker pool internals
+ */
+class cmWorkerPoolInternal
+{
+public:
+ // -- Constructors
+ cmWorkerPoolInternal(cmWorkerPool* pool);
+ ~cmWorkerPoolInternal();
+
+ /**
+ * Runs the libuv loop.
+ */
+ bool Process();
+
+ /**
+ * Clear queue and abort threads.
+ */
+ void Abort();
+
+ /**
+ * Push a job to the queue and notify a worker.
+ */
+ bool PushJob(cmWorkerPool::JobHandleT&& jobHandle);
+
+ /**
+ * Worker thread main loop method.
+ */
+ void Work(unsigned int workerIndex);
+
+ // -- Request slots
+ static void UVSlotBegin(uv_async_t* handle);
+ static void UVSlotEnd(uv_async_t* handle);
+
+public:
+ // -- UV loop
+#ifdef CMAKE_UV_SIGNAL_HACK
+ std::unique_ptr<cmUVSignalHackRAII> UVHackRAII;
+#endif
+ std::unique_ptr<uv_loop_t> UVLoop;
+ cm::uv_async_ptr UVRequestBegin;
+ cm::uv_async_ptr UVRequestEnd;
+
+ // -- Thread pool and job queue
+ std::mutex Mutex;
+ bool Processing = false;
+ bool Aborting = false;
+ bool FenceProcessing = false;
+ unsigned int WorkersRunning = 0;
+ unsigned int WorkersIdle = 0;
+ unsigned int JobsProcessing = 0;
+ std::deque<cmWorkerPool::JobHandleT> Queue;
+ std::condition_variable Condition;
+ std::vector<std::unique_ptr<cmWorkerPoolWorker>> Workers;
+
+ // -- References
+ cmWorkerPool* Pool = nullptr;
+};
+
+void cmWorkerPool::ProcessResultT::reset()
+{
+ ExitStatus = 0;
+ TermSignal = 0;
+ if (!StdOut.empty()) {
+ StdOut.clear();
+ StdOut.shrink_to_fit();
+ }
+ if (!StdErr.empty()) {
+ StdErr.clear();
+ StdErr.shrink_to_fit();
+ }
+ if (!ErrorMessage.empty()) {
+ ErrorMessage.clear();
+ ErrorMessage.shrink_to_fit();
+ }
+}
+
+cmWorkerPoolInternal::cmWorkerPoolInternal(cmWorkerPool* pool)
+ : Pool(pool)
+{
+ // Initialize libuv loop
+ uv_disable_stdio_inheritance();
+#ifdef CMAKE_UV_SIGNAL_HACK
+ UVHackRAII = cm::make_unique<cmUVSignalHackRAII>();
+#endif
+ UVLoop = cm::make_unique<uv_loop_t>();
+ uv_loop_init(UVLoop.get());
+}
+
+cmWorkerPoolInternal::~cmWorkerPoolInternal()
+{
+ uv_loop_close(UVLoop.get());
+}
+
+bool cmWorkerPoolInternal::Process()
+{
+ // Reset state flags
+ Processing = true;
+ Aborting = false;
+ // Initialize libuv asynchronous request
+ UVRequestBegin.init(*UVLoop, &cmWorkerPoolInternal::UVSlotBegin, this);
+ UVRequestEnd.init(*UVLoop, &cmWorkerPoolInternal::UVSlotEnd, this);
+ // Send begin request
+ UVRequestBegin.send();
+ // Run libuv loop
+ bool success = (uv_run(UVLoop.get(), UV_RUN_DEFAULT) == 0);
+ // Update state flags
+ Processing = false;
+ Aborting = false;
+ return success;
+}
+
+void cmWorkerPoolInternal::Abort()
+{
+ bool notifyThreads = false;
+ // Clear all jobs and set abort flag
+ {
+ std::lock_guard<std::mutex> guard(Mutex);
+ if (Processing && !Aborting) {
+ // Register abort and clear queue
+ Aborting = true;
+ Queue.clear();
+ notifyThreads = true;
+ }
+ }
+ if (notifyThreads) {
+ // Wake threads
+ Condition.notify_all();
+ }
+}
+
+inline bool cmWorkerPoolInternal::PushJob(cmWorkerPool::JobHandleT&& jobHandle)
+{
+ std::lock_guard<std::mutex> guard(Mutex);
+ if (Aborting) {
+ return false;
+ }
+ // Append the job to the queue
+ Queue.emplace_back(std::move(jobHandle));
+ // Notify an idle worker if there's one
+ if (WorkersIdle != 0) {
+ Condition.notify_one();
+ }
+ // Return success
+ return true;
+}
+
+void cmWorkerPoolInternal::UVSlotBegin(uv_async_t* handle)
+{
+ auto& gint = *reinterpret_cast<cmWorkerPoolInternal*>(handle->data);
+ // Create worker threads
+ {
+ unsigned int const num = gint.Pool->ThreadCount();
+ // Create workers
+ gint.Workers.reserve(num);
+ for (unsigned int ii = 0; ii != num; ++ii) {
+ gint.Workers.emplace_back(
+ cm::make_unique<cmWorkerPoolWorker>(*gint.UVLoop));
+ }
+ // Start worker threads
+ for (unsigned int ii = 0; ii != num; ++ii) {
+ gint.Workers[ii]->SetThread(
+ std::thread(&cmWorkerPoolInternal::Work, &gint, ii));
+ }
+ }
+ // Destroy begin request
+ gint.UVRequestBegin.reset();
+}
+
+void cmWorkerPoolInternal::UVSlotEnd(uv_async_t* handle)
+{
+ auto& gint = *reinterpret_cast<cmWorkerPoolInternal*>(handle->data);
+ // Join and destroy worker threads
+ gint.Workers.clear();
+ // Destroy end request
+ gint.UVRequestEnd.reset();
+}
+
+void cmWorkerPoolInternal::Work(unsigned int workerIndex)
+{
+ cmWorkerPool::JobHandleT jobHandle;
+ std::unique_lock<std::mutex> uLock(Mutex);
+ // Increment running workers count
+ ++WorkersRunning;
+ // Enter worker main loop
+ while (true) {
+ // Abort on request
+ if (Aborting) {
+ break;
+ }
+ // Wait for new jobs
+ if (Queue.empty()) {
+ ++WorkersIdle;
+ Condition.wait(uLock);
+ --WorkersIdle;
+ continue;
+ }
+
+ // Check for fence jobs
+ if (FenceProcessing || Queue.front()->IsFence()) {
+ if (JobsProcessing != 0) {
+ Condition.wait(uLock);
+ continue;
+ }
+ // No jobs get processed. Set the fence job processing flag.
+ FenceProcessing = true;
+ }
+
+ // Pop next job from queue
+ jobHandle = std::move(Queue.front());
+ Queue.pop_front();
+
+ // Unlocked scope for job processing
+ ++JobsProcessing;
+ {
+ uLock.unlock();
+ jobHandle->Work(Pool, workerIndex); // Process job
+ jobHandle.reset(); // Destroy job
+ uLock.lock();
+ }
+ --JobsProcessing;
+
+ // Was this a fence job?
+ if (FenceProcessing) {
+ FenceProcessing = false;
+ Condition.notify_all();
+ }
+ }
+
+ // Decrement running workers count
+ if (--WorkersRunning == 0) {
+ // Last worker thread about to finish. Send libuv event.
+ UVRequestEnd.send();
+ }
+}
+
+cmWorkerPool::JobT::~JobT() = default;
+
+bool cmWorkerPool::JobT::RunProcess(ProcessResultT& result,
+ std::vector<std::string> const& command,
+ std::string const& workingDirectory)
+{
+ // Get worker by index
+ auto* wrk = Pool_->Int_->Workers.at(WorkerIndex_).get();
+ return wrk->RunProcess(result, command, workingDirectory);
+}
+
+cmWorkerPool::cmWorkerPool()
+ : Int_(cm::make_unique<cmWorkerPoolInternal>(this))
+{
+}
+
+cmWorkerPool::~cmWorkerPool() = default;
+
+void cmWorkerPool::SetThreadCount(unsigned int threadCount)
+{
+ if (!Int_->Processing) {
+ ThreadCount_ = (threadCount > 0) ? threadCount : 1u;
+ }
+}
+
+bool cmWorkerPool::Process(void* userData)
+{
+ // Setup user data
+ UserData_ = userData;
+ // Run libuv loop
+ bool success = Int_->Process();
+ // Clear user data
+ UserData_ = nullptr;
+ // Return
+ return success;
+}
+
+bool cmWorkerPool::PushJob(JobHandleT&& jobHandle)
+{
+ return Int_->PushJob(std::move(jobHandle));
+}
+
+void cmWorkerPool::Abort()
+{
+ Int_->Abort();
+}
diff --git a/Source/cmWorkerPool.h b/Source/cmWorkerPool.h
new file mode 100644
index 000000000..f08bb4f8d
--- /dev/null
+++ b/Source/cmWorkerPool.h
@@ -0,0 +1,226 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#ifndef cmWorkerPool_h
+#define cmWorkerPool_h
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include "cmAlgorithms.h" // IWYU pragma: keep
+
+#include <memory> // IWYU pragma: keep
+#include <stdint.h>
+#include <string>
+#include <utility>
+#include <vector>
+
+// -- Types
+class cmWorkerPoolInternal;
+
+/** @class cmWorkerPool
+ * @brief Thread pool with job queue
+ */
+class cmWorkerPool
+{
+public:
+ /**
+ * Return value and output of an external process.
+ */
+ struct ProcessResultT
+ {
+ void reset();
+ bool error() const
+ {
+ return (ExitStatus != 0) || (TermSignal != 0) || !ErrorMessage.empty();
+ }
+
+ std::int64_t ExitStatus = 0;
+ int TermSignal = 0;
+ std::string StdOut;
+ std::string StdErr;
+ std::string ErrorMessage;
+ };
+
+ /**
+ * Abstract job class for concurrent job processing.
+ */
+ class JobT
+ {
+ public:
+ JobT(JobT const&) = delete;
+ JobT& operator=(JobT const&) = delete;
+
+ /**
+ * Virtual destructor.
+ */
+ virtual ~JobT();
+
+ /**
+ * Fence job flag
+ *
+ * Fence jobs require that:
+ * - all jobs before in the queue have been processed
+ * - no jobs later in the queue will be processed before this job was
+ * processed
+ */
+ bool IsFence() const { return Fence_; }
+
+ protected:
+ /**
+ * Protected default constructor
+ */
+ JobT(bool fence = false)
+ : Fence_(fence)
+ {
+ }
+
+ /**
+ * Abstract processing interface that must be implement in derived classes.
+ */
+ virtual void Process() = 0;
+
+ /**
+ * Get the worker pool.
+ * Only valid during the JobT::Process() call!
+ */
+ cmWorkerPool* Pool() const { return Pool_; }
+
+ /**
+ * Get the user data.
+ * Only valid during the JobT::Process() call!
+ */
+ void* UserData() const { return Pool_->UserData(); };
+
+ /**
+ * Get the worker index.
+ * This is the index of the thread processing this job and is in the range
+ * [0..ThreadCount).
+ * Concurrently processing jobs will never have the same WorkerIndex().
+ * Only valid during the JobT::Process() call!
+ */
+ unsigned int WorkerIndex() const { return WorkerIndex_; }
+
+ /**
+ * Run an external read only process.
+ * Use only during JobT::Process() call!
+ */
+ bool RunProcess(ProcessResultT& result,
+ std::vector<std::string> const& command,
+ std::string const& workingDirectory);
+
+ private:
+ //! Needs access to Work()
+ friend class cmWorkerPoolInternal;
+ //! Worker thread entry method.
+ void Work(cmWorkerPool* pool, unsigned int workerIndex)
+ {
+ Pool_ = pool;
+ WorkerIndex_ = workerIndex;
+ this->Process();
+ }
+
+ private:
+ cmWorkerPool* Pool_ = nullptr;
+ unsigned int WorkerIndex_ = 0;
+ bool Fence_ = false;
+ };
+
+ /**
+ * Job handle type
+ */
+ typedef std::unique_ptr<JobT> JobHandleT;
+
+ /**
+ * Fence job base class
+ */
+ class JobFenceT : public JobT
+ {
+ public:
+ JobFenceT()
+ : JobT(true)
+ {
+ }
+ //! Does nothing
+ void Process() override{};
+ };
+
+ /**
+ * Fence job that aborts the worker pool.
+ *
+ * Useful as the last job in the job queue.
+ */
+ class JobEndT : JobFenceT
+ {
+ public:
+ //! Does nothing
+ void Process() override { Pool()->Abort(); }
+ };
+
+public:
+ // -- Methods
+ cmWorkerPool();
+ ~cmWorkerPool();
+
+ /**
+ * Number of worker threads.
+ */
+ unsigned int ThreadCount() const { return ThreadCount_; }
+
+ /**
+ * Set the number of worker threads.
+ *
+ * Calling this method during Process() has no effect.
+ */
+ void SetThreadCount(unsigned int threadCount);
+
+ /**
+ * Blocking function that starts threads to process all Jobs in the queue.
+ *
+ * This method blocks until a job calls the Abort() method.
+ * @arg threadCount Number of threads to process jobs.
+ * @arg userData Common user data pointer available in all Jobs.
+ */
+ bool Process(void* userData = nullptr);
+
+ /**
+ * User data reference passed to Process().
+ *
+ * Only valid during Process().
+ */
+ void* UserData() const { return UserData_; }
+
+ // -- Job processing interface
+
+ /**
+ * Clears the job queue and aborts all worker threads.
+ *
+ * This method is thread safe and can be called from inside a job.
+ */
+ void Abort();
+
+ /**
+ * Push job to the queue.
+ *
+ * This method is thread safe and can be called from inside a job or before
+ * Process().
+ */
+ bool PushJob(JobHandleT&& jobHandle);
+
+ /**
+ * Push job to the queue
+ *
+ * This method is thread safe and can be called from inside a job or before
+ * Process().
+ */
+ template <class T, typename... Args>
+ bool EmplaceJob(Args&&... args)
+ {
+ return PushJob(cm::make_unique<T>(std::forward<Args>(args)...));
+ }
+
+private:
+ void* UserData_ = nullptr;
+ unsigned int ThreadCount_ = 1;
+ std::unique_ptr<cmWorkerPoolInternal> Int_;
+};
+
+#endif
diff --git a/Source/cm_static_string_view.hxx b/Source/cm_static_string_view.hxx
new file mode 100644
index 000000000..1bef0c68b
--- /dev/null
+++ b/Source/cm_static_string_view.hxx
@@ -0,0 +1,41 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#ifndef cm_static_string_view_hxx
+#define cm_static_string_view_hxx
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include "cm_string_view.hxx"
+
+#include <cstddef>
+
+namespace cm {
+
+/** A string_view that only binds to static storage.
+ *
+ * This is used together with the `""_s` user-defined literal operator
+ * to construct a type-safe abstraction of a string_view that only views
+ * statically allocated strings. These strings are const and available
+ * for the entire lifetime of the program.
+ */
+class static_string_view : public string_view
+{
+ static_string_view(string_view v)
+ : string_view(v)
+ {
+ }
+
+ friend static_string_view operator"" _s(const char* data, size_t size);
+};
+
+/** Create a static_string_view using `""_s` literal syntax. */
+inline static_string_view operator"" _s(const char* data, size_t size)
+{
+ return string_view(data, size);
+}
+
+} // namespace cm
+
+using cm::operator"" _s;
+
+#endif
diff --git a/Source/cm_string_view.cxx b/Source/cm_string_view.cxx
new file mode 100644
index 000000000..61fa80e82
--- /dev/null
+++ b/Source/cm_string_view.cxx
@@ -0,0 +1,301 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+
+#include "cm_string_view.hxx"
+
+#ifndef CMake_HAVE_CXX_STRING_VIEW
+
+# include "cm_kwiml.h"
+
+# include <algorithm>
+# include <ostream>
+# include <stdexcept>
+
+namespace cm {
+
+string_view::const_reference string_view::at(size_type pos) const
+{
+ if (pos >= size_) {
+ throw std::out_of_range("Index out of range in string_view::at");
+ }
+ return data_[pos];
+}
+
+string_view::size_type string_view::copy(char* dest, size_type count,
+ size_type pos) const
+{
+ if (pos > size_) {
+ throw std::out_of_range("Index out of range in string_view::copy");
+ }
+ size_type const rcount = std::min(count, size_ - pos);
+ traits_type::copy(dest, data_ + pos, rcount);
+ return rcount;
+}
+
+string_view string_view::substr(size_type pos, size_type count) const
+{
+ if (pos > size_) {
+ throw std::out_of_range("Index out of range in string_view::substr");
+ }
+ size_type const rcount = std::min(count, size_ - pos);
+ return string_view(data_ + pos, rcount);
+}
+
+int string_view::compare(string_view v) const noexcept
+{
+ size_type const rlen = std::min(size_, v.size_);
+ int c = traits_type::compare(data_, v.data_, rlen);
+ if (c == 0) {
+ if (size_ < v.size_) {
+ c = -1;
+ } else if (size_ > v.size_) {
+ c = 1;
+ }
+ }
+ return c;
+}
+
+int string_view::compare(size_type pos1, size_type count1, string_view v) const
+{
+ return substr(pos1, count1).compare(v);
+}
+
+int string_view::compare(size_type pos1, size_type count1, string_view v,
+ size_type pos2, size_type count2) const
+{
+ return substr(pos1, count1).compare(v.substr(pos2, count2));
+}
+
+int string_view::compare(const char* s) const
+{
+ return compare(string_view(s));
+}
+
+int string_view::compare(size_type pos1, size_type count1, const char* s) const
+{
+ return substr(pos1, count1).compare(string_view(s));
+}
+
+int string_view::compare(size_type pos1, size_type count1, const char* s,
+ size_type count2) const
+{
+ return substr(pos1, count1).compare(string_view(s, count2));
+}
+
+string_view::size_type string_view::find(string_view v, size_type pos) const
+ noexcept
+{
+ for (; pos + v.size_ <= size_; ++pos) {
+ if (std::char_traits<char>::compare(data_ + pos, v.data_, v.size_) == 0) {
+ return pos;
+ }
+ }
+ return npos;
+}
+
+string_view::size_type string_view::find(char c, size_type pos) const noexcept
+{
+ return find(string_view(&c, 1), pos);
+}
+
+string_view::size_type string_view::find(const char* s, size_type pos,
+ size_type count) const
+{
+ return find(string_view(s, count), pos);
+}
+
+string_view::size_type string_view::find(const char* s, size_type pos) const
+{
+ return find(string_view(s), pos);
+}
+
+string_view::size_type string_view::rfind(string_view v, size_type pos) const
+ noexcept
+{
+ if (size_ >= v.size_) {
+ for (pos = std::min(pos, size_ - v.size_) + 1; pos > 0;) {
+ --pos;
+ if (std::char_traits<char>::compare(data_ + pos, v.data_, v.size_) ==
+ 0) {
+ return pos;
+ }
+ }
+ }
+ return npos;
+}
+
+string_view::size_type string_view::rfind(char c, size_type pos) const noexcept
+{
+ return rfind(string_view(&c, 1), pos);
+}
+
+string_view::size_type string_view::rfind(const char* s, size_type pos,
+ size_type count) const
+{
+ return rfind(string_view(s, count), pos);
+}
+
+string_view::size_type string_view::rfind(const char* s, size_type pos) const
+{
+ return rfind(string_view(s), pos);
+}
+
+string_view::size_type string_view::find_first_of(string_view v,
+ size_type pos) const noexcept
+{
+ for (; pos < size_; ++pos) {
+ if (traits_type::find(v.data_, v.size_, data_[pos])) {
+ return pos;
+ }
+ }
+ return npos;
+}
+
+string_view::size_type string_view::find_first_of(char c, size_type pos) const
+ noexcept
+{
+ return find_first_of(string_view(&c, 1), pos);
+}
+
+string_view::size_type string_view::find_first_of(const char* s, size_type pos,
+ size_type count) const
+{
+ return find_first_of(string_view(s, count), pos);
+}
+
+string_view::size_type string_view::find_first_of(const char* s,
+ size_type pos) const
+{
+ return find_first_of(string_view(s), pos);
+}
+
+string_view::size_type string_view::find_last_of(string_view v,
+ size_type pos) const noexcept
+{
+ if (size_ > 0) {
+ for (pos = std::min(pos, size_ - 1) + 1; pos > 0;) {
+ --pos;
+ if (traits_type::find(v.data_, v.size_, data_[pos])) {
+ return pos;
+ }
+ }
+ }
+ return npos;
+}
+
+string_view::size_type string_view::find_last_of(char c, size_type pos) const
+ noexcept
+{
+ return find_last_of(string_view(&c, 1), pos);
+}
+
+string_view::size_type string_view::find_last_of(const char* s, size_type pos,
+ size_type count) const
+{
+ return find_last_of(string_view(s, count), pos);
+}
+
+string_view::size_type string_view::find_last_of(const char* s,
+ size_type pos) const
+{
+ return find_last_of(string_view(s), pos);
+}
+
+string_view::size_type string_view::find_first_not_of(string_view v,
+ size_type pos) const
+ noexcept
+{
+ for (; pos < size_; ++pos) {
+ if (!traits_type::find(v.data_, v.size_, data_[pos])) {
+ return pos;
+ }
+ }
+ return npos;
+}
+
+string_view::size_type string_view::find_first_not_of(char c,
+ size_type pos) const
+ noexcept
+{
+ return find_first_not_of(string_view(&c, 1), pos);
+}
+
+string_view::size_type string_view::find_first_not_of(const char* s,
+ size_type pos,
+ size_type count) const
+{
+ return find_first_not_of(string_view(s, count), pos);
+}
+
+string_view::size_type string_view::find_first_not_of(const char* s,
+ size_type pos) const
+{
+ return find_first_not_of(string_view(s), pos);
+}
+
+string_view::size_type string_view::find_last_not_of(string_view v,
+ size_type pos) const
+ noexcept
+{
+ if (size_ > 0) {
+ for (pos = std::min(pos, size_ - 1) + 1; pos > 0;) {
+ --pos;
+ if (!traits_type::find(v.data_, v.size_, data_[pos])) {
+ return pos;
+ }
+ }
+ }
+ return npos;
+}
+
+string_view::size_type string_view::find_last_not_of(char c,
+ size_type pos) const
+ noexcept
+{
+ return find_last_not_of(string_view(&c, 1), pos);
+}
+
+string_view::size_type string_view::find_last_not_of(const char* s,
+ size_type pos,
+ size_type count) const
+{
+ return find_last_not_of(string_view(s, count), pos);
+}
+
+string_view::size_type string_view::find_last_not_of(const char* s,
+ size_type pos) const
+{
+ return find_last_not_of(string_view(s), pos);
+}
+
+std::ostream& operator<<(std::ostream& o, string_view v)
+{
+ return o.write(v.data(), v.size());
+}
+
+std::string& operator+=(std::string& s, string_view v)
+{
+ s.append(v.data(), v.size());
+ return s;
+}
+}
+
+std::hash<cm::string_view>::result_type std::hash<cm::string_view>::operator()(
+ argument_type const& s) const noexcept
+{
+ // FNV-1a hash.
+ static KWIML_INT_uint64_t const fnv_offset_basis = 0xcbf29ce484222325;
+ static KWIML_INT_uint64_t const fnv_prime = 0x100000001b3;
+ KWIML_INT_uint64_t h = fnv_offset_basis;
+ for (char const& c : s) {
+ h = h ^ KWIML_INT_uint64_t(KWIML_INT_uint8_t(c));
+ h = h * fnv_prime;
+ }
+ return result_type(h);
+}
+#else
+// Avoid empty translation unit.
+void cm_string_view_cxx()
+{
+}
+#endif
diff --git a/Source/cm_string_view.hxx b/Source/cm_string_view.hxx
new file mode 100644
index 000000000..d368ed8e2
--- /dev/null
+++ b/Source/cm_string_view.hxx
@@ -0,0 +1,217 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#ifndef cm_string_view_hxx
+#define cm_string_view_hxx
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#if __cplusplus >= 201703L || defined(_MSVC_LANG) && _MSVC_LANG >= 201703L
+# define CMake_HAVE_CXX_STRING_VIEW
+#endif
+
+#ifdef CMake_HAVE_CXX_STRING_VIEW
+# include <string_view>
+namespace cm {
+using std::string_view;
+}
+#else
+# include <cstddef>
+# include <functional>
+# include <iosfwd>
+# include <iterator>
+# include <string>
+
+namespace cm {
+
+class string_view
+{
+public:
+ using traits_type = std::string::traits_type;
+ using value_type = char;
+ using pointer = char*;
+ using const_pointer = const char*;
+ using reference = char&;
+ using const_reference = char const&;
+ using const_iterator = const char*;
+ using iterator = const_iterator;
+ using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+ using reverse_iterator = const_reverse_iterator;
+ using size_type = std::string::size_type;
+ using difference_type = std::string::difference_type;
+
+ static size_type const npos = static_cast<size_type>(-1);
+
+ string_view() noexcept = default;
+ string_view(string_view const&) noexcept = default;
+
+ string_view(const char* s, size_t count) noexcept
+ : data_(s)
+ , size_(count)
+ {
+ }
+
+ string_view(const char* s) noexcept
+ : data_(s)
+ , size_(traits_type::length(s))
+ {
+ }
+
+ // C++17 does not define this constructor. Instead it defines
+ // a conversion operator on std::string to create a string_view.
+ // Since this implementation is used in C++11, std::string does
+ // not have that conversion.
+ string_view(std::string const& s) noexcept
+ : data_(s.data())
+ , size_(s.size())
+ {
+ }
+
+ // C++17 does not define this conversion. Instead it defines
+ // a constructor on std::string that can take a string_view.
+ // Since this implementation is used in C++11, std::string does
+ // not have that constructor.
+ explicit operator std::string() const { return std::string(data_, size_); }
+
+ string_view& operator=(string_view const&) = default;
+
+ const_iterator begin() const noexcept { return data_; }
+ const_iterator end() const noexcept { return data_ + size_; }
+ const_iterator cbegin() const noexcept { return begin(); }
+ const_iterator cend() const noexcept { return end(); }
+
+ const_reverse_iterator rbegin() const noexcept
+ {
+ return const_reverse_iterator(end());
+ }
+ const_reverse_iterator rend() const noexcept
+ {
+ return const_reverse_iterator(begin());
+ }
+ const_reverse_iterator crbegin() const noexcept { return rbegin(); }
+ const_reverse_iterator crend() const noexcept { return rend(); }
+
+ const_reference operator[](size_type pos) const noexcept
+ {
+ return data_[pos];
+ }
+ const_reference at(size_type pos) const;
+ const_reference front() const noexcept { return data_[0]; }
+ const_reference back() const noexcept { return data_[size_ - 1]; }
+ const_pointer data() const noexcept { return data_; }
+
+ size_type size() const noexcept { return size_; }
+ size_type length() const noexcept { return size_; }
+ size_type max_size() const noexcept { return npos - 1; }
+ bool empty() const noexcept { return size_ == 0; }
+
+ void remove_prefix(size_type n) noexcept
+ {
+ data_ += n;
+ size_ -= n;
+ }
+ void remove_suffix(size_type n) noexcept { size_ -= n; }
+ void swap(string_view& v) noexcept
+ {
+ string_view tmp = v;
+ v = *this;
+ *this = tmp;
+ }
+
+ size_type copy(char* dest, size_type count, size_type pos = 0) const;
+ string_view substr(size_type pos = 0, size_type count = npos) const;
+
+ int compare(string_view v) const noexcept;
+ int compare(size_type pos1, size_type count1, string_view v) const;
+ int compare(size_type pos1, size_type count1, string_view v, size_type pos2,
+ size_type count2) const;
+ int compare(const char* s) const;
+ int compare(size_type pos1, size_type count1, const char* s) const;
+ int compare(size_type pos1, size_type count1, const char* s,
+ size_type count2) const;
+
+ size_type find(string_view v, size_type pos = 0) const noexcept;
+ size_type find(char c, size_type pos = 0) const noexcept;
+ size_type find(const char* s, size_type pos, size_type count) const;
+ size_type find(const char* s, size_type pos = 0) const;
+
+ size_type rfind(string_view v, size_type pos = npos) const noexcept;
+ size_type rfind(char c, size_type pos = npos) const noexcept;
+ size_type rfind(const char* s, size_type pos, size_type count) const;
+ size_type rfind(const char* s, size_type pos = npos) const;
+
+ size_type find_first_of(string_view v, size_type pos = 0) const noexcept;
+ size_type find_first_of(char c, size_type pos = 0) const noexcept;
+ size_type find_first_of(const char* s, size_type pos, size_type count) const;
+ size_type find_first_of(const char* s, size_type pos = 0) const;
+
+ size_type find_last_of(string_view v, size_type pos = npos) const noexcept;
+ size_type find_last_of(char c, size_type pos = npos) const noexcept;
+ size_type find_last_of(const char* s, size_type pos, size_type count) const;
+ size_type find_last_of(const char* s, size_type pos = npos) const;
+
+ size_type find_first_not_of(string_view v, size_type pos = 0) const noexcept;
+ size_type find_first_not_of(char c, size_type pos = 0) const noexcept;
+ size_type find_first_not_of(const char* s, size_type pos,
+ size_type count) const;
+ size_type find_first_not_of(const char* s, size_type pos = 0) const;
+
+ size_type find_last_not_of(string_view v, size_type pos = npos) const
+ noexcept;
+ size_type find_last_not_of(char c, size_type pos = npos) const noexcept;
+ size_type find_last_not_of(const char* s, size_type pos,
+ size_type count) const;
+ size_type find_last_not_of(const char* s, size_type pos = npos) const;
+
+private:
+ const char* data_ = nullptr;
+ size_type size_ = 0;
+};
+
+std::ostream& operator<<(std::ostream& o, string_view v);
+
+std::string& operator+=(std::string& s, string_view v);
+
+inline bool operator==(string_view l, string_view r) noexcept
+{
+ return l.compare(r) == 0;
+}
+
+inline bool operator!=(string_view l, string_view r) noexcept
+{
+ return l.compare(r) != 0;
+}
+
+inline bool operator<(string_view l, string_view r) noexcept
+{
+ return l.compare(r) < 0;
+}
+
+inline bool operator<=(string_view l, string_view r) noexcept
+{
+ return l.compare(r) <= 0;
+}
+
+inline bool operator>(string_view l, string_view r) noexcept
+{
+ return l.compare(r) > 0;
+}
+
+inline bool operator>=(string_view l, string_view r) noexcept
+{
+ return l.compare(r) >= 0;
+}
+}
+
+namespace std {
+
+template <>
+struct hash<cm::string_view>
+{
+ typedef cm::string_view argument_type;
+ typedef size_t result_type;
+ result_type operator()(argument_type const& s) const noexcept;
+};
+}
+
+#endif
+#endif
diff --git a/Source/cm_thread.hxx b/Source/cm_thread.hxx
new file mode 100644
index 000000000..b1f064585
--- /dev/null
+++ b/Source/cm_thread.hxx
@@ -0,0 +1,48 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing for details. */
+#ifndef CM_THREAD_HXX
+#define CM_THREAD_HXX
+
+#include "cmConfigure.h" // IWYU pragma: keep
+#include "cm_uv.h"
+
+namespace cm {
+
+class shared_mutex
+{
+ uv_rwlock_t _M_;
+
+public:
+ shared_mutex() { uv_rwlock_init(&_M_); }
+ ~shared_mutex() { uv_rwlock_destroy(&_M_); }
+
+ shared_mutex(shared_mutex const&) = delete;
+ shared_mutex& operator=(shared_mutex const&) = delete;
+
+ void lock() { uv_rwlock_wrlock(&_M_); }
+ void unlock() { uv_rwlock_wrunlock(&_M_); }
+
+ void lock_shared() { uv_rwlock_rdlock(&_M_); }
+ void unlock_shared() { uv_rwlock_rdunlock(&_M_); }
+};
+
+template <typename T>
+class shared_lock
+{
+ T& _mutex;
+
+public:
+ shared_lock(T& m)
+ : _mutex(m)
+ {
+ _mutex.lock_shared();
+ }
+
+ ~shared_lock() { _mutex.unlock_shared(); }
+
+ shared_lock(shared_lock const&) = delete;
+ shared_lock& operator=(shared_lock const&) = delete;
+};
+}
+
+#endif
diff --git a/Source/kwsys/testConfigure.cxx b/Source/kwsys/testConfigure.cxx
new file mode 100644
index 000000000..a3c2ed3ae
--- /dev/null
+++ b/Source/kwsys/testConfigure.cxx
@@ -0,0 +1,30 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+file Copyright.txt or https://cmake.org/licensing#kwsys for details. */
+#include "kwsysPrivate.h"
+#include KWSYS_HEADER(Configure.hxx)
+
+// Work-around CMake dependency scanning limitation. This must
+// duplicate the above list of headers.
+#if 0
+# include "Configure.hxx.in"
+#endif
+
+static bool testFallthrough(int n)
+{
+ int r = 0;
+ switch (n) {
+ case 1:
+ ++r;
+ KWSYS_FALLTHROUGH;
+ default:
+ ++r;
+ }
+ return r == 2;
+}
+
+int testConfigure(int, char* [])
+{
+ bool res = true;
+ res = testFallthrough(1) && res;
+ return res ? 0 : 1;
+}
diff --git a/Source/kwsys/testDirectory.cxx b/Source/kwsys/testDirectory.cxx
new file mode 100644
index 000000000..b1ab0c872
--- /dev/null
+++ b/Source/kwsys/testDirectory.cxx
@@ -0,0 +1,110 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+file Copyright.txt or https://cmake.org/licensing#kwsys for details. */
+#include "kwsysPrivate.h"
+#include KWSYS_HEADER(Directory.hxx)
+#include KWSYS_HEADER(Encoding.hxx)
+#include KWSYS_HEADER(SystemTools.hxx)
+
+// Work-around CMake dependency scanning limitation. This must
+// duplicate the above list of headers.
+#if 0
+# include "Directory.hxx.in"
+# include "Encoding.hxx.in"
+# include "SystemTools.hxx.in"
+#endif
+
+#include <fstream>
+#include <iostream>
+#include <sstream>
+
+#include <testSystemTools.h>
+
+int _doLongPathTest()
+{
+ using namespace kwsys;
+ static const int LONG_PATH_THRESHOLD = 512;
+ int res = 0;
+ std::string topdir(TEST_SYSTEMTOOLS_BINARY_DIR "/directory_testing/");
+ std::stringstream testpathstrm;
+ std::string testdirpath;
+ std::string extendedtestdirpath;
+
+ testpathstrm << topdir;
+ size_t pathlen = testpathstrm.str().length();
+ testpathstrm.seekp(0, std::ios_base::end);
+ while (pathlen < LONG_PATH_THRESHOLD) {
+ testpathstrm << "0123456789/";
+ pathlen = testpathstrm.str().length();
+ }
+
+ testdirpath = testpathstrm.str();
+#ifdef _WIN32
+ extendedtestdirpath =
+ Encoding::ToNarrow(SystemTools::ConvertToWindowsExtendedPath(testdirpath));
+#else
+ extendedtestdirpath = testdirpath;
+#endif
+
+ if (SystemTools::MakeDirectory(extendedtestdirpath)) {
+ std::ofstream testfile1(
+ (extendedtestdirpath + "longfilepathtest1.txt").c_str());
+ std::ofstream testfile2(
+ (extendedtestdirpath + "longfilepathtest2.txt").c_str());
+ testfile1 << "foo";
+ testfile2 << "bar";
+ testfile1.close();
+ testfile2.close();
+
+ Directory testdir;
+ // Set res to failure if the directory doesn't load
+ res += !testdir.Load(testdirpath);
+ // Increment res failure if the directory appears empty
+ res += testdir.GetNumberOfFiles() == 0;
+ // Increment res failures if the path has changed from
+ // what was provided.
+ res += testdirpath != testdir.GetPath();
+
+ SystemTools::RemoveADirectory(topdir);
+ } else {
+ std::cerr << "Failed to create directory with long path: "
+ << extendedtestdirpath << std::endl;
+ res += 1;
+ }
+ return res;
+}
+
+int _copyDirectoryTest()
+{
+ using namespace kwsys;
+ const std::string source(TEST_SYSTEMTOOLS_BINARY_DIR
+ "/directory_testing/copyDirectoryTestSrc");
+ if (SystemTools::PathExists(source)) {
+ std::cerr << source << " shouldn't exist before test" << std::endl;
+ return 1;
+ }
+ const std::string destination(TEST_SYSTEMTOOLS_BINARY_DIR
+ "/directory_testing/copyDirectoryTestDst");
+ if (SystemTools::PathExists(destination)) {
+ std::cerr << destination << " shouldn't exist before test" << std::endl;
+ return 2;
+ }
+ const bool copysuccess = SystemTools::CopyADirectory(source, destination);
+ const bool destinationexists = SystemTools::PathExists(destination);
+ if (copysuccess) {
+ std::cerr << "CopyADirectory should have returned false" << std::endl;
+ SystemTools::RemoveADirectory(destination);
+ return 3;
+ }
+ if (destinationexists) {
+ std::cerr << "CopyADirectory returned false, but destination directory"
+ << " has been created" << std::endl;
+ SystemTools::RemoveADirectory(destination);
+ return 4;
+ }
+ return 0;
+}
+
+int testDirectory(int, char* [])
+{
+ return _doLongPathTest() + _copyDirectoryTest();
+}
diff --git a/Source/kwsys/testDynloadImpl.c b/Source/kwsys/testDynloadImpl.c
new file mode 100644
index 000000000..2b9069bc7
--- /dev/null
+++ b/Source/kwsys/testDynloadImpl.c
@@ -0,0 +1,10 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing#kwsys for details. */
+
+#include "testDynloadImpl.h"
+
+int TestDynamicLoaderImplData = 0;
+
+void TestDynamicLoaderImplSymbolPointer()
+{
+}
diff --git a/Source/kwsys/testDynloadImpl.h b/Source/kwsys/testDynloadImpl.h
new file mode 100644
index 000000000..d0c9dfb75
--- /dev/null
+++ b/Source/kwsys/testDynloadImpl.h
@@ -0,0 +1,15 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing#kwsys for details. */
+#ifdef _WIN32
+# ifdef BUILDING_TestDynloadImpl
+# define DLIMPL_EXPORT __declspec(dllexport)
+# else
+# define DLIMPL_EXPORT __declspec(dllimport)
+# endif
+#else
+# define DLIMPL_EXPORT
+#endif
+
+DLIMPL_EXPORT int TestDynamicLoaderImplData;
+
+DLIMPL_EXPORT void TestDynamicLoaderImplSymbolPointer();
diff --git a/Source/kwsys/testDynloadUse.c b/Source/kwsys/testDynloadUse.c
new file mode 100644
index 000000000..5402add6a
--- /dev/null
+++ b/Source/kwsys/testDynloadUse.c
@@ -0,0 +1,15 @@
+/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
+ file Copyright.txt or https://cmake.org/licensing#kwsys for details. */
+#include "testDynloadImpl.h"
+
+#ifdef _WIN32
+# define DL_EXPORT __declspec(dllexport)
+#else
+# define DL_EXPORT
+#endif
+
+DL_EXPORT int TestLoad()
+{
+ TestDynamicLoaderImplSymbolPointer();
+ return TestDynamicLoaderImplData;
+}