Skip to content

Commit

Permalink
Rename env_impl param to installer
Browse files Browse the repository at this point in the history
  • Loading branch information
layday authored and gaborbernat committed Mar 10, 2024
1 parent 577ebc0 commit a90e7db
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 60 deletions.
27 changes: 13 additions & 14 deletions src/build/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,9 @@ def _build_in_isolated_env(
outdir: StrPath,
distribution: Distribution,
config_settings: ConfigSettings | None,
env_impl: _env.EnvImpl | None,
installer: _env.Installer,
) -> str:
with DefaultIsolatedEnv(env_impl) as env:
with DefaultIsolatedEnv(installer=installer) as env:
builder = ProjectBuilder.from_isolated_env(env, srcdir)
# first install the build dependencies
env.install(builder.build_system_requires)
Expand Down Expand Up @@ -162,10 +162,10 @@ def _build(
distribution: Distribution,
config_settings: ConfigSettings | None,
skip_dependency_check: bool,
env_impl: _env.EnvImpl | None,
installer: _env.Installer,
) -> str:
if isolation:
return _build_in_isolated_env(srcdir, outdir, distribution, config_settings, env_impl)
return _build_in_isolated_env(srcdir, outdir, distribution, config_settings, installer)
else:
return _build_in_current_env(srcdir, outdir, distribution, config_settings, skip_dependency_check)

Expand Down Expand Up @@ -219,7 +219,7 @@ def build_package(
config_settings: ConfigSettings | None = None,
isolation: bool = True,
skip_dependency_check: bool = False,
env_impl: _env.EnvImpl | None = None,
installer: _env.Installer = 'pip',
) -> Sequence[str]:
"""
Run the build process.
Expand All @@ -233,7 +233,7 @@ def build_package(
"""
built: list[str] = []
for distribution in distributions:
out = _build(isolation, srcdir, outdir, distribution, config_settings, skip_dependency_check, env_impl)
out = _build(isolation, srcdir, outdir, distribution, config_settings, skip_dependency_check, installer)
built.append(os.path.basename(out))
return built

