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