Skip to content

Commit 2f9d8f8

Browse files
committed
Merge branch 'main' into trampoline-dynamic-path
2 parents 4f2bec5 + 1650d6a commit 2f9d8f8

21 files changed

+250
-35
lines changed

.github/workflows/rust.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ jobs:
133133
cache: true
134134
- uses: Swatinem/rust-cache@v2
135135
with:
136-
workspaces: ". -> target-pixi"
136+
workspaces: ". -> target/pixi"
137137
key: ${{ hashFiles('pixi.lock') }}
138138
- name: Test pixi
139139
run: pixi run test-slow

.github/workflows/test_common_wheels.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ jobs:
88
name: ${{ matrix.arch.name }} - Test Installation of Common Wheels
99
runs-on: ${{ matrix.arch.os }}
1010
env:
11-
TARGET_RELEASE: "${{ github.workspace }}/target-pixi/release"
11+
TARGET_RELEASE: "${{ github.workspace }}/target/pixi/release"
1212
LOGS_DIR: "${{ github.workspace }}/tests/wheel_tests/.logs"
1313
SUMMARY_FILE: "${{ github.workspace }}/tests/wheel_tests/.summary.md"
1414
PYTHONIOENCODING: utf-8

.github/workflows/test_downstream.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ jobs:
156156
timeout-minutes: 30
157157
runs-on: ${{ matrix.arch.os }}
158158
env:
159-
TARGET_RELEASE: "${{ github.workspace }}/target-pixi/release"
159+
TARGET_RELEASE: "${{ github.workspace }}/target/pixi/release"
160160
strategy:
161161
fail-fast: false
162162
matrix:

.gitignore

-1
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,3 @@ __pycache__
99
site/
1010
.cache
1111
pytest-temp
12-
target-pixi

crates/pixi_manifest/src/pypi/pypi_requirement.rs

+45-8
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ pub enum PyPiRequirement {
8585
version: VersionOrStar,
8686
#[serde(default)]
8787
extras: Vec<ExtraName>,
88+
#[serde(default)]
89+
index: Option<Url>,
8890
},
8991
RawVersion(VersionOrStar),
9092
}
@@ -138,6 +140,10 @@ struct RawPyPiRequirement {
138140

139141
// Git and Url only
140142
pub subdirectory: Option<String>,
143+
144+
// Pinned index
145+
#[serde(default)]
146+
pub index: Option<Url>,
141147
}
142148

143149
impl<'de> Deserialize<'de> for PyPiRequirement {
@@ -186,18 +192,24 @@ impl<'de> Deserialize<'de> for PyPiRequirement {
186192
)));
187193
}
188194

