diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b14c1c307..f0e8025f2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,6 +30,7 @@ jobs: python3-numpy python3-pil python3-pytest + python3-pip - name: Install collada2gltf run: | @@ -43,13 +44,13 @@ jobs: - name: Build run: | - python setup.py --testing install --prefix=install-prefix + pip install -v --config-settings testing=True . env: DEBUG: 1 - name: Test run: | - ./install-prefix/bin/pymol -ckqy testing/testing.py --run all + pymol -ckqy testing/testing.py --run all build-Windows: @@ -72,7 +73,7 @@ jobs: shell: cmd run: |- CALL %CONDA_ROOT%\\Scripts\\activate.bat - conda install -y -c conda-forge -c schrodinger python cmake libpng freetype pyqt glew libxml2 numpy=1.26.4 catch2=2.13.3 glm libnetcdf collada2gltf biopython pillow msgpack-python pytest + conda install -y -c conda-forge -c schrodinger python cmake libpng freetype pyqt glew libxml2 numpy=1.26.4 catch2=2.13.3 glm libnetcdf collada2gltf biopython pillow msgpack-python pytest pip python-build - name: Conda info shell: cmd @@ -92,13 +93,13 @@ jobs: shell: cmd run: | CALL %CONDA_ROOT%\\Scripts\\activate.bat - python setup.py --testing install --prefix=%GITHUB_WORKSPACE%\\install-prefix + pip install -v --config-settings testing=True . - name: Test shell: cmd run: | CALL %CONDA_ROOT%\\Scripts\\activate.bat - %GITHUB_WORKSPACE%\\install-prefix\\Scripts\\pymol.bat -ckqy testing\\testing.py --run all + pymol.bat -ckqy testing\\testing.py --run all build-MacOS: @@ -115,7 +116,7 @@ jobs: bash $CONDA_ROOT.sh -b -p $CONDA_ROOT export PATH="$CONDA_ROOT/bin:$PATH" conda config --set quiet yes - conda install -y -c conda-forge -c schrodinger python cmake libpng freetype pyqt glew libxml2 numpy=1.26.4 catch2=2.13.3 glm libnetcdf collada2gltf biopython pillow msgpack-python pytest + conda install -y -c conda-forge -c schrodinger python cmake libpng freetype pyqt glew libxml2 numpy=1.26.4 catch2=2.13.3 glm libnetcdf collada2gltf biopython pillow msgpack-python pytest pip python-build conda info - name: Get additional sources @@ -129,9 +130,9 @@ jobs: run: |- export MACOSX_DEPLOYMENT_TARGET=12.0 export PATH="$CONDA_ROOT/bin:$PATH" - python setup.py install --prefix=${GITHUB_WORKSPACE}/install-prefix + pip install -v --config-settings testing=True . - name: Test run: |- export PATH="$CONDA_ROOT/bin:$PATH" - ${GITHUB_WORKSPACE}/install-prefix/bin/pymol -ckqy testing/testing.py --run all + pymol -ckqy testing/testing.py --run all diff --git a/_custom_build/backend.py b/_custom_build/backend.py new file mode 100644 index 000000000..9b190597c --- /dev/null +++ b/_custom_build/backend.py @@ -0,0 +1,36 @@ +# Adopted from pillow and pycapnp + +import sys + +from setuptools.build_meta import build_wheel + +backend_class = build_wheel.__self__.__class__ + + +class _CustomBuildMetaBackend(backend_class): + def run_setup(self, setup_script="setup.py"): + if self.config_settings: + flags = [ + f"--{k}={v}" + for k, v in self.config_settings.items() + ] + sys.argv = sys.argv[:1] + ["build_ext"] + flags + sys.argv[1:] + return super().run_setup(setup_script) + + def build_wheel( + self, wheel_directory, config_settings=None, metadata_directory=None + ): + self.config_settings = config_settings + return super().build_wheel(wheel_directory, config_settings, metadata_directory) + + def build_editable( + self, wheel_directory, config_settings=None, metadata_directory=None + ): + self.config_settings = config_settings + return super().build_editable( + wheel_directory, config_settings, metadata_directory + ) + +_backend = _CustomBuildMetaBackend() +build_wheel = _backend.build_wheel +build_editable = _backend.build_editable diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..d8629e40a --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,49 @@ +[project] +name = "pymol" +readme = "README.md" +requires-python = ">=3.9" +dynamic=["version"] +license = {file = "LICENSE"} +description = """ +PyMOL is a Python-enhanced molecular graphics tool. +It excels at 3D visualization of proteins, small molecules, density, +surfaces, and trajectories. It also includes molecular editing, +ray tracing, and movies. Open Source PyMOL is free to everyone! +""" +authors = [ + {name = "Schrodinger", email = "pymol-users@lists.sourceforge.net"}, +] +dependencies = [ + "numpy>=1.26.4,<2", +] + +[build-system] +build-backend = "backend" +backend-path = ["_custom_build"] +requires = [ + "numpy>=1.26.4,<2", + "setuptools>=69.2.0", +] + +[project.optional-dependencies] +test = [ + "pillow==10.3.0", + "pytest==8.2.2", +] + +[project.urls] +Homepage = "https://pymol.org" +Documentation = "https://pymol.org/dokuwiki" +Repository = "https://github.com/schrodinger/pymol-open-source" +"Bug Tracker" = "https://github.com/schrodinger/pymol-open-source/issues" +Changelog = "https://github.com/schrodinger/pymol-open-source/blob/master/ChangeLog" + +[project.scripts] +pymol = "pymol:launch" + +[tool.setuptools.packages.find] +where = ["modules"] + +[tool.setuptools.package-data] +pmg_qt = ["forms/*.ui"] + diff --git a/setup.py b/setup.py index 83111878b..4f3538632 100644 --- a/setup.py +++ b/setup.py @@ -8,144 +8,310 @@ import argparse import glob +import io as cStringIO import os import pathlib import re +import shutil import sys import sysconfig -import shutil +import time +from collections import defaultdict +from subprocess import PIPE, Popen -from setuptools import setup, Extension +import numpy +from setuptools import Extension, setup from setuptools.command.build_ext import build_ext from setuptools.command.build_py import build_py from setuptools.command.install import install -import create_shadertext - # non-empty DEBUG variable turns off optimization and adds -g flag -DEBUG = bool(os.getenv('DEBUG', '')) +DEBUG = bool(os.getenv("DEBUG", "")) +WIN = sys.platform.startswith("win") +MAC = sys.platform.startswith("darwin") + + +# have to be moved. Full explanation +# https://github.com/pypa/setuptools/issues/3939 +def create_all(generated_dir, pymoldir="."): + """ + Generate various stuff + """ + create_shadertext( + os.path.join(pymoldir, "data", "shaders"), + generated_dir, + os.path.join(generated_dir, "ShaderText.h"), + os.path.join(generated_dir, "ShaderText.cpp"), + ) + create_buildinfo(generated_dir, pymoldir) + + +class openw(object): + """ + File-like object for writing files. File is actually only + written if the content changed. + """ + + def __init__(self, filename): + if os.path.exists(filename): + self.out = cStringIO.StringIO() + self.filename = filename + else: + os.makedirs(os.path.dirname(filename), exist_ok=True) + self.out = open(filename, "w") + self.filename = None + + def close(self): + if self.out.closed: + return + if self.filename: + with open(self.filename) as handle: + oldcontents = handle.read() + newcontents = self.out.getvalue() + if oldcontents != newcontents: + self.out = open(self.filename, "w") + self.out.write(newcontents) + self.out.close() + + def __getattr__(self, name): + return getattr(self.out, name) + + def __enter__(self): + return self + + def __exit__(self, *a, **k): + self.close() + + def __del__(self): + self.close() + + +def create_shadertext(shaderdir, shaderdir2, outputheader, outputfile): + outputheader = openw(outputheader) + outputfile = openw(outputfile) + + include_deps = defaultdict(set) + ifdef_deps = defaultdict(set) + + # get all *.gs *.vs *.fs *.shared from the two input directories + shaderfiles = set() + for sdir in [shaderdir, shaderdir2]: + for ext in ["gs", "vs", "fs", "shared", "tsc", "tse"]: + shaderfiles.update( + map(os.path.basename, sorted(glob.glob(os.path.join(sdir, "*." + ext)))) + ) + + varname = "_shader_cache_raw" + outputheader.write("extern const char * %s[];\n" % varname) + outputfile.write("const char * %s[] = {\n" % varname) + + for filename in sorted(shaderfiles): + shaderfile = os.path.join(shaderdir, filename) + if not os.path.exists(shaderfile): + shaderfile = os.path.join(shaderdir2, filename) + + with open(shaderfile, "r") as handle: + contents = handle.read() + + if True: + outputfile.write('"%s", ""\n' % (filename)) + + for line in contents.splitlines(): + 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 + outputfile.write( + '"%s\\n"\n' % line.replace("\\", "\\\\").replace('"', r"\"") + ) + + # include and ifdef dependencies + if line.startswith("#include"): + include_deps[line.split()[1]].add(filename) + elif line.startswith("#ifdef") or line.startswith("#ifndef"): + ifdef_deps[line.split()[1]].add(filename) + + outputfile.write(",\n") + + outputfile.write("0};\n") + + # include and ifdef dependencies + for varname, deps in [("_include_deps", include_deps), ("_ifdef_deps", ifdef_deps)]: + outputheader.write("extern const char * %s[];\n" % varname) + outputfile.write("const char * %s[] = {\n" % varname) + for name, itemdeps in deps.items(): + outputfile.write('"%s", "%s", 0,\n' % (name, '", "'.join(sorted(itemdeps)))) + outputfile.write("0};\n") + + outputheader.close() + outputfile.close() + + +def create_buildinfo(outputdir, pymoldir="."): + try: + sha = ( + Popen(["git", "rev-parse", "HEAD"], cwd=pymoldir, stdout=PIPE) + .stdout.read() + .strip() + .decode() + ) + except OSError: + sha = "" + + with openw(os.path.join(outputdir, "PyMOLBuildInfo.h")) as out: + print( + """ +#define _PyMOL_BUILD_DATE %d +#define _PYMOL_BUILD_GIT_SHA "%s" + """ + % (time.time(), sha), + file=out, + ) -WIN = sys.platform.startswith('win') -MAC = sys.platform.startswith('darwin') # handle extra arguments +def str2bool(v: str): + if v.lower() == "true": + return True + elif v.lower() == "false": + return False + else: + raise argparse.ArgumentTypeError('Boolean value expected.') + class options: osx_frameworks = True - jobs = int(os.getenv('JOBS', 0)) + jobs = int(os.getenv("JOBS", 0)) no_libxml = False no_glut = True - use_msgpackc = 'guess' + use_msgpackc = "guess" testing = False openvr = False - use_openmp = 'no' if MAC else 'yes' - use_vtkm = 'no' - vmd_plugins = True + use_openmp = "no" if MAC else "yes" + use_vtkm = "no" + vmd_plugins = False 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('--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 False: - import monkeypatch_distutils - 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 - ''' +parser.add_argument( + "--glut", dest="no_glut", type=str2bool, help="link with GLUT (legacy GUI)" +) +parser.add_argument( + "--no-osx-frameworks", + dest="osx_frameworks", + help="on MacOS use XQuartz instead of native frameworks", + type=str2bool, +) +parser.add_argument( + "--jobs", + "-j", + type=int, + help="for parallel builds " "(defaults to number of processors)", +) +parser.add_argument( + "--no-libxml", + type=str2bool, + 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("--testing", type=str2bool, help="Build C-level tests") +parser.add_argument("--openvr", dest="openvr", type=str2bool) +parser.add_argument( + "--no-vmd-plugins", + dest="vmd_plugins", + type=str2bool, + help="Disable VMD molfile plugins (libnetcdf dependency)", +) +options, sys.argv[1:] = parser.parse_known_args(namespace=options) -def get_prefix_path(): - ''' +def get_prefix_path() -> list[str]: + """ 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 + """ + paths = [] + + if (prefix_path := os.environ.get("PREFIX_PATH")) is not None: + paths += prefix_path.split(os.pathsep) if sys.platform.startswith("freebsd"): - return ["/usr/local"] + paths += ["/usr/local"] - X11 = ['/usr/X11'] * (not options.osx_frameworks) + if not options.osx_frameworks: + paths += ["/usr/X11"] - if sys.platform == 'darwin': - for prefix in ['/sw', '/opt/local', '/usr/local']: + if sys.platform == "darwin": + for prefix in ["/sw", "/opt/local", "/usr/local"]: if sys.base_prefix.startswith(prefix): - return [prefix] + X11 + paths += [prefix] if is_conda_env(): - if sys.platform.startswith('win'): - return [os.path.join(sys.prefix, 'Library')] + if sys.platform.startswith("win"): + if "CONDA_PREFIX" in os.environ: + paths += [os.path.join(os.environ["CONDA_PREFIX"], "Library")] + paths += [os.path.join(sys.prefix, "Library")] - return [sys.prefix] + X11 + paths += [sys.prefix] + paths - return ['/usr'] + X11 + paths += ["/usr"] + return paths 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')) + "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}') + 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()) + 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 "c++11" - return 'no' + return "no" class CMakeExtension(Extension): - - def __init__(self, - name, - sources, - include_dirs=[], - libraries=[], - library_dirs=[], - define_macros=[], - extra_link_args=[], - extra_compile_args=[]): + def __init__( + self, + name, + sources, + include_dirs=[], + libraries=[], + library_dirs=[], + define_macros=[], + extra_link_args=[], + extra_compile_args=[], + ): # don't invoke the original build_ext for this special extension super().__init__(name, sources=[]) self.sources = sources @@ -172,7 +338,7 @@ def build_cmake(self, ext): # these dirs will be created in build_py, so if you don't have # any python sources to bundle, the dirs will be missing - name_split = ext.name.split('.') + name_split = ext.name.split(".") target_name = name_split[-1] build_temp = pathlib.Path(self.build_temp) / target_name build_temp.mkdir(parents=True, exist_ok=True) @@ -182,22 +348,22 @@ def build_cmake(self, ext): extdir.parent.mkdir(parents=True, exist_ok=True) def concat_paths(paths): - return ''.join(path.replace('\\', '/') + ";" for path in paths) + return "".join(path.replace("\\", "/") + ";" for path in paths) - config = 'Debug' if DEBUG else 'Release' + config = "Debug" if DEBUG else "Release" lib_output_dir = str(extdir.parent.absolute()) all_files = ext.sources all_src = concat_paths(all_files) - all_defs = ''.join(mac[0] + ";" for mac in ext.define_macros) - all_libs = ''.join(f"{lib};" for lib in ext.libraries) - all_ext_link = ' '.join(ext.extra_link_args) - all_comp_args = ''.join(f"{arg};" for arg in ext.extra_compile_args) + all_defs = "".join(mac[0] + ";" for mac in ext.define_macros) + all_libs = "".join(f"{lib};" for lib in ext.libraries) + all_ext_link = " ".join(ext.extra_link_args) + all_comp_args = "".join(f"{arg};" for arg in ext.extra_compile_args) all_lib_dirs = concat_paths(ext.library_dirs) all_inc_dirs = concat_paths(ext.include_dirs) lib_mode = "RUNTIME" if WIN else "LIBRARY" - shared_suffix = sysconfig.get_config_var('EXT_SUFFIX') + shared_suffix = sysconfig.get_config_var("EXT_SUFFIX") cmake_args = [ f"-DTARGET_NAME={target_name}", @@ -210,23 +376,25 @@ def concat_paths(paths): f"-DALL_LIB={all_libs}", f"-DALL_COMP_ARGS={all_comp_args}", f"-DALL_EXT_LINK={all_ext_link}", - f"-DSHARED_SUFFIX={shared_suffix}" + f"-DSHARED_SUFFIX={shared_suffix}", ] # example of build args - build_args = ['--config', config] + build_args = ["--config", config] if not WIN: # Win /MP flag on compilation level cpu_count = os.cpu_count() or 1 - build_args += [f'-j{cpu_count}'] + build_args += [f"-j{cpu_count}"] os.chdir(str(build_temp)) - self.spawn(['cmake', str(cwd)] + cmake_args) + self.spawn(["cmake", str(cwd)] + cmake_args) if not self.dry_run: - self.spawn(['cmake', '--build', '.'] + build_args) + self.spawn(["cmake", "--build", "."] + build_args) if WIN: # Move up from VS release folder - cmake_lib_loc = pathlib.Path(lib_output_dir, "Release", f"{target_name}{shared_suffix}") + cmake_lib_loc = pathlib.Path( + lib_output_dir, "Release", f"{target_name}{shared_suffix}" + ) if cmake_lib_loc.exists(): shutil.move(cmake_lib_loc, extdirabs) @@ -238,7 +406,6 @@ def concat_paths(paths): class build_py_pymol(build_py): def run(self): build_py.run(self) - forms_uic(self.build_lib) class install_pymol(install): @@ -247,9 +414,9 @@ class install_pymol(install): no_launcher = False 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'), + ("pymol-path=", None, "PYMOL_PATH"), + ("bundled-pmw", None, "install bundled Pmw module"), + ("no-launcher", None, "skip installation of the pymol launcher"), ] def finalize_options(self): @@ -258,11 +425,9 @@ def finalize_options(self): self.pymol_path_is_default = self.pymol_path is None if self.pymol_path is None: - self.pymol_path = os.path.join( - self.install_libbase, 'pymol', 'pymol_path') + self.pymol_path = os.path.join(self.install_libbase, "pymol", "pymol_path") elif self.root is not None: - self.pymol_path = install_pymol.change_root( - self.root, self.pymol_path) + self.pymol_path = install_pymol.change_root(self.root, self.pymol_path) def run(self): super().run() @@ -272,20 +437,23 @@ def run(self): self.make_launch_script() if self.bundled_pmw: - raise Exception('--bundled-pmw has been removed, please install Pmw from ' - 'https://github.com/schrodinger/pmw-patched') + raise Exception( + "--bundled-pmw has been removed, please install Pmw from " + "https://github.com/schrodinger/pmw-patched" + ) def unchroot(self, name): if self.root is not None and name.startswith(self.root): - return name[len(self.root):] + return name[len(self.root) :] return name def copy_tree_nosvn(self, src, dst): - def ignore(src, names): return set([ - ]).intersection(names) + def ignore(src, names): + return set([]).intersection(names) + if os.path.exists(dst): shutil.rmtree(dst) - print('copying %s -> %s' % (src, dst)) + print("copying %s -> %s" % (src, dst)) shutil.copytree(src, dst, ignore=ignore) def copy(self, src, dst): @@ -294,54 +462,66 @@ def copy(self, src, dst): def install_pymol_path(self): self.mkpath(self.pymol_path) - for name in ['LICENSE', 'data', 'test', 'examples', ]: + for name in [ + "LICENSE", + "data", + "test", + "examples", + ]: self.copy(name, os.path.join(self.pymol_path, name)) if options.openvr: - self.copy('contrib/vr/README.md', - os.path.join(self.pymol_path, 'README-VR.txt')) + self.copy( + "contrib/vr/README.md", os.path.join(self.pymol_path, "README-VR.txt") + ) def make_launch_script(self): - if sys.platform.startswith('win'): - launch_script = 'pymol.bat' + if sys.platform.startswith("win"): + launch_script = "pymol.bat" else: - launch_script = 'pymol' + launch_script = "pymol" + + install_scripts = sysconfig.get_path("scripts") + install_lib = sysconfig.get_path("purelib") - self.mkpath(self.install_scripts) - launch_script = os.path.join(self.install_scripts, launch_script) + self.mkpath(install_scripts) + launch_script = os.path.join(install_scripts, launch_script) python_exe = os.path.abspath(sys.executable) - pymol_file = self.unchroot(os.path.join( - self.install_libbase, 'pymol', '__init__.py')) + pymol_file = self.unchroot( + os.path.join(install_lib, "pymol", "__init__.py") + ) pymol_path = self.unchroot(self.pymol_path) - with open(launch_script, 'w') as out: - if sys.platform.startswith('win'): + 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) + python_exe = "%~dp0\\" + os.path.relpath( + python_exe, install_scripts + ) except ValueError: pass try: - pymol_file = '%~dp0\\' + \ - os.path.relpath(pymol_file, self.install_scripts) + pymol_file = "%~dp0\\" + os.path.relpath( + pymol_file, 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(f"set PYMOL_PATH={pymol_path}" + os.linesep) out.write('"%s" "%s"' % (python_exe, pymol_file)) - out.write(' %*' + os.linesep) + out.write(" %*" + os.linesep) else: - out.write('#!/bin/sh' + os.linesep) + 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) + out.write('exec "%s" "%s" "$@"' % (python_exe, pymol_file) + os.linesep) os.chmod(launch_script, 0o755) + # ============================================================================ @@ -349,7 +529,7 @@ def make_launch_script(self): # known to build and install instances generated_dir = os.path.join(os.environ.get("PYMOL_BLD", "build"), "generated") -create_shadertext.create_all(generated_dir) +create_all(generated_dir) # can be changed with environment variable PREFIX_PATH prefix_path = get_prefix_path() @@ -382,22 +562,23 @@ def make_launch_script(self): 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 ["/MP"] +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 ["/MP"] +) ext_link_args = [] -ext_objects = [] -data_files = [] -ext_modules = [] -if options.use_openmp == 'yes': +if options.use_openmp == "yes": def_macros += [ ("PYMOL_OPENMP", None), ] @@ -413,10 +594,10 @@ def make_launch_script(self): if options.vmd_plugins: # VMD plugin support inc_dirs += [ - 'contrib/uiuc/plugins/include', + "contrib/uiuc/plugins/include", ] pymol_src_dirs += [ - 'contrib/uiuc/plugins/molfile_plugin/src', + "contrib/uiuc/plugins/molfile_plugin/src", ] def_macros += [ ("_PYMOL_VMD_PLUGINS", None), @@ -424,24 +605,22 @@ def make_launch_script(self): if not options.no_libxml: # COLLADA support - def_macros += [ - ("_HAVE_LIBXML", None) - ] + def_macros += [("_HAVE_LIBXML", None)] libs += ["xml2"] -if options.use_msgpackc == 'guess': +if options.use_msgpackc == "guess": options.use_msgpackc = guess_msgpackc() -if options.use_msgpackc == 'no': +if options.use_msgpackc == "no": def_macros += [("_PYMOL_NO_MSGPACKC", None)] else: - if options.use_msgpackc == 'c++11': + if options.use_msgpackc == "c++11": def_macros += [ ("MMTF_MSGPACK_USE_CPP11", None), ("MSGPACK_NO_BOOST", None), ] else: - libs += ['msgpackc'] + libs += ["msgpackc"] pymol_src_dirs += ["contrib/mmtf-c"] @@ -493,27 +672,32 @@ def make_launch_script(self): libs += [ "Advapi32", # Registry (RegCloseKey etc.) - "Ws2_32", # htonl + "Ws2_32", # htonl ] - if True: - libs += [ + libs += ( + [ "glew32", "freetype", "libpng", - ] + (not options.no_glut) * [ + ] + + (not options.no_glut) + * [ "freeglut", - ] + (not options.no_libxml) * [ + ] + + (not options.no_libxml) + * [ "libxml2", ] + ) - if DEBUG: - ext_comp_args += ['/Z7'] - ext_link_args += ['/DEBUG'] + if DEBUG: + ext_comp_args += ["/Z7"] + ext_link_args += ["/DEBUG"] - libs += [ - "opengl32", - ] + libs += [ + "opengl32", + ] # TODO: Remove when we move to setup-CMake ext_comp_args += ["/std:c++17"] @@ -527,13 +711,13 @@ def make_launch_script(self): if options.use_vtkm != "no": for prefix in prefix_path: - vtkm_inc_dir = os.path.join( - prefix, "include", f"vtkm-{options.use_vtkm}") + 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)}') + raise LookupError( + "VTK-m headers not found." f' PREFIX_PATH={":".join(prefix_path)}' + ) def_macros += [ ("_PYMOL_VTKM", None), ] @@ -547,8 +731,9 @@ def make_launch_script(self): ] 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}", + f"vtkm_filter-{options.use_vtkm}" + if options.use_vtkm == "1.5" + else f"vtkm_filter_contour-{options.use_vtkm}", ] if options.vmd_plugins: @@ -561,73 +746,58 @@ def make_launch_script(self): "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])) +inc_dirs += [ + numpy.get_include(), +] +def_macros += [ + ("_PYMOL_NUMPY", None), +] -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"] +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])) + +# 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)]) + return re.findall(r'_PyMOL_VERSION "(.*)"', open("layer0/Version.h").read())[0] -def get_packages(base, parent='', r=None): - from os.path import join, exists - if r is None: - r = [] - if parent: - r.append(parent) - for name in os.listdir(join(base, parent)): - if '.' not in name and exists(join(base, parent, name, '__init__.py')): - get_packages(base, join(parent, name), r) - return r +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)] + ) -package_dir = dict((x, os.path.join(base, x)) - for base in ['modules'] - for x in get_packages(base)) - # Python includes -inc_dirs.append(sysconfig.get_paths()['include']) -inc_dirs.append(sysconfig.get_paths()['platinclude']) +inc_dirs.append(sysconfig.get_paths()["include"]) +inc_dirs.append(sysconfig.get_paths()["platinclude"]) -champ_inc_dirs = ['contrib/champ'] -champ_inc_dirs.append(sysconfig.get_paths()['include']) -champ_inc_dirs.append(sysconfig.get_paths()['platinclude']) +champ_inc_dirs = ["contrib/champ"] +champ_inc_dirs.append(sysconfig.get_paths()["include"]) +champ_inc_dirs.append(sysconfig.get_paths()["platinclude"]) if WIN: # pyconfig.py forces linking against pythonXY.lib on MSVC - py_lib = pathlib.Path(sysconfig.get_paths()['stdlib']).parent / 'libs' + py_lib = pathlib.Path(sysconfig.get_paths()["stdlib"]).parent / "libs" lib_dirs.append(str(py_lib)) -ext_modules += [ +ext_modules = [ CMakeExtension( name="pymol._cmd", sources=get_sources(pymol_src_dirs), @@ -638,35 +808,21 @@ def get_packages(base, parent='', r=None): extra_link_args=ext_link_args, extra_compile_args=ext_comp_args, ), - CMakeExtension( name="chempy.champ._champ", - sources=get_sources(['contrib/champ']), + sources=get_sources(["contrib/champ"]), include_dirs=champ_inc_dirs, library_dirs=lib_dirs, ), ] -distribution = setup( # Distribution meta-data +setup( # Distribution meta-data cmdclass={ - 'build_ext': build_ext_pymol, - 'build_py': build_py_pymol, - 'install': install_pymol, + "build_ext": build_ext_pymol, + "build_py": build_py_pymol, + "install": install_pymol, }, - name="pymol", version=get_pymol_version(), - author="Schrodinger", - url="http://pymol.org", - contact="pymol-users@lists.sourceforge.net", - description=("PyMOL is a Python-enhanced molecular graphics tool. " - "It excels at 3D visualization of proteins, small molecules, density, " - "surfaces, and trajectories. It also includes molecular editing, " - "ray tracing, and movies. Open Source PyMOL is free to everyone!"), - - package_dir=package_dir, - packages=list(package_dir), - package_data={'pmg_qt': ['forms/*.ui']}, - ext_modules=ext_modules, - data_files=data_files, ) +