/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file LICENSE.rst or https://cmake.org/licensing for details. */ #include "cmCxxModuleMetadata.h" #include #include #include #include #include #include #include #include "cmsys/FStream.hxx" #include "cmFileSet.h" #include "cmFileSetMetadata.h" #include "cmGeneratedFileStream.h" #include "cmJSONState.h" #include "cmListFileCache.h" #include "cmStringAlgorithms.h" #include "cmSystemTools.h" #include "cmTarget.h" namespace { bool JsonIsStringArray(Json::Value const& v) { return v.isArray() && std::all_of(v.begin(), v.end(), [](Json::Value const& it) { return it.isString(); }); } bool ParsePreprocessorDefine(Json::Value& dval, cmCxxModuleMetadata::PreprocessorDefineData& out, cmJSONState* state) { if (!dval.isObject()) { state->AddErrorAtValue("each entry in 'definitions' must be an object", &dval); return false; } if (!dval.isMember("name") || !dval["name"].isString() || dval["name"].asString().empty()) { state->AddErrorAtValue( "preprocessor definition requires a non-empty 'name'", &dval["name"]); return false; } out.Name = dval["name"].asString(); if (dval.isMember("value")) { if (dval["value"].isString()) { out.Value = dval["value"].asString(); } else if (!dval["value"].isNull()) { state->AddErrorAtValue( "'value' in preprocessor definition must be string or null", &dval["value"]); return false; } } if (dval.isMember("undef")) { if (!dval["undef"].isBool()) { state->AddErrorAtValue( "'undef' in preprocessor definition must be boolean", &dval["undef"]); return false; } out.Undef = dval["undef"].asBool(); } return true; } bool ParseCMakeLocalArgumentsVendor( Json::Value& cmlav, cmCxxModuleMetadata::LocalArgumentsData& out, cmJSONState* state) { if (!cmlav.isObject()) { state->AddErrorAtValue("'vendor' must be an object", &cmlav); return false; } if (cmlav.isMember("compile-options")) { if (!JsonIsStringArray(cmlav["compile-options"])) { state->AddErrorAtValue("'compile-options' must be an array of strings", &cmlav["compile-options"]); return false; } for (auto const& s : cmlav["compile-options"]) { out.CompileOptions.push_back(s.asString()); } } if (cmlav.isMember("compile-features")) { if (!JsonIsStringArray(cmlav["compile-features"])) { state->AddErrorAtValue("'compile-features' must be an array of strings", &cmlav["compile-features"]); return false; } for (auto const& s : cmlav["compile-features"]) { out.CompileFeatures.push_back(s.asString()); } } return true; } bool ParseLocalArguments(Json::Value& lav, cmCxxModuleMetadata::LocalArgumentsData& out, cmJSONState* state) { if (!lav.isObject()) { state->AddErrorAtValue("'local-arguments' must be an object", &lav); return false; } if (lav.isMember("include-directories")) { if (!JsonIsStringArray(lav["include-directories"])) { state->AddErrorAtValue( "'include-directories' must be an array of strings", &lav["include-directories"]); return false; } for (auto const& s : lav["include-directories"]) { out.IncludeDirectories.push_back(s.asString()); } } if (lav.isMember("system-include-directories")) { if (!JsonIsStringArray(lav["system-include-directories"])) { state->AddErrorAtValue( "'system-include-directories' must be an array of strings", &lav["system-include-directories"]); return false; } for (auto const& s : lav["system-include-directories"]) { out.SystemIncludeDirectories.push_back(s.asString()); } } if (lav.isMember("definitions")) { if (!lav["definitions"].isArray()) { state->AddErrorAtValue("'definitions' must be an array", &lav["definitions"]); return false; } for (Json::Value& dval : lav["definitions"]) { out.Definitions.emplace_back(); if (!ParsePreprocessorDefine(dval, out.Definitions.back(), state)) { return false; } } } if (lav.isMember("vendor")) { if (!ParseCMakeLocalArgumentsVendor(lav["vendor"], out, state)) { return false; } } return true; } bool ParseModule(Json::Value& mval, cmCxxModuleMetadata::ModuleData& mod, cmJSONState* state) { if (!mval.isObject()) { state->AddErrorAtValue("each entry in 'modules' must be an object", &mval); return false; } if (!mval.isMember("logical-name") || !mval["logical-name"].isString() || mval["logical-name"].asString().empty()) { state->AddErrorAtValue( "module entries require a non-empty 'logical-name' string", &mval["logical-name"]); return false; } mod.LogicalName = mval["logical-name"].asString(); if (!mval.isMember("source-path") || !mval["source-path"].isString() || mval["source-path"].asString().empty()) { state->AddErrorAtValue( "module entries require a non-empty 'source-path' string", &mval["source-path"]); return false; } mod.SourcePath = mval["source-path"].asString(); if (mval.isMember("is-interface")) { if (!mval["is-interface"].isBool()) { state->AddErrorAtValue("'is-interface' must be boolean", &mval["is-interface"]); return false; } mod.IsInterface = mval["is-interface"].asBool(); } else { mod.IsInterface = true; } if (mval.isMember("is-std-library")) { if (!mval["is-std-library"].isBool()) { state->AddErrorAtValue("'is-std-library' must be boolean", &mval["is-std-library"]); return false; } mod.IsStdLibrary = mval["is-std-library"].asBool(); } else { mod.IsStdLibrary = false; } if (mval.isMember("local-arguments")) { mod.LocalArguments.emplace(); if (!ParseLocalArguments(mval["local-arguments"], *mod.LocalArguments, state)) { return false; } } return true; } bool ParseRoot(Json::Value& root, cmCxxModuleMetadata& meta, cmJSONState* state) { if (!root.isMember("version") || !root["version"].isInt()) { state->AddErrorAtValue( "Top-level member 'version' is required and must be an integer", &root); return false; } meta.Version = root["version"].asInt(); if (root.isMember("revision")) { if (!root["revision"].isInt()) { state->AddErrorAtValue("'revision' must be an integer", &root["revision"]); return false; } meta.Revision = root["revision"].asInt(); } if (meta.Version != 1) { state->AddErrorAtValue(cmStrCat("Module manifest version number, '", meta.Version, '.', meta.Revision, "' is newer than max supported (1.1)"), &root); return false; } if (root.isMember("modules")) { if (!root["modules"].isArray()) { state->AddErrorAtValue("'modules' must be an array", &root["modules"]); return false; } for (Json::Value& mval : root["modules"]) { meta.Modules.emplace_back(); if (!ParseModule(mval, meta.Modules.back(), state)) { return false; } } } return true; } } // namespace cmCxxModuleMetadata::ParseResult cmCxxModuleMetadata::LoadFromFile( std::string const& path) { ParseResult res; Json::Value root; cmJSONState parseState(path, &root); if (!parseState.errors.empty()) { res.Error = parseState.GetErrorMessage(); return res; } cmCxxModuleMetadata meta; if (!ParseRoot(root, meta, &parseState)) { res.Error = parseState.GetErrorMessage(); return res; } meta.MetadataFilePath = path; res.Meta = std::move(meta); return res; } namespace { Json::Value SerializePreprocessorDefine( cmCxxModuleMetadata::PreprocessorDefineData const& d) { Json::Value dv(Json::objectValue); dv["name"] = d.Name; if (d.Value) { dv["value"] = *d.Value; } if (d.Undef) { dv["undef"] = d.Undef; } return dv; } Json::Value SerializeCMakeLocalArgumentsVendor( cmCxxModuleMetadata::LocalArgumentsData const& la) { Json::Value vend(Json::objectValue); if (!la.CompileOptions.empty()) { Json::Value& opts = vend["compile-options"] = Json::arrayValue; for (auto const& s : la.CompileOptions) { opts.append(s); } } if (!la.CompileFeatures.empty()) { Json::Value& feats = vend["compile-features"] = Json::arrayValue; for (auto const& s : la.CompileFeatures) { feats.append(s); } } return vend; } Json::Value SerializeLocalArguments( cmCxxModuleMetadata::LocalArgumentsData const& la) { Json::Value lav(Json::objectValue); if (!la.IncludeDirectories.empty()) { Json::Value& inc = lav["include-directories"] = Json::arrayValue; for (auto const& s : la.IncludeDirectories) { inc.append(s); } } if (!la.SystemIncludeDirectories.empty()) { Json::Value& sinc = lav["system-include-directories"] = Json::arrayValue; for (auto const& s : la.SystemIncludeDirectories) { sinc.append(s); } } if (!la.Definitions.empty()) { Json::Value& defs = lav["definitions"] = Json::arrayValue; for (auto const& d : la.Definitions) { defs.append(SerializePreprocessorDefine(d)); } } Json::Value vend = SerializeCMakeLocalArgumentsVendor(la); if (!vend.empty()) { Json::Value& cmvend = lav["vendor"] = Json::objectValue; cmvend["cmake"] = std::move(vend); } return lav; } Json::Value SerializeModule(std::string& manifestRoot, cmCxxModuleMetadata::ModuleData const& m) { Json::Value mv(Json::objectValue); mv["logical-name"] = m.LogicalName; if (cmSystemTools::FileIsFullPath(m.SourcePath)) { mv["source-path"] = m.SourcePath; } else { mv["source-path"] = cmSystemTools::ForceToRelativePath( manifestRoot, cmStrCat('/', m.SourcePath)); } mv["is-interface"] = m.IsInterface; mv["is-std-library"] = m.IsStdLibrary; if (m.LocalArguments) { mv["local-arguments"] = SerializeLocalArguments(*m.LocalArguments); } return mv; } } // namespace Json::Value cmCxxModuleMetadata::ToJsonValue(cmCxxModuleMetadata const& meta) { Json::Value root(Json::objectValue); root["version"] = meta.Version; root["revision"] = meta.Revision; Json::Value& modules = root["modules"] = Json::arrayValue; std::string manifestRoot = cmSystemTools::GetFilenamePath(meta.MetadataFilePath); if (!cmSystemTools::FileIsFullPath(meta.MetadataFilePath)) { manifestRoot = cmStrCat('/', manifestRoot); } for (auto const& m : meta.Modules) { modules.append(SerializeModule(manifestRoot, m)); } return root; } cmCxxModuleMetadata::SaveResult cmCxxModuleMetadata::SaveToFile( std::string const& path, cmCxxModuleMetadata const& meta) { SaveResult st; cmGeneratedFileStream ofs(path); if (!ofs.is_open()) { st.Error = "Unable to open temp file for writing"; return st; } Json::StreamWriterBuilder wbuilder; wbuilder["indentation"] = " "; ofs << Json::writeString(wbuilder, ToJsonValue(meta)); ofs.Close(); if (!ofs.good()) { st.Error = cmStrCat("Write failed for file: "_s, path); return st; } st.Ok = true; return st; } namespace { struct MetaDataProperties { std::string MetadataDir; std::set AllCompileFeatures; std::set AllCompileOptions; std::set AllIncludeDirectories; std::set AllCompileDefinitions; std::set BaseDirs; std::set Sources; std::string NormalizePath(std::string const& in) const { std::string out = in; if (!cmSystemTools::FileIsFullPath(in)) { out = cmStrCat(MetadataDir, '/', in); } return cmSystemTools::CollapseFullPath(out); } }; MetaDataProperties CollectMetaProperties(cmCxxModuleMetadata const& meta) { MetaDataProperties props; props.MetadataDir = cmSystemTools::GetFilenamePath(meta.MetadataFilePath); #ifndef _WIN32 props.BaseDirs.insert("/"); #endif for (auto const& module : meta.Modules) { std::string sourcePath = props.NormalizePath(module.SourcePath); props.Sources.insert(sourcePath); #ifdef _WIN32 std::string root; cmSystemTools::SplitPathRootComponent(sourcePath, &root); props.BaseDirs.insert(root); #endif if (module.LocalArguments) { for (auto const& incDir : module.LocalArguments->IncludeDirectories) { props.AllIncludeDirectories.emplace(props.NormalizePath(incDir)); } for (auto const& sysIncDir : module.LocalArguments->SystemIncludeDirectories) { props.AllIncludeDirectories.emplace(props.NormalizePath(sysIncDir)); } for (auto const& opt : module.LocalArguments->CompileOptions) { props.AllCompileOptions.emplace(opt); } for (auto const& opt : module.LocalArguments->CompileFeatures) { props.AllCompileFeatures.emplace(opt); } for (auto const& def : module.LocalArguments->Definitions) { if (!def.Undef) { if (def.Value) { props.AllCompileDefinitions.emplace( cmStrCat(def.Name, "="_s, *def.Value)); } else { props.AllCompileDefinitions.emplace(def.Name); } } } } } return props; } void PopulateFileSet(cmTarget& target, MetaDataProperties const& props) { auto fileSet = target.GetOrCreateFileSet(std::string{ cm::FileSetMetadata::CXX_MODULES }, std::string{ cm::FileSetMetadata::CXX_MODULES }, cm::FileSetMetadata::Visibility::Public); for (auto const& source : props.Sources) { fileSet.first->AddFileEntry(source); } for (auto const& baseDir : props.BaseDirs) { fileSet.first->AddDirectoryEntry(baseDir); } } void PopulateLocalTarget(cmTarget& target, MetaDataProperties const& props) { if (!props.AllIncludeDirectories.empty()) { target.AppendProperty("INCLUDE_DIRECTORIES", cmJoin(props.AllIncludeDirectories, ";")); } if (!props.AllCompileDefinitions.empty()) { target.AppendProperty("COMPILE_DEFINITIONS", cmJoin(props.AllCompileDefinitions, ";")); } if (!props.AllCompileOptions.empty()) { target.AppendProperty("COMPILE_OPTIONS", cmJoin(props.AllCompileOptions, ";")); } if (!props.AllCompileFeatures.empty()) { target.AppendProperty("COMPILE_FEATURES", cmJoin(props.AllCompileFeatures, ";")); } } void PopulateImportedTarget(cmTarget& target, MetaDataProperties const& props, cmCxxModuleMetadata const& meta, std::vector const& configs) { if (!props.AllIncludeDirectories.empty()) { target.SetProperty("IMPORTED_CXX_MODULES_INCLUDE_DIRECTORIES", cmJoin(props.AllIncludeDirectories, ";")); } if (!props.AllCompileDefinitions.empty()) { target.SetProperty("IMPORTED_CXX_MODULES_COMPILE_DEFINITIONS", cmJoin(props.AllCompileDefinitions, ";")); } if (!props.AllCompileOptions.empty()) { target.SetProperty("IMPORTED_CXX_MODULES_COMPILE_OPTIONS", cmJoin(props.AllCompileOptions, ";")); } if (!props.AllCompileFeatures.empty()) { target.SetProperty("IMPORTED_CXX_MODULES_COMPILE_FEATURES", cmJoin(props.AllCompileFeatures, ";")); } for (auto const& config : configs) { std::vector moduleList; for (auto const& module : meta.Modules) { if (module.IsInterface) { moduleList.push_back(cmStrCat(module.LogicalName, "="_s, props.NormalizePath(module.SourcePath))); } } if (!moduleList.empty()) { std::string upperConfig = cmSystemTools::UpperCase(config); std::string propertyName = cmStrCat("IMPORTED_CXX_MODULES_"_s, upperConfig); target.SetProperty(propertyName, cmJoin(moduleList, ";")); } } } } // namespace void cmCxxModuleMetadata::PopulateTarget( cmTarget& target, cmCxxModuleMetadata const& meta, std::vector const& configs) { auto props = CollectMetaProperties(meta); PopulateFileSet(target, props); if (target.IsImported()) { PopulateImportedTarget(target, props, meta, configs); } else { PopulateLocalTarget(target, props); } }