diff --git a/setup.py b/setup.py index 13d896eb4..b9d0cd7b0 100644 --- a/setup.py +++ b/setup.py @@ -1,541 +1,548 @@ #!/usr/bin/env python # -# This script only applies if you are performing a Python Distutils-based +# This script only applies if you are performing a Python setuptools-based # installation of PyMOL. # # It may assume that all of PyMOL's external dependencies are # pre-installed into the system. + import argparse -import glob import os +import platform import re import sys -import shutil - -from distutils.core import setup, Extension -from distutils.util import change_root - -import create_shadertext -import monkeypatch_distutils - -# Important: import 'distutils.command' modules after monkeypatch_distutils -from distutils.command.build_ext import build_ext -from distutils.command.build_py import build_py -from distutils.command.install import install +import time +from collections import defaultdict +from collections.abc import Iterable +from functools import cached_property +from pathlib import Path +from subprocess import PIPE, Popen +import numpy +from setuptools import Extension, setup # non-empty DEBUG variable turns off optimization and adds -g flag -DEBUG = bool(os.getenv('DEBUG', '')) - -WIN = sys.platform.startswith('win') -MAC = sys.platform.startswith('darwin') - -# handle extra arguments - - -class options: - osx_frameworks = True - jobs = int(os.getenv('JOBS', 0)) - no_libxml = False - no_glut = True - use_msgpackc = 'guess' - help_distutils = False - testing = False - openvr = False - use_openmp = 'no' if MAC else 'yes' - use_vtkm = 'no' - vmd_plugins = True - +DEBUG = bool(os.getenv("DEBUG", "")) + + +def create_all(generated_dir: str, pymoldir="."): + """ + Generate various stuff + """ + create_shadertext( + Path(pymoldir) / "data" / "shaders", + Path(generated_dir), + Path(generated_dir) / "ShaderText.h", + Path(generated_dir) / "ShaderText.cpp", + ) + create_buildinfo(generated_dir, pymoldir) + + +def create_shadertext( + shaderdir: Path, shaderdir2: Path, header: Path, source: Path +) -> None: + + include_deps = defaultdict(set) + ifdef_deps = defaultdict(set) + + # get all *.gs *.vs *.fs *.shared from the two input directories + shader_files = set() + for sdir in (shaderdir, shaderdir2): + shader_files.update(sdir.glob(".*[gs vs fs shared tsc tse]*")) + + varname = "_shader_cache_raw" + with header.open(mode="w") as header_file, source.open(mode="w") as source_file: + header_file.write(f"extern const char * {varname}[];\n") + source_file.write(f"const char * {varname}[] = {{\n") + + for file in sorted(shader_files): + source_file.write(f'"{file.name}", ""\n') + with file.open() as f: + for line in f: + line = line.strip() + + # skip blank lines and obvious comments + if not line or line.startswith("//") and not "*/" in line: + continue + + # write line, quoted, escaped and with a line feed + content = line.replace("\\", "\\\\").replace('"', r"\"") + source_file.write(f'"{content}\\n"\n') + + # include and ifdef dependencies + if line.startswith("#include"): + include_deps[line.split()[1]].add(file.name) + elif line.startswith("#ifdef") or line.startswith("#ifndef"): + ifdef_deps[line.split()[1]].add(file.name) + + source_file.write(",\n") + source_file.write("0};\n") + + # include and ifdef dependencies + for varname, deps in (("_include_deps", include_deps), ("_ifdef_deps", ifdef_deps)): + header_file.write(f"extern const char * {varname}[];\n") + source_file.write(f"const char * {varname}[] = {{\n") + for name, dependency in deps.items(): + source_file.write( + '"%s", "%s", 0,\n' % (name, '", "'.join(sorted(dependency))) + ) + source_file.write("0};\n") + + +def create_buildinfo(outputdir, pymoldir="."): -parser = argparse.ArgumentParser() -parser.add_argument('--glut', dest='no_glut', action="store_false", - help="link with GLUT (legacy GUI)") -parser.add_argument('--no-osx-frameworks', dest='osx_frameworks', - help="on MacOS use XQuartz instead of native frameworks", - action="store_false") -parser.add_argument('--jobs', '-j', type=int, help="for parallel builds " - "(defaults to number of processors)") -parser.add_argument('--no-libxml', action="store_true", - help="skip libxml2 dependency, disables COLLADA export") -parser.add_argument('--use-openmp', choices=('yes', 'no'), - help="Use OpenMP") -parser.add_argument('--use-vtkm', choices=('1.5', '1.6', '1.7', 'no'), - help="Use VTK-m for isosurface generation") -parser.add_argument('--use-msgpackc', choices=('c++11', 'c', 'guess', 'no'), - help="c++11: use msgpack-c header-only library; c: link against " - "shared library; no: disable fast MMTF load support") -parser.add_argument('--help-distutils', action="store_true", - help="show help for distutils options and exit") -parser.add_argument('--testing', action="store_true", - help="Build C-level tests") -parser.add_argument('--openvr', dest='openvr', action='store_true') -parser.add_argument('--no-vmd-plugins', dest='vmd_plugins', - action='store_false', - help='Disable VMD molfile plugins (libnetcdf dependency)') -options, sys.argv[1:] = parser.parse_known_args(namespace=options) - -if options.help_distutils: - sys.argv.append("--help") - -if True: - monkeypatch_distutils.set_parallel_jobs(options.jobs) - - -def forms_uic(build_lib='modules'): - ''' - Convert Qt UI files in "modules/pmg_qt/forms" to Python files in place - ''' - - -def get_prefix_path(): - ''' - Return a list of paths which will be searched for "include", - "include/freetype2", "lib", "lib64" etc. - ''' try: - return os.environ['PREFIX_PATH'].split(os.pathsep) - except KeyError: - pass - - if sys.platform.startswith("freebsd"): - return ["/usr/local"] - - X11 = ['/usr/X11'] * (not options.osx_frameworks) - - if sys.platform == 'darwin': - for prefix in ['/sw', '/opt/local', '/usr/local']: - if sys.base_prefix.startswith(prefix): - return [prefix] + X11 - - if is_conda_env(): - if sys.platform.startswith('win'): - return [os.path.join(sys.prefix, 'Library')] - - return [sys.prefix] + X11 - - return ['/usr'] + X11 - - -def is_conda_env(): - return ( - 'conda' in sys.prefix or - 'conda' in sys.version or - 'Continuum' in sys.version or - sys.prefix == os.getenv('CONDA_PREFIX')) - - -def guess_msgpackc(): - for prefix in prefix_path: - for suffix in ['h', 'hpp']: - f = os.path.join(prefix, 'include', 'msgpack', f'version_master.{suffix}') - - try: - m = re.search(r'MSGPACK_VERSION_MAJOR\s+(\d+)', open(f).read()) - except EnvironmentError: - continue - - if m is not None: - major = int(m.group(1)) - if major > 1: - return 'c++11' - - return 'no' - - -class build_ext_pymol(build_ext): - def initialize_options(self): - build_ext.initialize_options(self) - if DEBUG and not WIN: - self.debug = True - - -class build_py_pymol(build_py): - def run(self): - build_py.run(self) - forms_uic(self.build_lib) - - -class install_pymol(install): - pymol_path = None - bundled_pmw = False - no_launcher = False + sha = ( + Popen(["git", "rev-parse", "HEAD"], cwd=pymoldir, stdout=PIPE) + .stdout.read() + .strip() + .decode() + ) + except OSError: + sha = "" + + with open(os.path.join(outputdir, "PyMOLBuildInfo.h")) as out: + out.writelines( + [ + f"#define _PyMOL_BUILD_DATE {time.time()}", + f'#define _PYMOL_BUILD_GIT_SHA "{sha}"', + ] + ) + + +class PyMolBuild: + + def __init__( + self, + osx_frameworks=True, + no_libxml=False, + no_glut=True, + use_msgpackc="guess", + help_setuptools=False, + testing=False, + openvr=False, + use_openmp="yes", + use_vtkm="no", + vmd_plugins=True, + ) -> None: + self.osx_frameworks = osx_frameworks + self.no_libxml = no_libxml + self.no_glut = no_glut + self.use_msgpackc = use_msgpackc + self.help_setuptools = help_setuptools + self.testing = testing + self.openvr = openvr + self.use_openmp = "no" if self.is_macos else use_openmp + self.use_vtkm = use_vtkm + self.vmd_plugins = vmd_plugins + + self.pymol_src_dirs = [ + "ov/src", + "layer0", + "layer1", + "layer2", + "layer3", + "layer4", + "layer5", + ] + self.system_lib_names = ["png", "freetype"] + self.include_paths = ["include", numpy.get_include()] + self.library_paths = [] + self.macros = [ + ("_PYMOL_LIBPNG", None), + ("_PYMOL_FREETYPE", None), + ("_PYMOL_NUMPY", None), + ] + self.extra_link_args = [] + self.extra_compile_args = ( + [ + "-Werror=return-type", + "-Wunused-variable", + "-Wno-switch", + "-Wno-narrowing", + # legacy stuff"png", "freetype" + "-Wno-char-subscripts", + ] + if not self.is_windows + else ["/MP"] + ) + + @property + def version(self) -> str: + with open("layer0/Version.h") as f: + return re.findall(r'_PyMOL_VERSION "(.*)"', f.read())[0] + + @classmethod + def filter_by_extention( + cls, dirs: Iterable[str], extensions: Iterable[str] = (".c", ".cpp") + ) -> list[str]: + return [ + str(file_path) + for _dir in dirs + for file_path in Path(_dir).glob(f"{', '.join(extensions)}") + ] - user_options = install.user_options + [ - ('pymol-path=', None, 'PYMOL_PATH'), - ('bundled-pmw', None, 'install bundled Pmw module'), - ('no-launcher', None, 'skip installation of the pymol launcher'), - ] + @property + def build_dir_path(self) -> str: + path = Path(os.environ.get("PYMOL_BLD", "build")) / "generated" + return str(path) + + @cached_property + def is_windows(self) -> bool: + return platform.system() == "Windows" + + @cached_property + def is_macos(self) -> bool: + return platform.system() == "Darwin" + + @cached_property + def is_conda_env(self): + return ( + "conda" in sys.prefix + or "conda" in sys.version + or "Continuum" in sys.version + or sys.prefix == os.getenv("CONDA_PREFIX") + ) + + @cached_property + def preprocessed_lib_paths(self) -> list[Path]: + """ + Return a list of paths which will be searched for "include", + "include/freetype2", "lib", "lib64" etc. + """ + + if os.environ.get("PREFIX_PATH") is not None: + return [Path(path) for path in os.environ["PREFIX_PATH"].split(os.pathsep)] + + if platform.system() == "FreeBSD": + return [Path("/usr/local")] + + dirs = [] + + if not self.osx_frameworks: + dirs.append(Path("/usr/X11")) + + if self.is_macos: + for prefix in ("/sw", "/opt/local", "/usr/local"): + if sys.base_prefix.startswith(prefix): + dirs.append(Path(prefix)) + return dirs + + if self.is_conda_env: + if self.is_windows: + path = Path(sys.prefix) / "Library" + return [path] + + dirs.append(Path(sys.prefix)) + return dirs + + dirs.append(Path("/usr")) + return dirs + + def guess_msgpackc(self) -> str: + for possible_lib in self.preprocessed_lib_paths: + for suffix in ["h", "hpp"]: + version_file = ( + possible_lib / "include" / "msgpack" / f"version_master.{suffix}" + ) + if not version_file.exists(): + continue + try: + version = re.search( + r"MSGPACK_VERSION_MAJOR\s+(\d+)", version_file.read_text() + ) + except EnvironmentError: + continue + + if version is not None: + major = int(version.group(1)) + if major > 1: + return "c++11" + return "no" + + def render_debug_arg(self) -> None: + if DEBUG and not self.is_windows: + # bounds checking in STL containers + self.macros.append( + ("_GLIBCXX_ASSERTIONS", None), + ) + + optimize_level = "-Og" if DEBUG else "-O3" + self.extra_compile_args.append(optimize_level) + + def render_use_openmp_arg(self) -> None: + if self.use_openmp != "yes": + return + + self.macros.append(("PYMOL_OPENMP", None)) + if self.is_macos: + self.extra_compile_args.extend(["-Xpreprocessor", "-fopenmp"]) + self.system_lib_names.append("omp") + + elif self.is_windows: + self.extra_compile_args.append("/openmp") - def finalize_options(self): - install.finalize_options(self) + else: + self.extra_compile_args.append("-fopenmp") + self.extra_link_args.append("-fopenmp") + + def render_vmd_plugins_arg(self) -> None: + if not self.vmd_plugins: + return + self.include_paths.append("contrib/uiuc/plugins/include") + self.pymol_src_dirs.append("contrib/uiuc/plugins/molfile_plugin/src") + self.macros.append(("_PYMOL_VMD_PLUGINS", None)) + self.system_lib_names.append("netcdf") + + def render_no_libxml_arg(self) -> None: + if self.no_libxml: + return + self.macros.append(("_HAVE_LIBXML", None)) + self.system_lib_names.append("xml2") + + def render_use_msgpackc_arg(self) -> None: + if self.use_msgpackc == "guess": + self.use_msgpackc = self.guess_msgpackc() + + if self.use_msgpackc == "no": + self.macros.append(("_PYMOL_NO_MSGPACKC", None)) + else: + if self.use_msgpackc == "c++11": + self.macros.extend( + [ + ("MMTF_MSGPACK_USE_CPP11", None), + ("MSGPACK_NO_BOOST", None), + ] + ) + else: + self.system_lib_names.append("msgpackc") - self.pymol_path_is_default = self.pymol_path is None + self.pymol_src_dirs.append("contrib/mmtf-c") - if self.pymol_path is None: - self.pymol_path = os.path.join( - self.install_libbase, 'pymol', 'pymol_path') - elif self.root is not None: - self.pymol_path = change_root(self.root, self.pymol_path) + def render_no_glut_arg(self) -> None: + if self.no_glut: + self.macros.append(("_PYMOL_NO_MAIN", None)) - def run(self): - install.run(self) - self.install_pymol_path() + def render_testing_arg(self) -> None: + if self.testing: + self.pymol_src_dirs.append("layerCTest") + self.macros.append(("_PYMOL_CTEST", None)) - if not self.no_launcher: - self.make_launch_script() + def render_openvr_arg(self) -> None: + if self.openvr: + self.pymol_src_dirs.append("contrib/vr") + self.macros.append(("_PYMOL_OPENVR", None)) + self.system_lib_names.append("openvr_api") - if self.bundled_pmw: - raise Exception('--bundled-pmw has been removed, please install Pmw from ' - 'https://github.com/schrodinger/pmw-patched') + def render_use_vtkm(self) -> None: + if self.use_vtkm == "no": + return - def unchroot(self, name): - if self.root is not None and name.startswith(self.root): - return name[len(self.root):] - return name + for possible_lib in self.preprocessed_lib_paths: + vtkm_inc_dir = possible_lib / "include" / f"vtkm-{self.use_vtkm}" + if vtkm_inc_dir.exists(): + break + else: + raise LookupError( + f"VTK-m headers not found. PREFIX_PATH={':'.join(str(p) for p in self.preprocessed_lib_paths)}" + ) + + self.macros.append(("_PYMOL_VTKM", None)) + + self.include_paths.extend( + [ + str(vtkm_inc_dir), + str(vtkm_inc_dir / "/vtkm/thirdparty/diy/vtkmdiy/include"), + str(vtkm_inc_dir / "/vtkm/thirdparty/lcl/vtkmlcl"), + ] + ) + + if self.use_vtkm == "1.5": + self.include_paths.extend( + [ + str(vtkm_inc_dir / "/vtkm/thirdparty/diy"), + str(vtkm_inc_dir / "/vtkm/thirdparty/taotuple"), + ] + ) + self.system_lib_names.extend( + [ + f"vtkm_cont-{self.use_vtkm}", + ( + f"vtkm_filter-{self.use_vtkm}" + if self.use_vtkm == "1.5" + else f"vtkm_filter_contour-{self.use_vtkm}" + ), + ] + ) + + def render_macos(self) -> None: + self.system_lib_names.append("GLEW") + self.macros.append(("PYMOL_CURVE_VALIDATE", None)) + self.extra_compile_args.append("-fno-strict-aliasing") + + if self.osx_frameworks: + self.extra_link_args.extend(["-framework OpenGL"]) + if not self.no_glut: + self.extra_link_args.extend(["-framework GLUT"]) + + self.macros.append(("_PYMOL_OSX", None)) + return + + self.system_lib_names.append("GL") + if not self.no_glut: + self.system_lib_names.append("glut") + + def render_windows(self) -> None: + # clear + self.system_lib_names.clear() + + self.macros.append(("WIN32", None)) + self.system_lib_names.extend( + ["Advapi32", "Ws2_32", "glew32", "freetype", "libpng", "opengl32"] + ) + + if not self.no_glut: + self.system_lib_names.append("freeglut") + if not self.no_libxml: + self.system_lib_names.append("libxml2") - def copy_tree_nosvn(self, src, dst): - def ignore(src, names): return set([ - ]).intersection(names) - if os.path.exists(dst): - shutil.rmtree(dst) - print('copying %s -> %s' % (src, dst)) - shutil.copytree(src, dst, ignore=ignore) + if DEBUG: + self.extra_compile_args.append("/Z7") + self.extra_link_args.append("/DEBUG") + + # TODO: Remove when we move to setup-CMake + self.extra_compile_args.append("/std:c++17") + + def render_unix_like(self) -> None: + self.system_lib_names.extend(["GL", "GLEW"]) + if not self.no_glut: + self.system_lib_names.append("glut") + + def render_os_specific(self) -> None: + if self.is_macos: + self.render_macos() + elif self.is_windows: + self.render_windows() + else: + self.render_unix_like() + + def parse_include_paths(self) -> None: + expected_paths = [ + "include", + "include/freetype2", + "include/libxml2", + "include/openvr", + ] + for possible_lib in self.preprocessed_lib_paths: + for expected_path in expected_paths: + path = possible_lib / expected_path + if not path.exists(): + continue + self.include_paths.append(str(path)) + + def parse_library_paths(self) -> None: + expected_paths = [ + "lib", + "lib64", + ] + for possible_lib in self.preprocessed_lib_paths: + for expected_path in expected_paths: + path = possible_lib / expected_path + if not path.exists(): + continue + self.library_paths.append(str(path)) - def copy(self, src, dst): - copy = self.copy_tree_nosvn if os.path.isdir(src) else self.copy_file - copy(src, dst) + def external_step(self) -> None: + self.pymol_src_dirs.append(self.build_dir_path) + self.include_paths.extend(self.pymol_src_dirs) - def install_pymol_path(self): - self.mkpath(self.pymol_path) - for name in ['LICENSE', 'data', 'test', 'examples', ]: - self.copy(name, os.path.join(self.pymol_path, name)) + def process_args(self) -> None: + self.render_debug_arg() + self.render_use_openmp_arg() + self.render_vmd_plugins_arg() + self.render_no_libxml_arg() + self.render_use_msgpackc_arg() + self.render_no_glut_arg() + self.render_testing_arg() + self.render_openvr_arg() + self.render_use_vtkm() - if options.openvr: - self.copy('contrib/vr/README.md', - os.path.join(self.pymol_path, 'README-VR.txt')) + self.render_os_specific() - def make_launch_script(self): - if sys.platform.startswith('win'): - launch_script = 'pymol.bat' - else: - launch_script = 'pymol' + self.parse_include_paths() + self.parse_library_paths() - self.mkpath(self.install_scripts) - launch_script = os.path.join(self.install_scripts, launch_script) + self.external_step() - python_exe = os.path.abspath(sys.executable) - pymol_file = self.unchroot(os.path.join( - self.install_libbase, 'pymol', '__init__.py')) - pymol_path = self.unchroot(self.pymol_path) - with open(launch_script, 'w') as out: - if sys.platform.startswith('win'): - # paths relative to launcher, if possible - try: - python_exe = '%~dp0\\' + \ - os.path.relpath(python_exe, self.install_scripts) - except ValueError: - pass - try: - pymol_file = '%~dp0\\' + \ - os.path.relpath(pymol_file, self.install_scripts) - except ValueError: - pymol_file = os.path.abspath(pymol_file) - - if not self.pymol_path_is_default: - out.write(f'set PYMOL_PATH={pymol_path}' + os.linesep) - out.write('"%s" "%s"' % (python_exe, pymol_file)) - out.write(' %*' + os.linesep) - else: - out.write('#!/bin/sh' + os.linesep) - if not self.pymol_path_is_default: - out.write(f'export PYMOL_PATH="{pymol_path}"' + os.linesep) - out.write('exec "%s" "%s" "$@"' % - (python_exe, pymol_file) + os.linesep) - - os.chmod(launch_script, 0o755) - -# ============================================================================ - - -# should be something like (build_base + "/generated"), but that's only -# known to build and install instances -generated_dir = os.path.join(os.environ.get("PYMOL_BLD", "build"), "generated") - -create_shadertext.create_all(generated_dir) - -# can be changed with environment variable PREFIX_PATH -prefix_path = get_prefix_path() - -inc_dirs = [ - "include", -] - -pymol_src_dirs = [ - "ov/src", - "layer0", - "layer1", - "layer2", - "layer3", - "layer4", - "layer5", - generated_dir, -] - -def_macros = [ - ("_PYMOL_LIBPNG", None), - ("_PYMOL_FREETYPE", None), -] - -if DEBUG and not WIN: - def_macros += [ - # bounds checking in STL containers - ("_GLIBCXX_ASSERTIONS", None), - ] - -libs = ["png", "freetype"] -lib_dirs = [] -ext_comp_args = [ - "-Werror=return-type", - "-Wunused-variable", - "-Wno-switch", - "-Wno-narrowing", - # legacy stuff - '-Wno-char-subscripts', - # optimizations - "-Og" if DEBUG else "-O3", -] if not WIN else [] -ext_link_args = [] -ext_objects = [] -data_files = [] -ext_modules = [] - -if options.use_openmp == 'yes': - def_macros += [ - ("PYMOL_OPENMP", None), - ] - if MAC: - ext_comp_args += ["-Xpreprocessor", "-fopenmp"] - libs += ["omp"] - elif WIN: - ext_comp_args += ["/openmp"] - else: - ext_comp_args += ["-fopenmp"] - ext_link_args += ["-fopenmp"] - -if options.vmd_plugins: - # VMD plugin support - inc_dirs += [ - 'contrib/uiuc/plugins/include', - ] - pymol_src_dirs += [ - 'contrib/uiuc/plugins/molfile_plugin/src', - ] - def_macros += [ - ("_PYMOL_VMD_PLUGINS", None), - ] - -if not options.no_libxml: - # COLLADA support - def_macros += [ - ("_HAVE_LIBXML", None) - ] - libs += ["xml2"] - -if options.use_msgpackc == 'guess': - options.use_msgpackc = guess_msgpackc() - -if options.use_msgpackc == 'no': - def_macros += [("_PYMOL_NO_MSGPACKC", None)] -else: - if options.use_msgpackc == 'c++11': - def_macros += [ - ("MMTF_MSGPACK_USE_CPP11", None), - ("MSGPACK_NO_BOOST", None), - ] - else: - libs += ['msgpackc'] - - pymol_src_dirs += ["contrib/mmtf-c"] - -if options.no_glut: - def_macros += [ - ("_PYMOL_NO_MAIN", None), - ] - -if options.testing: - pymol_src_dirs += ["layerCTest"] - def_macros += [("_PYMOL_CTEST", None)] - -if options.openvr: - def_macros += [("_PYMOL_OPENVR", None)] - pymol_src_dirs += [ - "contrib/vr", - ] - -inc_dirs += pymol_src_dirs - -# ============================================================================ -if MAC: - libs += ["GLEW"] - def_macros += [("PYMOL_CURVE_VALIDATE", None)] - - if options.osx_frameworks: - ext_link_args += [ - "-framework", "OpenGL", - ] + (not options.no_glut) * [ - "-framework", "GLUT", - ] - def_macros += [ - ("_PYMOL_OSX", None), - ] - else: - libs += [ - "GL", - ] + (not options.no_glut) * [ - "glut", - ] +parser = argparse.ArgumentParser() +parser.add_argument( + "--glut", dest="no_glut", action="store_false", help="link with GLUT (legacy GUI)" +) +parser.add_argument( + "--no-osx-frameworks", + dest="osx_frameworks", + help="on MacOS use XQuartz instead of native frameworks", + action="store_false", +) +parser.add_argument( + "--no-libxml", + action="store_true", + help="skip libxml2 dependency, disables COLLADA export", +) +parser.add_argument("--use-openmp", choices=("yes", "no"), help="Use OpenMP") +parser.add_argument( + "--use-vtkm", + choices=("1.5", "1.6", "1.7", "no"), + help="Use VTK-m for isosurface generation", +) +parser.add_argument( + "--use-msgpackc", + choices=("c++11", "c", "guess", "no"), + help="c++11: use msgpack-c header-only library; c: link against " + "shared library; no: disable fast MMTF load support", +) +parser.add_argument( + "--help-setuptools", + action="store_true", + help="show help for setuptools options and exit", +) +parser.add_argument("--testing", action="store_true", help="Build C-level tests") +parser.add_argument("--openvr", dest="openvr", action="store_true") +parser.add_argument( + "--no-vmd-plugins", + dest="vmd_plugins", + action="store_false", + help="Disable VMD molfile plugins (libnetcdf dependency)", +) +knows_args = parser.parse_args() -if WIN: - # clear - libs = [] - - def_macros += [ - ("WIN32", None), - ] - - libs += [ - "Advapi32", # Registry (RegCloseKey etc.) - "Ws2_32", # htonl - ] - - if True: - libs += [ - "glew32", - "freetype", - "libpng", - ] + (not options.no_glut) * [ - "freeglut", - ] + (not options.no_libxml) * [ - "libxml2", - ] +pymol_build = PyMolBuild(**vars(knows_args)) - if DEBUG: - ext_comp_args += ['/Z7'] - ext_link_args += ['/DEBUG'] +if pymol_build.help_setuptools: + sys.argv.append("--help") - libs += [ - "opengl32", - ] - # TODO: Remove when we move to setup-CMake - ext_comp_args += ["/std:c++17"] - -if not (MAC or WIN): - libs += [ - "GL", - "GLEW", - ] + (not options.no_glut) * [ - "glut", - ] - -if options.use_vtkm != "no": - for prefix in prefix_path: - vtkm_inc_dir = os.path.join( - prefix, "include", f"vtkm-{options.use_vtkm}") - if os.path.exists(vtkm_inc_dir): - break - else: - raise LookupError('VTK-m headers not found.' - f' PREFIX_PATH={":".join(prefix_path)}') - def_macros += [ - ("_PYMOL_VTKM", None), - ] - inc_dirs += [ - vtkm_inc_dir, - vtkm_inc_dir + "/vtkm/thirdparty/diy/vtkmdiy/include", - vtkm_inc_dir + "/vtkm/thirdparty/lcl/vtkmlcl", - ] + (options.use_vtkm == "1.5") * [ - vtkm_inc_dir + "/vtkm/thirdparty/diy", - vtkm_inc_dir + "/vtkm/thirdparty/taotuple", - ] - libs += [ - f"vtkm_cont-{options.use_vtkm}", - f"vtkm_filter-{options.use_vtkm}" if options.use_vtkm == "1.5" else - f"vtkm_filter_contour-{options.use_vtkm}", - ] - -if options.vmd_plugins: - libs += [ - "netcdf", - ] - -if options.openvr: - libs += [ - "openvr_api", - ] - -if True: - try: - import numpy - inc_dirs += [ - numpy.get_include(), - ] - def_macros += [ - ("_PYMOL_NUMPY", None), - ] - except ImportError: - print("numpy not available") - -if True: - for prefix in prefix_path: - for dirs, suffixes in [ - [inc_dirs, [("include",), ("include", "freetype2"), - ("include", "libxml2"), ("include", "openvr")]], - [lib_dirs, [("lib64",), ("lib",)]], - ]: - dirs.extend( - filter(os.path.isdir, [os.path.join(prefix, *s) for s in suffixes])) - -if True: - # optimization currently causes a clang segfault on OS X 10.9 when - # compiling layer2/RepCylBond.cpp - if sys.platform == 'darwin': - ext_comp_args += ["-fno-strict-aliasing"] - - -def get_pymol_version(): - return re.findall(r'_PyMOL_VERSION "(.*)"', open('layer0/Version.h').read())[0] - - -def get_sources(subdirs, suffixes=('.c', '.cpp')): - return sorted([f for d in subdirs for s in suffixes for f in glob.glob(d + '/*' + s)]) - - -ext_modules += [ - Extension("pymol._cmd", - get_sources(pymol_src_dirs), - include_dirs=inc_dirs, - libraries=libs, - library_dirs=lib_dirs, - define_macros=def_macros, - extra_link_args=ext_link_args, - extra_compile_args=ext_comp_args, - extra_objects=ext_objects, - ), - - Extension("chempy.champ._champ", - get_sources(['contrib/champ']), - include_dirs=["contrib/champ"], - ), -] - -distribution = setup( # Distribution meta-data - cmdclass={ - 'build_ext': build_ext_pymol, - 'build_py': build_py_pymol, - 'install': install_pymol, - }, - version=get_pymol_version(), - ext_modules=ext_modules, +pymol_build.process_args() +create_all(pymol_build.build_dir_path) + +setup( + version=pymol_build.version, + ext_modules=[ + Extension( + name="pymol._cmd", + sources=PyMolBuild.filter_by_extention(pymol_build.pymol_src_dirs), + libraries=pymol_build.system_lib_names, + include_dirs=pymol_build.include_paths, + library_dirs=pymol_build.library_paths, + define_macros=pymol_build.macros, + extra_link_args=pymol_build.extra_link_args, + extra_compile_args=pymol_build.extra_compile_args, + ), + Extension( + name="chempy.champ._champ", + sources=PyMolBuild.filter_by_extention(["contrib/champ"]), + include_dirs=["contrib/champ"], + ), + ], )