Expand All @@ -245,7 +245,7 @@ def build_package_via_sdist(
config_settings: ConfigSettings | None = None,
isolation: bool = True,
skip_dependency_check: bool = False,
env_impl: _env.EnvImpl | None = None,
installer: _env.Installer = 'pip',
) -> Sequence[str]:
"""
Build a sdist and then the specified distributions from it.
Expand All @@ -263,7 +263,7 @@ def build_package_via_sdist(
msg = 'Only binary distributions are allowed but sdist was specified'
raise ValueError(msg)

sdist = _build(isolation, srcdir, outdir, 'sdist', config_settings, skip_dependency_check, env_impl)
sdist = _build(isolation, srcdir, outdir, 'sdist', config_settings, skip_dependency_check, installer)

sdist_name = os.path.basename(sdist)
sdist_out = tempfile.mkdtemp(prefix='build-via-sdist-')
Expand All @@ -276,7 +276,7 @@ def build_package_via_sdist(
_ctx.log(f'Building {_natural_language_list(distributions)} from sdist')
srcdir = os.path.join(sdist_out, sdist_name[: -len('.tar.gz')])
for distribution in distributions:
out = _build(isolation, srcdir, outdir, distribution, config_settings, skip_dependency_check, env_impl)
out = _build(isolation, srcdir, outdir, distribution, config_settings, skip_dependency_check, installer)
built.append(os.path.basename(out))
finally:
shutil.rmtree(sdist_out, ignore_errors=True)
Expand Down Expand Up @@ -369,10 +369,9 @@ def main_parser() -> argparse.ArgumentParser:
'Build dependencies must be installed separately when this option is used',
)
env_group.add_argument(
'--env-impl',
choices=_env.ENV_IMPLS,
help='isolated environment implementation to use. Defaults to virtualenv if installed, '
' otherwise venv. uv support is experimental.',
'--installer',
choices=_env.INSTALLERS,
help='Python package installer to use (defaults to pip)',
)
parser.add_argument(
'--config-setting',
Expand Down Expand Up @@ -432,7 +431,7 @@ def main(cli_args: Sequence[str], prog: str | None = None) -> None:
config_settings,
not args.no_isolation,
args.skip_dependency_check,
args.env_impl,
args.installer,
)
artifact_list = _natural_language_list(
['{underline}{}{reset}{bold}{green}'.format(artifact, **_styles.get()) for artifact in built]
Expand Down
43 changes: 21 additions & 22 deletions src/build/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,9 @@
from ._util import check_dependency


EnvImpl = typing.Literal['venv+uv']
Installer = typing.Literal['pip', 'uv']

ENV_IMPLS = typing.get_args(EnvImpl)

_EnvImplInclDefault = typing.Literal['venv+pip', 'virtualenv+pip', EnvImpl]
INSTALLERS = typing.get_args(Installer)


class IsolatedEnv(typing.Protocol):
Expand Down Expand Up @@ -67,9 +65,10 @@ class DefaultIsolatedEnv(IsolatedEnv):

def __init__(
self,
env_impl: EnvImpl | None = None,
*,
installer: Installer = 'pip',
) -> None:
self.env_impl = env_impl
self.installer: Installer = installer

def __enter__(self) -> DefaultIsolatedEnv:
try:
Expand All @@ -82,16 +81,16 @@ def __enter__(self) -> DefaultIsolatedEnv:
path = os.path.realpath(path)
self._path = path

self._env_impl_backend: _EnvImplBackend
self._env_backend: _EnvBackend

# uv is opt-in only.
if self.env_impl == 'venv+uv':
self._env_impl_backend = _UvImplBackend()
if self.installer == 'uv':
self._env_backend = _UvBackend()
else:
self._env_impl_backend = _DefaultImplBackend()
self._env_backend = _PipBackend()

_ctx.log(f'Creating isolated environment: {self._env_impl_backend.name}...')
self._env_impl_backend.create(self._path)
_ctx.log(f'Creating isolated environment: {self._env_backend.display_name}...')
self._env_backend.create(self._path)

except Exception: # cleanup folder if creation fails
self.__exit__(*sys.exc_info())
Expand All @@ -111,14 +110,14 @@ def path(self) -> str:
@property
def python_executable(self) -> str:
"""The python executable of the isolated build environment."""
return self._env_impl_backend.python_executable
return self._env_backend.python_executable

def make_extra_environ(self) -> dict[str, str]:
path = os.environ.get('PATH')
return {
'PATH': os.pathsep.join([self._env_impl_backend.scripts_dir, path])
'PATH': os.pathsep.join([self._env_backend.scripts_dir, path])
if path is not None
else self._env_impl_backend.scripts_dir
else self._env_backend.scripts_dir
}

def install(self, requirements: Collection[str]) -> None:
Expand All @@ -134,10 +133,10 @@ def install(self, requirements: Collection[str]) -> None:
return

_ctx.log('Installing packages in isolated environment:\n' + '\n'.join(f'- {r}' for r in sorted(requirements)))
self._env_impl_backend.install_requirements(requirements)
self._env_backend.install_requirements(requirements)


class _EnvImplBackend(typing.Protocol): # pragma: no cover
class _EnvBackend(typing.Protocol): # pragma: no cover
python_executable: str
scripts_dir: str

Expand All @@ -148,11 +147,11 @@ def install_requirements(self, requirements: Collection[str]) -> None:
...

@property
def name(self) -> _EnvImplInclDefault:
def display_name(self) -> str:
...


class _DefaultImplBackend(_EnvImplBackend):
class _PipBackend(_EnvBackend):
def __init__(self) -> None:
self._create_with_virtualenv = not self._has_valid_outer_pip and self._has_virtualenv

Expand Down Expand Up @@ -271,11 +270,11 @@ def install_requirements(self, requirements: Collection[str]) -> None:
os.unlink(req_file.name)

@property
def name(self) -> _EnvImplInclDefault:
def display_name(self) -> str:
return 'virtualenv+pip' if self._create_with_virtualenv else 'venv+pip'


class _UvImplBackend(_EnvImplBackend):
class _UvBackend(_EnvBackend):
def create(self, path: str) -> None:
import venv

Expand Down Expand Up @@ -307,7 +306,7 @@ def install_requirements(self, requirements: Collection[str]) -> None:
run_subprocess([*cmd, 'install', *requirements], env={**os.environ, 'VIRTUAL_ENV': self._env_path})

@property
def name(self) -> EnvImpl:
def display_name(self) -> str:
return 'venv+uv'


Expand Down
4 changes: 2 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,13 @@ def pytest_runtest_call(item: pytest.Item):

@pytest.fixture
def local_pip(monkeypatch):
monkeypatch.setattr(build.env._DefaultImplBackend, '_has_valid_outer_pip', None)
monkeypatch.setattr(build.env._PipBackend, '_has_valid_outer_pip', None)


@pytest.fixture(autouse=True, params=[False])
def has_virtualenv(request, monkeypatch):
if request.param is not None:
monkeypatch.setattr(build.env._DefaultImplBackend, '_has_virtualenv', request.param)
monkeypatch.setattr(build.env._PipBackend, '_has_virtualenv', request.param)


@pytest.fixture(scope='session', autouse=True)
Expand Down
34 changes: 17 additions & 17 deletions tests/test_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ def test_install_short_circuits(
mocker: pytest_mock.MockerFixture,
):
with build.env.DefaultIsolatedEnv() as env:
install_requirements = mocker.patch.object(env._env_impl_backend, 'install_requirements')
install_requirements = mocker.patch.object(env._env_backend, 'install_requirements')

env.install([])
install_requirements.assert_not_called()
Expand Down Expand Up @@ -210,7 +210,7 @@ def test_uv_impl_install_cmd_well_formed(
):
mocker.patch.object(build.env._ctx, 'verbosity', verbosity)

with build.env.DefaultIsolatedEnv('venv+uv') as env:
with build.env.DefaultIsolatedEnv(installer='uv') as env:
run_subprocess = mocker.patch('build.env.run_subprocess')

env.install(['some', 'requirements'])
Expand All @@ -230,37 +230,37 @@ def test_uv_impl_install_cmd_well_formed(

@pytest.mark.usefixtures('local_pip')
@pytest.mark.parametrize(
('env_impl', 'env_impl_display_name', 'has_virtualenv'),
('installer', 'env_backend_display_name', 'has_virtualenv'),
[
(None, 'venv+pip', False),
(None, 'virtualenv+pip', True),
(None, 'virtualenv+pip', None), # Fall-through
('venv+uv', 'venv+uv', None),
('pip', 'venv+pip', False),
('pip', 'virtualenv+pip', True),
('pip', 'virtualenv+pip', None), # Fall-through
('uv', 'venv+uv', None),
],
indirect=('has_virtualenv',),
)
def test_env_creation(
env_impl: build.env.EnvImpl | None,
env_impl_display_name: str,
def test_venv_creation(
installer: build.env.Installer,
env_backend_display_name: str,
):
with build.env.DefaultIsolatedEnv(env_impl) as env:
assert env._env_impl_backend.name == env_impl_display_name
with build.env.DefaultIsolatedEnv(installer=installer) as env:
assert env._env_backend.display_name == env_backend_display_name


@pytest.mark.network
@pytest.mark.usefixtures('local_pip')
@pytest.mark.parametrize(
'env_impl',
'installer',
[
None,
pytest.param('venv+uv', marks=pytest.mark.xfail(IS_PYPY and IS_WINDOWS, reason='uv cannot find PyPy executable')),
pytest.param('uv', marks=pytest.mark.xfail(IS_PYPY and IS_WINDOWS, reason='uv cannot find PyPy executable')),
],
)
def test_requirement_installation(
package_test_flit: str,
env_impl: build.env.EnvImpl | None,
installer: build.env.Installer,
):
with build.env.DefaultIsolatedEnv(env_impl) as env:
with build.env.DefaultIsolatedEnv(installer=installer) as env:
env.install([f'test-flit @ {Path(package_test_flit).as_uri()}'])


Expand All @@ -270,5 +270,5 @@ def test_uv_missing(
mocker.patch('shutil.which', return_value=None)

with pytest.raises(RuntimeError, match='uv executable missing'):
with build.env.DefaultIsolatedEnv('venv+uv'):
with build.env.DefaultIsolatedEnv(installer='uv'):
pass
6 changes: 3 additions & 3 deletions tests/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ def _ignore_folder(base, filenames):
)
@pytest.mark.parametrize(
'args',
[[], ['--env-impl', 'venv+uv'], ['-x', '--no-isolation']],
ids=['isolated', 'isolated_venv+uv', 'no_isolation'],
[[], ['--installer', 'uv'], ['-x', '--no-isolation']],
ids=['isolated_pip', 'isolated_uv', 'no_isolation'],
)
@pytest.mark.parametrize(
'project',
Expand All @@ -93,7 +93,7 @@ def _ignore_folder(base, filenames):
)
@pytest.mark.isolated
def test_build(request, monkeypatch, project, args, call, tmp_path):
if args == ['--env-impl', 'venv+uv'] and IS_PYPY:
if args == ['--installer', 'uv'] and IS_WINDOWS and IS_PYPY:
pytest.xfail('uv cannot find PyPy executable')
if project in {'build', 'flit'} and '--no-isolation' in args:
pytest.xfail(f"can't build {project} without isolation due to missing dependencies")
Expand Down
4 changes: 2 additions & 2 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@
'build_package_via_sdist',
),
(
['--env-impl', 'venv+uv'],
[cwd, out, ['wheel'], {}, True, False, 'venv+uv'],
['--installer', 'uv'],
[cwd, out, ['wheel'], {}, True, False, 'uv'],
'build_package_via_sdist',
),
(
Expand Down

0 comments on commit a90e7db

Please sign in to comment.