Skip to content

Commit b65aa40

Browse files
authored
Merge pull request #153 from zooba/msvccompiler
Ensure Windows SDK directories are not cleared when caller specifies include/library dirs
2 parents 3e4c7a7 + 9f9a3e5 commit b65aa40

3 files changed

Lines changed: 85 additions & 26 deletions

File tree

‎distutils/_msvccompiler.py‎

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,18 @@ def __init__(self, verbose=0, dry_run=0, force=0):
223223
# target platform (.plat_name is consistent with 'bdist')
224224
self.plat_name = None
225225

226+
@classmethod
227+
def _configure(cls, vc_env):
228+
"""
229+
Set class-level include/lib dirs.
230+
"""
231+
cls.include_dirs = cls._parse_path(vc_env.get('include', ''))
232+
cls.library_dirs = cls._parse_path(vc_env.get('lib', ''))
233+
234+
@staticmethod
235+
def _parse_path(val):
236+
return [dir.rstrip(os.sep) for dir in val.split(os.pathsep) if dir]
237+
226238
def initialize(self, plat_name=None):
227239
def _repeat_call_error(plat_name=None):
228240
raise AssertionError("repeat initialize not allowed")
@@ -243,6 +255,7 @@ def _repeat_call_error(plat_name=None):
243255
raise DistutilsPlatformError(
244256
"Unable to find a compatible " "Visual Studio installation."
245257
)
258+
self._configure(vc_env)
246259

247260
self._paths = vc_env.get('path', '')
248261
paths = self._paths.split(os.pathsep)
@@ -253,14 +266,6 @@ def _repeat_call_error(plat_name=None):
253266
self.mc = _find_exe("mc.exe", paths) # message compiler
254267
self.mt = _find_exe("mt.exe", paths) # message compiler
255268

256-
for dir in vc_env.get('include', '').split(os.pathsep):
257-
if dir:
258-
self.add_include_dir(dir.rstrip(os.sep))
259-
260-
for dir in vc_env.get('lib', '').split(os.pathsep):
261-
if dir:
262-
self.add_library_dir(dir.rstrip(os.sep))
263-
264269
self.preprocess_options = None
265270
# bpo-38597: Always compile with dynamic linking
266271
# Future releases of Python 3.x will include all past

‎distutils/ccompiler.py‎

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,16 @@ class CCompiler:
9191
}
9292
language_order = ["c++", "objc", "c"]
9393

94+
include_dirs = []
95+
"""
96+
include dirs specific to this compiler class
97+
"""
98+
99+
library_dirs = []
100+
"""
101+
library dirs specific to this compiler class
102+
"""
103+
94104
def __init__(self, verbose=0, dry_run=0, force=0):
95105
self.dry_run = dry_run
96106
self.force = force
@@ -324,24 +334,7 @@ def set_link_objects(self, objects):
324334

325335
def _setup_compile(self, outdir, macros, incdirs, sources, depends, extra):
326336
"""Process arguments and decide which source files to compile."""
327-
if outdir is None:
328-
outdir = self.output_dir
329-
elif not isinstance(outdir, str):
330-
raise TypeError("'output_dir' must be a string or None")
331-
332-
if macros is None:
333-
macros = self.macros
334-
elif isinstance(macros, list):
335-
macros = macros + (self.macros or [])
336-
else:
337-
raise TypeError("'macros' (if supplied) must be a list of tuples")
338-
339-
if incdirs is None:
340-
incdirs = self.include_dirs
341-
elif isinstance(incdirs, (list, tuple)):
342-
incdirs = list(incdirs) + (self.include_dirs or [])
343-
else:
344-
raise TypeError("'include_dirs' (if supplied) must be a list of strings")
337+
outdir, macros, incdirs = self._fix_compile_args(outdir, macros, incdirs)
345338

346339
if extra is None:
347340
extra = []
@@ -400,6 +393,9 @@ def _fix_compile_args(self, output_dir, macros, include_dirs):
400393
else:
401394
raise TypeError("'include_dirs' (if supplied) must be a list of strings")
402395

396+
# add include dirs for class
397+
include_dirs += self.__class__.include_dirs
398+
403399
return output_dir, macros, include_dirs
404400

405401
def _prep_compile(self, sources, output_dir, depends=None):
@@ -456,6 +452,9 @@ def _fix_lib_args(self, libraries, library_dirs, runtime_library_dirs):
456452
else:
457453
raise TypeError("'library_dirs' (if supplied) must be a list of strings")
458454

455+
# add library dirs for class
456+
library_dirs += self.__class__.library_dirs
457+
459458
if runtime_library_dirs is None:
460459
runtime_library_dirs = self.runtime_library_dirs
461460
elif isinstance(runtime_library_dirs, (list, tuple)):

‎distutils/tests/test_ccompiler.py‎

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import os
2+
import sys
3+
import platform
4+
import textwrap
5+
import sysconfig
6+
7+
import pytest
8+
9+
from distutils import ccompiler
10+
11+
12+
def _make_strs(paths):
13+
"""
14+
Convert paths to strings for legacy compatibility.
15+
"""
16+
if sys.version_info > (3, 8) and platform.system() != "Windows":
17+
return paths
18+
return list(map(os.fspath, paths))
19+
20+
21+
@pytest.fixture
22+
def c_file(tmp_path):
23+
c_file = tmp_path / 'foo.c'
24+
gen_headers = ('Python.h',)
25+
is_windows = platform.system() == "Windows"
26+
plat_headers = ('windows.h',) * is_windows
27+
all_headers = gen_headers + plat_headers
28+
headers = '\n'.join(f'#include <{header}>\n' for header in all_headers)
29+
payload = (
30+
textwrap.dedent(
31+
"""
32+
#headers
33+
void PyInit_foo(void) {}
34+
"""
35+
)
36+
.lstrip()
37+
.replace('#headers', headers)
38+
)
39+
c_file.write_text(payload)
40+
return c_file
41+
42+
43+
def test_set_include_dirs(c_file):
44+
"""
45+
Extensions should build even if set_include_dirs is invoked.
46+
In particular, compiler-specific paths should not be overridden.
47+
"""
48+
compiler = ccompiler.new_compiler()
49+
python = sysconfig.get_paths()['include']
50+
compiler.set_include_dirs([python])
51+
compiler.compile(_make_strs([c_file]))
52+
53+
# do it again, setting include dirs after any initialization
54+
compiler.set_include_dirs([python])
55+
compiler.compile(_make_strs([c_file]))

0 commit comments

Comments
 (0)