Coverage Report

Created: 2026-04-29 07:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmEnvironment.cxx
Line
Count
Source
1
/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
2
   file LICENSE.rst or https://cmake.org/licensing for details.  */
3
#include "cmEnvironment.h"
4
5
#include <set>
6
#include <sstream>
7
#include <utility>
8
9
#include <cm/string_view>
10
#include <cm/vector>
11
#include <cmext/algorithm>
12
13
#include "cmStringAlgorithms.h"
14
#include "cmSystemTools.h"
15
16
#if defined(_WIN32)
17
#  include "cmsys/String.h"
18
#endif
19
20
bool cmEnvironment::EnvNameLess::operator()(std::string const& lhs,
21
                                            std::string const& rhs) const
22
0
{
23
#if defined(_WIN32)
24
  // Environment variable names are case-insensitive on Windows
25
  return cmsysString_strcasecmp(lhs.c_str(), rhs.c_str()) < 0;
26
#else
27
0
  return lhs < rhs;
28
0
#endif
29
0
}
30
31
cmEnvironment::cmEnvironment(std::vector<std::string> const& env)
32
0
{
33
0
  for (std::string const& var : env) {
34
0
    this->PutEnv(var);
35
0
  }
36
0
}
37
38
void cmEnvironment::PutEnv(std::string const& env)
39
0
{
40
0
  auto const pos = env.find('=');
41
0
  if (pos != std::string::npos) {
42
0
    this->Map[env.substr(0, pos)] = env.substr(pos + 1);
43
0
  } else {
44
0
    this->Map[env] = cm::nullopt;
45
0
  }
46
0
}
47
48
void cmEnvironment::UnPutEnv(std::string const& env)
49
0
{
50
0
  this->Map[env] = cm::nullopt;
51
0
}
52
53
void cmEnvironment::Update(cmEnvironment&& other)
54
0
{
55
0
  for (auto& kv : other.Map) {
56
0
    this->Map[kv.first] = std::move(kv.second);
57
0
  }
58
0
  other.Map.clear();
59
0
}
60
61
std::vector<std::string> cmEnvironment::GetVariables() const
62
0
{
63
0
  auto result = std::vector<std::string>{};
64
0
  result.reserve(this->Map.size());
65
0
  for (auto const& elem : this->Map) {
66
0
    if (elem.second) {
67
0
      result.push_back(elem.first + '=' + *elem.second);
68
0
    }
69
0
  }
70
0
  return result;
71
0
}
72
73
std::string cmEnvironment::RecordDifference(
74
  cmEnvironment const& original) const
