def commit(args): home = args.assert_home() env_repo = home.get_env_repo(args.repo) if not env_repo.status(): print style_error('Nothing to commit.') return 1 # Pick the level of the patch. while not args.micro and args.semver_level is None: print '%s [%s]:' % ( style('How severe are the changes?', 'green', bold=True), style('major,minor,PATCH,micro', faint=True), ), res = raw_input().strip() or 'patch' try: args.semver_level = dict(major=0, minor=1, patch=2, micro=None)[res] except KeyError: pass else: break if args.message is None: default_message = default_messages[args.semver_level] print '%s [%s]:' % ( style('Enter a short commit message', 'green', bold=True), style(default_message, faint=True), ), args.message = raw_input().strip() or default_message env_repo.commit(args.message, args.semver_level)
def create_if_not_exists(self): python = os.path.join(self.path, 'bin', 'python') if not os.path.exists(python): makedirs(self.path) print style('Creating Python virtualenv', 'blue', bold=True), style(self.path, bold=True) virtualenv.create_environment(self.path, no_setuptools=True, no_pip=True) if not os.path.exists(python + '-config'): names = ( 'python%d.%d-config' % sys.version_info[:2], 'python%d-config' % sys.version_info[0], 'python-config', ) prefix = getattr(sys, 'real_prefix', sys.prefix) for name in names: old_path = os.path.join(prefix, 'bin', name) if os.path.exists(old_path): for name in names: new_path = os.path.join(self.path, 'bin', name) self.rewrite_shebang_or_link(old_path, new_path) break else: log.warning('Could not find python-config')
def list_(args): home = args.assert_home() if args.availible: for req, url in iter_availible_requirements(home): print style_note(req.name, str(req)) return if args.glob: cur = home.db.execute( 'SELECT * FROM development_packages WHERE name GLOB ? ORDER BY lower(name)', [args.glob]) else: cur = home.db.execute( 'SELECT * FROM development_packages ORDER BY lower(name)') for dev_pkg in sorted(home.iter_development_packages(), key=lambda p: p.name.lower()): if args.glob and not fnmatch.fnmatch(dev_pkg.name, args.glob): continue path = dev_pkg.work_tree.replace(home.dev_root, '$VEE_DEV').replace( home.root, '$VEE') print style_note(dev_pkg.name, path) if args.show_environ: for k, v in sorted( render_envvars(dev_pkg.environ, dev_pkg.work_tree).iteritems()): if os.environ.get(k): v = v.replace(os.environ[k], '$' + k) v = v.replace(home.dev_root, '$VEE_DEV') v = v.replace(home.root, '$VEE') print style(' %s=' % k) + v
def install(args): """Install the given requirements without linking them into an environment. This is a low-level command, and is generally unused. Examples: # Install a single package. vee install [email protected]:westernx/sgmock # Install multiple packages. vee install [email protected]:westernx/sgmock [email protected]:westernx/sgsession \\ http:/example.org/path/to/tarball.tgz --make-install # Install from a requirement set. vee install path/to/requirements.txt """ home = args.assert_home() if not args.requirements: raise ValueError('please provide requirements to install') reqs = Requirements(args.requirements, home=home) pkgs = PackageSet(home=home) # TODO: Resolve everything at once like upgrade does. for req in reqs.iter_packages(): pkg = pkgs.resolve(req, check_existing=not args.force) try: pkgs.install(pkg.name, reinstall=args.force) except AlreadyInstalled: print style('Already installed', 'blue', bold=True), style(str(pkg.freeze()), bold=True)
def describe(pkg, cache, depth=0): print((' ' * depth) + style(pkg.name, 'blue'), pkg.id, style('***' if pkg.id in cache else str(pkg), faint=True)) if pkg.id in cache: return cache[pkg.id] = pkg for dep in pkg.dependencies: dep.resolve_existing() describe(dep, cache, depth + 1)
def format(self, record): record.message = record.msg % record.args if record.args else record.msg if record.levelname == 'DEBUG': record.message = style( 'Debug [%s:%s]: %s' % (record.name, record.lineno or '', record.message), faint=True) elif record.levelname != 'INFO': colour = {'WARNING': 'yellow'}.get(record.levelname, 'red') record.message = '%s %s' % ( style(record.levelname.title() + ':', fg=colour), record.message, ) return record.message
def format_cli_exc(e, verbose=False): res = '' tb = sys.exc_info()[2] if verbose and tb: stack = traceback.format_list(traceback.extract_tb(tb)) res = style(''.join(stack).rstrip(), faint=True) + '\n' return res + cli_exc_str(e)
def list_(args): home = args.assert_home() if args.availible: for req, url in iter_availible_requirements(home): log.info(style_note(req.name, str(req))) return for dev_pkg in sorted(home.iter_development_packages(), key=lambda p: p.name.lower()): if args.glob and not fnmatch.fnmatch(dev_pkg.name, args.glob): continue path = dev_pkg.work_tree.replace(home.dev_root, '$VEE_DEV').replace( home.root, '$VEE') log.info(style_note(dev_pkg.name, path)) if args.show_environ: for k, v in sorted( render_envvars(dev_pkg.environ, dev_pkg.work_tree).items()): if os.environ.get(k): v = v.replace(os.environ[k], '$' + k) v = v.replace(home.dev_root, '$VEE_DEV') v = v.replace(home.root, '$VEE') log.info(style(' %s=' % k) + v)
def find_command(name): path = which(name) if path: print(style_note(repr(path) + ":")) return 0 else: print(style(name + ":", 'yellow'), None) return 1
def list_environments(args): home = args.assert_home() con = home.db.connect() cache = {} for env in con.execute( 'SELECT * from environments ORDER by created_at ASC'): print(env['created_at'], style(env['name'], 'blue'), env['id'])
def cli_exc_str(obj, use_magic=True): # Need to be able to avoid recursion. if use_magic: method = getattr(obj, '__cli_str__', None) if method: return method() title = getattr(obj, '__cli_title__', None) or obj.__class__.__name__ format = getattr(obj, '__cli_format__', None) if format is not None: message = format.format(self=obj) else: message = str(obj) detail = getattr(obj, '__cli_detail__', None) return ('%s %s\n%s' % (style('%s:' % title, 'red', bold=True) if title is not None else '', style(message, bold=True) if message is not None else '', style(detail, faint=True) if detail is not None else '')).strip()
def environ_diff(self): if self._environ_diff is None: self._environ_diff = {} for e in (self.base_environ, self.environ): for k, v in e.items(): self._environ_diff[k] = self.render_template(v, name=k) # Just for debugging... for k, v in sorted(self._environ_diff.items()): old_v = os.environ.get(k) if old_v is not None: v = v.replace(old_v, '@') v = v.replace(self.home.root, '$VEE') log.debug('%s %s=%s' % (style('setenv', 'blue'), k, v), verbosity=2) return self._environ_diff
def summarize_rev_distance( local, remote, local_name='You', local_verb='are', remote_name='', fork_action='please rebase.', ahead_action='you may push.', behind_action='please pull.', indent=' ', ): if local and remote: print indent + style_warning( '%s and %s have forked%s' % (local_name, remote_name or 'the remote', '; ' + fork_action if fork_action else '.')) print indent + style_warning( 'There are %d local commit%s, and %d remote commit%s.' % ( local, 's' if local > 1 else '', remote, 's' if remote > 1 else '', )) elif local: print indent + style('%s %s ahead%s by %d commit%s%s' % ( local_name, local_verb, ' of ' + remote_name if remote_name else '', local, 's' if local > 1 else '', '; ' + ahead_action if ahead_action else '.', ), fg='green', reset=True) elif remote: print indent + style_warning('%s %s behind%s by %d commit%s%s' % ( local_name, local_verb, ' ' + remote_name if remote_name else '', remote, 's' if remote > 1 else '', '; ' + behind_action if behind_action else '.', ))
def doctor(args): """Perform self-checks to make sure VEE is OK.""" if args.ping: print('pong') return import vee.__about__ as about if args.version: print(about.__version__ + ('+' + about.__revision__ if args.revision else '')) return if args.revision: print(about.__revision__) return print(style_note('==> core')) print(style_note('version:', repr(about.__version__))) print(style_note('revision:', repr(about.__revision__))) print( style_note('package:', repr(os.path.abspath(os.path.join(__file__, '..', '..'))))) res = 0 def find_command(name): path = which(name) if path: print(style_note(repr(path) + ":")) return 0 else: print(style(name + ":", 'yellow'), None) return 1 print(style_note('==> dependencies')) for name, expected_version, in [ ('setuptools', '36.0.0'), ('pip', '20.0.0'), ('virtualenv', '15.0.0'), ('packaging', '16.0'), ('wheel', '0.29.0'), ]: try: module = __import__(name) except ImportError as e: print(style(name + ":", 'yellow'), None) res = 2 continue actual_version = module.__version__ if parse_version(expected_version) <= parse_version(actual_version): print( style_note( name + ":", "{!r} >= {!r}".format(actual_version, expected_version))) else: print( style(name + ":", 'yellow'), "{!r} < {!r} (from {})".format(actual_version, expected_version, module.__file__)) res = 2 print(style_note('==> executables')) print(style_note('python:', sys.executable)) res = find_command('git') or res if sys.platform == 'darwin': res = find_command('install_name_tool') or res if sys.platform.startswith('linux'): res = find_command('patchelf') or res print(style_note('==> runtime')) print( style_note('sys.real_prefix:', repr(getattr(sys, 'real_prefix', None)))) print( style_note('sys.base_prefix:', repr(getattr(sys, 'base_prefix', None)))) print(style_note('sys.prefix:', repr(sys.prefix))) print(style_note('==> config')) home = args.assert_home() print(style_note('home:', repr(home.root))) try: repo = home.get_repo() print(style_note('repo:', repr(repo.name), repr(repo.remote_url))) except ValueError: print(style("repo:", 'yellow'), None) res = res or 3 print(style_note('==> summary')) if not res: print(style('Everything looks OK.', 'green')) else: print(style('Something looks wrong.', 'yellow')) return res
def install(self, names=None, link_env=None, reinstall=False, relink=False, no_deps=False): # I'd love to split this method into an "install" and "link" step, but # then we'd need to reimplement the dependency resolution. That would # be a good idea to do anyways, but... meh. if isinstance(names, str): names = [names] names = list(names if names else self.keys()) for name in names: if name not in self: raise KeyError(name) if not isinstance(reinstall, set): reinstall = set( names if no_deps else self.keys()) if reinstall else set() if not isinstance(relink, set): relink = set( names if no_deps else self.keys()) if relink else set() while names: name = names.pop(0) self._parent_names.setdefault(name, None) parent_chain = [] tip = name while tip and tip not in parent_chain: parent_chain.append(tip) tip = self._parent_names.get(tip) parent_chain = parent_chain[1:] print( '==>', style(name, 'blue'), style('(%s)' % ' < '.join(parent_chain), faint=True) if parent_chain else '') with log.indent(): # Avoid infinite error loops. if name in self._errored: log.warning('Skipping due to previous error.') continue try: self._install_one(names, name, link_env, reinstall, relink, no_deps) except PipelineError as e: self._errored.add(name) log.error(str(e)) continue except Exception as e: self._errored.add(name) print_cli_exc(e, verbose=True) log.exception('Exception while processing %s' % name) continue if self._errored: log.warning('There were errors in: %s' % ', '.join(sorted(self._errored)))
def link(args): """Link the given requirement or requirements into the given environment, e.g.:: # Install and link a single package. $ vee link [email protected]:vfxetc/sgmock # Install and link multiple packages. $ vee link [email protected]:vfxetc/sgmock [email protected]:vfxetc/sgsession \\ http:/example.org/path/to/tarball.tgz --make-install # Install and link from a requirement set. $ vee link path/to/manifest.txt """ if args.no_install and args.re_install: raise ValueError( 'please use only one of --no-install and --re-install') home = args.assert_home() if sum( int(bool(x)) for x in (args.repo, args.environment, args.directory)) > 1: raise ValueError( 'use only one of --repo, --environment, or --directory') if args.environment: env = Environment(args.environment, home=home) elif args.directory: env = Environment(os.path.abspath(args.directory), home=home) else: repo = home.get_repo(args.repo) env = Environment('%s/%s' % (repo.name, repo.branch_name), home=home) if args.raw: for dir_ in args.requirements: log.info(style_note('Linking', dir_)) env.link_directory(dir_) return manifest = Manifest(args.requirements, home=home) pkg_set = PackageSet(env=env, home=home) # Register the whole set, so that dependencies are pulled from here instead # of weakly resolved from installed packages. pkg_set.resolve_set(manifest) for req in manifest.iter_packages(): # Skip if it wasn't requested. if args.subset and req.name not in args.subset: continue log.info(style('==> %s' % req.name, 'blue')) pkg = pkg_set.resolve(req, check_existing=not args.reinstall) if args.no_install and not pkg.installed: raise CliError('not installed: %s' % req) try: with log.indent(): pkg_set.install(pkg.name, link_env=env, reinstall=args.reinstall, relink=args.force) except AlreadyInstalled: pass except AlreadyLinked as e: log.info(style('Already linked ', 'blue') + str(req), verbosity=1) except Exception as e: print_cli_exc(e, verbose=True) log.exception('Failed to link %s' % req) continue
def doctor(args): """Perform self-checks to make sure VEE is OK.""" if args.ping: print 'pong' return import vee.__about__ as about if args.version: print about.__version__ + ('+' + about.__revision__ if args.revision else '') return if args.revision: print about.__revision__ return print style_note('==> VEE') print style_note('version:', about.__version__) print style_note('revision:', about.__revision__) print style_note('package:', os.path.abspath(os.path.join(__file__, '..', '..'))) res = 0 def find_command(name, warn=False): path = which(name) if path: print style_note('%s:' % name, path) else: if warn: print style_warning('cannot find %s' % name) else: print style_error('cannot find %s' % name) return 1 print style_note('==> dependencies') for name, expected_version, in [('setuptools', '18.0.1'), ('virtualenv', '13.1.0')]: module = globals()[name] actual_version = module.__version__ if expected_version == actual_version: print style_note(name + ':', expected_version) else: print style( '%s: %s (expected vendored %s) from %s' % (name, actual_version, expected_version, module.__file__), 'yellow') res = 2 print style_note('==> executables') print style_note('python:', sys.executable) res = find_command('git') or res if sys.platform == 'darwin': res = find_command('install_name_tool') or res if sys.platform.startswith('linux'): res = find_command('patchelf', warn=True) or res print style_note('==> configuration') home = args.assert_home() print style_note('home:', home.root) try: repo = home.get_env_repo() except ValueError: print style_warning('no default repo.', 'Use `vee repo add --default URL`.') return print style_note('repo:', repo.name, repo.remote_url) print style_note('==> summary') if not res: print style('Everything looks OK', 'green') else: print style('Something may be wrong', 'yellow') return res
def add(args): home = args.assert_home() env_repo = home.get_env_repo(args.repo) req_set = env_repo.load_requirements() pkg_set = PackageSet(home=home) baked_any = None if args.update: baked_any = False for req in req_set.iter_packages(): pkg = pkg_set.resolve(req, check_existing=False) if pkg.fetch_type != 'git': continue print style_note('Fetching', str(req)) pkg.repo.fetch('origin', 'master') # TODO: track these another way? if pkg.repo.check_ff_safety('origin/master'): pkg.repo.checkout('origin/master') head = pkg.repo.head[:8] if head != req.revision: req.revision = pkg.repo.head[:8] print style_note('Updated', str(req)) baked_any = True if args.bake_installed: baked_any = False for req in req_set.iter_packages(): pkg = pkg_set.resolve(req) if pkg.fetch_type != 'git': continue repo = pkg.pipeline.steps['fetch'].repo if req.name and req.name == guess_name(req.url): req.name = None baked_any = True print style_note('Unset redundant name', req.name) if pkg.installed and req.revision != repo.head[:8]: req.revision = repo.head[:8] baked_any = True print style_note('Pinned', req.name, req.revision) if args.checksum: baked_any = False for req in req_set.iter_packages(): pkg = pkg_set.resolve(req) if pkg.checksum: continue if not pkg.package_path or not os.path.isfile(pkg.package_path): continue req.checksum = checksum_file(pkg.package_path) print style_note('Checksummed', pkg.name, req.checksum) baked_any = True if baked_any is not None: if baked_any: env_repo.dump_requirements(req_set) else: print style_note('No changes.') return row = home.get_development_record(os.path.abspath(args.package)) if not row: raise ValueError('No development package %r' % args.package) dev_repo = GitRepo(row['path']) # Get the normalized origin. dev_remote_urls = set() for url in dev_repo.remotes().itervalues(): url = normalize_git_url(url, prefer='scp') or url log.debug('adding dev remote url: %s' % url) dev_remote_urls.add(url) if not dev_remote_urls: print style_error('No git remotes for %s' % row['path']) return 1 for req in req_set.iter_packages(eval_control=False): # We only deal with git packages. pkg = pkg_set.resolve(req, check_existing=False) if pkg.fetch_type != 'git': continue req_url = normalize_git_url(req.url, prefer='scp') log.debug('does match package url?: %s' % req_url) if req_url in dev_remote_urls: if req.revision == dev_repo.head[:8]: print style_note('No change to', str(req)) else: req.revision = dev_repo.head[:8] print style_note('Updated', str(req)) break else: if not args.init: print '{error}: No required package {name}; would match one of:'.format( error=style('Error', 'red'), name=style(args.package, bold=True)) for url in sorted(dev_remote_urls): print ' {}'.format(url) print 'Use {} to setup: git+{} --revision {}'.format( style('vee add --init %s' % args.package, 'green'), dev_repo.remotes()['origin'], dev_repo.head[:8]) return 1 req = Package( url=normalize_git_url(dev_repo.remotes()['origin'], prefix=True), revision=dev_repo.head[:8], home=home, ) req_set.append(('', req, '')) env_repo.dump_requirements(req_set)
def status(args): home = args.assert_home() env_repo = home.get_env_repo(args.repo) pkg_set = PackageSet(home=home) by_name = {} # Dev packages. for row in home.db.execute('SELECT * FROM development_packages'): row = dict(row) if not os.path.exists(row['path']): continue dev_repo = GitRepo(row['path']) row['remotes'] = dev_repo.remotes() by_name.setdefault(row['name'], {})['dev'] = row # Current requirements. for revision, name in [ (None, 'work'), ('HEAD', 'head'), ]: for req in env_repo.load_requirements( revision=revision).iter_packages(): pkg = pkg_set.resolve(req, check_existing=False) if pkg.fetch_type != 'git': continue by_name.setdefault(pkg.name, {})[name] = req by_name = by_name.items() by_name.sort(key=lambda x: x[0].lower()) if args.names: by_name = [x for x in by_name if x[0] in args.names] for name, everything in by_name: dev_row = everything.get('dev') work_req = everything.get('work') head_req = everything.get('head') has_dev = dev_row is not None only_has_dev = has_dev and not (work_req or head_req) # Skip dev-only stuff most of the time. if only_has_dev and not args.all_dev: continue # Title. print '%s %s' % (style( '%s %s' % ('==>' if has_dev else '-->', name), fg='blue'), '(dev only)' if only_has_dev else '') # Status of requirements. if work_req and head_req and str(work_req) == str(head_req): if args.verbose: print '=== %s' % work_req else: # Print a lovely coloured diff of the specific arguments that # are changing. # TODO: make this environment relative to the context. head_args = head_req.to_args( exclude=('base_environ', )) if head_req else [] work_args = work_req.to_args( exclude=('base_environ', )) if work_req else [] differ = difflib.SequenceMatcher(None, head_args, work_args) opcodes = differ.get_opcodes() if head_req is not None: print style('---', fg='red', bold=True), for tag, i1, i2, j1, j2 in opcodes: if tag in ('replace', 'delete'): print style(' '.join(head_args[i1:i2]), fg='red', bold=True) elif tag in ('equal', ): print ' '.join(head_args[i1:i2]), if work_req is not None: print style('+++', fg='green', bold=True), for tag, i1, i2, j1, j2 in opcodes: if tag in ('replace', 'insert'): print style(' '.join(work_args[j1:j2]), fg='green', bold=True) elif tag in ('equal', ): print ' '.join(work_args[j1:j2]), if dev_row: if 'warning' in dev_row: print dev_row['warning'] if 'origin' in dev_row['remotes']: dev_row['remote_name'] = 'origin' else: remote_names = sorted(dev_row['remotes']) dev_row['remote_name'] = remote_names[0] if len(remote_names) != 1: print ' ' + style_warning( 'More that one non-origin remote; picking %s' % dev_row['remote_name']) dev_repo = dev_row and GitRepo(dev_row['path']) if dev_repo and not dev_repo.exists: print style_warning('Git repo does not exist.') dev_row = dev_repo = None if dev_repo: if dev_repo.status(): print ' ' + style_warning('Work tree is dirty.') if args.fetch: dev_remote_head = dev_repo.fetch(dev_row['remote_name'], 'master') else: dev_remote_head = dev_repo.rev_parse(dev_row['remote_name'] + '/master') # Check your local dev vs. its remote. dev_local, dev_remote = dev_repo.distance(dev_repo.head, dev_remote_head) summarize_rev_distance( dev_local, dev_remote, local_name=name, local_verb='is', remote_name='%s/master' % dev_row['remote_name'], behind_action='please pull or `vee dev ff %s`' % name, ) if dev_repo and work_req and work_req.revision: # Check your local dev vs the required revision try: pkg_revision = dev_repo.rev_parse(work_req.revision) pkg_local, pkg_remote = dev_repo.distance( dev_repo.head, pkg_revision) summarize_rev_distance( pkg_local, pkg_remote, local_name=name, local_verb='is', remote_name='%s repo' % env_repo.name, ahead_action='you may `vee add %s`' % name, behind_action='please `vee dev checkout --repo %s %s`' % (env_repo.name, name), ) except Exception as e: print ' ' + format_cli_exc(e)