Skip to content

Commit f7bc156

Browse files
committed
fix: revert setup.py and conftest.py to original
1 parent 8b93472 commit f7bc156

File tree

2 files changed

+211
-12
lines changed

2 files changed

+211
-12
lines changed

setup.py

Lines changed: 180 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,200 @@
33
# Extensions script for diffpy.pdffit2
44

55
import glob
6-
import os # noqa
7-
import re # noqa
8-
import sys # noqa
6+
import os
7+
import re
8+
import shutil
9+
import sys
10+
import warnings
11+
from pathlib import Path
912

1013
from setuptools import Extension, setup
14+
from setuptools.command.build_ext import build_ext
1115

12-
# Define extension arguments here
13-
ext_kws = {
14-
"libraries": [],
15-
"extra_compile_args": [],
16-
"extra_link_args": [],
17-
"include_dirs": [],
18-
}
16+
# Use this version when git data are not available, like in git zip archive.
17+
# Update when tagging a new release.
18+
FALLBACK_VERSION = "1.4.3"
19+
20+
MYDIR = str(Path(__file__).parent.resolve())
21+
22+
# Helper functions -----------------------------------------------------------
23+
24+
25+
def get_compiler_type():
26+
"""Return the compiler type used during the build."""
27+
cc_arg = [a for a in sys.argv if a.startswith("--compiler=")]
28+
if cc_arg:
29+
return cc_arg[-1].split("=", 1)[1]
30+
from distutils.ccompiler import new_compiler
31+
32+
return new_compiler().compiler_type
33+
34+
35+
def get_gsl_config():
36+
"""
37+
Determine the GSL include and library directories by trying in order:
38+
1) CONDA_PREFIX,
39+
2) GSL_PATH,
40+
3) gsl-config (for Unix-like systems).
41+
Raises EnvironmentError if none are found.
42+
"""
43+
rv = {"include_dirs": [], "library_dirs": []}
44+
45+
# 1. Check using CONDA_PREFIX.
46+
conda_prefix = os.environ.get("CONDA_PREFIX", "")
47+
if conda_prefix:
48+
if os.name == "nt":
49+
inc = Path(conda_prefix) / "Library" / "include"
50+
lib = Path(conda_prefix) / "Library" / "lib"
51+
else:
52+
inc = Path(conda_prefix) / "include"
53+
lib = Path(conda_prefix) / "lib"
54+
if inc.is_dir() and lib.is_dir():
55+
rv["include_dirs"].append(str(inc))
56+
rv["library_dirs"].append(str(lib))
57+
return rv
58+
else:
59+
warnings.warn(
60+
f"CONDA_PREFIX is set to {conda_prefix}, "
61+
"but GSL not found at those paths. Proceeding..."
62+
)
63+
64+
# 2. Check using GSL_PATH.
65+
gsl_path = os.environ.get("GSL_PATH", "")
66+
if gsl_path:
67+
inc = Path(gsl_path) / "include"
68+
lib = Path(gsl_path) / "lib"
69+
if inc.is_dir() and lib.is_dir():
70+
rv["include_dirs"].append(str(inc))
71+
rv["library_dirs"].append(str(lib))
72+
return rv
73+
else:
74+
raise EnvironmentError(
75+
f"GSL_PATH={gsl_path} is set, but {inc} or {lib} not found. "
76+
"Please verify your GSL_PATH."
77+
)
78+
79+
# 3. Try using the gsl-config executable (only on Unix-like systems).
80+
if os.name != "nt":
81+
path_dirs = os.environ.get("PATH", "").split(os.pathsep)
82+
gslcfg_paths = [Path(p) / "gsl-config" for p in path_dirs if p]
83+
gslcfg_paths = [p for p in gslcfg_paths if p.is_file()]
84+
if gslcfg_paths:
85+
gslcfg = gslcfg_paths[0]
86+
txt = gslcfg.read_text()
87+
prefix_match = re.search(r"(?m)^prefix=(.+)", txt)
88+
include_match = re.search(r"(?m)^[^#]*\s-I(\S+)", txt)
89+
lib_match = re.search(r"(?m)^[^#]*\s-L(\S+)", txt)
90+
if prefix_match:
91+
prefix_path = Path(prefix_match.group(1))
92+
inc_dir = (
93+
include_match.group(1)
94+
if include_match
95+
else (prefix_path / "include")
96+
)
97+
lib_dir = (
98+
lib_match.group(1) if lib_match else (prefix_path / "lib")
99+
)
100+
rv["include_dirs"].append(str(inc_dir))
101+
rv["library_dirs"].append(str(lib_dir))
102+
return rv
103+
else:
104+
raise RuntimeError(f"Cannot parse 'prefix=' from {gslcfg}.")
105+
else:
106+
warnings.warn(
107+
"No gsl-config found in PATH. GSL may not be installed or not in PATH. "
108+
"Proceeding without GSL configuration."
109+
)
110+
111+
# 4. Nothing found: raise error.
112+
raise EnvironmentError(
113+
"Unable to locate GSL:\n"
114+
"1) CONDA_PREFIX not set or no GSL there\n"
115+
"2) GSL_PATH not set or invalid\n"
116+
"3) gsl-config not available\n"
117+
"Please set GSL_PATH or use a conda environment with GSL."
118+
)
119+
120+
121+
class CustomBuildExt(build_ext):
122+
def run(self):
123+
# Retrieve the GSL library directories and append them to each extension.
124+
gsl_cfg = get_gsl_config()
125+
lib_dirs = gsl_cfg.get("library_dirs", [])
126+
for ext in self.extensions:
127+
# Add gsl lib for linking.
128+
ext.library_dirs.extend(lib_dirs)
129+
# Embed RPATH flags, runtime linking without LD_LIBRARY_PATH.
130+
ext.extra_link_args = ext.extra_link_args or []
131+
for lib in lib_dirs:
132+
ext.extra_link_args.append(f"-Wl,-rpath,{lib}")
133+
super().run()
134+
# Avoid dll error
135+
gsl_path = (
136+
Path(os.environ.get("GSL_PATH"))
137+
if os.environ.get("GSL_PATH")
138+
else Path(os.environ.get("CONDA_PREFIX", "")) / "Library"
139+
)
140+
bin_path = gsl_path / "bin"
141+
dest_path = Path(self.build_lib) / "diffpy" / "pdffit2"
142+
dest_path.mkdir(parents=True, exist_ok=True)
143+
for dll_file in bin_path.glob("gsl*.dll"):
144+
shutil.copy(str(dll_file), str(dest_path))
19145