75
0
{
76
0
  cm::string_view nl;
77
0
  std::ostringstream os;
78
0
  for (auto const& elem : this->Map) {
79
0
    if (!elem.second) {
80
      // Signify that this variable is being actively unset
81
0
      os << nl << '#' << elem.first << '=';
82
0
      nl = "\n";
83
0
      continue;
84
0
    }
85
0
    auto const it = original.Map.find(elem.first);
86
0
    if (it != original.Map.end() && *elem.second == it->second) {
87
      // Skip variables that are unchanged
88
0
      continue;
89
0
    }
90
0
    os << nl << elem.first << '=' << *elem.second;
91
0
    nl = "\n";
92
0
  }
93
0
  return os.str();
94
0
}
95
96
namespace {
97
98
auto const ValidOperators = std::set<cm::string_view>{
99
  "set",
100
  "unset",
101
  "string_append",
102
  "string_prepend",
103
  "path_list_append",
104
  "path_list_prepend",
105
  "cmake_list_append",
106
  "cmake_list_prepend",
107
};
108
109
struct ListAppend
110
{
111
public:
112
  ListAppend(std::string val, char sep)
113
0
    : value(std::move(val))
114
0
    , separator(sep)
115
0
  {
116
0
  }
117
118
  void operator()(std::string& output) const
119
0
  {
120
0
    if (!output.empty()) {
121
0
      output += separator;
122
0
    }
123
0
    output += value;
124
0
  }
125
126
private:
127
  std::string value;
128
  char separator;
129
};
130
131
struct ListPrepend
132
{
133
public:
134
  ListPrepend(std::string val, char sep)
135
0
    : value(std::move(val))
136
0
    , separator(sep)
137
0
  {
138
0
  }
139
140
  void operator()(std::string& output) const
141
0
  {
142
0
    if (!output.empty()) {
143
0
      output.insert(output.begin(), separator);
144
0
    }
145
0
    output.insert(0, value);
146
0
  }
147
148
private:
149
  std::string value;
150
  char separator;
151
};
152
153
} // namespace
154
155
bool cmEnvironmentModification::Add(std::vector<std::string> const& envmod)
156
0
{
157
0
  bool ok = true;
158
0
  for (auto const& entry : envmod) {
159
0
    ok &= this->Add(entry);
160
0
  }
161
0
  return ok;
162
0
}
163
164
bool cmEnvironmentModification::Add(std::string const& envmod)
165
0
{
166
  // Split on `=`
167
0
  auto const eq_loc = envmod.find_first_of('=');
168
0
  if (eq_loc == std::string::npos) {
169
0
    cmSystemTools::Error(cmStrCat(
170
0
      "Error: Missing `=` after the variable name in: ", envmod, '\n'));
171
0
    return false;
172
0
  }
173
174
  // Split operation on `:`
175
0
  auto const op_value_start = eq_loc + 1;
176
0
  auto const colon_loc = envmod.find_first_of(':', op_value_start);
177
0
  if (colon_loc == std::string::npos) {
178
0
    cmSystemTools::Error(
179
0
      cmStrCat("Error: Missing `:` after the operation in: ", envmod, '\n'));
180
0
    return false;
181
0
  }
182
183
0
  auto entry = Entry{};
184
0
  entry.Name = envmod.substr(0, eq_loc);
185
0
  entry.Op = envmod.substr(op_value_start, colon_loc - op_value_start);
186
0
  entry.Value = envmod.substr(colon_loc + 1);
187
188
0
  if (entry.Op == "reset") {
189
0
    cm::erase_if(this->Entries,
190
0
                 [&entry](Entry const& e) { return e.Name == entry.Name; });
191
0
    return true;
192
0
  }
193
194
0
  if (!cm::contains(ValidOperators, entry.Op)) {
195
0
    cmSystemTools::Error(cmStrCat(
196
0
      "Error: Unrecognized environment manipulation argument: ", entry.Op,
197
0
      '\n'));
198
0
    return false;
199
0
  }
200
201
0
  this->Entries.push_back(std::move(entry));
202
0
  return true;
203
0
}
204
205
void cmEnvironmentModification::ApplyTo(cmEnvironment& env)
206
0
{
207
0
  char const path_sep = cmSystemTools::GetSystemPathlistSeparator();
208
209
0
  for (auto const& e : this->Entries) {
210
0
    if (e.Op == "set") {
211
0
      env.PutEnv(e.Name + "=" + e.Value);
212
0
    } else if (e.Op == "unset") {
213
0
      env.UnPutEnv(e.Name);
214
0
    } else if (e.Op == "string_append") {
215
0
      env.Modify(e.Name, [&e](std::string& output) { output += e.Value; });
216
0
    } else if (e.Op == "string_prepend") {
217
0
      env.Modify(e.Name,
218
0
                 [&e](std::string& output) { output.insert(0, e.Value); });
219
0
    } else if (e.Op == "path_list_append") {
220
0
      env.Modify(e.Name, ListAppend(e.Value, path_sep));
221
0
    } else if (e.Op == "path_list_prepend") {
222
0
      env.Modify(e.Name, ListPrepend(e.Value, path_sep));
223
0
    } else if (e.Op == "cmake_list_append") {
224
0
      env.Modify(e.Name, ListAppend(e.Value, ';'));
225
0
    } else if (e.Op == "cmake_list_prepend") {
226
0
      env.Modify(e.Name, ListPrepend(e.Value, ';'));
227
0
    }
228
0
  }
229
0
}