From f9d20b9537bf2521d8ca4d35b32155b0a9a99e95 Mon Sep 17 00:00:00 2001 From: Ben Jackson Date: Fri, 24 Jul 2020 17:01:16 +0100 Subject: [PATCH] Upgrade gadgets when they change This adds a --upgrade option to install_gadget.py and makes VimspectorUpdate only update things which have changed. To do this, we record the gadget spec in a manfiest file and compare it with the current spec when in upgrade mode. 'Changed' in this case means that the gadget spec has changed from the last time the installer was run. It does _not_ actually check the presence of the gadget. --- install_gadget.py | 21 +++- python3/vimspector/gadgets.py | 5 +- python3/vimspector/install.py | 5 + python3/vimspector/installer.py | 193 +++++++++++++++++++++----------- syntax/vimspector-installer.vim | 8 ++ 5 files changed, 162 insertions(+), 70 deletions(-) diff --git a/install_gadget.py b/install_gadget.py index 4b42984..52621c3 100755 --- a/install_gadget.py +++ b/install_gadget.py @@ -69,6 +69,10 @@ parser.add_argument( '--force-all', action = 'store_true', help = 'Enable all unsupported completers' ) +parser.add_argument( '--upgrade', + action = 'store_true', + help = 'Only update adapters changed from the manifest' ) + parser.add_argument( '--quiet', action = 'store_true', help = 'Suppress installation output' ) @@ -175,6 +179,7 @@ failed = [] succeeded = [] all_adapters = installer.ReadAdapters( read_existing = args.update_gadget_config ) +manifest = installer.Manifest() for name, gadget in gadgets.GADGETS.items(): if not gadget.get( 'enabled', True ): @@ -187,15 +192,27 @@ for name, gadget in gadgets.GADGETS.items(): if getattr( args, 'disable_' + gadget[ 'language' ] ): continue + if not args.upgrade: + manifest.Clear( name ) + installer.InstallGagdet( name, gadget, + manifest, succeeded, failed, all_adapters ) for name, gadget in CUSTOM_GADGETS.items(): - installer.InstallGagdet( name, gadget, succeeded, failed, all_adapters ) + if not args.upgrade: + manifest.Clear( name ) + + installer.InstallGagdet( name, + gadget, + manifest, + succeeded, + failed, + all_adapters ) if args.no_gadget_config: print( "" ) @@ -204,6 +221,8 @@ if args.no_gadget_config: else: installer.WriteAdapters( all_adapters ) +manifest.Write() + if args.basedir: print( "" ) print( "***NOTE***: You set --basedir to " + args.basedir + diff --git a/python3/vimspector/gadgets.py b/python3/vimspector/gadgets.py index a96c9f5..7e1d619 100644 --- a/python3/vimspector/gadgets.py +++ b/python3/vimspector/gadgets.py @@ -234,8 +234,7 @@ GADGETS = { }, 'macos': { 'file_name': 'netcoredbg-osx-master.tar.gz', - 'checksum': - 'c1dc6ed58c3f5b0473cfb4985a96552999360ceb9795e42d9c9be64af054f821', + 'checksum': '', }, 'linux': { 'file_name': 'netcoredbg-linux-master.tar.gz', @@ -385,7 +384,7 @@ GADGETS = { 'enabled': False, 'repo': { 'url': 'https://github.com/microsoft/vscode-node-debug2', - 'ref': 'v1.42.0', + 'ref': 'v1.42.5' }, 'do': lambda name, root, gadget: installer.InstallNodeDebug( name, root, diff --git a/python3/vimspector/install.py b/python3/vimspector/install.py index 7dc0897..d6ceb78 100644 --- a/python3/vimspector/install.py +++ b/python3/vimspector/install.py @@ -42,6 +42,11 @@ def GetGadgetDir( vimspector_base ): return os.path.join( os.path.abspath( vimspector_base ), 'gadgets', GetOS() ) +def GetManifestFile( vimspector_base ): + return os.path.join( GetGadgetDir( vimspector_base ), + '.gadgets.manifest.json' ) + + def GetGadgetConfigFile( vimspector_base ): return os.path.join( GetGadgetDir( vimspector_base ), '.gadgets.json' ) diff --git a/python3/vimspector/installer.py b/python3/vimspector/installer.py index 3e4440d..485aed5 100644 --- a/python3/vimspector/installer.py +++ b/python3/vimspector/installer.py @@ -162,6 +162,7 @@ def RunUpdate( api_prefix, leave_open, *args ): insatller_args.extend( FindGadgetForAdapter( adapter_name ) ) if insatller_args: + insatller_args.append( '--upgrade' ) RunInstaller( api_prefix, leave_open, *insatller_args ) @@ -217,6 +218,91 @@ def FindGadgetForAdapter( adapter_name ): return candidates +class Manifest: + manifest: dict + + def __init__( self ): + self.manifest = {} + self.Read() + + def Read( self ): + try: + with open( install.GetManifestFile( options.vimspector_base ), 'r' ) as f: + self.manifest = json.load( f ) + except OSError: + pass + + def Write( self ): + with open( install.GetManifestFile( options.vimspector_base ), 'w' ) as f: + json.dump( self.manifest, f ) + + + def Clear( self, name: str ): + try: + del self.manifest[ name ] + except KeyError: + pass + + + def Update( self, name: str, gadget_spec: dict ): + self.manifest[ name ] = gadget_spec + + + def RequiresUpdate( self, name: str, gadget_spec: dict ): + try: + current_spec = self.manifest[ name ] + except KeyError: + # It's new. + return True + + # If anything changed in the spec, update + if not current_spec == gadget_spec: + return True + + # Always update if the version string is 'master'. Probably a git repo + # that pulls master (which tbh we shouldn't have) + if current_spec.get( 'version' ) in ( 'master', '' ): + return True + if current_spec.get( 'repo', {} ).get( 'ref' ) == 'master': + return True + + return False + + +def ReadAdapters( read_existing = True ): + all_adapters = {} + if read_existing: + try: + with open( install.GetGadgetConfigFile( options.vimspector_base ), + 'r' ) as f: + all_adapters = json.load( f ).get( 'adapters', {} ) + except OSError: + pass + + # Include "built-in" adapter for multi-session mode + all_adapters.update( { + 'multi-session': { + 'port': '${port}', + 'host': '${host}' + }, + } ) + + return all_adapters + + +def WriteAdapters( all_adapters, to_file=None ): + adapter_config = json.dumps ( { 'adapters': all_adapters }, + indent=2, + sort_keys=True ) + + if to_file: + to_file.write( adapter_config ) + else: + with open( install.GetGadgetConfigFile( options.vimspector_base ), + 'w' ) as f: + f.write( adapter_config ) + + def InstallGeneric( name, root, gadget ): extension = os.path.join( root, 'extension' ) for f in gadget.get( 'make_executable', [] ): @@ -300,52 +386,57 @@ def InstallTclProDebug( name, root, gadget ): def InstallNodeDebug( name, root, gadget ): - node_version = subprocess.check_output( [ 'node', '--version' ], - universal_newlines=True ).strip() - Print( "Node.js version: {}".format( node_version ) ) - if list( map( int, node_version[ 1: ].split( '.' ) ) ) >= [ 12, 0, 0 ]: - Print( "Can't install vscode-debug-node2:" ) - Print( "Sorry, you appear to be running node 12 or later. That's not " - "compatible with the build system for this extension, and as far as " - "we know, there isn't a pre-built independent package." ) - Print( "My advice is to install nvm, then do:" ) - Print( " $ nvm install --lts 10" ) - Print( " $ nvm use --lts 10" ) - Print( " $ ./install_gadget.py --enable-node ..." ) - raise RuntimeError( 'Node 10 is required to install node debugger (sadly)' ) - with CurrentWorkingDir( root ): CheckCall( [ 'npm', 'install' ] ) CheckCall( [ 'npm', 'run', 'build' ] ) MakeSymlink( name, root ) -def InstallGagdet( name, gadget, succeeded, failed, all_adapters ): +def InstallGagdet( name: str, + gadget: dict, + manifest: Manifest, + succeeded: list, + failed: list, + all_adapters: dict ): + try: - print( f"Installing {name}..." ) - v = {} - v.update( gadget.get( 'all', {} ) ) - v.update( gadget.get( install.GetOS(), {} ) ) + # Spec is an os-specific definition of the gadget + spec = {} + spec.update( gadget.get( 'all', {} ) ) + spec.update( gadget.get( install.GetOS(), {} ) ) + + def save_adapters(): + # allow per-os adapter overrides. v already did that for us... + all_adapters.update( spec.get( 'adapters', {} ) ) + # add any other "all" adapters + all_adapters.update( gadget.get( 'adapters', {} ) ) if 'download' in gadget: - if 'file_name' not in v: + if 'file_name' not in spec: raise RuntimeError( "Unsupported OS {} for gadget {}".format( install.GetOS(), name ) ) + print( f"Installing {name}@{spec[ 'version' ]}..." ) + spec[ 'download' ] = gadget[ 'download' ] + if not manifest.RequiresUpdate( name, spec ): + save_adapters() + print( " - Skip - up to date" ) + return + destination = os.path.join( install.GetGadgetDir( options.vimspector_base ), 'download', name, - v[ 'version' ] ) + spec[ 'version' ] ) - url = string.Template( gadget[ 'download' ][ 'url' ] ).substitute( v ) + url = string.Template( gadget[ 'download' ][ 'url' ] ).substitute( spec ) file_path = DownloadFileTo( url, destination, file_name = gadget[ 'download' ].get( 'target' ), - checksum = v.get( 'checksum' ), + checksum = spec.get( 'checksum' ), check_certificate = not options.no_check_certificate ) root = os.path.join( destination, 'root' ) @@ -354,8 +445,15 @@ def InstallGagdet( name, gadget, succeeded, failed, all_adapters ): root, format = gadget[ 'download' ].get( 'format', 'zip' ) ) elif 'repo' in gadget: - url = string.Template( gadget[ 'repo' ][ 'url' ] ).substitute( v ) - ref = string.Template( gadget[ 'repo' ][ 'ref' ] ).substitute( v ) + url = string.Template( gadget[ 'repo' ][ 'url' ] ).substitute( spec ) + ref = string.Template( gadget[ 'repo' ][ 'ref' ] ).substitute( spec ) + + print( f"Installing {name}@{gadget[ 'repo' ][ 'ref' ]}..." ) + spec[ 'repo' ] = gadget[ 'repo' ] + if not manifest.RequiresUpdate( name, spec ): + save_adapters() + print( " - Skip - up to date" ) + return destination = os.path.join( install.GetGadgetDir( options.vimspector_base ), @@ -365,15 +463,12 @@ def InstallGagdet( name, gadget, succeeded, failed, all_adapters ): root = destination if 'do' in gadget: - gadget[ 'do' ]( name, root, v ) + gadget[ 'do' ]( name, root, spec ) else: - InstallGeneric( name, root, v ) - - # Allow per-OS adapter overrides. v already did that for us... - all_adapters.update( v.get( 'adapters', {} ) ) - # Add any other "all" adapters - all_adapters.update( gadget.get( 'adapters', {} ) ) + InstallGeneric( name, root, spec ) + save_adapters() + manifest.Update( name, spec ) succeeded.append( name ) print( f" - Done installing {name}" ) except Exception as e: @@ -383,40 +478,6 @@ def InstallGagdet( name, gadget, succeeded, failed, all_adapters ): print( f" - FAILED installing {name}: {e}".format( name, e ) ) -def ReadAdapters( read_existing = True ): - all_adapters = {} - if read_existing: - try: - with open( install.GetGadgetConfigFile( options.vimspector_base ), - 'r' ) as f: - all_adapters = json.load( f ).get( 'adapters', {} ) - except OSError: - pass - - # Include "built-in" adapter for multi-session mode - all_adapters.update( { - 'multi-session': { - 'port': '${port}', - 'host': '${host}' - }, - } ) - - return all_adapters - - -def WriteAdapters( all_adapters, to_file=None ): - adapter_config = json.dumps ( { 'adapters': all_adapters }, - indent=2, - sort_keys=True ) - - if to_file: - to_file.write( adapter_config ) - else: - with open( install.GetGadgetConfigFile( options.vimspector_base ), - 'w' ) as f: - f.write( adapter_config ) - - @contextlib.contextmanager def CurrentWorkingDir( d ): cur_d = os.getcwd() diff --git a/syntax/vimspector-installer.vim b/syntax/vimspector-installer.vim index 98ea48b..4eb6170 100644 --- a/syntax/vimspector-installer.vim +++ b/syntax/vimspector-installer.vim @@ -4,10 +4,18 @@ endif let b:current_syntax = 'vimspector-installer' +syn match VimspectorGadget /[^ ]*\ze@/ +syn match VimspectorGadgetVersion /@\@<=[^ ]*\ze\.\.\./ + + syn keyword VimspectorInstalling Installing syn keyword VimspectorDone Done +syn keyword VimspectorSkip Skip syn keyword VimspectorError Failed FAILED hi default link VimspectorInstalling Constant hi default link VimspectorDone DiffAdd +hi default link VimspectorSkip DiffAdd hi default link VimspectorError WarningMsg +hi default link VimspectorGadget String +hi default link VimspectorGadgetVersion Identifier