Skip to content

Commit

Permalink
Merge pull request #64 from coveo/fix/include-host-env-vars
Browse files Browse the repository at this point in the history
fix: inject environment variables from the host
  • Loading branch information
jonapich authored Oct 18, 2024
2 parents db9512a + e06ad7a commit 46995e9
Show file tree
Hide file tree
Showing 13 changed files with 192 additions and 131 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/coveo-stew.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
poetry-version: [""]
os: [ubuntu-latest, windows-latest, macos-latest]

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Similar to: nothing! it's unique! 😎


# Prerequisites
*Changed in 3.1*: You need python 3.9+ to run stew, but you can still use it on projects requiring older versions of python.

*Changed in 3.0*: `poetry` is no longer provided out-of-the-box.

Expand Down
10 changes: 7 additions & 3 deletions coveo_stew/ci/any_runner.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from enum import Enum, auto
from typing import Iterable, List, Optional, Tuple, Union
from typing import Any, Iterable, List, Optional, Tuple, Union

from coveo_styles.styles import ExitWithFailure
from coveo_systools.filesystem import find_repo_root
Expand Down Expand Up @@ -68,7 +68,9 @@ def __init__(
f"Working directory for {self.name} should be within {WorkingDirectoryKind.valid_values()}"
)

async def _launch(self, environment: PythonEnvironment, *extra_args: str) -> RunnerStatus:
async def _launch(
self, environment: PythonEnvironment, *extra_args: str, **kwargs: Any
) -> RunnerStatus:
args = [self.check_args] if isinstance(self.check_args, str) else self.check_args
command = environment.build_command(self.executable, *args)

Expand All @@ -83,6 +85,7 @@ async def _launch(self, environment: PythonEnvironment, *extra_args: str) -> Run
*extra_args,
working_directory=working_directory,
verbose=self._pyproject.verbose,
**kwargs,
)
).split("\n")
)
Expand All @@ -97,7 +100,7 @@ def name(self) -> str:
def executable(self) -> str:
return self._executable or self.name

async def _custom_autofix(self, environment: PythonEnvironment) -> None:
async def _custom_autofix(self, environment: PythonEnvironment, **kwargs: Any) -> None:
args = [self.autofix_args] if isinstance(self.autofix_args, str) else self.autofix_args
command = environment.build_command(self.executable, *args)

Expand All @@ -111,6 +114,7 @@ async def _custom_autofix(self, environment: PythonEnvironment) -> None:
*command,
working_directory=working_directory,
verbose=self._pyproject.verbose,
**kwargs,
)
).split("\n")
)
19 changes: 13 additions & 6 deletions coveo_stew/ci/black_runner.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from typing import Any

from coveo_systools.subprocess import DetailedCalledProcessError, async_check_output

from coveo_stew.ci.runner import ContinuousIntegrationRunner
Expand All @@ -14,22 +16,27 @@ def __init__(self, *, _pyproject: PythonProject) -> None:
super().__init__(_pyproject=_pyproject)
self._auto_fix_routine = self.reformat_files

async def _launch(self, environment: PythonEnvironment, *extra_args: str) -> RunnerStatus:
async def _launch(
self, environment: PythonEnvironment, *extra_args: str, **kwargs: Any
) -> RunnerStatus:
try:
await self._launch_internal(environment, "--check", "--quiet", *extra_args)
await self._launch_internal(environment, "--check", "--quiet", *extra_args, **kwargs)
except DetailedCalledProcessError:
# re-run without the quiet switch so that the output appears in the console
await self._launch_internal(environment, "--check", *extra_args)
await self._launch_internal(environment, "--check", *extra_args, **kwargs)
return RunnerStatus.Success

async def reformat_files(self, environment: PythonEnvironment) -> None:
await self._launch_internal(environment, "--quiet")
async def reformat_files(self, environment: PythonEnvironment, **kwargs: Any) -> None:
await self._launch_internal(environment, "--quiet", **kwargs)

async def _launch_internal(self, environment: PythonEnvironment, *extra_args: str) -> None:
async def _launch_internal(
self, environment: PythonEnvironment, *extra_args: str, **kwargs: Any
) -> None:
# projects may opt to use coveo-stew's black version by not including black in their dependencies.
command = environment.build_command(PythonTool.Black, ".", *extra_args)
await async_check_output(
*command,
working_directory=self._pyproject.project_path,
verbose=self._pyproject.verbose,
**kwargs,
)
7 changes: 5 additions & 2 deletions coveo_stew/ci/mypy_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import re
from contextlib import ExitStack
from pathlib import Path
from typing import Generator, Optional, Union
from typing import Any, Generator, Optional, Union

