diff --git a/external/yaml-cpp.patch b/external/yaml-cpp.patch index 384f48963..03819241c 100644 --- a/external/yaml-cpp.patch +++ b/external/yaml-cpp.patch @@ -1,3 +1,15 @@ +diff --git a/include/yaml-cpp/emittermanip.h b/include/yaml-cpp/emittermanip.h +index 976d149..ba73a48 100644 +--- a/include/yaml-cpp/emittermanip.h ++++ b/include/yaml-cpp/emittermanip.h +@@ -32,6 +32,7 @@ enum EMITTER_MANIP { + UpperNull, + CamelNull, + TildeNull, ++ EmptyNull, + + // bool manipulators + YesNoBool, // yes, no diff --git a/include/yaml-cpp/exceptions.h b/include/yaml-cpp/exceptions.h index f6b2602..a4c5537 100644 --- a/include/yaml-cpp/exceptions.h @@ -53,6 +65,31 @@ index 07cf81a..ed7d9cd 100644 void convert_to_map(const shared_memory_holder& pMemory); void convert_sequence_to_map(const shared_memory_holder& pMemory); +diff --git a/src/emitter.cpp b/src/emitter.cpp +index 4d48307..d4485be 100644 +--- a/src/emitter.cpp ++++ b/src/emitter.cpp +@@ -816,6 +816,8 @@ const char* Emitter::ComputeNullName() const { + return "NULL"; + case CamelNull: + return "Null"; ++ case EmptyNull: ++ return ""; + case TildeNull: + // fallthrough + default: +diff --git a/src/emitterstate.cpp b/src/emitterstate.cpp +index 3dbe401..0dd0b17 100644 +--- a/src/emitterstate.cpp ++++ b/src/emitterstate.cpp +@@ -305,6 +305,7 @@ bool EmitterState::SetNullFormat(EMITTER_MANIP value, FmtScope::value scope) { + case LowerNull: + case UpperNull: + case CamelNull: ++ case EmptyNull: + case TildeNull: + _Set(m_nullFmt, value, scope); + return true; diff --git a/src/exceptions.cpp b/src/exceptions.cpp index 43a7976..af99fd6 100644 --- a/src/exceptions.cpp diff --git a/tools/projmgr/include/ProjMgrParser.h b/tools/projmgr/include/ProjMgrParser.h index e86131971..b6965bd75 100644 --- a/tools/projmgr/include/ProjMgrParser.h +++ b/tools/projmgr/include/ProjMgrParser.h @@ -204,6 +204,25 @@ struct GeneratorsItem { std::map options; }; +/** + * @brief executes item containing + * execute description, + * command string, + * boolean run always, + * list of input files, + * list of output files, + * type inclusion +*/ +struct ExecutesItem { + std::string execute; + std::string run; + bool always; + std::vector input; + std::vector output; + std::vector dependsOn; + TypeFilter typeFilter; +}; + /** * @brief layer item containing * layer name, @@ -355,7 +374,8 @@ struct CdefaultItem { * list of contexts descriptors, * list of packs, * cdefault enable switch, - * generator options + * generator options, + * list of executes */ struct CsolutionItem { std::string name; @@ -372,6 +392,7 @@ struct CsolutionItem { bool enableCdefault; GeneratorsItem generators; CbuildPackItem cbuildPack; + std::vector executes; }; /** @@ -389,7 +410,8 @@ struct CsolutionItem { * list of connections, * list of packs, * list of linker entries, - * generator options + * generator options, + * list of executes */ struct CprojectItem { std::string name; @@ -406,6 +428,7 @@ struct CprojectItem { std::vector packs; std::vector linker; GeneratorsItem generators; + std::vector executes; }; /** diff --git a/tools/projmgr/include/ProjMgrUtils.h b/tools/projmgr/include/ProjMgrUtils.h index 802b3c8d6..fa8d80b9f 100644 --- a/tools/projmgr/include/ProjMgrUtils.h +++ b/tools/projmgr/include/ProjMgrUtils.h @@ -195,6 +195,20 @@ class ProjMgrUtils { */ static std::string ConvertToVersionRange(const std::string& version); + /** + * @brief create IO sequences table according to executes nodes input/output + * @param vector of executes nodes + * @return map with IO sequences + */ + static StrMap CreateIOSequenceMap(const std::vector& executes); + + /** + * @brief replace delimiters "::|:|&|@>=|@|.|/| " by underscore character + * @param input string + * @return string with replaced characters + */ + static std:: string ReplaceDelimiters(const std::string input); + protected: static std::string ConstructID(const std::vector>& elements); /** diff --git a/tools/projmgr/include/ProjMgrWorker.h b/tools/projmgr/include/ProjMgrWorker.h index 86ab21ee1..ed17e493c 100644 --- a/tools/projmgr/include/ProjMgrWorker.h +++ b/tools/projmgr/include/ProjMgrWorker.h @@ -472,6 +472,12 @@ class ProjMgrWorker { */ void GetYmlOrderedContexts(std::vector &contexts); + /** + * @brief get executes node at solution level + * @param reference to executes map + */ + void GetExecutes(std::map& executes); + /** * @brief set output directory * @param reference to output directory @@ -614,7 +620,23 @@ class ProjMgrWorker { bool ProcessGlobalGenerators(ContextItem* context, const std::string& generatorId, std::string& projectType, StrVec& siblings); + /** + * @brief check whether variable definition error is set + * @return true if error is set + */ bool HasVarDefineError(); + + /** + * @brief process solution level executes + * @return true if it is processed successfully + */ + bool ProcessSolutionExecutes(); + + /** + * @brief process executes nodes dependencies + */ + void ProcessExecutesDependencies(); + protected: ProjMgrParser* m_parser = nullptr; ProjMgrKernel* m_kernel = nullptr; @@ -644,6 +666,7 @@ class ProjMgrWorker { bool m_relativePaths; bool m_varDefineError; StrMap m_packMetadata; + std::map m_executes; bool LoadPacks(ContextItem& context); bool CheckMissingPackRequirements(const std::string& contextName); @@ -670,11 +693,12 @@ class ProjMgrWorker { bool ProcessGpdsc(ContextItem& context); bool ProcessConfigFiles(ContextItem& context); bool ProcessComponentFiles(ContextItem& context); + bool ProcessExecutes(ContextItem& context, bool solutionLevel = false); bool ProcessGroups(ContextItem& context); bool ProcessSequencesRelatives(ContextItem& context, bool rerun); - bool ProcessSequencesRelatives(ContextItem& context, std::vector& src, const std::string& ref = std::string(), bool withHeadingDot = false); + bool ProcessSequencesRelatives(ContextItem& context, std::vector& src, const std::string& ref = std::string(), std::string outDir = std::string(), bool withHeadingDot = false, bool solutionLevel = false); bool ProcessSequencesRelatives(ContextItem& context, BuildType& build, const std::string& ref = std::string()); - bool ProcessSequenceRelative(ContextItem& context, std::string& item, const std::string& ref = std::string(), bool withHeadingDot = false); + bool ProcessSequenceRelative(ContextItem& context, std::string& item, const std::string& ref = std::string(), std::string outDir = std::string(), bool withHeadingDot = false, bool solutionLevel = false); bool ProcessOutputFilenames(ContextItem& context); bool ProcessLinkerOptions(ContextItem& context); bool ProcessLinkerOptions(ContextItem& context, const LinkerItem& linker, const std::string& ref); @@ -727,7 +751,7 @@ class ProjMgrWorker { void CheckAndGenerateRegionsHeader(ContextItem& context); bool GenerateRegionsHeader(ContextItem& context, std::string& generatedRegionsFile); void UpdatePartialReferencedContext(ContextItem& context, std::string& contextName); - void ExpandAccessSequence(const ContextItem& context, const ContextItem& refContext, const std::string& sequence, std::string& item, bool withHeadingDot); + void ExpandAccessSequence(const ContextItem& context, const ContextItem& refContext, const std::string& sequence, const std::string& outdir, std::string& item, bool withHeadingDot); bool GetGeneratorDir(const RteGenerator* generator, ContextItem& context, const std::string& layer, std::string& genDir); bool GetGeneratorDir(const std::string& generatorId, ContextItem& context, const std::string& layer, std::string& genDir); bool GetExtGeneratorDir(const std::string& generatorId, ContextItem& context, const std::string& layer, std::string& genDir); @@ -741,6 +765,9 @@ class ProjMgrWorker { std::string GetContextRteFolder(ContextItem& context); std::vector FindMatchingPackIdsInCbuildPack(const PackItem& needle, const std::vector& resolvedPacks); void PrintContextErrors(const std::string& contextName); + void SetFilesDependencies(const GroupNode& group, const std::string& ouput, StrVec& dependsOn, const std::string& dep, const std::string& outDir); + void SetBuildOutputDependencies(const OutputTypes& outputTypes, const std::string& input, StrVec& dependsOn, const std::string& dep, const std::string& outDir); + void SetExecutesDependencies(const std::string& output, const std::string& dep, const std::string& outDir); }; #endif // PROJMGRWORKER_H diff --git a/tools/projmgr/include/ProjMgrYamlEmitter.h b/tools/projmgr/include/ProjMgrYamlEmitter.h index 9c18545fb..fd66c4557 100644 --- a/tools/projmgr/include/ProjMgrYamlEmitter.h +++ b/tools/projmgr/include/ProjMgrYamlEmitter.h @@ -29,12 +29,15 @@ class ProjMgrYamlEmitter { * @param parser reference * @param contexts vector with pointers to contexts * @param outputDir directory + * @param failed contexts + * @param executes nodes at solution level * @param boolean check schema of generated file * @return true if executed successfully */ static bool GenerateCbuildIndex(ProjMgrParser& parser, const std::vector& contexts, const std::string& outputDir, - const std::set& failedContexts, bool checkSchema); + const std::set& failedContexts, + const std::map& executes, bool checkSchema); /** * @brief generate cbuild-gen-idx.yml file diff --git a/tools/projmgr/include/ProjMgrYamlParser.h b/tools/projmgr/include/ProjMgrYamlParser.h index c000fc944..3b21d9bc5 100644 --- a/tools/projmgr/include/ProjMgrYamlParser.h +++ b/tools/projmgr/include/ProjMgrYamlParser.h @@ -14,6 +14,7 @@ * @brief YAML key definitions */ static constexpr const char* YAML_ADDPATH = "add-path"; +static constexpr const char* YAML_ALWAYS = "always"; static constexpr const char* YAML_ARGUMENT = "argument"; static constexpr const char* YAML_ARGUMENTS = "arguments"; static constexpr const char* YAML_ATTR = "attr"; @@ -71,6 +72,8 @@ static constexpr const char* YAML_DOWNLOAD_URL = "download-url"; static constexpr const char* YAML_DSP = "dsp"; static constexpr const char* YAML_ENDIAN = "endian"; static constexpr const char* YAML_ERRORS = "errors"; +static constexpr const char* YAML_EXECUTE = "execute"; +static constexpr const char* YAML_EXECUTES = "executes"; static constexpr const char* YAML_FILE = "file"; static constexpr const char* YAML_FILES = "files"; static constexpr const char* YAML_FROM_PACK = "from-pack"; @@ -90,6 +93,7 @@ static constexpr const char* YAML_GROUPS = "groups"; static constexpr const char* YAML_HOST = "host"; static constexpr const char* YAML_ID = "id"; static constexpr const char* YAML_INFO = "info"; +static constexpr const char* YAML_INPUT = "input"; static constexpr const char* YAML_INSTANCES = "instances"; static constexpr const char* YAML_LANGUAGE = "language"; static constexpr const char* YAML_LANGUAGE_C = "language-C"; @@ -238,6 +242,7 @@ class ProjMgrYamlParser { void ParseOutput(const YAML::Node& parent, const std::string& file, OutputItem& output); void ParseOutputDirs(const YAML::Node& parent, const std::string& file, struct DirectoriesItem& directories); void ParseGenerators(const YAML::Node& parent, const std::string& file, GeneratorsItem& generators); + void ParseExecutes(const YAML::Node& parent, const std::string& file, std::vector& executes); void ParseConnections(const YAML::Node& parent, std::vector& connects); bool ParseTargetType(const YAML::Node& parent, const std::string& file, TargetType& targetType); bool ParseBuildTypes(const YAML::Node& parent, const std::string& file, std::map& buildTypes); diff --git a/tools/projmgr/schemas/common.schema.json b/tools/projmgr/schemas/common.schema.json index 518ce6db6..80462bf93 100644 --- a/tools/projmgr/schemas/common.schema.json +++ b/tools/projmgr/schemas/common.schema.json @@ -800,7 +800,8 @@ "description": "The tool required to build this csolution project" }, "cdefault": { "type": "null", "description": "Enable use of cdefault.yml file" }, - "generators": { "$ref": "#/definitions/GeneratorsOutputType" } + "generators": { "$ref": "#/definitions/GeneratorsOutputType" }, + "executes": { "$ref": "#/definitions/ExecutesType" } }, "additionalProperties": false, "required": [ "target-types", "projects" ] @@ -833,7 +834,8 @@ "connections": { "$ref": "#/definitions/ConnectionsType" }, "linker": { "$ref": "#/definitions/LinkersType" }, "generators": { "$ref": "#/definitions/GeneratorsOutputType" }, - "rte": { "$ref": "#/definitions/RteType" } + "rte": { "$ref": "#/definitions/RteType" }, + "executes": { "$ref": "#/definitions/ExecutesType" } }, "additionalProperties": false }, @@ -846,7 +848,8 @@ "csolution": { "type": "string", "description": "Path to csolution.yml file" }, "cprojects": { "$ref": "#/definitions/BuildProjectsType" }, "cbuilds": { "$ref": "#/definitions/BuildContextsType" }, - "configurations": { "$ref": "#/definitions/BuildConfigurationsType" } + "configurations": { "$ref": "#/definitions/BuildConfigurationsType" }, + "executes": { "$ref": "#/definitions/BuildExecutesType" } }, "additionalProperties": false, "required": ["generated-by", "csolution", "cprojects"] @@ -1018,6 +1021,7 @@ "linker": { "$ref": "#/definitions/LinkerType" }, "groups": { "$ref": "#/definitions/BuildGroupsType" }, "generators": { "$ref": "#/definitions/GeneratorsType" }, + "executes": { "$ref": "#/definitions/BuildExecutesType" }, "constructed-files": { "type": "array", "items": { "$ref": "#/definitions/FileType" } @@ -1430,6 +1434,48 @@ "components": { "$ref": "#/definitions/ComponentsType" } }, "additionalProperties": false - } + }, + "ExecutesType": { + "description": "Execute and external command for pre or post build steps", + "type": "array", + "uniqueItems": true, + "items": { "$ref": "#/definitions/ExecuteType" } + }, + "ExecuteType": { + "type": "object", + "properties": { + "execute": { "type": "string", "description": "Description of the build step" }, + "run": { "type": "string", "description": "Command string with name of the program or script (optionally with path) along with argument string" }, + "always": { "type": "null", "description": "When present, the build step always runs and bypasses check for outdated files" }, + "input": { "type": "array", "description": "A list of input files (may contain Access Sequences)", "items": { "type": "string"} }, + "output": { "type": "array", "description": "A list of output files (may contain Access Sequences)", "items": { "type": "string"} }, + "for-context": { "$ref": "#/definitions/ForContext" }, + "not-for-context": { "$ref": "#/definitions/NotForContext" } + }, + "additionalProperties": false, + "allOf": [ + { "$ref": "#/definitions/TypeListMutualExclusion"}, + { "required": ["execute", "run"] } + ] + }, + "BuildExecutesType": { + "description": "Execute and external command for pre or post build steps", + "type": "array", + "uniqueItems": true, + "items": { "$ref": "#/definitions/BuildExecuteType" } + }, + "BuildExecuteType": { + "type": "object", + "properties": { + "execute": { "type": "string", "description": "Description of the build step" }, + "run": { "type": "string", "description": "Command string with name of the program or script (optionally with path) along with argument string" }, + "always": { "type": "null", "description": "When present, the build step always runs and bypasses check for outdated files" }, + "input": { "type": "array", "description": "A list of input files (may contain Access Sequences)", "items": { "type": "string"} }, + "output": { "type": "array", "description": "A list of output files (may contain Access Sequences)", "items": { "type": "string"} }, + "depends-on": { "$ref": "#/definitions/ArrayOfBuildContextWithProjectName" } + }, + "additionalProperties": false, + "required": ["execute", "run"] + } } } diff --git a/tools/projmgr/src/ProjMgr.cpp b/tools/projmgr/src/ProjMgr.cpp index 315459ab4..df504d7f9 100644 --- a/tools/projmgr/src/ProjMgr.cpp +++ b/tools/projmgr/src/ProjMgr.cpp @@ -493,7 +493,9 @@ bool ProjMgr::GenerateYMLConfigurationFiles() { // Generate cbuild index file if (!m_allContexts.empty()) { - if (!m_emitter.GenerateCbuildIndex(m_parser, m_allContexts, m_outputDir, m_failedContext, m_checkSchema)) { + map executes; + m_worker.GetExecutes(executes); + if (!m_emitter.GenerateCbuildIndex(m_parser, m_allContexts, m_outputDir, m_failedContext, executes, m_checkSchema)) { return false; } } @@ -563,6 +565,13 @@ bool ProjMgr::Configure() { } m_selectedToolchain = m_worker.GetSelectedToochain(); + // Process solution level executes + if (!m_worker.ProcessSolutionExecutes()) { + error = true; + } + // Process executes dependencies + m_worker.ProcessExecutesDependencies(); + // Print warnings for missing filters m_worker.PrintMissingFilters(); if (m_verbose) { @@ -830,7 +839,7 @@ bool ProjMgr::RunListLayers(void) { } if (!m_allContexts.empty()) { - if (!m_emitter.GenerateCbuildIndex(m_parser, m_allContexts, m_outputDir, m_failedContext, m_checkSchema)) { + if (!m_emitter.GenerateCbuildIndex(m_parser, m_allContexts, m_outputDir, m_failedContext, map(), m_checkSchema)) { return false; } } diff --git a/tools/projmgr/src/ProjMgrUtils.cpp b/tools/projmgr/src/ProjMgrUtils.cpp index 0d9cc6d95..cf1e75a92 100644 --- a/tools/projmgr/src/ProjMgrUtils.cpp +++ b/tools/projmgr/src/ProjMgrUtils.cpp @@ -292,3 +292,31 @@ string ProjMgrUtils::ConvertToVersionRange(const string& version) { } return versionRange; } + +StrMap ProjMgrUtils::CreateIOSequenceMap(const vector& executes) { + StrMap IOSeqMap = { + {"input", "${INPUT}"}, + {"output", "${OUTPUT}"} + }; + vector inputSizes, outputSizes; + for (auto& item : executes) { + inputSizes.push_back(item.input.size()); + outputSizes.push_back(item.output.size()); + } + if (inputSizes.size() > 0) { + for (int i = 0; i < *max_element(inputSizes.begin(), inputSizes.end()); i++) { + IOSeqMap["input(" + to_string(i) + ")"] = "${INPUT_" + to_string(i) + "}"; + } + } + if (outputSizes.size() > 0) { + for (int i = 0; i < *max_element(outputSizes.begin(), outputSizes.end()); i++) { + IOSeqMap["output(" + to_string(i) + ")"] = "${OUTPUT_" + to_string(i) + "}"; + } + } + return IOSeqMap; +} + +string ProjMgrUtils::ReplaceDelimiters(const string input) { + regex regEx = regex("::|:|&|@>=|@|\\.|/| "); + return(regex_replace(input, regEx, "_")); +} diff --git a/tools/projmgr/src/ProjMgrWorker.cpp b/tools/projmgr/src/ProjMgrWorker.cpp index a86b408de..2dba3fa06 100644 --- a/tools/projmgr/src/ProjMgrWorker.cpp +++ b/tools/projmgr/src/ProjMgrWorker.cpp @@ -191,6 +191,10 @@ void ProjMgrWorker::GetYmlOrderedContexts(vector &contexts) { contexts = m_ymlOrderedContexts; } +void ProjMgrWorker::GetExecutes(map& executes) { + executes = m_executes; +} + void ProjMgrWorker::SetOutputDir(const std::string& outputDir) { m_outputDir = outputDir; } @@ -2084,6 +2088,106 @@ bool ProjMgrWorker::ProcessComponentFiles(ContextItem& context) { return true; } +void ProjMgrWorker::SetFilesDependencies(const GroupNode& group, const string& ouput, StrVec& dependsOn, const string& dep, const string& outDir) { + for (auto fileNode : group.files) { + RteFsUtils::NormalizePath(fileNode.file, outDir); + if (fileNode.file == ouput) { + CollectionUtils::PushBackUniquely(dependsOn, dep); + } + } + for (const auto& groupNode : group.groups) { + SetFilesDependencies(groupNode, ouput, dependsOn, dep, outDir); + } +} + +void ProjMgrWorker::SetExecutesDependencies(const string& output, const string& dep, const string& outDir) { + for (auto& [_, item] : m_executes) { + for (auto input : item.input) { + RteFsUtils::NormalizePath(input, outDir); + if (input == output) { + CollectionUtils::PushBackUniquely(item.dependsOn, dep); + } + } + } +} + +void ProjMgrWorker::SetBuildOutputDependencies(const OutputTypes& types, const string& input, StrVec& dependsOn, const string& dep, const string& outDir) { + const vector> outputTypes = { + { types.bin.on, types.bin.filename }, + { types.elf.on, types.elf.filename }, + { types.hex.on, types.hex.filename }, + { types.lib.on, types.lib.filename }, + { types.cmse.on, types.cmse.filename }, + }; + for (auto [on, file] : outputTypes) { + if (on) { + RteFsUtils::NormalizePath(file, outDir); + if (file == input) { + CollectionUtils::PushBackUniquely(dependsOn, dep); + } + } + } +} + +void ProjMgrWorker::ProcessExecutesDependencies() { + // set dependencies by searching matching entries among: + // - executes outputs and user files + // - executes outputs and executes inputs (inter-dependencies) + // - executes inputs and build outputs + const string& outDir = m_outputDir.empty() ? m_parser->GetCsolution().directory : m_outputDir; + for (auto& [_, item] : m_executes) { + // iterate over output items + for (auto output : item.output) { + RteFsUtils::NormalizePath(output, outDir); + for (const auto& contextName : m_selectedContexts) { + auto& context = m_contexts.at(contextName); + // user files dependencies + for (const auto& groupNode : context.groups) { + SetFilesDependencies(groupNode, output, context.dependsOn, item.execute, context.directories.cprj); + } + } + // solution execute nodes inter-dependencies + SetExecutesDependencies(output, item.execute, m_outputDir.empty() ? m_parser->GetCsolution().directory : m_outputDir); + } + // iterate over input items + for (auto input : item.input) { + RteFsUtils::NormalizePath(input, outDir); + for (const auto& contextName : m_selectedContexts) { + auto& context = m_contexts.at(contextName); + // build output dependencies + SetBuildOutputDependencies(context.outputTypes, input, item.dependsOn, contextName, + fs::path(context.directories.cprj).append(context.directories.outdir).generic_string()); + } + } + } +} + +bool ProjMgrWorker::ProcessExecutes(ContextItem& context, bool solutionLevel) { + const vector& executes = solutionLevel ? m_parser->GetCsolution().executes : context.cproject->executes; + const string& ref = solutionLevel ? m_parser->GetCsolution().directory : context.cproject->directory; + const string& outDir = m_outputDir.empty() ? m_parser->GetCsolution().directory : m_outputDir; + const auto& IOSeqMap = ProjMgrUtils::CreateIOSequenceMap(executes); + for (const auto& item : executes) { + if (solutionLevel || CheckContextFilters(item.typeFilter, context)) { + const string& execute = (solutionLevel ? "" : context.name + "-") + ProjMgrUtils::ReplaceDelimiters(item.execute); + m_executes[execute] = item; + m_executes[execute].execute = execute; + // expand access sequences + m_executes[execute].run = RteUtils::ExpandAccessSequences(m_executes[execute].run, IOSeqMap); + if (!ProcessSequencesRelatives(context, m_executes[execute].input, ref, outDir, true, solutionLevel) || + !ProcessSequencesRelatives(context, m_executes[execute].output, ref, outDir, true, solutionLevel)) { + return false; + } + } + } + return true; +} + +bool ProjMgrWorker::ProcessSolutionExecutes() { + ContextItem context; + return ProcessExecutes(context, true); +} + bool ProjMgrWorker::IsPreIncludeByTarget(const RteTarget* activeTarget, const string& preInclude) { const auto& preIncludeFiles = activeTarget->GetPreIncludeFiles(); for (const auto& [_, fileSet] : preIncludeFiles) { @@ -2787,17 +2891,17 @@ void ProjMgrWorker::UpdatePartialReferencedContext(ContextItem& context, string& } } -void ProjMgrWorker::ExpandAccessSequence(const ContextItem & context, const ContextItem & refContext, const string & sequence, string & item, bool withHeadingDot) { +void ProjMgrWorker::ExpandAccessSequence(const ContextItem& context, const ContextItem& refContext, const string& sequence, const string& outdir, string& item, bool withHeadingDot) { const string refContextOutDir = refContext.directories.cprj + "/" + refContext.directories.outdir; - const string relOutDir = RteFsUtils::RelativePath(refContextOutDir, context.directories.cprj, withHeadingDot); + const string relOutDir = RteFsUtils::RelativePath(refContextOutDir, outdir, withHeadingDot); string regExStr = "\\$"; string replacement; if (sequence == RteConstants::AS_SOLUTION_DIR) { regExStr += RteConstants::AS_SOLUTION_DIR; - replacement = RteFsUtils::RelativePath(refContext.csolution->directory, context.directories.cprj, withHeadingDot); + replacement = RteFsUtils::RelativePath(refContext.csolution->directory, outdir, withHeadingDot); } else if (sequence == RteConstants::AS_PROJECT_DIR) { regExStr += RteConstants::AS_PROJECT_DIR; - replacement = RteFsUtils::RelativePath(refContext.cproject->directory, context.directories.cprj, withHeadingDot); + replacement = RteFsUtils::RelativePath(refContext.cproject->directory, outdir, withHeadingDot); } else if (sequence == RteConstants::AS_OUT_DIR) { regExStr += RteConstants::AS_OUT_DIR; replacement = relOutDir; @@ -2821,11 +2925,12 @@ void ProjMgrWorker::ExpandAccessSequence(const ContextItem & context, const Cont item = regex_replace(item, regEx, replacement); } -bool ProjMgrWorker::ProcessSequenceRelative(ContextItem& context, string& item, const string& ref, bool withHeadingDot) { +bool ProjMgrWorker::ProcessSequenceRelative(ContextItem& context, string& item, const string& ref, string outDir, bool withHeadingDot, bool solutionLevel) { size_t offset = 0; bool pathReplace = false; + outDir = outDir.empty() ? context.directories.cprj : outDir; // expand variables (static access sequences) - const string input = item = RteUtils::ExpandAccessSequences(item, context.variables); + const string input = item = solutionLevel ? item : RteUtils::ExpandAccessSequences(item, context.variables); // expand dynamic access sequences while (offset != string::npos) { string sequence; @@ -2841,8 +2946,25 @@ bool ProjMgrWorker::ProcessSequenceRelative(ContextItem& context, string& item, string contextName = asMatches[2]; // access sequences with 'context' argument lead to path replacement pathReplace = true; - // update referenced context name when it's partially specified - UpdatePartialReferencedContext(context, contextName); + // get referenced context name + if (solutionLevel) { + // solution level: referenced context name must lead to a compatible context + StrVec compatibleContexts; + for (const auto& [ctx, _] : m_contexts) { + if (ctx.find(contextName) != string::npos) { + compatibleContexts.push_back(ctx); + } + } + if (compatibleContexts.empty()) { + ProjMgrLogger::Error(m_parser->GetCsolution().path, "context '" + contextName + "' referenced by access sequence '" + sequenceName + "' is not compatible"); + return false; + } + contextName = compatibleContexts.front(); + context = m_contexts.at(contextName); + } else { + // context level: update referenced context name when it's partially specified + UpdatePartialReferencedContext(context, contextName); + } // find referenced context if (m_contexts.find(contextName) != m_contexts.end()) { error_code ec; @@ -2857,7 +2979,7 @@ bool ProjMgrWorker::ProcessSequenceRelative(ContextItem& context, string& item, } } // expand access sequence - ExpandAccessSequence(context, refContext, sequenceName, item, withHeadingDot); + ExpandAccessSequence(context, refContext, sequenceName, outDir, item, withHeadingDot); // store dependency information if (refContext.name != context.name) { CollectionUtils::PushBackUniquely(context.dependsOn, refContext.name); @@ -2877,9 +2999,9 @@ bool ProjMgrWorker::ProcessSequenceRelative(ContextItem& context, string& item, if (!pathReplace && !ref.empty()) { error_code ec; // adjust relative path according to the given reference - if (!fs::equivalent(context.directories.cprj, ref, ec)) { + if (!fs::equivalent(outDir, ref, ec)) { const string absPath = RteFsUtils::MakePathCanonical(fs::path(item).is_relative() ? ref + "/" + item : item); - item = RteFsUtils::RelativePath(absPath, context.directories.cprj, withHeadingDot); + item = RteFsUtils::RelativePath(absPath, outDir, withHeadingDot); } } return true; @@ -3172,6 +3294,7 @@ bool ProjMgrWorker::ProcessContext(ContextItem& context, bool loadGenFiles, bool } ret &= ProcessConfigFiles(context); ret &= ProcessComponentFiles(context); + ret &= ProcessExecutes(context); bool bUnresolvedDependencies = false; if (resolveDependencies) { // TODO: Add uniquely identified missing dependencies to RTE Model @@ -3786,9 +3909,9 @@ std::string ProjMgrWorker::GetBoardInfoString(const std::string& vendor, name + (revision.empty() ? "" : ":" + revision); } -bool ProjMgrWorker::ProcessSequencesRelatives(ContextItem& context, vector& src, const string& ref, bool withHeadingDot) { +bool ProjMgrWorker::ProcessSequencesRelatives(ContextItem& context, vector& src, const string& ref, string outDir, bool withHeadingDot, bool solutionLevel) { for (auto& item : src) { - if (!ProcessSequenceRelative(context, item, ref, withHeadingDot)) { + if (!ProcessSequenceRelative(context, item, ref, outDir, withHeadingDot, solutionLevel)) { return false; } } @@ -3803,15 +3926,15 @@ bool ProjMgrWorker::ProcessSequencesRelatives(ContextItem& context, BuildType& b return false; } for (auto& misc : build.misc) { - if (!ProcessSequencesRelatives(context, misc.as, "", true) || - !ProcessSequencesRelatives(context, misc.c, "", true) || - !ProcessSequencesRelatives(context, misc.cpp, "", true) || - !ProcessSequencesRelatives(context, misc.c_cpp, "", true) || - !ProcessSequencesRelatives(context, misc.lib, "", true) || - !ProcessSequencesRelatives(context, misc.library, "", true) || - !ProcessSequencesRelatives(context, misc.link, "", true) || - !ProcessSequencesRelatives(context, misc.link_c, "", true) || - !ProcessSequencesRelatives(context, misc.link_cpp, "", true)) { + if (!ProcessSequencesRelatives(context, misc.as, "", "", true) || + !ProcessSequencesRelatives(context, misc.c, "", "", true) || + !ProcessSequencesRelatives(context, misc.cpp, "", "", true) || + !ProcessSequencesRelatives(context, misc.c_cpp, "", "", true) || + !ProcessSequencesRelatives(context, misc.lib, "", "", true) || + !ProcessSequencesRelatives(context, misc.library, "", "", true) || + !ProcessSequencesRelatives(context, misc.link, "", "", true) || + !ProcessSequencesRelatives(context, misc.link_c, "", "", true) || + !ProcessSequencesRelatives(context, misc.link_cpp, "", "", true)) { return false; } } diff --git a/tools/projmgr/src/ProjMgrYamlEmitter.cpp b/tools/projmgr/src/ProjMgrYamlEmitter.cpp index 3821ad367..a72b2f6da 100644 --- a/tools/projmgr/src/ProjMgrYamlEmitter.cpp +++ b/tools/projmgr/src/ProjMgrYamlEmitter.cpp @@ -37,6 +37,7 @@ class ProjMgrYamlBase { bool CompareFile(const string& filename, const YAML::Node& rootNode); bool CompareNodes(const YAML::Node& lhs, const YAML::Node& rhs); bool WriteFile(YAML::Node& rootNode, const std::string& filename, bool allowUpdate = true); + void SetExecutesNode(YAML::Node node, const map& executes, const string& base, const string& ref); const bool m_useAbsolutePaths; const bool m_checkSchema; }; @@ -72,8 +73,8 @@ class ProjMgrYamlCbuildIdx : public ProjMgrYamlBase { friend class ProjMgrYamlEmitter; ProjMgrYamlCbuildIdx( YAML::Node node, const vector& processedContexts, - ProjMgrParser& parser, const string& directory, - const set& failedContexts, bool checkSchema); + ProjMgrParser& parser, const string& directory, const set& failedContexts, + const map& executes, bool checkSchema); void SetVariablesNode(YAML::Node node, const string& csolutionDir, const map>>& layerTypes); }; @@ -208,8 +209,8 @@ ProjMgrYamlBase::ProjMgrYamlBase(bool useAbsolutePaths, bool checkSchema) : m_us } ProjMgrYamlCbuildIdx::ProjMgrYamlCbuildIdx(YAML::Node node, - const vector& processedContexts, ProjMgrParser& parser, - const string& directory, const set& failedContexts, bool checkSchema) : ProjMgrYamlBase(false, checkSchema) + const vector& processedContexts, ProjMgrParser& parser, const string& directory, const set& failedContexts, + const map& executes, bool checkSchema) : ProjMgrYamlBase(false, checkSchema) { error_code ec; SetNodeValue(node[YAML_GENERATED_BY], ORIGINAL_FILENAME + string(" version ") + VERSION_STRING); @@ -318,6 +319,8 @@ ProjMgrYamlCbuildIdx::ProjMgrYamlCbuildIdx(YAML::Node node, node[YAML_CBUILDS].push_back(cbuildNode); } } + + SetExecutesNode(node[YAML_EXECUTES], executes, directory, directory); } void ProjMgrYamlCbuildIdx::SetVariablesNode(YAML::Node node, const string& csolutionDir, const map>>& layerTypes) { @@ -704,6 +707,29 @@ void ProjMgrYamlCbuild::SetLicenseInfoNode(YAML::Node node, const ContextItem* c } } +void ProjMgrYamlBase::SetExecutesNode(YAML::Node node, const map& executes, const string& base, const string& ref) { + for (const auto& [_, item] : executes) { + YAML::Node executeNode; + SetNodeValue(executeNode[YAML_EXECUTE], item.execute); + SetNodeValue(executeNode[YAML_RUN], item.run); + if (item.always) { + executeNode[YAML_ALWAYS] = YAML::Null; + } + vector input; + for (const auto& file : item.input) { + input.push_back(FormatPath(base + "/" + file, ref)); + } + SetNodeValue(executeNode[YAML_INPUT], input); + vector output; + for (const auto& file : item.output) { + output.push_back(FormatPath(base + "/" + file, ref)); + } + SetNodeValue(executeNode[YAML_OUTPUT], output); + SetNodeValue(executeNode[YAML_DEPENDS_ON], item.dependsOn); + node.push_back(executeNode); + } +} + void ProjMgrYamlCbuild::SetControlsNode(YAML::Node node, const ContextItem* context, const BuildType& controls) { SetNodeValue(node[YAML_OPTIMIZE], controls.optimize); SetNodeValue(node[YAML_DEBUG], controls.debug); @@ -881,6 +907,7 @@ bool ProjMgrYamlBase::WriteFile(YAML::Node& rootNode, const std::string& filenam return false; } YAML::Emitter emitter; + emitter.SetNullFormat(YAML::EmptyNull); emitter << rootNode; fileStream << emitter.c_str(); fileStream << endl; @@ -900,15 +927,16 @@ bool ProjMgrYamlBase::WriteFile(YAML::Node& rootNode, const std::string& filenam } bool ProjMgrYamlEmitter::GenerateCbuildIndex(ProjMgrParser& parser, - const vector& contexts, const string& outputDir, - const set& failedContexts, bool checkSchema) { + const vector& contexts, const string& outputDir, const set& failedContexts, + const map& executes, bool checkSchema) { + // generate cbuild-idx.yml const string& directory = outputDir.empty() ? parser.GetCsolution().directory : RteFsUtils::AbsolutePath(outputDir).generic_string(); const string& filename = directory + "/" + parser.GetCsolution().name + ".cbuild-idx.yml"; YAML::Node rootNode; ProjMgrYamlCbuildIdx cbuild( - rootNode[YAML_BUILD_IDX], contexts, parser, directory, failedContexts, checkSchema); + rootNode[YAML_BUILD_IDX], contexts, parser, directory, failedContexts, executes, checkSchema); return cbuild.WriteFile(rootNode, filename); } diff --git a/tools/projmgr/src/ProjMgrYamlParser.cpp b/tools/projmgr/src/ProjMgrYamlParser.cpp index 7316eeb05..53e2a25fa 100644 --- a/tools/projmgr/src/ProjMgrYamlParser.cpp +++ b/tools/projmgr/src/ProjMgrYamlParser.cpp @@ -104,6 +104,7 @@ bool ProjMgrYamlParser::ParseCsolution(const string& input, ParsePacks(solutionNode, csolution.path, csolution.packs); csolution.enableCdefault = solutionNode[YAML_CDEFAULT].IsDefined(); ParseGenerators(solutionNode, csolution.path, csolution.generators); + ParseExecutes(solutionNode, csolution.path, csolution.executes); } catch (YAML::Exception& e) { ProjMgrLogger::Error(input, e.mark.line + 1, e.mark.column + 1, e.msg); @@ -190,6 +191,8 @@ bool ProjMgrYamlParser::ParseCproject(const string& input, ParseRte(projectNode, cproject.rteBaseDir); + ParseExecutes(projectNode, cproject.path, cproject.executes); + } catch (YAML::Exception& e) { ProjMgrLogger::Error(input, e.mark.line + 1, e.mark.column + 1, e.msg); return false; @@ -485,6 +488,22 @@ void ProjMgrYamlParser::ParseGenerators(const YAML::Node& parent, const string& } } +void ProjMgrYamlParser::ParseExecutes(const YAML::Node& parent, const string& file, std::vector& executes) { + if (parent[YAML_EXECUTES].IsDefined()) { + const YAML::Node& executesNode = parent[YAML_EXECUTES]; + for (const auto& executesEntry : executesNode) { + ExecutesItem executesItem; + ParseString(executesEntry, YAML_EXECUTE, executesItem.execute); + ParseString(executesEntry, YAML_RUN, executesItem.run); + executesItem.always = executesEntry[YAML_ALWAYS].IsDefined(); + ParsePortablePaths(executesEntry, file, YAML_INPUT, executesItem.input); + ParsePortablePaths(executesEntry, file, YAML_OUTPUT, executesItem.output); + ParseTypeFilter(executesEntry, executesItem.typeFilter); + executes.push_back(executesItem); + } + } +} + void ProjMgrYamlParser::ParseRte(const YAML::Node& parent, string& rteBaseDir) { if (parent[YAML_RTE].IsDefined()) { const YAML::Node& rteNode = parent[YAML_RTE]; @@ -880,6 +899,7 @@ const set solutionKeys = { YAML_CREATED_FOR, YAML_CDEFAULT, YAML_GENERATORS, + YAML_EXECUTES, }; const set projectsKeys = { @@ -914,6 +934,7 @@ const set projectKeys = { YAML_LINKER, YAML_GENERATORS, YAML_RTE, + YAML_EXECUTES, }; const set setupKeys = { @@ -1151,6 +1172,16 @@ const set filesKeys = { YAML_MISC, }; +const set executesKeys = { + YAML_EXECUTE, + YAML_RUN, + YAML_ALWAYS, + YAML_INPUT, + YAML_OUTPUT, + YAML_FORCONTEXT, + YAML_NOTFORCONTEXT, +}; + const map> sequences = { {YAML_PROJECTS, projectsKeys}, {YAML_TARGETTYPES, targetTypeKeys}, @@ -1165,6 +1196,9 @@ const map> sequences = { {YAML_FILES, filesKeys}, {YAML_SETUPS, setupKeys}, {YAML_LINKER, linkerKeys}, + {YAML_EXECUTES, executesKeys}, + {YAML_INPUT, {}}, + {YAML_OUTPUT, {}}, }; const map> mappings = { @@ -1270,7 +1304,7 @@ bool ProjMgrYamlParser::ValidateKeys(const string& input, const YAML::Node& pare if (!ValidateSequence(input, item.second, key)) { valid = false; } - } else if (sequences.find(key) != sequences.end()) { + } else if ((sequences.find(key) != sequences.end()) && (mappings.find(key) == mappings.end())) { ProjMgrLogger::Error(input, item.first.Mark().line + 1, item.first.Mark().column + 1, "node '" + item.first.as() + "' shall contain sequence elements"); valid = false; } @@ -1278,7 +1312,7 @@ bool ProjMgrYamlParser::ValidateKeys(const string& input, const YAML::Node& pare if (!ValidateMapping(input, item.second, key)) { valid = false; } - } else if (mappings.find(key) != mappings.end()) { + } else if ((mappings.find(key) != mappings.end()) && (sequences.find(key) == sequences.end())) { ProjMgrLogger::Error(input, item.first.Mark().line + 1, item.first.Mark().column + 1, "node '" + item.first.as() + "' shall contain mapping elements"); valid = false; } diff --git a/tools/projmgr/test/data/TestSolution/Executes/error.csolution.yml b/tools/projmgr/test/data/TestSolution/Executes/error.csolution.yml new file mode 100644 index 000000000..4dc6b6596 --- /dev/null +++ b/tools/projmgr/test/data/TestSolution/Executes/error.csolution.yml @@ -0,0 +1,46 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Open-CMSIS-Pack/devtools/main/tools/projmgr/schemas/csolution.schema.json + +solution: + created-for: CMSIS-Toolbox@2.2.1 + cdefault: + + packs: + - pack: ARM::RteTest_DFP + + target-types: + - type: RteTest_ARMCM3 + device: RteTest_ARMCM3 + + build-types: + - type: Debug + - type: Release + + compiler: AC6 + + projects: + - project: project/project.cproject.yml + + executes: + - execute: Generate Project Sources + run: ${CMAKE_COMMAND} -DINPUT_1=$input(1)$ -DOUTPUT_0=$output(0)$ -DOUTPUT_1=$output(1)$ -P $input(0)$ + input: + - script/generate-sources.cmake + - $ProjectDir(project)$/source.c.template + output: + - $ProjectDir(project)$/source0.c + - $ProjectDir(project)$/source1.c + - execute: Archive Artifacts + run: ${CMAKE_COMMAND} -DINPUT=$input$ -DOUTPUT=$output$ -P $input(0)$ + input: + - script/archive.cmake + - $elf(unknown.Debug+RteTest_ARMCM3)$ + - $elf(project.Debug+RteTest_ARMCM3)$.signed + output: + - $SolutionDir()$/artifacts.zip + - execute: Run Always + run: ${CMAKE_COMMAND} -E echo "Execute Run Always" + always: + - execute: Run After Archiving + run: ${CMAKE_COMMAND} -E echo "Archive has been updated" + input: + - $SolutionDir()$/artifacts.zip diff --git a/tools/projmgr/test/data/TestSolution/Executes/project/project.cproject.yml b/tools/projmgr/test/data/TestSolution/Executes/project/project.cproject.yml new file mode 100644 index 000000000..9d11d04e4 --- /dev/null +++ b/tools/projmgr/test/data/TestSolution/Executes/project/project.cproject.yml @@ -0,0 +1,24 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Open-CMSIS-Pack/devtools/main/tools/projmgr/schemas/cproject.schema.json + +project: + + components: + - component: ARM::RteTest:CORE + - component: ARM::Device:Startup + + groups: + - group: Source + files: + - file: ./source0.c + - file: ./source1.c + + executes: + - execute: Sign Artifact + run: ${CMAKE_COMMAND} -DINPUT=$input$ -DOUTPUT=$output$ -P $input(0)$ + input: + - $SolutionDir()$/script/sign.cmake + - $elf()$ + output: + - $elf()$.signed + for-context: + - .Release diff --git a/tools/projmgr/test/data/TestSolution/Executes/ref/solution.cbuild-idx.yml b/tools/projmgr/test/data/TestSolution/Executes/ref/solution.cbuild-idx.yml new file mode 100644 index 000000000..19a261c56 --- /dev/null +++ b/tools/projmgr/test/data/TestSolution/Executes/ref/solution.cbuild-idx.yml @@ -0,0 +1,58 @@ +build-idx: + generated-by: csolution version 0.0.0+gdf212332 + cdefault: ${CMSIS_COMPILER_ROOT}/cdefault.yml + csolution: ../data/TestSolution/Executes/solution.csolution.yml + cprojects: + - cproject: ../data/TestSolution/Executes/project/project.cproject.yml + cbuilds: + - cbuild: project.Debug+RteTest_ARMCM3.cbuild.yml + project: project + configuration: .Debug+RteTest_ARMCM3 + depends-on: + - Generate_Project_Sources + - cbuild: project.Release+RteTest_ARMCM3.cbuild.yml + project: project + configuration: .Release+RteTest_ARMCM3 + depends-on: + - Generate_Project_Sources + executes: + - execute: Archive_Artifacts + run: ${CMAKE_COMMAND} -DINPUT=${INPUT} -DOUTPUT=${OUTPUT} -P ${INPUT_0} + input: + - ../data/TestSolution/Executes/script/archive.cmake + - out/project/RteTest_ARMCM3/Debug/project.axf + - out/project/RteTest_ARMCM3/Debug/project.axf.signed + output: + - ../data/TestSolution/Executes/artifacts.zip + depends-on: + - project.Debug+RteTest_ARMCM3 + - execute: Generate_Project_Sources + run: ${CMAKE_COMMAND} -DINPUT_1=${INPUT_1} -DOUTPUT_0=${OUTPUT_0} -DOUTPUT_1=${OUTPUT_1} -P ${INPUT_0} + input: + - ../data/TestSolution/Executes/script/generate-sources.cmake + - ../data/TestSolution/Executes/project/source.c.template + output: + - ../data/TestSolution/Executes/project/source0.c + - ../data/TestSolution/Executes/project/source1.c + - execute: Run_After_Archiving + run: ${CMAKE_COMMAND} -E echo "Archive has been updated" + input: + - ../data/TestSolution/Executes/artifacts.zip + depends-on: + - Archive_Artifacts + - execute: Run_Always + run: ${CMAKE_COMMAND} -E echo "Execute Run Always" + always: + input: + - ../data/TestSolution/Executes/artifacts.zip + depends-on: + - Archive_Artifacts + - execute: project.Release+RteTest_ARMCM3-Sign_Artifact + run: ${CMAKE_COMMAND} -DINPUT=${INPUT} -DOUTPUT=${OUTPUT} -P ${INPUT_0} + input: + - ../data/TestSolution/Executes/script/sign.cmake + - out/project/RteTest_ARMCM3/Release/project.axf + output: + - out/project/RteTest_ARMCM3/Release/project.axf.signed + depends-on: + - project.Release+RteTest_ARMCM3 diff --git a/tools/projmgr/test/data/TestSolution/Executes/solution.csolution.yml b/tools/projmgr/test/data/TestSolution/Executes/solution.csolution.yml new file mode 100644 index 000000000..420d32710 --- /dev/null +++ b/tools/projmgr/test/data/TestSolution/Executes/solution.csolution.yml @@ -0,0 +1,48 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Open-CMSIS-Pack/devtools/main/tools/projmgr/schemas/csolution.schema.json + +solution: + created-for: CMSIS-Toolbox@2.2.1 + cdefault: + + packs: + - pack: ARM::RteTest_DFP + + target-types: + - type: RteTest_ARMCM3 + device: RteTest_ARMCM3 + + build-types: + - type: Debug + - type: Release + + compiler: AC6 + + projects: + - project: project/project.cproject.yml + + executes: + - execute: Generate Project Sources + run: ${CMAKE_COMMAND} -DINPUT_1=$input(1)$ -DOUTPUT_0=$output(0)$ -DOUTPUT_1=$output(1)$ -P $input(0)$ + input: + - script/generate-sources.cmake + - $ProjectDir(project)$/source.c.template + output: + - $ProjectDir(project)$/source0.c + - $ProjectDir(project)$/source1.c + - execute: Archive Artifacts + run: ${CMAKE_COMMAND} -DINPUT=$input$ -DOUTPUT=$output$ -P $input(0)$ + input: + - script/archive.cmake + - $elf(project.Debug+RteTest_ARMCM3)$ + - $elf(project.Debug+RteTest_ARMCM3)$.signed + output: + - $SolutionDir()$/artifacts.zip + - execute: Run Always + run: ${CMAKE_COMMAND} -E echo "Execute Run Always" + always: + input: + - $SolutionDir()$/artifacts.zip + - execute: Run After Archiving + run: ${CMAKE_COMMAND} -E echo "Archive has been updated" + input: + - $SolutionDir()$/artifacts.zip diff --git a/tools/projmgr/test/data/TestSolution/invalid_keys_test.cbuild-set.yml b/tools/projmgr/test/data/TestSolution/invalid_keys_test.cbuild-set.yml index 0c51073f2..825795b55 100644 --- a/tools/projmgr/test/data/TestSolution/invalid_keys_test.cbuild-set.yml +++ b/tools/projmgr/test/data/TestSolution/invalid_keys_test.cbuild-set.yml @@ -2,7 +2,7 @@ cbuild-set: generated-by: csolution version 2.1.0 # invalid data - output: test1 + output-dirs: test1 contexts: - context: test2.Debug+CM0 - context: test2.Debug+CM3 diff --git a/tools/projmgr/test/src/ProjMgrUnitTests.cpp b/tools/projmgr/test/src/ProjMgrUnitTests.cpp index abf4b86e1..3bc3ceba1 100644 --- a/tools/projmgr/test/src/ProjMgrUnitTests.cpp +++ b/tools/projmgr/test/src/ProjMgrUnitTests.cpp @@ -5405,3 +5405,26 @@ TEST_F(ProjMgrUnitTests, CbuildPackSelectBy) { EXPECT_EQ(0, RunProjMgr(6, argv, 0)); EXPECT_TRUE(streamRedirect.GetErrorString().empty()); } + +TEST_F(ProjMgrUnitTests, Executes) { + char* argv[6]; + const string& csolution = testinput_folder + "/TestSolution/Executes/solution.csolution.yml"; + argv[1] = (char*)"convert"; + argv[2] = (char*)"--solution"; + argv[3] = (char*)csolution.c_str(); + argv[4] = (char*)"-o"; + argv[5] = (char*)testoutput_folder.c_str(); + EXPECT_EQ(0, RunProjMgr(6, argv, 0)); + + // Check generated files + ProjMgrTestEnv::CompareFile(testoutput_folder + "/solution.cbuild-idx.yml", + testinput_folder + "/TestSolution/Executes/ref/solution.cbuild-idx.yml"); + + // Check error message + StdStreamRedirect streamRedirect; + const string& csolutionError = testinput_folder + "/TestSolution/Executes/error.csolution.yml"; + argv[3] = (char*)csolutionError.c_str(); + EXPECT_EQ(1, RunProjMgr(6, argv, 0)); + auto errStr = streamRedirect.GetErrorString(); + EXPECT_NE(string::npos, errStr.find("error csolution: context 'unknown.Debug+RteTest_ARMCM3' referenced by access sequence 'elf' is not compatible")); +} diff --git a/tools/projmgr/test/src/ProjMgrUtilsUnitTests.cpp b/tools/projmgr/test/src/ProjMgrUtilsUnitTests.cpp index 030abc8b7..d919db4d4 100644 --- a/tools/projmgr/test/src/ProjMgrUtilsUnitTests.cpp +++ b/tools/projmgr/test/src/ProjMgrUtilsUnitTests.cpp @@ -446,3 +446,11 @@ TEST_F(ProjMgrUtilsUnitTests, ConvertToVersionRange) { EXPECT_EQ("1.2.3:1.2.3", ProjMgrUtils::ConvertToVersionRange("1.2.3")); EXPECT_EQ("1.2.3", ProjMgrUtils::ConvertToVersionRange(">=1.2.3")); } + +TEST_F(ProjMgrUtilsUnitTests, ReplaceDelimiters) { + EXPECT_EQ("Cvendor_Cbundle_Cclass_Cgroup_Cvariant_Cversion", ProjMgrUtils::ReplaceDelimiters("Cvendor&Cbundle::Cclass:Cgroup&Cvariant@Cversion")); + EXPECT_EQ("ARM_CMSIS_CORE_A", ProjMgrUtils::ReplaceDelimiters("ARM::CMSIS.CORE A")); + EXPECT_EQ("AC6_6_16_0", ProjMgrUtils::ReplaceDelimiters("AC6@>=6.16.0")); + EXPECT_EQ("path_with_spaces", ProjMgrUtils::ReplaceDelimiters("path/with spaces")); +} +