def __enter__(self): """Acquire lock""" if CFG.base: runez.Anchored.add(CFG.base.path) cutoff = time.time() + self.give_up holder_args = self._locked_by() while holder_args: if time.time() >= cutoff: lock = runez.bold(runez.short(self.lock_path)) holder_args = runez.bold(holder_args) raise SoftLockException( "Can't grab lock %s, giving up\nIt is being held by: pickley %s" % (lock, holder_args)) time.sleep(1) holder_args = self._locked_by() # We got the soft lock if runez.DRYRUN: print("Would acquire %s" % runez.short(self.lock_path)) else: runez.log.trace("Acquired %s" % runez.short(self.lock_path)) runez.write(self.lock_path, "%s\n%s\n" % (os.getpid(), runez.quoted(sys.argv[1:])), logger=False) self.pspec.resolve() return self
def perform_install(pspec, is_upgrade=False, force=False, quiet=False): """ Args: pspec (PackageSpec): Package spec to install is_upgrade (bool): If True, intent is an upgrade (not a new install) force (bool): If True, check latest version even if recently checked quiet (bool): If True, don't chatter Returns: (pickley.TrackedManifest): Manifest is successfully installed (or was already up-to-date) """ with SoftLock(pspec): started = time.time() pspec.resolve() skip_reason = pspec.skip_reason(force) if skip_reason: inform("Skipping installation of %s: %s" % (pspec.dashed, runez.bold(skip_reason))) return None manifest = pspec.get_manifest() if is_upgrade and not manifest and not quiet: abort("'%s' is not installed" % runez.red(pspec)) if not pspec.version: desired = pspec.get_desired_version_info(force=force) if desired.problem: action = "upgrade" if is_upgrade else "install" abort("Can't %s %s: %s" % (action, pspec, runez.red(desired.problem))) pspec.version = desired.version if not force and manifest and manifest.version == pspec.version and pspec.is_healthily_installed( ): if not quiet: status = "up-to-date" if is_upgrade else "installed" inform("%s v%s is already %s" % (pspec.dashed, runez.bold(pspec.version), status)) pspec.groom_installation() return manifest setup_audit_log() manifest = PACKAGER.install(pspec) if manifest and not quiet: note = " in %s" % runez.represented_duration(time.time() - started) action = "Upgraded" if is_upgrade else "Installed" if runez.DRYRUN: action = "Would state: %s" % action inform("%s %s v%s%s" % (action, pspec.dashed, runez.bold( pspec.version), runez.dim(note))) if not pspec._pickley_dev_mode: pspec.groom_installation() return manifest
def represented(self): """str: Human readable representation of this configuration""" result = ["%s: %s" % (runez.bold("base"), self), ""] for c in self.configs: result.append(c.represented()) return "\n".join(result).strip()
def find_python(self, pspec=None, fatal=True): """ Args: pspec (PackageSpec | None): Package spec, when applicable fatal (bool): If True, abort execution is no valid python could be found Returns: (runez.pyenv.PythonInstallation): Object representing python installation """ desired = self.get_value("python", pspec=pspec) desired = runez.flattened(desired, split=",") if not desired: # Edge case: configured empty python... just use invoker in that case return self.available_pythons.invoker issues = [] python = None for d in desired: python = self.available_pythons.find_python(d) if not python.problem: return python issues.append( "Python '%s' skipped: %s" % (runez.bold(runez.short(d)), runez.red(python.problem))) for i in issues: # Warn only if no python could be found at all LOG.warning(i) if fatal: abort("No suitable python installation found") return python
def mv(sample, target): """Move a test sample (and its baseline) to a new place""" new_category, _, new_basename = target.partition("/") if "/" in new_basename: sys.exit("Invalid target '%s': use at most one / separator" % runez.red(target)) if not new_basename: new_basename = sample.basename if new_basename.endswith(".yml"): new_basename = new_basename[:-4] old_source = os.path.join(sample.category, sample.basename) new_target = os.path.join(new_category, new_basename) if old_source == new_target: print("%s is already in the right spot" % runez.bold(sample)) sys.exit(0) existing = TestSamples.get_samples(new_target + ".yml") if existing: sys.exit("There is already a sample '%s'" % runez.red(new_target)) TestSamples.move_sample_file(sample, new_category, new_basename) TestSamples.move_sample_file(sample, new_category, new_basename, kind=TestSamples.K_DESERIALIZED) TestSamples.move_sample_file(sample, new_category, new_basename, kind=TestSamples.K_TOKEN) TestSamples.clean_samples()
def replay(existing, tokens, samples): """Rerun samples and compare them with their current baseline""" kinds = [] if tokens: kinds.append(TestSamples.K_TOKEN) skipped = 0 for sample in samples: report = sample.replay(*kinds) if report is runez.UNSET: skipped += 1 report = None if existing else " %s" % runez.yellow("skipped") elif report: report = "\n%s" % "\n".join(" %s" % s for s in report.splitlines()) else: report = " %s" % runez.green("OK") if report is not None: print("** %s:%s" % (runez.bold(sample.name), report)) if skipped: print(runez.dim("-- %s skipped" % runez.plural(skipped, "sample")))
def uninstall(all, packages): """Uninstall packages""" if packages and all: abort("Either specify packages to uninstall, or --all (but not both)") if not packages and not all: abort("Specify packages to uninstall, or --all") if packages and PICKLEY in packages: abort( "Run 'uninstall --all' if you wish to uninstall pickley itself (and everything it installed)" ) setup_audit_log() for pspec in CFG.package_specs(packages): manifest = pspec.get_manifest() if not manifest or not manifest.version: abort("%s was not installed with pickley" % runez.bold(pspec.dashed)) if manifest.entrypoints: for ep in manifest.entrypoints: runez.delete(pspec.exe_path(ep)) runez.delete(pspec.meta_path) action = "Would uninstall" if runez.DRYRUN else "Uninstalled" inform("%s %s" % (action, pspec.dashed)) if all: runez.delete(CFG.base.full_path(PICKLEY)) runez.delete(CFG.meta.path) inform("pickley is now %s" % runez.red("uninstalled"))
def header(self, report=None): """ :param GitRunReport|None report: Optional report to show (defaults to self.git.report) :return str: Textual representation """ report = GitRunReport(report or self.git.report(inspect_remotes=self.prefs.inspect_remotes)) result = "%s:" % self.aligned_name if self.git.is_git_checkout: branch = runez.bold(self.git.branches.shortened_current_branch) n = len(self.git.branches.local) if n > 1: branch += " +%s" % (n - 1) result += " [%s]" % branch freshness = self.git.status.freshness if freshness: result += " %s" % freshness if not report.has_problems and self.parent and self.prefs and self.prefs.all and self.parent.predominant: if self.origin_project != self.parent.predominant: report.add(note="not part of %s" % self.parent.predominant) if report: rep = report.representation() if rep: result += " %s" % rep return result
def clean_show(target): """ :param GitCheckout target: Target to show """ print(target.header()) if not target.git.local_cleanable_branches: print(" No local branches can be cleaned") else: for branch in target.git.local_cleanable_branches: print(" %s branch %s can be cleaned" % (runez.bold("local"), runez.bold(branch))) if not target.git.remote_cleanable_branches: print(" No remote branches can be cleaned") else: for branch in target.git.remote_cleanable_branches: print(" %s can be cleaned" % (runez.bold(branch)))
def represented(self): """str: Human readable representation of this configuration""" result = ["%s:" % runez.bold(runez.short(self.source))] if self.values: self._add_dict_representation(result, self.values) else: result[0] += runez.dim(" # empty") result.append("") return "\n".join(result)
def show_result(self, data, tokens=False): rtype = "tokens" if tokens else data.__class__.__name__ if data is not None else "None" rep = data if not tokens or isinstance(data, Exception): rep = TestSettings.represented(data) message = "---- %s: %s" % (runez.bold(self.name), runez.dim(rtype)) if isinstance(data, NotImplementedError): print("%s - %s" % (message, rep)) return print(message) print(rep)
def check(force, verbose, packages): """Check whether specified packages need an upgrade""" code = 0 packages = CFG.package_specs(packages) if not packages: print("No packages installed") sys.exit(0) for pspec in packages: skip_reason = pspec.skip_reason(force) if skip_reason: print( "%s: %s, %s" % (pspec.dashed, runez.bold("skipped"), runez.dim(skip_reason))) continue desired = pspec.get_desired_version_info(force=force) dv = runez.bold(desired and desired.version) manifest = pspec.get_manifest() if desired.problem: msg = desired.problem code = 1 elif not manifest or not manifest.version: msg = "v%s is not installed" % dv code = 1 elif manifest.version == desired.version: msg = "v%s is installed" % dv else: action = "upgraded to" if desired.source == "latest" else "caught up to %s" % desired.source msg = "v%s installed, can be %s v%s" % (runez.dim( manifest.version), action, dv) print("%s: %s" % (pspec.dashed, msg)) sys.exit(code)
def colored_key(key, indent): if (key in K_CLI or key in K_LEAVES) and indent in (1, 3): return runez.teal(key) if key in K_DIRECTIVES and indent == 1: return runez.dim(key) if key in K_GROUPS and indent == 1: return runez.purple(key) if indent == 2: return runez.bold(key) return runez.red(key)
def run_raw_git_command(self, *args): """ :param args: Execute git command with provided args, don't capture its output, but let it show through stdout/stderr :return GitRunReport: Report """ cmd, pretty_args = self._git_command(args) pretty_args = "git %s" % " ".join(args) print("Running: %s" % runez.bold(pretty_args)) proc = subprocess.Popen(cmd) # nosec proc.communicate() if proc.returncode: return GitRunReport(problem="git exited with code %s" % proc.returncode) return GitRunReport()
def header(self): result = "%s:" % runez.purple(runez.short(self.path)) if not self.projects: return "%s %s" % (result, runez.orange("no git folders")) if self.predominant: result += runez.bold(" %s %s" % (len(self.projects[self.predominant]), self.predominant)) else: result += runez.orange(" no predominant project") if self.additional: result += " (%s)" % runez.purple(", ".join("+%s %s" % (len(self.projects[project]), project) for project in self.additional)) return result
def install(pspec, ping=True): delivery = DeliveryMethod.delivery_method_by_name(pspec.settings.delivery) delivery.ping = ping args = [pspec.specced] if pspec.folder: args = [pspec.folder] elif pspec._pickley_dev_mode: args = ["-e", pspec._pickley_dev_mode] # pragma: no cover, convenience case for running pickley from .venv/ venv = PythonVenv(pspec) venv.pip_install(*args) contents = PackageContents(venv, pspec) if not contents.entry_points: runez.delete(pspec.meta_path) abort("Can't install '%s', it is %s" % (runez.bold(pspec.dashed), runez.red("not a CLI"))) return delivery.install(pspec, venv, contents.entry_points)
def package(build, dist, symlink, no_compile, sanity_check, project, requirement): """Package a project from source checkout""" started = time.time() runez.log.spec.default_logger = LOG.info CFG.set_base(runez.resolved_path(build)) finalizer = PackageFinalizer(project, dist, symlink) finalizer.sanity_check = sanity_check finalizer.requirements = requirement finalizer.compile = not no_compile finalizer.resolve() report = finalizer.finalize() if report: inform("") inform(report) inform("") elapsed = "in %s" % runez.represented_duration(time.time() - started) inform("Packaged %s successfully %s" % (runez.bold(runez.short(project)), runez.dim(elapsed)))
def auto_uninstall(target): """ Args: target (str): Path to executable to auto-uninstall if needed Returns: Aborts if uninstallation was not possible """ brew, name = find_brew_name(target) if brew and name: result = runez.run(brew, "uninstall", "-f", name, fatal=False, logger=LOG.info) if result.succeeded: LOG.info("Auto-uninstalled brew formula '%s'" % name) return # command = "%s uninstall %s" % (brew, name) abort("'%s' failed, please check" % runez.bold(command)) abort("Can't automatically uninstall %s" % runez.short(target))
def cmd_progress_bar(): """Show a progress bar sample""" names = AsciiAnimation.available_names() parser = runez.cli.parser() parser.add_argument( "--delay", "-d", type=float, default=100.0, help="Time in milliseconds to sleep between iterations.") parser.add_argument("--iterations", "-i", type=int, default=100, help="Number of iterations to run.") parser.add_argument("--log-every", "-l", type=int, default=5, help="Log a message every N iterations.") parser.add_argument("--spinner", "-s", choices=names, default=None, help="Pick spinner to use.") parser.add_argument( "--sleep", type=float, default=None, help= "Extra sleep when done, useful for inspecting animation a bit further." ) parser.add_argument( "--no-spinner", "-n", action="store_true", help="Useful to compare CPU usage with and without spinner.") parser.add_argument("--verbose", "-v", action="store_true", help="More chatty output.") parser.add_argument("name", nargs="*", help="Names of modules to show (by default: all).") args = parser.parse_args() process = None try: import psutil process = psutil.Process(os.getpid()) process.cpu_percent() except ImportError: # pragma: no cover pass runez.log.setup(console_format="%(levelname)s %(message)s", console_level=logging.INFO, trace="RUNEZ_DEBUG") if not args.no_spinner: runez.log.progress.start(frames=args.spinner, max_columns=40, spinner_color=runez.yellow) logger = logging.info if args.verbose else logging.debug for i in runez.ProgressBar(range(args.iterations)): i += 1 if args.log_every and i % args.log_every == 0: logger("Running\niteration %s %s", runez.red(i), "-" * 50) logger = logging.debug else: runez.log.trace("At iteration %s" % i) if args.verbose and i % 10 == 0: # pragma: no cover print("iteration %s" % runez.bold(i)) if i == 42: # pragma: no cover runez.log.progress.show( "some progress msg" ) # debug() and trace() messages don't appear any more after this for _ in runez.ProgressBar(range(10)): time.sleep(0.1) time.sleep(args.delay / 1000) msg = "done" if process: cpu_usage = ("%.2f" % process.cpu_percent()).rstrip("0") msg += " (%s%% CPU usage)" % cpu_usage print(msg) if args.sleep: runez.log.progress.show(msg) time.sleep(args.sleep)
def handle_single_clean(target, what): """ :param GitCheckout target: Single checkout to clean :param str what: Operation """ report = target.git.fetch() if report.has_problems: if what != "reset": what = "clean" print( target.header( GitRunReport(report).add(problem="<can't %s" % what))) runez.abort("") if what == "reset": return clean_reset(target) if what == "show": return clean_show(target) total_cleaned = 0 print(target.header()) if what in "remote all": if not target.git.remote_cleanable_branches: print(" No remote branches can be cleaned") else: total = len(target.git.remote_cleanable_branches) cleaned = 0 for branch in target.git.remote_cleanable_branches: remote, _, name = branch.partition("/") if not remote and name: raise Exception("Unknown branch spec '%s'" % branch) if run_git(target, False, "branch", "--delete", "--remotes", branch): cleaned += run_git(target, False, "push", "--delete", remote, name) total_cleaned += cleaned if cleaned == total: print("%s cleaned" % runez.plural(cleaned, "remote branch")) else: print("%s/%s remote branches cleaned" % (cleaned, total)) target.git.reset_cached_properties() if what == "all": # Fetch to update remote branches (and correctly detect new dangling local) target.git.fetch() if what in "local all": if not target.git.local_cleanable_branches: print(" No local branches can be cleaned") else: total = len(target.git.local_cleanable_branches) cleaned = 0 for branch in target.git.local_cleanable_branches: if branch == target.git.branches.current: fallback = target.git.fallback_branch() if not fallback: print( "Skipping branch '%s', can't determine fallback branch" % target.git.branches.current) continue run_git(target, True, "checkout", fallback) run_git(target, True, "pull") cleaned += run_git(target, False, "branch", "--delete", branch) total_cleaned += cleaned if cleaned == total: print( runez.bold("%s cleaned" % runez.plural(cleaned, "local branch"))) else: print( runez.orange("%s/%s local branches cleaned" % (cleaned, total))) target.git.reset_cached_properties() if total_cleaned: print(target.header())
def test_colors(): dim = runez.color.style.dim assert runez.color.cast_style(dim) is dim assert runez.color.cast_style(runez.dim) is runez.dim assert runez.color.cast_style("dim") is dim assert runez.color.cast_color(dim) is dim assert runez.color.cast_color("dim") is dim assert runez.color.cast_color("blue") is runez.color.fg.blue msg1 = dim("hi") msg2 = runez.colored("hi", "dim") assert msg1 == msg2 with pytest.raises(ValueError): runez.color.cast_style("foo") assert not runez.color.is_coloring() with runez.ActivateColors(terminal.Ansi16Backend): # Check that backend can be passed as class (flavor auto-determined in that case) assert runez.color.is_coloring() assert "ansi16" in runez.color.backend.name msg1 = runez.dim("hi") msg2 = runez.colored("hi", "dim") assert msg1 == msg2 assert not runez.color.is_coloring() with runez.ActivateColors(terminal.Ansi16Backend(flavor="neutral")): assert runez.color.is_coloring() assert runez.red(None) == "\x1b[31mNone\x1b[39m" assert runez.blue("") == "" assert runez.plain("hello") == "hello" assert runez.yellow("hello") == "\x1b[33mhello\x1b[39m" assert runez.yellow("hello", size=4) == "\x1b[33mh...\x1b[39m" assert runez.bold(1) == "\x1b[1m1\x1b[22m" assert runez.color.bg.get(None) is None assert runez.color.bg.get("blue") is runez.color.bg.blue assert runez.dim("") == "" assert runez.dim("hello", size=4) == "\x1b[2mh...\x1b[22m" # Verify unicode char 'μ' from represented_duration() works assert "foo: %s" % runez.dim(runez.represented_duration(0.010049)) == "foo: \x1b[2m10 ms 49 μs\x1b[22m" assert "foo: %s" % runez.blue(runez.represented_duration(0.010049)) == "foo: \x1b[34m10 ms 49 μs\x1b[39m" assert not runez.color.is_coloring() assert runez.black("") == "" assert runez.blue("") == "" assert runez.brown("") == "" assert runez.gray("") == "" assert runez.green("") == "" assert runez.orange("") == "" assert runez.plain("hello") == "hello" assert runez.purple("") == "" assert runez.red(None) == "None" assert runez.teal("") == "" assert runez.white("") == "" assert runez.yellow("hello") == "hello" assert runez.blink("hello") == "hello" assert runez.bold(1) == "1" assert runez.dim("") == "" assert runez.invert("") == "" assert runez.italic("") == "" assert runez.strikethrough("") == "" assert runez.underline("") == "" assert str(runez.color.fg.black) == "black"