├── .gitignore ├── .vscode ├── launch.json └── settings.json ├── LICENSE ├── README.md ├── __init__.py ├── cxx_modules_converter.py ├── cxx_modules_converter_lib.py ├── cxx_modules_converter_lib_test.py ├── poetry.lock ├── pyproject.toml ├── pytest.ini ├── requirements-test.txt └── test_data ├── compat ├── expected │ ├── simple.cpp │ ├── simple.cppm │ ├── simple.h │ └── subdir │ │ ├── simple2.cpp │ │ ├── simple2.cppm │ │ └── simple2.h └── input │ ├── simple.cpp │ ├── simple.h │ └── subdir │ ├── simple2.cpp │ └── simple2.h ├── header ├── expected │ ├── simple.cpp │ └── simple.h └── input │ ├── simple.cpp │ └── simple.h ├── header_subdir ├── expected │ └── subdir │ │ ├── simple.cpp │ │ └── simple.h └── input │ └── subdir │ ├── simple.cpp │ └── simple.h ├── header_subdir_nested ├── expected │ └── subdir │ │ └── nested │ │ ├── simple.cpp │ │ └── simple.h └── input │ └── subdir │ └── nested │ ├── simple.cpp │ └── simple.h ├── inext ├── expected │ ├── simple.cpp │ ├── simple.cppm │ └── simple2.h └── input │ ├── simple.cpp │ ├── simple.hpp │ └── simple2.h ├── modules_path ├── expected │ ├── dir2 │ │ ├── local_include.cppm │ │ ├── simple.cpp │ │ └── simple.cppm │ └── subdir │ │ ├── local_include.cppm │ │ ├── simple.cpp │ │ └── simple.cppm └── input │ ├── dir2 │ ├── local_include.h │ ├── simple.cpp │ └── simple.h │ └── subdir │ ├── local_include.h │ ├── simple.cpp │ └── simple.h ├── named1 ├── expected │ ├── simple.cpp │ └── simple.cppm └── input │ ├── simple.cpp │ └── simple.h ├── other ├── expected │ └── other.txt └── input │ └── other.txt ├── outext ├── expected │ ├── simple.cxx │ ├── simple.ixx │ └── simple2.hpp └── input │ ├── simple.cpp │ ├── simple.h │ └── simple2.hpp ├── prefix ├── expected │ └── subdir │ │ ├── simple.cpp │ │ └── simple.cppm └── input │ ├── skipped.h │ └── subdir │ ├── simple.cpp │ └── simple.h ├── prefix_named ├── expected │ └── subdir │ │ ├── local_include.cppm │ │ ├── simple.cpp │ │ └── simple.cppm └── input │ ├── skipped.h │ └── subdir │ ├── local_include.h │ ├── simple.cpp │ └── simple.h ├── simple ├── expected │ ├── simple.cpp │ └── simple.cppm └── input │ ├── simple.cpp │ └── simple.h ├── simple_std ├── expected │ ├── simple.cpp │ └── simple.cppm └── input │ ├── simple.cpp │ └── simple.h ├── skip ├── expected │ └── subdir1 │ │ └── simple1.cppm └── input │ ├── skipdir │ └── empty.txt │ └── subdir1 │ ├── simple1.h │ ├── simple2.h │ └── skipsubdir │ └── empty.txt ├── subdir ├── expected │ └── subdir1 │ │ ├── simple1.cppm │ │ └── simple2.cppm └── input │ └── subdir1 │ ├── simple1.h │ └── simple2.h ├── subdirs ├── expected │ ├── simple1.cppm │ └── subdir1 │ │ ├── simple1.cppm │ │ ├── simple2.cppm │ │ └── subdir2 │ │ ├── simple1.cppm │ │ └── simple2.cppm └── input │ ├── simple1.h │ └── subdir1 │ ├── simple1.h │ ├── simple2.h │ └── subdir2 │ ├── simple1.h │ └── simple2.h ├── subdirs_rooted ├── expected │ └── subdir │ │ ├── simple1.cppm │ │ └── subdir1 │ │ ├── simple1.cppm │ │ ├── simple2.cppm │ │ ├── subdir2 │ │ ├── simple1.cpp │ │ ├── simple1.cppm │ │ └── simple2.cppm │ │ ├── use_relative_include.cppm │ │ ├── use_relative_include_missing.cppm │ │ └── use_search_path_include_existing.cppm └── input │ └── subdir │ ├── simple1.h │ └── subdir1 │ ├── simple1.h │ ├── simple2.h │ ├── subdir2 │ ├── simple1.cpp │ ├── simple1.h │ └── simple2.h │ ├── use_relative_include.h │ ├── use_relative_include_missing.h │ └── use_search_path_include_existing.h ├── subdirs_rooted_brackets ├── expected │ ├── subdir │ │ ├── simple1.cppm │ │ └── subdir1 │ │ │ ├── simple1.cppm │ │ │ ├── simple2.cppm │ │ │ ├── subdir2 │ │ │ ├── simple1.cpp │ │ │ ├── simple1.cppm │ │ │ └── simple2.cppm │ │ │ ├── use_relative_include.cppm │ │ │ ├── use_relative_include_missing.cppm │ │ │ └── use_search_path_include_existing.cppm │ └── subdir2 │ │ └── simple_existing.cppm └── input │ ├── subdir │ ├── simple1.h │ └── subdir1 │ │ ├── simple1.h │ │ ├── simple2.h │ │ ├── subdir2 │ │ ├── simple1.cpp │ │ ├── simple1.h │ │ └── simple2.h │ │ ├── use_relative_include.h │ │ ├── use_relative_include_missing.h │ │ └── use_search_path_include_existing.h │ └── subdir2 │ └── simple_existing.h ├── twice ├── expected │ ├── other.txt │ ├── simple.cpp │ └── simple.cppm └── input │ ├── other.txt │ ├── simple.cpp │ └── simple.h └── two ├── expected ├── simple1.cppm └── simple2.cppm └── input ├── simple1.h └── simple2.h /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | .pytest_cache 3 | 4 | dist 5 | result 6 | results 7 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Python Debugger: Current File with Arguments", 9 | "type": "debugpy", 10 | "request": "launch", 11 | "program": "${file}", 12 | "console": "integratedTerminal", 13 | "args": "${command:pickArgs}" 14 | }, 15 | { 16 | "name": "Python Debugger: main Utils", 17 | "type": "debugpy", 18 | "request": "launch", 19 | "program": "cxx_modules_converter.py", 20 | "console": "integratedTerminal", 21 | "args": "-s ~/project/cxx_modules_converter_other_test_data/Utils -d result --parent" 22 | }, 23 | ] 24 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.testing.pytestArgs": [ 3 | "." 4 | ], 5 | "python.testing.unittestEnabled": false, 6 | "python.testing.pytestEnabled": true, 7 | "python.analysis.typeCheckingMode": "strict" 8 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Alexander Petrov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cxx_modules_converter 2 | 3 | `cxx_modules_converter.py` is a Python script to convert C++ sources and headers to C++20 modules. 4 | 5 | ## License 6 | 7 | cxx_modules_converter is licensed under the [MIT](LICENSE) license. 8 | 9 | ## Usage 10 | Script can be used as following: 11 | > cxx_modules_converter.py [-h] [-s DIRECTORY] [-d DESTINATION] [-a {modules,headers}] [-r ROOT] [-p] [-I INCLUDE] [-n NAME] [-k SKIP] [-c COMPAT] [-m COMPAT_MACRO] [-e HEADER] [--export EXPORT] [--exportsuffix EXPORTSUFFIX] [-v] 12 | 13 | ### Options: 14 | * -h, --help show this help message and exit 15 | * -s DIRECTORY, --directory DIRECTORY 16 | the directory with files 17 | * -i, --inplace convert files in the same directory or put conversion result to destination (unsupported) 18 | * -d DESTINATION, --destination DESTINATION 19 | destination directory where to put conversion result, ignored when --inplace is provided 20 | * -a {modules,headers}, --action {modules,headers} 21 | action to perform - convert to modules or headers 22 | * -r ROOT, --root ROOT resolve module names starting from this root directory, ignored when --parent 23 | * -p, --parent resolve module names starting from parent of source directory 24 | * -I INCLUDE, --include INCLUDE 25 | include search path, starting from root or parent directory 26 | * -n NAME, --name NAME module name for modules in [root] directory which prefixes all modules 27 | * -k SKIP, --skip SKIP skip patterns - files and directories matching any pattern will not be converted or copied (fmatch is used) 28 | * -c COMPAT, --compat COMPAT 29 | compat patterns - files and directories matching any pattern will be converted in compatibility mode allowing to use as either module or header (fmatch is used) 30 | * -m COMPAT_MACRO, --compat-macro COMPAT_MACRO 31 | compatibility macro name used in compat modules and headers 32 | * -e HEADER, --header HEADER 33 | always include headers with matching names and copy them as is (fmatch is used) 34 | * --export EXPORT A=B means module A exports module B, i.e. `--export A=B` means module A will have `export import B;`. use `--export "A=*"` to export all imports. use `--export "*=B"` to export B from all modules. use `--export "*=*"` to 35 | export all from all modules. 36 | * --exportsuffix EXPORTSUFFIX 37 | export module suffix for which `export import` is used instead of simple `import` 38 | * --inextheader INEXTHEADER input header file extensions, .h by default. first use replaces the default, subsequent uses append. 39 | * --inextcxx INEXTCXX input C++ source file extensions, .cpp by default. first use replaces the default, subsequent uses append. 40 | * --outextmod OUTEXTMOD output module interface unit file extensions. default: .cppm 41 | * e.g. `--outextmod=.ixx` to skip the need to change `/interface /TP` options in msvc (https://learn.microsoft.com/en-us/cpp/build/reference/interface?view=msvc-170) 42 | * --outextmodimpl OUTEXTMODIMPL output module implementation unit file extensions. default: .cpp 43 | * --modules MODULES `M=P`: start modules tree `M` at path `P`, directory separator is converted to `.` (dot). Default: use directory name and file name as module name. 44 | * --modulestd MODULESTD 45 | Enable `std` module, i.e. define `--modules vector=std` to replace `vector` and other standard headers to `import std;`. 46 | * --modulestdcompat MODULESTDCOMPAT 47 | Enable `std.compat` module, i.e. define `--modules vector=std.compat` to replace `vector` and other standard headers to `import std.compat;`. 48 | * -v, --version show version 49 | 50 | ## Assumptions 51 | The converter has several assumptions which are not configurable (at the moment): 52 | * following source files extensions are used: 53 | * `.h` - header file 54 | * `.cpp` - c++ source file 55 | * `.cpp` - module implementation unit 56 | * `.cppm` - module interface unit 57 | * header file path is used to determine module name by joining path parts with dots (`.`); same for c++ source files 58 | * system header includes using `#include <>` are moved to global module fragment 59 | 60 | ## Tests 61 | [`pytest`](https://pytest.org/) is used to run tests. 62 | The `venv` can be used to create python3 virtual environment, assuming Linux and bash is used: 63 | ```bash 64 | python3 -m venv .venv 65 | source .venv/bin/activate 66 | ``` 67 | Install pytest requirements using 68 | ```bash 69 | pip install -r requirements-test.txt 70 | ``` 71 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zowers/cxx_modules_converter/110b784836b745cd231685aaff0a7c06277689ac/__init__.py -------------------------------------------------------------------------------- /cxx_modules_converter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Convert C++20 modules to headers and headers to modules 4 | # 5 | from __future__ import annotations 6 | 7 | import argparse 8 | import importlib.metadata 9 | from pathlib import Path 10 | import sys 11 | 12 | from cxx_modules_converter_lib import ( 13 | Converter, 14 | ConvertAction, 15 | COMPAT_MACRO_DEFAULT, 16 | always_include_names, 17 | ContentType, 18 | Options, 19 | ) 20 | 21 | def get_version() -> str | None: 22 | try: 23 | metadata_version = importlib.metadata.version('cxx_modules_converter') 24 | if metadata_version: 25 | return metadata_version 26 | except importlib.metadata.PackageNotFoundError: 27 | return 'not-installed' 28 | 29 | def parse_args(argv: list[str] | None = None): 30 | version = get_version() 31 | parser = argparse.ArgumentParser( 32 | prog='cxx_modules_converter', 33 | description=f'Convert C++20 modules to headers and headers to modules, version: {version}', 34 | epilog='') 35 | directory = '.' 36 | options = Options() 37 | parser.add_argument('-s','--directory', default=directory, help='the directory with files') 38 | parser.add_argument('-i', '--inplace', default=False, action='store_true', help='convert files in the same directory or put conversion result to destination') 39 | parser.add_argument('-d', '--destination', default=directory, help='destination directory where to put conversion result, ignored when --inplace is provided') 40 | parser.add_argument('-a', '--action', default=ConvertAction.MODULES, 41 | choices=[ConvertAction.MODULES, ConvertAction.HEADERS], 42 | help='action to perform - convert to modules or headers') 43 | parser.add_argument('-r', '--root', default=directory, help='resolve module names starting from this root directory, ignored when --parent') 44 | parser.add_argument('-p', '--parent', action='store_true', default=False, help='resolve module names starting from parent of source directory') 45 | parser.add_argument('-I', '--include', action='append', default=[], help='include search path, starting from root or parent directory') 46 | parser.add_argument('-n', '--name', default='', help='module name for modules in [root] directory which prefixes all modules') 47 | parser.add_argument('-k', '--skip', action='append', default=[], help='skip patterns - files and directories matching any pattern will not be converted or copied (fmatch is used)') 48 | parser.add_argument('-c', '--compat', action='append', default=[], 49 | help='compat patterns - files and directories matching any pattern' 50 | + ' will be converted in compatibility mode allowing to use as either module or header (fmatch is used)') 51 | parser.add_argument('-m', '--compat-macro', default=COMPAT_MACRO_DEFAULT, help='compatibility macro name used in compat modules and headers') 52 | parser.add_argument('-e', '--header', action='append', default=always_include_names, help='always include headers with matching names and copy them as is (fmatch is used)') 53 | parser.add_argument('--export', action='append', default=[], 54 | help='A=B means module A exports module B, i.e. `--export A=B` means module A will have `export import B;`.' 55 | + ' use `--export "A=*"` to export all imports.' 56 | + ' use `--export "*=B"` to export B from all modules.' 57 | + ' use `--export "*=*"` to export all from all modules.' 58 | ) 59 | parser.add_argument('--exportsuffix', action='append', default=[], help='export module suffix for which `export import` is used instead of simple `import`') 60 | parser.add_argument('--inextheader', action='append', default=[], help='input header file extensions, .h by default. first use replaces the default, subsequent uses append.') 61 | parser.add_argument('--inextcxx', action='append', default=[], help='input C++ source file extensions, .cpp by default. first use replaces the default, subsequent uses append.') 62 | parser.add_argument('--outextmod', help=f'output module interface unit file extensions. default: {options.content_type_to_ext[ContentType.MODULE_INTERFACE]}') 63 | parser.add_argument('--outextmodimpl', help=f'output module implementation unit file extensions. default: {options.content_type_to_ext[ContentType.MODULE_IMPL]}') 64 | parser.add_argument('--modules', action='append', default=[], help='`M=P`: start modules tree `M` at path `P`, directory separator is converted to `.` (dot). Default: use directory name and file name as module name.') 65 | parser.add_argument('--modulestd', default=False, action='store_true', help='Enable `std` module, i.e. define `--modules vector=std` to replace `vector` and other standard headers to `import std;`.') 66 | parser.add_argument('--modulestdcompat', default=False, action='store_true', help='Enable `std.compat` module, i.e. define `--modules vector=std.compat` to replace `vector` and other standard headers to `import std.compat;`.') 67 | parser.add_argument('-v', '--version', default=False, action='store_true', help='show version') 68 | parsed_args = parser.parse_args(argv) 69 | return parsed_args 70 | 71 | def log(message: str): 72 | print('cxx_modules_converter:', message) 73 | 74 | def main(): 75 | parsed_args = parse_args() 76 | if parsed_args.version: 77 | version = get_version() 78 | log(f'{version}') 79 | return 80 | if not parsed_args.directory: 81 | log('--directory argument is required') 82 | return 1 83 | log_messages: list[str] = [] 84 | log_messages.append(f'converting files of directory "{parsed_args.directory}" to {parsed_args.action} {"inplace" if parsed_args.inplace else " into " + parsed_args.destination}') 85 | if parsed_args.inplace: 86 | destination = parsed_args.directory 87 | else: 88 | destination = parsed_args.destination 89 | assert(destination != parsed_args.directory) 90 | path = Path(parsed_args.directory) 91 | converter = Converter(parsed_args.action) 92 | if parsed_args.parent: 93 | parsed_args.root = path.parent 94 | if parsed_args.root: 95 | root_dir = Path(parsed_args.root) 96 | converter.options.root_dir = root_dir 97 | else: 98 | converter.options.root_dir = path 99 | converter.options.set_root_dir_module_name(parsed_args.name) 100 | for include in parsed_args.include: 101 | log_messages.append(f'include search path: "{include}"') 102 | converter.options.search_path.append(include) 103 | for skip_pattern in parsed_args.skip: 104 | log_messages.append(f'skip pattern: "{skip_pattern}"') 105 | converter.options.skip_patterns.append(skip_pattern) 106 | for compat_pattern in parsed_args.compat: 107 | log_messages.append(f'compat pattern: "{compat_pattern}"') 108 | converter.options.compat_patterns.append(compat_pattern) 109 | if parsed_args.compat_macro: 110 | converter.options.compat_macro = parsed_args.compat_macro 111 | for header in parsed_args.header: 112 | log_messages.append(f'header: "{header}"') 113 | converter.options.always_include_names.append(header) 114 | for export_pair in parsed_args.export: 115 | owner, export = export_pair.split('=') 116 | log_messages.append(f'export: "{owner}" exports "{export}"') 117 | converter.options.add_export_module(owner, export) 118 | for export_suffix in parsed_args.exportsuffix: 119 | log_messages.append(f'export suffix: "{export_suffix}"') 120 | converter.options.export_suffixes.append(export_suffix) 121 | for ext in parsed_args.inextheader: 122 | log_messages.append(f'input header extension: "{ext}"') 123 | converter.options.add_module_action_ext_type(ext, ContentType.HEADER) 124 | for ext in parsed_args.inextcxx: 125 | log_messages.append(f'input C++ source extension: "{ext}"') 126 | converter.options.add_module_action_ext_type(ext, ContentType.CXX) 127 | if parsed_args.outextmod: 128 | ext = parsed_args.outextmod 129 | log_messages.append(f'output module interface unit extension: "{ext}"') 130 | converter.options.set_output_content_type_to_ext(ContentType.MODULE_INTERFACE, ext) 131 | if parsed_args.outextmodimpl: 132 | ext = parsed_args.outextmodimpl 133 | log_messages.append(f'output module implementation unit extension: "{ext}"') 134 | converter.options.set_output_content_type_to_ext(ContentType.MODULE_IMPL, ext) 135 | for modules_pair in parsed_args.modules: 136 | module_prefix, path = modules_pair.split('=') 137 | log_messages.append(f'module prefix: "{module_prefix}" in path "{path}"') 138 | converter.options.add_modules_path(module_prefix, path) 139 | if parsed_args.modulestd: 140 | assert(not parsed_args.modulestdcompat) 141 | log_messages.append(f'module std: "std"') 142 | converter.options.add_std_module() 143 | if parsed_args.modulestdcompat: 144 | assert(not parsed_args.modulestd) 145 | log_messages.append(f'module std.compat: "std.compat"') 146 | converter.options.add_std_compat_module() 147 | log_text = '\n'.join(log_messages) 148 | log(log_text) 149 | converter.convert_directory(path, Path(destination)) 150 | log(log_text) 151 | log(f'done, all: {converter.all_files}, convertable: {converter.convertable_files}, converted: {converter.converted_files}, copied: {converter.copied_files} ') 152 | 153 | if __name__ == '__main__': 154 | sys.exit(main()) 155 | -------------------------------------------------------------------------------- /cxx_modules_converter_lib.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import fnmatch 4 | from collections.abc import Callable 5 | import copy 6 | import enum 7 | import os 8 | import os.path 9 | from pathlib import Path, PurePosixPath 10 | import re 11 | import shutil 12 | 13 | from typing import Any, cast 14 | try: 15 | # make pylance happy 16 | from typing import TypeAlias as TypeAlias2 17 | except ImportError: 18 | TypeAlias2 = Any 19 | TypeAlias = TypeAlias2 20 | 21 | class ConvertAction(enum.Enum): 22 | MODULES = 'modules' 23 | HEADERS = 'headers' 24 | 25 | def __str__(self) -> str: 26 | return self.value 27 | 28 | AlwaysIncludeNames: TypeAlias = set[str] 29 | always_include_names = [ 30 | 'cassert', 31 | 'assert.h', 32 | ] 33 | 34 | COMPAT_MACRO_DEFAULT: str = "CXX_COMPAT_HEADER" 35 | STAR_MODULE_EXPORT: str = '*' 36 | 37 | STD_MODULE = 'std' 38 | STD_COMPAT_MODULE = 'std.compat' 39 | 40 | # Standard library headers, list of files from libstdc++-14-dev 41 | STD_MODULE_PATHS = [ 42 | 'algorithm', 43 | 'any', 44 | 'array', 45 | 'atomic', 46 | 'barrier', 47 | 'bit', 48 | 'bitset', 49 | # 'cassert', 50 | 'ccomplex', 51 | 'cctype', 52 | 'cerrno', 53 | 'cfenv', 54 | 'cfloat', 55 | 'charconv', 56 | 'chrono', 57 | 'cinttypes', 58 | 'ciso646', 59 | 'climits', 60 | 'clocale', 61 | 'cmath', 62 | 'codecvt', 63 | 'compare', 64 | 'complex', 65 | 'concepts', 66 | 'condition_variable', 67 | 'coroutine', 68 | 'csetjmp', 69 | 'csignal', 70 | 'cstdalign', 71 | 'cstdarg', 72 | 'cstdbool', 73 | 'cstddef', 74 | 'cstdint', 75 | 'cstdio', 76 | 'cstdlib', 77 | 'cstring', 78 | 'ctgmath', 79 | 'ctime', 80 | 'cuchar', 81 | 'cwchar', 82 | 'cwctype', 83 | 'deque', 84 | 'exception', 85 | 'execution', 86 | 'expected', 87 | 'filesystem', 88 | 'format', 89 | 'forward_list', 90 | 'fstream', 91 | 'functional', 92 | 'future', 93 | 'generator', 94 | 'initializer_list', 95 | 'iomanip', 96 | 'ios', 97 | 'iosfwd', 98 | 'iostream', 99 | 'istream', 100 | 'iterator', 101 | 'latch', 102 | 'limits', 103 | 'list', 104 | 'locale', 105 | 'map', 106 | 'memory', 107 | 'memory_resource', 108 | 'mutex', 109 | 'new', 110 | 'numbers', 111 | 'numeric', 112 | 'optional', 113 | 'ostream', 114 | 'print', 115 | 'queue', 116 | 'random', 117 | 'ranges', 118 | 'ratio', 119 | 'regex', 120 | 'scoped_allocator', 121 | 'semaphore', 122 | 'set', 123 | 'shared_mutex', 124 | 'source_location', 125 | 'span', 126 | 'spanstream', 127 | 'sstream', 128 | 'stack', 129 | 'stacktrace', 130 | 'stdexcept', 131 | 'stdfloat', 132 | 'stop_token', 133 | 'streambuf', 134 | 'string', 135 | 'string_view', 136 | 'syncstream', 137 | 'system_error', 138 | 'text_encoding', 139 | 'thread', 140 | 'tuple', 141 | 'typeindex', 142 | 'typeinfo', 143 | 'type_traits', 144 | 'unordered_map', 145 | 'unordered_set', 146 | 'utility', 147 | 'valarray', 148 | 'variant', 149 | 'vector', 150 | # 'version', 151 | ] 152 | class ContentType(enum.Enum): 153 | HEADER = 1 154 | CXX = 2 155 | MODULE_INTERFACE = 3 156 | MODULE_IMPL = 4 157 | OTHER = 5 158 | 159 | ExtTypes: TypeAlias = dict[str, ContentType] 160 | ContentTypeToExt: TypeAlias = dict[ContentType, str] 161 | 162 | class Options: 163 | def __init__(self): 164 | self.always_include_names = copy.copy(always_include_names) 165 | self.root_dir: Path = Path() 166 | # self.root_dir_module_name: str = '' 167 | self.search_path: list[str] = [] 168 | self.skip_patterns: list[str] = [] 169 | self.compat_patterns: list[str] = [] 170 | self.compat_macro: str = COMPAT_MACRO_DEFAULT 171 | self.export: dict[str, set[str]] = {} 172 | self.export_suffixes: list[str] = [] 173 | self.modules_ext_types: ExtTypes = { 174 | '.h': ContentType.HEADER, 175 | '.cpp': ContentType.CXX, 176 | } 177 | self._modules_ext_types_is_default = set(self.modules_ext_types.values()) 178 | self.headers_ext_types: ExtTypes = { 179 | '.cppm': ContentType.MODULE_INTERFACE, 180 | '.cpp': ContentType.MODULE_IMPL, 181 | } 182 | self._headers_ext_types_is_default = set(self.headers_ext_types.values()) 183 | self.content_type_to_ext: ContentTypeToExt = { 184 | ContentType.HEADER: '.h', 185 | ContentType.CXX: '.cpp', 186 | ContentType.MODULE_INTERFACE: '.cppm', 187 | ContentType.MODULE_IMPL: '.cpp', 188 | } 189 | self.path_to_module_prefix_map: dict[PurePosixPath, str] = {} 190 | 191 | def add_export_module(self, owner: str, export: str): 192 | owner_exports = self.export.setdefault(owner, set()) 193 | owner_exports.add(export) 194 | 195 | def add_module_action_ext_type(self, ext: str, type: ContentType): 196 | if not ext.startswith('.'): 197 | ext = '.' + ext 198 | if type in self._modules_ext_types_is_default: 199 | # first add replaces the default 200 | self._modules_ext_types_is_default.remove(type) 201 | for e in self.modules_ext_types: 202 | if self.modules_ext_types[e] == type: 203 | del self.modules_ext_types[e] 204 | break 205 | self.modules_ext_types[ext] = type 206 | 207 | def add_header_action_ext_type(self, ext: str, type: ContentType): 208 | if not ext.startswith('.'): 209 | ext = '.' + ext 210 | if type in self._headers_ext_types_is_default: 211 | # first add replaces the default 212 | self._headers_ext_types_is_default.remove(type) 213 | for e in self.headers_ext_types: 214 | if self.headers_ext_types[e] == type: 215 | del self.headers_ext_types[e] 216 | break 217 | self.headers_ext_types[ext] = type 218 | 219 | def set_output_content_type_to_ext(self, type: ContentType, ext: str): 220 | if not ext.startswith('.'): 221 | ext = '.' + ext 222 | assert(ext != '.') 223 | self.content_type_to_ext[type] = ext 224 | 225 | def add_modules_path(self, module_prefix: str, pathStr: str): 226 | path = PurePosixPath(pathStr) 227 | if path in self.path_to_module_prefix_map: 228 | print(f'warning: path {path} already mapped to module prefix {self.path_to_module_prefix_map[path]}') 229 | return 230 | self.path_to_module_prefix_map[path] = module_prefix 231 | 232 | def root_dir_module_name(self) -> str: 233 | return self.path_to_module_prefix_map.get(PurePosixPath(), '') 234 | 235 | def set_root_dir_module_name(self, prefix: str): 236 | self.add_modules_path(prefix, '') 237 | 238 | def add_std_module(self): 239 | for path in STD_MODULE_PATHS: 240 | self.add_modules_path(STD_MODULE, path) 241 | 242 | def add_std_compat_module(self): 243 | for path in STD_MODULE_PATHS: 244 | self.add_modules_path(STD_COMPAT_MODULE, path) 245 | 246 | class FileOptions: 247 | def __init__(self): 248 | self.convert_as_compat: bool = False 249 | 250 | def filename_to_module_name(filename: PurePosixPath, base_module: str|None) -> str: 251 | filenameStr, _ = os.path.splitext(filename) 252 | parts = list(PurePosixPath(filenameStr).parts) 253 | if base_module: 254 | parts.insert(0, base_module) 255 | result = '.'.join(parts) 256 | if result.startswith('.'): 257 | result = result[1:] 258 | return result 259 | 260 | class FileEntryType(enum.Enum): 261 | FILE = 1 262 | DIR = 2 263 | 264 | FilesMapDict: TypeAlias = dict[str, "FilesMapDict | FileEntryType"] 265 | 266 | class FilesMap: 267 | def __init__(self): 268 | self.value: FilesMapDict = {} 269 | 270 | def find(self, path: PurePosixPath) -> FilesMapDict | FileEntryType | None: 271 | value: FilesMapDict = self.value 272 | for part in path.parts: 273 | if not value: 274 | break 275 | nextValue = value.get(part, None) 276 | if type(nextValue) is not dict: 277 | return nextValue 278 | value = nextValue 279 | return value 280 | 281 | def add_filesystem_directory(self, path: Path): 282 | for (root, dirs, files) in os.walk(path): 283 | relative_root = Path(os.path.relpath(root, path)) 284 | if relative_root == Path(''): 285 | root_node: FilesMapDict = self.value 286 | else: 287 | parent_node = self.find(PurePosixPath(relative_root.parent)) 288 | assert(type(parent_node) is dict) 289 | root_node = parent_node[relative_root.name] = {} 290 | 291 | for name in dirs: 292 | root_node[name] = {} 293 | for name in files: 294 | root_node[name] = FileEntryType.FILE 295 | 296 | def add_files_map_dict(self, other: FilesMapDict): 297 | self.value.update(other) 298 | 299 | ActionExtTypes: TypeAlias = dict[ConvertAction, ExtTypes] 300 | EmptyPath = PurePosixPath('') 301 | 302 | class FilesResolver: 303 | def __init__(self, options: Options): 304 | self.options: Options = options 305 | self.files_map = FilesMap() 306 | self.source_ext_types: ActionExtTypes = { 307 | ConvertAction.MODULES: self.options.modules_ext_types, 308 | ConvertAction.HEADERS: self.options.headers_ext_types, 309 | } 310 | self.destination_ext_types: ActionExtTypes = { 311 | ConvertAction.HEADERS: self.options.modules_ext_types, 312 | ConvertAction.MODULES: self.options.headers_ext_types, 313 | } 314 | 315 | def resolve_in_search_path(self, current_dir: Path, current_filename: str|Path, include_filename: str, is_quote: bool) -> PurePosixPath | None: 316 | include_path = PurePosixPath(include_filename) 317 | # search relative to root 318 | if self.files_map.find(include_path): 319 | return include_path 320 | # search relative to current_dir 321 | full_path = PurePosixPath(current_dir.joinpath(include_path)) 322 | if is_quote and self.files_map.find(full_path): 323 | return full_path 324 | # search in search_path 325 | for search_path_item in self.options.search_path: 326 | path = PurePosixPath(search_path_item).joinpath(include_path) 327 | if self.files_map.find(path): 328 | return path 329 | if is_quote: 330 | print(f'warning: file not found: "{include_filename}" referenced from "{current_filename}"') 331 | return include_path 332 | return None 333 | 334 | def convert_filename_to_module_name(self, filename: PurePosixPath) -> str: 335 | base_module = None 336 | root_dir_module_name = self.options.root_dir_module_name() 337 | if root_dir_module_name and self.files_map.find(filename): 338 | base_module = root_dir_module_name 339 | module_name = filename_to_module_name(filename, base_module) 340 | return module_name 341 | 342 | def get_source_content_type(self, action: ConvertAction, filename: Path) -> ContentType: 343 | parts = os.path.splitext(filename) 344 | extension = parts[-1] 345 | action_ext_types = self.source_ext_types[action] 346 | return action_ext_types.get(extension, ContentType.OTHER) 347 | 348 | def convert_filename_to_content_type(self, filename: Path, content_type: ContentType) -> str: 349 | parts = os.path.splitext(filename) 350 | new_extension = self.options.content_type_to_ext[content_type] 351 | new_filename = parts[0] + new_extension 352 | return new_filename 353 | 354 | def make_defined_module_for_path(self, filename: PurePosixPath) -> str | None: 355 | inmodule_path = EmptyPath 356 | while True: 357 | module_prefix = self.options.path_to_module_prefix_map.get(filename, None) 358 | if module_prefix is not None: 359 | if filename == EmptyPath: 360 | return self.convert_filename_to_module_name(filename) 361 | else: 362 | return filename_to_module_name(inmodule_path, module_prefix) 363 | name = PurePosixPath(filename.name) 364 | if inmodule_path == EmptyPath: 365 | inmodule_path = PurePosixPath(filename_to_module_name(name, '')) 366 | else: 367 | inmodule_path = name.joinpath(inmodule_path) 368 | # go up 369 | filename = filename.parent 370 | if filename == EmptyPath: 371 | break 372 | return None 373 | 374 | class ModuleFilesResolver: 375 | def __init__(self, parent_resolver: FilesResolver, options: Options): 376 | self.parent_resolver: FilesResolver = parent_resolver 377 | self.options: Options = options 378 | self.module_filename: Path = Path() 379 | self.module_dir: Path = Path() 380 | 381 | def set_filename(self, filename: Path): 382 | self.module_filename = filename 383 | self.module_dir = self.module_filename.parent 384 | 385 | def resolve_include(self, include_filename: str, is_quote: bool) -> PurePosixPath | None: 386 | result = self.parent_resolver.resolve_in_search_path(self.module_dir, self.module_filename, include_filename, is_quote) 387 | return result 388 | 389 | def resolve_include_to_module_name(self, include_filename: str, is_quote: bool) -> str | None: 390 | resolved_include_filename = self.parent_resolver.resolve_in_search_path(self.module_dir, self.module_filename, include_filename, is_quote) 391 | if resolved_include_filename is None: 392 | result = self.parent_resolver.make_defined_module_for_path(PurePosixPath(include_filename)) 393 | return result 394 | result = self.parent_resolver.make_defined_module_for_path(resolved_include_filename) 395 | if result is not None: 396 | return result 397 | result = self.parent_resolver.convert_filename_to_module_name(resolved_include_filename) 398 | return result 399 | 400 | ContentTypeToName: TypeAlias = dict[ContentType, str] 401 | content_type_to_name: ContentTypeToName = { 402 | ContentType.HEADER: 'header', 403 | ContentType.CXX: 'source', 404 | ContentType.MODULE_INTERFACE: 'module interface unit', 405 | ContentType.MODULE_IMPL: 'module implementation unit', 406 | } 407 | 408 | ContentTypeToConverted: TypeAlias = dict[ContentType, ContentType] 409 | content_type_to_converted: ContentTypeToConverted = { 410 | ContentType.HEADER: ContentType.MODULE_INTERFACE, 411 | ContentType.CXX: ContentType.MODULE_IMPL, 412 | ContentType.MODULE_INTERFACE: ContentType.HEADER, 413 | ContentType.MODULE_IMPL: ContentType.CXX, 414 | } 415 | 416 | interface_content_types: set[ContentType] = {ContentType.MODULE_INTERFACE, ContentType.HEADER} 417 | 418 | def get_converted_content_type(content_type: ContentType) -> ContentType: 419 | return content_type_to_converted[content_type] 420 | 421 | class FileContent: 422 | def __init__(self, filename: str|Path, content_type: ContentType, content: str): 423 | self.filename: Path = Path(filename) 424 | self.content_type: ContentType = content_type 425 | self.content: str = content 426 | 427 | def __repr__(self): 428 | return str(self.__dict__) 429 | 430 | def __eq__(self, other: object): 431 | return self.__dict__ == other.__dict__ 432 | 433 | FileContentList: TypeAlias = list[FileContent] 434 | 435 | StrList: TypeAlias = list[str] 436 | new_line = '\n' 437 | 438 | class LineCompatibility(enum.Enum): 439 | GLOBAL_MODULE_FRAGMENT = 1 440 | MODULE_CONTENT = 2 441 | ANY = 3 442 | 443 | # regex: spaces only 444 | spaces_rx = re.compile(r'''^\s*$''') 445 | # regex: #include 446 | preprocessor_include_brackets_rx = re.compile(r'''^(\s*)#(\s*)include\s*<(.+)>(.*)$''') 447 | # regex: #include "quote_header.h" 448 | preprocessor_include_quote_rx = re.compile(r'''^(\s*)#(\s*)include\s*"(.+)"(.*)$''') 449 | # regex: line comment 450 | preprocessor_line_comment_rx = re.compile(r'''^\s*//.*$''') 451 | # regex: block comment start 452 | preprocessor_block_comment_rx = re.compile(r'''^\s*/\*.*$''') 453 | # regex: block comment end 454 | preprocessor_block_comment_rx = re.compile(r'''^\s*\*/.*$''') 455 | # regex: #if 456 | preprocessor_if_rx = re.compile(r'''^\s*#\s*if.*''') 457 | # regex: #endif 458 | preprocessor_endif_rx = re.compile(r'''^\s*#\s*endif.*$''') 459 | # regex: #define 460 | preprocessor_define_rx = re.compile(r'''^\s*#\s*define.*''') 461 | # regex: #error 462 | # regex: #elif 463 | # regex: #else 464 | # regex: #pragma 465 | # regex: #warning 466 | preprocessor_other_rx = re.compile(r'''^\s*#\s*(error|elif|else|pragma|warning).*''') 467 | # regex: #pragma once 468 | preprocessor_pragma_once_rx = re.compile(r'''^\s*#\s*pragma\s+once.*$''') 469 | 470 | class FileBaseBuilder: 471 | content_type: ContentType = ContentType.OTHER 472 | 473 | def __init__(self, options: Options, parent_resolver: FilesResolver): 474 | self.options: Options = options 475 | self.parent_resolver: FilesResolver = parent_resolver 476 | self.source_filename: Path = Path('') 477 | 478 | def set_source_filename(self, source_filename: Path): 479 | self.source_filename = source_filename 480 | 481 | def converted_filename(self) -> str: 482 | return self.parent_resolver.convert_filename_to_content_type(self.source_filename, self.content_type) 483 | 484 | def build_result(self) -> str: 485 | raise NotImplementedError('build_result') 486 | 487 | def build_file_content(self) -> FileContent: 488 | content = self.build_result() 489 | return FileContent(self.converted_filename(), self.content_type, content) 490 | 491 | class ModuleBaseBuilder(FileBaseBuilder): 492 | module_purview_start_prefix: str = '' # 'module' or 'export module' - overriden in implementation classes 493 | content_type: ContentType = ContentType.OTHER 494 | 495 | def __init__(self, options: Options, parent_resolver: FilesResolver, file_options: FileOptions): 496 | super().__init__(options, parent_resolver) 497 | self.file_options: FileOptions = file_options 498 | self.resolver: ModuleFilesResolver = ModuleFilesResolver(self.parent_resolver, self.options) 499 | self.module_name: str = '' # name of the module 500 | self.file_copyright: StrList = [] # // File copyright 501 | self.global_module_fragment_start: StrList = [] # module; 502 | self.global_module_fragment_compat_includes: StrList = [] # compat header includes 503 | self.global_module_fragment_compat_end: StrList = [] # compat header includes endif 504 | self.global_module_fragment: StrList = [] # header includes 505 | self.module_purview_start: StrList = [] # export module ; // Start of module purview. 506 | # self.module_imports: StrList = [] 507 | # self.module_purview_special_headers: StrList = [] # // Configuration, export, etc. 508 | self.module_content: StrList = [] 509 | self.main_module_content_index: int|None = None 510 | 511 | self.module_staging: StrList = [] # staging area for next module entry 512 | self.flushed_module_preprocessor_nesting_count: int = 0 # count of opened preprocessor #if statements flushed to module content 513 | self.global_module_fragment_staging: StrList = [] # staging area for next global_module_fragment entry 514 | self.preprocessor_nesting_count: int = 0 # count of opened preprocessor #if statements 515 | self.global_module_fragment_includes_count: int = 0 # count of #include <> statements 516 | self.flushed_global_module_fragment_includes_count: int = 0 # count of #include <> statements flushed to global module content 517 | self.module_staging_last_unnested_index: int = 0 # last index in module staging without opened preprocessor #if statements 518 | 519 | def set_source_filename(self, source_filename: Path): 520 | super().set_source_filename(source_filename) 521 | self.resolver.set_filename(source_filename) 522 | pure_source_filename = PurePosixPath(source_filename) 523 | module_name = self.parent_resolver.make_defined_module_for_path(pure_source_filename) 524 | if not module_name: 525 | module_name = self.parent_resolver.convert_filename_to_module_name(pure_source_filename) 526 | assert(module_name) 527 | self.set_module_name(module_name) 528 | 529 | def set_module_name(self, name: str): 530 | assert(not self.module_name) 531 | self.module_name = name 532 | 533 | def set_is_actually_module(self) -> None: 534 | if self.global_module_fragment: 535 | self.set_global_module_fragment_start() 536 | 537 | def get_is_actually_module(self) -> bool: 538 | raise NotImplementedError('get_is_actually_module') 539 | 540 | def get_module_interface_builder(self) -> ModuleInterfaceUnitBuilder | None: 541 | raise NotImplementedError('get_module_interface_builder') 542 | 543 | def set_global_module_fragment_start(self): 544 | if self.global_module_fragment_start: 545 | return 546 | if not self.get_is_actually_module(): 547 | return 548 | if self.convert_as_compat_header(): 549 | self.global_module_fragment_start = [ 550 | f'''#ifndef {self.options.compat_macro}''', 551 | f'''module;''', 552 | f'''#else''', 553 | f'''#pragma once''', 554 | ] 555 | self.global_module_fragment_compat_end = [ 556 | f'''#endif''', 557 | ] 558 | else: 559 | self.global_module_fragment_start = [ 560 | 'module;' 561 | ] 562 | 563 | def set_module_purview_start(self): 564 | if self.module_purview_start: 565 | return 566 | if not self.get_is_actually_module(): 567 | return 568 | assert(self.module_purview_start_prefix) 569 | assert(self.module_name) 570 | module_purview_start = f'''{self.module_purview_start_prefix} {self.module_name};''' 571 | self.module_purview_start = self.wrap_in_compat_macro_if_compat_header([ 572 | module_purview_start 573 | ]) 574 | 575 | def add_file_copyright(self, line: str): 576 | self.file_copyright.append(line) 577 | 578 | def _flush_global_module_fragment(self): 579 | self.flushed_global_module_fragment_includes_count = 0 580 | if self.preprocessor_nesting_count != 0: 581 | return 582 | if self.global_module_fragment_includes_count == 0 and not self.convert_as_compat_header(): 583 | return 584 | self.set_global_module_fragment_start() 585 | for line in self.global_module_fragment_staging: 586 | self.global_module_fragment.append(line) 587 | self.global_module_fragment_staging = [] 588 | self.flushed_global_module_fragment_includes_count = self.global_module_fragment_includes_count 589 | self.global_module_fragment_includes_count = 0 590 | self._flush_module_staging() 591 | 592 | def add_global_module_fragment(self, line: str): 593 | self.global_module_fragment_includes_count += 1 594 | self._add_global_module_fragment_staging(line, 0) 595 | self._flush_global_module_fragment() 596 | 597 | def handle_preprocessor(self, line: str, nesting_advance: int): 598 | self.add_staging(line, nesting_advance) 599 | 600 | def add_staging(self, line: str, nesting_advance: int): 601 | self.preprocessor_nesting_count += nesting_advance 602 | self._add_module_staging(line, nesting_advance) 603 | self._add_global_module_fragment_staging(line, nesting_advance) 604 | 605 | def _add_global_module_fragment_staging(self, line: str, nesting_advance: int): 606 | self.global_module_fragment_staging.append(line) 607 | self._flush_global_module_fragment() 608 | 609 | def add_module_purview_special_headers(self, line: str): 610 | self.set_module_purview_start() 611 | # self.module_purview_special_headers.append(line) 612 | 613 | def handle_main_content(self, line: str): 614 | self._flush_module_staging(self._set_main_module_content_start) 615 | self.add_module_content(line) 616 | 617 | def _set_main_module_content_start(self): 618 | if self.main_module_content_index is None: 619 | self.main_module_content_index = len(self.module_content) 620 | 621 | def _mark_module_interface_unit_export(self): 622 | if self.main_module_content_index is None: 623 | return 624 | module_start = [] 625 | module_end = [] 626 | if self.file_options.convert_as_compat: 627 | # wrap module content in extern "C++" {} 628 | module_start = [ 629 | '''extern "C++" {''' 630 | ] 631 | module_end = [ 632 | '''} // extern "C++"''' 633 | ] 634 | if self.content_type == ContentType.MODULE_INTERFACE: 635 | # wrap module interface unit in export {} 636 | module_start = [ 637 | '''export {''' 638 | ] + module_start 639 | module_end = module_end + [ 640 | '''} // export''' 641 | ] 642 | if self.file_options.convert_as_compat: 643 | # wrap in #ifdef CXX_COMPAT_HEADER/#endif 644 | module_start = self.wrap_in_compat_macro_if_compat_header(module_start) 645 | module_end = self.wrap_in_compat_macro_if_compat_header(module_end) 646 | for line in reversed(module_start): 647 | self.module_content.insert(self.main_module_content_index, line) 648 | self.module_content = self.module_content + module_end 649 | 650 | def add_module_content(self, line: str): 651 | self._flush_module_staging() 652 | self.set_module_purview_start() 653 | self.module_content.append(line) 654 | 655 | def _add_module_staging(self, line: str, nesting_advance: int): 656 | if (self.preprocessor_nesting_count == 0 and nesting_advance == 0 657 | or self.preprocessor_nesting_count == 1 and nesting_advance == 1): 658 | self.module_staging_last_unnested_index = len(self.module_staging) 659 | self.module_staging.append(line) 660 | if (self.preprocessor_nesting_count == 0 and nesting_advance == -1): 661 | self.module_staging_last_unnested_index = len(self.module_staging) 662 | 663 | def _flush_module_staging(self, last_unnested_inserter: Callable[[], None] | None = None): 664 | self.set_module_purview_start() 665 | if self.flushed_global_module_fragment_includes_count == 0 or self.flushed_module_preprocessor_nesting_count != 0: 666 | for i in range(len(self.module_staging)): 667 | if last_unnested_inserter and i == self.module_staging_last_unnested_index: 668 | last_unnested_inserter() 669 | line = self.module_staging[i] 670 | self.module_content.append(line) 671 | if last_unnested_inserter and self.module_staging_last_unnested_index == len(self.module_staging): 672 | last_unnested_inserter() 673 | self.module_staging = [] 674 | self.flushed_module_preprocessor_nesting_count = self.preprocessor_nesting_count 675 | self.flushed_global_module_fragment_includes_count = 0 676 | self.module_staging_last_unnested_index = 0 677 | 678 | def convert_as_compat_header(self): 679 | return self.file_options.convert_as_compat and self.content_type == ContentType.MODULE_INTERFACE 680 | 681 | def wrap_in_compat_macro_if_compat_header(self, lines: StrList): 682 | if self.convert_as_compat_header(): 683 | return [ 684 | f'''#ifndef {self.options.compat_macro}''', 685 | ] + lines + [ 686 | f'''#endif''', # end compat_macro 687 | ] 688 | else: 689 | return lines 690 | 691 | def handle_include_brackets(self, line: str, match: re.Match[str] | None = None): 692 | if self.convert_as_compat_header(): 693 | self.add_compat_include(line) 694 | self.add_module_import_from_include(line, match, False) 695 | # self.add_global_module_fragment(line) 696 | 697 | def handle_include_quote(self, line: str, match: re.Match[str] | None = None): 698 | if self.convert_as_compat_header(): 699 | self.add_compat_include(line) 700 | self.add_module_import_from_include(line, match, True) 701 | 702 | def handle_pragma_once(self, line: str): 703 | self._add_module_staging(f'''// {line}''', 0) 704 | 705 | def add_module_import_from_include(self, line: str, match: re.Match[str] | None = None, is_quote: bool = False): 706 | self.set_module_purview_start() 707 | if not match: 708 | match = preprocessor_include_quote_rx.match(line) 709 | if not match: 710 | print('warning: preprocessor_include_local_rx not matched') 711 | self.add_module_content(line) 712 | return 713 | 714 | line_space1 = match[1] 715 | line_space2 = match[1] 716 | line_include_filename = match[3] 717 | line_tail = match[4] 718 | 719 | resolved_include_filename = self.resolver.resolve_include(line_include_filename, is_quote) 720 | if resolved_include_filename is None and is_quote: 721 | self.add_global_module_fragment(line) 722 | return 723 | if any_pattern_maches(self.options.always_include_names, resolved_include_filename or PurePosixPath(line_include_filename)): 724 | self.add_global_module_fragment(line) 725 | return 726 | 727 | line_module_name = self.resolver.resolve_include_to_module_name(line_include_filename, is_quote) 728 | if line_module_name is None: 729 | self.add_global_module_fragment(line) 730 | return 731 | if line_module_name == self.module_name: 732 | self.set_is_actually_module() 733 | self.set_module_purview_start() 734 | return 735 | 736 | export_opt = '''export ''' if self._needs_export_for_import(line_module_name) else '' 737 | 738 | import_line = f'''{line_space1}{line_space2}{export_opt}import {line_module_name};{line_tail}''' 739 | import_lines = self.wrap_in_compat_macro_if_compat_header([import_line]) 740 | for import_line in import_lines: 741 | self.add_module_content(import_line) 742 | 743 | def _needs_export_for_import(self, import_module_name: str) -> bool: 744 | if self.content_type != ContentType.MODULE_INTERFACE: 745 | return False 746 | owner_exports = self.options.export.get(self.module_name) 747 | if owner_exports and (import_module_name in owner_exports or STAR_MODULE_EXPORT in owner_exports): 748 | return True 749 | star_owner_exports = self.options.export.get(STAR_MODULE_EXPORT) 750 | if star_owner_exports and (import_module_name in star_owner_exports or STAR_MODULE_EXPORT in star_owner_exports): 751 | return True 752 | for suffix in self.options.export_suffixes: 753 | if self.module_name + suffix == import_module_name: 754 | return True 755 | return False 756 | 757 | def add_compat_include(self, line: str): 758 | self.set_global_module_fragment_start() 759 | self.global_module_fragment_compat_includes.append(line) 760 | 761 | def build_result(self): 762 | if self.global_module_fragment or self.global_module_fragment_compat_includes: 763 | assert(bool(self.global_module_fragment_start) == self.get_is_actually_module()) 764 | module_interface_builder = self.get_module_interface_builder() 765 | if (self.content_type == ContentType.MODULE_IMPL and self.get_is_actually_module() 766 | and module_interface_builder and module_interface_builder.global_module_fragment): 767 | # include interface GMF at the start of impl GMF 768 | self.set_global_module_fragment_start() 769 | self.global_module_fragment = module_interface_builder.global_module_fragment + self.global_module_fragment 770 | self._flush_module_staging() 771 | self._mark_module_interface_unit_export() 772 | parts = [ 773 | new_line.join(self.file_copyright), 774 | new_line.join(self.global_module_fragment_start), 775 | new_line.join(self.global_module_fragment_compat_includes), 776 | new_line.join(self.global_module_fragment_compat_end), 777 | new_line.join(self.global_module_fragment), 778 | new_line.join(self.module_purview_start), 779 | # new_line.join(self.module_imports), 780 | # new_line.join(self.module_purview_special_headers), 781 | new_line.join(self.module_content), 782 | ] 783 | parts = filter(None, parts) # remove empty parts 784 | return new_line.join(parts) + new_line 785 | 786 | class ModuleInterfaceUnitBuilder(ModuleBaseBuilder): 787 | content_type = ContentType.MODULE_INTERFACE 788 | ''' 789 | // Module interface unit. 790 | 791 | // File copyright 792 | 793 | module; // Start of global module fragment. 794 | 795 |
796 | 797 | export module ; // Start of module purview. 798 | 799 | 800 | 801 | // Configuration, export, etc. 802 | 803 | 804 | 805 | 806 | ''' 807 | module_purview_start_prefix: str = 'export module' 808 | 809 | def set_is_actually_module(self) -> None: 810 | pass 811 | 812 | def get_is_actually_module(self) -> bool: 813 | '''module interface unit is always actually module''' 814 | return True 815 | 816 | def get_module_interface_builder(self) -> ModuleInterfaceUnitBuilder | None: 817 | return None 818 | 819 | class ModuleImplUnitBuilder(ModuleBaseBuilder): 820 | content_type = ContentType.MODULE_IMPL 821 | ''' 822 | // Module implementation unit. 823 | 824 | // File copyright 825 | 826 | module; // Start of global module fragment. 827 | 828 |
829 | 830 | module ; // Start of module purview. 831 | 832 | // Only additional to interface. 833 | 834 | 835 | ''' 836 | module_purview_start_prefix: str = 'module' 837 | 838 | def __init__(self, options: Options, parent_resolver: FilesResolver, file_options: FileOptions): 839 | super().__init__(options, parent_resolver, file_options) 840 | self._is_actually_module = False 841 | self.module_interface_builder: ModuleInterfaceUnitBuilder | None = None 842 | 843 | def set_is_actually_module(self) -> None: 844 | self._is_actually_module = True 845 | super().set_is_actually_module() 846 | 847 | def get_is_actually_module(self) -> bool: 848 | return self._is_actually_module 849 | 850 | def get_module_interface_builder(self) -> ModuleInterfaceUnitBuilder | None: 851 | return self.module_interface_builder 852 | 853 | def set_module_interface_builder(self, module_interface_builder: ModuleInterfaceUnitBuilder | None): 854 | self.module_interface_builder = module_interface_builder 855 | 856 | class CompatHeaderBuilder(FileBaseBuilder): 857 | content_type = ContentType.HEADER 858 | def __init__(self, options: Options, module_builder: ModuleBaseBuilder): 859 | super().__init__(options, module_builder.parent_resolver) 860 | self.module_builder: ModuleBaseBuilder = module_builder 861 | assert(module_builder.content_type == ContentType.MODULE_INTERFACE) 862 | module_interface_unit_filename: str = module_builder.converted_filename() 863 | assert(module_interface_unit_filename) 864 | self.relative_module_interface_unit_filename: str = os.path.basename(module_interface_unit_filename) 865 | 866 | def build_result(self) -> str: 867 | compat_macro = self.options.compat_macro 868 | assert(compat_macro) 869 | relative_module_interface_unit_filename = self.relative_module_interface_unit_filename 870 | parts = [ 871 | f'''#pragma once''', 872 | f'''#ifndef {compat_macro}''', 873 | f'''#define {compat_macro}''', 874 | f'''#include "{relative_module_interface_unit_filename}"''', 875 | f'''#undef {compat_macro}''', 876 | f'''#else''', 877 | f'''#include "{relative_module_interface_unit_filename}"''', 878 | f'''#endif''', 879 | ] 880 | return new_line.join(parts) + new_line 881 | 882 | class HeaderScanState(enum.Enum): 883 | START = enum.auto() 884 | FILE_COMMENT = enum.auto() 885 | MAIN = enum.auto() 886 | 887 | class Matcher: 888 | def __init__(self): 889 | self.rx: re.Pattern[str] | None = None 890 | self.matched: re.Match[str] | None = None 891 | def match(self, rx: re.Pattern[str], s: str) -> re.Match[str] | None: 892 | self.rx = rx 893 | self.matched = self.rx.match(s) 894 | return self.matched 895 | 896 | class Converter: 897 | 898 | def __init__(self, action: ConvertAction): 899 | self.action = action 900 | self.options = Options() 901 | self.resolver = FilesResolver(self.options) 902 | self.all_files = 0 903 | self.convertable_files = 0 904 | self.converted_files = 0 905 | self.copied_files = 0 906 | self.module_interface_builders: dict[str, ModuleInterfaceUnitBuilder] = {} 907 | 908 | def convert_file_content_to_module(self, content: str, filename: Path, content_type: ContentType, file_options: FileOptions) -> FileContentList: 909 | if content_type in {ContentType.MODULE_INTERFACE, ContentType.MODULE_IMPL}: 910 | return [FileContent(filename, content_type, content)] 911 | content_lines = content.splitlines() 912 | 913 | builder = self.make_builder_to_module(filename, content_type, file_options) 914 | 915 | is_comment = lambda: (not line 916 | or line2 == '//' 917 | or line2 == '/*' 918 | or line3 == '\ufeff//' 919 | or line3 == '\ufeff/*') 920 | 921 | scanState = HeaderScanState.START 922 | i = 0 923 | while i < len(content_lines): 924 | line = content_lines[i] 925 | while line and line[-1] == '\\' and i+1 < len(content_lines): 926 | # handle backslash \ as last character on line -- line continues on next line 927 | i += 1 928 | line += new_line + content_lines[i] 929 | line2 = line[0:2] 930 | line3 = line[0:3] 931 | if scanState == HeaderScanState.START: 932 | if is_comment(): 933 | scanState = HeaderScanState.FILE_COMMENT 934 | continue 935 | else: 936 | scanState = HeaderScanState.MAIN 937 | continue 938 | elif scanState == HeaderScanState.FILE_COMMENT: 939 | line2 = line.strip()[0:2] 940 | if (is_comment() 941 | or line2[0] == '*' 942 | ): 943 | builder.add_file_copyright(line) 944 | else: 945 | scanState = HeaderScanState.MAIN 946 | continue 947 | elif scanState == HeaderScanState.MAIN: 948 | m = Matcher() 949 | if m.match(preprocessor_include_brackets_rx, line): 950 | builder.handle_include_brackets(line, m.matched) 951 | elif m.match(preprocessor_include_quote_rx, line): 952 | builder.handle_include_quote(line, m.matched) 953 | elif m.match(preprocessor_pragma_once_rx, line): 954 | builder.handle_pragma_once(line) 955 | elif (m.match(preprocessor_line_comment_rx, line) 956 | or m.match(preprocessor_other_rx, line) 957 | or m.match(spaces_rx, line)): 958 | builder.handle_preprocessor(line, 0) 959 | elif (m.match(preprocessor_define_rx, line)): 960 | builder.handle_preprocessor(line, 0) 961 | elif m.match(preprocessor_if_rx, line): 962 | builder.handle_preprocessor(line, 1) 963 | elif m.match(preprocessor_endif_rx, line): 964 | builder.handle_preprocessor(line, -1) 965 | else: 966 | builder.handle_main_content(line) 967 | i += 1 968 | 969 | result: FileContentList = [] 970 | result.append(builder.build_file_content()) 971 | 972 | if file_options.convert_as_compat and builder.content_type == ContentType.MODULE_INTERFACE: 973 | compat_header_builder = CompatHeaderBuilder(self.options, builder) 974 | compat_header_builder.set_source_filename(Path(filename)) 975 | result.append(compat_header_builder.build_file_content()) 976 | 977 | return result 978 | 979 | def make_builder_to_module(self, filename: str|Path, content_type: ContentType, file_options: FileOptions|None = None) -> ModuleBaseBuilder: 980 | filename = Path(filename) 981 | if not file_options: 982 | file_options = FileOptions() 983 | if content_type == ContentType.HEADER: 984 | builder = ModuleInterfaceUnitBuilder(self.options, self.resolver, file_options) 985 | elif content_type == ContentType.CXX: 986 | builder = ModuleImplUnitBuilder(self.options, self.resolver, file_options) 987 | else: 988 | raise RuntimeError(f'Unknown content type {content_type}') 989 | 990 | builder.set_source_filename(filename) 991 | 992 | if content_type == ContentType.HEADER: 993 | self.module_interface_builders[builder.module_name] = cast(ModuleInterfaceUnitBuilder, builder) 994 | elif content_type == ContentType.CXX: 995 | cast(ModuleImplUnitBuilder, builder).set_module_interface_builder(self.module_interface_builders.get(builder.module_name, None)) 996 | return builder 997 | 998 | def convert_file_content_to_headers(self, content: str, filename: Path, content_type: ContentType, file_options: FileOptions) -> FileContentList: 999 | if content_type in {ContentType.HEADER, ContentType.CXX}: 1000 | return [FileContent(filename, content_type, content)] 1001 | return [FileContent(filename, content_type, content)] 1002 | 1003 | def convert_file_content(self, content: str, filename: str|Path, file_options: FileOptions|None = None) -> FileContentList: 1004 | filename = Path(filename) 1005 | if file_options is None: 1006 | file_options = FileOptions() 1007 | action = self.action 1008 | content_type = self.resolver.get_source_content_type(action, filename) 1009 | if action == ConvertAction.MODULES: 1010 | return self.convert_file_content_to_module(content, filename, content_type, file_options) 1011 | elif action == ConvertAction.HEADERS: 1012 | return self.convert_file_content_to_headers(content, filename, content_type, file_options) 1013 | else: 1014 | raise RuntimeError(f'Unknown action: "{action}"') 1015 | 1016 | def convert_file(self, source_directory: Path, destination_directory: Path, filename: Path, file_options: FileOptions) -> FileContentList: 1017 | self.convertable_files += 1 1018 | with open(source_directory.joinpath(filename)) as source_file: 1019 | source_content = source_file.read() 1020 | converted_files = self.convert_file_content(source_content, filename, file_options) 1021 | for converted_file in converted_files: 1022 | converted_content = converted_file.content 1023 | converted_filename = converted_file.filename 1024 | self._create_or_update_file_content_if_diff(destination_directory.joinpath(converted_filename), converted_content) 1025 | return converted_files 1026 | 1027 | def _create_or_update_file_content_if_diff(self, file_path: Path, content: str): 1028 | if file_path.exists(): 1029 | with open(file_path, 'r') as existing_destination_file: 1030 | existing_file_content = existing_destination_file.read() 1031 | if existing_file_content == content: 1032 | return 1033 | with open(file_path, 'w') as destination_file: 1034 | destination_file.write(content) 1035 | self.converted_files += 1 1036 | 1037 | def convert_or_copy_file(self, source_directory: Path, destination_directory: Path, filename: Path, file_options: FileOptions): 1038 | self.all_files += 1 1039 | content_type = self.resolver.get_source_content_type(self.action, filename) 1040 | if content_type == ContentType.OTHER or any_pattern_maches(self.options.always_include_names, PurePosixPath(filename)): 1041 | self._copy_file_content_if_diff(source_directory.joinpath(filename), destination_directory.joinpath(filename)) 1042 | else: 1043 | print('converting', filename) 1044 | converted_files = self.convert_file(source_directory, destination_directory, filename, file_options) 1045 | for converted_file in converted_files: 1046 | print('converted ', converted_file.filename, '\t', converted_file.content_type) 1047 | 1048 | def _copy_file_content_if_diff(self, source_file_path: Path, destination_file_path: Path): 1049 | with open(source_file_path, 'rb') as source_file: 1050 | source_file_content = source_file.read() 1051 | if destination_file_path.exists(): 1052 | with open(destination_file_path, 'rb') as existing_destination_file: 1053 | existing_file_content = existing_destination_file.read() 1054 | if existing_file_content == source_file_content: 1055 | return 1056 | shutil.copy2(source_file_path, destination_file_path) 1057 | self.copied_files += 1 1058 | 1059 | def convert_directory(self, source_directory: Path, destination_directory: Path): 1060 | if self.options.root_dir and self.options.root_dir != Path() and source_directory != self.options.root_dir: 1061 | self.add_filesystem_directory(self.options.root_dir) 1062 | self.convert_directory_impl(self.options.root_dir, destination_directory, source_directory.relative_to(self.options.root_dir), FileOptions()) 1063 | else: 1064 | self.add_filesystem_directory(source_directory) 1065 | self.convert_directory_impl(source_directory, destination_directory, Path(), FileOptions()) 1066 | 1067 | def add_filesystem_directory(self, directory: Path): 1068 | print('adding filesystem directory', directory) 1069 | self.resolver.files_map.add_filesystem_directory(directory) 1070 | 1071 | def convert_directory_impl(self, source_directory: Path, destination_directory: Path, subdir: Path, file_options: FileOptions): 1072 | source_directory_w_subdir = source_directory.joinpath(subdir or '') 1073 | destination_directory_w_subdir = destination_directory.joinpath(subdir or '') 1074 | destination_directory_w_subdir.mkdir(parents=True, exist_ok=True) 1075 | for filepath in sorted(source_directory_w_subdir.iterdir(), 1076 | key = lambda filepath: self.interface_then_impl_key(filepath)): 1077 | filename = filepath.relative_to(source_directory) 1078 | if any_pattern_maches(self.options.skip_patterns, PurePosixPath(filename)): 1079 | print(f'skipping "{filename}"') 1080 | continue 1081 | next_file_options = self.make_next_file_options(file_options, filename) 1082 | if filepath.is_file(): 1083 | self.convert_or_copy_file(source_directory, destination_directory, filename, next_file_options) 1084 | if filepath.is_dir(): 1085 | self.convert_directory_impl(source_directory, destination_directory, filename, next_file_options) 1086 | 1087 | def interface_then_impl_key(self, file_path: Path): 1088 | content_type = self.resolver.get_source_content_type(self.action, file_path) 1089 | if content_type in interface_content_types: 1090 | return 0 1091 | return 1 1092 | 1093 | 1094 | def make_next_file_options(self, file_options: FileOptions, filename: Path): 1095 | next_file_options = copy.copy(file_options) 1096 | if not file_options.convert_as_compat: 1097 | convert_as_compat = any_pattern_maches(self.options.compat_patterns, PurePosixPath(filename)) 1098 | if convert_as_compat: 1099 | next_file_options.convert_as_compat = convert_as_compat 1100 | return next_file_options 1101 | 1102 | def any_pattern_maches(patterns: list[str], filename: PurePosixPath) -> bool: 1103 | for skip_pattern in patterns: 1104 | if fnmatch.fnmatchcase(filename.as_posix(), skip_pattern): 1105 | return True 1106 | return False 1107 | 1108 | def convert_file_content(action: ConvertAction, content: str, filename: str) -> str: 1109 | converter = Converter(action) 1110 | file_content_list: FileContentList = converter.convert_file_content(content, filename, FileOptions()) 1111 | return file_content_list[0].content 1112 | 1113 | def convert_directory(action: ConvertAction, source_directory: Path, destination_directory: Path, subdir: str | None = None): 1114 | converter = Converter(action) 1115 | return converter.convert_directory(source_directory, destination_directory) 1116 | -------------------------------------------------------------------------------- /cxx_modules_converter_lib_test.py: -------------------------------------------------------------------------------- 1 | import os 2 | import os.path 3 | from pathlib import Path, PurePosixPath 4 | import pytest 5 | from .cxx_modules_converter_lib import ( 6 | Converter, 7 | convert_file_content, 8 | convert_directory, 9 | ConvertAction, 10 | ContentType, 11 | FilesMap, 12 | FileEntryType, 13 | Options, 14 | FileOptions, 15 | FilesResolver, 16 | ModuleFilesResolver, 17 | FileContent, 18 | ) 19 | 20 | def test_module_empty(): 21 | converted = convert_file_content( 22 | ConvertAction.MODULES, 23 | '''''', 'simple.h') 24 | assert(converted == '''export module simple; 25 | ''') 26 | 27 | def test_module_include_system(): 28 | converted = convert_file_content( 29 | ConvertAction.MODULES, 30 | '''#include 31 | ''', 'simple.h') 32 | assert(converted == 33 | '''module; 34 | #include 35 | export module simple; 36 | ''') 37 | 38 | def test_module_include_local(): 39 | converted = convert_file_content( 40 | ConvertAction.MODULES, 41 | '''#include "local_include.h" 42 | ''', 'simple.h') 43 | assert(converted == 44 | '''export module simple; 45 | import local_include; 46 | ''') 47 | 48 | def test_module_include_system_w_inline_comment(): 49 | converted = convert_file_content( 50 | ConvertAction.MODULES, 51 | '''#include // inline comment 52 | ''', 'simple.h') 53 | assert(converted == 54 | '''module; 55 | #include // inline comment 56 | export module simple; 57 | ''') 58 | 59 | def test_module_include_local_w_inline_comment(): 60 | converted = convert_file_content( 61 | ConvertAction.MODULES, 62 | '''#include "local_include.h" // inline comment 63 | ''', 'simple.h') 64 | assert(converted == 65 | '''export module simple; 66 | import local_include; // inline comment 67 | ''') 68 | 69 | def test_module_include_system_w_left_padding(): 70 | converted = convert_file_content( 71 | ConvertAction.MODULES, 72 | ''' # include 73 | ''', 'simple.h') 74 | assert(converted == 75 | '''module; 76 | # include 77 | export module simple; 78 | ''') 79 | 80 | def test_module_include_local_w_left_padding(): 81 | converted = convert_file_content( 82 | ConvertAction.MODULES, 83 | ''' # include "local_include.h" 84 | ''', 'simple.h') 85 | assert(converted == 86 | '''export module simple; 87 | import local_include; 88 | ''') 89 | 90 | def test_module_include_system_ifdef(): 91 | converted = convert_file_content( 92 | ConvertAction.MODULES, 93 | '''#include "local_include.h" 94 | #ifdef FLAG 95 | # include 96 | #endif // FLAG 97 | ''', 'simple.h') 98 | assert(converted == 99 | '''module; 100 | #ifdef FLAG 101 | # include 102 | #endif // FLAG 103 | export module simple; 104 | import local_include; 105 | ''') 106 | 107 | def test_module_include_system_ifdef_and_content(): 108 | converted = convert_file_content( 109 | ConvertAction.MODULES, 110 | '''#include "local_include.h" 111 | #ifdef FLAG 112 | # include 113 | #endif // FLAG 114 | namespace TestNS {} 115 | ''', 'simple.h') 116 | assert(converted == 117 | '''module; 118 | #ifdef FLAG 119 | # include 120 | #endif // FLAG 121 | export module simple; 122 | import local_include; 123 | export { 124 | namespace TestNS {} 125 | } // export 126 | ''') 127 | 128 | def test_module_include_local_ifdef(): 129 | converted = convert_file_content( 130 | ConvertAction.MODULES, 131 | '''#include "local_include.h" 132 | #ifdef FLAG 133 | # include "local_include_2.h" 134 | #endif // FLAG 135 | ''', 'simple.h') 136 | assert(converted == 137 | '''export module simple; 138 | import local_include; 139 | #ifdef FLAG 140 | import local_include_2; 141 | #endif // FLAG 142 | ''') 143 | 144 | def test_module_include_local_ifdef_and_content(): 145 | converted = convert_file_content( 146 | ConvertAction.MODULES, 147 | '''#include "local_include.h" 148 | #ifdef FLAG 149 | # include "local_include_2.h" 150 | #endif // FLAG 151 | namespace TestNS {} 152 | ''', 'simple.h') 153 | assert(converted == 154 | '''export module simple; 155 | import local_include; 156 | #ifdef FLAG 157 | import local_include_2; 158 | #endif // FLAG 159 | export { 160 | namespace TestNS {} 161 | } // export 162 | ''') 163 | 164 | def test_module_include_system_ifdef_and_content_w_newlines(): 165 | converted = convert_file_content( 166 | ConvertAction.MODULES, 167 | '''#include "local_include.h" 168 | 169 | #ifdef FLAG 170 | 171 | # include 172 | 173 | #endif // FLAG 174 | 175 | namespace TestNS {} 176 | ''', 'simple.h') 177 | assert(converted == 178 | '''module; 179 | 180 | #ifdef FLAG 181 | 182 | # include 183 | 184 | #endif // FLAG 185 | export module simple; 186 | import local_include; 187 | export { 188 | 189 | namespace TestNS {} 190 | } // export 191 | ''') 192 | 193 | def test_module_include_system_ifdef_twice(): 194 | converted = convert_file_content( 195 | ConvertAction.MODULES, 196 | '''#include "local_include.h" 197 | #ifdef FLAG1 198 | # include 199 | #endif // FLAG1 200 | #ifdef FLAG2 201 | # include 202 | #endif // FLAG2 203 | ''', 'simple.h') 204 | assert(converted == 205 | '''module; 206 | #ifdef FLAG1 207 | # include 208 | #endif // FLAG1 209 | #ifdef FLAG2 210 | # include 211 | #endif // FLAG2 212 | export module simple; 213 | import local_include; 214 | ''') 215 | 216 | def test_module_include_local_ifdef_twice(): 217 | converted = convert_file_content( 218 | ConvertAction.MODULES, 219 | '''#include "local_include.h" 220 | #ifdef FLAG1 221 | # include "local_include_2.h" 222 | #endif // FLAG1 223 | #ifdef FLAG2 224 | # include "local_include_3.h" 225 | #endif // FLAG2 226 | ''', 'simple.h') 227 | assert(converted == 228 | '''export module simple; 229 | import local_include; 230 | #ifdef FLAG1 231 | import local_include_2; 232 | #endif // FLAG1 233 | #ifdef FLAG2 234 | import local_include_3; 235 | #endif // FLAG2 236 | ''') 237 | 238 | def test_module_include_system_ifdef_elif_else(): 239 | converted = convert_file_content( 240 | ConvertAction.MODULES, 241 | '''#include "local_include.h" 242 | #ifdef FLAG1 243 | # include 244 | #elif FLAG2 245 | # include 246 | #else // FLAG2 247 | # include 248 | #endif // FLAG2 249 | ''', 'simple.h') 250 | assert(converted == 251 | '''module; 252 | #ifdef FLAG1 253 | # include 254 | #elif FLAG2 255 | # include 256 | #else // FLAG2 257 | # include 258 | #endif // FLAG2 259 | export module simple; 260 | import local_include; 261 | ''') 262 | 263 | def test_module_include_pragma_once(): 264 | converted = convert_file_content( 265 | ConvertAction.MODULES, 266 | '''#pragma once 267 | #include "local_include.h" 268 | #include 269 | ''', 'simple.h') 270 | assert(converted == 271 | '''module; 272 | #include 273 | export module simple; 274 | // #pragma once 275 | import local_include; 276 | ''') 277 | 278 | def test_module_include_system_pragma_define(): 279 | converted = convert_file_content( 280 | ConvertAction.MODULES, 281 | '''#include "local_include.h" 282 | #define FLAG 283 | #error "error" 284 | #pragma test 285 | #warning "warning" 286 | #include 287 | ''', 'simple.h') 288 | assert(converted == 289 | '''module; 290 | #define FLAG 291 | #error "error" 292 | #pragma test 293 | #warning "warning" 294 | #include 295 | export module simple; 296 | import local_include; 297 | ''') 298 | 299 | def test_module_include_local_and_system_multiline_define(): 300 | converted = convert_file_content( 301 | ConvertAction.MODULES, 302 | '''#include "local_include.h" 303 | #define FLAG \\ 304 | 1 \\ 305 | 2 306 | #include 307 | ''', 'simple.h') 308 | assert(converted == 309 | '''module; 310 | #define FLAG \\ 311 | 1 \\ 312 | 2 313 | #include 314 | export module simple; 315 | import local_include; 316 | ''') 317 | 318 | def test_module_include_system_and_local_in_one_ifdef(): 319 | converted = convert_file_content( 320 | ConvertAction.MODULES, 321 | '''#include "local_include.h" 322 | #ifdef FLAG 323 | # include 324 | # include "local_include_2.h" 325 | #endif // FLAG 326 | ''', 'simple.h') 327 | assert(converted == 328 | '''module; 329 | #ifdef FLAG 330 | # include 331 | #endif // FLAG 332 | export module simple; 333 | import local_include; 334 | #ifdef FLAG 335 | import local_include_2; 336 | #endif // FLAG 337 | ''') 338 | 339 | def test_module_include_system_and_local_in_two_ifdefs_and_comments(): 340 | converted = convert_file_content( 341 | ConvertAction.MODULES, 342 | '''#include "local_include.h" 343 | // comment flag1 344 | #ifdef FLAG1 345 | // comment system include 346 | # include 347 | #endif // FLAG1 348 | // comment flag2 349 | #ifdef FLAG2 350 | // comment local include 351 | # include "local_include_2.h" 352 | #endif // FLAG2 353 | ''', 'simple.h') 354 | assert(converted == 355 | '''module; 356 | // comment flag1 357 | #ifdef FLAG1 358 | // comment system include 359 | # include 360 | #endif // FLAG1 361 | export module simple; 362 | import local_include; 363 | // comment flag2 364 | #ifdef FLAG2 365 | // comment local include 366 | import local_include_2; 367 | #endif // FLAG2 368 | ''') 369 | 370 | def test_module_include_system_and_local_multiline_ifdef_elif(): 371 | converted = convert_file_content( 372 | ConvertAction.MODULES, 373 | '''#include "local_include.h" 374 | #ifdef FLAG1 \\ 375 | && FLAG11 376 | # include 377 | #elif FLAG1 \\ 378 | && FLAG11 379 | // nothing 380 | #endif // FLAG1 381 | #ifdef FLAG2 \\ 382 | && FLAG22 383 | # include "local_include_2.h" 384 | #endif // FLAG2 385 | ''', 'simple.h') 386 | assert(converted == 387 | '''module; 388 | #ifdef FLAG1 \\ 389 | && FLAG11 390 | # include 391 | #elif FLAG1 \\ 392 | && FLAG11 393 | // nothing 394 | #endif // FLAG1 395 | export module simple; 396 | import local_include; 397 | #ifdef FLAG2 \\ 398 | && FLAG22 399 | import local_include_2; 400 | #endif // FLAG2 401 | ''') 402 | 403 | 404 | def test_module_ifdef_w_content(): 405 | converted = convert_file_content( 406 | ConvertAction.MODULES, 407 | '''#include "local_include.h" 408 | #include 409 | 410 | // before preprocessor 411 | #ifdef FLAG1 412 | 413 | // inside preprocessor 414 | namespace TestNS 415 | { 416 | 417 | namespace Test 418 | { 419 | 420 | class TestClass 421 | { 422 | }; 423 | 424 | } // namespace Test 425 | } // namespace TestNS 426 | #endif // FLAG1 427 | ''', 'simple.h') 428 | assert(converted == 429 | '''module; 430 | #include 431 | export module simple; 432 | import local_include; 433 | 434 | // before preprocessor 435 | export { 436 | #ifdef FLAG1 437 | 438 | // inside preprocessor 439 | namespace TestNS 440 | { 441 | 442 | namespace Test 443 | { 444 | 445 | class TestClass 446 | { 447 | }; 448 | 449 | } // namespace Test 450 | } // namespace TestNS 451 | #endif // FLAG1 452 | } // export 453 | ''') 454 | 455 | def test_module_include_system_comment(): 456 | converted = convert_file_content( 457 | ConvertAction.MODULES, 458 | '''#include "local_include.h" 459 | // comment before system include 460 | #include 461 | ''', 'simple.h') 462 | assert(converted == 463 | '''module; 464 | // comment before system include 465 | #include 466 | export module simple; 467 | import local_include; 468 | ''') 469 | 470 | def test_module_include_local_comment(): 471 | converted = convert_file_content( 472 | ConvertAction.MODULES, 473 | '''#include "local_include.h" 474 | // comment before local include 475 | # include "local_include_2.h" 476 | ''', 'simple.h') 477 | assert(converted == 478 | '''export module simple; 479 | import local_include; 480 | // comment before local include 481 | import local_include_2; 482 | ''') 483 | 484 | def test_module_include_always_include_names_assert_h(): 485 | converted = convert_file_content( 486 | ConvertAction.MODULES, 487 | '''#include "assert.h" 488 | ''', 'simple.h') 489 | assert(converted == 490 | '''module; 491 | #include "assert.h" 492 | export module simple; 493 | ''') 494 | 495 | def test_module_include_always_include_names_options(): 496 | converter = Converter(ConvertAction.MODULES) 497 | converter.options.always_include_names.append('options.h') 498 | converted = converter.convert_file_content( 499 | '''#include "options.h" 500 | ''', 'simple.h') 501 | assert(converted[0].content == 502 | '''module; 503 | #include "options.h" 504 | export module simple; 505 | ''') 506 | 507 | def test_module_include_always_include_names_in_subdir(): 508 | converter = Converter(ConvertAction.MODULES) 509 | converter.options.always_include_names.append('subdir/options.h') 510 | converter.resolver.files_map.add_files_map_dict({ 511 | 'subdir': { 512 | 'options.h': FileEntryType.FILE, 513 | 'simple.h': FileEntryType.FILE, 514 | }, 515 | }) 516 | converted = converter.convert_file_content( 517 | '''#include "options.h" 518 | ''', 'subdir/simple.h') 519 | assert(converted[0].content == 520 | '''module; 521 | #include "options.h" 522 | export module subdir.simple; 523 | ''') 524 | 525 | def test_module_include_always_include_names_subdir(): 526 | converter = Converter(ConvertAction.MODULES) 527 | converter.options.always_include_names.append('subdir/*') 528 | converter.resolver.files_map.add_files_map_dict({ 529 | 'subdir': { 530 | 'options.h': FileEntryType.FILE, 531 | 'simple.h': FileEntryType.FILE, 532 | }, 533 | }) 534 | converted = converter.convert_file_content( 535 | '''#include "options.h" 536 | ''', 'subdir/simple.h') 537 | assert(converted[0].content == 538 | '''module; 539 | #include "options.h" 540 | export module subdir.simple; 541 | ''') 542 | 543 | def test_module_include_always_include_names_subdir_root_named(): 544 | converter = Converter(ConvertAction.MODULES) 545 | converter.options.always_include_names.append('subdir/options.h') 546 | converter.resolver.files_map.add_files_map_dict({ 547 | 'subdir': { 548 | 'options.h': FileEntryType.FILE, 549 | 'simple.h': FileEntryType.FILE, 550 | }, 551 | }) 552 | converter.options.set_root_dir_module_name('org') 553 | converted = converter.convert_file_content( 554 | '''#include "options.h" 555 | ''', 'subdir/simple.h') 556 | assert(converted[0].content == 557 | '''module; 558 | #include "options.h" 559 | export module org.subdir.simple; 560 | ''') 561 | 562 | def test_module_impl_include_local(): 563 | converted = convert_file_content( 564 | ConvertAction.MODULES, 565 | '''#include "local_include.h" 566 | ''', 'simple.cpp') 567 | assert(converted == 568 | '''import local_include; 569 | ''') 570 | 571 | def test_module_impl_include_system(): 572 | converted = convert_file_content( 573 | ConvertAction.MODULES, 574 | '''#include 575 | ''', 'simple.cpp') 576 | assert(converted == 577 | '''#include 578 | ''') 579 | 580 | def test_module_impl_include_self_header(): 581 | converted = convert_file_content( 582 | ConvertAction.MODULES, 583 | '''#include "simple.h" 584 | ''', 'simple.cpp') 585 | assert(converted == 586 | '''module simple; 587 | ''') 588 | 589 | def test_module_impl_include_self_header_and_system(): 590 | converted = convert_file_content( 591 | ConvertAction.MODULES, 592 | '''#include "simple.h" 593 | #include 594 | ''', 'simple.cpp') 595 | assert(converted == 596 | '''module; 597 | #include 598 | module simple; 599 | ''') 600 | 601 | def test_module_impl_include_self_header_and_system_and_local(): 602 | converted = convert_file_content( 603 | ConvertAction.MODULES, 604 | '''#include "simple.h" 605 | #include "local_include.h" 606 | #include 607 | ''', 'simple.cpp') 608 | assert(converted == 609 | '''module; 610 | #include 611 | module simple; 612 | import local_include; 613 | ''') 614 | 615 | def test_module_impl_include_self_header_and_system_and_local_and_assert_h(): 616 | converted = convert_file_content( 617 | ConvertAction.MODULES, 618 | '''#include "simple.h" 619 | #include "local_include.h" 620 | #include "assert.h" 621 | #include 622 | ''', 'simple.cpp') 623 | assert(converted == 624 | '''module; 625 | #include "assert.h" 626 | #include 627 | module simple; 628 | import local_include; 629 | ''') 630 | 631 | def test_module_impl_include_self_header_compat(): 632 | converter = Converter(ConvertAction.MODULES) 633 | file_options = FileOptions() 634 | file_options.convert_as_compat = True 635 | converted = converter.convert_file_content( 636 | '''#include "simple.h" 637 | ''', 'simple.cpp', file_options) 638 | assert(converted == [ 639 | FileContent("simple.cpp", ContentType.MODULE_IMPL, 640 | '''module simple; 641 | '''), 642 | ]) 643 | 644 | def test_module_impl_include_assert_and_self_header_compat(): 645 | converter = Converter(ConvertAction.MODULES) 646 | file_options = FileOptions() 647 | file_options.convert_as_compat = True 648 | converted = converter.convert_file_content( 649 | '''#include "assert.h" 650 | #include "simple.h" 651 | ''', 'simple.cpp', file_options) 652 | assert(converted == [ 653 | FileContent("simple.cpp", ContentType.MODULE_IMPL, 654 | '''module; 655 | #include "assert.h" 656 | module simple; 657 | '''), 658 | ]) 659 | 660 | def test_module_interface_export(): 661 | converter = Converter(ConvertAction.MODULES) 662 | converter.options.add_export_module('simple', 'simple_fwd') 663 | converted = converter.convert_file_content( 664 | '''#include "simple_fwd.h" 665 | #include "simple2.h" 666 | ''', 'simple.h') 667 | assert(converted[0].content == 668 | '''export module simple; 669 | export import simple_fwd; 670 | import simple2; 671 | ''') 672 | 673 | def test_module_impl_export(): 674 | converter = Converter(ConvertAction.MODULES) 675 | converter.options.add_export_module('simple', 'simple_fwd') 676 | converted = converter.convert_file_content( 677 | '''#include "simple.h" 678 | #include "simple_fwd.h" 679 | #include "simple2.h" 680 | ''', 'simple.cpp') 681 | assert(converted[0].content == 682 | '''module simple; 683 | import simple_fwd; 684 | import simple2; 685 | ''') 686 | 687 | def test_module_interface_export_A_star(): 688 | converter = Converter(ConvertAction.MODULES) 689 | converter.options.add_export_module('simple', '*') 690 | converted = converter.convert_file_content( 691 | '''#include "simple_fwd.h" 692 | #include "simple2.h" 693 | ''', 'simple.h') 694 | assert(converted[0].content == 695 | '''export module simple; 696 | export import simple_fwd; 697 | export import simple2; 698 | ''') 699 | 700 | def test_module_interface_export_star_B(): 701 | converter = Converter(ConvertAction.MODULES) 702 | converter.options.add_export_module('*', 'simple2') 703 | converted = converter.convert_file_content( 704 | '''#include "simple_fwd.h" 705 | #include "simple2.h" 706 | ''', 'simple.h') 707 | assert(converted[0].content == 708 | '''export module simple; 709 | import simple_fwd; 710 | export import simple2; 711 | ''') 712 | 713 | def test_module_interface_export_star_star(): 714 | converter = Converter(ConvertAction.MODULES) 715 | converter.options.add_export_module('*', '*') 716 | converted = converter.convert_file_content( 717 | '''#include "simple_fwd.h" 718 | #include "simple2.h" 719 | ''', 'simple.h') 720 | assert(converted[0].content == 721 | '''export module simple; 722 | export import simple_fwd; 723 | export import simple2; 724 | ''') 725 | 726 | def test_module_interface_export_suffix(): 727 | converter = Converter(ConvertAction.MODULES) 728 | converter.options.export_suffixes.append('_fwd') 729 | converted = converter.convert_file_content( 730 | '''#include "simple_fwd.h" 731 | #include "simple2.h" 732 | #include "simple2_fwd.h" 733 | ''', 'simple.h') 734 | assert(converted[0].content == 735 | '''export module simple; 736 | export import simple_fwd; 737 | import simple2; 738 | import simple2_fwd; 739 | ''') 740 | 741 | def test_module_impl_export_suffix(): 742 | converter = Converter(ConvertAction.MODULES) 743 | converter.options.export_suffixes.append('_fwd') 744 | converted = converter.convert_file_content( 745 | '''#include "simple.h" 746 | #include "simple_fwd.h" 747 | #include "simple2.h" 748 | ''', 'simple.cpp') 749 | assert(converted[0].content == 750 | '''module simple; 751 | import simple_fwd; 752 | import simple2; 753 | ''') 754 | 755 | def test_module_interface_export_suffix_named(): 756 | converter = Converter(ConvertAction.MODULES) 757 | converter.options.export_suffixes.append('_fwd') 758 | converter.options.set_root_dir_module_name('org') 759 | converter.resolver.files_map.add_files_map_dict({ 760 | 'simple.h': FileEntryType.FILE, 761 | 'simple_fwd.h': FileEntryType.FILE, 762 | 'simple2.h': FileEntryType.FILE, 763 | 'simple2_fwd.h': FileEntryType.FILE, 764 | }) 765 | converted = converter.convert_file_content( 766 | '''#include "simple_fwd.h" 767 | #include "simple2.h" 768 | #include "simple2_fwd.h" 769 | ''', 'simple.h') 770 | assert(converted[0].content == 771 | '''export module org.simple; 772 | export import org.simple_fwd; 773 | import org.simple2; 774 | import org.simple2_fwd; 775 | ''') 776 | 777 | def test_resolve_include(): 778 | converter = Converter(ConvertAction.MODULES) 779 | builder = converter.make_builder_to_module('subdir/simple.cpp', ContentType.CXX) 780 | resolver = builder.resolver 781 | assert(resolver.module_dir == PurePosixPath('subdir')) 782 | assert(resolver.resolve_include('simple.h', True) == PurePosixPath('simple.h')) 783 | assert(resolver.resolve_include('simple.h', False) is None) 784 | # make `simple.h` available in subdir 785 | resolver.parent_resolver.files_map.add_files_map_dict({ 786 | 'subdir': { 787 | 'simple.h': FileEntryType.FILE, 788 | }, 789 | }) 790 | assert(resolver.resolve_include('simple.h', True) == PurePosixPath('subdir/simple.h')) 791 | assert(resolver.resolve_include('simple.h', False) is None) 792 | # make `simple.h` available in root 793 | resolver.parent_resolver.files_map.add_files_map_dict({ 794 | 'simple.h': FileEntryType.FILE, 795 | }) 796 | assert(resolver.resolve_include('simple.h', False) == PurePosixPath('simple.h')) 797 | 798 | def test_resolve_include_no_dir(): 799 | converter = Converter(ConvertAction.MODULES) 800 | builder = converter.make_builder_to_module('simple.cpp', ContentType.CXX) 801 | resolver = builder.resolver 802 | assert(resolver.module_dir == PurePosixPath('')) 803 | assert(resolver.resolve_include('simple.h', True) == PurePosixPath('simple.h')) 804 | assert(resolver.resolve_include('simple.h', False) is None) 805 | # make `simple.h` available 806 | resolver.parent_resolver.files_map.add_files_map_dict({ 807 | 'simple.h': FileEntryType.FILE, 808 | }) 809 | assert(resolver.resolve_include('simple.h', False) == PurePosixPath('simple.h')) 810 | 811 | def test_resolve_include_to_module_name(): 812 | converter = Converter(ConvertAction.MODULES) 813 | builder = converter.make_builder_to_module('simple.cpp', ContentType.CXX) 814 | resolver = builder.resolver 815 | assert(resolver.resolve_include_to_module_name('simple.h', True) == 'simple') 816 | assert(resolver.resolve_include_to_module_name('simple.h', False) is None) 817 | assert(resolver.resolve_include_to_module_name('subdir/simple.h', True) == 'subdir.simple') 818 | assert(resolver.resolve_include_to_module_name('subdir/simple.h', False) is None) 819 | # make `simple.h` available 820 | resolver.parent_resolver.files_map.add_files_map_dict({ 821 | 'simple.h': FileEntryType.FILE, 822 | }) 823 | assert(resolver.resolve_include_to_module_name('simple.h', False) == 'simple') 824 | assert(resolver.resolve_include_to_module_name('subdir/simple.h', False) is None) 825 | # make `subdir/simple.h` available 826 | resolver.parent_resolver.files_map.add_files_map_dict({ 827 | 'subdir': { 828 | 'simple.h': FileEntryType.FILE, 829 | } 830 | }) 831 | assert(resolver.resolve_include_to_module_name('simple.h', False) == 'simple') 832 | assert(resolver.resolve_include_to_module_name('subdir/simple.h', False) == 'subdir.simple') 833 | 834 | def test_resolve_include_to_module_name_add_modules_path_subdir(): 835 | converter = Converter(ConvertAction.MODULES) 836 | builder = converter.make_builder_to_module('simple.cpp', ContentType.CXX) 837 | resolver = builder.resolver 838 | assert(resolver.resolve_include_to_module_name('subdir/simple.h', True) == 'subdir.simple') 839 | assert(resolver.resolve_include_to_module_name('subdir/simple.h', False) is None) 840 | resolver.options.add_modules_path('TestModule', 'subdir') 841 | assert(resolver.resolve_include_to_module_name('subdir/simple.h', True) == 'TestModule.simple') 842 | assert(resolver.resolve_include_to_module_name('subdir/simple.h', False) == 'TestModule.simple') 843 | assert(resolver.resolve_include_to_module_name('subdir/subdir2/simple.h', True) == 'TestModule.subdir2.simple') 844 | assert(resolver.resolve_include_to_module_name('subdir/subdir2/simple.h', False) == 'TestModule.subdir2.simple') 845 | 846 | def test_resolve_include_to_module_name_add_modules_path_subdir_level2(): 847 | converter = Converter(ConvertAction.MODULES) 848 | builder = converter.make_builder_to_module('simple.cpp', ContentType.CXX) 849 | resolver = builder.resolver 850 | assert(resolver.resolve_include_to_module_name('subdir/subdir2/simple.h', True) == 'subdir.subdir2.simple') 851 | assert(resolver.resolve_include_to_module_name('subdir/subdir2/simple.h', False) is None) 852 | resolver.options.add_modules_path('TestModule', 'subdir/subdir2') 853 | assert(resolver.resolve_include_to_module_name('subdir/subdir2/simple.h', True) == 'TestModule.simple') 854 | assert(resolver.resolve_include_to_module_name('subdir/subdir2/simple.h', False) == 'TestModule.simple') 855 | 856 | def test_resolve_include_to_module_name_add_modules_path_subdir_level2_in_subdir(): 857 | converter = Converter(ConvertAction.MODULES) 858 | builder = converter.make_builder_to_module('subdir/simple.cpp', ContentType.CXX) 859 | resolver = builder.resolver 860 | # make `simple.h` available in subdir 861 | resolver.parent_resolver.files_map.add_files_map_dict({ 862 | 'subdir': { 863 | 'subdir2': { 864 | 'simple.h': FileEntryType.FILE, 865 | }, 866 | }, 867 | }) 868 | assert(resolver.resolve_include_to_module_name('subdir2/simple.h', True) == 'subdir.subdir2.simple') 869 | assert(resolver.resolve_include_to_module_name('subdir2/simple.h', False) is None) 870 | resolver.options.add_modules_path('TestModule', 'subdir/subdir2') 871 | assert(resolver.resolve_include_to_module_name('subdir2/simple.h', True) == 'TestModule.simple') 872 | assert(resolver.resolve_include_to_module_name('subdir/subdir2/simple.h', True) == 'TestModule.simple') 873 | assert(resolver.resolve_include_to_module_name('subdir2/simple.h', False) == None) 874 | assert(resolver.resolve_include_to_module_name('subdir/subdir2/simple.h', False) == 'TestModule.simple') 875 | 876 | def test_resolve_include_to_module_name_std(): 877 | converter = Converter(ConvertAction.MODULES) 878 | builder = converter.make_builder_to_module('simple.cpp', ContentType.CXX) 879 | resolver = builder.resolver 880 | assert(resolver.resolve_include_to_module_name('vector', False) is None) 881 | resolver.options.add_std_module() 882 | assert(resolver.resolve_include_to_module_name('vector', False) == 'std') 883 | 884 | def test_resolve_include_to_module_name_std_compat(): 885 | converter = Converter(ConvertAction.MODULES) 886 | builder = converter.make_builder_to_module('simple.cpp', ContentType.CXX) 887 | resolver = builder.resolver 888 | assert(resolver.resolve_include_to_module_name('vector', False) is None) 889 | resolver.options.add_std_compat_module() 890 | assert(resolver.resolve_include_to_module_name('vector', False) == 'std.compat') 891 | 892 | def test_resolve_include_to_module_name_std_w_root_name(): 893 | converter = Converter(ConvertAction.MODULES) 894 | builder = converter.make_builder_to_module('simple.cpp', ContentType.CXX) 895 | resolver = builder.resolver 896 | assert(resolver.resolve_include_to_module_name('vector', False) is None) 897 | resolver.options.add_std_module() 898 | resolver.options.set_root_dir_module_name('TestModule') 899 | assert(resolver.resolve_include_to_module_name('vector', False) == 'std') 900 | 901 | def test_resolver_convert_filename_to_module_name(): 902 | converter = Converter(ConvertAction.MODULES) 903 | assert(converter.resolver.convert_filename_to_module_name(PurePosixPath('simple.cpp')) == 'simple') 904 | assert(converter.resolver.convert_filename_to_module_name(PurePosixPath('subdir/simple.cpp')) == 'subdir.simple') 905 | 906 | def test_FilesMap_add_filesystem_directory(): 907 | files_map = FilesMap() 908 | files_map.add_filesystem_directory(Path('test_data/subdirs/input')) 909 | assert(files_map.value == { 910 | 'simple1.h': FileEntryType.FILE, 911 | 'subdir1': { 912 | 'simple1.h': FileEntryType.FILE, 913 | 'simple2.h': FileEntryType.FILE, 914 | 'subdir2': { 915 | 'simple1.h': FileEntryType.FILE, 916 | 'simple2.h': FileEntryType.FILE, 917 | }, 918 | }, 919 | }) 920 | files_map.add_filesystem_directory(Path('test_data/other/input')) 921 | assert(files_map.value == { 922 | 'other.txt': FileEntryType.FILE, 923 | 'simple1.h': FileEntryType.FILE, 924 | 'subdir1': { 925 | 'simple1.h': FileEntryType.FILE, 926 | 'simple2.h': FileEntryType.FILE, 927 | 'subdir2': { 928 | 'simple1.h': FileEntryType.FILE, 929 | 'simple2.h': FileEntryType.FILE, 930 | }, 931 | }, 932 | }) 933 | 934 | def test_FilesMap_add_map(): 935 | files_map = FilesMap() 936 | files_map.add_files_map_dict({ 937 | 'simple1.h': FileEntryType.FILE, 938 | }) 939 | assert(files_map.value == { 940 | 'simple1.h': FileEntryType.FILE, 941 | }) 942 | files_map.add_files_map_dict({ 943 | 'subdir1': { 944 | 'simple2.h': FileEntryType.FILE, 945 | }, 946 | }) 947 | assert(files_map.value == { 948 | 'simple1.h': FileEntryType.FILE, 949 | 'subdir1': { 950 | 'simple2.h': FileEntryType.FILE, 951 | }, 952 | }) 953 | 954 | def test_FilesResolver_resolve_in_search_path_empty_map(): 955 | options = Options() 956 | files_resolver = FilesResolver(options) 957 | assert(files_resolver.resolve_in_search_path(Path(''), 'test', 'root.h', True) == PurePosixPath('root.h')) 958 | assert(files_resolver.resolve_in_search_path(Path(''), 'test', 'root.h', False) is None) 959 | assert(files_resolver.resolve_in_search_path(Path('subdir1'), 'test', 'simple1.h', True) == PurePosixPath('simple1.h')) 960 | assert(files_resolver.resolve_in_search_path(Path('subdir1'), 'test', 'simple1.h', False) is None) 961 | 962 | 963 | def test_ModuleFilesResolver_resolve_in_search_path(): 964 | options = Options() 965 | files_resolver = FilesResolver(options) 966 | modules_resolver = ModuleFilesResolver(files_resolver, options) 967 | files_map = files_resolver.files_map 968 | files_map.add_files_map_dict({ 969 | 'root.h': FileEntryType.FILE, 970 | 'subdir1': { 971 | 'simple1.h': FileEntryType.FILE, 972 | 'subdir2': { 973 | 'simple2.h': FileEntryType.FILE, 974 | }, 975 | }, 976 | 'dir2': { 977 | 'simple1.h': FileEntryType.FILE, 978 | 'subdir2': { 979 | 'simple2.h': FileEntryType.FILE, 980 | 'simple3.h': FileEntryType.FILE, 981 | }, 982 | }, 983 | }) 984 | modules_resolver.set_filename(Path('subdir1/test')) 985 | # check existing file from root 986 | assert(modules_resolver.resolve_include('subdir1/subdir2/simple2.h', True) == PurePosixPath('subdir1/subdir2/simple2.h')) 987 | assert(modules_resolver.resolve_include('subdir1/subdir2/simple2.h', False) == PurePosixPath('subdir1/subdir2/simple2.h')) 988 | # check existing file in relative subdir 989 | assert(modules_resolver.resolve_include('subdir2/simple2.h', True) == PurePosixPath('subdir1/subdir2/simple2.h')) 990 | assert(modules_resolver.resolve_include('subdir2/simple2.h', False) is None) 991 | # check missing file 992 | assert(modules_resolver.resolve_include('subdir2/simple3.h', True) == PurePosixPath('subdir2/simple3.h')) 993 | assert(modules_resolver.resolve_include('subdir2/simple3.h', False) is None) 994 | # add search path in another dir 995 | options.search_path.append('dir2') 996 | # check file existing in relative subdir and search path 997 | assert(modules_resolver.resolve_include('subdir2/simple2.h', True) == PurePosixPath('subdir1/subdir2/simple2.h')) 998 | assert(modules_resolver.resolve_include('subdir2/simple2.h', False) == PurePosixPath('dir2/subdir2/simple2.h')) 999 | # check file existing in search path but missing in relative subdir 1000 | assert(modules_resolver.resolve_include('subdir2/simple3.h', True) == PurePosixPath('dir2/subdir2/simple3.h')) 1001 | assert(modules_resolver.resolve_include('subdir2/simple3.h', False) == PurePosixPath('dir2/subdir2/simple3.h')) 1002 | # check file missing in relative subdir and search path 1003 | assert(modules_resolver.resolve_include('subdir2/simple4.h', True) == PurePosixPath('subdir2/simple4.h')) 1004 | assert(modules_resolver.resolve_include('subdir2/simple4.h', False) is None) 1005 | 1006 | def test_FilesResolver_convert_filename_to_module_name(): 1007 | options = Options() 1008 | files_resolver = FilesResolver(options) 1009 | files_map = files_resolver.files_map 1010 | files_map.add_files_map_dict({ 1011 | 'root.h': FileEntryType.FILE, 1012 | 'subdir1': { 1013 | 'simple1.h': FileEntryType.FILE, 1014 | 'subdir2': { 1015 | 'simple2.h': FileEntryType.FILE, 1016 | }, 1017 | }, 1018 | 'dir2': { 1019 | 'simple1.h': FileEntryType.FILE, 1020 | 'subdir2': { 1021 | 'simple2.h': FileEntryType.FILE, 1022 | 'simple3.h': FileEntryType.FILE, 1023 | }, 1024 | }, 1025 | }) 1026 | assert(files_resolver.convert_filename_to_module_name(PurePosixPath('root.h')) == 'root') 1027 | assert(files_resolver.convert_filename_to_module_name(PurePosixPath('subdir1/simple1.h')) == 'subdir1.simple1') 1028 | assert(files_resolver.convert_filename_to_module_name(PurePosixPath('missing.h')) == 'missing') 1029 | options.set_root_dir_module_name('org') 1030 | assert(files_resolver.convert_filename_to_module_name(PurePosixPath('root.h')) == 'org.root') 1031 | assert(files_resolver.convert_filename_to_module_name(PurePosixPath('subdir1/simple1.h')) == 'org.subdir1.simple1') 1032 | assert(files_resolver.convert_filename_to_module_name(PurePosixPath('missing.h')) == 'missing') 1033 | 1034 | def test_FilesResolver_get_source_content_type(): 1035 | options = Options() 1036 | files_resolver = FilesResolver(options) 1037 | assert(files_resolver.get_source_content_type(ConvertAction.MODULES, Path('test.h')) == ContentType.HEADER) 1038 | assert(files_resolver.get_source_content_type(ConvertAction.MODULES, Path('test.hpp')) == ContentType.OTHER) 1039 | assert(files_resolver.get_source_content_type(ConvertAction.MODULES, Path('test.cpp')) == ContentType.CXX) 1040 | assert(files_resolver.get_source_content_type(ConvertAction.MODULES, Path('test.cxx')) == ContentType.OTHER) 1041 | # first use -- replace default 1042 | options.add_module_action_ext_type(".hpp", ContentType.HEADER) 1043 | assert(files_resolver.get_source_content_type(ConvertAction.MODULES, Path('test.h')) == ContentType.OTHER) 1044 | assert(files_resolver.get_source_content_type(ConvertAction.MODULES, Path('test.hpp')) == ContentType.HEADER) 1045 | # subsequent use -- append (+ no dot) 1046 | options.add_module_action_ext_type("h", ContentType.HEADER) 1047 | assert(files_resolver.get_source_content_type(ConvertAction.MODULES, Path('test.h')) == ContentType.HEADER) 1048 | assert(files_resolver.get_source_content_type(ConvertAction.MODULES, Path('test.hpp')) == ContentType.HEADER) 1049 | assert(files_resolver.get_source_content_type(ConvertAction.MODULES, Path('test.cpp')) == ContentType.CXX) 1050 | assert(files_resolver.get_source_content_type(ConvertAction.MODULES, Path('test.cxx')) == ContentType.OTHER) 1051 | 1052 | # first use -- replace default 1053 | options.add_module_action_ext_type(".cxx", ContentType.CXX) 1054 | assert(files_resolver.get_source_content_type(ConvertAction.MODULES, Path('test.cpp')) == ContentType.OTHER) 1055 | assert(files_resolver.get_source_content_type(ConvertAction.MODULES, Path('test.cxx')) == ContentType.CXX) 1056 | # subsequent use -- append 1057 | options.add_module_action_ext_type(".cpp", ContentType.CXX) 1058 | assert(files_resolver.get_source_content_type(ConvertAction.MODULES, Path('test.cpp')) == ContentType.CXX) 1059 | assert(files_resolver.get_source_content_type(ConvertAction.MODULES, Path('test.cxx')) == ContentType.CXX) 1060 | assert(files_resolver.get_source_content_type(ConvertAction.MODULES, Path('test.h')) == ContentType.HEADER) 1061 | assert(files_resolver.get_source_content_type(ConvertAction.MODULES, Path('test.hpp')) == ContentType.HEADER) 1062 | 1063 | def test_FilesResolver_convert_filename_to_content_type(): 1064 | options = Options() 1065 | files_resolver = FilesResolver(options) 1066 | assert(files_resolver.convert_filename_to_content_type(Path('test.h'), ContentType.MODULE_INTERFACE) == 'test.cppm') 1067 | assert(files_resolver.convert_filename_to_content_type(Path('test.cpp'), ContentType.MODULE_IMPL) == 'test.cpp') 1068 | options.set_output_content_type_to_ext(ContentType.MODULE_INTERFACE, '.ixx') 1069 | options.set_output_content_type_to_ext(ContentType.MODULE_IMPL, 'cxx') 1070 | assert(files_resolver.convert_filename_to_content_type(Path('test.h'), ContentType.MODULE_INTERFACE) == 'test.ixx') 1071 | assert(files_resolver.convert_filename_to_content_type(Path('test.cpp'), ContentType.MODULE_IMPL) == 'test.cxx') 1072 | 1073 | def test_module_impl_include_local_self_header_subdir(): 1074 | converter = Converter(ConvertAction.MODULES) 1075 | converter.resolver.files_map.add_files_map_dict({ 1076 | 'subdir': { 1077 | 'local_include.h': FileEntryType.FILE, 1078 | 'simple.h': FileEntryType.FILE, 1079 | }, 1080 | }) 1081 | converted = converter.convert_file_content( 1082 | '''#include "simple.h" 1083 | #include "local_include.h" 1084 | ''', 'subdir/simple.cpp') 1085 | assert(converted[0].content == 1086 | '''module subdir.simple; 1087 | import subdir.local_include; 1088 | ''') 1089 | 1090 | def test_module_impl_include_local_self_header_subdir_prefix(): 1091 | converter = Converter(ConvertAction.MODULES) 1092 | converter.resolver.files_map.add_files_map_dict({ 1093 | 'prefix': { 1094 | 'subdir': { 1095 | 'local_include.h': FileEntryType.FILE, 1096 | 'simple.h': FileEntryType.FILE, 1097 | }, 1098 | }, 1099 | }) 1100 | converted = converter.convert_file_content( 1101 | '''#include "simple.h" 1102 | #include "local_include.h" 1103 | ''', 'prefix/subdir/simple.cpp') 1104 | assert(converted[0].content == 1105 | '''module prefix.subdir.simple; 1106 | import prefix.subdir.local_include; 1107 | ''') 1108 | 1109 | def test_module_include_local_and_system(): 1110 | converted = convert_file_content( 1111 | ConvertAction.MODULES, 1112 | '''#include "local_include.h" 1113 | #include 1114 | ''', 'simple.h') 1115 | assert(converted == 1116 | '''module; 1117 | #include 1118 | export module simple; 1119 | import local_include; 1120 | ''') 1121 | 1122 | def test_module_file_comment(): 1123 | converted = convert_file_content( 1124 | ConvertAction.MODULES, 1125 | '''// this is file comment 1126 | ''', 'simple.h') 1127 | assert(converted == '''// this is file comment 1128 | export module simple; 1129 | ''') 1130 | 1131 | def test_module_bom_and_file_comment(): 1132 | converted = convert_file_content( 1133 | ConvertAction.MODULES, 1134 | '''\ufeff// this is file comment 1135 | ''', 'simple.h') 1136 | assert(converted == 1137 | '''\ufeff// this is file comment 1138 | export module simple; 1139 | ''') 1140 | 1141 | def test_module_file_comment_and_include_system(): 1142 | converted = convert_file_content( 1143 | ConvertAction.MODULES, 1144 | '''// this is file comment 1145 | #include 1146 | ''', 'simple.h') 1147 | assert(converted == '''// this is file comment 1148 | module; 1149 | #include 1150 | export module simple; 1151 | ''') 1152 | 1153 | def test_module_file_comment_and_include_local_and_system(): 1154 | converted = convert_file_content( 1155 | ConvertAction.MODULES, 1156 | '''// this is file comment 1157 | #include "local_include.h" 1158 | #include 1159 | ''', 'simple.h') 1160 | assert(converted == 1161 | '''// this is file comment 1162 | module; 1163 | #include 1164 | export module simple; 1165 | import local_include; 1166 | ''') 1167 | 1168 | def test_module_file_comment_and_include_system_w_newlines(): 1169 | converted = convert_file_content( 1170 | ConvertAction.MODULES, 1171 | ''' 1172 | // this is file comment 1173 | 1174 | #include 1175 | 1176 | ''', 'simple.h') 1177 | assert(converted == 1178 | ''' 1179 | // this is file comment 1180 | 1181 | module; 1182 | #include 1183 | export module simple; 1184 | ''') 1185 | 1186 | def test_module_file_comment_and_include_local_and_system_w_newlines(): 1187 | converted = convert_file_content( 1188 | ConvertAction.MODULES, 1189 | ''' 1190 | // this is file comment 1191 | 1192 | #include "local_include.h" 1193 | 1194 | #include 1195 | ''', 'simple.h') 1196 | assert(converted == 1197 | ''' 1198 | // this is file comment 1199 | 1200 | module; 1201 | 1202 | #include 1203 | export module simple; 1204 | import local_include; 1205 | ''') 1206 | 1207 | def test_module_file_comment_and_include_local_and_system_w_content(): 1208 | converted = convert_file_content( 1209 | ConvertAction.MODULES, 1210 | ''' 1211 | // this is file comment 1212 | 1213 | #include "local_include.h" 1214 | 1215 | #include 1216 | 1217 | namespace TestNS 1218 | { 1219 | namespace Test 1220 | { 1221 | class TestClass 1222 | { 1223 | }; 1224 | } // namespace Test 1225 | } // namespace TestNS 1226 | 1227 | ''', 'simple.h') 1228 | assert(converted == 1229 | ''' 1230 | // this is file comment 1231 | 1232 | module; 1233 | 1234 | #include 1235 | export module simple; 1236 | import local_include; 1237 | export { 1238 | 1239 | namespace TestNS 1240 | { 1241 | namespace Test 1242 | { 1243 | class TestClass 1244 | { 1245 | }; 1246 | } // namespace Test 1247 | } // namespace TestNS 1248 | 1249 | } // export 1250 | ''') 1251 | 1252 | def test_module_interface_compat_header_empty(): 1253 | converter = Converter(ConvertAction.MODULES) 1254 | file_options = FileOptions() 1255 | file_options.convert_as_compat = True 1256 | converted = converter.convert_file_content( 1257 | '''''', 'empty.h', file_options) 1258 | assert(converted == [ 1259 | FileContent("empty.cppm", ContentType.MODULE_INTERFACE, 1260 | '''#ifndef CXX_COMPAT_HEADER 1261 | export module empty; 1262 | #endif 1263 | '''), 1264 | FileContent("empty.h", ContentType.HEADER, 1265 | '''#pragma once 1266 | #ifndef CXX_COMPAT_HEADER 1267 | #define CXX_COMPAT_HEADER 1268 | #include "empty.cppm" 1269 | #undef CXX_COMPAT_HEADER 1270 | #else 1271 | #include "empty.cppm" 1272 | #endif 1273 | '''), 1274 | ]) 1275 | 1276 | def test_module_interface_compat_header(): 1277 | converter = Converter(ConvertAction.MODULES) 1278 | file_options = FileOptions() 1279 | file_options.convert_as_compat = True 1280 | converted = converter.convert_file_content( 1281 | '''#include "local_include.h" 1282 | ''', 'simple.h', file_options) 1283 | assert(converted == [ 1284 | FileContent("simple.cppm", ContentType.MODULE_INTERFACE, 1285 | '''#ifndef CXX_COMPAT_HEADER 1286 | module; 1287 | #else 1288 | #pragma once 1289 | #include "local_include.h" 1290 | #endif 1291 | #ifndef CXX_COMPAT_HEADER 1292 | export module simple; 1293 | #endif 1294 | #ifndef CXX_COMPAT_HEADER 1295 | import local_include; 1296 | #endif 1297 | '''), 1298 | FileContent("simple.h", ContentType.HEADER, 1299 | '''#pragma once 1300 | #ifndef CXX_COMPAT_HEADER 1301 | #define CXX_COMPAT_HEADER 1302 | #include "simple.cppm" 1303 | #undef CXX_COMPAT_HEADER 1304 | #else 1305 | #include "simple.cppm" 1306 | #endif 1307 | '''), 1308 | ]) 1309 | 1310 | def test_module_interface_compat_header_w_system_includes(): 1311 | converter = Converter(ConvertAction.MODULES) 1312 | file_options = FileOptions() 1313 | file_options.convert_as_compat = True 1314 | converted = converter.convert_file_content( 1315 | '''#include "local_include.h" 1316 | #include 1317 | ''', 'simple.h', file_options) 1318 | assert(converted == [ 1319 | FileContent("simple.cppm", ContentType.MODULE_INTERFACE, 1320 | '''#ifndef CXX_COMPAT_HEADER 1321 | module; 1322 | #else 1323 | #pragma once 1324 | #include "local_include.h" 1325 | #include 1326 | #endif 1327 | #include 1328 | #ifndef CXX_COMPAT_HEADER 1329 | export module simple; 1330 | #endif 1331 | #ifndef CXX_COMPAT_HEADER 1332 | import local_include; 1333 | #endif 1334 | '''), 1335 | FileContent("simple.h", ContentType.HEADER, 1336 | '''#pragma once 1337 | #ifndef CXX_COMPAT_HEADER 1338 | #define CXX_COMPAT_HEADER 1339 | #include "simple.cppm" 1340 | #undef CXX_COMPAT_HEADER 1341 | #else 1342 | #include "simple.cppm" 1343 | #endif 1344 | '''), 1345 | ]) 1346 | 1347 | def test_module_impl_compat(): 1348 | converter = Converter(ConvertAction.MODULES) 1349 | file_options = FileOptions() 1350 | file_options.convert_as_compat = True 1351 | converted = converter.convert_file_content( 1352 | '''#include "local_include.h" 1353 | ''', 'simple.cpp', file_options) 1354 | assert(converted == [ 1355 | FileContent("simple.cpp", ContentType.MODULE_IMPL, 1356 | '''import local_include; 1357 | '''), 1358 | ]) 1359 | 1360 | 1361 | @pytest.fixture(scope="function") 1362 | def dir_simple(tmp_path_factory: pytest.TempPathFactory): 1363 | path = tmp_path_factory.mktemp("simple") 1364 | return path 1365 | 1366 | def assert_files(expected_dir: Path, result_dir: Path, expected_files: list[str]): 1367 | result_files: set[str] = set() 1368 | for (root, _, files) in os.walk(result_dir): 1369 | relative_root = Path(os.path.relpath(root, result_dir)) 1370 | for name in files: 1371 | result_files.add(relative_root.joinpath(name).as_posix()) 1372 | assert(result_files == set(expected_files)) 1373 | 1374 | for filename in expected_files: 1375 | result_path = result_dir.joinpath(filename) 1376 | print('assert_files filename:', filename) 1377 | assert os.path.exists(result_path) 1378 | with open(expected_dir.joinpath(filename)) as expected_file: 1379 | expected_content = expected_file.read() 1380 | with open(result_dir.joinpath(filename)) as result_file: 1381 | result_content = result_file.read() 1382 | assert(result_content == expected_content) 1383 | 1384 | def test_dir_simple(dir_simple: Path): 1385 | data_directory = Path('test_data/simple') 1386 | convert_directory(ConvertAction.MODULES, data_directory.joinpath('input'), dir_simple) 1387 | assert_files(data_directory.joinpath('expected'), dir_simple, [ 1388 | 'simple.cppm', 1389 | 'simple.cpp', 1390 | ]) 1391 | 1392 | def test_dir_simple_std(dir_simple: Path): 1393 | data_directory = Path('test_data/simple_std') 1394 | converter = Converter(ConvertAction.MODULES) 1395 | converter.options.add_std_module() 1396 | converter.convert_directory(data_directory.joinpath('input'), dir_simple) 1397 | assert_files(data_directory.joinpath('expected'), dir_simple, [ 1398 | 'simple.cppm', 1399 | 'simple.cpp', 1400 | ]) 1401 | 1402 | def test_dir_named1(dir_simple: Path): 1403 | data_directory = Path('test_data/named1') 1404 | converter = Converter(ConvertAction.MODULES) 1405 | converter.options.set_root_dir_module_name('org') 1406 | converter.convert_directory(data_directory.joinpath('input'), dir_simple) 1407 | # convert_directory(ConvertAction.MODULES, data_directory.joinpath('input'), dir_simple) 1408 | assert_files(data_directory.joinpath('expected'), dir_simple, [ 1409 | 'simple.cppm', 1410 | 'simple.cpp', 1411 | ]) 1412 | 1413 | def test_dir_prefix(dir_simple: Path): 1414 | data_directory = Path('test_data/prefix') 1415 | converter = Converter(ConvertAction.MODULES) 1416 | input_dir = data_directory.joinpath('input') 1417 | converter.options.root_dir = input_dir 1418 | converter.convert_directory(input_dir.joinpath('subdir'), dir_simple) 1419 | assert_files(data_directory.joinpath('expected'), dir_simple, [ 1420 | 'subdir/simple.cppm', 1421 | 'subdir/simple.cpp', 1422 | ]) 1423 | 1424 | def test_dir_prefix_named(dir_simple: Path): 1425 | data_directory = Path('test_data/prefix_named') 1426 | converter = Converter(ConvertAction.MODULES) 1427 | input_dir = data_directory.joinpath('input') 1428 | converter.options.root_dir = input_dir 1429 | converter.options.set_root_dir_module_name('org') 1430 | converter.convert_directory(input_dir.joinpath('subdir'), dir_simple) 1431 | assert_files(data_directory.joinpath('expected'), dir_simple, [ 1432 | 'subdir/local_include.cppm', 1433 | 'subdir/simple.cppm', 1434 | 'subdir/simple.cpp', 1435 | ]) 1436 | 1437 | def test_dir_modules_path(dir_simple: Path): 1438 | data_directory = Path('test_data/modules_path') 1439 | converter = Converter(ConvertAction.MODULES) 1440 | input_dir = data_directory.joinpath('input') 1441 | converter.options.root_dir = input_dir 1442 | converter.options.add_modules_path('org', '') # same as `.set_root_dir_module_name('org')` 1443 | converter.options.add_modules_path('org2', 'dir2') 1444 | converter.convert_directory(input_dir, dir_simple) 1445 | assert_files(data_directory.joinpath('expected'), dir_simple, [ 1446 | 'subdir/local_include.cppm', 1447 | 'subdir/simple.cppm', 1448 | 'subdir/simple.cpp', 1449 | 'dir2/local_include.cppm', 1450 | 'dir2/simple.cppm', 1451 | 'dir2/simple.cpp', 1452 | ]) 1453 | 1454 | def test_dir_compat(dir_simple: Path): 1455 | data_directory = Path('test_data/compat') 1456 | converter = Converter(ConvertAction.MODULES) 1457 | converter.options.compat_patterns = [ 1458 | 'simple.h', 1459 | 'simple.cpp', 1460 | 'subdir/*', 1461 | ] 1462 | converter.convert_directory(data_directory.joinpath('input'), dir_simple) 1463 | assert_files(data_directory.joinpath('expected'), dir_simple, [ 1464 | 'simple.h', 1465 | 'simple.cppm', 1466 | 'simple.cpp', 1467 | 'subdir/simple2.h', 1468 | 'subdir/simple2.cppm', 1469 | 'subdir/simple2.cpp', 1470 | ]) 1471 | 1472 | def test_dir_other(dir_simple: Path): 1473 | data_directory = Path('test_data/other') 1474 | convert_directory(ConvertAction.MODULES, data_directory.joinpath('input'), dir_simple) 1475 | assert_files(data_directory.joinpath('expected'), dir_simple, [ 1476 | 'other.txt', 1477 | ]) 1478 | 1479 | def test_dir_two(dir_simple: Path): 1480 | data_directory = Path('test_data/two') 1481 | convert_directory(ConvertAction.MODULES, data_directory.joinpath('input'), dir_simple) 1482 | assert_files(data_directory.joinpath('expected'), dir_simple, [ 1483 | 'simple1.cppm', 1484 | 'simple2.cppm', 1485 | ]) 1486 | 1487 | def test_dir_subdir(dir_simple: Path): 1488 | data_directory = Path('test_data/subdir') 1489 | convert_directory(ConvertAction.MODULES, data_directory.joinpath('input'), dir_simple) 1490 | assert_files(data_directory.joinpath('expected'), dir_simple, [ 1491 | 'subdir1/simple1.cppm', 1492 | 'subdir1/simple2.cppm', 1493 | ]) 1494 | 1495 | def test_dir_skip(dir_simple: Path): 1496 | data_directory = Path('test_data/skip') 1497 | converter = Converter(ConvertAction.MODULES) 1498 | converter.options.skip_patterns = [ 1499 | 'skipdir', 1500 | 'subdir1/simple2.h', 1501 | 'subdir1/skipsubdir', 1502 | ] 1503 | converter.convert_directory(data_directory.joinpath('input'), dir_simple) 1504 | assert_files(data_directory.joinpath('expected'), dir_simple, [ 1505 | 'subdir1/simple1.cppm', 1506 | ]) 1507 | 1508 | def test_dir_subdirs(dir_simple: Path): 1509 | data_directory = Path('test_data/subdirs') 1510 | convert_directory(ConvertAction.MODULES, data_directory.joinpath('input'), dir_simple) 1511 | assert_files(data_directory.joinpath('expected'), dir_simple, [ 1512 | 'simple1.cppm', 1513 | 'subdir1/simple1.cppm', 1514 | 'subdir1/simple2.cppm', 1515 | 'subdir1/subdir2/simple1.cppm', 1516 | 'subdir1/subdir2/simple2.cppm', 1517 | ]) 1518 | 1519 | def test_dir_subdirs_rooted(dir_simple: Path): 1520 | data_directory = Path('test_data/subdirs_rooted') 1521 | converter = Converter(ConvertAction.MODULES) 1522 | input_dir = data_directory.joinpath('input') 1523 | converter.options.root_dir = input_dir 1524 | converter.convert_directory(input_dir.joinpath('subdir'), dir_simple) 1525 | assert_files(data_directory.joinpath('expected'), dir_simple, [ 1526 | 'subdir/simple1.cppm', 1527 | 'subdir/subdir1/simple1.cppm', 1528 | 'subdir/subdir1/simple2.cppm', 1529 | 'subdir/subdir1/use_relative_include.cppm', 1530 | 'subdir/subdir1/use_relative_include_missing.cppm', 1531 | 'subdir/subdir1/use_search_path_include_existing.cppm', 1532 | 'subdir/subdir1/subdir2/simple1.cppm', 1533 | 'subdir/subdir1/subdir2/simple1.cpp', 1534 | 'subdir/subdir1/subdir2/simple2.cppm', 1535 | ]) 1536 | 1537 | def test_dir_subdirs_rooted_brackets(dir_simple: Path): 1538 | data_directory = Path('test_data/subdirs_rooted_brackets') 1539 | converter = Converter(ConvertAction.MODULES) 1540 | input_dir = data_directory.joinpath('input') 1541 | converter.options.root_dir = input_dir 1542 | converter.convert_directory(input_dir.joinpath('subdir'), dir_simple) 1543 | assert_files(data_directory.joinpath('expected'), dir_simple, [ 1544 | 'subdir/simple1.cppm', 1545 | 'subdir/subdir1/simple1.cppm', 1546 | 'subdir/subdir1/simple2.cppm', 1547 | 'subdir/subdir1/use_relative_include.cppm', 1548 | 'subdir/subdir1/use_relative_include_missing.cppm', 1549 | 'subdir/subdir1/use_search_path_include_existing.cppm', 1550 | 'subdir/subdir1/subdir2/simple1.cppm', 1551 | 'subdir/subdir1/subdir2/simple1.cpp', 1552 | 'subdir/subdir1/subdir2/simple2.cppm', 1553 | ]) 1554 | 1555 | def test_dir_header(dir_simple: Path): 1556 | data_directory = Path('test_data/header') 1557 | converter = Converter(ConvertAction.MODULES) 1558 | converter.options.always_include_names.append('simple.h') 1559 | converter.convert_directory(data_directory.joinpath('input'), dir_simple) 1560 | assert_files(data_directory.joinpath('expected'), dir_simple, [ 1561 | 'simple.h', 1562 | 'simple.cpp', 1563 | ]) 1564 | 1565 | def test_dir_header_subdir(dir_simple: Path): 1566 | data_directory = Path('test_data/header_subdir') 1567 | converter = Converter(ConvertAction.MODULES) 1568 | converter.options.always_include_names.append('subdir/**') 1569 | converter.convert_directory(data_directory.joinpath('input'), dir_simple) 1570 | assert_files(data_directory.joinpath('expected'), dir_simple, [ 1571 | 'subdir/simple.h', 1572 | 'subdir/simple.cpp', 1573 | ]) 1574 | 1575 | def test_dir_header_subdir_nested(dir_simple: Path): 1576 | data_directory = Path('test_data/header_subdir_nested') 1577 | converter = Converter(ConvertAction.MODULES) 1578 | converter.options.always_include_names.append('subdir/**') 1579 | converter.convert_directory(data_directory.joinpath('input'), dir_simple) 1580 | assert_files(data_directory.joinpath('expected'), dir_simple, [ 1581 | 'subdir/nested/simple.h', 1582 | 'subdir/nested/simple.cpp', 1583 | ]) 1584 | 1585 | def test_dir_twice(dir_simple: Path): 1586 | data_directory = Path('test_data/twice') 1587 | converter = Converter(ConvertAction.MODULES) 1588 | converter.convert_directory(data_directory.joinpath('input'), dir_simple) 1589 | assert_files(data_directory.joinpath('expected'), dir_simple, [ 1590 | 'simple.cppm', 1591 | 'simple.cpp', 1592 | 'other.txt', 1593 | ]) 1594 | assert(converter.all_files == 3) 1595 | assert(converter.convertable_files == 2) 1596 | assert(converter.converted_files == 2) 1597 | assert(converter.copied_files == 1) 1598 | converter = Converter(ConvertAction.MODULES) 1599 | converter.convert_directory(data_directory.joinpath('input'), dir_simple) 1600 | assert(converter.all_files == 3) 1601 | assert(converter.convertable_files == 2) 1602 | assert(converter.converted_files == 0) 1603 | assert(converter.copied_files == 0) 1604 | 1605 | def test_dir_inext(dir_simple: Path): 1606 | data_directory = Path('test_data/inext') 1607 | converter = Converter(ConvertAction.MODULES) 1608 | converter.options.add_module_action_ext_type('.hpp', ContentType.HEADER) 1609 | converter.convert_directory(data_directory.joinpath('input'), dir_simple) 1610 | assert_files(data_directory.joinpath('expected'), dir_simple, [ 1611 | 'simple.cppm', 1612 | 'simple2.h', 1613 | 'simple.cpp', 1614 | ]) 1615 | 1616 | def test_dir_outext(dir_simple: Path): 1617 | data_directory = Path('test_data/outext') 1618 | converter = Converter(ConvertAction.MODULES) 1619 | converter.options.set_output_content_type_to_ext(ContentType.MODULE_INTERFACE, '.ixx') 1620 | converter.options.set_output_content_type_to_ext(ContentType.MODULE_IMPL, '.cxx') 1621 | converter.convert_directory(data_directory.joinpath('input'), dir_simple) 1622 | assert_files(data_directory.joinpath('expected'), dir_simple, [ 1623 | 'simple.ixx', 1624 | 'simple.cxx', 1625 | 'simple2.hpp', 1626 | ]) 1627 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "colorama" 5 | version = "0.4.6" 6 | description = "Cross-platform colored terminal text." 7 | optional = false 8 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 9 | groups = ["dev"] 10 | markers = "sys_platform == \"win32\"" 11 | files = [ 12 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 13 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 14 | ] 15 | 16 | [[package]] 17 | name = "exceptiongroup" 18 | version = "1.2.2" 19 | description = "Backport of PEP 654 (exception groups)" 20 | optional = false 21 | python-versions = ">=3.7" 22 | groups = ["dev"] 23 | markers = "python_version < \"3.11\"" 24 | files = [ 25 | {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, 26 | {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, 27 | ] 28 | 29 | [package.extras] 30 | test = ["pytest (>=6)"] 31 | 32 | [[package]] 33 | name = "iniconfig" 34 | version = "2.0.0" 35 | description = "brain-dead simple config-ini parsing" 36 | optional = false 37 | python-versions = ">=3.7" 38 | groups = ["dev"] 39 | files = [ 40 | {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, 41 | {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, 42 | ] 43 | 44 | [[package]] 45 | name = "packaging" 46 | version = "24.2" 47 | description = "Core utilities for Python packages" 48 | optional = false 49 | python-versions = ">=3.8" 50 | groups = ["dev"] 51 | files = [ 52 | {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, 53 | {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, 54 | ] 55 | 56 | [[package]] 57 | name = "pluggy" 58 | version = "1.5.0" 59 | description = "plugin and hook calling mechanisms for python" 60 | optional = false 61 | python-versions = ">=3.8" 62 | groups = ["dev"] 63 | files = [ 64 | {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, 65 | {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, 66 | ] 67 | 68 | [package.extras] 69 | dev = ["pre-commit", "tox"] 70 | testing = ["pytest", "pytest-benchmark"] 71 | 72 | [[package]] 73 | name = "pytest" 74 | version = "8.3.4" 75 | description = "pytest: simple powerful testing with Python" 76 | optional = false 77 | python-versions = ">=3.8" 78 | groups = ["dev"] 79 | files = [ 80 | {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, 81 | {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, 82 | ] 83 | 84 | [package.dependencies] 85 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 86 | exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} 87 | iniconfig = "*" 88 | packaging = "*" 89 | pluggy = ">=1.5,<2" 90 | tomli = {version = ">=1", markers = "python_version < \"3.11\""} 91 | 92 | [package.extras] 93 | dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] 94 | 95 | [[package]] 96 | name = "tomli" 97 | version = "2.2.1" 98 | description = "A lil' TOML parser" 99 | optional = false 100 | python-versions = ">=3.8" 101 | groups = ["dev"] 102 | markers = "python_version < \"3.11\"" 103 | files = [ 104 | {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, 105 | {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, 106 | {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, 107 | {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, 108 | {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, 109 | {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, 110 | {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, 111 | {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, 112 | {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, 113 | {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, 114 | {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, 115 | {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, 116 | {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, 117 | {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, 118 | {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, 119 | {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, 120 | {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, 121 | {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, 122 | {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, 123 | {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, 124 | {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, 125 | {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, 126 | {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, 127 | {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, 128 | {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, 129 | {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, 130 | {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, 131 | {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, 132 | {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, 133 | {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, 134 | {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, 135 | {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, 136 | ] 137 | 138 | [metadata] 139 | lock-version = "2.1" 140 | python-versions = ">=3.9" 141 | content-hash = "61800311da478a07da351e58f66eff9e0b66a3377cc8c9154c1dbaa8919a52fa" 142 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "cxx-modules-converter" 3 | version = "0.1.15" 4 | description = "Convert C++20 modules to headers and headers to modules" 5 | authors = [ 6 | {name = "Alexander Petrov", email = "zowers@zowers.net"} 7 | ] 8 | license = {text = "MIT"} 9 | readme = "README.md" 10 | requires-python = ">=3.9" 11 | dependencies = [] 12 | 13 | [project.scripts] 14 | cxx_modules_converter = "cxx_modules_converter:main" 15 | 16 | [build-system] 17 | requires = ["poetry-core>=2.0.0,<3.0.0"] 18 | build-backend = "poetry.core.masonry.api" 19 | 20 | [tool.poetry.group.dev.dependencies] 21 | pytest = "^8.3.4" 22 | 23 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts = -vv 3 | -------------------------------------------------------------------------------- /requirements-test.txt: -------------------------------------------------------------------------------- 1 | pytest==8.3.4 2 | -------------------------------------------------------------------------------- /test_data/compat/expected/simple.cpp: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | 4 | 5 | #include 6 | module simple; 7 | extern "C++" { 8 | 9 | namespace TestNS 10 | { 11 | namespace Test 12 | { 13 | TestClass::TestClass() 14 | { 15 | } 16 | } // namespace Test 17 | } // namespace TestNS 18 | } // extern "C++" 19 | -------------------------------------------------------------------------------- /test_data/compat/expected/simple.cppm: -------------------------------------------------------------------------------- 1 | #ifndef CXX_COMPAT_HEADER 2 | module; 3 | #else 4 | #pragma once 5 | #include "local_include.h" 6 | #include 7 | #endif 8 | #include 9 | 10 | #ifndef CXX_COMPAT_HEADER 11 | export module simple; 12 | #endif 13 | #ifndef CXX_COMPAT_HEADER 14 | import local_include; 15 | #endif 16 | 17 | #ifndef CXX_COMPAT_HEADER 18 | export { 19 | extern "C++" { 20 | #endif 21 | namespace TestNS 22 | { 23 | namespace Test 24 | { 25 | class TestClass 26 | { 27 | TestClass(); 28 | }; 29 | } // namespace Test 30 | } // namespace TestNS 31 | #ifndef CXX_COMPAT_HEADER 32 | } // extern "C++" 33 | } // export 34 | #endif 35 | -------------------------------------------------------------------------------- /test_data/compat/expected/simple.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifndef CXX_COMPAT_HEADER 3 | #define CXX_COMPAT_HEADER 4 | #include "simple.cppm" 5 | #undef CXX_COMPAT_HEADER 6 | #else 7 | #include "simple.cppm" 8 | #endif 9 | -------------------------------------------------------------------------------- /test_data/compat/expected/subdir/simple2.cpp: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | 4 | 5 | #include 6 | module subdir.simple2; 7 | extern "C++" { 8 | 9 | namespace TestNS 10 | { 11 | namespace Test 12 | { 13 | TestClass::TestClass() 14 | { 15 | } 16 | } // namespace Test 17 | } // namespace TestNS 18 | } // extern "C++" 19 | -------------------------------------------------------------------------------- /test_data/compat/expected/subdir/simple2.cppm: -------------------------------------------------------------------------------- 1 | #ifndef CXX_COMPAT_HEADER 2 | module; 3 | #else 4 | #pragma once 5 | #include "local_include.h" 6 | #include 7 | #endif 8 | #include 9 | 10 | #ifndef CXX_COMPAT_HEADER 11 | export module subdir.simple2; 12 | #endif 13 | #ifndef CXX_COMPAT_HEADER 14 | import local_include; 15 | #endif 16 | 17 | #ifndef CXX_COMPAT_HEADER 18 | export { 19 | extern "C++" { 20 | #endif 21 | namespace TestNS 22 | { 23 | namespace Test 24 | { 25 | class TestClass 26 | { 27 | TestClass(); 28 | }; 29 | } // namespace Test 30 | } // namespace TestNS 31 | #ifndef CXX_COMPAT_HEADER 32 | } // extern "C++" 33 | } // export 34 | #endif 35 | -------------------------------------------------------------------------------- /test_data/compat/expected/subdir/simple2.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifndef CXX_COMPAT_HEADER 3 | #define CXX_COMPAT_HEADER 4 | #include "simple2.cppm" 5 | #undef CXX_COMPAT_HEADER 6 | #else 7 | #include "simple2.cppm" 8 | #endif 9 | -------------------------------------------------------------------------------- /test_data/compat/input/simple.cpp: -------------------------------------------------------------------------------- 1 | #include "simple.h" 2 | 3 | #include 4 | 5 | namespace TestNS 6 | { 7 | namespace Test 8 | { 9 | TestClass::TestClass() 10 | { 11 | } 12 | } // namespace Test 13 | } // namespace TestNS 14 | -------------------------------------------------------------------------------- /test_data/compat/input/simple.h: -------------------------------------------------------------------------------- 1 | #include "local_include.h" 2 | #include 3 | 4 | namespace TestNS 5 | { 6 | namespace Test 7 | { 8 | class TestClass 9 | { 10 | TestClass(); 11 | }; 12 | } // namespace Test 13 | } // namespace TestNS 14 | -------------------------------------------------------------------------------- /test_data/compat/input/subdir/simple2.cpp: -------------------------------------------------------------------------------- 1 | #include "simple2.h" 2 | 3 | #include 4 | 5 | namespace TestNS 6 | { 7 | namespace Test 8 | { 9 | TestClass::TestClass() 10 | { 11 | } 12 | } // namespace Test 13 | } // namespace TestNS 14 | -------------------------------------------------------------------------------- /test_data/compat/input/subdir/simple2.h: -------------------------------------------------------------------------------- 1 | #include "local_include.h" 2 | #include 3 | 4 | namespace TestNS 5 | { 6 | namespace Test 7 | { 8 | class TestClass 9 | { 10 | TestClass(); 11 | }; 12 | } // namespace Test 13 | } // namespace TestNS 14 | -------------------------------------------------------------------------------- /test_data/header/expected/simple.cpp: -------------------------------------------------------------------------------- 1 | #include "simple.h" 2 | 3 | #include 4 | 5 | namespace TestNS 6 | { 7 | namespace Test 8 | { 9 | TestClass::TestClass() 10 | { 11 | } 12 | } // namespace Test 13 | } // namespace TestNS 14 | -------------------------------------------------------------------------------- /test_data/header/expected/simple.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace TestNS 4 | { 5 | namespace Test 6 | { 7 | class TestClass 8 | { 9 | TestClass(); 10 | }; 11 | } // namespace Test 12 | } // namespace TestNS 13 | -------------------------------------------------------------------------------- /test_data/header/input/simple.cpp: -------------------------------------------------------------------------------- 1 | #include "simple.h" 2 | 3 | #include 4 | 5 | namespace TestNS 6 | { 7 | namespace Test 8 | { 9 | TestClass::TestClass() 10 | { 11 | } 12 | } // namespace Test 13 | } // namespace TestNS 14 | -------------------------------------------------------------------------------- /test_data/header/input/simple.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace TestNS 4 | { 5 | namespace Test 6 | { 7 | class TestClass 8 | { 9 | TestClass(); 10 | }; 11 | } // namespace Test 12 | } // namespace TestNS 13 | -------------------------------------------------------------------------------- /test_data/header_subdir/expected/subdir/simple.cpp: -------------------------------------------------------------------------------- 1 | #include "simple.h" 2 | 3 | #include 4 | 5 | namespace TestNS 6 | { 7 | namespace Test 8 | { 9 | TestClass::TestClass() 10 | { 11 | } 12 | } // namespace Test 13 | } // namespace TestNS 14 | -------------------------------------------------------------------------------- /test_data/header_subdir/expected/subdir/simple.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace TestNS 4 | { 5 | namespace Test 6 | { 7 | class TestClass 8 | { 9 | TestClass(); 10 | }; 11 | } // namespace Test 12 | } // namespace TestNS 13 | -------------------------------------------------------------------------------- /test_data/header_subdir/input/subdir/simple.cpp: -------------------------------------------------------------------------------- 1 | #include "simple.h" 2 | 3 | #include 4 | 5 | namespace TestNS 6 | { 7 | namespace Test 8 | { 9 | TestClass::TestClass() 10 | { 11 | } 12 | } // namespace Test 13 | } // namespace TestNS 14 | -------------------------------------------------------------------------------- /test_data/header_subdir/input/subdir/simple.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace TestNS 4 | { 5 | namespace Test 6 | { 7 | class TestClass 8 | { 9 | TestClass(); 10 | }; 11 | } // namespace Test 12 | } // namespace TestNS 13 | -------------------------------------------------------------------------------- /test_data/header_subdir_nested/expected/subdir/nested/simple.cpp: -------------------------------------------------------------------------------- 1 | #include "simple.h" 2 | 3 | #include 4 | 5 | namespace TestNS 6 | { 7 | namespace Test 8 | { 9 | TestClass::TestClass() 10 | { 11 | } 12 | } // namespace Test 13 | } // namespace TestNS 14 | -------------------------------------------------------------------------------- /test_data/header_subdir_nested/expected/subdir/nested/simple.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace TestNS 4 | { 5 | namespace Test 6 | { 7 | class TestClass 8 | { 9 | TestClass(); 10 | }; 11 | } // namespace Test 12 | } // namespace TestNS 13 | -------------------------------------------------------------------------------- /test_data/header_subdir_nested/input/subdir/nested/simple.cpp: -------------------------------------------------------------------------------- 1 | #include "simple.h" 2 | 3 | #include 4 | 5 | namespace TestNS 6 | { 7 | namespace Test 8 | { 9 | TestClass::TestClass() 10 | { 11 | } 12 | } // namespace Test 13 | } // namespace TestNS 14 | -------------------------------------------------------------------------------- /test_data/header_subdir_nested/input/subdir/nested/simple.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace TestNS 4 | { 5 | namespace Test 6 | { 7 | class TestClass 8 | { 9 | TestClass(); 10 | }; 11 | } // namespace Test 12 | } // namespace TestNS 13 | -------------------------------------------------------------------------------- /test_data/inext/expected/simple.cpp: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | 4 | #include 5 | module simple; 6 | import simple2; 7 | 8 | namespace TestNS 9 | { 10 | namespace Test 11 | { 12 | TestClass::TestClass() 13 | { 14 | } 15 | } // namespace Test 16 | } // namespace TestNS 17 | -------------------------------------------------------------------------------- /test_data/inext/expected/simple.cppm: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | export module simple; 4 | export { 5 | 6 | namespace TestNS 7 | { 8 | namespace Test 9 | { 10 | class TestClass 11 | { 12 | TestClass(); 13 | }; 14 | } // namespace Test 15 | } // namespace TestNS 16 | } // export 17 | -------------------------------------------------------------------------------- /test_data/inext/expected/simple2.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace TestNS 4 | { 5 | namespace Test 6 | { 7 | class TestClass 8 | { 9 | TestClass(); 10 | }; 11 | } // namespace Test 12 | } // namespace TestNS 13 | -------------------------------------------------------------------------------- /test_data/inext/input/simple.cpp: -------------------------------------------------------------------------------- 1 | #include "simple.hpp" 2 | #include "simple2.h" 3 | 4 | #include 5 | 6 | namespace TestNS 7 | { 8 | namespace Test 9 | { 10 | TestClass::TestClass() 11 | { 12 | } 13 | } // namespace Test 14 | } // namespace TestNS 15 | -------------------------------------------------------------------------------- /test_data/inext/input/simple.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace TestNS 4 | { 5 | namespace Test 6 | { 7 | class TestClass 8 | { 9 | TestClass(); 10 | }; 11 | } // namespace Test 12 | } // namespace TestNS 13 | -------------------------------------------------------------------------------- /test_data/inext/input/simple2.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace TestNS 4 | { 5 | namespace Test 6 | { 7 | class TestClass 8 | { 9 | TestClass(); 10 | }; 11 | } // namespace Test 12 | } // namespace TestNS 13 | -------------------------------------------------------------------------------- /test_data/modules_path/expected/dir2/local_include.cppm: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | export module org2.local_include; 4 | import org.subdir.local_include; 5 | -------------------------------------------------------------------------------- /test_data/modules_path/expected/dir2/simple.cpp: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | 4 | #include 5 | module org2.simple; 6 | -------------------------------------------------------------------------------- /test_data/modules_path/expected/dir2/simple.cppm: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | export module org2.simple; 4 | import org2.local_include; 5 | import missing; 6 | import subdir.subdir-missing; 7 | import org2.dir2-missing; 8 | -------------------------------------------------------------------------------- /test_data/modules_path/expected/subdir/local_include.cppm: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | export module org.subdir.local_include; 4 | -------------------------------------------------------------------------------- /test_data/modules_path/expected/subdir/simple.cpp: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | 4 | #include 5 | module org.subdir.simple; 6 | -------------------------------------------------------------------------------- /test_data/modules_path/expected/subdir/simple.cppm: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | export module org.subdir.simple; 4 | import org.subdir.local_include; 5 | import subdir.missing; 6 | -------------------------------------------------------------------------------- /test_data/modules_path/input/dir2/local_include.h: -------------------------------------------------------------------------------- 1 | #include "subdir/local_include.h" 2 | #include 3 | -------------------------------------------------------------------------------- /test_data/modules_path/input/dir2/simple.cpp: -------------------------------------------------------------------------------- 1 | #include "simple.h" 2 | 3 | #include 4 | -------------------------------------------------------------------------------- /test_data/modules_path/input/dir2/simple.h: -------------------------------------------------------------------------------- 1 | #include "local_include.h" 2 | #include "missing.h" 3 | #include "subdir/subdir-missing.h" 4 | #include "dir2/dir2-missing.h" 5 | #include 6 | -------------------------------------------------------------------------------- /test_data/modules_path/input/subdir/local_include.h: -------------------------------------------------------------------------------- 1 | #include "subdir/local_include.h" 2 | #include 3 | -------------------------------------------------------------------------------- /test_data/modules_path/input/subdir/simple.cpp: -------------------------------------------------------------------------------- 1 | #include "simple.h" 2 | 3 | #include 4 | -------------------------------------------------------------------------------- /test_data/modules_path/input/subdir/simple.h: -------------------------------------------------------------------------------- 1 | #include "subdir/local_include.h" 2 | #include "subdir/missing.h" 3 | #include 4 | -------------------------------------------------------------------------------- /test_data/named1/expected/simple.cpp: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | 4 | #include 5 | module org.simple; 6 | 7 | namespace TestNS 8 | { 9 | namespace Test 10 | { 11 | TestClass::TestClass() 12 | { 13 | } 14 | } // namespace Test 15 | } // namespace TestNS 16 | -------------------------------------------------------------------------------- /test_data/named1/expected/simple.cppm: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | export module org.simple; 4 | export { 5 | 6 | namespace TestNS 7 | { 8 | namespace Test 9 | { 10 | class TestClass 11 | { 12 | TestClass(); 13 | }; 14 | } // namespace Test 15 | } // namespace TestNS 16 | } // export 17 | -------------------------------------------------------------------------------- /test_data/named1/input/simple.cpp: -------------------------------------------------------------------------------- 1 | #include "simple.h" 2 | 3 | #include 4 | 5 | namespace TestNS 6 | { 7 | namespace Test 8 | { 9 | TestClass::TestClass() 10 | { 11 | } 12 | } // namespace Test 13 | } // namespace TestNS 14 | -------------------------------------------------------------------------------- /test_data/named1/input/simple.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace TestNS 4 | { 5 | namespace Test 6 | { 7 | class TestClass 8 | { 9 | TestClass(); 10 | }; 11 | } // namespace Test 12 | } // namespace TestNS 13 | -------------------------------------------------------------------------------- /test_data/other/expected/other.txt: -------------------------------------------------------------------------------- 1 | text 2 | -------------------------------------------------------------------------------- /test_data/other/input/other.txt: -------------------------------------------------------------------------------- 1 | text 2 | -------------------------------------------------------------------------------- /test_data/outext/expected/simple.cxx: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | 4 | #include 5 | module simple; 6 | import simple2; 7 | 8 | namespace TestNS 9 | { 10 | namespace Test 11 | { 12 | TestClass::TestClass() 13 | { 14 | } 15 | } // namespace Test 16 | } // namespace TestNS 17 | -------------------------------------------------------------------------------- /test_data/outext/expected/simple.ixx: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | export module simple; 4 | export { 5 | 6 | namespace TestNS 7 | { 8 | namespace Test 9 | { 10 | class TestClass 11 | { 12 | TestClass(); 13 | }; 14 | } // namespace Test 15 | } // namespace TestNS 16 | } // export 17 | -------------------------------------------------------------------------------- /test_data/outext/expected/simple2.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace TestNS 4 | { 5 | namespace Test 6 | { 7 | class TestClass 8 | { 9 | TestClass(); 10 | }; 11 | } // namespace Test 12 | } // namespace TestNS 13 | -------------------------------------------------------------------------------- /test_data/outext/input/simple.cpp: -------------------------------------------------------------------------------- 1 | #include "simple.h" 2 | #include "simple2.hpp" 3 | 4 | #include 5 | 6 | namespace TestNS 7 | { 8 | namespace Test 9 | { 10 | TestClass::TestClass() 11 | { 12 | } 13 | } // namespace Test 14 | } // namespace TestNS 15 | -------------------------------------------------------------------------------- /test_data/outext/input/simple.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace TestNS 4 | { 5 | namespace Test 6 | { 7 | class TestClass 8 | { 9 | TestClass(); 10 | }; 11 | } // namespace Test 12 | } // namespace TestNS 13 | -------------------------------------------------------------------------------- /test_data/outext/input/simple2.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace TestNS 4 | { 5 | namespace Test 6 | { 7 | class TestClass 8 | { 9 | TestClass(); 10 | }; 11 | } // namespace Test 12 | } // namespace TestNS 13 | -------------------------------------------------------------------------------- /test_data/prefix/expected/subdir/simple.cpp: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | 4 | #include 5 | module subdir.simple; 6 | -------------------------------------------------------------------------------- /test_data/prefix/expected/subdir/simple.cppm: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | export module subdir.simple; 4 | import subdir.local_include; 5 | -------------------------------------------------------------------------------- /test_data/prefix/input/skipped.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zowers/cxx_modules_converter/110b784836b745cd231685aaff0a7c06277689ac/test_data/prefix/input/skipped.h -------------------------------------------------------------------------------- /test_data/prefix/input/subdir/simple.cpp: -------------------------------------------------------------------------------- 1 | #include "simple.h" 2 | 3 | #include 4 | -------------------------------------------------------------------------------- /test_data/prefix/input/subdir/simple.h: -------------------------------------------------------------------------------- 1 | #include "subdir/local_include.h" 2 | #include 3 | -------------------------------------------------------------------------------- /test_data/prefix_named/expected/subdir/local_include.cppm: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | export module org.subdir.local_include; 4 | -------------------------------------------------------------------------------- /test_data/prefix_named/expected/subdir/simple.cpp: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | 4 | #include 5 | module org.subdir.simple; 6 | -------------------------------------------------------------------------------- /test_data/prefix_named/expected/subdir/simple.cppm: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | export module org.subdir.simple; 4 | import org.subdir.local_include; 5 | import subdir.missing; 6 | -------------------------------------------------------------------------------- /test_data/prefix_named/input/skipped.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zowers/cxx_modules_converter/110b784836b745cd231685aaff0a7c06277689ac/test_data/prefix_named/input/skipped.h -------------------------------------------------------------------------------- /test_data/prefix_named/input/subdir/local_include.h: -------------------------------------------------------------------------------- 1 | #include "subdir/local_include.h" 2 | #include 3 | -------------------------------------------------------------------------------- /test_data/prefix_named/input/subdir/simple.cpp: -------------------------------------------------------------------------------- 1 | #include "simple.h" 2 | 3 | #include 4 | -------------------------------------------------------------------------------- /test_data/prefix_named/input/subdir/simple.h: -------------------------------------------------------------------------------- 1 | #include "subdir/local_include.h" 2 | #include "subdir/missing.h" 3 | #include 4 | -------------------------------------------------------------------------------- /test_data/simple/expected/simple.cpp: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | 4 | #include 5 | module simple; 6 | 7 | namespace TestNS 8 | { 9 | namespace Test 10 | { 11 | TestClass::TestClass() 12 | { 13 | } 14 | } // namespace Test 15 | } // namespace TestNS 16 | -------------------------------------------------------------------------------- /test_data/simple/expected/simple.cppm: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | export module simple; 4 | export { 5 | 6 | namespace TestNS 7 | { 8 | namespace Test 9 | { 10 | class TestClass 11 | { 12 | TestClass(); 13 | }; 14 | } // namespace Test 15 | } // namespace TestNS 16 | } // export 17 | -------------------------------------------------------------------------------- /test_data/simple/input/simple.cpp: -------------------------------------------------------------------------------- 1 | #include "simple.h" 2 | 3 | #include 4 | 5 | namespace TestNS 6 | { 7 | namespace Test 8 | { 9 | TestClass::TestClass() 10 | { 11 | } 12 | } // namespace Test 13 | } // namespace TestNS 14 | -------------------------------------------------------------------------------- /test_data/simple/input/simple.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace TestNS 4 | { 5 | namespace Test 6 | { 7 | class TestClass 8 | { 9 | TestClass(); 10 | }; 11 | } // namespace Test 12 | } // namespace TestNS 13 | -------------------------------------------------------------------------------- /test_data/simple_std/expected/simple.cpp: -------------------------------------------------------------------------------- 1 | module simple; 2 | 3 | import std; 4 | 5 | namespace TestNS 6 | { 7 | namespace Test 8 | { 9 | TestClass::TestClass() 10 | { 11 | } 12 | } // namespace Test 13 | } // namespace TestNS 14 | -------------------------------------------------------------------------------- /test_data/simple_std/expected/simple.cppm: -------------------------------------------------------------------------------- 1 | export module simple; 2 | import std; 3 | export { 4 | 5 | namespace TestNS 6 | { 7 | namespace Test 8 | { 9 | class TestClass 10 | { 11 | TestClass(); 12 | }; 13 | } // namespace Test 14 | } // namespace TestNS 15 | } // export 16 | -------------------------------------------------------------------------------- /test_data/simple_std/input/simple.cpp: -------------------------------------------------------------------------------- 1 | #include "simple.h" 2 | 3 | #include 4 | 5 | namespace TestNS 6 | { 7 | namespace Test 8 | { 9 | TestClass::TestClass() 10 | { 11 | } 12 | } // namespace Test 13 | } // namespace TestNS 14 | -------------------------------------------------------------------------------- /test_data/simple_std/input/simple.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace TestNS 4 | { 5 | namespace Test 6 | { 7 | class TestClass 8 | { 9 | TestClass(); 10 | }; 11 | } // namespace Test 12 | } // namespace TestNS 13 | -------------------------------------------------------------------------------- /test_data/skip/expected/subdir1/simple1.cppm: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | export module subdir1.simple1; 4 | -------------------------------------------------------------------------------- /test_data/skip/input/skipdir/empty.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zowers/cxx_modules_converter/110b784836b745cd231685aaff0a7c06277689ac/test_data/skip/input/skipdir/empty.txt -------------------------------------------------------------------------------- /test_data/skip/input/subdir1/simple1.h: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /test_data/skip/input/subdir1/simple2.h: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /test_data/skip/input/subdir1/skipsubdir/empty.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zowers/cxx_modules_converter/110b784836b745cd231685aaff0a7c06277689ac/test_data/skip/input/subdir1/skipsubdir/empty.txt -------------------------------------------------------------------------------- /test_data/subdir/expected/subdir1/simple1.cppm: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | export module subdir1.simple1; 4 | -------------------------------------------------------------------------------- /test_data/subdir/expected/subdir1/simple2.cppm: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | export module subdir1.simple2; 4 | -------------------------------------------------------------------------------- /test_data/subdir/input/subdir1/simple1.h: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /test_data/subdir/input/subdir1/simple2.h: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /test_data/subdirs/expected/simple1.cppm: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | export module simple1; 4 | -------------------------------------------------------------------------------- /test_data/subdirs/expected/subdir1/simple1.cppm: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | export module subdir1.simple1; 4 | -------------------------------------------------------------------------------- /test_data/subdirs/expected/subdir1/simple2.cppm: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | export module subdir1.simple2; 4 | -------------------------------------------------------------------------------- /test_data/subdirs/expected/subdir1/subdir2/simple1.cppm: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | export module subdir1.subdir2.simple1; 4 | -------------------------------------------------------------------------------- /test_data/subdirs/expected/subdir1/subdir2/simple2.cppm: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | export module subdir1.subdir2.simple2; 4 | -------------------------------------------------------------------------------- /test_data/subdirs/input/simple1.h: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /test_data/subdirs/input/subdir1/simple1.h: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /test_data/subdirs/input/subdir1/simple2.h: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /test_data/subdirs/input/subdir1/subdir2/simple1.h: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /test_data/subdirs/input/subdir1/subdir2/simple2.h: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /test_data/subdirs_rooted/expected/subdir/simple1.cppm: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | export module subdir.simple1; 4 | -------------------------------------------------------------------------------- /test_data/subdirs_rooted/expected/subdir/subdir1/simple1.cppm: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | export module subdir.subdir1.simple1; 4 | -------------------------------------------------------------------------------- /test_data/subdirs_rooted/expected/subdir/subdir1/simple2.cppm: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | export module subdir.subdir1.simple2; 4 | import subdir.subdir1.simple1; 5 | -------------------------------------------------------------------------------- /test_data/subdirs_rooted/expected/subdir/subdir1/subdir2/simple1.cpp: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | #include 4 | module subdir.subdir1.subdir2.simple1; 5 | 6 | namespace TestNS 7 | { 8 | } 9 | -------------------------------------------------------------------------------- /test_data/subdirs_rooted/expected/subdir/subdir1/subdir2/simple1.cppm: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | export module subdir.subdir1.subdir2.simple1; 4 | -------------------------------------------------------------------------------- /test_data/subdirs_rooted/expected/subdir/subdir1/subdir2/simple2.cppm: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | export module subdir.subdir1.subdir2.simple2; 4 | import subdir.subdir1.subdir2.simple1; 5 | -------------------------------------------------------------------------------- /test_data/subdirs_rooted/expected/subdir/subdir1/use_relative_include.cppm: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | export module subdir.subdir1.use_relative_include; 4 | import subdir.subdir1.subdir2.simple1; 5 | -------------------------------------------------------------------------------- /test_data/subdirs_rooted/expected/subdir/subdir1/use_relative_include_missing.cppm: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | export module subdir.subdir1.use_relative_include_missing; 4 | import subdir2.simple3; 5 | -------------------------------------------------------------------------------- /test_data/subdirs_rooted/expected/subdir/subdir1/use_search_path_include_existing.cppm: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | export module subdir.subdir1.use_search_path_include_existing; 4 | import subdir2.simple3; 5 | -------------------------------------------------------------------------------- /test_data/subdirs_rooted/input/subdir/simple1.h: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /test_data/subdirs_rooted/input/subdir/subdir1/simple1.h: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /test_data/subdirs_rooted/input/subdir/subdir1/simple2.h: -------------------------------------------------------------------------------- 1 | #include "simple1.h" 2 | #include 3 | -------------------------------------------------------------------------------- /test_data/subdirs_rooted/input/subdir/subdir1/subdir2/simple1.cpp: -------------------------------------------------------------------------------- 1 | #include "simple1.h" 2 | #include 3 | 4 | namespace TestNS 5 | { 6 | } 7 | -------------------------------------------------------------------------------- /test_data/subdirs_rooted/input/subdir/subdir1/subdir2/simple1.h: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /test_data/subdirs_rooted/input/subdir/subdir1/subdir2/simple2.h: -------------------------------------------------------------------------------- 1 | #include "simple1.h" 2 | #include 3 | -------------------------------------------------------------------------------- /test_data/subdirs_rooted/input/subdir/subdir1/use_relative_include.h: -------------------------------------------------------------------------------- 1 | #include "subdir2/simple1.h" 2 | #include 3 | -------------------------------------------------------------------------------- /test_data/subdirs_rooted/input/subdir/subdir1/use_relative_include_missing.h: -------------------------------------------------------------------------------- 1 | #include "subdir2/simple3.h" 2 | #include 3 | -------------------------------------------------------------------------------- /test_data/subdirs_rooted/input/subdir/subdir1/use_search_path_include_existing.h: -------------------------------------------------------------------------------- 1 | #include "subdir2/simple3.h" 2 | #include 3 | -------------------------------------------------------------------------------- /test_data/subdirs_rooted_brackets/expected/subdir/simple1.cppm: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | export module subdir.simple1; 4 | -------------------------------------------------------------------------------- /test_data/subdirs_rooted_brackets/expected/subdir/subdir1/simple1.cppm: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | export module subdir.subdir1.simple1; 4 | -------------------------------------------------------------------------------- /test_data/subdirs_rooted_brackets/expected/subdir/subdir1/simple2.cppm: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | #include 4 | export module subdir.subdir1.simple2; 5 | -------------------------------------------------------------------------------- /test_data/subdirs_rooted_brackets/expected/subdir/subdir1/subdir2/simple1.cpp: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | #include 4 | module subdir.subdir1.subdir2.simple1; 5 | 6 | namespace TestNS 7 | { 8 | } 9 | -------------------------------------------------------------------------------- /test_data/subdirs_rooted_brackets/expected/subdir/subdir1/subdir2/simple1.cppm: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | export module subdir.subdir1.subdir2.simple1; 4 | -------------------------------------------------------------------------------- /test_data/subdirs_rooted_brackets/expected/subdir/subdir1/subdir2/simple2.cppm: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | #include 4 | export module subdir.subdir1.subdir2.simple2; 5 | -------------------------------------------------------------------------------- /test_data/subdirs_rooted_brackets/expected/subdir/subdir1/use_relative_include.cppm: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | #include 4 | export module subdir.subdir1.use_relative_include; 5 | -------------------------------------------------------------------------------- /test_data/subdirs_rooted_brackets/expected/subdir/subdir1/use_relative_include_missing.cppm: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | #include 4 | export module subdir.subdir1.use_relative_include_missing; 5 | -------------------------------------------------------------------------------- /test_data/subdirs_rooted_brackets/expected/subdir/subdir1/use_search_path_include_existing.cppm: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | export module subdir.subdir1.use_search_path_include_existing; 4 | import subdir2.simple_existing; 5 | -------------------------------------------------------------------------------- /test_data/subdirs_rooted_brackets/expected/subdir2/simple_existing.cppm: -------------------------------------------------------------------------------- 1 | // simple_existing 2 | export module subdir2.simple_existing 3 | -------------------------------------------------------------------------------- /test_data/subdirs_rooted_brackets/input/subdir/simple1.h: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /test_data/subdirs_rooted_brackets/input/subdir/subdir1/simple1.h: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /test_data/subdirs_rooted_brackets/input/subdir/subdir1/simple2.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | -------------------------------------------------------------------------------- /test_data/subdirs_rooted_brackets/input/subdir/subdir1/subdir2/simple1.cpp: -------------------------------------------------------------------------------- 1 | #include "simple1.h" 2 | #include 3 | 4 | namespace TestNS 5 | { 6 | } 7 | -------------------------------------------------------------------------------- /test_data/subdirs_rooted_brackets/input/subdir/subdir1/subdir2/simple1.h: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /test_data/subdirs_rooted_brackets/input/subdir/subdir1/subdir2/simple2.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | -------------------------------------------------------------------------------- /test_data/subdirs_rooted_brackets/input/subdir/subdir1/use_relative_include.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | -------------------------------------------------------------------------------- /test_data/subdirs_rooted_brackets/input/subdir/subdir1/use_relative_include_missing.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | -------------------------------------------------------------------------------- /test_data/subdirs_rooted_brackets/input/subdir/subdir1/use_search_path_include_existing.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | -------------------------------------------------------------------------------- /test_data/subdirs_rooted_brackets/input/subdir2/simple_existing.h: -------------------------------------------------------------------------------- 1 | // simple_existing 2 | -------------------------------------------------------------------------------- /test_data/twice/expected/other.txt: -------------------------------------------------------------------------------- 1 | text 2 | -------------------------------------------------------------------------------- /test_data/twice/expected/simple.cpp: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | 4 | #include 5 | module simple; 6 | 7 | namespace TestNS 8 | { 9 | namespace Test 10 | { 11 | TestClass::TestClass() 12 | { 13 | } 14 | } // namespace Test 15 | } // namespace TestNS 16 | -------------------------------------------------------------------------------- /test_data/twice/expected/simple.cppm: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | export module simple; 4 | export { 5 | 6 | namespace TestNS 7 | { 8 | namespace Test 9 | { 10 | class TestClass 11 | { 12 | TestClass(); 13 | }; 14 | } // namespace Test 15 | } // namespace TestNS 16 | } // export 17 | -------------------------------------------------------------------------------- /test_data/twice/input/other.txt: -------------------------------------------------------------------------------- 1 | text 2 | -------------------------------------------------------------------------------- /test_data/twice/input/simple.cpp: -------------------------------------------------------------------------------- 1 | #include "simple.h" 2 | 3 | #include 4 | 5 | namespace TestNS 6 | { 7 | namespace Test 8 | { 9 | TestClass::TestClass() 10 | { 11 | } 12 | } // namespace Test 13 | } // namespace TestNS 14 | -------------------------------------------------------------------------------- /test_data/twice/input/simple.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace TestNS 4 | { 5 | namespace Test 6 | { 7 | class TestClass 8 | { 9 | TestClass(); 10 | }; 11 | } // namespace Test 12 | } // namespace TestNS 13 | -------------------------------------------------------------------------------- /test_data/two/expected/simple1.cppm: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | export module simple1; 4 | -------------------------------------------------------------------------------- /test_data/two/expected/simple2.cppm: -------------------------------------------------------------------------------- 1 | module; 2 | #include 3 | export module simple2; 4 | -------------------------------------------------------------------------------- /test_data/two/input/simple1.h: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /test_data/two/input/simple2.h: -------------------------------------------------------------------------------- 1 | #include 2 | --------------------------------------------------------------------------------