3 The flang plugin ``flang-omp-report`` takes one Fortran
4 file in and returns a YAML report file of the input file.
5 This becomes an issue when you want to analyse an entire project
7 The purpose of this Python script is to generate a final YAML
8 summary from all of the files generated by ``flang-omp-report``.
10 Currently, it requires ``ruamel.yaml``,
11 which can be installed with:
13 ``pip3 install ruamel.yaml``
15 By default it scans the directory it is ran in
16 for any YAML files and outputs a summary to
17 stdout. It can be ran as:
19 ``python3 yaml_summarizer.py``
23 -d --directory Specify which directory to scan. Multiple directories can be searched by
24 providing a semicolon separated list of directories.
26 -l --log Combine all yaml files into one log (instead of generating a summary)
28 -o --output Specify a directory in which to save the summary file
30 -r --recursive Recursively search directory for all yaml files
34 ``python3 yaml_summarizer.py -d ~/llvm-project/build/ -r``
36 ``python3 yaml_summarizer.py -d "~/llvm-project/build/;~/llvm-project/flang/test/Examples"``
38 ``python3 yaml_summarizer.py -l -o ~/examples/report.yaml``
44 $ python3 yaml_summarizer.py file_1.yaml file_2.yaml
45 <Unique OMP constructs with there grouped clauses from file_1.yaml and file_2.yaml>
47 Constructs are in the form:
48 - construct: someOMPconstruct
58 $ python3 yaml_summarizer.py -l file_1.yaml file_2.yaml
60 <OMP clauses and constructs from file_1.yaml>
62 <OMP clauses and constructs from file_2.yaml>
64 Constructs are in the form:
65 - construct: someOMPConstruct
69 details: 'someDetailForClause'
75 from pathlib
import Path
76 from os
.path
import isdir
78 from ruamel
.yaml
import YAML
81 def find_yaml_files(search_directory
: Path
, search_pattern
: str):
83 Find all '.yaml' files and returns an iglob iterator to them.
86 search_pattern -- Search pattern for 'iglob' to use for finding '.yaml' files.
87 If this is set to 'None', then it will default to just searching
88 for all '.yaml' files in the current directory.
90 # @TODO: Currently *all* yaml files are read - regardless of whether they have
91 # been generated with 'flang-omp-report' or not. This might result in the script
92 # reading files that it should ignore.
95 str(search_directory
.joinpath(search_pattern
)), recursive
=True
98 return glob
.iglob(str("/" + search_pattern
), recursive
=True)
101 def process_log(data
, result
: list):
103 Process the data input as a 'log' to the result array. This esssentially just
104 stitches together all of the input '.yaml' files into one result.
107 data -- Data from yaml.load() for a yaml file. So the type can be 'Any'.
108 result -- Array to add the processed data to.
111 items
= result
.get(datum
["file"], [])
114 "construct": datum
["construct"],
115 "line": datum
["line"],
116 "clauses": datum
["clauses"],
119 result
[datum
["file"]] = items
122 def add_clause(datum
, construct
):
124 Add clauses to the construct if they're missing
125 Otherwise increment their count by one.
128 datum -- Data construct containing clauses to check.
129 construct -- Construct to add or increment clause count.
131 to_check
= [i
["clause"] for i
in construct
["clauses"]]
132 to_add
= [i
["clause"] for i
in datum
["clauses"]]
133 clauses
= construct
["clauses"]
136 for clause
in clauses
:
137 if clause
["clause"] == item
:
140 clauses
.append({"clause": item
, "count": 1})
143 def process_summary(data
, result
: dict):
145 Process the data input as a 'summary' to the 'result' dictionary.
148 data -- Data from yaml.load() for a yaml file. So the type can be 'Any'.
149 result -- Dictionary to add the processed data to.
153 (item
for item
in result
if item
["construct"] == datum
["construct"]), None
156 # Add the construct and clauses to the summary if
157 # they haven't been seen before
159 for i
in datum
["clauses"]:
160 clauses
.append({"clause": i
["clause"], "count": 1})
162 {"construct": datum
["construct"], "count": 1, "clauses": clauses
}
165 construct
["count"] += 1
167 add_clause(datum
, construct
)
170 def clean_summary(result
):
171 """Cleans the result after processing the yaml files with summary format."""
172 # Remove all "clauses" that are empty to keep things compact
173 for construct
in result
:
174 if construct
["clauses"] == []:
175 construct
.pop("clauses")
178 def clean_log(result
):
179 """Cleans the result after processing the yaml files with log format."""
180 for constructs
in result
.values():
181 for construct
in constructs
:
182 if construct
["clauses"] == []:
183 construct
.pop("clauses")
186 def output_result(yaml
: YAML
, result
, output_file
: Path
):
188 Outputs result to either 'stdout' or to a output file.
191 result -- Format result to output.
192 output_file -- File to output result to. If this is 'None' then result will be
193 outputted to 'stdout'.
196 with
open(output_file
, "w+", encoding
="utf-8") as file:
197 if output_file
.suffix
== ".yaml":
198 yaml
.dump(result
, file)
202 yaml
.dump(result
, sys
.stdout
)
206 search_directories
: list, search_pattern
: str, result_format
: str, output_file
: Path
209 Reads each yaml file, calls the appropiate format function for
210 the file and then ouputs the result to either 'stdout' or to an output file.
213 search_directories -- List of directory paths to search for '.yaml' files in.
214 search_pattern -- String pattern formatted for use with glob.iglob to find all
216 result_format -- String representing output format. Current supported strings are: 'log'.
217 output_file -- Path to output file (If value is None, then default to outputting to 'stdout').
219 if result_format
== "log":
222 clean_report
= clean_log
225 action
= process_summary
226 clean_report
= clean_summary
230 for search_directory
in search_directories
:
231 for file in find_yaml_files(search_directory
, search_pattern
):
232 with
open(file, "r", encoding
="utf-8") as yaml_file
:
233 data
= yaml
.load(yaml_file
)
236 if clean_report
is not None:
239 output_result(yaml
, result
, output_file
)
242 def create_arg_parser():
243 """Create and return a argparse.ArgumentParser modified for script."""
244 parser
= argparse
.ArgumentParser()
246 "-d", "--directory", help="Specify a directory to scan", dest
="dir", type=str
251 help="Writes to a file instead of\
259 help="Recursive search for .yaml files",
267 exclusive_parser
= parser
.add_mutually_exclusive_group()
268 exclusive_parser
.add_argument(
271 help="Modifies report format: " "Combines the log '.yaml' files into one file.",
278 def parse_arguments():
279 """Parses arguments given to script and returns a tuple of processed arguments."""
280 parser
= create_arg_parser()
281 args
= parser
.parse_args()
284 search_directory
= [Path(path
) for path
in args
.dir.split(";")]
286 search_directory
= [Path
.cwd()]
289 search_pattern
= "**/*.yaml"
291 search_pattern
= "*.yaml"
294 result_format
= "log"
296 result_format
= "summary"
299 if isdir(args
.output
):
300 output_file
= Path(args
.output
).joinpath("summary.yaml")
301 elif isdir(Path(args
.output
).resolve().parent
):
302 output_file
= Path(args
.output
)
306 return (search_directory
, search_pattern
, result_format
, output_file
)
310 """Main function of script."""
311 (search_directory
, search_pattern
, result_format
, output_file
) = parse_arguments()
313 process_yaml(search_directory
, search_pattern
, result_format
, output_file
)
318 if __name__
== "__main__":