|
7 | 7 | import os
|
8 | 8 | import subprocess
|
9 | 9 | import sys
|
| 10 | +import tempfile |
10 | 11 | import typing
|
11 | 12 |
|
12 | 13 | from .options import PreprocessorFunction
|
@@ -104,6 +105,110 @@ def _preprocess_file(filename: str, content: typing.Optional[str]) -> str:
|
104 | 105 | return _preprocess_file
|
105 | 106 |
|
106 | 107 |
|
| 108 | +# |
| 109 | +# Microsoft Visual Studio preprocessor support |
| 110 | +# |
| 111 | + |
| 112 | + |
| 113 | +def _msvc_filter(fp: typing.TextIO) -> str: |
| 114 | + # MSVC outputs the original file as the very first #line directive |
| 115 | + # so we just use that |
| 116 | + new_output = io.StringIO() |
| 117 | + keep = True |
| 118 | + |
| 119 | + first = fp.readline() |
| 120 | + assert first.startswith("#line") |
| 121 | + fname = first[first.find('"') :] |
| 122 | + |
| 123 | + for line in fp: |
| 124 | + if line.startswith("#line"): |
| 125 | + keep = line.endswith(fname) |
| 126 | + |
| 127 | + if keep: |
| 128 | + new_output.write(line) |
| 129 | + |
| 130 | + new_output.seek(0) |
| 131 | + return new_output.read() |
| 132 | + |
| 133 | + |
| 134 | +def make_msvc_preprocessor( |
| 135 | + *, |
| 136 | + defines: typing.List[str] = [], |
| 137 | + include_paths: typing.List[str] = [], |
| 138 | + retain_all_content: bool = False, |
| 139 | + encoding: typing.Optional[str] = None, |
| 140 | + msvc_args: typing.List[str] = ["cl.exe"], |
| 141 | + print_cmd: bool = True, |
| 142 | +) -> PreprocessorFunction: |
| 143 | + """ |
| 144 | + Creates a preprocessor function that uses cl.exe from Microsoft Visual Studio |
| 145 | + to preprocess the input text. cl.exe is not typically on the path, so you |
| 146 | + may need to open the correct developer tools shell or pass in the correct path |
| 147 | + to cl.exe in the `msvc_args` parameter. |
| 148 | +
|
| 149 | + cl.exe will throw an error if a file referenced by an #include directive is not found. |
| 150 | +
|
| 151 | + :param defines: list of #define macros specified as "key value" |
| 152 | + :param include_paths: list of directories to search for included files |
| 153 | + :param retain_all_content: If False, only the parsed file content will be retained |
| 154 | + :param encoding: If specified any include files are opened with this encoding |
| 155 | + :param msvc_args: This is the path to cl.exe and any extra args you might want |
| 156 | + :param print_cmd: Prints the command as its executed |
| 157 | +
|
| 158 | + .. code-block:: python |
| 159 | +
|
| 160 | + pp = make_msvc_preprocessor() |
| 161 | + options = ParserOptions(preprocessor=pp) |
| 162 | +
|
| 163 | + parse_file(content, options=options) |
| 164 | +
|
| 165 | + """ |
| 166 | + |
| 167 | + if not encoding: |
| 168 | + encoding = "utf-8" |
| 169 | + |
| 170 | + def _preprocess_file(filename: str, content: typing.Optional[str]) -> str: |
| 171 | + cmd = msvc_args + ["/nologo", "/E", "/C"] |
| 172 | + |
| 173 | + for p in include_paths: |
| 174 | + cmd.append(f"/I{p}") |
| 175 | + for d in defines: |
| 176 | + cmd.append(f"/D{d.replace(' ', '=')}") |
| 177 | + |
| 178 | + tfpname = None |
| 179 | + |
| 180 | + try: |
| 181 | + kwargs = {"encoding": encoding} |
| 182 | + if filename == "<str>": |
| 183 | + if content is None: |
| 184 | + raise PreprocessorError("no content specified for stdin") |
| 185 | + |
| 186 | + tfp = tempfile.NamedTemporaryFile( |
| 187 | + mode="w", encoding=encoding, suffix=".h", delete=False |
| 188 | + ) |
| 189 | + tfpname = tfp.name |
| 190 | + tfp.write(content) |
| 191 | + tfp.close() |
| 192 | + |
| 193 | + cmd.append(tfpname) |
| 194 | + else: |
| 195 | + cmd.append(filename) |
| 196 | + |
| 197 | + if print_cmd: |
| 198 | + print("+", " ".join(cmd), file=sys.stderr) |
| 199 | + |
| 200 | + result: str = subprocess.check_output(cmd, **kwargs) # type: ignore |
| 201 | + if not retain_all_content: |
| 202 | + result = _msvc_filter(io.StringIO(result)) |
| 203 | + finally: |
| 204 | + if tfpname: |
| 205 | + os.unlink(tfpname) |
| 206 | + |
| 207 | + return result |
| 208 | + |
| 209 | + return _preprocess_file |
| 210 | + |
| 211 | + |
107 | 212 | #
|
108 | 213 | # PCPP preprocessor support (not installed by default)
|
109 | 214 | #
|
|
0 commit comments