diff --git a/tools/projmgr/docs/Manual/Overview.md b/tools/projmgr/docs/Manual/Overview.md index 79f3de54e..db2430757 100644 --- a/tools/projmgr/docs/Manual/Overview.md +++ b/tools/projmgr/docs/Manual/Overview.md @@ -143,35 +143,37 @@ Usage: csolution [.csolution.yml] [options] Commands: - convert Convert user input *.yml files to *.cprj files - list boards Print list of available board names - list contexts Print list of contexts in a .csolution.yml - list components Print list of available components - list dependencies Print list of unresolved project dependencies - list devices Print list of available device names - list environment Print list of environment configurations - list generators Print list of code generators of a given context - list layers Print list of available, referenced and compatible layers - list packs Print list of used packs from the pack repository - list toolchains Print list of supported toolchains - run Run code generator - update-rte Create/update configuration files and validate solution + convert Convert user input *.yml files to *.cprj files + list boards Print list of available board names + list contexts Print list of contexts in a .csolution.yml + list components Print list of available components + list dependencies Print list of unresolved project dependencies + list devices Print list of available device names + list environment Print list of environment configurations + list generators Print list of code generators of a given context + list layers Print list of available, referenced and compatible layers + list packs Print list of used packs from the pack repository + list toolchains Print list of supported toolchains + run Run code generator + update-rte Create/update configuration files and validate solution Options: - -c, --context arg [...] Input context names [][.][+] - -d, --debug Enable debug messages - -e, --export arg Set suffix for exporting .cprj retaining only specified versions - -f, --filter arg Filter words - -g, --generator arg Code generator identifier - -l, --load arg Set policy for packs loading [latest | all | required] - -L, --clayer-path arg Set search path for external clayers - -m, --missing List only required packs that are missing in the pack repository - -n, --no-check-schema Skip schema check - -N, --no-update-rte Skip creation of RTE directory and files - -o, --output arg Output directory - -t, --toolchain arg Selection of the toolchain used in the project optionally with version - -v, --verbose Enable verbose messages - -V, --version Print version + -c, --context arg [...] Input context names [][.][+] + --context-replacement arg Input context replacement name [][.][+] + -d, --debug Enable debug messages + -D, --dry-run Enable dry-run + -e, --export arg Set suffix for exporting .cprj retaining only specified versions + -f, --filter arg Filter words + -g, --generator arg Code generator identifier + -l, --load arg Set policy for packs loading [latest | all | required] + -L, --clayer-path arg Set search path for external clayers + -m, --missing List only required packs that are missing in the pack repository + -n, --no-check-schema Skip schema check + -N, --no-update-rte Skip creation of RTE directory and files + -o, --output arg Output directory + -t, --toolchain arg Selection of the toolchain used in the project optionally with version + -v, --verbose Enable verbose messages + -V, --version Print version Use 'csolution -h' for more information about a command. ``` diff --git a/tools/projmgr/include/ProjMgr.h b/tools/projmgr/include/ProjMgr.h index a3ee00bb9..0cac97a25 100644 --- a/tools/projmgr/include/ProjMgr.h +++ b/tools/projmgr/include/ProjMgr.h @@ -92,6 +92,7 @@ class ProjMgr { std::string m_csolutionFile; std::string m_cdefaultFile; std::vector m_context; + std::string m_contextReplacement; std::string m_filter; std::string m_codeGenerator; std::string m_command; diff --git a/tools/projmgr/include/ProjMgrUtils.h b/tools/projmgr/include/ProjMgrUtils.h index 1bb4ad256..d056556b6 100644 --- a/tools/projmgr/include/ProjMgrUtils.h +++ b/tools/projmgr/include/ProjMgrUtils.h @@ -262,17 +262,49 @@ class ProjMgrUtils { */ static void SetOutputType(const std::string typeString, OutputTypes& type); + struct Error { + Error(std::string errMsg = RteUtils::EMPTY_STRING) { + m_errMsg = errMsg; + } + std::string m_errMsg; + operator bool() const { return (m_errMsg != RteUtils::EMPTY_STRING); } + }; + /** - * @brief get filtered list of contexts + * @brief get selected list of contexts * @param selectedContexts list of matched contexts * @param allAvailableContexts list of all available contexts * @param contextFilter filter criteria * @return list of filters for which match was not found, else empty list */ - static std::vector GetSelectedContexts( - std::list& selectedContexts, + static Error GetSelectedContexts( + std::vector& selectedContexts, const std::vector& allAvailableContexts, - const std::vector& contextFilter); + const std::vector& contextFilters); + + /** + * @brief replace list of contexts + * @param selectedContexts list of matched contexts + * @param allContexts list of all available contexts + * @param contextReplace filter criteria + * @return Error object with error message (if any) + */ + static Error ReplaceContexts( + std::vector& selectedContexts, + const std::vector& allContexts, + const std::string& contextReplace); + +protected: + static std::string ConstructID(const std::vector>& elements); + /** + * @brief get filtered list of contexts + * @param allContexts list of all available contexts + * @param contextFilter filter criteria + * @return list of filters for which match was not found, else empty list + */ + static std::vector GetFilteredContexts( + const std::vector& allContexts, + const std::string& contextFilter); }; #endif // PROJMGRUTILS_H diff --git a/tools/projmgr/include/ProjMgrWorker.h b/tools/projmgr/include/ProjMgrWorker.h index 9725b2672..fad694103 100644 --- a/tools/projmgr/include/ProjMgrWorker.h +++ b/tools/projmgr/include/ProjMgrWorker.h @@ -527,9 +527,12 @@ class ProjMgrWorker { /** * @brief parse context selection * @param contexts pattern (wildcards are allowed) + * @param context replacement pattern (wildcards are allowed) * @return true if executed successfully */ - bool ParseContextSelection(const std::vector& contextSelection); + bool ParseContextSelection( + const std::vector& contextSelection, + const std::string& contextReplace = RteUtils::EMPTY_STRING); /** * @brief check if context is selected @@ -559,7 +562,7 @@ class ProjMgrWorker { std::vector m_ymlOrderedContexts; std::map m_contexts; std::map* m_contextsPtr; - std::list m_selectedContexts; + std::vector m_selectedContexts; std::string m_outputDir; std::string m_packRoot; std::string m_compilerRoot; diff --git a/tools/projmgr/src/ProjMgr.cpp b/tools/projmgr/src/ProjMgr.cpp index 9196e9f10..dcbd28425 100644 --- a/tools/projmgr/src/ProjMgr.cpp +++ b/tools/projmgr/src/ProjMgr.cpp @@ -22,36 +22,37 @@ static constexpr const char* USAGE = "\n\ Usage:\n\ csolution [.csolution.yml] [options]\n\n\ Commands:\n\ - convert Convert user input *.yml files to *.cprj files\n\ - list boards Print list of available board names\n\ - list contexts Print list of contexts in a .csolution.yml\n\ - list components Print list of available components\n\ - list dependencies Print list of unresolved project dependencies\n\ - list devices Print list of available device names\n\ - list environment Print list of environment configurations\n\ - list generators Print list of code generators of a given context\n\ - list layers Print list of available, referenced and compatible layers\n\ - list packs Print list of used packs from the pack repository\n\ - list toolchains Print list of supported toolchains\n\ - run Run code generator\n\ - update-rte Create/update configuration files and validate solution\n\n\ + convert Convert user input *.yml files to *.cprj files\n\ + list boards Print list of available board names\n\ + list contexts Print list of contexts in a .csolution.yml\n\ + list components Print list of available components\n\ + list dependencies Print list of unresolved project dependencies\n\ + list devices Print list of available device names\n\ + list environment Print list of environment configurations\n\ + list generators Print list of code generators of a given context\n\ + list layers Print list of available, referenced and compatible layers\n\ + list packs Print list of used packs from the pack repository\n\ + list toolchains Print list of supported toolchains\n\ + run Run code generator\n\ + update-rte Create/update configuration files and validate solution\n\n\ Options:\n\ - -c, --context arg [...] Input context names [][.][+]\n\ - -d, --debug Enable debug messages\n\ - -D, --dry-run Enable dry-run\n\ - -e, --export arg Set suffix for exporting .cprj retaining only specified versions\n\ - -f, --filter arg Filter words\n\ - -g, --generator arg Code generator identifier\n\ - -l, --load arg Set policy for packs loading [latest | all | required]\n\ - -L, --clayer-path arg Set search path for external clayers\n\ - -m, --missing List only required packs that are missing in the pack repository\n\ - -n, --no-check-schema Skip schema check\n\ - -N, --no-update-rte Skip creation of RTE directory and files\n\ - -o, --output arg Output directory\n\ - -t, --toolchain arg Selection of the toolchain used in the project optionally with version\n\ - -v, --verbose Enable verbose messages\n\ - -V, --version Print version\n\n\ -Use 'csolution -h' for more information about a command.\ + -c, --context arg [...] Input context names [][.][+]\n\ + --context-replacement arg Input context replacement name [][.][+]\n\ + -d, --debug Enable debug messages\n\ + -D, --dry-run Enable dry-run\n\ + -e, --export arg Set suffix for exporting .cprj retaining only specified versions\n\ + -f, --filter arg Filter words\n\ + -g, --generator arg Code generator identifier\n\ + -l, --load arg Set policy for packs loading [latest | all | required]\n\ + -L, --clayer-path arg Set search path for external clayers\n\ + -m, --missing List only required packs that are missing in the pack repository\n\ + -n, --no-check-schema Skip schema check\n\ + -N, --no-update-rte Skip creation of RTE directory and files\n\ + -o, --output arg Output directory\n\ + -t, --toolchain arg Selection of the toolchain used in the project optionally with version\n\ + -v, --verbose Enable verbose messages\n\ + -V, --version Print version\n\n\ +Use 'csolution -h' for more information about a command.\n\ "; ProjMgr::ProjMgr(void) : m_checkSchema(false), m_updateRteFiles(true) { @@ -119,6 +120,7 @@ int ProjMgr::RunProjMgr(int argc, char **argv, char** envp) { cxxopts::Option solution("s,solution", "Input csolution.yml file", cxxopts::value()); cxxopts::Option context("c,context", "Input context names [][.][+]", cxxopts::value>()); + cxxopts::Option contextReplacement("context-replacement", "Input context replacement name [][.][+]", cxxopts::value()); cxxopts::Option filter("f,filter", "Filter words", cxxopts::value()); cxxopts::Option help("h,help", "Print usage"); cxxopts::Option generator("g,generator", "Code generator identifier", cxxopts::value()); @@ -148,16 +150,16 @@ int ProjMgr::RunProjMgr(int argc, char **argv, char** envp) { {"list components", { true, {context, debug, filter, load, schemaCheck, toolchain, verbose}}}, {"list dependencies", { false, {context, debug, filter, load, schemaCheck, toolchain, verbose}}}, {"list contexts", { false, {debug, filter, schemaCheck, verbose, ymlOrder}}}, - {"list generators", { false, {context, debug, load, schemaCheck, toolchain, verbose}}}, - {"list layers", { false, {context, debug, load, clayerSearchPath, schemaCheck, toolchain, verbose}}}, - {"list toolchains", { false, {context, debug, toolchain, verbose}}}, + {"list generators", { false, {context, contextReplacement, debug, load, schemaCheck, toolchain, verbose}}}, + {"list layers", { false, {context, contextReplacement, debug, load, clayerSearchPath, schemaCheck, toolchain, verbose}}}, + {"list toolchains", { false, {context, contextReplacement, debug, toolchain, verbose}}}, {"list environment", { true, {}}}, }; try { options.add_options("", { {"positional", "", cxxopts::value>()}, - solution, context, filter, generator, + solution, context, contextReplacement, filter, generator, load, clayerSearchPath, missing, schemaCheck, noUpdateRte, output, help, version, verbose, debug, dryRun, exportSuffix, toolchain, ymlOrder }); @@ -213,6 +215,9 @@ int ProjMgr::RunProjMgr(int argc, char **argv, char** envp) { if (parseResult.count("context")) { manager.m_context = parseResult["context"].as>(); } + if (parseResult.count("context-replacement")) { + manager.m_contextReplacement = parseResult["context-replacement"].as(); + } if (parseResult.count("filter")) { manager.m_filter = parseResult["filter"].as(); } @@ -419,7 +424,7 @@ bool ProjMgr::RunConfigure(bool printConfig) { return false; } // Parse context selection - if (!m_worker.ParseContextSelection(m_context)) { + if (!m_worker.ParseContextSelection(m_context, m_contextReplacement)) { return false; } // Get context pointers @@ -512,7 +517,7 @@ bool ProjMgr::RunListPacks(void) { } } // Parse context selection - if (!m_worker.ParseContextSelection(m_context)) { + if (!m_worker.ParseContextSelection(m_context, m_contextReplacement)) { return false; } vector packs; @@ -531,7 +536,7 @@ bool ProjMgr::RunListBoards(void) { } } // Parse context selection - if (!m_worker.ParseContextSelection(m_context)) { + if (!m_worker.ParseContextSelection(m_context, m_contextReplacement)) { return false; } vector boards; @@ -553,7 +558,7 @@ bool ProjMgr::RunListDevices(void) { } } // Parse context selection - if (!m_worker.ParseContextSelection(m_context)) { + if (!m_worker.ParseContextSelection(m_context, m_contextReplacement)) { return false; } vector devices; @@ -575,7 +580,7 @@ bool ProjMgr::RunListComponents(void) { } } // Parse context selection - if (!m_worker.ParseContextSelection(m_context)) { + if (!m_worker.ParseContextSelection(m_context, m_contextReplacement)) { return false; } vector components; @@ -595,7 +600,7 @@ bool ProjMgr::RunListDependencies(void) { return false; } // Parse context selection - if (!m_worker.ParseContextSelection(m_context)) { + if (!m_worker.ParseContextSelection(m_context, m_contextReplacement)) { return false; } vector dependencies; @@ -631,7 +636,7 @@ bool ProjMgr::RunListGenerators(void) { return false; } // Parse context selection - if (!m_worker.ParseContextSelection(m_context)) { + if (!m_worker.ParseContextSelection(m_context, m_contextReplacement)) { return false; } // Get generators @@ -653,7 +658,7 @@ bool ProjMgr::RunListLayers(void) { } } // Parse context selection - if (!m_worker.ParseContextSelection(m_context)) { + if (!m_worker.ParseContextSelection(m_context, m_contextReplacement)) { return false; } // Get layers @@ -678,7 +683,7 @@ bool ProjMgr::RunCodeGenerator(void) { return false; } // Parse context selection - if (!m_worker.ParseContextSelection(m_context)) { + if (!m_worker.ParseContextSelection(m_context, m_contextReplacement)) { return false; } // Run code generator @@ -696,7 +701,7 @@ bool ProjMgr::RunListToolchains(void) { } } // Parse context selection - if (!m_worker.ParseContextSelection(m_context)) { + if (!m_worker.ParseContextSelection(m_context, m_contextReplacement)) { return false; } vector toolchains; diff --git a/tools/projmgr/src/ProjMgrUtils.cpp b/tools/projmgr/src/ProjMgrUtils.cpp index d0f0c6934..c983cd545 100644 --- a/tools/projmgr/src/ProjMgrUtils.cpp +++ b/tools/projmgr/src/ProjMgrUtils.cpp @@ -260,11 +260,13 @@ void ProjMgrUtils::SetOutputType(const string typeString, OutputTypes& type) { } } -vector ProjMgrUtils::GetSelectedContexts(list& selectedContexts, - const vector& allContexts, const vector& contextFilters) +ProjMgrUtils::Error ProjMgrUtils::GetSelectedContexts(vector& selectedContexts, + const vector& allContexts, const vector& contextFilters) { - vector errFilters; + Error error; + vector unmatchedFilters; selectedContexts.clear(); + if (contextFilters.empty()) { if (allContexts.empty()) { // default context @@ -278,36 +280,160 @@ vector ProjMgrUtils::GetSelectedContexts(list& selectedContexts, } } else { - string contextPattern; - ContextName inputContext; for (const auto& contextFilter : contextFilters) { - bool match = false; - if (!ProjMgrUtils::ParseContextEntry(contextFilter, inputContext)) { - errFilters.push_back(contextFilter); + auto filteredContexts = GetFilteredContexts(allContexts, contextFilter); + if (filteredContexts.size() == 0) { + unmatchedFilters.push_back(contextFilter); continue; } - for (const auto& context : allContexts) { - if (context == contextFilter) { - ProjMgrUtils::PushBackUniquely(selectedContexts, context); - match = true; - continue; - } - ContextName contextItem; - ProjMgrUtils::ParseContextEntry(context, contextItem); - contextPattern = (inputContext.project != RteUtils::EMPTY_STRING ? inputContext.project : "*"); - if (contextItem.build != RteUtils::EMPTY_STRING) { - contextPattern += "." + (inputContext.build != RteUtils::EMPTY_STRING ? inputContext.build : "*"); - } - contextPattern += "+" + (inputContext.target != RteUtils::EMPTY_STRING ? inputContext.target : "*"); - if (WildCards::Match(context, contextPattern)) { - ProjMgrUtils::PushBackUniquely(selectedContexts, context); - match = true; - } + + // append element to the output list + for_each(filteredContexts.begin(), filteredContexts.end(), [&](const string& context) { + ProjMgrUtils::PushBackUniquely(selectedContexts, context); + }); + } + } + + if (unmatchedFilters.size() > 0) { + error.m_errMsg = "invalid context name(s). Following context name(s) was not found:\n"; + for (const auto& filter : unmatchedFilters) { + error.m_errMsg += " " + filter + "\n"; + } + } + return error; +} + +ProjMgrUtils::Error ProjMgrUtils::ReplaceContexts(vector& selectedContexts, + const vector& allContexts, const string& contextReplace) +{ + Error error; + if (contextReplace == RteUtils::EMPTY_STRING) { + return error; + } + + // validate if the replace filter is valid + auto replaceContexts = GetFilteredContexts(allContexts, contextReplace); + if (replaceContexts.size() == 0) { + error.m_errMsg = "invalid context replacement name. \"" + contextReplace + "\" was not found.\n"; + selectedContexts.clear(); + return error; + } + + // no replacement needed when replacement contexts list + // is exactly same as selected contexts list + if (selectedContexts.size() == replaceContexts.size()) { + set sortedSelectedContexts(selectedContexts.begin(), selectedContexts.end()); + set sortedReplaceContexts(replaceContexts.begin(), replaceContexts.end()); + if (sortedSelectedContexts == sortedReplaceContexts) { + // no replacement needed + return error; + } + } + + // validate if replace filter requests more contexts than selected contexts + if (selectedContexts.size() < replaceContexts.size()) { + error.m_errMsg = "invalid replacement request. Replacement contexts are more than the selected contexts"; + selectedContexts.clear(); + return error; + } + + ContextName replaceFilter; + ProjMgrUtils::ParseContextEntry(contextReplace, replaceFilter); + + // validate if the replace filter has contexts which are not matching with selected context list + bool found = false; + for (auto& selectedContext : selectedContexts) { + ContextName context; + ProjMgrUtils::ParseContextEntry(selectedContext, context); + if (replaceFilter.project.empty() || + WildCards::Match(replaceFilter.project, context.project)) { + found = true; + break; + } + } + if (!found) { + error.m_errMsg = "no suitable replacement found for context replacement \"" + contextReplace + "\""; + selectedContexts.clear(); + return error; + } + + if (replaceFilter.project.empty()) { + replaceFilter.project = "*"; + } + + // get a list of contexts needs to be replaced + vector filteredContexts; + ContextName repContextItem, selContextItem; + for (string& selectedContext : selectedContexts) { + ProjMgrUtils::ParseContextEntry(selectedContext, selContextItem); + for (const string& contextItr : replaceContexts) { + if ((!replaceFilter.project.empty()) && (!WildCards::Match(replaceFilter.project, selContextItem.project))) { + // if 'project name' does not match the replace filter push it into the results and iterate further + ProjMgrUtils::PushBackUniquely(filteredContexts, selectedContext); + continue; + } + + ProjMgrUtils::ParseContextEntry(contextItr, repContextItem); + // construct context name for replacement + string replaceContext = selContextItem.project; + if (!repContextItem.build.empty()) { + replaceContext += "." + repContextItem.build; + } + replaceContext += "+" + repContextItem.target; + + if (find(allContexts.begin(), allContexts.end(), replaceContext) != allContexts.end()) { + // if 'replaceContext' is valid push it into the results + ProjMgrUtils::PushBackUniquely(filteredContexts, replaceContext); + } + } + } + + if (filteredContexts.size() == 0) { + // no match found + error.m_errMsg = "no suitable replacements found for context replacement \"" + contextReplace + "\""; + selectedContexts.clear(); + return error; + } + + if (selectedContexts.size() != filteredContexts.size()) { + // incompatible change + error.m_errMsg = "incompatible replacements found for \"" + contextReplace + "\""; + selectedContexts.clear(); + return error; + } + + // update selected contexts with the filtered ones + selectedContexts = filteredContexts; + return error; +} + +vector ProjMgrUtils::GetFilteredContexts( + const vector& allContexts, const string& contextFilter) +{ + vector selectedContexts; + string contextPattern; + ContextName inputContext; + + if (ProjMgrUtils::ParseContextEntry(contextFilter, inputContext)) { + for (const auto& context : allContexts) { + // add context to output list if exact match + if (context == contextFilter) { + ProjMgrUtils::PushBackUniquely(selectedContexts, context); + continue; } - if (!match) { - errFilters.push_back(contextFilter); + + // match contexts + ContextName contextItem; + ProjMgrUtils::ParseContextEntry(context, contextItem); + contextPattern = (inputContext.project != RteUtils::EMPTY_STRING ? inputContext.project : "*"); + if (contextItem.build != RteUtils::EMPTY_STRING) { + contextPattern += "." + (inputContext.build != RteUtils::EMPTY_STRING ? inputContext.build : "*"); + } + contextPattern += "+" + (inputContext.target != RteUtils::EMPTY_STRING ? inputContext.target : "*"); + if (WildCards::Match(context, contextPattern)) { + ProjMgrUtils::PushBackUniquely(selectedContexts, context); } } } - return errFilters; + return selectedContexts; } diff --git a/tools/projmgr/src/ProjMgrWorker.cpp b/tools/projmgr/src/ProjMgrWorker.cpp index 20ef8d916..0d61b484e 100644 --- a/tools/projmgr/src/ProjMgrWorker.cpp +++ b/tools/projmgr/src/ProjMgrWorker.cpp @@ -3265,18 +3265,34 @@ bool ProjMgrWorker::ProcessSequencesRelatives(ContextItem& context, BuildType& b return true; } -bool ProjMgrWorker::ParseContextSelection(const vector& contextSelection) { +bool ProjMgrWorker::ParseContextSelection(const vector& contextSelection, const string& contextReplace) { vector contexts; ListContexts(contexts); - const auto& errContextFilters = ProjMgrUtils::GetSelectedContexts(m_selectedContexts, contexts, contextSelection); - if (errContextFilters.size() != 0) { - string errMsg = "following context(s) was not found:\n"; - for (const auto& filter : errContextFilters) { - errMsg += " " + filter + "\n"; - } - ProjMgrLogger::Error(errMsg); + const auto& filterError = ProjMgrUtils::GetSelectedContexts(m_selectedContexts, contexts, contextSelection); + if (filterError) { + ProjMgrLogger::Error(filterError.m_errMsg); return false; } + + auto selectedContexts = m_selectedContexts; + if (contextReplace != RteUtils::EMPTY_STRING) { + const auto& replaceError = ProjMgrUtils::ReplaceContexts(m_selectedContexts, contexts, contextReplace); + if (replaceError) { + ProjMgrLogger::Error(replaceError.m_errMsg); + return false; + } + } + + if (m_verbose && contextReplace != RteUtils::EMPTY_STRING) { + cout << "selected context(s):" << endl; + for (const auto& context : selectedContexts) { + cout << " " + context << endl; + } + cout << "is replaced with:" << endl; + for (const auto& context : m_selectedContexts) { + cout << " " + context << endl; + } + } return true; } diff --git a/tools/projmgr/test/src/ProjMgrUnitTests.cpp b/tools/projmgr/test/src/ProjMgrUnitTests.cpp index 6975e227d..891c8ccb4 100644 --- a/tools/projmgr/test/src/ProjMgrUnitTests.cpp +++ b/tools/projmgr/test/src/ProjMgrUnitTests.cpp @@ -119,7 +119,7 @@ TEST_F(ProjMgrUnitTests, RunProjMgr_ListPacks) { {{"TestSolution/test.csolution_unknown_file.yml", "test1.Debug+CM0"}, "error csolution: csolution file was not found"}, {{"TestSolution/test.csolution.yml", "invalid.context"}, - "following context(s) was not found:\n invalid.context"} + "Following context name(s) was not found:\n invalid.context"} }; // negative tests for (const auto& [input, expected] : testFalseInputs) { @@ -131,6 +131,7 @@ TEST_F(ProjMgrUnitTests, RunProjMgr_ListPacks) { EXPECT_EQ(1, RunProjMgr(7, argv, 0)); auto errStr = streamRedirect.GetErrorString(); + auto outStr = streamRedirect.GetOutString(); EXPECT_NE(string::npos, errStr.find(expected)) << "error listing pack for " << csolution << endl; } } @@ -3408,7 +3409,7 @@ TEST_F(ProjMgrUnitTests, RunProjMgrInvalidContext) { EXPECT_EQ(1, RunProjMgr(10, argv, 0)); auto errStr = streamRedirect.GetErrorString(); - EXPECT_NE(string::npos, errStr.find("following context(s) was not found:\n test3*")); + EXPECT_NE(string::npos, errStr.find("Following context name(s) was not found:\n test3*")); } TEST_F(ProjMgrUnitTests, RunProjMgrCovertMultipleContext) { @@ -3589,3 +3590,26 @@ TEST_F(ProjMgrUnitTests, EnsurePortability) { EXPECT_TRUE(errStr.find(expected) != string::npos) << " Missing Expected: " + expected; } } + +TEST_F(ProjMgrUnitTests, RunProjMgrSolution_context_replacement) { + char* argv[10]; + StdStreamRedirect streamRedirect; + // convert --solution solution.yml + const string& csolution = testinput_folder + "/TestSolution/test.csolution.yml"; + argv[1] = (char*)"convert"; + argv[2] = (char*)"--solution"; + argv[3] = (char*)csolution.c_str(); + argv[4] = (char*)"-c"; + argv[5] = (char*)"test1.Release"; + argv[6] = (char*)"--context-replacement"; + argv[7] = (char*)"test1.Debug"; + argv[8] = (char*)testoutput_folder.c_str(); + argv[9] = (char*)"-v"; + EXPECT_EQ(0, RunProjMgr(10, argv, 0)); + + auto outStr = streamRedirect.GetOutString(); + EXPECT_NE(string::npos, outStr.find("test1.Debug+CM0.cprj - info csolution: file generated successfully")); + EXPECT_EQ(string::npos, outStr.find("test1.Release+CM0.cprj - info csolution: file generated successfully")); + EXPECT_EQ(string::npos, outStr.find("test2.Debug+CM0.cprj - info csolution: file generated successfully")); + EXPECT_EQ(string::npos, outStr.find("test2.Debug+CM3.cprj - info csolution: file generated successfully")); +} diff --git a/tools/projmgr/test/src/ProjMgrUtilsUnitTests.cpp b/tools/projmgr/test/src/ProjMgrUtilsUnitTests.cpp index b5fa2f483..f81cf8cce 100644 --- a/tools/projmgr/test/src/ProjMgrUtilsUnitTests.cpp +++ b/tools/projmgr/test/src/ProjMgrUtilsUnitTests.cpp @@ -217,55 +217,214 @@ TEST_F(ProjMgrUtilsUnitTests, GetSelectedContexts) { "Project2.Release+Target2", }; - list allContextsList; - allContextsList.assign(allContexts.begin(), allContexts.end()); - vector emptyResult{}; - vector, vector, const list>> vecTestData = { + Error noError; + vector, Error, const vector>> vecTestData = { // contextFilter, expectedRetval, expectedContexts - { {""}, emptyResult, allContextsList}, - { {"Project1"}, emptyResult, { "Project1.Debug+Target","Project1.Release+Target","Project1.Debug+Target2","Project1.Release+Target2"}}, - { {".Debug"}, emptyResult, { "Project1.Debug+Target","Project1.Debug+Target2","Project2.Debug+Target","Project2.Debug+Target2"}}, - { {"+Target"}, emptyResult, { "Project1.Debug+Target", "Project1.Release+Target", "Project2.Debug+Target", "Project2.Release+Target"}}, - { {"Project1.Debug"}, emptyResult, { "Project1.Debug+Target", "Project1.Debug+Target2" }}, - { {"Project1+Target"}, emptyResult, { "Project1.Debug+Target", "Project1.Release+Target" }}, - { {".Release+Target2"}, emptyResult, { "Project1.Release+Target2", "Project2.Release+Target2" }}, - { {"Project1.Release+Target2"}, emptyResult, { "Project1.Release+Target2" }}, - - { {"*"}, emptyResult, allContextsList}, - { {"*.*+*"}, emptyResult, allContextsList}, - { {"*.*"}, emptyResult, allContextsList}, - { {"Proj*"}, emptyResult, allContextsList}, - { {".De*"}, emptyResult, { "Project1.Debug+Target","Project1.Debug+Target2","Project2.Debug+Target","Project2.Debug+Target2"}}, - { {"+Tar*"}, emptyResult, allContextsList}, - { {"Proj*.D*g"}, emptyResult, { "Project1.Debug+Target","Project1.Debug+Target2","Project2.Debug+Target","Project2.Debug+Target2"}}, - { {"Proj*+Tar*"}, emptyResult, allContextsList}, - { {"Project2.Rel*+Tar*"}, emptyResult, {"Project2.Release+Target", "Project2.Release+Target2"}}, - { {".Rel*+*2"}, emptyResult, {"Project1.Release+Target2", "Project2.Release+Target2"}}, - { {"Project*.Release+*"}, emptyResult, {"Project1.Release+Target", "Project1.Release+Target2","Project2.Release+Target", "Project2.Release+Target2"}}, + { {""}, noError, allContexts}, + { {"Project1"}, noError, { "Project1.Debug+Target","Project1.Release+Target","Project1.Debug+Target2","Project1.Release+Target2"}}, + { {".Debug"}, noError, { "Project1.Debug+Target","Project1.Debug+Target2","Project2.Debug+Target","Project2.Debug+Target2"}}, + { {"+Target"}, noError, { "Project1.Debug+Target", "Project1.Release+Target", "Project2.Debug+Target", "Project2.Release+Target"}}, + { {"Project1.Debug"}, noError, { "Project1.Debug+Target", "Project1.Debug+Target2" }}, + { {"Project1+Target"}, noError, { "Project1.Debug+Target", "Project1.Release+Target" }}, + { {".Release+Target2"}, noError, { "Project1.Release+Target2", "Project2.Release+Target2" }}, + { {"Project1.Release+Target2"}, noError, { "Project1.Release+Target2" }}, + + { {"*"}, noError, allContexts}, + { {"*.*+*"}, noError, allContexts}, + { {"*.*"}, noError, allContexts}, + { {"Proj*"}, noError, allContexts}, + { {".De*"}, noError, { "Project1.Debug+Target","Project1.Debug+Target2","Project2.Debug+Target","Project2.Debug+Target2"}}, + { {"+Tar*"}, noError, allContexts}, + { {"Proj*.D*g"}, noError, { "Project1.Debug+Target","Project1.Debug+Target2","Project2.Debug+Target","Project2.Debug+Target2"}}, + { {"Proj*+Tar*"}, noError, allContexts}, + { {"Project2.Rel*+Tar*"}, noError, {"Project2.Release+Target", "Project2.Release+Target2"}}, + { {".Rel*+*2"}, noError, {"Project1.Release+Target2", "Project2.Release+Target2"}}, + { {"Project*.Release+*"}, noError, {"Project1.Release+Target", "Project1.Release+Target2","Project2.Release+Target", "Project2.Release+Target2"}}, // negative tests - { {"Unknown"}, {"Unknown"}, {}}, - { {".UnknownBuild"}, {".UnknownBuild"}, {}}, - { {"+UnknownTarget"}, {"+UnknownTarget"}, {}}, - { {"Project.UnknownBuild"}, {"Project.UnknownBuild"}, {}}, - { {"Project+UnknownTarget"}, {"Project+UnknownTarget"}, {}}, - { {".UnknownBuild+Target"}, {".UnknownBuild+Target"}, {}}, - { {"TestProject*"}, {"TestProject*"}, {}}, - { {"Project.*Build"}, {"Project.*Build"}, {}}, - { {"Project.Debug+*H"}, {"Project.Debug+*H"}, {}}, - { {"Project1.Release.Debug+Target"}, {"Project1.Release.Debug+Target"}, {}}, - { {"Project1.Debug+Target+Target2"}, {"Project1.Debug+Target+Target2"}, {}}, + { {"Unknown"}, Error("Following context name(s) was not found:\n Unknown"), {}}, + { {".UnknownBuild"}, Error("Following context name(s) was not found:\n .UnknownBuild"), {}}, + { {"+UnknownTarget"}, Error("Following context name(s) was not found:\n +UnknownTarget"), {}}, + { {"Project.UnknownBuild"}, Error("Following context name(s) was not found:\n Project.UnknownBuild"), {}}, + { {"Project+UnknownTarget"}, Error("Following context name(s) was not found:\n Project+UnknownTarget"), {}}, + { {".UnknownBuild+Target"}, Error("Following context name(s) was not found:\n .UnknownBuild+Target"), {}}, + { {"TestProject*"}, Error("Following context name(s) was not found:\n TestProject*"), {}}, + { {"Project.*Build"}, Error("Following context name(s) was not found:\n Project.*Build"), {}}, + { {"Project.Debug+*H"}, Error("Following context name(s) was not found:\n Project.Debug+*H"), {}}, + { {"Project1.Release.Debug+Target"}, Error("Following context name(s) was not found:\n Project1.Release.Debug+Target"), {}}, + { {"Project1.Debug+Target+Target2"}, Error("Following context name(s) was not found:\n Project1.Debug+Target+Target2"), {}}, }; - list selectedContexts; - for (const auto& [contextFilters, expectedRetval, expectedContexts] : vecTestData) { + vector selectedContexts; + for (const auto& [contextFilters, expectedError, expectedContexts] : vecTestData) { string input; selectedContexts.clear(); std::for_each(contextFilters.begin(), contextFilters.end(), [&](const std::string& item) { input += item + " "; }); - EXPECT_EQ(expectedRetval, GetSelectedContexts(selectedContexts, allContexts, contextFilters)) << - "failed for input \"" << input << "\""; - ASSERT_EQ(selectedContexts.size(), expectedContexts.size()); + const auto& outError = GetSelectedContexts(selectedContexts, allContexts, contextFilters); + EXPECT_EQ(expectedError, outError) << "failed for input \"" << input << "\""; + EXPECT_NE(string::npos, outError.m_errMsg.find(expectedError.m_errMsg)) << "failed for input \"" << input << "\""; EXPECT_EQ(selectedContexts, expectedContexts); } } + +TEST_F(ProjMgrUtilsUnitTests, ReplaceContexts) { + const vector allContexts_1 = { + "Project1.Debug+Target", + "Project1.Release+Target", + "Project1.Debug+Target2", + "Project1.Release+Target2", + "Project2.Debug+Target", + "Project2.Release+Target", + "Project2.Debug+Target2", + "Project2.Release+Target2", + }; + + const vector allContexts_2 = { + "Project1.Debug+Target1", + "Project1.Release+Target1", + "Project2.Debug+Target1", + "Project2.Debug+Target2", + }; + + const vector allContexts_3 = { + "test1.Debug+CM3", + "test1.Release+CM3", + "test1.Test+CM3", + "test2.Debug+CM3", + "test2.Release+CM3", + "test2.Test+CM3", + }; + + const vector allContexts_4 = { + "Project1.Debug1+Target", + "Project1.Debug2+Target2", + "Project1.Release1+Target", + "Project1.Release2+Target2", + "Project2.Debug1+Target", + "Project2.Debug2+Target2", + "Project2.Release2+Target", + "Project2.Release2+Target2", + }; + + struct TestCase { + vector inputAllContexts; + vector contextFilters; + string contextReplacement; + vector expectedSelectedContexts; + Error expectedOutError; + + string toString() const { + string input; + std::for_each(contextFilters.begin(), contextFilters.end(), + [&](const std::string& item) { input += "--context " + item + " "; }); + return input + "--context-replacement " + contextReplacement; + } + }; + + vector vecTestCase = { + // Positive tests + { allContexts_1, {""}, "", allContexts_1, {}}, + { allContexts_1, {""}, "+*", allContexts_1, {}}, + { allContexts_1, {"Proj*"}, "Proj*", allContexts_1, {}}, + { allContexts_1, {".Release"}, ".Release", {"Project1.Release+Target", "Project1.Release+Target2", "Project2.Release+Target", "Project2.Release+Target2"}, {}}, + { allContexts_1, {"Project2.Debug+Target"}, "Project2.Release+Target", {"Project2.Release+Target"}, {}}, + { allContexts_1, {"Project1.Debug"}, "Project1.Release", {"Project1.Release+Target","Project1.Release+Target2"}, {}}, + { allContexts_1, {"+Target2"}, "+Target", {"Project1.Debug+Target","Project1.Release+Target", "Project2.Debug+Target","Project2.Release+Target"}, {}}, + { allContexts_1, {"*.Debug", "*.Release"}, "*+Target*", {"Project1.Debug+Target", "Project1.Debug+Target2", "Project2.Debug+Target", "Project2.Debug+Target2", "Project1.Release+Target", "Project1.Release+Target2", "Project2.Release+Target", "Project2.Release+Target2" }, {}}, + { allContexts_1, {"Project2.Debug+Target"}, "Project2.Release+Target2", {"Project2.Release+Target2"}, {}}, + { allContexts_4, {"Project*.Rel*"}, "Project*.Debug*", {"Project1.Debug1+Target", "Project1.Debug2+Target2", "Project2.Debug1+Target", "Project2.Debug2+Target2"}, {}}, + + // Negative tests + { allContexts_1, {"Project2"}, "Project1.Release", {}, Error("no suitable replacement found for context replacement \"Project1.Release\"")}, + { allContexts_1, {"Project1.Debug"}, "Project1.UnknownBuildType", {}, Error("invalid context replacement name. \"Project1.UnknownBuildType\" was not found.\n")}, + { allContexts_1, {".Debug", ".Release"}, ".Release", {}, Error("incompatible replacements found for \".Release\"")}, + { allContexts_1, {"Project1"}, ".Release", {}, Error("incompatible replacements found for \".Release\"")}, + { allContexts_1, {"Project2+Target2"}, "+Target2", {}, Error("invalid replacement request. Replacement contexts are more than the selected contexts")}, + { allContexts_2, {"Project2"}, ".Release", {}, Error("no suitable replacements found for context replacement \".Release\"")}, + { allContexts_2, {"*.Debug"}, ".Release", {}, Error("incompatible replacements found for \".Release\"")}, + { allContexts_3, {"+CM3"}, ".Test", {}, Error("incompatible replacements found for \".Test\"")}, + { allContexts_3, {"test2"}, "test2.Test", {}, Error("incompatible replacements found for \"test2.Test\"")}, + { allContexts_3, {"*.Debug"}, "+CM4", {}, Error("invalid context replacement name. \"+CM4\" was not found.")}, + }; + + const auto ValidateOutput = [&](const auto& selectedContexts, const Error& outError, + const TestCase& testCase) -> testing::AssertionResult + { + if (testCase.expectedOutError != outError) { + return testing::AssertionFailure() << "expected error: " << testCase.expectedOutError.m_errMsg; + } + if (string::npos == outError.m_errMsg.find(testCase.expectedOutError.m_errMsg)) { + return testing::AssertionFailure() << "expected error: " << testCase.expectedOutError.m_errMsg; + } + if (testCase.expectedSelectedContexts != selectedContexts) { + return testing::AssertionFailure() << "selected and expected contexts don't match"; + } + return testing::AssertionSuccess(); + }; + + vector selectedContexts; + for (const auto& test : vecTestCase) { + selectedContexts.clear(); + ASSERT_EQ(false, GetSelectedContexts(selectedContexts, test.inputAllContexts, test.contextFilters)); + auto outError = ReplaceContexts(selectedContexts, test.inputAllContexts, test.contextReplacement); + + EXPECT_TRUE(ValidateOutput(selectedContexts, outError, test)) << + "failed for input \"" << test.toString() << "\""; + } +} + +TEST_F(ProjMgrUtilsUnitTests, GetFilteredContexts) { + const vector allContexts = { + "Project1.Debug+Target", + "Project1.Release+Target", + "Project1.Debug+Target2", + "Project1.Release+Target2", + "Project2.Debug+Target", + "Project2.Release+Target", + "Project2.Debug+Target2", + "Project2.Release+Target2", + }; + + vector>> vecTestData = { + // contextFilter, expectedContexts + { "", allContexts}, + { "Project1", { "Project1.Debug+Target","Project1.Release+Target","Project1.Debug+Target2","Project1.Release+Target2"}}, + { ".Debug", { "Project1.Debug+Target","Project1.Debug+Target2","Project2.Debug+Target","Project2.Debug+Target2"}}, + { "+Target", { "Project1.Debug+Target", "Project1.Release+Target", "Project2.Debug+Target", "Project2.Release+Target"}}, + { "Project1.Debug", { "Project1.Debug+Target", "Project1.Debug+Target2" }}, + { "Project1+Target", { "Project1.Debug+Target", "Project1.Release+Target" }}, + { ".Release+Target2", { "Project1.Release+Target2", "Project2.Release+Target2" }}, + { "Project1.Release+Target2", { "Project1.Release+Target2" }}, + + { "*", allContexts}, + { "*.*+*", allContexts}, + { "*.*", allContexts}, + { "Proj*", allContexts}, + { ".De*", { "Project1.Debug+Target","Project1.Debug+Target2","Project2.Debug+Target","Project2.Debug+Target2"}}, + { "+Tar*", allContexts}, + { "Proj*.D*g", { "Project1.Debug+Target","Project1.Debug+Target2","Project2.Debug+Target","Project2.Debug+Target2"}}, + { "Proj*+Tar*", allContexts}, + { "Project2.Rel*+Tar*", {"Project2.Release+Target", "Project2.Release+Target2"}}, + { ".Rel*+*2", {"Project1.Release+Target2", "Project2.Release+Target2"}}, + { "Project*.Release+*", {"Project1.Release+Target", "Project1.Release+Target2","Project2.Release+Target", "Project2.Release+Target2"}}, + + // negative tests + { "Unknown", RteUtils::EMPTY_STRING_VECTOR}, + { ".UnknownBuild", RteUtils::EMPTY_STRING_VECTOR}, + { "+UnknownTarget", RteUtils::EMPTY_STRING_VECTOR}, + { "Project.UnknownBuild", RteUtils::EMPTY_STRING_VECTOR}, + { "Project+UnknownTarget", RteUtils::EMPTY_STRING_VECTOR}, + { ".UnknownBuild+Target", RteUtils::EMPTY_STRING_VECTOR}, + { "TestProject*", RteUtils::EMPTY_STRING_VECTOR}, + { "Project.*Build", RteUtils::EMPTY_STRING_VECTOR}, + { "Project.Debug+*H", RteUtils::EMPTY_STRING_VECTOR}, + { "Project1.Release.Debug+Target", RteUtils::EMPTY_STRING_VECTOR}, + { "Project1.Debug+Target+Target2", RteUtils::EMPTY_STRING_VECTOR}, + }; + + for (const auto& [contextFilter, expectedContexts] : vecTestData) { + EXPECT_EQ(expectedContexts, GetFilteredContexts(allContexts, contextFilter)) << + "failed for input \"" << contextFilter << "\""; + } +}