Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure Windows SDK directories are not cleared when caller specifies include/library dirs #153

Merged
merged 15 commits into from
Aug 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 13 additions & 8 deletions distutils/_msvccompiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,18 @@ def __init__(self, verbose=0, dry_run=0, force=0):
self.plat_name = None
self.initialized = False

@classmethod
def _configure(cls, vc_env):
"""
Set class-level include/lib dirs.
"""
cls.include_dirs = cls._parse_path(vc_env.get('include', ''))
cls.library_dirs = cls._parse_path(vc_env.get('lib', ''))

@staticmethod
def _parse_path(val):
return [dir.rstrip(os.sep) for dir in val.split(os.pathsep) if dir]

def initialize(self, plat_name=None):
# multi-init means we would need to check platform same each time...
assert not self.initialized, "don't init multiple times"
Expand All @@ -243,6 +255,7 @@ def initialize(self, plat_name=None):
raise DistutilsPlatformError(
"Unable to find a compatible " "Visual Studio installation."
)
self._configure(vc_env)

self._paths = vc_env.get('path', '')
paths = self._paths.split(os.pathsep)
Expand All @@ -253,14 +266,6 @@ def initialize(self, plat_name=None):
self.mc = _find_exe("mc.exe", paths) # message compiler
self.mt = _find_exe("mt.exe", paths) # message compiler

for dir in vc_env.get('include', '').split(os.pathsep):
if dir:
self.add_include_dir(dir.rstrip(os.sep))

for dir in vc_env.get('lib', '').split(os.pathsep):
if dir:
self.add_library_dir(dir.rstrip(os.sep))

self.preprocess_options = None
# bpo-38597: Always compile with dynamic linking
# Future releases of Python 3.x will include all past
Expand Down
35 changes: 17 additions & 18 deletions distutils/ccompiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,16 @@ class CCompiler:
}
language_order = ["c++", "objc", "c"]

include_dirs = []
"""
include dirs specific to this compiler class
"""

library_dirs = []
"""
library dirs specific to this compiler class
"""

def __init__(self, verbose=0, dry_run=0, force=0):
self.dry_run = dry_run
self.force = force
Expand Down Expand Up @@ -324,24 +334,7 @@ def set_link_objects(self, objects):

def _setup_compile(self, outdir, macros, incdirs, sources, depends, extra):
"""Process arguments and decide which source files to compile."""
if outdir is None:
outdir = self.output_dir
elif not isinstance(outdir, str):
raise TypeError("'output_dir' must be a string or None")

if macros is None:
macros = self.macros
elif isinstance(macros, list):
macros = macros + (self.macros or [])
else:
raise TypeError("'macros' (if supplied) must be a list of tuples")

if incdirs is None:
incdirs = self.include_dirs
elif isinstance(incdirs, (list, tuple)):
incdirs = list(incdirs) + (self.include_dirs or [])
else:
raise TypeError("'include_dirs' (if supplied) must be a list of strings")
outdir, macros, incdirs = self._fix_compile_args(outdir, macros, incdirs)

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

# add include dirs for class
include_dirs += self.__class__.include_dirs

return output_dir, macros, include_dirs

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

# add library dirs for class
library_dirs += self.__class__.library_dirs

if runtime_library_dirs is None:
runtime_library_dirs = self.runtime_library_dirs
elif isinstance(runtime_library_dirs, (list, tuple)):
Expand Down
55 changes: 55 additions & 0 deletions distutils/tests/test_ccompiler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import os
import sys
import platform
import textwrap
import sysconfig

import pytest

from distutils import ccompiler


def _make_strs(paths):
"""
Convert paths to strings for legacy compatibility.
"""
if sys.version_info > (3, 8) and platform.system() != "Windows":
return paths
return list(map(os.fspath, paths))


@pytest.fixture
def c_file(tmp_path):
c_file = tmp_path / 'foo.c'
gen_headers = ('Python.h',)
is_windows = platform.system() == "Windows"
plat_headers = ('windows.h',) * is_windows
all_headers = gen_headers + plat_headers
headers = '\n'.join(f'#include <{header}>\n' for header in all_headers)
payload = (
textwrap.dedent(
"""
#headers
void PyInit_foo(void) {}
"""
)
.lstrip()
.replace('#headers', headers)
)
c_file.write_text(payload)
return c_file


def test_set_include_dirs(c_file):
"""
Extensions should build even if set_include_dirs is invoked.
In particular, compiler-specific paths should not be overridden.
"""
compiler = ccompiler.new_compiler()
python = sysconfig.get_paths()['include']
compiler.set_include_dirs([python])
compiler.compile(_make_strs([c_file]))

# do it again, setting include dirs after any initialization
compiler.set_include_dirs([python])
compiler.compile(_make_strs([c_file]))