import importlib_resources
from coveo_styles.styles import echo
Expand Down Expand Up @@ -46,7 +46,9 @@ def _find_typed_folders(self) -> Generator[Path, None, None]:
self._pyproject.project_path.iterdir(),
)

async def _launch(self, environment: PythonEnvironment, *extra_args: str) -> RunnerStatus:
async def _launch(
self, environment: PythonEnvironment, *extra_args: str, **kwargs: Any
) -> RunnerStatus:
typed_folders = tuple(folder.name for folder in self._find_typed_folders())

if not typed_folders:
Expand Down Expand Up @@ -84,6 +86,7 @@ async def _launch(self, environment: PythonEnvironment, *extra_args: str) -> Run
*command,
working_directory=self._pyproject.project_path,
verbose=self._pyproject.verbose,
**kwargs,
)
return RunnerStatus.Success

Expand Down
7 changes: 6 additions & 1 deletion coveo_stew/ci/poetry_runners.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from typing import Any

from coveo_systools.subprocess import async_check_output

from coveo_stew.ci.runner import ContinuousIntegrationRunner
Expand All @@ -9,9 +11,12 @@ class PoetryCheckRunner(ContinuousIntegrationRunner):
name: str = "poetry-check"
check_failed_exit_codes = [1]

async def _launch(self, environment: PythonEnvironment, *extra_args: str) -> RunnerStatus:
async def _launch(
self, environment: PythonEnvironment, *extra_args: str, **kwargs: Any
) -> RunnerStatus:
await async_check_output(
*environment.build_command(PythonTool.Poetry, "check"),
working_directory=self._pyproject.project_path,
**kwargs,
)
return RunnerStatus.Success
7 changes: 6 additions & 1 deletion coveo_stew/ci/pytest_runner.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from typing import Any

from coveo_systools.subprocess import async_check_output

