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