def run(global_config): try: user_nox_module = load_user_nox_module(global_config.noxfile) except IOError: logger.error('Noxfile {} not found.'.format(global_config.noxfile)) return False session_functions = discover_session_functions(user_nox_module) sessions = make_sessions(session_functions, global_config) if global_config.list_sessions: print('Available sessions:') for session in sessions: print('*', session.signature or session.name) return True if global_config.sessions: sessions = [x for x in sessions if ( x.name in global_config.sessions or x.signature in global_config.sessions)] success = True for session in sessions: result = session.execute() success = success and result if not success and global_config.stop_on_first_error: return False return success
def _normalize_path(envdir, path): """Normalizes a string to be a "safe" filesystem path for a virtualenv.""" if isinstance(path, bytes): path = path.decode("utf-8") path = unicodedata.normalize("NFKD", path).encode("ascii", "ignore") path = path.decode("ascii") path = re.sub(r"[^\w\s-]", "-", path).strip().lower() path = re.sub(r"[-\s]+", "-", path) path = path.strip("-") full_path = os.path.join(envdir, path) if len(full_path) > 100 - len("bin/pythonX.Y"): if len(envdir) < 100 - 9: path = hashlib.sha1(path.encode("ascii")).hexdigest()[:8] full_path = os.path.join(envdir, path) logger.warning("The virtualenv name was hashed to avoid being too long.") else: logger.error( "The virtualenv path {} is too long and will cause issues on " "some environments. Use the --envdir path to modify where " "nox stores virtualenvs.".format(full_path) ) return full_path
def run(self): cmd, args = self.args[0], self.args[1:] full_cmd = ' '.join(self.args) logger.info(full_cmd) cmd_path = which(cmd, self.path) try: return_code, output = popen([cmd_path] + list(args), silent=self.silent, env=self.env) if return_code not in self.success_codes: logger.error('Command {} failed with exit code {}{}'.format( full_cmd, return_code, ':' if self.silent else '')) if self.silent: sys.stderr.write(output) raise CommandFailed('Returned code {}'.format(return_code)) return output if self.silent else True except KeyboardInterrupt: logger.error('Interrupted...') raise
def execute(self): session_friendly_name = self.signature or self.name logger.warning('Running session {}'.format(session_friendly_name)) try: # Set up the SessionConfig object (which session functions refer # to as `session`) and then the virtualenv. self._create_config() self._create_venv() # Run the actual commands prescribed by the session. cwd = py.path.local(os.getcwd()).as_cwd() with cwd: self._run_commands() # Nothing went wrong; return a success. return Result(self, Status.SUCCESS) except _SessionQuit: return Result(self, Status.ABORTED) except _SessionSkip: return Result(self, Status.SKIPPED) except CommandFailed: return Result(self, Status.FAILED) except KeyboardInterrupt: logger.error('Session {} interrupted.'.format(self)) raise
def load_nox_module(global_config): """Load the user's noxfile and return the module object for it. .. note:: This task has two side effects; it makes ``global_config.noxfile`` an absolute path, and changes the working directory of the process. Args: global_config (.nox.main.GlobalConfig): The global config. Returns: module: The module designated by the Noxfile path. """ try: # Save the absolute path to the Noxfile. # This will inoculate it if nox changes paths because of an implicit # or explicit chdir (like the one below). global_config.noxfile = os.path.realpath( # Be sure to expand variables os.path.expandvars(global_config.noxfile) ) # Move to the path where the Noxfile is. # This will ensure that the Noxfile's path is on sys.path, and that # import-time path resolutions work the way the Noxfile author would # guess. os.chdir(os.path.realpath(os.path.dirname(global_config.noxfile))) return imp.load_source("user_nox_module", global_config.noxfile) except (IOError, OSError): logger.error("Noxfile {} not found.".format(global_config.noxfile)) return 2
def filter_manifest(manifest, global_config): """Filter the manifest according to the provided configuration. Args: manifest (~.Manifest): The manifest of sessions to be run. global_config (~nox.main.GlobalConfig): The global configuration. Returns: Union[~.Manifest,int]: ``3`` if a specified session is not found, the manifest otherwise (to be sent to the next task). """ # Filter by the name of any explicit sessions. # This can raise KeyError if a specified session does not exist; # log this if it happens. if global_config.sessions: try: manifest.filter_by_name(global_config.sessions) except KeyError as exc: logger.error("Error while collecting sessions.") logger.error(exc.args[0]) return 3 # Filter by keywords. # This function never errors, but may cause an empty list of sessions # (which is an error condition later). if global_config.keywords: manifest.filter_by_keywords(global_config.keywords) # Return the modified manifest. return manifest
def execute(self): logger.warning('Running session {}'.format(self.signature or self.name)) try: self._create_config() self._create_venv() self._install_dependencies() if self.config._dir != '.': logger.info('Changing directory to {}'.format( self.config._dir)) cwd = py.path.local(self.config._dir).as_cwd() with cwd: self._run_commands() logger.success('Session {} successful. :)'.format(self.name)) return True except CommandFailed: logger.error('Session {} failed. :('.format(self.name)) return False except KeyboardInterrupt: logger.error('Session {} interrupted.'.format(self.name)) raise
def _normalize_path(envdir, path): """Normalizes a string to be a "safe" filesystem path for a virtualenv.""" if isinstance(path, six.binary_type): path = path.decode('utf-8') path = unicodedata.normalize('NFKD', path).encode('ascii', 'ignore') path = path.decode('ascii') path = re.sub('[^\w\s-]', '-', path).strip().lower() path = re.sub('[-\s]+', '-', path) path = path.strip('-') full_path = os.path.join(envdir, path) if len(full_path) > 100 - len('bin/pythonX.Y'): if len(envdir) < 100 - 9: path = hashlib.sha1(path.encode('ascii')).hexdigest()[:8] full_path = os.path.join(envdir, path) logger.warning( 'The virtualenv name was hashed to avoid being too long.') else: logger.error( 'The virtualenv path {} is too long and will cause issues on ' 'some environments. Use the --envdir path to modify where ' 'nox stores virtualenvs.'.format(full_path)) return full_path
def filter_manifest(manifest: Manifest, global_config: Namespace) -> Union[Manifest, int]: """Filter the manifest according to the provided configuration. Args: manifest (~.Manifest): The manifest of sessions to be run. global_config (~nox.main.GlobalConfig): The global configuration. Returns: Union[~.Manifest,int]: ``3`` if a specified session is not found, the manifest otherwise (to be sent to the next task). """ # Filter by the name of any explicit sessions. # This can raise KeyError if a specified session does not exist; # log this if it happens. if global_config.sessions: try: manifest.filter_by_name(global_config.sessions) except KeyError as exc: logger.error("Error while collecting sessions.") logger.error(exc.args[0]) return 3 # Filter by keywords. # This function never errors, but may cause an empty list of sessions # (which is an error condition later). if global_config.keywords: manifest.filter_by_keywords(global_config.keywords) # Return the modified manifest. return manifest
def run(global_config): try: user_nox_module = load_user_nox_module(global_config.noxfile) except IOError: logger.error('Noxfile {} not found.'.format(global_config.noxfile)) return False session_functions = discover_session_functions(user_nox_module) sessions = make_sessions(session_functions, global_config) if global_config.list_sessions: print('Available sessions:') for session in sessions: print('*', session.signature or session.name) return True if global_config.sessions: sessions = [ x for x in sessions if (x.name in global_config.sessions or x.signature in global_config.sessions) ] success = True for session in sessions: result = session.execute() success = success and result if not success and global_config.stop_on_first_error: return False return success
def run(global_config): try: # Save the absolute path to the Noxfile. # This will innoculate it if nox changes paths because of an implicit # or explicit chdir (like the one below). global_config.noxfile = os.path.realpath(global_config.noxfile) # Move to the path where the Noxfile is. # This will ensure that the Noxfile's path is on sys.path, and that # import-time path resolutions work the way the Noxfile author would # guess. os.chdir(os.path.realpath(os.path.dirname(global_config.noxfile))) user_nox_module = load_user_nox_module(global_config.noxfile) except (IOError, OSError): logger.error('Noxfile {} not found.'.format(global_config.noxfile)) return False session_functions = discover_session_functions(user_nox_module) sessions = make_sessions(session_functions, global_config) if global_config.sessions: sessions = filter_sessions_by_name(global_config.sessions, sessions) if global_config.keywords: sessions = filter_sessions_by_keywords(global_config.keywords, sessions) if global_config.list_sessions: print('Available sessions:') for session in sessions: print('*', session.signature or session.name) return True if not sessions: return False success = True results = [] for session in sessions: result = session.execute() results.append((session, result)) success = success and result if not success and global_config.stop_on_first_error: success = False break if len(results) > 1: print_summary(results) if global_config.report is not None: create_report(global_config.report, success, results) return success
def run(self, path_override=None, env_fallback=None, **kwargs): path = self.path if path_override is None else path_override env = env_fallback.copy() if env_fallback is not None else None if self.env is not None: if env is None: env = self.env else: env.update(self.env) cmd, args = self.args[0], self.args[1:] full_cmd = ' '.join(self.args) logger.info(full_cmd) cmd_path = which(cmd, path) # Environment variables must be the "str" type. # In other words, they must be bytestrings in Python 2, and Unicode # text strings in Python 3. clean_env = {} if env is not None else None if clean_env is not None: # Ensure systemroot is passed down, otherwise Windows will explode. clean_env[str('SYSTEMROOT')] = os.environ.get( 'SYSTEMROOT', str('')) for key, value in six.iteritems(env): key = coerce_str(key) value = coerce_str(value) clean_env[key] = value try: return_code, output = popen( [cmd_path] + list(args), silent=self.silent, env=clean_env) if return_code not in self.success_codes: logger.error('Command {} failed with exit code {}{}'.format( full_cmd, return_code, ':' if self.silent else '')) if self.silent: sys.stderr.write(output) raise CommandFailed('Returned code {}'.format(return_code)) return output if self.silent else True except KeyboardInterrupt: logger.error('Interrupted...') raise
def filter_sessions(specified_sessions, available_sessions): sessions = [x for x in available_sessions if ( x.name in specified_sessions or x.signature in specified_sessions)] missing_sessions = set(specified_sessions) - set( itertools.chain( [x.name for x in sessions if x.name], [x.signature for x in sessions if x.signature])) if missing_sessions: logger.error('Sessions {} not found.'.format(', '.join( missing_sessions))) return False return sessions
def load_nox_module(global_config: Namespace) -> types.ModuleType | int: """Load the user's Noxfile and return the module object for it. .. note:: This task has two side effects; it makes ``global_config.noxfile`` an absolute path, and changes the working directory of the process. Args: global_config (.nox.main.GlobalConfig): The global config. Returns: module: The module designated by the Noxfile path. """ try: # Save the absolute path to the Noxfile. # This will inoculate it if Nox changes paths because of an implicit # or explicit chdir (like the one below). global_config.noxfile = os.path.realpath( # Be sure to expand variables os.path.expandvars(global_config.noxfile)) noxfile_parent_dir = os.path.realpath( os.path.dirname(global_config.noxfile)) # Check ``nox.needs_version`` by parsing the AST. check_nox_version(global_config.noxfile) # Move to the path where the Noxfile is. # This will ensure that the Noxfile's path is on sys.path, and that # import-time path resolutions work the way the Noxfile author would # guess. The original working directory (the directory that Nox was # invoked from) gets stored by the .invoke_from "option" in _options. os.chdir(noxfile_parent_dir) except (VersionCheckFailed, InvalidVersionSpecifier) as error: logger.error(str(error)) return 2 except FileNotFoundError: logger.error( f"Failed to load Noxfile {global_config.noxfile}, no such file exists." ) return 2 except OSError: logger.exception(f"Failed to load Noxfile {global_config.noxfile}") return 2 else: return _load_and_exec_nox_module(global_config)
def which(program: str, paths: list[str] | None) -> str: """Finds the full path to an executable.""" full_path = None if paths: full_path = py.path.local.sysfind(program, paths=paths) if full_path: return full_path.strpath # type: ignore[no-any-return] full_path = py.path.local.sysfind(program) if full_path: return full_path.strpath # type: ignore[no-any-return] logger.error(f"Program {program} not found.") raise CommandFailed(f"Program {program} not found")
def which(program: str, paths: Optional[List[str]]) -> str: """Finds the full path to an executable.""" full_path = None if paths: full_path = py.path.local.sysfind(program, paths=paths) if full_path: return full_path.strpath full_path = py.path.local.sysfind(program) if full_path: return full_path.strpath logger.error(f"Program {program} not found.") raise CommandFailed(f"Program {program} not found")
def which(program, path): """Finds the full path to an executable.""" full_path = None if path: full_path = py.path.local.sysfind(program, paths=[path]) if full_path: return full_path.strpath full_path = py.path.local.sysfind(program) if full_path: return full_path.strpath logger.error("Program {} not found.".format(program)) raise CommandFailed("Program {} not found".format(program))
def which(program: str, path: Optional[str]) -> str: """Finds the full path to an executable.""" full_path = None if path: full_path = py.path.local.sysfind(program, paths=[path]) if full_path: return full_path.strpath full_path = py.path.local.sysfind(program) if full_path: return full_path.strpath logger.error("Program {} not found.".format(program)) raise CommandFailed("Program {} not found".format(program))
def which(program, path): """Finds the full path to an executable.""" full_path = None if path: full_path = py.path.local.sysfind(program, paths=[path]) if full_path: return full_path.strpath full_path = py.path.local.sysfind(program) if full_path: return full_path.strpath logger.error('Program {} not found.'.format(program)) raise CommandFailed('Program {} not found'.format(program))
def run(self, path_override=None, env_override=None): path = self.path if path_override is None else path_override env = self.env if env_override is None else env_override cmd, args = self.args[0], self.args[1:] full_cmd = ' '.join(self.args) logger.info(full_cmd) cmd_path = which(cmd, path) # Environment variables must be bytestrings. clean_env = {} if env is not None else None if clean_env is not None: # Ensure systemroot is passed down, otherwise Windows will explode. clean_env[str('SYSTEMROOT')] = os.environ.get( 'SYSTEMROOT', str('')) for key, value in six.iteritems(env): if not isinstance(key, six.text_type): key = key.decode('utf-8') if not isinstance(value, six.text_type): value = value.decode('utf-8') clean_env[key] = value try: return_code, output = popen([cmd_path] + list(args), silent=self.silent, env=clean_env) if return_code not in self.success_codes: logger.error('Command {} failed with exit code {}{}'.format( full_cmd, return_code, ':' if self.silent else '')) if self.silent: sys.stderr.write(output) raise CommandFailed('Returned code {}'.format(return_code)) return output if self.silent else True except KeyboardInterrupt: logger.error('Interrupted...') raise
def execute(self) -> Result: logger.warning(f"Running session {self.friendly_name}") try: # By default, Nox should quietly change to the directory where # the noxfile.py file is located. cwd = py.path.local( os.path.realpath(os.path.dirname( self.global_config.noxfile))).as_cwd() with cwd: self._create_venv() session = Session(self) self.func(session) # Nothing went wrong; return a success. return Result(self, Status.SUCCESS) except nox.virtualenv.InterpreterNotFound as exc: if self.global_config.error_on_missing_interpreters: return Result(self, Status.FAILED, reason=str(exc)) else: logger.warning( "Missing interpreters will error by default on CI systems." ) return Result(self, Status.SKIPPED, reason=str(exc)) except _SessionQuit as exc: return Result(self, Status.ABORTED, reason=str(exc)) except _SessionSkip as exc: return Result(self, Status.SKIPPED, reason=str(exc)) except nox.command.CommandFailed: return Result(self, Status.FAILED) except KeyboardInterrupt: logger.error(f"Session {self.friendly_name} interrupted.") raise except Exception as exc: logger.exception( f"Session {self.friendly_name} raised exception {exc!r}") return Result(self, Status.FAILED)
def execute(self): logger.warning("Running session {}".format(self.friendly_name)) try: # By default, nox should quietly change to the directory where # the noxfile.py file is located. cwd = py.path.local( os.path.realpath(os.path.dirname(self.global_config.noxfile)) ).as_cwd() with cwd: self._create_venv() session = Session(self) self.func(session) # Nothing went wrong; return a success. return Result(self, Status.SUCCESS) except nox.virtualenv.InterpreterNotFound as exc: if self.global_config.error_on_missing_interpreters: return Result(self, Status.FAILED, reason=str(exc)) else: return Result(self, Status.SKIPPED, reason=str(exc)) except _SessionQuit as exc: return Result(self, Status.ABORTED, reason=str(exc)) except _SessionSkip as exc: return Result(self, Status.SKIPPED, reason=str(exc)) except nox.command.CommandFailed: return Result(self, Status.FAILED) except KeyboardInterrupt: logger.error("Session {} interrupted.".format(self.friendly_name)) raise except Exception as exc: logger.exception( "Session {} raised exception {!r}".format(self.friendly_name, exc) ) return Result(self, Status.FAILED)
def load_nox_module(global_config: Namespace) -> Union[types.ModuleType, int]: """Load the user's noxfile and return the module object for it. .. note:: This task has two side effects; it makes ``global_config.noxfile`` an absolute path, and changes the working directory of the process. Args: global_config (.nox.main.GlobalConfig): The global config. Returns: module: The module designated by the Noxfile path. """ try: # Save the absolute path to the Noxfile. # This will inoculate it if nox changes paths because of an implicit # or explicit chdir (like the one below). global_config.noxfile = os.path.realpath( # Be sure to expand variables os.path.expandvars(global_config.noxfile)) # Check ``nox.needs_version`` by parsing the AST. check_nox_version(global_config.noxfile) # Move to the path where the Noxfile is. # This will ensure that the Noxfile's path is on sys.path, and that # import-time path resolutions work the way the Noxfile author would # guess. os.chdir(os.path.realpath(os.path.dirname(global_config.noxfile))) return importlib.machinery.SourceFileLoader( "user_nox_module", global_config.noxfile).load_module() # type: ignore except (VersionCheckFailed, InvalidVersionSpecifier) as error: logger.error(str(error)) return 2 except (IOError, OSError): logger.exception("Failed to load Noxfile {}".format( global_config.noxfile)) return 2
def execute(self): session_friendly_name = self.signature or self.name logger.warning('Running session {}'.format(session_friendly_name)) try: if not self._create_config(): logger.error( 'Session {} aborted.'.format(session_friendly_name)) return False self._create_venv() cwd = py.path.local(os.getcwd()).as_cwd() with cwd: self._run_commands() logger.success( 'Session {} successful. :)'.format(session_friendly_name)) return True except CommandFailed: logger.error('Session {} failed. :('.format(session_friendly_name)) return False except KeyboardInterrupt: logger.error( 'Session {} interrupted.'.format(session_friendly_name)) raise
def run(global_config): try: user_nox_module = load_user_nox_module(global_config.noxfile) except IOError: logger.error('Noxfile {} not found.'.format(global_config.noxfile)) return False session_functions = discover_session_functions(user_nox_module) sessions = make_sessions(session_functions, global_config) if global_config.list_sessions: print('Available sessions:') for session in sessions: print('*', session.signature or session.name) return True if global_config.sessions: sessions = filter_sessions(global_config.sessions, sessions) if not sessions: return False success = True results = [] for session in sessions: result = session.execute() results.append((session, result)) success = success and result if not success and global_config.stop_on_first_error: success = False break if len(results) > 1: print_summary(results) if global_config.report is not None: create_report(global_config.report, success, results) return success
def run(args, *, env=None, silent=False, path=None, success_codes=None, log=True, external=False, **popen_kws): """Run a command-line program.""" if success_codes is None: success_codes = [0] cmd, args = args[0], args[1:] full_cmd = "{} {}".format(cmd, " ".join(args)) cmd_path = which(cmd, path) if log: logger.info(full_cmd) is_external_tool = path is not None and not cmd_path.startswith(path) if is_external_tool: if external == "error": logger.error( "Error: {} is not installed into the virtualenv, it is located at {}. " "Pass external=True into run() to explicitly allow this.". format(cmd, cmd_path)) raise CommandFailed("External program disallowed.") elif external is False: logger.warning( "Warning: {} is not installed into the virtualenv, it is located at {}. This might cause issues! " "Pass external=True into run() to silence this message.". format(cmd, cmd_path)) env = _clean_env(env) try: return_code, output = popen([cmd_path] + list(args), silent=silent, env=env, **popen_kws) if return_code not in success_codes: logger.error("Command {} failed with exit code {}{}".format( full_cmd, return_code, ":" if silent else "")) if silent: sys.stderr.write(output) raise CommandFailed("Returned code {}".format(return_code)) return output if silent else True except KeyboardInterrupt: logger.error("Interrupted...") raise
def execute(self): session_friendly_name = self.signature or self.name logger.warning("Running session {}".format(session_friendly_name)) try: # By default, nox should quietly change to the directory where # the noxfile.py file is located. cwd = py.path.local( os.path.realpath(os.path.dirname( self.global_config.noxfile))).as_cwd() with cwd: self._create_venv() session = Session(self) self.func(session) # Nothing went wrong; return a success. return Result(self, Status.SUCCESS) except _SessionQuit: return Result(self, Status.ABORTED) except _SessionSkip: return Result(self, Status.SKIPPED) except nox.command.CommandFailed: return Result(self, Status.FAILED) except KeyboardInterrupt: logger.error("Session {} interrupted.".format(self)) raise except Exception as exc: logger.exception("Session {} raised exception {!r}".format( self, exc)) return Result(self, Status.FAILED)
def run( args: Sequence[str], *, env: Optional[dict] = None, silent: bool = False, paths: Optional[List[str]] = None, success_codes: Optional[Iterable[int]] = None, log: bool = True, external: Union[Literal["error"], bool] = False, **popen_kws: Any, ) -> Union[str, bool]: """Run a command-line program.""" if success_codes is None: success_codes = [0] cmd, args = args[0], args[1:] full_cmd = f"{cmd} {_shlex_join(args)}" cmd_path = which(cmd, paths) if log: logger.info(full_cmd) is_external_tool = paths is not None and not any( cmd_path.startswith(path) for path in paths) if is_external_tool: if external == "error": logger.error( f"Error: {cmd} is not installed into the virtualenv, it is located at {cmd_path}. " "Pass external=True into run() to explicitly allow this.") raise CommandFailed("External program disallowed.") elif external is False: logger.warning( f"Warning: {cmd} is not installed into the virtualenv, it is located at {cmd_path}. This might cause issues! " "Pass external=True into run() to silence this message.") env = _clean_env(env) try: return_code, output = popen([cmd_path] + list(args), silent=silent, env=env, **popen_kws) if return_code not in success_codes: suffix = ":" if silent else "" logger.error( f"Command {full_cmd} failed with exit code {return_code}{suffix}" ) if silent: sys.stderr.write(output) raise CommandFailed(f"Returned code {return_code}") if output: logger.output(output) return output if silent else True except KeyboardInterrupt: logger.error("Interrupted...") raise
def error(self, *args, **kwargs): logger.error(*args, **kwargs) raise _SessionQuit()
def run( args, *, env=None, silent=False, path=None, success_codes=None, log=True, external=False ): """Run a command-line program.""" if success_codes is None: success_codes = [0] cmd, args = args[0], args[1:] full_cmd = "{} {}".format(cmd, " ".join(args)) cmd_path = which(cmd, path) if log: logger.info(full_cmd) is_external_tool = path is not None and not cmd_path.startswith(path) if is_external_tool: if external == "error": logger.error( "Error: {} is not installed into the virtualenv, it is located at {}. " "Pass external=True into run() to explicitly allow this.".format( cmd, cmd_path ) ) raise CommandFailed("External program disallowed.") elif external is False: logger.warning( "Warning: {} is not installed into the virtualenv, is it located at {}. This might cause issues! " "Pass external=True into run() to silence this message.".format( cmd, cmd_path ) ) env = _clean_env(env) try: return_code, output = popen([cmd_path] + list(args), silent=silent, env=env) if return_code not in success_codes: logger.error( "Command {} failed with exit code {}{}".format( full_cmd, return_code, ":" if silent else "" ) ) if silent: sys.stderr.write(output) raise CommandFailed("Returned code {}".format(return_code)) return output if silent else True except KeyboardInterrupt: logger.error("Interrupted...") raise
def error(self, *args, **kwargs): """Immediately aborts the session and optionally logs an error.""" if args or kwargs: logger.error(*args, **kwargs) raise _SessionQuit()
def pre_commit(session: Session) -> None: # noqa: R0912 """Format and check the code.""" if "skip_install" not in session.posargs: extras = "pre-commit testing docs poetry dev_nox" session.poetry_install( extras, no_root=(TOX_CALLS or SKIP_INSTALL), no_dev=(TOX_CALLS or IN_CI), pip_require_venv=poetry_require_venv(session), ) else: session.log("Skipping install step.") #: Set 'show-diff' and 'skip identity hook' show_diff = [] env = {"SKIP": "identity"} if (session.interactive and "diff" in session.posargs) or ( not session.interactive and "nodiff" not in session.posargs): show_diff = ["--show-diff-on-failure"] env = {} #: Add SKIP from posargs to env skip = "" for arg in session.posargs: if arg.startswith("SKIP="): skip = arg break if skip: env = {"SKIP": f"{skip[5:]},{env.get('SKIP', '')}"} #: Get hooks from posargs hooks_arg = "" for arg in session.posargs: if arg.startswith("HOOKS="): hooks_arg = arg break #: Remove processed posargs for arg in ("skip_install", "diff", "nodiff", skip, hooks_arg): with contextlib.suppress(ValueError): session.posargs.remove(arg) hooks = hooks_arg[6:].split(",") if hooks_arg else [""] color = ["--color=always"] if FORCE_COLOR else [] error_hooks = [] for hook in hooks: add_args = show_diff + session.posargs + ([hook] if hook else []) try: session.run("pre-commit", "run", *color, "--all-files", *add_args, env=env) except CommandFailed: error_hooks.append(hook) print( "HINT: to add checks as pre-commit hook run: ", f'"{get_venv_bin_dir(get_venv_path()) / "pre-commit"} ' "install -t pre-commit -t commit-msg.", ) if error_hooks: if hooks != [""]: nox_logger.error( f"The following pre-commit hooks failed: {error_hooks}." # noqa: G004 ) raise CommandFailed
def filter_manifest(manifest: Manifest, global_config: Namespace) -> Manifest | int: """Filter the manifest according to the provided configuration. Args: manifest (~.Manifest): The manifest of sessions to be run. global_config (~nox.main.GlobalConfig): The global configuration. Returns: Union[~.Manifest,int]: ``3`` if a specified session is not found, the manifest otherwise (to be sent to the next task). """ # Shouldn't happen unless the Noxfile is empty if not manifest: logger.error(f"No sessions found in {global_config.noxfile}.") return 3 # Filter by the name of any explicit sessions. # This can raise KeyError if a specified session does not exist; # log this if it happens. The sessions does not come from the Noxfile # if keywords is not empty. if global_config.sessions is not None: try: manifest.filter_by_name(global_config.sessions) except KeyError as exc: logger.error("Error while collecting sessions.") logger.error(exc.args[0]) return 3 if not manifest and not global_config.list_sessions: print( "No sessions selected. Please select a session with -s <session name>.\n" ) _produce_listing(manifest, global_config) return 0 # Filter by python interpreter versions. if global_config.pythons: manifest.filter_by_python_interpreter(global_config.pythons) if not manifest and not global_config.list_sessions: logger.error( "Python version selection caused no sessions to be selected.") return 3 # Filter by tags. if global_config.tags is not None: manifest.filter_by_tags(global_config.tags) if not manifest and not global_config.list_sessions: logger.error("Tag selection caused no sessions to be selected.") return 3 # Filter by keywords. if global_config.keywords: try: ast.parse(global_config.keywords, mode="eval") except SyntaxError: logger.error( "Error while collecting sessions: keywords argument must be a Python" " expression.") return 3 # This function never errors, but may cause an empty list of sessions # (which is an error condition later). manifest.filter_by_keywords(global_config.keywords) if not manifest and not global_config.list_sessions: logger.error("No sessions selected after filtering by keyword.") return 3 # Return the modified manifest. return manifest