20146

21147
def create_extensions():
22-
"Initialize Extension objects for the setup function."
148+
"""Create the list of Extension objects for the build."""
149+
# lazy evaluation prevents build sdist failure
150+
try:
151+
gcfg = get_gsl_config()
152+
except EnvironmentError:
153+
return []
154+
155+
libraries = ["gsl"]
156+
157+
include_dirs = [MYDIR] + gcfg["include_dirs"]
158+
library_dirs = gcfg["library_dirs"]
159+
define_macros = []
160+
extra_objects = []
161+
extra_compile_args = []
162+
extra_link_args = []
163+
164+
compiler_type = get_compiler_type()
165+
if compiler_type in ("unix", "cygwin", "mingw32"):
166+
extra_compile_args = [
167+
"-std=c++11",
168+
"-Wall",
169+
"-Wno-write-strings",
170+
"-O3",
171+
"-funroll-loops",
172+
"-ffast-math",
173+
]
174+
elif compiler_type == "msvc":
175+
define_macros += [("_USE_MATH_DEFINES", None)]
176+
extra_compile_args = ["/EHs"]
177+
178+
# Extension keyword arguments.
179+
ext_kws = {
180+
"include_dirs": include_dirs,
181+
"libraries": libraries,
182+
"library_dirs": library_dirs,
183+
"define_macros": define_macros,
184+
"extra_compile_args": extra_compile_args,
185+
"extra_link_args": extra_link_args,
186+
"extra_objects": extra_objects,
187+
}
23188
ext = Extension(
24-
"diffpy.pdffit2.pdffit2", glob.glob("src/extensions/*.cpp"), **ext_kws
189+
"diffpy.pdffit2.pdffit2",
190+
glob.glob("src/extensions/**/*.cc"),
191+
**ext_kws,
25192
)
26193
return [ext]
27194

28195

29196
# Extensions not included in pyproject.toml
30197
setup_args = dict(
31198
ext_modules=[],
199+
cmdclass={"build_ext": CustomBuildExt},
32200
)
33201

34202

tests/conftest.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1+
import io
12
import json
23
from pathlib import Path
34

45
import pytest
56

7+
import diffpy.pdffit2
8+
import diffpy.pdffit2.output # assuming this is the correct import path
9+
610

711
@pytest.fixture
812
def user_filesystem(tmp_path):
@@ -17,3 +21,30 @@ def user_filesystem(tmp_path):
1721
json.dump(home_config_data, f)
1822

1923
yield tmp_path
24+
25+
26+
@pytest.fixture
27+
def datafile():
28+
"""Fixture to dynamically load any test file."""
29+
30+
def _load(filename):
31+
return "tests/testdata/" + filename
32+
33+
return _load
34+
35+
36+
@pytest.fixture
37+
def capture_output():
38+
"""Capture output from pdffit2 engine produced in function call."""
39+
40+
def _capture(f, *args, **kwargs):
41+
savestdout = diffpy.pdffit2.output.stdout
42+
fp = io.StringIO()
43+
diffpy.pdffit2.redirect_stdout(fp)
44+
try:
45+
f(*args, **kwargs)
46+
finally:
47+
diffpy.pdffit2.redirect_stdout(savestdout)
48+
return fp.getvalue()
49+
50+
return _capture

0 commit comments

Comments
 (0)