flexiblesusy is hosted by Hepforge, IPPP Durham
FlexibleSUSY
depgen.cpp
Go to the documentation of this file.
1// ====================================================================
2// This file is part of FlexibleSUSY.
3//
4// FlexibleSUSY is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published
6// by the Free Software Foundation, either version 3 of the License,
7// or (at your option) any later version.
8//
9// FlexibleSUSY is distributed in the hope that it will be useful, but
10// WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with FlexibleSUSY. If not, see
16// <http://www.gnu.org/licenses/>.
17// ====================================================================
18
19#include <algorithm>
20#include <cstddef>
21#include <cstdio>
22#include <cstdlib>
23#include <cstring>
24#include <exception>
25#include <fstream>
26#include <iostream>
27#include <iterator>
28#include <set>
29#include <string>
30#include <utility>
31#include <vector>
32
33namespace flexiblesusy {
34namespace depgen {
35
36struct Config {
37 std::string file_name;
38 std::string target_name;
39 std::string output_file;
40 std::vector<std::string> search_paths;
42 bool ignore_non_existing{false};
44};
45
47std::string directory(const std::string& file_name)
48{
49 const std::size_t pos = file_name.find_last_of("/\\");
50 if (pos == std::string::npos)
51 return ".";
52 return file_name.substr(0,pos);
53}
54
56std::string filename(const std::string& file_name)
57{
58 const std::size_t pos = file_name.find_last_of("/\\");
59 return file_name.substr(pos+1);
60}
61
63std::vector<std::string> filenames(const std::vector<std::string>& file_names)
64{
65 std::vector<std::string> result(file_names.size());
66 std::transform(file_names.begin(), file_names.end(), result.begin(), filename);
67 return result;
68}
69
71bool file_exists(const std::string& name)
72{
73 if (FILE *file = std::fopen(name.c_str(), "r")) {
74 std::fclose(file);
75 return true;
76 }
77 return false;
78}
79
81 bool operator()(const std::string& element) {
82 return s.insert(element).second;
83 }
84private:
85 std::set<std::string> s;
86};
87
89 bool operator()(const std::string& element) {
90 return s.insert(filename(element)).second;
91 }
92private:
93 std::set<std::string> s;
94};
95
97template <typename Predicate = decltype(Is_not_duplicate())>
98std::vector<std::string> delete_duplicates(
99 const std::vector<std::string>& vec,
100 Predicate pred = Is_not_duplicate())
101{
102 std::vector<std::string> unique_vector;
103
104 std::copy_if(vec.begin(), vec.end(), std::back_inserter(unique_vector),
105 [&pred] (const std::string& f) { return pred(f); });
106
107 return unique_vector;
108}
109
111std::string replace_extension(const std::string& str, const std::string& ext)
112{
113 const std::string no_ext(str.substr(0, str.find_last_of('.')));
114 return no_ext + '.' + ext;
115}
116
118bool starts_with(const std::string& str, const std::string& prefix)
119{
120 return str.compare(0, prefix.size(), prefix) == 0;
121}
122
124void print_usage(const std::string& program_name)
125{
126 std::cout << "Usage: " << program_name << " [options] filename\n"
127 "\n"
128 "Options:\n"
129 " -I<path> Search for header files in <path>\n"
130 " -MF <file> Write dependencies to <file>\n"
131 " -MG Add missing headers to dependency list\n"
132 " -MI Ignore errors of non-existing header(s)\n"
133 " -MM Ignore system headers enclosed by < and >\n"
134 " -MMD <file> Equivalent to -MM -MF <file>\n"
135 " -MP Add phony target for each dependency other than main file\n"
136 " -MT <target> Set name of the target\n"
137 " -o <file> Equivalent to -MF <file>\n"
138 " --help,-h Print this help message and exit\n"
139 "\n"
140 "Unsupported options:\n"
141 " -M Add system headers to dependency list\n"
142 " -MD <file> Equivalent to -M -MF <file>\n"
143 " -MQ <target> Same as -MT <traget> but quote characters special to make\n";
144}
145
147void print_dependencies(std::ostream& ostr,
148 const std::string& target_name,
149 const std::vector<std::string>& dependencies)
150{
151 ostr << target_name << ':';
152
153 for (const auto& d: dependencies)
154 ostr << ' ' << d;
155
156 ostr << '\n';
157}
158
160void print_empty_phony_targets(std::ostream& ostr, const std::vector<std::string>& dependencies)
161{
162 for (const auto& d: dependencies)
163 ostr << '\n' << d << ":\n";
164}
165
167template <class OutputIterator>
168void get_filename_from_include(const char* s, OutputIterator it)
169{
170 if (!s || !*s) return;
171
172 // skip whitespace
173 while (*s && std::isspace(*s)) s++;
174
175 // skip #
176 if (*s && *s == '#') s++;
177 else return;
178
179 // skip whitespace
180 while (*s && std::isspace(*s)) s++;
181
182 // skip `include'
183 if (*s && std::strncmp(s, "include", 7) == 0) s += 7;
184 else return;
185
186 // extract file name from "file-name"
187 if (*s) s = std::strchr(s, '"');
188 if (!s) return;
189 if (*s) s++;
190
191 const char* pos1 = s;
192
193 if (*s) s = std::strchr(s, '"');
194 if (!s) return;
195
196 const char* pos2 = s;
197
198 const std::ptrdiff_t len = pos2 - pos1;
199
200 if (len > 0) {
201 *it = std::string(pos1, len);
202 it++;
203 }
204}
205
207std::vector<std::string> get_included_files(const std::string& file_name)
208{
209 std::ifstream istr(file_name);
210 std::vector<std::string> includes;
211 std::string line;
212
213 while (std::getline(istr, line)) {
214 get_filename_from_include(line.c_str(), std::back_inserter(includes));
215 }
216
217 return includes;
218}
219
221std::vector<std::string> prepend(const std::string& str,
222 const std::vector<std::string>& strings)
223{
224 std::vector<std::string> result;
225 result.reserve(strings.size());
226
227 for (const auto& s: strings) {
228 result.push_back(str + s);
229 }
230
231 return result;
232}
233
235std::vector<std::string> insert_at_front(
236 const std::vector<std::string>& strings,
237 const std::string& str)
238{
239 auto result = strings;
240 result.insert(result.begin(), str);
241
242 return result;
243}
244
246template <class Predicate>
247std::vector<std::string> filter(
248 const std::vector<std::string>& vec,
249 const Predicate& pred)
250{
251 std::vector<std::string> match;
252
253 std::copy_if(vec.begin(), vec.end(),
254 std::back_inserter(match), pred);
255
256 return match;
257}
258
260template <class Predicate>
261std::vector<std::string> filter_files(
262 const std::string& dir,
263 const std::vector<std::string>& files,
264 const Predicate& pred)
265{
266 const std::string dirname(dir.empty() || dir == "." ? "" : dir + '/');
267 const auto files_in_dir = prepend(dirname, files);
268
269 return filter(files_in_dir, pred);
270}
271
273std::vector<std::string> complement(
274 const std::vector<std::string>& v1,
275 const std::vector<std::string>& v2)
276{
277 auto tv1 = v1;
278 auto tv2 = v2;
279
280 std::sort(tv1.begin(), tv1.end());
281 std::sort(tv2.begin(), tv2.end());
282
283 std::vector<std::string> diff;
284
285 std::set_difference(tv1.begin(), tv1.end(),
286 tv2.begin(), tv2.end(),
287 std::back_inserter(diff));
288
289 return diff;
290}
291
293template <typename T>
294std::string concat(const std::vector<std::string>& strings, const T& separator)
295{
296 std::string result;
297
298 for (const auto& s: strings)
299 result += s + separator;
300
301 return result;
302}
303
306void search_includes(const Config& config,
307 std::vector<std::string>& result,
308 int max_depth)
309{
310 if (max_depth <= 0) {
311 throw std::runtime_error(
312 "Error: #include nested too deeply (maximum depth: "
313 + std::to_string(max_depth) + "): " + config.file_name);
314 }
315
316 // find included files from #include statements, that are not
317 // already in result
318 const auto includes =
320
321 // select only files that exist in paths
322 std::vector<std::string> existing;
323 for (const auto& p: config.search_paths) {
324 const auto existing_in_path = filter_files(p, includes, file_exists);
325 existing.insert(existing.end(), existing_in_path.cbegin(), existing_in_path.cend());
326 result.insert(result.end(), existing_in_path.cbegin(), existing_in_path.cend());
327 }
328
329 // search recursively for included files in existing headers
330 for (const auto& f: existing) {
331 auto cfg = config;
332 cfg.file_name = f;
333 search_includes(cfg, result, max_depth - 1);
334 }
335
336 // search for non-existing headers
337 const auto non_existing =
338 complement(filenames(includes), filenames(existing));
339
340 if (!config.ignore_non_existing && !config.include_non_existing &&
341 !non_existing.empty()) {
342 throw std::runtime_error(
343 "Error: cannot find the following header file(s): " +
344 concat(non_existing, ' '));
345 }
346
347 if (config.include_non_existing)
348 result.insert(result.end(), non_existing.cbegin(), non_existing.cend());
349}
350
353std::vector<std::string> search_includes(const Config& config,
354 int max_depth = 100)
355{
356 std::vector<std::string> result;
357 search_includes(config, result, max_depth);
358 return result;
359}
360
361} // namespace depgen
362} // namespace flexiblesusy
363
364int main(int argc, char* argv[])
365{
366 using namespace flexiblesusy::depgen;
367
368 if (argc < 2) {
369 std::cerr << "Error: no input file\n";
370 print_usage(argv[0]);
371 return EXIT_FAILURE;
372 }
373
374 Config config;
375
376 for (int i = 1; i < argc; i++) {
377 const std::string arg(argv[i]);
378 if (starts_with(arg, "-D")) {
379 continue;
380 }
381 if (starts_with(arg, "-I") && arg.length() > 2) {
382 config.search_paths.push_back(arg.substr(std::strlen("-I")));
383 continue;
384 }
385 if (arg == "-MG") {
386 config.include_non_existing = true;
387 continue;
388 }
389 if (arg == "-MI") {
390 config.ignore_non_existing = true;
391 continue;
392 }
393 if (arg == "-MM") {
394 // ignore headers from system directories (default)
395 continue;
396 }
397 if (arg == "-M") {
398 std::cerr << "Warning: ignoring unsupported option " << arg << '\n';
399 continue;
400 }
401 if (arg == "-MD" || arg == "-MQ") {
402 std::cerr << "Warning: ignoring unsupported option " << arg << '\n';
403 i++;
404 continue;
405 }
406 if (arg == "-MP") {
407 config.add_empty_phony_targets = true;
408 continue;
409 }
410 if (arg == "-MT" && i + 1 < argc) {
411 config.target_name = argv[++i];
412 continue;
413 }
414 if (arg == "-MF" || arg == "-MMD" || arg == "-o") {
415 // interpret next argument as output file name
416 if (i + 1 < argc) {
417 const std::string of(argv[i + 1]);
418 if (!starts_with(of, "-I") && !starts_with(of, "-M") &&
419 !starts_with(of, "-D") && of != "-o") {
420 config.output_file = std::move(of);
421 i++;
422 } else {
423 std::cerr << "Error: " << arg << " expects a file name argument\n";
424 return EXIT_FAILURE;
425 }
426 }
427 continue;
428 }
429 if (arg == "--help" || arg == "-h") {
430 print_usage(argv[0]);
431 return EXIT_SUCCESS;
432 }
433 // interpret last argument as file name
434 if (i + 1 == argc) {
435 config.file_name = arg;
436 if (!file_exists(config.file_name)) {
437 std::cerr << "Error: file does not exist: " << config.file_name << '\n';
438 return EXIT_FAILURE;
439 }
440 continue;
441 }
442
443 std::cerr << "Error: unknown option: " << arg << '\n';
444 print_usage(argv[0]);
445 return EXIT_FAILURE;
446 }
447
448 if (config.file_name.empty()) {
449 std::cerr << "Error: no input file\n";
450 return EXIT_FAILURE;
451 }
452
453 // select output stream
454 std::ofstream fstr(config.output_file);
455 std::ostream* ostr = &std::cout;
456
457 if (!config.output_file.empty()) {
458 if (!fstr.good()) {
459 std::cerr << "Error: cannot write to file " << config.output_file << '\n';
460 return EXIT_FAILURE;
461 }
462 ostr = &fstr;
463 }
464
465 // include paths
467 config.search_paths.emplace_back(".");
469
470 try {
471 // search for header inclusions in file
472 const auto dependencies
474 search_includes(config),
476
477 if (config.target_name.empty()) {
478 config.target_name =
480 }
481
482 // output
483 print_dependencies(*ostr, config.target_name,
484 insert_at_front(dependencies, config.file_name));
485
486 if (config.add_empty_phony_targets) {
487 print_empty_phony_targets(*ostr, dependencies);
488 }
489 } catch (const std::exception& e) {
490 std::cerr << e.what() << '\n';
491 return EXIT_FAILURE;
492 }
493
494 return EXIT_SUCCESS;
495}
int main(int argc, char *argv[])
Definition: depgen.cpp:364
std::string replace_extension(const std::string &str, const std::string &ext)
replace file name extension by ‘ext’
Definition: depgen.cpp:111
bool file_exists(const std::string &name)
checks if given file exists
Definition: depgen.cpp:71
void print_usage(const std::string &program_name)
print usage message
Definition: depgen.cpp:124
std::vector< std::string > get_included_files(const std::string &file_name)
extract include statements from file (ignoring system headers)
Definition: depgen.cpp:207
void print_dependencies(std::ostream &ostr, const std::string &target_name, const std::vector< std::string > &dependencies)
print dependency list
Definition: depgen.cpp:147
std::vector< std::string > prepend(const std::string &str, const std::vector< std::string > &strings)
prepend ‘str’ to all strings
Definition: depgen.cpp:221
std::vector< std::string > complement(const std::vector< std::string > &v1, const std::vector< std::string > &v2)
returns all elements of ‘v1’, which are not ‘v2’
Definition: depgen.cpp:273
std::vector< std::string > filenames(const std::vector< std::string > &file_names)
returns file names w/o directory
Definition: depgen.cpp:63
std::vector< std::string > search_includes(const Config &config, int max_depth=100)
Definition: depgen.cpp:353
void get_filename_from_include(const char *s, OutputIterator it)
extracts file name from include "..." statement
Definition: depgen.cpp:168
std::vector< std::string > insert_at_front(const std::vector< std::string > &strings, const std::string &str)
insert ‘str’ at the beginning of vector
Definition: depgen.cpp:235
std::vector< std::string > filter_files(const std::string &dir, const std::vector< std::string > &files, const Predicate &pred)
returns files in directory ‘dir’ for which pred(f) == true
Definition: depgen.cpp:261
std::string concat(const std::vector< std::string > &strings, const T &separator)
concatenate strings with separator
Definition: depgen.cpp:294
std::vector< std::string > filter(const std::vector< std::string > &vec, const Predicate &pred)
returns elements of ‘vec’ for which pred(f) == true
Definition: depgen.cpp:247
std::string filename(const std::string &file_name)
returns file name w/o directory
Definition: depgen.cpp:56
std::string directory(const std::string &file_name)
returns directory from file name
Definition: depgen.cpp:47
bool starts_with(const std::string &str, const std::string &prefix)
tests whether ‘str’ starts with ‘prefix’
Definition: depgen.cpp:118
void search_includes(const Config &config, std::vector< std::string > &result, int max_depth)
Definition: depgen.cpp:306
std::vector< std::string > delete_duplicates(const std::vector< std::string > &vec, Predicate pred=Is_not_duplicate())
deletes duplicate elements from a vector (preseves order)
Definition: depgen.cpp:98
void print_empty_phony_targets(std::ostream &ostr, const std::vector< std::string > &dependencies)
print empty phony targets for each dependency
Definition: depgen.cpp:160
std::complex< double > f(double tau) noexcept
constexpr T arg(const Complex< T > &z) noexcept
Definition: complex.hpp:42
std::string to_string(char a)
std::vector< std::string > search_paths
Definition: depgen.cpp:40
bool operator()(const std::string &element)
Definition: depgen.cpp:89
bool operator()(const std::string &element)
Definition: depgen.cpp:81
std::set< std::string > s
Definition: depgen.cpp:85