Add MSVC preprocessor support
This commit is contained in:
parent
3d23375190
commit
196e88b85e
4
.github/workflows/dist.yml
vendored
4
.github/workflows/dist.yml
vendored
@ -112,6 +112,10 @@ jobs:
|
||||
- name: Install test dependencies
|
||||
run: python -m pip --disable-pip-version-check install -r tests/requirements.txt
|
||||
|
||||
- name: Setup MSVC compiler
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
if: matrix.os == 'windows-latest'
|
||||
|
||||
- name: Test wheel
|
||||
shell: bash
|
||||
run: |
|
||||
|
@ -7,6 +7,7 @@ import re
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import typing
|
||||
|
||||
from .options import PreprocessorFunction
|
||||
@ -104,6 +105,110 @@ def make_gcc_preprocessor(
|
||||
return _preprocess_file
|
||||
|
||||
|
||||
#
|
||||
# Microsoft Visual Studio preprocessor support
|
||||
#
|
||||
|
||||
|
||||
def _msvc_filter(fp: typing.TextIO) -> str:
|
||||
# MSVC outputs the original file as the very first #line directive
|
||||
# so we just use that
|
||||
new_output = io.StringIO()
|
||||
keep = True
|
||||
|
||||
first = fp.readline()
|
||||
assert first.startswith("#line")
|
||||
fname = first[first.find('"') :]
|
||||
|
||||
for line in fp:
|
||||
if line.startswith("#line"):
|
||||
keep = line.endswith(fname)
|
||||
|
||||
if keep:
|
||||
new_output.write(line)
|
||||
|
||||
new_output.seek(0)
|
||||
return new_output.read()
|
||||
|
||||
|
||||
def make_msvc_preprocessor(
|
||||
*,
|
||||
defines: typing.List[str] = [],
|
||||
include_paths: typing.List[str] = [],
|
||||
retain_all_content: bool = False,
|
||||
encoding: typing.Optional[str] = None,
|
||||
msvc_args: typing.List[str] = ["cl.exe"],
|
||||
print_cmd: bool = True,
|
||||
) -> PreprocessorFunction:
|
||||
"""
|
||||
Creates a preprocessor function that uses cl.exe from Microsoft Visual Studio
|
||||
to preprocess the input text. cl.exe is not typically on the path, so you
|
||||
may need to open the correct developer tools shell or pass in the correct path
|
||||
to cl.exe in the `msvc_args` parameter.
|
||||
|
||||
cl.exe will throw an error if a file referenced by an #include directive is not found.
|
||||
|
||||
:param defines: list of #define macros specified as "key value"
|
||||
:param include_paths: list of directories to search for included files
|
||||
:param retain_all_content: If False, only the parsed file content will be retained
|
||||
:param encoding: If specified any include files are opened with this encoding
|
||||
:param msvc_args: This is the path to cl.exe and any extra args you might want
|
||||
:param print_cmd: Prints the command as its executed
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
pp = make_msvc_preprocessor()
|
||||
options = ParserOptions(preprocessor=pp)
|
||||
|
||||
parse_file(content, options=options)
|
||||
|
||||
"""
|
||||
|
||||
if not encoding:
|
||||
encoding = "utf-8"
|
||||
|
||||
def _preprocess_file(filename: str, content: typing.Optional[str]) -> str:
|
||||
cmd = msvc_args + ["/nologo", "/E", "/C"]
|
||||
|
||||
for p in include_paths:
|
||||
cmd.append(f"/I{p}")
|
||||
for d in defines:
|
||||
cmd.append(f"/D{d.replace(' ', '=')}")
|
||||
|
||||
tfpname = None
|
||||
|
||||
try:
|
||||
kwargs = {"encoding": encoding}
|
||||
if filename == "<str>":
|
||||
if content is None:
|
||||
raise PreprocessorError("no content specified for stdin")
|
||||
|
||||
tfp = tempfile.NamedTemporaryFile(
|
||||
mode="w", encoding=encoding, suffix=".h", delete=False
|
||||
)
|
||||
tfpname = tfp.name
|
||||
tfp.write(content)
|
||||
tfp.close()
|
||||
|
||||
cmd.append(tfpname)
|
||||
else:
|
||||
cmd.append(filename)
|
||||
|
||||
if print_cmd:
|
||||
print("+", " ".join(cmd), file=sys.stderr)
|
||||
|
||||
result: str = subprocess.check_output(cmd, **kwargs) # type: ignore
|
||||
if not retain_all_content:
|
||||
result = _msvc_filter(io.StringIO(result))
|
||||
finally:
|
||||
if tfpname:
|
||||
os.unlink(tfpname)
|
||||
|
||||
return result
|
||||
|
||||
return _preprocess_file
|
||||
|
||||
|
||||
#
|
||||
# PCPP preprocessor support (not installed by default)
|
||||
#
|
||||
|
@ -26,7 +26,7 @@ from cxxheaderparser.types import (
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(params=["gcc", "pcpp"])
|
||||
@pytest.fixture(params=["gcc", "msvc", "pcpp"])
|
||||
def make_pp(request) -> typing.Callable[..., PreprocessorFunction]:
|
||||
param = request.param
|
||||
if param == "gcc":
|
||||
@ -36,6 +36,12 @@ def make_pp(request) -> typing.Callable[..., PreprocessorFunction]:
|
||||
|
||||
subprocess.run([gcc_path, "--version"])
|
||||
return preprocessor.make_gcc_preprocessor
|
||||
elif param == "msvc":
|
||||
gcc_path = shutil.which("cl.exe")
|
||||
if not gcc_path:
|
||||
pytest.skip("cl.exe not found")
|
||||
|
||||
return preprocessor.make_msvc_preprocessor
|
||||
elif param == "pcpp":
|
||||
if preprocessor.pcpp is None:
|
||||
pytest.skip("pcpp not installed")
|
||||
|
Loading…
x
Reference in New Issue
Block a user