diff --git a/helm/blueapi/config_schema.json b/helm/blueapi/config_schema.json index 0755a2c6e..8f7861c38 100644 --- a/helm/blueapi/config_schema.json +++ b/helm/blueapi/config_schema.json @@ -424,6 +424,12 @@ "description": "Revision (branch or tag) to check out when cloning - defaults to remote's HEAD. If a tag is used, the repo will be left in a 'detached head' state.", "title": "Target Revision", "type": "string" + }, + "use_uv_lock": { + "default": false, + "description": "Whether to install the repository using uv.lock", + "title": "Use Uv Lock", + "type": "boolean" } }, "title": "ScratchRepository", diff --git a/helm/blueapi/values.schema.json b/helm/blueapi/values.schema.json index 3020e5502..7db42697e 100644 --- a/helm/blueapi/values.schema.json +++ b/helm/blueapi/values.schema.json @@ -849,6 +849,12 @@ "title": "Target Revision", "description": "Revision (branch or tag) to check out when cloning - defaults to remote's HEAD. If a tag is used, the repo will be left in a 'detached head' state.", "type": "string" + }, + "use_uv_lock": { + "title": "Use Uv Lock", + "description": "Whether to install the repository using uv.lock", + "default": false, + "type": "boolean" } }, "additionalProperties": false diff --git a/src/blueapi/cli/scratch.py b/src/blueapi/cli/scratch.py index 6fa01d83e..d06d6325e 100644 --- a/src/blueapi/cli/scratch.py +++ b/src/blueapi/cli/scratch.py @@ -66,6 +66,7 @@ def setup_scratch( scratch_install( *(config.root / repo.name for repo in config.repositories), + use_uv_lock=[repo.use_uv_lock for repo in config.repositories], timeout=install_timeout, ) @@ -116,7 +117,11 @@ def ensure_repo( ) -def scratch_install(*paths: Path, timeout: float = _DEFAULT_INSTALL_TIMEOUT) -> None: +def scratch_install( + *paths: Path, + use_uv_lock: list[bool] | None = None, + timeout: float = _DEFAULT_INSTALL_TIMEOUT, +) -> None: """ Install scratch packages. Make blueapi aware of repositories checked out in the scratch area. Make it automatically follow code changes to those repositories @@ -125,15 +130,25 @@ def scratch_install(*paths: Path, timeout: float = _DEFAULT_INSTALL_TIMEOUT) -> Args: paths: List of Paths to the checked out repositories + use_uv_lock: Optional list of booleans indicating whether to install + using uv.lock timeout: Time to wait for installation subprocess """ if not paths: return + + use_uv_lock_list = use_uv_lock or [False] * len(paths) + LOGGER.info("Installing packages") - for path in paths: + for path, uv_lock in zip(paths, use_uv_lock_list, strict=True): _validate_directory(path) - args = ["uv", "pip", "install", "-e", str(path)] - process = Popen(args) + if uv_lock: + args = ["uv", "sync", "--inexact"] + process = Popen(args, cwd=path) + else: + args = ["uv", "pip", "install", "-e", str(path)] + process = Popen(args) + process.wait(timeout=timeout) if process.returncode != 0: raise RuntimeError( diff --git a/src/blueapi/config.py b/src/blueapi/config.py index 9c339c71f..6e25667b8 100644 --- a/src/blueapi/config.py +++ b/src/blueapi/config.py @@ -209,6 +209,10 @@ class ScratchRepository(BlueapiBaseModel): # include an invalid value default_factory=lambda: None, ) + use_uv_lock: bool = Field( + description="Whether to install the repository using uv.lock", + default=False, + ) @field_validator("remote_url") @classmethod diff --git a/tests/unit_tests/cli/test_scratch.py b/tests/unit_tests/cli/test_scratch.py index 22f3ad3d8..60fc5dff0 100644 --- a/tests/unit_tests/cli/test_scratch.py +++ b/tests/unit_tests/cli/test_scratch.py @@ -62,6 +62,22 @@ def test_scratch_install_installs_path( ) +@patch("blueapi.cli.scratch.Popen") +def test_scratch_install_with_uv_lock( + mock_popen: Mock, + directory_path_with_sgid: Path, +): + mock_process = Mock() + mock_process.returncode = 0 + mock_popen.return_value = mock_process + + scratch_install(directory_path_with_sgid, use_uv_lock=[True], timeout=1.0) + + mock_popen.assert_called_once_with( + ["uv", "sync", "--inexact"], cwd=directory_path_with_sgid + ) + + def test_scratch_install_fails_on_file(file_path: Path): with pytest.raises(KeyError): scratch_install(file_path, timeout=1.0) @@ -348,6 +364,7 @@ def test_setup_scratch_iterates_repos( call( directory_path_with_sgid / "foo", directory_path_with_sgid / "bar", + use_uv_lock=[False, False], timeout=120.0, ), ] diff --git a/tests/unit_tests/test_config.py b/tests/unit_tests/test_config.py index a335434df..6a05c9a3b 100644 --- a/tests/unit_tests/test_config.py +++ b/tests/unit_tests/test_config.py @@ -333,6 +333,7 @@ def test_config_yaml_parsed(temp_yaml_config_file): { "name": "dodal", "remote_url": "https://github.com/DiamondLightSource/dodal.git", + "use_uv_lock": False, } ], }, @@ -396,6 +397,7 @@ def test_config_yaml_parsed(temp_yaml_config_file): { "name": "dodal", "remote_url": "https://github.com/DiamondLightSource/dodal.git", + "use_uv_lock": False, } ], },