DUNE-DAQ
DUNE Trigger and Data Acquisition software
Loading...
Searching...
No Matches
oks_validate_repository.cxx
Go to the documentation of this file.
1// DUNE DAQ modification notice:
2// This file has been modified from the original ATLAS oks source for the DUNE DAQ project.
3// Fork baseline commit: oks-08-03-04 (2022-04-14).
4// Renamed since fork: yes (from bin/oks_validate_repository.cpp to apps/oks_validate_repository.cxx).
5
12
13#include "oks/exceptions.hpp"
14#include "oks/kernel.hpp"
15#include "oks/pipeline.hpp"
16
17#include <boost/program_options.hpp>
18
19#include "ers/ers.hpp"
20#include "logging/Logging.hpp"
21
22#include <algorithm>
23#include <chrono>
24#include <filesystem>
25#include <iostream>
26#include <mutex>
27#include <vector>
28
29using namespace dunedaq::oks;
30
44
45std::string s_load_error;
46
47static void
48init_file_load_error(const std::string& file)
49{
50 s_load_error = "repository validation failed for file \'";
51 s_load_error += file;
52 s_load_error += "\':\n";
53}
54
55struct OksValidateJob : public OksJob
56{
57public:
58 OksValidateJob(OksKernel& kernel, const std::string& file_name)
59 : m_kernel(kernel)
60 , m_file_name(file_name)
61 {
62 ;
63 }
64
65 void run()
66 {
67 static std::mutex s_mutex;
68
69 try {
70 auto start_usage = std::chrono::steady_clock::now();
71
73
75
77 std::lock_guard lock(s_mutex);
78
79 if (s_load_error.empty()) {
81
82 if (!m_kernel.get_bind_classes_status().empty()) {
83 s_load_error += "the schema contains dangling references to non-loaded classes:\n";
85 }
86
87 if (!m_kernel.get_bind_objects_status().empty()) {
88 s_load_error += "the data contain dangling references to non-loaded objects:\n";
90 }
91 }
92 }
93
94 static std::mutex s_log_mutex;
95 std::lock_guard scoped_lock(s_log_mutex);
96
97 log_timestamp() << "validated file \"" << m_file_name << "\" in "
98 << std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() -
99 start_usage)
100 .count() /
101 1000.
102 << " ms\n";
103 } catch (std::exception& ex) {
104 std::lock_guard lock(s_mutex);
105
106 if (s_load_error.empty()) {
108 s_load_error += ex.what();
109
110 const std::string& user_repository(m_kernel.get_user_repository_root());
111 const std::size_t user_repository_len(m_kernel.get_user_repository_root().length() + 1);
112
113 std::size_t pos;
114 while ((pos = s_load_error.find(user_repository)) != std::string::npos)
115 s_load_error.replace(pos, user_repository_len, "");
116 }
117 }
118 }
119
120private:
122 const std::string& m_file_name;
123
124 // protect usage of copy constructor and assignment operator
125
126private:
129};
130
132{
133 unsigned int m_count;
134 std::ostringstream m_text;
135
137 : m_count(0)
138 {
139 ;
140 }
142
144{
145 TestCircularDependency(const std::string* file)
146 {
147 p_set_includes.insert(file);
148 p_vector_includes.push_back(file);
149 }
150
151 bool push(const std::string* file)
152 {
153 auto it = p_set_includes.insert(file);
154 if (it.second == false) {
155 std::ostringstream s;
156
157 bool report = false;
158
159 for (const auto& x : p_vector_includes) {
160 if (x == *it.first) {
162 << "]:";
163 report = true;
164 }
165
166 if (report)
167 s_circular_dependency_message.m_text << '\n' << " - \"" << *x << "\"";
168 }
169
170 return false;
171 }
172
173 p_vector_includes.push_back(file);
174
175 return true;
176 }
177
178 void pop()
179 {
180 p_set_includes.erase(p_vector_includes.back());
181 p_vector_includes.pop_back();
182 }
183
184 std::vector<const std::string*> p_vector_includes;
185 std::set<const std::string*, OksFile::SortByName> p_set_includes;
186};
187
188std::set<std::string>&
189define_includes(const std::string& f,
190 const std::set<std::string>& s,
191 std::map<std::string, std::set<std::string>>& file_all_includes,
192 std::map<std::string, std::set<std::string>>& file_explicit_includes,
193 TestCircularDependency& cd_fuse)
194{
195 std::set<std::string>& all_includes = file_all_includes[f];
196
197 if (all_includes.empty()) {
198 for (auto& x : s) {
199 if (cd_fuse.push(&x)) {
200 all_includes.insert(x);
201
202 std::set<std::string>& includes =
203 define_includes(x, file_explicit_includes[x], file_all_includes, file_explicit_includes, cd_fuse);
204 for (const auto& y : includes)
205 all_includes.insert(y);
206
207 cd_fuse.pop();
208 }
209 }
210 }
211
212 return all_includes;
213}
214
215int
216main(int argc, char** argv)
217{
218 boost::program_options::options_description desc(
219 "This program validates OKS git repository for commit by pre-receive hook");
220
221 std::vector<std::string> created, updated, deleted;
222 bool circular_dependency_between_includes_is_error = true;
223 bool verbose = false;
224 std::string user;
225 std::size_t pipeline_size = 4;
226
227 try {
228 std::vector<std::string> app_types_list;
229 std::vector<std::string> segments_list;
230
231 desc.add_options()("add,a",
232 boost::program_options::value<std::vector<std::string>>(&created)->multitoken(),
233 "list of new OKS files and directories to be added to the repository")(
234 "update,u",
235 boost::program_options::value<std::vector<std::string>>(&updated)->multitoken(),
236 "list of new OKS files and directories to be updated in the repository")(
237 "remove,r",
238 boost::program_options::value<std::vector<std::string>>(&deleted)->multitoken(),
239 "list of new OKS files and directories to be removed from the repository")(
240 "permissive-circular-dependencies-between-includes,C",
241 "downgrade severity of detected circular dependencies between includes from errors to warnings")(
242 "user,U", boost::program_options::value<std::string>(&user), "user id")(
243 "threads-number,t",
244 boost::program_options::value<std::size_t>(&pipeline_size)->default_value(pipeline_size),
245 "number of threads used by validation pipeline")("verbose,v", "Print debug information")("help,h",
246 "Print help message");
247
248 boost::program_options::variables_map vm;
249 boost::program_options::store(boost::program_options::parse_command_line(argc, argv, desc), vm);
250
251 if (vm.count("help")) {
252 std::cout << desc << std::endl;
253 return __Success__;
254 }
255
256 if (vm.count("permissive-circular-dependencies-between-includes"))
257 circular_dependency_between_includes_is_error = false;
258
259 if (vm.count("verbose"))
260 verbose = true;
261
262 boost::program_options::notify(vm);
263
264 } catch (std::exception& ex) {
265 std::cerr << "Command line parsing errors occurred:\n" << ex.what() << std::endl;
266 return __BadCommandLine__;
267 }
268
269 try {
270 OksKernel kernel;
271
274
275 if (kernel.get_user_repository_root().empty()) {
276 std::cerr << "There is no OKS repository set (check TDAQ_DB_REPOSITORY)" << std::endl;
277 return __NoRepository__;
278 }
279
280 std::filesystem::current_path(kernel.get_user_repository_root());
281
282 auto start_usage = std::chrono::steady_clock::now();
283
284 // directories
285
286 std::set<std::string> directories;
287
288 // file: explicit includes
289 std::map<std::string, std::set<std::string>> file_explicit_includes;
290
291 for (auto& p : std::filesystem::recursive_directory_iterator("."))
292 if (std::filesystem::is_directory(p))
293 directories.insert(p.path().native().substr(2));
294 else if (std::filesystem::is_regular_file(p) && p.path().native().find("./.git") != 0 &&
295 p.path().native().find("./admin") != 0 && p.path().native().find("./README.md") != 0)
296 kernel.get_includes(p.path().native(), file_explicit_includes[p.path().native().substr(2)], true);
297
298 if (verbose)
299 log_timestamp(Debug) << "scan " << file_explicit_includes.size() << " repository files in "
300 << std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() -
301 start_usage)
302 .count() /
303 1000.
304 << " ms\n";
305
306 auto start_usage2 = std::chrono::steady_clock::now();
307
308 std::set<std::string> all_includes;
309
310 // check every include exists
311 for (const auto& f : file_explicit_includes) {
312 for (const auto& i : f.second) {
313 if (file_explicit_includes.find(i) != file_explicit_includes.end()) {
314 all_includes.insert(i);
315 } else {
316 std::cerr << "Cannot find file \"" << i << "\" included by \"" << f.first << "\"" << std::endl;
317 return __NoIncludedFile__;
318 }
319 }
320 }
321
322 if (verbose)
323 log_timestamp(Debug) << "check existence of includes in "
324 << std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() -
325 start_usage2)
326 .count() /
327 1000.
328 << " ms\n";
329
330 auto start_usage3 = std::chrono::steady_clock::now();
331
332 // file: all includes
333 std::map<std::string, std::set<std::string>> file_all_includes;
334
335 for (auto& x : file_explicit_includes) {
336 TestCircularDependency cd_fuse(&x.first);
337 define_includes(x.first, x.second, file_all_includes, file_explicit_includes, cd_fuse);
338 }
339
340 auto stop_usage3 = std::chrono::steady_clock::now();
341
342 if (verbose)
344 << "calculated inclusion graph in "
345 << std::chrono::duration_cast<std::chrono::microseconds>(stop_usage3 - start_usage3).count() / 1000. << " ms\n";
346
347 log_timestamp() << "process " << file_explicit_includes.size() << " repository files and their includes in "
348 << std::chrono::duration_cast<std::chrono::microseconds>(stop_usage3 - start_usage).count() / 1000.
349 << " ms" << std::endl;
350
352 log_timestamp((circular_dependency_between_includes_is_error == true ? Error : Warning))
353 << "Detected " << s_circular_dependency_message.m_count
354 << " circular dependencies between includes of the repository files:"
355 << s_circular_dependency_message.m_text.str() << std::endl;
356
357 if (circular_dependency_between_includes_is_error == true)
359 }
360
361 if (ers::debug_level() >= 2) {
362 std::ostringstream text;
363 text << "ALL INCLUDES:\n";
364
365 for (const auto& x : file_all_includes) {
366 text << "FILE \"" << x.first << "\" has " << x.second.size() << " includes:\n";
367
368 for (const auto& y : x.second)
369 text << " - \"" << y << "\"\n";
370 }
371
372 TLOG_DEBUG(2) << text.str();
373 }
374
375 OksPipeline pipeline(pipeline_size);
376
377 // do not run check for README file
378 auto ignore_files = [](std::string& x) {
379 static const std::string readme_file("README.md");
380 return x != readme_file;
381 };
382
383 // all modified file paths
384 std::set<std::string> modified;
385 std::copy_if(created.begin(), created.end(), std::inserter(modified, modified.end()), ignore_files);
386 std::copy_if(updated.begin(), updated.end(), std::inserter(modified, modified.end()), ignore_files);
387
388 // validate independently every created or updated file
389 for (const auto& x : modified)
390 pipeline.addJob(new OksValidateJob(kernel, x));
391
392 std::copy_if(deleted.begin(), deleted.end(), std::inserter(modified, modified.end()), ignore_files);
393
394 for (const auto& f : file_explicit_includes)
395 if (all_includes.find(f.first) == all_includes.end()) {
396 if (modified.empty() == false) {
397 if (modified.find(f.first) == modified.end()) {
398 const auto& file_includes = file_all_includes[f.first];
399
400 bool found = false;
401
402 for (const auto& x : modified)
403 if (file_includes.find(x) != file_includes.end()) {
404 found = true;
405 TLOG_DEBUG(1) << "file \"" << f.first << "\" contains modified include \"" << x << '\"';
406 break;
407 }
408
409 if (found == false) {
410 TLOG_DEBUG(1) << "skip file \"" << f.first << '\"';
411 continue;
412 }
413 } else {
414 TLOG_DEBUG(1) << "list of modified files contains file \"" << f.first << '\"';
415 }
416 }
417
418 if (modified.find(f.first) == modified.end())
419 pipeline.addJob(new OksValidateJob(kernel, f.first));
420 }
421
422 pipeline.waitForCompletion();
423
424 if (!s_load_error.empty()) {
425 log_timestamp(Error) << s_load_error << std::endl;
427 }
428 } catch (exception& ex) {
429 log_timestamp(Error) << "Caught oks exception:\n" << ex << std::endl;
430 return __ExceptionCaught__;
431 } catch (std::exception& e) {
432 log_timestamp(Error) << "Caught standard C++ exception:\n" << e.what() << std::endl;
433 return __ExceptionCaught__;
434 } catch (...) {
435 log_timestamp(Error) << "Caught unknown exception" << std::endl;
436 return __ExceptionCaught__;
437 }
438
439 return __Success__;
440}
Provides interface to the OKS kernel.
Definition kernel.hpp:615
const std::string & get_user_repository_root() const
Get user OKS repository root.
Definition kernel.cpp:335
OksFile * load_file(const std::string &name, bool bind=true)
Load OKS database file.
Definition kernel.cpp:1625
const std::string & get_bind_classes_status() const noexcept
Return status of oks classes binding.
Definition kernel.hpp:1934
void set_allow_duplicated_objects_mode(const bool b)
Set status of duplicated objects mode. To switch 'On'/'Off' use the method's parameter:
Definition kernel.hpp:813
void set_silence_mode(const bool b)
Set status of silence mode. To switch 'On'/'Off' use the method's parameter:
Definition kernel.hpp:730
void set_test_duplicated_objects_via_inheritance_mode(const bool b)
Set status of test inherited duplicated objects mode. To switch 'On'/'Off' use the method's parameter...
Definition kernel.hpp:782
const std::string & get_bind_objects_status() const noexcept
Return status of oks objects binding.
Definition kernel.hpp:1922
void get_includes(const std::string &file_name, std::set< std::string > &includes, bool use_repository_name=false)
Opens file and reads its shallow includes.
Definition kernel.cpp:1752
void addJob(OksJob *job)
Definition pipeline.cpp:99
int main(int argc, char *argv[])
#define TLOG_DEBUG(lvl,...)
Definition Logging.hpp:116
std::ostream & log_timestamp(__LogSeverity__ severity=Log)
Definition kernel.cpp:5470
int debug_level()
Definition ers.hpp:80
@ __BadCommandLine__
@ __Success__
@ __ExceptionCaught__
Definition oks_dump.cxx:37
std::set< std::string > & define_includes(const std::string &f, const std::set< std::string > &s, std::map< std::string, std::set< std::string > > &file_all_includes, std::map< std::string, std::set< std::string > > &file_explicit_includes, TestCircularDependency &cd_fuse)
struct FoundCircularDependency s_circular_dependency_message
static void init_file_load_error(const std::string &file)
__OksValidateRepositoryExitStatus__
@ __UserAuthenticationFailure__
@ __IncludesCircularDependencyError__
@ __AccessManagerAuthorizationFailed__
@ __AccessManagerNoPermission__
std::string s_load_error
OksValidateJob(OksKernel &kernel, const std::string &file_name)
OksValidateJob & operator=(const OksValidateJob &)
const std::string & m_file_name
OksValidateJob(const OksValidateJob &)
std::set< const std::string *, OksFile::SortByName > p_set_includes
TestCircularDependency(const std::string *file)
bool push(const std::string *file)
std::vector< const std::string * > p_vector_includes