diff --git a/src/Python/Utilities/VTKModulesForCxx.md b/src/Python/Utilities/VTKModulesForCxx.md new file mode 100644 index 0000000000000000000000000000000000000000..16f2a7c6fea8b2af4fe6c284da42e44b9290dce5 --- /dev/null +++ b/src/Python/Utilities/VTKModulesForCxx.md @@ -0,0 +1,11 @@ +### Description + +Use this to generate a `find_package(VTK COMPONENTS ...)` command for CMake. + +It requires `modules.json`, found in your VTK build folder, and your source files. After running, it will generate a `find_package(VTK COMPONENTS ...)` command listing all the vtk modules needed by the C++ source and header files in your code. + +Paths for more than one source path can be specified. If there are spaces in the paths, enclose the path in quotes. + +If it is unable to find modules for your headers then a list of these, along with the files they are in, is produced so you can manually add the corresponding modules or rebuild VTK to include the missing modules. + +You will need to manually add any third-party modules (if used) to the find_package command. diff --git a/src/Python/Utilities/VTKModulesForCxx.py b/src/Python/Utilities/VTKModulesForCxx.py new file mode 100755 index 0000000000000000000000000000000000000000..09062841eda91541b423cebac0947301a651267d --- /dev/null +++ b/src/Python/Utilities/VTKModulesForCxx.py @@ -0,0 +1,215 @@ +#!/usr/bin/env python + +import collections +import json +import os +import re +from pathlib import Path + + +def get_program_parameters(argv): + import argparse + description = 'Generate a find_package(VTK COMPONENTS ...) command for CMake.' + epilogue = ''' +Uses modules.json and your source files to generate a + find_package(VTK COMPONENTS ...) command listing all the vtk modules + needed by the C++ source and header files in your code. + +Paths for more than one source path can be specified. + +Note than if there are spaces in the paths, enclose the path in quotes. + +If it is unable to find modules for your headers then + a list of these, along with the files they are in, is produced + so you can manually add the corresponding modules or rebuild VTK + to include the missing modules. + +You will need to manually add any third-party modules + (if used) to the find_package command. + ''' + parser = argparse.ArgumentParser(description=description, epilog=epilogue, + formatter_class=argparse.RawTextHelpFormatter) + parser.add_argument('json', default=['modules.json'], help='The path to the VTK JSON file (modules.json).') + parser.add_argument('sources', nargs='+', help='The path to the source files or to source files.') + args = parser.parse_args() + return args.json, args.sources + + +def get_headers_modules(json_data): + """ + From the parsed JSON data file make a dictionary whose key is the + header filename and value is the module. + :param json_data: The parsed JSON file modules.json. + :return: + """ + + # The headers should be unique to a module, however we will not assume this. + res = collections.defaultdict(set) + for k, v in json_data['modules'].items(): + if 'headers' in v: + for k1 in v['headers']: + res[k1].add(k) + return res + + +def get_vtk_components(jpath, paths): + """ + Get the VTK components + :param jpath: The path to the JSON file. + :param paths: The C++ file paths. + :return: + """ + + header_pattern = re.compile(r'^#include *[<\"](\S+)[>\"]') + vtk_include_pattern = re.compile(r'^(vtk\S+)') + vtk_qt_include_pattern = re.compile(r'^(QVTK\S+)') + + with open(jpath) as data_file: + json_data = json.load(data_file) + vtk_headers_modules = get_headers_modules(json_data) + + modules = set() + inc_no_mod = set() + inc_no_mod_headers = collections.defaultdict(set) + mod_implements = collections.defaultdict(set) + headers = collections.defaultdict(set) + + for path in paths: + if path.is_file(): + content = path.read_text().split('\n') + for line in content: + m = header_pattern.match(line.strip()) + if m: + # We have a header name, split it from its path (if the path exists). + header_parts = os.path.split(m.group(1)) + m = vtk_include_pattern.match(header_parts[1]) + if m: + headers[m.group(1)].add(path) + continue + m = vtk_qt_include_pattern.match(header_parts[1]) + if m: + headers[m.group(1)].add(path) + for incl in headers: + if incl in vtk_headers_modules: + m = vtk_headers_modules[incl] + for v in m: + modules.add(v) + else: + inc_no_mod.add(incl) + inc_no_mod_headers[incl] = headers[incl] + + if headers: + for m in modules: + if not json_data['modules'][m]['implementable']: + continue + for i in json_data['modules']: + if i in modules: + continue + if m in json_data['modules'][i]['implements']: + # Suggest module i since it implements m + mod_implements[i].add(m) + + return modules, mod_implements, inc_no_mod, inc_no_mod_headers + + +def disp_components(modules, module_implements): + """ + For the found modules display them in a form that the user can + copy/paste into their CMakeLists.txt file. + :param modules: The modules. + :param module_implements: Modules implementing other modules. + :return: + """ + res = ['find_package(VTK\n COMPONENTS'] + for m in sorted(modules): + res.append(' {:s}'.format(m.split('::')[1])) + if module_implements: + keys = sorted(module_implements) + max_width = len(max(keys, key=len).split('::')[1]) + comments = [ + ' #', + ' # These modules are suggested since they implement an existing module.', + ' # You may need to uncomment one or more of these.', + ' # If vtkRenderWindow is used and you want to use OpenGL,', + ' # you also need the RenderingOpenGL2 module.', + ' # If vtkRenderWindowInteractor is used,', + ' # uncomment RenderingUI and possibly InteractionStyle.', + ' # If text rendering is used, uncomment RenderingFreeType', + ' #' + ] + res.extend(comments) + for key in keys: + res.append( + f' # {key.split("::")[1]:<{max_width}} # implements {", ".join(sorted(module_implements[key]))}') + res.append(')') + + return res + + +def disp_missing_components(inc_no_mod, inc_no_mod_headers): + """ + Display the headers along with the missing VTK modules. + + :param inc_no_mod: Missing modules. + :param inc_no_mod_headers: Headers with missing modules. + :return: + """ + if inc_no_mod: + res = ['' + '*' * 64, + 'You will need to manually add the modules that', + ' use these headers to the find_package command.', + 'These could be external modules not in the modules.json file.', + 'Or you may need to rebuild VTK to include the missing modules.', + '', + 'Here are the vtk headers and corresponding files:'] + sinmd = sorted(inc_no_mod) + for i in sinmd: + sh = sorted(list(inc_no_mod_headers[i])) + res.append(f'in {i}:') + for j in sh: + res.append(f' {j}') + res.append('*' * 64) + + return res + else: + return None + + +def main(json_path, src_paths): + jpath = Path(json_path) + if jpath.is_dir(): + jpath = jpath / 'modules.json' + if not jpath.is_file(): + print(f'Non existent JSON file: {jpath}') + return + + paths = list() + valid_ext = ['.h', '.hxx', '.cxx', '.cpp', '.txx'] + path_list = list() + for fn in src_paths: + path = Path(fn) + if path.is_file() and path.suffix in valid_ext: + paths.append(path) + elif path.is_dir(): + for e in valid_ext: + path_list += list(Path(fn).rglob(f'*{e}')) + program_path = Path(__file__) + for path in path_list: + if path.resolve() != program_path.resolve(): + paths.append(path) + else: + print(f'Non existent path: {path}') + + modules, mod_implements, inc_no_mod, inc_no_mod_headers = get_vtk_components(jpath, paths) + + print('\n'.join(disp_components(modules, mod_implements))) + if inc_no_mod: + print('\n'.join(disp_missing_components(inc_no_mod, inc_no_mod_headers))) + + +if __name__ == '__main__': + import sys + + json_paths, src_paths = get_program_parameters(sys.argv) + main(json_paths, src_paths)