Line data Source code
1 : /**
2 : * @file oks_check_schema.cxx
3 : *
4 : * Program to do some simple integrity checks on a schema file
5 : *
6 : * This is part of the DUNE DAQ Application Framework, copyright 2025.
7 : * Licensing/copyright details are in the COPYING file that you should have
8 : * received with this code.
9 : */
10 :
11 : #include "CLI/CLI.hpp"
12 : #include "logging/Logging.hpp"
13 : #include "oks/class.hpp"
14 : #include "oks/file.hpp"
15 : #include "oks/kernel.hpp"
16 : #include "oks/relationship.hpp"
17 :
18 : #include <fmt/core.h>
19 : #include <set>
20 : #include <string>
21 :
22 : using namespace dunedaq::oks;
23 :
24 : int
25 0 : main(int argc, char** argv)
26 : {
27 0 : enum Exitcode : int
28 : {
29 : SUCCESS,
30 : BADCMD,
31 : NOFILE,
32 : LOADFAIL,
33 : BADRELATIONSHIP,
34 : UNRESOLVED,
35 : MISSING_INCLUDE
36 : };
37 :
38 0 : CLI::App app{
39 0 : "Check consistency of OKS schema file\n" +
40 0 : fmt::format("Return codes: {} Success, file is OK\n", Exitcode::SUCCESS) +
41 0 : fmt::format(" {} Bad command line\n", Exitcode::BADCMD) +
42 0 : fmt::format(" {} Failed to find file\n", Exitcode::NOFILE) +
43 0 : fmt::format(" {} Failed to load file -- invalid schema\n", Exitcode::LOADFAIL) +
44 0 : fmt::format(" {} File contains relationship to non loaded class\n", Exitcode::BADRELATIONSHIP) +
45 0 : fmt::format(" {} File contains object with relationship to non loaded class/object\n",
46 0 : Exitcode::UNRESOLVED) +
47 0 : fmt::format(" {} File refers to class in file not directly included\n", Exitcode::MISSING_INCLUDE)
48 0 : };
49 :
50 0 : std::string filename;
51 0 : app.add_option("-f,--file", filename, "Schema file")->required();
52 :
53 0 : CLI11_PARSE(app, argc, argv);
54 :
55 0 : OksKernel kernel;
56 0 : OksFile* file{ nullptr };
57 0 : try {
58 0 : file = kernel.load_file(filename);
59 0 : } catch (FailedLoadFile& fail) {
60 0 : TLOG() << fail.what() << "\n";
61 0 : return Exitcode::LOADFAIL;
62 0 : } catch (CanNotOpenFile& fail) {
63 0 : TLOG() << fail.what() << "\n";
64 0 : return Exitcode::NOFILE;
65 0 : } catch (std::exception& exc) {
66 0 : TLOG() << exc.what() << "\n";
67 0 : return Exitcode::LOADFAIL;
68 0 : }
69 :
70 0 : if (file == nullptr) {
71 0 : TLOG() << "Failed to load " << filename;
72 0 : return Exitcode::LOADFAIL;
73 : }
74 :
75 0 : std::set<std::string> includes;
76 0 : for (const auto& ifile : file->get_include_files()) {
77 0 : includes.insert(ifile);
78 0 : TLOG_DEBUG(2) << "direct inserting " << ifile << "\n";
79 : }
80 :
81 0 : Exitcode result = Exitcode::SUCCESS;
82 :
83 0 : if (filename.ends_with(".schema.xml")) {
84 : // Check classes defined in this schema file
85 0 : for (auto [name, oks_class] : kernel.classes()) {
86 :
87 0 : if (file->get_full_file_name() != oks_class->get_file()->get_full_file_name()) {
88 : // Class not defined in this file
89 0 : continue;
90 : }
91 :
92 0 : auto cfile = oks_class->get_file()->get_short_file_name();
93 0 : if (cfile != file->get_short_file_name() && !includes.contains(cfile)) {
94 0 : TLOG() << "Error " << filename << " misses include of " << cfile << " required by " << name;
95 0 : if (result == Exitcode::SUCCESS) {
96 0 : result = Exitcode::MISSING_INCLUDE;
97 : }
98 : }
99 0 : auto relationships = oks_class->direct_relationships();
100 0 : if (relationships != nullptr) {
101 0 : for (auto rel : *relationships) {
102 0 : auto rel_class = rel->get_class_type();
103 0 : if (rel_class == nullptr) {
104 0 : TLOG() << "Error class '" << name << "' has relationship '" << rel->get_name() << "' to a class '"
105 0 : << rel->get_type() << "' that is not loaded\n";
106 0 : if (result == Exitcode::SUCCESS) {
107 0 : result = Exitcode::BADRELATIONSHIP;
108 : }
109 : }
110 : }
111 : }
112 : // Could check super-classes but we wouldn't have got past the
113 : // load if they referred to a missing class.
114 0 : }
115 0 : } else if (filename.ends_with(".data.xml")) {
116 :
117 0 : auto check_relationship = [&](OksData* data) {
118 0 : auto relobj = data->data.OBJECT;
119 0 : if (relobj == nullptr) {
120 0 : return;
121 : }
122 0 : auto ofile_name = relobj->get_file()->get_short_file_name();
123 0 : if (file->get_short_file_name() != ofile_name && !includes.contains(ofile_name)) {
124 0 : TLOG() << "Error " << filename << " misses include of " << ofile_name << " required by " << relobj->GetId();
125 0 : if (result == Exitcode::SUCCESS) {
126 0 : result = Exitcode::MISSING_INCLUDE;
127 : }
128 : }
129 0 : };
130 :
131 0 : auto data_objs = kernel.create_list_of_data_objects(file);
132 0 : for (auto obj : *data_objs) {
133 : // Check that any objects defined in this data file have their
134 : // corresponding schema file included directly in this file
135 0 : auto oks_class = obj->GetClass();
136 0 : auto cfile = oks_class->get_file()->get_short_file_name();
137 0 : if (!includes.contains(cfile)) {
138 0 : TLOG() << "Error " << filename << " misses include of " << cfile << " required by " << obj->GetId()
139 0 : << " of class " << oks_class->get_name();
140 0 : if (result == Exitcode::SUCCESS) {
141 0 : result = Exitcode::MISSING_INCLUDE;
142 : }
143 : }
144 :
145 : // Check that any objects referenced by objects in this file are
146 : // either defined in this file or one that is directly included
147 0 : auto relationships = oks_class->all_relationships();
148 0 : if (relationships != nullptr) {
149 0 : for (auto rel : *relationships) {
150 0 : auto relval = obj->GetRelationshipValue(rel->get_name());
151 0 : if (relval == nullptr) {
152 0 : continue;
153 : }
154 0 : if (rel->get_high_cardinality_constraint() == OksRelationship::Many) {
155 0 : for (OksData::List::iterator iter = relval->data.LIST->begin(); iter != relval->data.LIST->end(); ++iter) {
156 0 : check_relationship(*iter);
157 : }
158 : } else {
159 0 : check_relationship(relval);
160 : }
161 : }
162 : }
163 0 : }
164 0 : delete data_objs;
165 : }
166 :
167 0 : auto status = kernel.get_bind_objects_status();
168 0 : if (!status.empty()) {
169 0 : if (result == Exitcode::SUCCESS) {
170 0 : result = Exitcode::UNRESOLVED;
171 : }
172 : }
173 :
174 0 : return result;
175 0 : }
|