3232import labscript_utils .h5_lock , h5py
3333import labscript_utils .properties
3434from labscript_utils .labconfig import LabConfig
35+ from labscript_utils .filewatcher import FileWatcher
3536
3637# This imports the default Qt library that other labscript suite code will
3738# import as well, since it all uses qtutils. By having a Qt library already
7071 startupinfo .dwFlags |= 1 #subprocess.STARTF_USESHOWWINDOW # This variable isn't defined, but apparently it's equal to one.
7172else :
7273 startupinfo = None
73-
74+
75+ # Extract settings from labconfig
76+ _SAVE_HG_INFO = LabConfig ().getboolean ('labscript' , 'save_hg_info' , fallback = True )
77+ _SAVE_GIT_INFO = LabConfig ().getboolean ('labscript' , 'save_git_info' , fallback = False )
7478
7579class config (object ):
7680 suppress_mild_warnings = True
@@ -2174,8 +2178,88 @@ def generate_connection_table(hdf5_file):
21742178 else :
21752179 master_pseudoclock_name = compiler .master_pseudoclock .name
21762180 dataset .attrs ['master_pseudoclock' ] = master_pseudoclock_name
2177-
2178-
2181+
2182+ # Create a dictionary for caching results from vcs commands. The keys will be
2183+ # the paths to files that are saved during save_labscripts(). The values will be
2184+ # a list of tuples of the form (command, info, err); see the "Returns" section
2185+ # of the _run_vcs_commands() docstring for more info. Also create a FileWatcher
2186+ # instance for tracking when vcs results need updating. The callback will
2187+ # replace the outdated cache entry with a new list of updated vcs commands and
2188+ # outputs.
2189+ _vcs_cache = {}
2190+ def _file_watcher_callback (name , info , event ):
2191+ _vcs_cache [name ] = _run_vcs_commands (name )
2192+
2193+ _file_watcher = FileWatcher (_file_watcher_callback )
2194+
2195+ def _run_vcs_commands (path ):
2196+ """Run some VCS commands on a file and return their output.
2197+
2198+ The function is used to gather up version control system information so that
2199+ it can be stored in the hdf5 files of shots. This is for convenience and
2200+ compliments the full copy of the file already included in the shot file.
2201+
2202+ Whether hg and git commands are run is controlled by the `save_hg_info`
2203+ and `save_git_info` options in the `[labscript]` section of the labconfig.
2204+
2205+ Args:
2206+ path (str): The path with file name and extension of the file on which
2207+ the commands will be run. The working directory will be set to the
2208+ directory containing the specified file.
2209+
2210+ Returns:
2211+ results (list of (tuple, str, str)): A list of tuples, each
2212+ containing information related to one vcs command of the form
2213+ (command, info, err). The first entry in that tuple is itself a
2214+ tuple of strings which was passed to subprocess.Popen() in order to
2215+ run the command. Then info is a string that contains the text
2216+ printed to stdout by that command, and err contains the text printed
2217+ to stderr by the command.
2218+ """
2219+ # Gather together a list of commands to run.
2220+ module_directory , module_filename = os .path .split (path )
2221+ vcs_commands = []
2222+ if compiler .save_hg_info :
2223+ hg_commands = [
2224+ ['log' , '--limit' , '1' ],
2225+ ['status' ],
2226+ ['diff' ],
2227+ ]
2228+ for command in hg_commands :
2229+ command = tuple (['hg' ] + command + [module_filename ])
2230+ vcs_commands .append ((command , module_directory ))
2231+ if compiler .save_git_info :
2232+ git_commands = [
2233+ ['branch' , '--show-current' ],
2234+ ['describe' , '--tags' , '--always' , 'HEAD' ],
2235+ ['rev-parse' , 'HEAD' ],
2236+ ['diff' , 'HEAD' , module_filename ],
2237+ ]
2238+ for command in git_commands :
2239+ command = tuple (['git' ] + command )
2240+ vcs_commands .append ((command , module_directory ))
2241+
2242+ # Now go through and start running the commands.
2243+ process_list = []
2244+ for command , module_directory in vcs_commands :
2245+ process = subprocess .Popen (
2246+ command ,
2247+ cwd = module_directory ,
2248+ stdout = subprocess .PIPE ,
2249+ stderr = subprocess .PIPE ,
2250+ startupinfo = startupinfo ,
2251+ )
2252+ process_list .append ((command , process ))
2253+
2254+ # Gather up results from the commands issued.
2255+ results = []
2256+ for command , process in process_list :
2257+ info , err = process .communicate ()
2258+ info = info .decode ('utf-8' )
2259+ err = err .decode ('utf-8' )
2260+ results .append ((command , info , err ))
2261+ return results
2262+
21792263def save_labscripts (hdf5_file ):
21802264 if compiler .labscript_file is not None :
21812265 script_text = open (compiler .labscript_file ).read ()
@@ -2199,28 +2283,17 @@ def save_labscripts(hdf5_file):
21992283 # Doesn't seem to want to double count files if you just import the contents of a file within a module
22002284 continue
22012285 hdf5_file .create_dataset (save_path , data = open (path ).read ())
2202- if compiler .save_hg_info :
2203- hg_commands = [['log' , '--limit' , '1' ], ['status' ], ['diff' ]]
2204- process_list = []
2205- for command in hg_commands :
2206- process = subprocess .Popen (['hg' ] + command + [os .path .split (path )[1 ]], cwd = os .path .split (path )[0 ],
2207- stdout = subprocess .PIPE , stderr = subprocess .PIPE , startupinfo = startupinfo )
2208- process_list .append (process )
2209- for process , command in zip (process_list , hg_commands ):
2210- info , err = process .communicate ()
2211- if info or err :
2212- hdf5_file [save_path ].attrs ['hg ' + str (command [0 ])] = info .decode ('utf-8' ) + '\n ' + err .decode ('utf-8' )
2213- if compiler .save_git_info :
2214- module_filename = os .path .split (path )[1 ]
2215- git_commands = [['branch' , '--show-current' ], ['describe' , '--tags' , '--always' , 'HEAD' ], ['rev-parse' , 'HEAD' ], ['diff' , 'HEAD' , module_filename ]]
2216- process_list = []
2217- for command in git_commands :
2218- process = subprocess .Popen (['git' ] + command , cwd = os .path .split (path )[0 ], stdout = subprocess .PIPE ,
2219- stderr = subprocess .PIPE , startupinfo = startupinfo )
2220- process_list .append (process )
2221- for process , command in zip (process_list , git_commands ):
2222- info , err = process .communicate ()
2223- hdf5_file [save_path ].attrs ['git ' + str (command [0 ])] = info .decode ('utf-8' ) + '\n ' + err .decode ('utf-8' )
2286+ if path not in _file_watcher .files :
2287+ # Add file to watch list and create its entry in the cache.
2288+ _file_watcher .add_file (path )
2289+ _file_watcher_callback (path , None , None )
2290+ # Get a reference to the current results list in case
2291+ # another thread updates _vcs_cache[path] to point at a new
2292+ # list during this loop.
2293+ results = _vcs_cache [path ]
2294+ for command , info , err in results :
2295+ attribute_str = command [0 ] + ' ' + command [1 ]
2296+ hdf5_file [save_path ].attrs [attribute_str ] = (info + '\n ' + err )
22242297 except ImportError :
22252298 pass
22262299 except WindowsError if os .name == 'nt' else None :
@@ -2554,8 +2627,8 @@ def labscript_cleanup():
25542627 compiler .wait_delay = 0
25552628 compiler .time_markers = {}
25562629 compiler ._PrimaryBLACS = None
2557- compiler .save_hg_info = LabConfig (). getboolean ( 'labscript' , 'save_hg_info' , fallback = True )
2558- compiler .save_git_info = LabConfig (). getboolean ( 'labscript' , 'save_git_info' , fallback = False )
2630+ compiler .save_hg_info = _SAVE_HG_INFO
2631+ compiler .save_git_info = _SAVE_GIT_INFO
25592632 compiler .shot_properties = {}
25602633
25612634class compiler (object ):
@@ -2575,8 +2648,8 @@ class compiler(object):
25752648 wait_delay = 0
25762649 time_markers = {}
25772650 _PrimaryBLACS = None
2578- save_hg_info = LabConfig (). getboolean ( 'labscript' , 'save_hg_info' , fallback = True )
2579- save_git_info = LabConfig (). getboolean ( 'labscript' , 'save_git_info' , fallback = False )
2651+ save_hg_info = _SAVE_HG_INFO
2652+ save_git_info = _SAVE_GIT_INFO
25802653 shot_properties = {}
25812654
25822655 # safety measure in case cleanup is called before init
0 commit comments