1212import compas ._os
1313import compas .plugins
1414
15- __all__ = ['install' ]
15+ __all__ = [
16+ 'install' ,
17+ 'installable_rhino_packages' ,
18+ 'after_rhino_install' ,
19+ ]
1620
1721
1822def install (version = None , packages = None ):
@@ -84,8 +88,13 @@ def install(version=None, packages=None):
8488 symlinks = [(link ['source_path' ], link ['link' ]) for link in symlinks_to_install ]
8589 install_results = compas ._os .create_symlinks (symlinks )
8690
91+ installed_packages = []
8792 for install_data , success in zip (symlinks_to_install , install_results ):
88- result = 'OK' if success else 'ERROR: Cannot create symlink, try to run as administrator.'
93+ if success :
94+ installed_packages .append (install_data ['name' ])
95+ result = 'OK'
96+ else :
97+ result = 'ERROR: Cannot create symlink, try to run as administrator.'
8998 results .append ((install_data ['name' ], result ))
9099
91100 if not all (install_results ):
@@ -106,11 +115,49 @@ def install(version=None, packages=None):
106115 if status != 'OK' :
107116 exit_code = - 1
108117
118+ if exit_code == 0 and len (installed_packages ):
119+ print ()
120+ print ('Running post-installation steps...' )
121+ print ()
122+ if not _run_post_execution_steps (after_rhino_install (installed_packages )):
123+ exit_code = - 1
124+
109125 print ('\n Completed.' )
110126 if exit_code != 0 :
111127 sys .exit (exit_code )
112128
113129
130+ def _run_post_execution_steps (steps_generator ):
131+ all_steps_succeeded = True
132+ post_execution_errors = []
133+
134+ for result in steps_generator :
135+ if isinstance (result , Exception ):
136+ post_execution_errors .append (result )
137+ continue
138+
139+ for item in result :
140+ try :
141+ package , message , success = item
142+ status = 'OK' if success else 'ERROR'
143+ if not success :
144+ all_steps_succeeded = False
145+ print (' {} {}: {}' .format (package .ljust (20 ), status , message ))
146+ except ValueError :
147+ post_execution_errors .append (ValueError ('Step ran without errors but result is wrongly formatted: {}' .format (str (item ))))
148+
149+ if post_execution_errors :
150+ print ()
151+ print ('One or more errors occurred:' )
152+ print ()
153+ for error in post_execution_errors :
154+ print (' - {}' .format (repr (error )))
155+
156+ all_steps_succeeded = False
157+
158+ return all_steps_succeeded
159+
160+
114161@compas .plugins .plugin (category = 'install' , pluggable_name = 'installable_rhino_packages' , tryfirst = True )
115162def default_installable_rhino_packages ():
116163 # While this list could obviously be hard-coded, I think
@@ -143,6 +190,36 @@ def installable_rhino_packages():
143190 pass
144191
145192
193+ @compas .plugins .pluggable (category = 'install' , selector = 'collect_all' )
194+ def after_rhino_install (installed_packages ):
195+ """Allows extensions to execute actions after install to Rhino is done.
196+
197+ Extensions providing Rhino or Grasshopper features
198+ can implement this pluggable interface to perform
199+ additional steps after an installation to Rhino has
200+ been completed.
201+
202+ Parameters
203+ ----------
204+ installed_packages : :obj:`list` of :obj:`str`
205+ List of packages that have been installed successfully.
206+
207+ Examples
208+ --------
209+ >>> import compas.plugins
210+ >>> @compas.plugins.plugin(category='install')
211+ ... def after_rhino_install(installed_packages):
212+ ... # Do something after package is installed to Rhino, eg, copy components, etc
213+ ... return [('compas_ghpython', 'GH Components installed', True)]
214+
215+ Returns
216+ -------
217+ :obj:`list` of 3-tuple (str, str, bool)
218+ List containing a 3-tuple with component name, message and ``True``/``False`` success flag.
219+ """
220+ pass
221+
222+
146223def _update_bootstrapper (install_path , packages ):
147224 # Take either the CONDA environment directory or the current Python executable's directory
148225 python_directory = os .environ .get ('CONDA_PREFIX' , None ) or os .path .dirname (sys .executable )
0 commit comments