189-
let req = match (raw_req.url, raw_req.path, raw_req.git, raw_req.extras) {
190-
(Some(url), None, None, extras) => PyPiRequirement::Url {
195+
let req = match (
196+
raw_req.url,
197+
raw_req.path,
198+
raw_req.git,
199+
raw_req.extras,
200+
raw_req.index,
201+
) {
202+
(Some(url), None, None, extras, None) => PyPiRequirement::Url {
191203
url,
192204
extras,
193205
subdirectory: raw_req.subdirectory,
194206
},
195-
(None, Some(path), None, extras) => PyPiRequirement::Path {
207+
(None, Some(path), None, extras, None) => PyPiRequirement::Path {
196208
path,
197209
editable: raw_req.editable,
198210
extras,
199211
},
200-
(None, None, Some(git), extras) => PyPiRequirement::Git {
212+
(None, None, Some(git), extras, None) => PyPiRequirement::Git {
201213
url: ParsedGitUrl {
202214
git,
203215
branch: raw_req.branch,
@@ -207,13 +219,15 @@ impl<'de> Deserialize<'de> for PyPiRequirement {
207219
},
208220
extras,
209221
},
210-
(None, None, None, extras) => PyPiRequirement::Version {
222+
(None, None, None, extras, index) => PyPiRequirement::Version {
211223
version: raw_req.version.unwrap_or(VersionOrStar::Star),
212224
extras,
225+
index,
213226
},
214-
(_, _, _, extras) if !extras.is_empty() => PyPiRequirement::Version {
227+
(_, _, _, extras, index) if !extras.is_empty() => PyPiRequirement::Version {
215228
version: raw_req.version.unwrap_or(VersionOrStar::Star),
216229
extras,
230+
index,
217231
},
218232
_ => {
219233
return Err(serde_untagged::de::Error::custom(
@@ -278,17 +292,35 @@ impl From<PyPiRequirement> for toml_edit::Value {
278292
}
279293
}
280294

295+
fn insert_index(table: &mut toml_edit::InlineTable, index: &Option<Url>) {
296+
if let Some(index) = index {
297+
table.insert(
298+
"index",
299+
toml_edit::Value::String(toml_edit::Formatted::new(index.to_string())),
300+
);
301+
}
302+
}
303+
281304
match &val {
282-
PyPiRequirement::Version { version, extras } if extras.is_empty() => {
305+
PyPiRequirement::Version {
306+
version,
307+
extras,
308+
index,
309+
} if extras.is_empty() && index.is_none() => {
283310
toml_edit::Value::from(version.to_string())
284311
}
285-
PyPiRequirement::Version { version, extras } => {
312+
PyPiRequirement::Version {
313+
version,
314+
extras,
315+
index,
316+
} => {
286317
let mut table = toml_edit::Table::new().into_inline_table();
287318
table.insert(
288319
"version",
289320
toml_edit::Value::String(toml_edit::Formatted::new(version.to_string())),
290321
);
291322
insert_extras(&mut table, extras);
323+
insert_index(&mut table, index);
292324
toml_edit::Value::InlineTable(table.to_owned())
293325
}
294326
PyPiRequirement::Git {
@@ -423,6 +455,7 @@ impl TryFrom<pep508_rs::Requirement> for PyPiRequirement {
423455
pep508_rs::VersionOrUrl::VersionSpecifier(v) => PyPiRequirement::Version {
424456
version: v.into(),
425457
extras: req.extras,
458+
index: None,
426459
},
427460
pep508_rs::VersionOrUrl::Url(u) => {
428461
let url = u.to_url();
@@ -494,6 +527,7 @@ impl TryFrom<pep508_rs::Requirement> for PyPiRequirement {
494527
PyPiRequirement::Version {
495528
version: VersionOrStar::Star,
496529
extras: req.extras,
530+
index: None,
497531
}
498532
} else {
499533
PyPiRequirement::RawVersion(VersionOrStar::Star)
@@ -616,6 +650,7 @@ mod tests {
616650
&PyPiRequirement::Version {
617651
version: ">=3.12".parse().unwrap(),
618652
extras: vec![ExtraName::from_str("bar").unwrap()],
653+
index: None,
619654
}
620655
);
621656

@@ -636,6 +671,7 @@ mod tests {
636671
ExtraName::from_str("bar").unwrap(),
637672
ExtraName::from_str("foo").unwrap(),
638673
],
674+
index: None,
639675
}
640676
);
641677
}
@@ -659,6 +695,7 @@ mod tests {
659695
ExtraName::from_str("feature1").unwrap(),
660696
ExtraName::from_str("feature2").unwrap()
661697
],
698+
index: None,
662699
}
663700
);
664701
}

crates/pixi_manifest/src/pypi/snapshots/pixi_manifest__pypi__pypi_requirement__tests__deserialize_failing.snap

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ expression: snapshot
55
- input:
66
ver: 1.2.3
77
result:
8-
error: "ERROR: unknown field `ver`, expected one of `version`, `extras`, `path`, `editable`, `git`, `branch`, `tag`, `rev`, `url`, `subdirectory`"
8+
error: "ERROR: unknown field `ver`, expected one of `version`, `extras`, `path`, `editable`, `git`, `branch`, `tag`, `rev`, `url`, `subdirectory`, `index`"
99
- input:
1010
path: foobar
1111
version: "==1.2.3"

crates/pixi_manifest/src/pypi/snapshots/pixi_manifest__pypi__pypi_requirement__tests__deserialize_succeeding.snap

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ expression: snapshot
99
result:
1010
version: "==1.2.3"
1111
extras: []
12+
index: ~
1213
- input: "*"
1314
result: "*"
1415
- input:

crates/pixi_uv_conversions/src/requirements.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,11 @@ pub fn as_uv_req(
8282
) -> Result<uv_pypi_types::Requirement, AsPep508Error> {
8383
let name = PackageName::new(name.to_owned())?;
8484
let source = match req {
85-
PyPiRequirement::Version { version, .. } => {
85+
PyPiRequirement::Version { version, index, .. } => {
8686
// TODO: implement index later
8787
RequirementSource::Registry {
8888
specifier: to_version_specificers(version)?,
89-
index: None,
89+
index: index.clone(),
9090
}
9191
}
9292
PyPiRequirement::Git {

docs/reference/project_configuration.md

+13
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,19 @@ ruff = "~=1.0.0"
469469
pytest = {version = "*", extras = ["dev"]}
470470
```
471471

472+
##### `index`
473+
474+
The index parameter allows you to specify the URL of a custom package index for the installation of a specific package.
475+
This feature is useful when you want to ensure that a package is retrieved from a particular source, rather than from the default index.
476+
477+
For example, to use some other than the official Python Package Index (PyPI) at https://pypi.org/simple, you can use the `index` parameter:
478+
479+
```toml
480+
torch = { version = "*", index = "https://download.pytorch.org/whl/cu118" }
481+
```
482+
483+
This is useful for PyTorch specifically, as the registries are pinned to different CUDA versions.
484+
472485
##### `git`
473486

474487
A git repository to install from.

schema/examples/valid/full.toml

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ requests = { version = ">= 2.8.1, ==2.8.*", extras = [
4949
"security",
5050
"tests",
5151
] } # Using the map allows the user to add `extras`
52+
test-pinning-index = { version = "*", index = "https://example.com/test" }
5253
testpypi = "*"
5354
testpypi1 = "*"
5455

schema/model.py

+4
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,10 @@ class PyPIVersion(_PyPIRequirement):
248248
None,
249249
description="The version of the package in [PEP 440](https://www.python.org/dev/peps/pep-0440/) format",
250250
)
251+
index: NonEmptyStr | None = Field(
252+
None,
253+
description="The index to fetch the package from",
254+
)
251255

252256

253257
PyPIRequirement = (

schema/schema.json

+6
Original file line numberDiff line numberDiff line change
@@ -1081,6 +1081,12 @@
10811081
"minLength": 1
10821082
}
10831083
},
1084+
"index": {
1085+
"title": "Index",
1086+
"description": "The index to fetch the package from",
1087+
"type": "string",
1088+
"minLength": 1
1089+
},
10841090
"version": {
10851091
"title": "Version",
10861092
"description": "The version of the package in [PEP 440](https://www.python.org/dev/peps/pep-0440/) format",

scripts/activate.bat

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
@echo off
2-
set CARGO_TARGET_DIR=target-pixi
2+
set CARGO_TARGET_DIR=target/pixi

scripts/activate.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#!/bin/bash
22
set -Eeuo pipefail
3-
export CARGO_TARGET_DIR="target-pixi"
3+
export CARGO_TARGET_DIR="target/pixi"
44
export CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER="clang"
55
export CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS="-C link-arg=-fuse-ld=$CONDA_PREFIX/bin/mold"

src/cli/project/export/conda_environment.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,9 @@ fn format_pip_dependency(name: &PyPiPackageName, requirement: &PyPiRequirement)
106106

107107
url_string
108108
}
109-
PyPiRequirement::Version { version, extras } => {
109+
PyPiRequirement::Version {
110+
version, extras, ..
111+
} => {
110112
format!(
111113
"{name}{extras}{version}",
112114
name = name.as_normalized(),

src/global/trampoline.rs

+18-15
Original file line numberDiff line numberDiff line change
@@ -343,9 +343,25 @@ impl Trampoline {
343343
}
344344

345345
async fn write_trampoline(&self) -> miette::Result<()> {
346-
self.update_trampoline().await?;
347-
348346
let trampoline_path = self.trampoline_path();
347+
348+
// We need to check that there's indeed a trampoline at the path
349+
if trampoline_path.is_file().not()
350+
|| Trampoline::is_trampoline(&self.trampoline_path())
351+
.await?
352+
.not()
353+
{
354+
tokio_fs::create_dir_all(self.root_path.join(TRAMPOLINE_CONFIGURATION))
355+
.await
356+
.into_diagnostic()?;
357+
tokio_fs::write(
358+
self.trampoline_path(),
359+
Trampoline::decompressed_trampoline(),
360+
)
361+
.await
362+
.into_diagnostic()?;
363+
}
364+
349365
// If the path doesn't exist yet, create a hard link to the shared trampoline binary
350366
// If creating a hard link doesn't succeed, try copying
351367
// Hard-linking might for example fail because the file-system enforces a maximum number of hard-links per file
@@ -370,19 +386,6 @@ impl Trampoline {
370386
Ok(())
371387
}
372388

373-
async fn update_trampoline(&self) -> Result<(), miette::Error> {
374-
let trampoline_path = self.trampoline_path();
375-
if trampoline_path.exists().not() || Self::is_trampoline(&trampoline_path).await?.not() {
376-
tokio_fs::create_dir_all(self.root_path.join(TRAMPOLINE_CONFIGURATION))
377-
.await
378-
.into_diagnostic()?;
379-
tokio_fs::write(trampoline_path, Trampoline::decompressed_trampoline())
380-
.await
381-
.into_diagnostic()?;
382-
}
383-
Ok(())
384-
}
385-
386389
/// Writes the manifest file of the trampoline
387390
async fn write_manifest(&self) -> miette::Result<()> {
388391
let manifest_string =

tests/integration_python/conftest.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def pytest_addoption(parser: pytest.Parser) -> None:
1515
@pytest.fixture
1616
def pixi(request: pytest.FixtureRequest) -> Path:
1717
pixi_build = request.config.getoption("--pixi-build")
18-
return Path(__file__).parent.joinpath(f"../../target-pixi/{pixi_build}/pixi")
18+
return Path(__file__).parent.joinpath(f"../../target/pixi/{pixi_build}/pixi")
1919

2020

2121
@pytest.fixture

0 commit comments

Comments
 (0)