diff options
Diffstat (limited to 'Source')
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; +} |