Skip to content

Commit

Permalink
Fix automatic installation of environment plugins (#1141)
Browse files Browse the repository at this point in the history
  • Loading branch information
ofek authored Dec 14, 2023
1 parent 3d5c35e commit 0029e35
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 18 deletions.
1 change: 1 addition & 0 deletions .github/workflows/build-hatch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ jobs:
PYAPP_REPO: pyapp
PYAPP_VERSION: "0.11.1"
PYAPP_PIP_EXTERNAL: "true"
PYAPP_PASS_LOCATION: "true"

steps:
- name: Checkout code
Expand Down
1 change: 1 addition & 0 deletions docs/history/hatch.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
***Fixed:***

- Fix regression in calling subprocesses with updated PATH
- Fix automatic installation of environment plugins when running as a standalone binary

## [1.8.0](https://github.com/pypa/hatch/releases/tag/hatch-v1.8.0) - 2023-12-11 ## {: #hatch-v1.8.0 }

Expand Down
33 changes: 19 additions & 14 deletions src/hatch/cli/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,26 +185,31 @@ def ensure_plugin_dependencies(self, dependencies: list[Requirement], *, wait_me
from hatch.env.utils import add_verbosity_flag
from hatchling.dep.core import dependencies_in_sync

if dependencies_in_sync(dependencies):
return
if app_path := os.environ.get('PYAPP'):
from hatch.utils.env import PythonInfo

management_command = os.environ['PYAPP_COMMAND_NAME']
executable = self.platform.check_command_output([app_path, management_command, 'python-path']).strip()
python_info = PythonInfo(self.platform, executable=executable)
if dependencies_in_sync(dependencies, sys_path=python_info.sys_path):
return

pip_command = [app_path, management_command, 'pip']
else:
if dependencies_in_sync(dependencies):
return

pip_command = [sys.executable, '-u', '-m', 'pip']

command = [
sys.executable,
'-u',
'-m',
'pip',
'install',
'--disable-pip-version-check',
'--no-python-version-warning',
]
pip_command.extend(['install', '--disable-pip-version-check', '--no-python-version-warning'])

# Default to -1 verbosity
add_verbosity_flag(command, self.verbosity, adjustment=-1)
add_verbosity_flag(pip_command, self.verbosity, adjustment=-1)

command.extend(str(dependency) for dependency in dependencies)
pip_command.extend(str(dependency) for dependency in dependencies)

with self.status(wait_message):
self.platform.check_command(command)
self.platform.check_command(pip_command)

def get_env_directory(self, environment_type):
directories = self.config.dirs.env
Expand Down
63 changes: 63 additions & 0 deletions tests/cli/env/test_create.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
import sys

import pytest

Expand Down Expand Up @@ -1622,3 +1623,65 @@ def test_plugin_dependencies_met(hatch, config_file, helpers, temp_dir):
env_path = env_dirs[0]

assert env_path.name == project_path.name


@pytest.mark.usefixtures('mock_plugin_installation')
def test_plugin_dependencies_met_as_app(hatch, config_file, helpers, temp_dir):
config_file.model.template.plugins['default']['tests'] = False
config_file.save()

project_name = 'My.App'

with temp_dir.as_cwd():
result = hatch('new', project_name)

assert result.exit_code == 0, result.output

project_path = temp_dir / 'my-app'
data_path = temp_dir / 'data'
data_path.mkdir()

dependency = 'hatch'
(project_path / DEFAULT_CONFIG_FILE).write_text(
helpers.dedent(
f"""
[env]
requires = ["{dependency}"]
"""
)
)

project = Project(project_path)
helpers.update_project_environment(project, 'default', {'skip-install': True, **project.config.envs['default']})

with project_path.as_cwd(
env_vars={ConfigEnvVars.DATA: str(data_path), 'PYAPP': sys.executable, 'PYAPP_COMMAND_NAME': 'self'}
):
result = hatch('env', 'create')

assert result.exit_code == 0, result.output
assert result.output == helpers.dedent(
"""
Creating environment: default
Checking dependencies
"""
)

env_data_path = data_path / 'env' / 'virtual'
assert env_data_path.is_dir()

project_data_path = env_data_path / project_path.name
assert project_data_path.is_dir()

storage_dirs = list(project_data_path.iterdir())
assert len(storage_dirs) == 1

storage_path = storage_dirs[0]
assert len(storage_path.name) == 8

env_dirs = list(storage_path.iterdir())
assert len(env_dirs) == 1

env_path = env_dirs[0]

assert env_path.name == project_path.name
12 changes: 8 additions & 4 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -366,11 +366,15 @@ def mock_plugin_installation(mocker):
mocked_subprocess_run = mocker.MagicMock(returncode=0)

def _mock(command, **kwargs):
if not isinstance(command, list) or command[:5] != [sys.executable, '-u', '-m', 'pip', 'install']: # no cov
return subprocess_run(command, **kwargs)
if isinstance(command, list):
if command[:5] == [sys.executable, '-u', '-m', 'pip', 'install']:
mocked_subprocess_run(command, **kwargs)
return mocked_subprocess_run

mocked_subprocess_run(command, **kwargs)
return mocked_subprocess_run
if command[:3] == [sys.executable, 'self', 'python-path']:
return mocker.MagicMock(returncode=0, stdout=sys.executable.encode())

return subprocess_run(command, **kwargs) # no cov

mocker.patch('subprocess.run', side_effect=_mock)

Expand Down

0 comments on commit 0029e35

Please sign in to comment.