|
3 | 3 | # Extensions script for diffpy.pdffit2 |
4 | 4 |
|
5 | 5 | 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 |
9 | 12 |
|
10 | 13 | from setuptools import Extension, setup |
| 14 | +from setuptools.command.build_ext import build_ext |
11 | 15 |
|
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)) |
19 | 145 |
|
20 | 146 |
|
21 | 147 | 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 | + } |
23 | 188 | 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, |
25 | 192 | ) |
26 | 193 | return [ext] |
27 | 194 |
|
28 | 195 |
|
29 | 196 | # Extensions not included in pyproject.toml |
30 | 197 | setup_args = dict( |
31 | 198 | ext_modules=[], |
| 199 | + cmdclass={"build_ext": CustomBuildExt}, |
32 | 200 | ) |
33 | 201 |
|
34 | 202 |
|
|
0 commit comments