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 :
25 :
26 0 : int main(int argc, char **argv) {
27 0 : enum Exitcode : int {SUCCESS, BADCMD, NOFILE, LOADFAIL,
28 : BADRELATIONSHIP, UNRESOLVED,
29 : MISSING_INCLUDE};
30 :
31 0 : CLI::App app{"Check consistency of OKS schema file\n"+
32 0 : fmt::format("Return codes: {} Success, file is OK\n", Exitcode::SUCCESS)+
33 0 : fmt::format(" {} Bad command line\n", Exitcode::BADCMD)+
34 0 : fmt::format(" {} Failed to find file\n", Exitcode::NOFILE)+
35 0 : fmt::format(" {} Failed to load file -- invalid schema\n", Exitcode::LOADFAIL)+
36 0 : fmt::format(" {} File contains relationship to non loaded class\n", Exitcode::BADRELATIONSHIP)+
37 0 : fmt::format(" {} File contains object with relationship to non loaded class/object\n", Exitcode::UNRESOLVED)+
38 0 : fmt::format(" {} File refers to class in file not directly included\n", Exitcode::MISSING_INCLUDE)
39 0 : };
40 :
41 0 : std::string filename;
42 0 : app.add_option("-f,--file", filename, "Schema file")
43 0 : ->required();
44 :
45 0 : CLI11_PARSE(app, argc, argv);
46 :
47 0 : OksKernel kernel;
48 0 : OksFile* file{nullptr};
49 0 : try {
50 0 : file = kernel.load_file(filename);
51 : }
52 0 : catch (FailedLoadFile& fail) {
53 0 : TLOG() << fail.what() << "\n";
54 0 : return Exitcode::LOADFAIL;
55 0 : }
56 0 : catch (CanNotOpenFile& fail) {
57 0 : TLOG() << fail.what() << "\n";
58 0 : return Exitcode::NOFILE;
59 0 : }
60 0 : catch (std::exception& exc) {
61 0 : TLOG() << exc.what() << "\n";
62 0 : return Exitcode::LOADFAIL;
63 0 : }
64 :
65 0 : if (file == nullptr) {
66 0 : TLOG() << "Failed to load " << filename;
67 0 : return Exitcode::LOADFAIL;
68 : }
69 :
70 0 : std::set<std::string> includes;
71 0 : for (const auto& ifile : file->get_include_files()) {
72 0 : includes.insert(ifile);
73 0 : TLOG_DEBUG(2) << "direct inserting " << ifile << "\n";
74 : }
75 :
76 0 : Exitcode result=Exitcode::SUCCESS;
77 :
78 0 : if (filename.ends_with(".schema.xml")) {
79 : // Check classes defined in this schema file
80 0 : for (auto [name, oks_class] : kernel.classes()) {
81 :
82 0 : if (file->get_full_file_name() != oks_class->get_file()->get_full_file_name()) {
83 : // Class not defined in this file
84 0 : continue;
85 : }
86 :
87 0 : auto cfile = oks_class->get_file()->get_short_file_name();
88 0 : if (cfile != file->get_short_file_name() && !includes.contains(cfile)) {
89 0 : TLOG() << "Error " << filename << " misses include of " << cfile << " required by " << name;
90 0 : if (result==Exitcode::SUCCESS) {
91 0 : result = Exitcode::MISSING_INCLUDE;
92 : }
93 : }
94 0 : auto relationships = oks_class->direct_relationships();
95 0 : if (relationships != nullptr) {
96 0 : for (auto rel: *relationships) {
97 0 : auto rel_class = rel->get_class_type();
98 0 : if (rel_class == nullptr) {
99 0 : TLOG() << "Error class '" << name
100 0 : << "' has relationship '" << rel->get_name()
101 0 : << "' to a class '" << rel->get_type()
102 0 : << "' that is not loaded\n";
103 0 : if (result==Exitcode::SUCCESS) {
104 0 : result = Exitcode::BADRELATIONSHIP;
105 : }
106 : }
107 :
108 : }
109 : }
110 : // Could check super-classes but we wouldn't have got past the
111 : // load if they referred to a missing class.
112 0 : }
113 0 : } else if (filename.ends_with(".data.xml")) {
114 :
115 0 : auto check_relationship = [&](OksData* data) {
116 0 : auto relobj = data->data.OBJECT;
117 0 : if (relobj == nullptr) {
118 0 : return;
119 : }
120 0 : auto ofile_name = relobj->get_file()->get_short_file_name();
121 0 : if (file->get_short_file_name() != ofile_name && !includes.contains(ofile_name)) {
122 0 : TLOG() << "Error " << filename << " misses include of " << ofile_name
123 0 : << " required by " << relobj->GetId();
124 0 : if (result==Exitcode::SUCCESS) {
125 0 : result = Exitcode::MISSING_INCLUDE;
126 : }
127 : }
128 0 : };
129 :
130 0 : auto data_objs = kernel.create_list_of_data_objects(file);
131 0 : for (auto obj : *data_objs) {
132 : // Check that any objects defined in this data file have their
133 : // corresponding schema file included directly in this file
134 0 : auto oks_class = obj->GetClass();
135 0 : auto cfile = oks_class->get_file()->get_short_file_name();
136 0 : if (!includes.contains(cfile)) {
137 0 : TLOG() << "Error " << filename << " misses include of " << cfile
138 0 : << " 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();
156 0 : iter != relval->data.LIST->end(); ++iter) {
157 0 : check_relationship(*iter);
158 : }
159 : } else {
160 0 : check_relationship(relval);
161 : }
162 : }
163 : }
164 0 : }
165 0 : delete data_objs;
166 : }
167 :
168 0 : auto status = kernel.get_bind_objects_status();
169 0 : if (!status.empty()) {
170 0 : if (result==Exitcode::SUCCESS) {
171 0 : result = Exitcode::UNRESOLVED;
172 : }
173 : }
174 :
175 0 : return result;
176 0 : }
|