1616import sys
1717import subprocess
1818import keyword
19+ import threading
1920from inspect import getcallargs
2021from functools import wraps
2122
3132
3233import labscript_utils .h5_lock , h5py
3334import labscript_utils .properties
35+ from labscript_utils .labconfig import LabConfig
36+ from labscript_utils .filewatcher import FileWatcher
3437
3538# This imports the default Qt library that other labscript suite code will
3639# import as well, since it all uses qtutils. By having a Qt library already
6972 startupinfo .dwFlags |= 1 #subprocess.STARTF_USESHOWWINDOW # This variable isn't defined, but apparently it's equal to one.
7073else :
7174 startupinfo = None
72-
75+
76+ # Extract settings from labconfig
77+ _SAVE_HG_INFO = LabConfig ().getboolean ('labscript' , 'save_hg_info' , fallback = True )
78+ _SAVE_GIT_INFO = LabConfig ().getboolean ('labscript' , 'save_git_info' , fallback = False )
7379
7480class config (object ):
7581 suppress_mild_warnings = True
@@ -2995,8 +3001,90 @@ def generate_connection_table(hdf5_file):
29953001 else :
29963002 master_pseudoclock_name = compiler .master_pseudoclock .name
29973003 dataset .attrs ['master_pseudoclock' ] = master_pseudoclock_name
2998-
2999-
3004+
3005+ # Create a dictionary for caching results from vcs commands. The keys will be
3006+ # the paths to files that are saved during save_labscripts(). The values will be
3007+ # a list of tuples of the form (command, info, err); see the "Returns" section
3008+ # of the _run_vcs_commands() docstring for more info. Also create a FileWatcher
3009+ # instance for tracking when vcs results need updating. The callback will
3010+ # replace the outdated cache entry with a new list of updated vcs commands and
3011+ # outputs.
3012+ _vcs_cache = {}
3013+ _vcs_cache_rlock = threading .RLock ()
3014+ def _file_watcher_callback (name , info , event ):
3015+ with _vcs_cache_rlock :
3016+ _vcs_cache [name ] = _run_vcs_commands (name )
3017+
3018+ _file_watcher = FileWatcher (_file_watcher_callback )
3019+
3020+ def _run_vcs_commands (path ):
3021+ """Run some VCS commands on a file and return their output.
3022+
3023+ The function is used to gather up version control system information so that
3024+ it can be stored in the hdf5 files of shots. This is for convenience and
3025+ compliments the full copy of the file already included in the shot file.
3026+
3027+ Whether hg and git commands are run is controlled by the `save_hg_info`
3028+ and `save_git_info` options in the `[labscript]` section of the labconfig.
3029+
3030+ Args:
3031+ path (str): The path with file name and extension of the file on which
3032+ the commands will be run. The working directory will be set to the
3033+ directory containing the specified file.
3034+
3035+ Returns:
3036+ results (list of (tuple, str, str)): A list of tuples, each
3037+ containing information related to one vcs command of the form
3038+ (command, info, err). The first entry in that tuple is itself a
3039+ tuple of strings which was passed to subprocess.Popen() in order to
3040+ run the command. Then info is a string that contains the text
3041+ printed to stdout by that command, and err contains the text printed
3042+ to stderr by the command.
3043+ """
3044+ # Gather together a list of commands to run.
3045+ module_directory , module_filename = os .path .split (path )
3046+ vcs_commands = []
3047+ if compiler .save_hg_info :
3048+ hg_commands = [
3049+ ['log' , '--limit' , '1' ],
3050+ ['status' ],
3051+ ['diff' ],
3052+ ]
3053+ for command in hg_commands :
3054+ command = tuple (['hg' ] + command + [module_filename ])
3055+ vcs_commands .append ((command , module_directory ))
3056+ if compiler .save_git_info :
3057+ git_commands = [
3058+ ['branch' , '--show-current' ],
3059+ ['describe' , '--tags' , '--always' , 'HEAD' ],
3060+ ['rev-parse' , 'HEAD' ],
3061+ ['diff' , 'HEAD' , module_filename ],
3062+ ]
3063+ for command in git_commands :
3064+ command = tuple (['git' ] + command )
3065+ vcs_commands .append ((command , module_directory ))
3066+
3067+ # Now go through and start running the commands.
3068+ process_list = []
3069+ for command , module_directory in vcs_commands :
3070+ process = subprocess .Popen (
3071+ command ,
3072+ cwd = module_directory ,
3073+ stdout = subprocess .PIPE ,
3074+ stderr = subprocess .PIPE ,
3075+ startupinfo = startupinfo ,
3076+ )
3077+ process_list .append ((command , process ))
3078+
3079+ # Gather up results from the commands issued.
3080+ results = []
3081+ for command , process in process_list :
3082+ info , err = process .communicate ()
3083+ info = info .decode ('utf-8' )
3084+ err = err .decode ('utf-8' )
3085+ results .append ((command , info , err ))
3086+ return results
3087+
30003088def save_labscripts (hdf5_file ):
30013089 """Writes the script files for the compiled shot to the shot file.
30023090
@@ -3028,18 +3116,21 @@ def save_labscripts(hdf5_file):
30283116 # Doesn't seem to want to double count files if you just import the contents of a file within a module
30293117 continue
30303118 hdf5_file .create_dataset (save_path , data = open (path ).read ())
3031- if compiler .save_hg_info :
3032- hg_commands = [['log' , '--limit' , '1' ], ['status' ], ['diff' ]]
3033- for command in hg_commands :
3034- process = subprocess .Popen (['hg' ] + command + [os .path .split (path )[1 ]], cwd = os .path .split (path )[0 ],
3035- stdout = subprocess .PIPE , stderr = subprocess .PIPE , startupinfo = startupinfo )
3036- info , err = process .communicate ()
3037- if info or err :
3038- hdf5_file [save_path ].attrs ['hg ' + str (command [0 ])] = info .decode ('utf-8' ) + '\n ' + err .decode ('utf-8' )
3119+ with _vcs_cache_rlock :
3120+ already_cached = path in _vcs_cache
3121+ if not already_cached :
3122+ # Add file to watch list and create its entry in the cache.
3123+ _file_watcher .add_file (path )
3124+ _file_watcher_callback (path , None , None )
3125+ with _vcs_cache_rlock :
3126+ # Save the cached vcs output to the file.
3127+ for command , info , err in _vcs_cache [path ]:
3128+ attribute_str = command [0 ] + ' ' + command [1 ]
3129+ hdf5_file [save_path ].attrs [attribute_str ] = (info + '\n ' + err )
30393130 except ImportError :
30403131 pass
30413132 except WindowsError if os .name == 'nt' else None :
3042- sys .stderr .write ('Warning: Cannot save Mercurial data for imported scripts. Check that the hg command can be run from the command line.\n ' )
3133+ sys .stderr .write ('Warning: Cannot save version control data for imported scripts. Check that the hg and/or git command can be run from the command line.\n ' )
30433134
30443135
30453136def write_device_properties (hdf5_file ):
@@ -3409,7 +3500,8 @@ def labscript_cleanup():
34093500 compiler .wait_delay = 0
34103501 compiler .time_markers = {}
34113502 compiler ._PrimaryBLACS = None
3412- compiler .save_hg_info = True
3503+ compiler .save_hg_info = _SAVE_HG_INFO
3504+ compiler .save_git_info = _SAVE_GIT_INFO
34133505 compiler .shot_properties = {}
34143506
34153507class compiler (object ):
@@ -3431,7 +3523,8 @@ class compiler(object):
34313523 wait_delay = 0
34323524 time_markers = {}
34333525 _PrimaryBLACS = None
3434- save_hg_info = True
3526+ save_hg_info = _SAVE_HG_INFO
3527+ save_git_info = _SAVE_GIT_INFO
34353528 shot_properties = {}
34363529
34373530 # safety measure in case cleanup is called before init
0 commit comments