88
99import argparse
1010import glob
11- import io as cStringIO
1211import os
13- import pathlib
1412import re
1513import shutil
1614import sys
1715import sysconfig
1816import time
1917from collections import defaultdict
18+ from itertools import chain
19+ from pathlib import Path
2020from subprocess import PIPE , Popen
2121
2222import numpy
2626from setuptools .command .install import install
2727
2828# non-empty DEBUG variable turns off optimization and adds -g flag
29- DEBUG = bool (os .getenv ("DEBUG" , "" ))
29+ DEBUG = bool (os .getenv ("DEBUG" , False ))
3030WIN = sys .platform .startswith ("win" )
3131MAC = sys .platform .startswith ("darwin" )
3232
3333
3434# Have to copy from "create_shadertext.py" script due to the use of pyproject.toml
3535# Full explanation:
3636# https://github.com/pypa/setuptools/issues/3939
37- def create_all (generated_dir , pymoldir = "." ):
37+ def create_all (generated_dir : str , pymol_dir : str = "." ):
3838 """
3939 Generate various stuff
4040 """
41- create_shadertext (
42- os .path .join (pymoldir , "data" , "shaders" ),
43- generated_dir ,
44- os .path .join (generated_dir , "ShaderText.h" ),
45- os .path .join (generated_dir , "ShaderText.cpp" ),
46- )
47- create_buildinfo (generated_dir , pymoldir )
48-
49-
50- class openw (object ):
51- """
52- File-like object for writing files. File is actually only
53- written if the content changed.
54- """
55-
56- def __init__ (self , filename ):
57- if os .path .exists (filename ):
58- self .out = cStringIO .StringIO ()
59- self .filename = filename
60- else :
61- os .makedirs (os .path .dirname (filename ), exist_ok = True )
62- self .out = open (filename , "w" )
63- self .filename = None
41+ generated_dir_path = Path (generated_dir )
42+ pymol_dir_path = Path (pymol_dir )
6443
65- def close (self ):
66- if self .out .closed :
67- return
68- if self .filename :
69- with open (self .filename ) as handle :
70- oldcontents = handle .read ()
71- newcontents = self .out .getvalue ()
72- if oldcontents != newcontents :
73- self .out = open (self .filename , "w" )
74- self .out .write (newcontents )
75- self .out .close ()
76-
77- def __getattr__ (self , name ):
78- return getattr (self .out , name )
79-
80- def __enter__ (self ):
81- return self
82-
83- def __exit__ (self , * a , ** k ):
84- self .close ()
85-
86- def __del__ (self ):
87- self .close ()
44+ generated_dir_path .mkdir (parents = True , exist_ok = True )
45+ pymol_dir_path .mkdir (parents = True , exist_ok = True )
8846
47+ create_shadertext (
48+ shader_dir = generated_dir_path / "data" / "shaders" ,
49+ shader_dir2 = pymol_dir_path ,
50+ output_header = generated_dir_path / "ShaderText.h" ,
51+ output_source = generated_dir_path / "ShaderText.cpp" ,
52+ )
53+ create_buildinfo (generated_dir , pymol_dir )
8954
90- def create_shadertext (shaderdir , shaderdir2 , outputheader , outputfile ):
91- outputheader = openw (outputheader )
92- outputfile = openw (outputfile )
9355
56+ def create_shadertext (
57+ shader_dir : Path ,
58+ shader_dir2 : Path ,
59+ output_header : Path ,
60+ output_source : Path ,
61+ ):
62+ varname = "_shader_cache_raw"
9463 include_deps = defaultdict (set )
9564 ifdef_deps = defaultdict (set )
65+ extension_regexp = "*.[gs][vs][fs][shared][tsc][tse]"
9666
9767 # get all *.gs *.vs *.fs *.shared from the two input directories
98- shaderfiles = set ()
99- for sdir in [shaderdir , shaderdir2 ]:
100- for ext in ["gs" , "vs" , "fs" , "shared" , "tsc" , "tse" ]:
101- shaderfiles .update (
102- map (os .path .basename , sorted (glob .glob (os .path .join (sdir , "*." + ext ))))
103- )
104-
105- varname = "_shader_cache_raw"
106- outputheader .write ("extern const char * %s[];\n " % varname )
107- outputfile .write ("const char * %s[] = {\n " % varname )
108-
109- for filename in sorted (shaderfiles ):
110- shaderfile = os .path .join (shaderdir , filename )
111- if not os .path .exists (shaderfile ):
112- shaderfile = os .path .join (shaderdir2 , filename )
68+ shaderfiles = set (
69+ chain (
70+ shader_dir .glob (extension_regexp ),
71+ shader_dir2 .glob (extension_regexp ),
72+ )
73+ )
11374
114- with open (shaderfile , "r" ) as handle :
115- contents = handle .read ()
75+ with (
76+ open (output_header , "w" ) as output_header_file ,
77+ open (output_source , "w" ) as output_source_file ,
78+ ):
79+ output_header_file .write (f"extern const char * { varname } [];\n " )
80+ output_source_file .write (f"const char * { varname } [] = {{\n " )
11681
117- if True :
118- outputfile .write ('"%s ", ""\n ' % ( filename ) )
82+ for filename in shaderfiles :
83+ output_source_file .write (f'" { filename . name } ", ""\n ' )
11984
85+ contents = filename .read_text ()
12086 for line in contents .splitlines ():
12187 line = line .strip ()
12288
@@ -125,52 +91,43 @@ def create_shadertext(shaderdir, shaderdir2, outputheader, outputfile):
12591 continue
12692
12793 # write line, quoted, escaped and with a line feed
128- outputfile .write (
129- '"%s\\ n"\n ' % line .replace ("\\ " , "\\ \\ " ).replace ('"' , r"\"" )
130- )
94+ escaped_line = line .replace ("\\ " , "\\ \\ " ).replace ('"' , r"\"" )
95+ output_source_file .write (f'"{ escaped_line } \\ n"\n ' )
13196
13297 # include and ifdef dependencies
13398 if line .startswith ("#include" ):
13499 include_deps [line .split ()[1 ]].add (filename )
135100 elif line .startswith ("#ifdef" ) or line .startswith ("#ifndef" ):
136101 ifdef_deps [line .split ()[1 ]].add (filename )
137102
138- outputfile .write (",\n " )
139-
140- outputfile .write ("0};\n " )
141-
142- # include and ifdef dependencies
143- for varname , deps in [("_include_deps" , include_deps ), ("_ifdef_deps" , ifdef_deps )]:
144- outputheader .write ("extern const char * %s[];\n " % varname )
145- outputfile .write ("const char * %s[] = {\n " % varname )
146- for name , itemdeps in deps .items ():
147- outputfile .write ('"%s", "%s", 0,\n ' % (name , '", "' .join (sorted (itemdeps ))))
148- outputfile .write ("0};\n " )
103+ output_source_file .write (",\n " )
104+ output_source_file .write ("0};\n " )
149105
150- outputheader .close ()
151- outputfile .close ()
152-
153-
154- def create_buildinfo (outputdir , pymoldir = "." ):
155- try :
156- sha = (
157- Popen (["git" , "rev-parse" , "HEAD" ], cwd = pymoldir , stdout = PIPE )
158- .stdout .read ()
159- .strip ()
160- .decode ()
161- )
162- except OSError :
163- sha = ""
164-
165- with openw (os .path .join (outputdir , "PyMOLBuildInfo.h" )) as out :
166- print (
167- """
168- #define _PyMOL_BUILD_DATE %d
169- #define _PYMOL_BUILD_GIT_SHA "%s"
170- """
171- % (time .time (), sha ),
172- file = out ,
173- )
106+ # include and ifdef dependencies
107+ for varname , deps in [
108+ ("_include_deps" , include_deps ),
109+ ("_ifdef_deps" , ifdef_deps ),
110+ ]:
111+ output_header_file .write (f"extern const char * { varname } [];\n " )
112+ output_source_file .write (f"const char * { varname } [] = {{\n " )
113+ for name , item_deps in deps .items ():
114+ item_deps = '", "' .join (sorted (item_deps ))
115+ output_source_file .write (f'"{ name } ", "{ item_deps } ", 0,\n ' )
116+ output_source_file .write ("0};\n " )
117+
118+
119+ def create_buildinfo (output_dir_path : str , pymoldir : str = "." ):
120+ output_dir = Path (output_dir_path )
121+ sha_raw = Popen (["git" , "rev-parse" , "HEAD" ], cwd = pymoldir , stdout = PIPE ).stdout
122+ sha = sha_raw .read ().strip ().decode () if sha_raw is not None else ""
123+
124+ info_file = output_dir / "PyMOLBuildInfo.h"
125+ info_file .write_text (
126+ f"""
127+ #define _PyMOL_BUILD_DATE { time .time ()}
128+ #define _PYMOL_BUILD_GIT_SHA "{ sha } "
129+ """
130+ )
174131
175132
176133# handle extra arguments
@@ -302,7 +259,6 @@ def guess_msgpackc():
302259
303260
304261class CMakeExtension (Extension ):
305-
306262 def __init__ (
307263 self ,
308264 name ,
@@ -336,15 +292,15 @@ def run(self):
336292 self .build_cmake (ext )
337293
338294 def build_cmake (self , ext ):
339- cwd = pathlib . Path ().absolute ()
295+ cwd = Path ().absolute ()
340296
341297 # these dirs will be created in build_py, so if you don't have
342298 # any python sources to bundle, the dirs will be missing
343299 name_split = ext .name .split ("." )
344300 target_name = name_split [- 1 ]
345- build_temp = pathlib . Path (self .build_temp ) / target_name
301+ build_temp = Path (self .build_temp ) / target_name
346302 build_temp .mkdir (parents = True , exist_ok = True )
347- extdir = pathlib . Path (self .get_ext_fullpath (ext .name ))
303+ extdir = Path (self .get_ext_fullpath (ext .name ))
348304 extdirabs = extdir .absolute ()
349305
350306 extdir .parent .mkdir (parents = True , exist_ok = True )
@@ -394,7 +350,7 @@ def concat_paths(paths):
394350
395351 if WIN :
396352 # Move up from VS release folder
397- cmake_lib_loc = pathlib . Path (
353+ cmake_lib_loc = Path (
398354 lib_output_dir , "Release" , f"{ target_name } { shared_suffix } "
399355 )
400356 if cmake_lib_loc .exists ():
@@ -487,7 +443,7 @@ def make_launch_script(self):
487443 launch_script = os .path .join (self .install_scripts , launch_script )
488444
489445 python_exe = os .path .abspath (sys .executable )
490- site_packages_dir = sysconfig .get_path (' purelib' )
446+ site_packages_dir = sysconfig .get_path (" purelib" )
491447 pymol_file = self .unchroot (
492448 os .path .join (site_packages_dir , "pymol" , "__init__.py" )
493449 )
@@ -701,8 +657,6 @@ def make_launch_script(self):
701657 libs += [
702658 "opengl32" ,
703659 ]
704- # TODO: Remove when we move to setup-CMake
705- ext_comp_args += ["/std:c++17" ]
706660
707661if not (MAC or WIN ):
708662 libs += [
@@ -816,7 +770,7 @@ def get_packages(base, parent="", r=None):
816770
817771if WIN :
818772 # pyconfig.py forces linking against pythonXY.lib on MSVC
819- py_lib = pathlib . Path (sysconfig .get_paths ()["stdlib" ]).parent / "libs"
773+ py_lib = Path (sysconfig .get_paths ()["stdlib" ]).parent / "libs"
820774 lib_dirs .append (str (py_lib ))
821775
822776ext_modules += [
0 commit comments