def develop_unlink(options, info): ''' Prepare development environment. Perform the following steps: - Unlink working ``.pioenvs`` directory into Conda ``Library`` directory. - Unlink ``dmf_control_board_firmware`` Python package from site packages directory. See Also -------- :func:`develop_link` ''' project_dir = ph.path(__file__).realpath().parent # Unlink working ``.pioenvs`` directory into Conda ``Library`` directory. info('Unlink working firmware directories from Conda environment.') pio_bin_dir = pioh.conda_bin_path() fw_bin_dir = pio_bin_dir.joinpath('dmf-control-board-firmware') if fw_bin_dir.exists(): fw_config_ini = fw_bin_dir.joinpath('platformio.ini') if fw_config_ini.exists(): fw_config_ini.unlink() fw_bin_dir.unlink() # Remove link to ``dmf_control_board_firmware`` Python package in # `conda.pth` in site packages directory. info('Unlink working Python directory from Conda environment...') ch.conda_exec('develop', '-u', project_dir, verbose=True) info(72 * '-' + '\nFinished')
def rollback(*args, **kwargs): ''' Restore previous revision of Conda environment according to most recent action in :attr:`MICRODROP_CONDA_ACTIONS`. .. versionchanged:: 0.18 Add support for action revision files compressed using ``bz2``. .. versionchanged:: 0.24 Remove channels argument. Use Conda channels as configured in Conda environment. Note that channels can still be explicitly set through :data:`*args`. Parameters ---------- *args Extra arguments to pass to Conda ``install`` roll-back command. Returns ------- int, dict Revision after roll back and Conda installation log object (from JSON Conda install output). See also -------- `wheeler-microfluidics/microdrop#200 <https://github.com/wheeler-microfluidics/microdrop/issues/200>` ''' action_files = MICRODROP_CONDA_ACTIONS.files() if not action_files: # No action files, return current revision. logger.debug('No rollback actions have been recorded.') revisions_js = ch.conda_exec('list', '--revisions', '--json', verbose=False) revisions = json.loads(revisions_js) return revisions[-1]['rev'] # Get file associated with most recent action. cre_rev = re.compile(r'rev(?P<rev>\d+)') action_file = sorted([(int(cre_rev.match(file_i.namebase).group('rev')), file_i) for file_i in action_files if cre_rev.match(file_i.namebase)], reverse=True)[0] # Do rollback (i.e., install state of previous revision). if action_file.ext.lower() == '.bz2': # Assume file is compressed using bz2. with bz2.BZ2File(action_file, mode='r') as input_: action = json.load(input_) else: # Assume it is raw JSON. with action_file.open('r') as input_: action = json.load(input_) rollback_revision = action['revisions'][-2] conda_args = (['install', '--json'] + list(args) + ['--revision', str(rollback_revision)]) install_log_js = ch.conda_exec(*conda_args, verbose=False) install_log = json.loads(install_log_js.split('\x00')[-1]) logger.debug('Rolled back to revision %s', rollback_revision) return rollback_revision, install_log
def _save_action(extra_context=None): ''' Save list of revisions revisions for active Conda environment. .. versionchanged:: 0.18 Compress action revision files using ``bz2`` to save disk space. Parameters ---------- extra_context : dict, optional Extra content to store in stored action revision. Returns ------- path_helpers.path, dict Path to which action was written and action object, including list of revisions for active Conda environment. ''' # Get list of revisions to Conda environment since creation. revisions_js = ch.conda_exec('list', '--revisions', '--json', verbose=False) revisions = json.loads(revisions_js) # Save list of revisions to `/etc/microdrop/plugins/actions/rev<rev>.json` # See [wheeler-microfluidics/microdrop#200][i200]. # # [i200]: https://github.com/wheeler-microfluidics/microdrop/issues/200 action = extra_context.copy() if extra_context else {} action['revisions'] = revisions action_path = (MICRODROP_CONDA_ACTIONS .joinpath('rev{}.json.bz2'.format(revisions[-1]['rev']))) action_path.parent.makedirs_p() # Compress action file using bz2 to save disk space. with bz2.BZ2File(action_path, mode='w') as output: json.dump(action, output, indent=2) return action_path, action
def main(): ''' .. versionadded:: 0.1.post62 .. versionchanged:: 0.7.5 Use Conda install dry-run to check for new version. .. versionchanged:: 0.7.6 Fix displayed package name during upgrade. Fail gracefully with warning on JSON decode error. .. versionchanged:: 0.7.7 Strip progress messages from Conda install output log to prevent JSON decoding errors. ''' # Upgrade `microdrop-launcher` package if there is a new version available. print 'Checking for `microdrop-launcher` updates', try: # Check if new version of `microdrop-launcher` would be installed. dry_run_response = json.loads( ch.conda_exec('install', '--dry-run', '--json', 'microdrop-launcher', verbose=False)) except RuntimeError, exception: if 'CondaHTTPError' in str(exception): print 'Error checking for updates - no network connection' return else: print 'Error checking for updates.\n{}'.format(exception) return
def available_packages(*args, **kwargs): ''' Query available plugin packages based on specified Conda channels. Parameters ---------- *args Extra arguments to pass to Conda ``search`` command. Returns ------- dict .. versionchanged:: 0.24 All Conda packages beginning with ``microdrop.`` prefix from all configured channels. Each *key* corresponds to a package name. Each *value* corresponds to a ``list`` of dictionaries, each corresponding to an available version of the respective package. For example: { "microdrop.dmf-device-ui-plugin": [ ... { ... "build_number": 0, "channel": "microdrop-plugins", "installed": true, "license": "BSD", "name": "microdrop.dmf-device-ui-plugin", "size": 62973, "version": "2.1.post2", ... }, ...], ... } ''' # Get list of available MicroDrop plugins, i.e., Conda packages that start # with the prefix `microdrop.`. try: plugin_packages_info_json = ch.conda_exec('search', '--json', '^microdrop\.', verbose=False) return json.loads(plugin_packages_info_json) except RuntimeError, exception: if 'CondaHTTPError' in str(exception): logger.warning('Could not connect to Conda server.') else: logger.warning('Error querying available MicroDrop plugins.', exc_info=True)
def develop_link(options, info): ''' Prepare development environment. Perform the following steps: - Uninstall ``dmf_control_board_firmware`` if installed as Conda package. - Install build and run-time Conda dependencies. - Link working ``.pioenvs`` directory into Conda ``Library`` directory to make development versions of compiled firmware binaries available to Python API. - Link ``dmf_control_board_firmware`` Python package into site packages directory. See Also -------- :func:`develop_unlink` ''' project_dir = ph.path(__file__).realpath().parent # Uninstall ``dmf_control_board_firmware`` if installed as Conda package. info('Check if Conda package is installed...') version_info = ch.conda_version_info('dmf-control-board-firmware') if version_info.get('installed') is not None: info('Uninstall `dmf-control-board-firmware` package...') ch.conda_exec('uninstall', '-y', 'dmf-control-board-firmware', verbose=True) else: info('`dmf-control-board-firmware` package is not installed.') # Install build and run-time Conda dependencies. info('Install build and run-time Conda dependencies...') recipe_dir = project_dir.joinpath('.conda-recipe').realpath() ch.conda_exec('install', '-y', '-n', 'root', 'conda-build', verbose=True) ch.development_setup(recipe_dir, verbose=True) # Link working ``.pioenvs`` directory into Conda ``Library`` directory. info('Link working firmware directories into Conda environment.') pio_bin_dir = pioh.conda_bin_path() fw_bin_dir = pio_bin_dir.joinpath('dmf-control-board-firmware') if not fw_bin_dir.exists(): project_dir.joinpath('.pioenvs').junction(fw_bin_dir) fw_config_ini = fw_bin_dir.joinpath('platformio.ini') if not fw_config_ini.exists(): project_dir.joinpath('platformio.ini').link(fw_config_ini) # Link ``dmf_control_board_firmware`` Python package `conda.pth` in site # packages directory. info('Link working Python directory into Conda environment...') ch.conda_exec('develop', project_dir, verbose=True) info(72 * '-' + '\nFinished')
def uninstall(plugin_name, *args): ''' Uninstall plugin packages. Plugin packages must have a directory with the same name as the package in the following directory: <conda prefix>/share/microdrop/plugins/available/ Parameters ---------- plugin_name : str or list Plugin package(s) to uninstall. *args Extra arguments to pass to Conda ``uninstall`` command. Returns ------- dict Conda uninstallation log object (from JSON Conda uninstall output). ''' if isinstance(plugin_name, types.StringTypes): plugin_name = [plugin_name] available_path = MICRODROP_CONDA_SHARE.joinpath('plugins', 'available') for name_i in plugin_name: plugin_module_i = name_i.split('.')[-1].replace('-', '_') plugin_path_i = available_path.joinpath(plugin_module_i) if not _islinklike(plugin_path_i) and not plugin_path_i.isdir(): raise IOError('Plugin `{}` not found in `{}`' .format(name_i, available_path)) else: logging.debug('[uninstall] Found plugin `%s`', plugin_path_i) # Perform uninstall operation. conda_args = ['uninstall', '--json', '-y'] + list(args) + plugin_name uninstall_log_js = ch.conda_exec(*conda_args, verbose=False) # Remove broken links in `<conda prefix>/etc/microdrop/plugins/enabled/`, # since uninstall may have made one or more packages unavailable. _remove_broken_links() logger.debug('Uninstalled plugins: ```%s```', plugin_name) return json.loads(uninstall_log_js.split('\x00')[-1])
def install(plugin_name, *args, **kwargs): ''' Install plugin packages based on specified Conda channels. .. versionchanged:: 0.19.1 Do not save rollback info on dry-run. .. versionchanged:: 0.24 Remove channels argument. Use Conda channels as configured in Conda environment. Note that channels can still be explicitly set through :data:`*args`. Parameters ---------- plugin_name : str or list Plugin package(s) to install. Version specifiers are also supported, e.g., ``package >=1.0.5``. *args Extra arguments to pass to Conda ``install`` command. Returns ------- dict Conda installation log object (from JSON Conda install output). ''' if isinstance(plugin_name, types.StringTypes): plugin_name = [plugin_name] # Perform installation conda_args = (['install', '-y', '--json'] + list(args) + plugin_name) install_log_js = ch.conda_exec(*conda_args, verbose=False) install_log = json.loads(install_log_js.split('\x00')[-1]) if 'actions' in install_log and not install_log.get('dry_run'): # Install command modified Conda environment. _save_action({'conda_args': conda_args, 'install_log': install_log}) logger.debug('Installed plugin(s): ```%s```', install_log['actions']) return install_log
def conda_version_info(package_name): ''' .. versionadded:: 0.2.post5 .. versionchanged:: 0.3.post2 Add support for running in Conda environments. .. versionchanged:: 0.7.3 Use :func:`conda_helpers.conda_exec` to search for available MicroDrop Conda packages. Add ``sci-bots`` Anaconda channel to Conda package search. .. versionchanged:: 0.7.8 Fall back to using ``conda list`` to search for the installed version of MicroDrop if the version cannot be determined using ``conda search``. Parameters ---------- package_name : str Conda package name. Returns ------- dict Version information: - ``latest``: Latest available version. - ``installed``: Conda package description dictionary for installed version (`None` if not installed). Raises ------ IOError If Conda executable not found. subprocess.CalledProcessError If `conda search` command fails. This happens, for example, if no internet connection is available. ''' # Use `-f` flag to search for package, but *no other packages that have # `<package_name>` in the name). json_output = ch.conda_exec('search', '-c', 'sci-bots', '-c', 'wheeler-microfluidics', '-f', 'microdrop', '--json', verbose=False) versions = json.loads(json_output)['microdrop'] installed_versions = [v_i for v_i in versions if v_i['installed']] installed_version = installed_versions[0] if installed_versions else None if installed_version is None: # If not able to find installed version from `microdrop` Conda package # search, use `conda list ...` to try determine the installed version of # MicroDrop. try: installed_version = ch.package_version('microdrop', verbose=False) except NameError: # Installed MicroDrop Conda package not found (perhaps this is a # development environment?) pass return {'installed': installed_version, 'versions': versions}
dry_run_unlinked, dry_run_linked = ch.install_info(dry_run_response, split_version=True) # Try to find package specifier for new version of `midrodrop-launcher`. # **N.B.**, `dry_run_linked` will be `None` if there is no new version. launcher_packages = [ package_i for package_i, version_i, channel_i in (dry_run_linked or []) if 'microdrop-launcher' == package_i ] if dry_run_linked and launcher_packages: # A new version of the launcher is available for installation. print 'Upgrading to:', launcher_packages[0] try: install_log_json = ch.conda_exec('install', '--json', 'microdrop-launcher', '--quiet', verbose=False) except RuntimeError, exception: if 'CondaHTTPError' in str(exception): print >> sys.stderr, ('Error upgrading `microdrop-launcher` - ' 'no network connection.') return install_log_json = _strip_conda_menuinst_messages(install_log_json) install_log_json = _strip_progress_messages(install_log_json) try: install_response = json.loads(install_log_json) except ValueError: # Error decoding JSON response. # XXX Assume install succeeded. print >> sys.stderr, ('Warning: could not decode '
def check_version_cache_for_upgrade(): ''' Prompt user to offer to upgrade if cached latest MicroDrop version is newer than currently installed version. .. versionadded:: 0.7.8 ''' # Get currently installed `microdrop` package information. # # Example `installed_info`: # # {u'base_url': None, # u'build_number': 0, # u'build_string': u'0', # u'channel': u'sci-bots', # u'dist_name': u'microdrop-2.10.2-0', # u'name': u'microdrop', # u'platform': None, # u'version': u'2.10.2', # u'with_features_depends': None} try: installed_info = ch.package_version('microdrop', verbose=False) except NameError: # Installed MicroDrop Conda package not found (perhaps this is a # development environment?) return cached_path, cached_info = load_cached_version() latest_version = cached_info.get('version') installed_version = installed_info.get('version') # If cached latest MicroDrop version is more recent than the currently # installed version, prompt user to offer to upgrade. if all([GUI_AVAILABLE, not cached_info.get('ignore'), latest_version is not None]): if (pkg_resources.parse_version(latest_version) <= pkg_resources.parse_version(installed_version)): return # Display dialog. dialog = gtk.MessageDialog(type=gtk.MESSAGE_QUESTION) dialog.set_icon_from_file(ICON_PATH) dialog.set_title('Upgrade to MicroDrop v{}'.format(latest_version)) dialog.add_buttons(gtk.STOCK_YES, gtk.RESPONSE_YES, "Not now", gtk.RESPONSE_NO, "Never", gtk.RESPONSE_CANCEL) dialog.set_markup('A new version of MicroDrop is available.\n\n' 'Would you like to upgrade to MicroDrop v{} (current' ' version: v{})?'.format(latest_version, installed_version)) response = dialog.run() dialog.destroy() if response == gtk.RESPONSE_CANCEL: # Ignore this specific version from now on. try: with cached_path.open('w') as output: cached_info = {'version': latest_version, 'ignore': True} yaml.dump(cached_info, stream=output) print ('new version available: MicroDrop v{} (not installing ' 'now)'.format(latest_version)) except: logger.error('Error caching latest version.', exc_info=True) elif response == gtk.RESPONSE_YES: # User selected `Yes`, so upgrade MicroDrop, but restrict upgrade # to within the same major version. try: major_version = int(cre_version.match(installed_version) .group('major')) install_log_json = ch.conda_exec('install', '--json', 'microdrop >={}, <{}' .format(major_version, major_version + 1)) install_response = json.loads(install_log_json) unlinked, linked = ch.install_info(install_response) print ch.format_install_info(unlinked, linked) try: # Remove stale cached MicroDrop version data. cached_path.remove() except: pass except: logger.error('Error upgrading MicroDrop.', exc_info=True)