| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying |
| file LICENSE.rst or https://cmake.org/licensing for details. */ |
| #include "cmEnvironment.h" |
| |
| #include <set> |
| #include <sstream> |
| #include <utility> |
| |
| #include <cm/string_view> |
| #include <cm/vector> |
| #include <cmext/algorithm> |
| |
| #include "cmStringAlgorithms.h" |
| #include "cmSystemTools.h" |
| |
| #if defined(_WIN32) |
| # include "cmsys/String.h" |
| #endif |
| |
| bool cmEnvironment::EnvNameLess::operator()(std::string const& lhs, |
| std::string const& rhs) const |
| { |
| #if defined(_WIN32) |
| // Environment variable names are case-insensitive on Windows |
| return cmsysString_strcasecmp(lhs.c_str(), rhs.c_str()) < 0; |
| #else |
| return lhs < rhs; |
| #endif |
| } |
| |
| cmEnvironment::cmEnvironment(std::vector<std::string> const& env) |
| { |
| for (std::string const& var : env) { |
| this->PutEnv(var); |
| } |
| } |
| |
| void cmEnvironment::PutEnv(std::string const& env) |
| { |
| auto const pos = env.find('='); |
| if (pos != std::string::npos) { |
| this->Map[env.substr(0, pos)] = env.substr(pos + 1); |
| } else { |
| this->Map[env] = cm::nullopt; |
| } |
| } |
| |
| void cmEnvironment::UnPutEnv(std::string const& env) |
| { |
| this->Map[env] = cm::nullopt; |
| } |
| |
| void cmEnvironment::Update(cmEnvironment&& other) |
| { |
| for (auto& kv : other.Map) { |
| this->Map[kv.first] = std::move(kv.second); |
| } |
| other.Map.clear(); |
| } |
| |
| std::vector<std::string> cmEnvironment::GetVariables() const |
| { |
| auto result = std::vector<std::string>{}; |
| result.reserve(this->Map.size()); |
| for (auto const& elem : this->Map) { |
| if (elem.second) { |
| result.push_back(elem.first + '=' + *elem.second); |
| } |
| } |
| return result; |
| } |
| |
| std::string cmEnvironment::RecordDifference( |
| cmEnvironment const& original) const |
| { |
| cm::string_view nl; |
| std::ostringstream os; |
| for (auto const& elem : this->Map) { |
| if (!elem.second) { |
| // Signify that this variable is being actively unset |
| os << nl << '#' << elem.first << '='; |
| nl = "\n"; |
| continue; |
| } |
| auto const it = original.Map.find(elem.first); |
| if (it != original.Map.end() && *elem.second == it->second) { |
| // Skip variables that are unchanged |
| continue; |
| } |
| os << nl << elem.first << '=' << *elem.second; |
| nl = "\n"; |
| } |
| return os.str(); |
| } |
| |
| namespace { |
| |
| auto const ValidOperators = std::set<cm::string_view>{ |
| "set", |
| "unset", |
| "string_append", |
| "string_prepend", |
| "path_list_append", |
| "path_list_prepend", |
| "cmake_list_append", |
| "cmake_list_prepend", |
| }; |
| |
| struct ListAppend |
| { |
| public: |
| ListAppend(std::string val, char sep) |
| : value(std::move(val)) |
| , separator(sep) |
| { |
| } |
| |
| void operator()(std::string& output) const |
| { |
| if (!output.empty()) { |
| output += separator; |
| } |
| output += value; |
| } |
| |
| private: |
| std::string value; |
| char separator; |
| }; |
| |
| struct ListPrepend |
| { |
| public: |
| ListPrepend(std::string val, char sep) |
| : value(std::move(val)) |
| , separator(sep) |
| { |
| } |
| |
| void operator()(std::string& output) const |
| { |
| if (!output.empty()) { |
| output.insert(output.begin(), separator); |
| } |
| output.insert(0, value); |
| } |
| |
| private: |
| std::string value; |
| char separator; |
| }; |
| |
| } // namespace |
| |
| bool cmEnvironmentModification::Add(std::vector<std::string> const& envmod) |
| { |
| bool ok = true; |
| for (auto const& entry : envmod) { |
| ok &= this->Add(entry); |
| } |
| return ok; |
| } |
| |
| bool cmEnvironmentModification::Add(std::string const& envmod) |
| { |
| // Split on `=` |
| auto const eq_loc = envmod.find_first_of('='); |
| if (eq_loc == std::string::npos) { |
| cmSystemTools::Error(cmStrCat( |
| "Error: Missing `=` after the variable name in: ", envmod, '\n')); |
| return false; |
| } |
| |
| // Split operation on `:` |
| auto const op_value_start = eq_loc + 1; |
| auto const colon_loc = envmod.find_first_of(':', op_value_start); |
| if (colon_loc == std::string::npos) { |
| cmSystemTools::Error( |
| cmStrCat("Error: Missing `:` after the operation in: ", envmod, '\n')); |
| return false; |
| } |
| |
| auto entry = Entry{}; |
| entry.Name = envmod.substr(0, eq_loc); |
| entry.Op = envmod.substr(op_value_start, colon_loc - op_value_start); |
| entry.Value = envmod.substr(colon_loc + 1); |
| |
| if (entry.Op == "reset") { |
| cm::erase_if(this->Entries, |
| [&entry](Entry const& e) { return e.Name == entry.Name; }); |
| return true; |
| } |
| |
| if (!cm::contains(ValidOperators, entry.Op)) { |
| cmSystemTools::Error(cmStrCat( |
| "Error: Unrecognized environment manipulation argument: ", entry.Op, |
| '\n')); |
| return false; |
| } |
| |
| this->Entries.push_back(std::move(entry)); |
| return true; |
| } |
| |
| void cmEnvironmentModification::ApplyTo(cmEnvironment& env) |
| { |
| char const path_sep = cmSystemTools::GetSystemPathlistSeparator(); |
| |
| for (auto const& e : this->Entries) { |
| if (e.Op == "set") { |
| env.PutEnv(e.Name + "=" + e.Value); |
| } else if (e.Op == "unset") { |
| env.UnPutEnv(e.Name); |
| } else if (e.Op == "string_append") { |
| env.Modify(e.Name, [&e](std::string& output) { output += e.Value; }); |
| } else if (e.Op == "string_prepend") { |
| env.Modify(e.Name, |
| [&e](std::string& output) { output.insert(0, e.Value); }); |
| } else if (e.Op == "path_list_append") { |
| env.Modify(e.Name, ListAppend(e.Value, path_sep)); |
| } else if (e.Op == "path_list_prepend") { |
| env.Modify(e.Name, ListPrepend(e.Value, path_sep)); |
| } else if (e.Op == "cmake_list_append") { |
| env.Modify(e.Name, ListAppend(e.Value, ';')); |
| } else if (e.Op == "cmake_list_prepend") { |
| env.Modify(e.Name, ListPrepend(e.Value, ';')); |
| } |
| } |
| } |