from coveo_stew.ci.runner import ContinuousIntegrationRunner
Expand All @@ -22,7 +24,9 @@ def __init__(
self.marker_expression = marker_expression
self.doctest_modules: bool = doctest_modules

async def _launch(self, environment: PythonEnvironment, *extra_args: str) -> RunnerStatus:
async def _launch(
self, environment: PythonEnvironment, *extra_args: str, **kwargs: Any
) -> RunnerStatus:
command = environment.build_command(
PythonTool.Pytest,
"--durations=5",
Expand All @@ -40,6 +44,7 @@ async def _launch(self, environment: PythonEnvironment, *extra_args: str) -> Run
*extra_args,
working_directory=self._pyproject.project_path,
verbose=self._pyproject.verbose,
**kwargs,
)

return RunnerStatus.Success
20 changes: 15 additions & 5 deletions coveo_stew/ci/runner.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import asyncio
import os
from abc import abstractmethod
from dataclasses import dataclass
from functools import cached_property
from pathlib import Path
from typing import Callable, Coroutine, Iterable, List, Optional, Sequence, Tuple
from typing import Any, Coroutine, Iterable, List, Optional, Protocol, Sequence, Tuple

from coveo_styles.styles import echo
from coveo_systools.subprocess import DetailedCalledProcessError
Expand All @@ -15,13 +16,19 @@
from coveo_stew.stew import PythonProject


class AutoFixRoutineCallable(Protocol):
def __call__(
self, environment: PythonEnvironment, **kwargs: Any
) -> Coroutine[None, None, None]: ...


class ContinuousIntegrationRunner:
status: RunnerStatus = RunnerStatus.NotRan
check_failed_exit_codes: Iterable[int] = []
outputs_own_report: bool = False # set to True if the runner produces its own report.

# implementations may provide an auto fix routine.
_auto_fix_routine: Optional[Callable[[PythonEnvironment], Coroutine[None, None, None]]] = None
_auto_fix_routine: Optional[AutoFixRoutineCallable] = None

def __init__(self, *, _pyproject: PythonProject) -> None:
"""Implementations may add additional keyword args."""
Expand All @@ -46,8 +53,9 @@ async def launch(
"""
self._last_output.clear()
self._test_cases.clear()
environment_variables = os.environ.copy()
try:
self.status = await self._launch(environment, *extra_args)
self.status = await self._launch(environment, *extra_args, env=environment_variables)
except DetailedCalledProcessError as exception:
if exception.returncode in self.check_failed_exit_codes:
self.status = RunnerStatus.CheckFailed
Expand All @@ -60,7 +68,7 @@ async def launch(
if all((auto_fix, self.supports_auto_fix, self.status == RunnerStatus.CheckFailed)):
echo.noise("Errors founds; launching auto-fix routine.")
assert self._auto_fix_routine is not None # mypy
await self._auto_fix_routine(environment)
await self._auto_fix_routine(environment, env=environment_variables)

# it should pass now!
await self.launch(environment, *extra_args)
Expand All @@ -85,7 +93,9 @@ def supports_auto_fix(self) -> bool:
return self._auto_fix_routine is not None

@abstractmethod
async def _launch(self, environment: PythonEnvironment, *extra_args: str) -> RunnerStatus:
async def _launch(
self, environment: PythonEnvironment, *extra_args: str, **kwargs: Any
) -> RunnerStatus:
"""Launch the continuous integration check using the given environment and store the output."""

def echo_last_failures(self) -> None:
Expand Down
10 changes: 8 additions & 2 deletions coveo_stew/ci/stew_runners.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import shutil
import tempfile
from pathlib import Path
from typing import Any

from coveo_styles.styles import echo
from coveo_systools.filesystem import pushd
Expand All @@ -16,7 +17,9 @@
class CheckOutdatedRunner(ContinuousIntegrationRunner):
name: str = "check-outdated"

async def _launch(self, environment: PythonEnvironment, *extra_args: str) -> RunnerStatus:
async def _launch(
self, environment: PythonEnvironment, *extra_args: str, **kwargs: Any
) -> RunnerStatus:
if self._pyproject.lock_is_outdated():
self._last_output = ['The lock file is out of date: run "stew fix-outdated"']
return RunnerStatus.CheckFailed
Expand All @@ -26,7 +29,9 @@ async def _launch(self, environment: PythonEnvironment, *extra_args: str) -> Run
class OfflineInstallRunner(ContinuousIntegrationRunner):
name: str = "poetry-build"

async def _launch(self, environment: PythonEnvironment, *extra_args: str) -> RunnerStatus:
async def _launch(
self, environment: PythonEnvironment, *extra_args: str, **kwargs: Any
) -> RunnerStatus:
temporary_folder = Path(tempfile.mkdtemp())
offline_install_location = temporary_folder / "wheels"

Expand Down Expand Up @@ -57,6 +62,7 @@ async def _launch(self, environment: PythonEnvironment, *extra_args: str) -> Run
else ""
),
),
**kwargs,
)
finally:
await asyncio.sleep(0.01) # give a few cycles to close handles/etc
Expand Down
8 changes: 6 additions & 2 deletions coveo_stew/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,19 @@ def build_command(self, tool: Union[PythonTool, str], *args: Any) -> List[Any]:
@lru_cache
def has_tool(self, tool: Union[PythonTool, str]) -> bool:
try:
_ = check_output(self.python_executable, "-c", f"import {tool};", stderr=PIPE)
_ = check_output(
self.python_executable, "-c", f"import {tool};", stderr=PIPE, env=os.environ.copy()
)
return True
except CalledProcessError:
return False

@property
def python_version(self) -> str:
if self._python_version is None:
self._python_version = check_output(str(self.python_executable), "--version").strip()
self._python_version = check_output(
str(self.python_executable), "--version", env=os.environ.copy()
).strip()
assert self._python_version is not None
return self._python_version

Expand Down
4 changes: 3 additions & 1 deletion coveo_stew/offline_publish.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ def _store_setup_dependencies_in_wheelhouse(
*self.environment.build_command(PythonTool.Pip, "wheel", dep),
working_directory=self.wheelhouse,
verbose=self.verbose,
env=os.environ.copy(),
)

def _store_dependencies_in_wheelhouse(self, project: Optional[PythonProject] = None) -> None:
Expand Down Expand Up @@ -167,7 +168,7 @@ def _store_dependencies_in_wheelhouse(self, project: Optional[PythonProject] = N
)

try:
_ = check_output(*command, verbose=self.verbose)
_ = check_output(*command, verbose=self.verbose, env=os.environ.copy())
finally:
try:
Path(requirements_file_path).unlink(missing_ok=True)
Expand All @@ -193,4 +194,5 @@ def _validate_package(self, package_specification: str) -> None:
),
working_directory=self.wheelhouse,
verbose=self.verbose,
env=os.environ.copy(),
)
Loading

0 comments on commit 46995e9

Please sign in to comment.