Repository: lmstudio-ai/venvstacks

Issue:
Bug: `--only-binary :all:` should be set when locking environments
`--only-binary :all:` is currently being passed to `pip install` when building environments, but it isn't being passed to `uv pip compile` when *locking* environments. This allows an invalid lock file to be generated that depends on source archives that the environment building step will fail to install.

This option should be added to the set of common pip arguments defined by `PackageIndexConfig` rather than being set in the individual CLI invocations.

Hints from issue comments:
@ncoghlan: Based on https://docs.astral.sh/uv/configuration/files/, I think a potential workaround may be to create a `uv.toml` file alongside `venvstacks.toml`, containing the text:

```toml
[pip]
only-binary=":all:"
```

(I haven't actually tested that, though, it's just theoretical based on reading the uv configuration docs)

Solution patch:
diff --git a/docs/changelog.d/20250324_223904_ncoghlan_require_binaries_when_locking.rst b/docs/changelog.d/20250324_223904_ncoghlan_require_binaries_when_locking.rst
new file mode 100644
index 0000000..85a7e0d
--- /dev/null
+++ b/docs/changelog.d/20250324_223904_ncoghlan_require_binaries_when_locking.rst
@@ -0,0 +1,6 @@
+Fixed
+-----
+
+- `--only-binary ":all:"` is now passed when locking the layers in addition
+  to being passed when creating the layer environments. This avoids emitting
+  requirements that can't be installed (resolved in :issue:`102`).
diff --git a/src/venvstacks/stacks.py b/src/venvstacks/stacks.py
index 50474d4..1740fa3 100755
--- a/src/venvstacks/stacks.py
+++ b/src/venvstacks/stacks.py
@@ -148,7 +148,8 @@ class PackageIndexConfig:
         ]
 
     def _get_common_pip_args(self) -> list[str]:
-        result = []
+        # Local wheel builds are expected for any source-only dependencies
+        result = ["--only-binary", ":all:"]
         if not self.query_default_index:
             result.append("--no-index")
         for local_wheel_path in self.local_wheel_paths:
@@ -1318,8 +1319,6 @@ class LayerEnvBase(ABC):
             "install",
             "--no-warn-script-location",
             *self.index_config._get_pip_install_args(),
-            "--only-binary",
-            ":all:",
             "--no-deps",
             "--upgrade",
             *pip_install_args,


Test patch:
diff --git a/tests/test_index_config.py b/tests/test_index_config.py
index 6718155..bc29cb8 100644
--- a/tests/test_index_config.py
+++ b/tests/test_index_config.py
@@ -13,10 +13,16 @@ class TestDefaultOptions:
     TEST_CONFIG = PackageIndexConfig()
 
     def test_uv_pip_compile(self) -> None:
-        assert self.TEST_CONFIG._get_uv_pip_compile_args() == []
+        assert self.TEST_CONFIG._get_uv_pip_compile_args() == [
+            "--only-binary",
+            ":all:",
+        ]
 
     def test_pip_install(self) -> None:
-        assert self.TEST_CONFIG._get_pip_install_args() == []
+        assert self.TEST_CONFIG._get_pip_install_args() == [
+            "--only-binary",
+            ":all:",
+        ]
 
 
 class TestConfiguredOptions:
@@ -29,6 +35,8 @@ class TestConfiguredOptions:
     def test_uv_pip_compile(self) -> None:
         # There are currently no locking specific args
         assert self.TEST_CONFIG._get_uv_pip_compile_args() == [
+            "--only-binary",
+            ":all:",
             "--no-index",
             "--find-links",
             self.WHEEL_DIR,
@@ -37,6 +45,8 @@ class TestConfiguredOptions:
     def test_pip_install(self) -> None:
         # There are currently no installation specific args
         assert self.TEST_CONFIG._get_pip_install_args() == [
+            "--only-binary",
+            ":all:",
             "--no-index",
             "--find-links",
             self.WHEEL_DIR,