Line data Source code
1 : /*!
2 : * \file esys/repo/manifest/xmlfileimpl_manifest.cpp
3 : * \brief
4 : *
5 : * \cond
6 : * __legal_b__
7 : *
8 : * Copyright (c) 2020 Michel Gillet
9 : * Distributed under the MIT License.
10 : * (See accompanying file LICENSE.txt or
11 : * copy at https://opensource.org/licenses/MIT)
12 : *
13 : * __legal_e__
14 : * \endcond
15 : *
16 : */
17 :
18 : #include "esys/repo/esysrepo_prec.h"
19 : #include "esys/repo/manifest/xmlfileimpl.h"
20 : #include "esys/repo/manifest/xmlfile.h"
21 : #include "esys/repo/manifest/depends.h"
22 : #include "esys/repo/manifest/workspace.h"
23 : #include "esys/repo/sharing/policy.h"
24 : #include "esys/repo/manifest/syncs.h"
25 :
26 : #include <esysfile/xml/writer.h>
27 : #include <esysfile/xml/file.h>
28 : #include <esysfile/xml/attr.h>
29 :
30 : #include <fstream>
31 : #include <sstream>
32 : #include <string>
33 :
34 : namespace esys::repo::manifest
35 : {
36 :
37 79 : XMLFileImpl::XMLFileImpl(XMLFile *self)
38 79 : : m_self(self)
39 : {
40 79 : }
41 :
42 158 : XMLFileImpl::~XMLFileImpl() = default;
43 :
44 73 : Result XMLFileImpl::read(const std::string &path)
45 : {
46 73 : esysfile::xml::File xml_file;
47 :
48 73 : xml_file.set_root_node_name("esysrepo_manifest");
49 73 : int result = xml_file.read(path);
50 73 : if (result < 0)
51 : {
52 0 : self()->add_error(result, "XML parsing failed.");
53 0 : return ESYSREPO_RESULT(ResultCode::MANIFEST_ERROR_PARSING_FILE, path);
54 : }
55 :
56 146 : if (self()->get_data() == nullptr) self()->set_data(std::make_shared<Manifest>());
57 :
58 73 : self()->get_data()->set_type(Type::ESYSREPO_MANIFEST);
59 :
60 146 : return read(xml_file.get_data());
61 73 : }
62 :
63 73 : Result XMLFileImpl::read(std::shared_ptr<esysfile::xml::Data> data)
64 : {
65 73 : if (data->get_attrs().size() != 0)
66 : {
67 86 : Result result = read_root_attributes(data);
68 43 : if (result.error())
69 : {
70 1 : self()->add_error(result.get_result_code_int(), "read_root_attributes failed.");
71 2 : return ESYSREPO_RESULT(result);
72 : }
73 43 : }
74 : else
75 60 : self()->get_data()->set_kind(manifest::Kind::ISOLATED);
76 :
77 72 : Result result;
78 :
79 230 : for (auto el : data->get_elements())
80 : {
81 164 : if (el->get_name() == "location")
82 315 : result = read_location(el);
83 59 : else if ((el->get_name() == "default"))
84 147 : result = read_default(el);
85 10 : else if ((el->get_name() == "group"))
86 15 : result = read_group(el);
87 5 : else if (el->get_name() == "revision-set")
88 12 : result = read_revision_set(el);
89 : else
90 1 : result = unknown_element(el->get_name());
91 164 : if (result.error())
92 : {
93 6 : self()->add_error(result.get_result_code_int(), "Reading XML element failed : " + el->get_name());
94 6 : return result;
95 : }
96 164 : }
97 :
98 66 : Result depends_result = self()->get_data()->validate_depends();
99 66 : if (depends_result.error()) return report_depends_error(depends_result);
100 64 : return ESYSREPO_RESULT(ResultCode::OK);
101 72 : }
102 :
103 1 : int XMLFileImpl::find_depends_line_in_file(const std::string &filename, const Depends &edge)
104 : {
105 1 : if (filename.empty()) return -1;
106 :
107 1 : std::ifstream in(filename);
108 1 : if (!in) return -1;
109 :
110 2 : const std::string needle = to_string(edge.get_selector()) + "=\"" + edge.get_value() + "\"";
111 1 : std::string line;
112 1 : int line_number = 0;
113 6 : while (std::getline(in, line))
114 : {
115 6 : ++line_number;
116 6 : if (line.find("depends") == std::string::npos) continue;
117 1 : if (line.find(needle) == std::string::npos) continue;
118 : return line_number;
119 : }
120 : return -1;
121 2 : }
122 :
123 2 : Result XMLFileImpl::report_depends_error(const Result &depends_result)
124 : {
125 2 : int line_number = -1;
126 2 : std::shared_ptr<Depends> unresolved;
127 4 : if (self()->get_data() != nullptr)
128 : {
129 5 : for (auto &location : self()->get_data()->get_locations())
130 : {
131 4 : for (auto &repo : location->get_repos())
132 : {
133 5 : for (auto &edge : repo->get_depends())
134 : {
135 3 : if (edge == nullptr) continue;
136 3 : if (!edge->get_resolved_path().empty()) continue;
137 1 : if (edge->get_selector() == DependsSelector::NOT_SET) continue;
138 1 : unresolved = edge;
139 1 : line_number = edge->get_line_number();
140 : break;
141 : }
142 3 : if (unresolved != nullptr) break;
143 : }
144 2 : if (unresolved != nullptr) break;
145 : }
146 : }
147 :
148 2 : if (line_number <= 0 && unresolved != nullptr)
149 1 : line_number = find_depends_line_in_file(self()->get_filename(), *unresolved);
150 :
151 2 : std::string detail;
152 8 : if (depends_result.get_error_info() != nullptr) detail = depends_result.get_error_info()->get_text();
153 2 : if (detail.empty()) detail = "validate_depends failed.";
154 :
155 2 : std::ostringstream oss;
156 2 : if (!self()->get_filename().empty())
157 : {
158 2 : oss << self()->get_filename();
159 2 : if (line_number > 0) oss << ":" << line_number;
160 2 : oss << ": ";
161 : }
162 0 : else if (line_number > 0)
163 : {
164 0 : oss << "line " << line_number << ": ";
165 : }
166 2 : oss << detail;
167 :
168 2 : const std::string msg = oss.str();
169 2 : self()->add_error(depends_result.get_result_code_int(), msg, line_number);
170 2 : return ESYSREPO_RESULT(depends_result.get_result_code(), msg);
171 3 : }
172 :
173 2 : Result XMLFileImpl::unknown_element(const std::string &name)
174 : {
175 2 : if (self()->get_parse_mode() == ParseMode::Tolerant) return ESYSREPO_RESULT(ResultCode::OK);
176 0 : return ESYSREPO_RESULT(ResultCode::MANIFEST_ELEMENT_UNKNOWN, name);
177 : }
178 :
179 3 : Result XMLFileImpl::unknown_attribute(const std::string &context, const std::string &attr_name)
180 : {
181 3 : if (self()->get_parse_mode() == ParseMode::Tolerant) return ESYSREPO_RESULT(ResultCode::OK);
182 1 : return ESYSREPO_RESULT(ResultCode::MANIFEST_ATTRIBUTE_UNKNOWN, context + ": " + attr_name);
183 : }
184 :
185 43 : Result XMLFileImpl::read_root_attributes(std::shared_ptr<esysfile::xml::Element> root_el)
186 : {
187 103 : for (auto attr : root_el->get_attrs())
188 : {
189 122 : Result result = read_root_attribute(attr);
190 62 : if (result.error()) return ESYSREPO_RESULT(result);
191 122 : }
192 42 : return ESYSREPO_RESULT(ResultCode::OK);
193 : }
194 :
195 61 : Result XMLFileImpl::read_root_attribute(std::shared_ptr<esysfile::xml::Attr> attr)
196 : {
197 61 : if (attr->get_name() == "kind")
198 : {
199 41 : manifest::Kind kind;
200 41 : Result result = convert(attr->get_value(), kind);
201 41 : if (result.error()) return ESYSREPO_RESULT(result, ResultCode::MANIFEST_KIND_UNKNOWN, attr->get_value());
202 :
203 41 : self()->get_data()->set_kind(kind);
204 41 : return ESYSREPO_RESULT(ResultCode::OK);
205 41 : }
206 20 : if (attr->get_name() == "schema-version")
207 : {
208 18 : const std::string &ver = attr->get_value();
209 33 : if (ver != "1.0" && ver != "1.1")
210 0 : return ESYSREPO_RESULT(ResultCode::MANIFEST_INCORRECT_ATTRIBUTE_VALUE, ver);
211 18 : self()->get_data()->set_schema_version(ver);
212 18 : return ESYSREPO_RESULT(ResultCode::OK);
213 : }
214 4 : return unknown_attribute("esysrepo_manifest", attr->get_name());
215 : }
216 :
217 105 : Result XMLFileImpl::read_location(std::shared_ptr<esysfile::xml::Element> el)
218 : {
219 105 : if (el->get_name() != "location") return ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR);
220 :
221 105 : std::shared_ptr<Location> location = std::make_shared<Location>();
222 :
223 105 : auto attr = el->get_attr("name");
224 105 : if (attr != nullptr)
225 105 : location->set_name(attr->get_value());
226 : else
227 0 : return ESYSREPO_RESULT(ResultCode::MANIFEST_LOCATION_WITHOUT_NAME);
228 :
229 210 : attr = el->get_attr("addr");
230 105 : if (attr != nullptr)
231 105 : location->set_address(attr->get_value());
232 : else
233 0 : return ESYSREPO_RESULT(ResultCode::MANIFEST_LOCATION_WITHOUT_ADDRESS);
234 :
235 210 : attr = el->get_attr("alt_addr");
236 105 : if (attr != nullptr) location->set_alt_address(attr->get_value());
237 :
238 105 : Result result;
239 :
240 514 : for (auto child_el : el->get_elements())
241 : {
242 415 : if (child_el->get_name() == "repository")
243 2070 : result = read_repository(child_el, location);
244 : else
245 1 : result = unknown_element(child_el->get_name());
246 415 : if (result.error()) return result;
247 415 : }
248 :
249 297 : self()->get_data()->add_location(location);
250 99 : return ESYSREPO_RESULT(ResultCode::OK);
251 315 : }
252 :
253 414 : Result XMLFileImpl::read_repository(std::shared_ptr<esysfile::xml::Element> el, std::shared_ptr<Location> location)
254 : {
255 414 : if (el->get_name() != "repository") return ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR);
256 :
257 414 : auto repository = std::make_shared<Repository>();
258 414 : repository->set_location(location.get());
259 :
260 414 : auto attr = el->get_attr("name");
261 414 : if (attr == nullptr) return ESYSREPO_RESULT(ResultCode::MANIFEST_REPOSITORY_WITHOUT_NAME);
262 414 : repository->set_name(attr->get_value());
263 :
264 828 : attr = el->get_attr("path");
265 414 : if (attr == nullptr) return ESYSREPO_RESULT(ResultCode::MANIFEST_REPOSITORY_WITHOUT_PATH);
266 414 : repository->set_path(attr->get_value());
267 :
268 828 : attr = el->get_attr("revision");
269 414 : if (attr != nullptr) repository->set_revision(attr->get_value());
270 :
271 828 : attr = el->get_attr("sync-s");
272 414 : if (attr != nullptr)
273 : {
274 1 : bool sync_s = false;
275 1 : if (!parse_sync_s_value(attr->get_value(), sync_s))
276 0 : return ESYSREPO_RESULT(ResultCode::MANIFEST_INCORRECT_ATTRIBUTE_VALUE, "repository.sync-s");
277 1 : repository->set_sync_s(sync_s);
278 : }
279 :
280 426 : for (auto child_el : el->get_elements())
281 : {
282 18 : if (child_el->get_name() == "depends")
283 : {
284 28 : Result result = read_depends(child_el, repository);
285 7 : if (result.error()) return result;
286 7 : }
287 11 : else if (child_el->get_name() == "sharing")
288 : {
289 16 : Result result = read_sharing(child_el, repository);
290 4 : if (result.error()) return result;
291 4 : }
292 7 : else if (child_el->get_name() == "workspace")
293 : {
294 28 : Result result = read_workspace(child_el, repository);
295 7 : if (result.error()) return result;
296 7 : }
297 : else
298 : {
299 0 : Result result = unknown_element(child_el->get_name());
300 0 : if (result.error()) return result;
301 0 : }
302 18 : }
303 :
304 816 : location->add_repo(repository);
305 :
306 408 : return ESYSREPO_RESULT(ResultCode::OK);
307 828 : }
308 :
309 7 : Result XMLFileImpl::read_workspace(std::shared_ptr<esysfile::xml::Element> el, std::shared_ptr<Repository> repository)
310 : {
311 7 : if (el->get_name() != "workspace") return ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR);
312 :
313 : // ADR-0043: <workspace nested="true"/> requires draft schema-version 1.1.
314 14 : if (self()->get_data()->get_schema_version() != "1.1")
315 : {
316 2 : return ESYSREPO_RESULT(ResultCode::WORKSPACE_REQUIRES_SCHEMA_1_1,
317 : "<workspace> requires schema-version=\"1.1\" (got '" +
318 : self()->get_data()->get_schema_version() + "')");
319 : }
320 :
321 6 : if (repository->get_path() == "." || repository->get_path() == "./")
322 : {
323 1 : return ESYSREPO_RESULT(ResultCode::WORKSPACE_NESTED_ROOT,
324 : "workspace root path=\".\" cannot carry <workspace nested=\"true\"/>");
325 : }
326 :
327 5 : auto nested_attr = el->get_attr("nested");
328 5 : if (nested_attr == nullptr)
329 0 : return ESYSREPO_RESULT(ResultCode::WORKSPACE_NESTED_INVALID, "workspace.nested is required");
330 :
331 5 : auto nested = WorkspaceConfig::parse_nested(nested_attr->get_value());
332 5 : if (!nested.has_value())
333 0 : return ESYSREPO_RESULT(ResultCode::WORKSPACE_NESTED_INVALID, "workspace.nested=" + nested_attr->get_value());
334 5 : if (!*nested)
335 1 : return ESYSREPO_RESULT(ResultCode::WORKSPACE_NESTED_INVALID,
336 : "workspace.nested must be \"true\" (got \"" + nested_attr->get_value() + "\")");
337 :
338 8 : for (auto child_attr : el->get_attrs())
339 : {
340 4 : if (child_attr->get_name() != "nested")
341 0 : return ESYSREPO_RESULT(ResultCode::MANIFEST_ATTRIBUTE_UNKNOWN, "workspace." + child_attr->get_name());
342 4 : }
343 :
344 4 : if (!el->get_elements().empty())
345 0 : return ESYSREPO_RESULT(ResultCode::MANIFEST_ELEMENT_UNKNOWN, "workspace child elements not allowed");
346 :
347 4 : repository->set_workspace(WorkspaceConfig(true));
348 4 : return ESYSREPO_RESULT(ResultCode::OK);
349 7 : }
350 :
351 4 : Result XMLFileImpl::read_sharing(std::shared_ptr<esysfile::xml::Element> el, std::shared_ptr<Repository> repository)
352 : {
353 4 : if (el->get_name() != "sharing") return ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR);
354 :
355 : // ADR-0040: <sharing> requires draft schema-version 1.1 (fail if absent / 1.0).
356 8 : if (self()->get_data()->get_schema_version() != "1.1")
357 : {
358 2 : return ESYSREPO_RESULT(ResultCode::SHARING_REQUIRES_SCHEMA_1_1,
359 : "<sharing> requires schema-version=\"1.1\" (got '" +
360 : self()->get_data()->get_schema_version() + "')");
361 : }
362 :
363 3 : if (repository->get_path() == "." || repository->get_path() == "./")
364 : {
365 1 : return ESYSREPO_RESULT(ResultCode::SHARING_ROOT_PROVIDER,
366 : "workspace root path=\".\" cannot carry <sharing>");
367 : }
368 :
369 2 : auto policy_attr = el->get_attr("policy");
370 2 : if (policy_attr == nullptr)
371 0 : return ESYSREPO_RESULT(ResultCode::MANIFEST_INCORRECT_ATTRIBUTE_VALUE, "sharing.policy is required");
372 :
373 2 : auto policy = sharing::Config::parse_policy(policy_attr->get_value());
374 2 : if (!policy.has_value())
375 1 : return ESYSREPO_RESULT(ResultCode::SHARING_UNKNOWN_POLICY, "sharing.policy=" + policy_attr->get_value());
376 :
377 1 : sharing::RevisionMismatch mismatch = sharing::RevisionMismatch::Warn;
378 1 : auto mismatch_attr = el->get_attr("revision-mismatch");
379 1 : if (mismatch_attr != nullptr)
380 : {
381 1 : auto parsed = sharing::Config::parse_revision_mismatch(mismatch_attr->get_value());
382 1 : if (!parsed.has_value())
383 0 : return ESYSREPO_RESULT(ResultCode::SHARING_UNKNOWN_REVISION_MISMATCH,
384 : "sharing.revision-mismatch=" + mismatch_attr->get_value());
385 1 : mismatch = *parsed;
386 : }
387 :
388 3 : for (auto child_attr : el->get_attrs())
389 : {
390 2 : const auto &n = child_attr->get_name();
391 3 : if (n != "policy" && n != "revision-mismatch")
392 : {
393 : // Unknown attributes on <sharing>: hard error (authorization must not be guessed).
394 : // Even in tolerant mode — ADR-0040 §6 covers unknown policy; extend same for attrs.
395 0 : return ESYSREPO_RESULT(ResultCode::MANIFEST_ATTRIBUTE_UNKNOWN, "sharing." + n);
396 : }
397 2 : }
398 :
399 1 : if (!el->get_elements().empty())
400 0 : return ESYSREPO_RESULT(ResultCode::MANIFEST_ELEMENT_UNKNOWN, "sharing child elements not allowed");
401 :
402 1 : repository->set_sharing(sharing::Config(*policy, mismatch));
403 1 : return ESYSREPO_RESULT(ResultCode::OK);
404 6 : }
405 :
406 7 : Result XMLFileImpl::read_depends(std::shared_ptr<esysfile::xml::Element> el, std::shared_ptr<Repository> repository)
407 : {
408 7 : if (el->get_name() != "depends") return ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR);
409 :
410 7 : auto depends = std::make_shared<Depends>();
411 7 : depends->set_line_number(el->get_line_number());
412 7 : int identity_count = 0;
413 :
414 7 : auto attr = el->get_attr("path");
415 7 : if (attr != nullptr)
416 : {
417 7 : depends->set_selector(DependsSelector::PATH);
418 7 : depends->set_value(attr->get_value());
419 : ++identity_count;
420 : }
421 14 : attr = el->get_attr("name");
422 7 : if (attr != nullptr)
423 : {
424 0 : if (identity_count > 0)
425 0 : return ESYSREPO_RESULT(ResultCode::MANIFEST_INCORRECT_ATTRIBUTE_VALUE, "depends: multiple identities");
426 0 : depends->set_selector(DependsSelector::NAME);
427 0 : depends->set_value(attr->get_value());
428 : ++identity_count;
429 : }
430 14 : attr = el->get_attr("project");
431 7 : if (attr != nullptr)
432 : {
433 0 : if (identity_count > 0)
434 0 : return ESYSREPO_RESULT(ResultCode::MANIFEST_INCORRECT_ATTRIBUTE_VALUE, "depends: multiple identities");
435 0 : depends->set_selector(DependsSelector::PROJECT);
436 0 : depends->set_value(attr->get_value());
437 : ++identity_count;
438 : }
439 14 : attr = el->get_attr("url");
440 7 : if (attr != nullptr)
441 : {
442 0 : if (identity_count > 0)
443 0 : return ESYSREPO_RESULT(ResultCode::MANIFEST_INCORRECT_ATTRIBUTE_VALUE, "depends: multiple identities");
444 0 : depends->set_selector(DependsSelector::URL);
445 0 : depends->set_value(attr->get_value());
446 : ++identity_count;
447 : }
448 :
449 7 : if (identity_count != 1)
450 0 : return ESYSREPO_RESULT(ResultCode::MANIFEST_DEPENDS_WITHOUT_IDENTITY, "repository=" + repository->get_path());
451 :
452 14 : for (auto child_attr : el->get_attrs())
453 : {
454 7 : const auto &n = child_attr->get_name();
455 7 : if (n != "path" && n != "name" && n != "project" && n != "url")
456 : {
457 0 : Result result = unknown_attribute("depends", n);
458 0 : if (result.error()) return result;
459 0 : }
460 7 : }
461 :
462 14 : repository->add_depends(depends);
463 7 : return ESYSREPO_RESULT(ResultCode::OK);
464 14 : }
465 :
466 49 : Result XMLFileImpl::read_default(std::shared_ptr<esysfile::xml::Element> el)
467 : {
468 49 : if (el->get_name() != "default") return ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR);
469 :
470 148 : for (auto attr : el->get_attrs())
471 : {
472 198 : Result result = read_default_attr(attr);
473 99 : if (result.error()) return result;
474 198 : }
475 49 : return ESYSREPO_RESULT(ResultCode::OK);
476 : }
477 :
478 99 : Result XMLFileImpl::read_default_attr(std::shared_ptr<esysfile::xml::Attr> attr)
479 : {
480 99 : if (attr->get_name() == "revision")
481 98 : self()->get_data()->set_default_revision(attr->get_value());
482 50 : else if (attr->get_name() == "sync-j")
483 92 : self()->get_data()->set_default_job_count(atoi(attr->get_value().c_str()));
484 4 : else if (attr->get_name() == "revision-set")
485 4 : self()->get_data()->set_default_revision_set(attr->get_value());
486 2 : else if (attr->get_name() == "sync-s")
487 : {
488 1 : bool sync_s = false;
489 1 : if (!parse_sync_s_value(attr->get_value(), sync_s))
490 0 : return ESYSREPO_RESULT(ResultCode::MANIFEST_INCORRECT_ATTRIBUTE_VALUE, "default.sync-s");
491 2 : self()->get_data()->set_default_sync_s(sync_s);
492 : }
493 : else
494 2 : return unknown_attribute("default", attr->get_name());
495 98 : return ESYSREPO_RESULT(ResultCode::OK);
496 : }
497 :
498 5 : Result XMLFileImpl::read_group(std::shared_ptr<esysfile::xml::Element> el)
499 : {
500 5 : auto attr = el->get_attr("name");
501 5 : if (attr == nullptr) return ESYSREPO_RESULT(ResultCode::MANIFEST_GROUP_WITHOUT_NAME);
502 :
503 5 : std::shared_ptr<Group> group = std::make_shared<Group>();
504 5 : group->set_name(attr->get_value());
505 :
506 12 : for (auto child : el->get_elements())
507 : {
508 28 : Result result = read_group_repo(child, group);
509 7 : if (result.error()) return ESYSREPO_RESULT(result);
510 14 : }
511 :
512 15 : self()->get_data()->get_groups().add_group(group);
513 :
514 5 : return ESYSREPO_RESULT(ResultCode::OK);
515 10 : }
516 :
517 7 : Result XMLFileImpl::read_group_repo(std::shared_ptr<esysfile::xml::Element> el, std::shared_ptr<Group> group)
518 : {
519 7 : if (el->get_name() != "repo") return unknown_element(el->get_name());
520 :
521 7 : auto attr = el->get_attr("path");
522 7 : if (attr == nullptr) return ESYSREPO_RESULT(ResultCode::MANIFEST_GROUP_REPO_WITHOUT_PATH);
523 :
524 7 : auto repo = self()->get_data()->find_repo_by_path(attr->get_value());
525 7 : if (repo == nullptr) return ESYSREPO_RESULT(ResultCode::MANIFEST_REPO_UNKNOWN, "path = " + attr->get_value());
526 :
527 14 : group->add_repo(repo);
528 7 : return ESYSREPO_RESULT(ResultCode::OK);
529 14 : }
530 :
531 4 : Result XMLFileImpl::read_revision_set(std::shared_ptr<esysfile::xml::Element> el)
532 : {
533 4 : if (el->get_name() != "revision-set") return ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR);
534 :
535 4 : auto attr = el->get_attr("name");
536 4 : if (attr == nullptr) return ESYSREPO_RESULT(ResultCode::MANIFEST_ATTRIBUTE_UNKNOWN, "revision-set missing name");
537 :
538 4 : auto revision_set = std::make_shared<RevisionSet>(attr->get_value());
539 10 : for (auto child : el->get_elements())
540 : {
541 24 : Result result = read_revision_set_child(child, revision_set);
542 6 : if (result.error()) return ESYSREPO_RESULT(result);
543 12 : }
544 :
545 12 : self()->get_data()->get_revision_sets().add_revision_set(revision_set);
546 4 : return ESYSREPO_RESULT(ResultCode::OK);
547 8 : }
548 :
549 6 : Result XMLFileImpl::read_revision_set_child(std::shared_ptr<esysfile::xml::Element> el,
550 : std::shared_ptr<RevisionSet> revision_set)
551 : {
552 6 : if (el->get_name() == "default")
553 : {
554 4 : auto attr = el->get_attr("revision");
555 4 : if (attr == nullptr) return ESYSREPO_RESULT(ResultCode::MANIFEST_ATTRIBUTE_UNKNOWN, "default missing revision");
556 4 : revision_set->set_default_revision(attr->get_value());
557 4 : return ESYSREPO_RESULT(ResultCode::OK);
558 4 : }
559 2 : if (el->get_name() == "repository")
560 : {
561 2 : auto path_attr = el->get_attr("path");
562 2 : auto rev_attr = el->get_attr("revision");
563 2 : if (path_attr == nullptr) return ESYSREPO_RESULT(ResultCode::MANIFEST_REPOSITORY_WITHOUT_PATH);
564 2 : if (rev_attr == nullptr) return ESYSREPO_RESULT(ResultCode::MANIFEST_ATTRIBUTE_UNKNOWN, "repository missing revision");
565 2 : revision_set->set_repo_revision(path_attr->get_value(), rev_attr->get_value());
566 2 : return ESYSREPO_RESULT(ResultCode::OK);
567 4 : }
568 0 : return unknown_element(el->get_name());
569 : }
570 :
571 10 : Result XMLFileImpl::write_xml()
572 : {
573 20 : if (self()->get_data() == nullptr) return ESYSREPO_RESULT(ResultCode::MANIFEST_XML_DATA_NULLPTR);
574 :
575 10 : m_xml_data = std::make_shared<esysfile::xml::Data>();
576 10 : m_xml_data->set_root_node_name("esysrepo_manifest");
577 :
578 20 : if (self()->get_data()->get_kind() != manifest::Kind::ISOLATED)
579 : {
580 0 : std::string kind;
581 0 : Result result = convert(self()->get_data()->get_kind(), kind);
582 0 : if (result.error()) return ESYSREPO_RESULT(result);
583 0 : m_xml_data->add_attr("kind", kind);
584 0 : }
585 :
586 : // Emit schema-version when set, or when any repository carries <sharing>.
587 10 : {
588 10 : std::string schema = self()->get_data()->get_schema_version();
589 10 : if (schema.empty())
590 : {
591 22 : for (const auto &loc : self()->get_data()->get_locations())
592 : {
593 10 : if (!loc) continue;
594 85 : for (const auto &repo : loc->get_repos())
595 : {
596 75 : if (repo && repo->get_sharing().has_value())
597 : {
598 0 : schema = "1.1";
599 : break;
600 : }
601 : }
602 10 : if (!schema.empty()) break;
603 : }
604 : }
605 14 : if (!schema.empty()) m_xml_data->add_attr("schema-version", schema);
606 0 : }
607 :
608 10 : {
609 20 : Result result = write_default(m_xml_data);
610 10 : if (result.error()) return ESYSREPO_RESULT(result);
611 10 : }
612 :
613 34 : for (auto location : self()->get_data()->get_locations())
614 : {
615 56 : Result result = write(m_xml_data, location);
616 14 : if (result.error()) return ESYSREPO_RESULT(result);
617 28 : }
618 :
619 24 : for (auto group : self()->get_data()->get_groups().get_groups())
620 : {
621 16 : Result result = write(m_xml_data, group);
622 4 : if (result.error()) return ESYSREPO_RESULT(result);
623 8 : }
624 :
625 22 : for (auto revision_set : self()->get_data()->get_revision_sets().get_revision_sets())
626 : {
627 8 : Result result = write(m_xml_data, revision_set);
628 2 : if (result.error()) return ESYSREPO_RESULT(result);
629 4 : }
630 10 : return ESYSREPO_RESULT(ResultCode::OK);
631 : }
632 :
633 10 : Result XMLFileImpl::write_default(std::shared_ptr<esysfile::xml::Element> parent_el)
634 : {
635 10 : auto data = self()->get_data();
636 : // Constructor default revision is "master"; omit <default> unless something is non-default
637 : // so existing 1.0 write round-trips stay unchanged.
638 10 : const bool has_jobs = data->get_default_job_count() != 1;
639 10 : const bool has_rev_set = !data->get_default_revision_set().empty();
640 10 : const bool has_nondefault_rev = !data->get_default_revision().empty() && data->get_default_revision() != "master";
641 10 : const bool has_sync_s = data->get_default_sync_s().has_value();
642 10 : if (!has_jobs && !has_rev_set && !has_nondefault_rev && !has_sync_s) return ESYSREPO_RESULT(ResultCode::OK);
643 :
644 5 : auto default_el = std::make_shared<esysfile::xml::Element>();
645 5 : default_el->set_name("default");
646 10 : if (!data->get_default_revision().empty()) default_el->add_attr("revision", data->get_default_revision());
647 9 : if (has_jobs) default_el->add_attr("sync-j", std::to_string(data->get_default_job_count()));
648 6 : if (has_rev_set) default_el->add_attr("revision-set", data->get_default_revision_set());
649 6 : if (has_sync_s) default_el->add_attr("sync-s", *data->get_default_sync_s() ? "true" : "false");
650 10 : parent_el->add_element(default_el);
651 5 : return ESYSREPO_RESULT(ResultCode::OK);
652 20 : }
653 :
654 8 : Result XMLFileImpl::write(const std::string &path)
655 : {
656 8 : Result result = write_xml();
657 8 : if (result.error()) return ESYSREPO_RESULT(result);
658 :
659 8 : esysfile::xml::Writer writer;
660 :
661 8 : writer.set_indent(4);
662 16 : int result_int = writer.write(path, m_xml_data);
663 8 : if (result_int < 0) return ESYSREPO_RESULT(ResultCode::RAW_INT_ERROR, result_int);
664 8 : return ESYSREPO_RESULT(ResultCode::OK);
665 8 : }
666 :
667 2 : Result XMLFileImpl::write(std::ostream &os)
668 : {
669 2 : Result result = write_xml();
670 2 : if (result.error()) return ESYSREPO_RESULT(result);
671 :
672 2 : esysfile::xml::Writer writer;
673 :
674 2 : writer.set_indent(4);
675 4 : int result_int = writer.write(os, m_xml_data);
676 2 : if (result_int < 0) return ESYSREPO_RESULT(ResultCode::RAW_INT_ERROR, result_int);
677 2 : return ESYSREPO_RESULT(ResultCode::OK);
678 2 : }
679 :
680 14 : Result XMLFileImpl::write(std::shared_ptr<esysfile::xml::Element> parent_el, std::shared_ptr<Location> location)
681 : {
682 14 : std::shared_ptr<esysfile::xml::Element> location_el = std::make_shared<esysfile::xml::Element>();
683 :
684 14 : location_el->set_name("location");
685 14 : location_el->add_attr("name", location->get_name());
686 14 : location_el->add_attr("addr", location->get_address());
687 :
688 99 : for (auto repo : location->get_repos())
689 : {
690 340 : Result result = write(location_el, repo);
691 85 : if (result.error()) return ESYSREPO_RESULT(result);
692 170 : }
693 :
694 28 : parent_el->add_element(location_el);
695 14 : return ESYSREPO_RESULT(ResultCode::OK);
696 14 : }
697 :
698 85 : Result XMLFileImpl::write(std::shared_ptr<esysfile::xml::Element> parent_el, std::shared_ptr<Repository> repository)
699 : {
700 85 : std::shared_ptr<esysfile::xml::Element> repo_el = std::make_shared<esysfile::xml::Element>();
701 :
702 85 : repo_el->set_name("repository");
703 85 : repo_el->add_attr("path", repository->get_path());
704 85 : repo_el->add_attr("name", repository->get_name());
705 113 : if (!repository->get_revision().empty()) repo_el->add_attr("revision", repository->get_revision());
706 85 : if (repository->get_sync_s().has_value())
707 3 : repo_el->add_attr("sync-s", *repository->get_sync_s() ? "true" : "false");
708 :
709 87 : for (const auto &depends : repository->get_depends())
710 : {
711 2 : if (depends == nullptr) continue;
712 2 : auto dep_el = std::make_shared<esysfile::xml::Element>();
713 2 : dep_el->set_name("depends");
714 2 : dep_el->add_attr(to_string(depends->get_selector()), depends->get_value());
715 6 : repo_el->add_element(dep_el);
716 2 : }
717 :
718 85 : if (repository->get_sharing().has_value())
719 : {
720 1 : const auto &cfg = *repository->get_sharing();
721 1 : auto share_el = std::make_shared<esysfile::xml::Element>();
722 1 : share_el->set_name("sharing");
723 1 : share_el->add_attr("policy", sharing::Config::to_string(cfg.get_policy()));
724 1 : share_el->add_attr("revision-mismatch", sharing::Config::to_string(cfg.get_revision_mismatch()));
725 3 : repo_el->add_element(share_el);
726 1 : }
727 :
728 85 : if (repository->is_nested_workspace())
729 : {
730 1 : auto ws_el = std::make_shared<esysfile::xml::Element>();
731 1 : ws_el->set_name("workspace");
732 1 : ws_el->add_attr("nested", "true");
733 3 : repo_el->add_element(ws_el);
734 1 : }
735 :
736 170 : parent_el->add_element(repo_el);
737 85 : return ESYSREPO_RESULT(ResultCode::OK);
738 85 : }
739 :
740 4 : Result XMLFileImpl::write(std::shared_ptr<esysfile::xml::Element> parent_el, std::shared_ptr<Group> group)
741 : {
742 4 : std::shared_ptr<esysfile::xml::Element> grp_el = std::make_shared<esysfile::xml::Element>();
743 :
744 4 : grp_el->set_name("group");
745 4 : grp_el->add_attr("name", group->get_name());
746 :
747 12 : for (auto repo : group->get_repos())
748 : {
749 8 : std::shared_ptr<esysfile::xml::Element> repo_el = std::make_shared<esysfile::xml::Element>();
750 :
751 8 : repo_el->set_name("repo");
752 8 : repo_el->add_attr("path", repo->get_path());
753 24 : grp_el->add_element(repo_el);
754 16 : }
755 :
756 8 : parent_el->add_element(grp_el);
757 4 : return ESYSREPO_RESULT(ResultCode::OK);
758 4 : }
759 :
760 2 : Result XMLFileImpl::write(std::shared_ptr<esysfile::xml::Element> parent_el, std::shared_ptr<RevisionSet> revision_set)
761 : {
762 2 : auto set_el = std::make_shared<esysfile::xml::Element>();
763 2 : set_el->set_name("revision-set");
764 2 : set_el->add_attr("name", revision_set->get_name());
765 :
766 2 : if (!revision_set->get_default_revision().empty())
767 : {
768 2 : auto default_el = std::make_shared<esysfile::xml::Element>();
769 2 : default_el->set_name("default");
770 2 : default_el->add_attr("revision", revision_set->get_default_revision());
771 6 : set_el->add_element(default_el);
772 2 : }
773 :
774 3 : for (const auto &kv : revision_set->get_repo_revisions())
775 : {
776 1 : auto repo_el = std::make_shared<esysfile::xml::Element>();
777 1 : repo_el->set_name("repository");
778 1 : repo_el->add_attr("path", kv.first);
779 1 : repo_el->add_attr("revision", kv.second);
780 3 : set_el->add_element(repo_el);
781 1 : }
782 :
783 4 : parent_el->add_element(set_el);
784 2 : return ESYSREPO_RESULT(ResultCode::OK);
785 2 : }
786 :
787 637 : XMLFile *XMLFileImpl::self()
788 : {
789 637 : return m_self;
790 : }
791 :
792 0 : const XMLFile *XMLFileImpl::self() const
793 : {
794 0 : return m_self;
795 : }
796 :
797 : } // namespace esys::repo::manifest
|