Skip to content

Commit 1eca84b

Browse files
committed
Add index URLs when provided via uv add --index / --default-index
1 parent 7f7dcad commit 1eca84b

File tree

6 files changed

+790
-3
lines changed

6 files changed

+790
-3
lines changed

crates/uv-cli/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -765,6 +765,10 @@ impl<T> Maybe<T> {
765765
Maybe::None => None,
766766
}
767767
}
768+
769+
pub fn is_some(&self) -> bool {
770+
matches!(self, Maybe::Some(_))
771+
}
768772
}
769773

770774
/// Parse a string into an [`IndexUrl`], mapping the empty string to `None`.

crates/uv-workspace/src/pyproject_mut.rs

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
use distribution_types::Index;
12
use itertools::Itertools;
23
use pep440_rs::{Version, VersionSpecifier, VersionSpecifiers};
34
use pep508_rs::{ExtraName, MarkerTree, PackageName, Requirement, VersionOrUrl};
45
use std::path::Path;
56
use std::str::FromStr;
67
use std::{fmt, mem};
78
use thiserror::Error;
8-
use toml_edit::{Array, DocumentMut, Item, RawString, Table, TomlError, Value};
9+
use toml_edit::{Array, ArrayOfTables, DocumentMut, Item, RawString, Table, TomlError, Value};
910
use uv_fs::PortablePath;
1011

1112
use crate::pyproject::{DependencyType, Source};
@@ -190,6 +191,77 @@ impl PyProjectTomlMut {
190191
Ok(edit)
191192
}
192193

194+
/// Add an [`Index`] to `tool.uv.index`.
195+
pub fn add_index(&mut self, index: &Index) -> Result<(), Error> {
196+
let existing = self
197+
.doc
198+
.entry("tool")
199+
.or_insert(implicit())
200+
.as_table_mut()
201+
.ok_or(Error::MalformedSources)?
202+
.entry("uv")
203+
.or_insert(implicit())
204+
.as_table_mut()
205+
.ok_or(Error::MalformedSources)?
206+
.entry("index")
207+
.or_insert(Item::ArrayOfTables(ArrayOfTables::new()))
208+
.as_array_of_tables()
209+
.ok_or(Error::MalformedSources)?;
210+
211+
let mut table = Table::new();
212+
if let Some(name) = index.name.as_ref() {
213+
table.insert("name", toml_edit::value(name.to_string()));
214+
}
215+
table.insert("url", toml_edit::value(index.url.to_string()));
216+
if index.default {
217+
table.insert("default", toml_edit::value(true));
218+
}
219+
220+
// Push the item to the table.
221+
let mut updated = ArrayOfTables::new();
222+
updated.push(table);
223+
for table in existing {
224+
// If there's another index with the same name, replace it.
225+
if table
226+
.get("name")
227+
.is_some_and(|name| name.as_str() == index.name.as_deref())
228+
{
229+
continue;
230+
}
231+
232+
// If there's another default index, remove it.
233+
if index.default
234+
&& table
235+
.get("default")
236+
.is_some_and(|default| default.as_bool() == Some(true))
237+
{
238+
continue;
239+
}
240+
241+
// If there's another index with the same URL, replace it.
242+
if table
243+
.get("url")
244+
.is_some_and(|url| url.as_str() == Some(index.url.url().as_str()))
245+
{
246+
continue;
247+
}
248+
249+
updated.push(table.clone());
250+
}
251+
self.doc
252+
.entry("tool")
253+
.or_insert(implicit())
254+
.as_table_mut()
255+
.ok_or(Error::MalformedSources)?
256+
.entry("uv")
257+
.or_insert(implicit())
258+
.as_table_mut()
259+
.ok_or(Error::MalformedSources)?
260+
.insert("index", Item::ArrayOfTables(updated));
261+
262+
Ok(())
263+
}
264+
193265
/// Adds a dependency to `project.optional-dependencies`.
194266
///
195267
/// Returns `true` if the dependency was added, `false` if it was updated.

crates/uv/src/commands/project/add.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::path::{Path, PathBuf};
88
use tracing::debug;
99

1010
use cache_key::RepositoryUrl;
11-
use distribution_types::UnresolvedRequirement;
11+
use distribution_types::{Index, UnresolvedRequirement};
1212
use pep508_rs::{ExtraName, Requirement, UnnamedRequirement, VersionOrUrl};
1313
use pypi_types::{redact_git_credentials, ParsedUrl, RequirementSource, VerbatimParsedUrl};
1414
use uv_auth::{store_credentials, store_credentials_from_url, Credentials};
@@ -58,6 +58,7 @@ pub(crate) async fn add(
5858
editable: Option<bool>,
5959
dependency_type: DependencyType,
6060
raw_sources: bool,
61+
indexes: Vec<Index>,
6162
rev: Option<String>,
6263
tag: Option<String>,
6364
branch: Option<String>,
@@ -487,6 +488,13 @@ pub(crate) async fn add(
487488
});
488489
}
489490

491+
// Add any indexes that were provided on the command-line.
492+
if !raw_sources {
493+
for index in indexes {
494+
toml.add_index(&index)?;
495+
}
496+
}
497+
490498
let content = toml.to_string();
491499

492500
// Save the modified `pyproject.toml` or script.

crates/uv/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1356,6 +1356,7 @@ async fn run_project(
13561356
args.editable,
13571357
args.dependency_type,
13581358
args.raw_sources,
1359+
args.indexes,
13591360
args.rev,
13601361
args.tag,
13611362
args.branch,

crates/uv/src/settings.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -804,6 +804,7 @@ pub(crate) struct AddSettings {
804804
pub(crate) script: Option<PathBuf>,
805805
pub(crate) python: Option<String>,
806806
pub(crate) refresh: Refresh,
807+
pub(crate) indexes: Vec<Index>,
807808
pub(crate) settings: ResolverInstallerSettings,
808809
}
809810

@@ -842,6 +843,51 @@ impl AddSettings {
842843
DependencyType::Production
843844
};
844845

846+
// Track the `--index` and `--default-index` arguments from the command-line.
847+
let indexes = installer
848+
.index_args
849+
.default_index
850+
.clone()
851+
.and_then(Maybe::into_option)
852+
.into_iter()
853+
.chain(
854+
installer
855+
.index_args
856+
.index
857+
.clone()
858+
.into_iter()
859+
.flatten()
860+
.filter_map(Maybe::into_option),
861+
)
862+
.collect::<Vec<_>>();
863+
864+
// If the user passed an `--index-url` or `--extra-index-url`, warn.
865+
if installer
866+
.index_args
867+
.index_url
868+
.as_ref()
869+
.is_some_and(Maybe::is_some)
870+
{
871+
if script.is_some() {
872+
warn_user_once!("Indexes specified via `--index-url` will not be persisted to the script; use `--default-index` instead.");
873+
} else {
874+
warn_user_once!("Indexes specified via `--index-url` will not be persisted to the `pyproject.toml` file; use `--default-index` instead.");
875+
}
876+
}
877+
878+
if installer
879+
.index_args
880+
.extra_index_url
881+
.as_ref()
882+
.is_some_and(|extra_index_url| extra_index_url.iter().any(Maybe::is_some))
883+
{
884+
if script.is_some() {
885+
warn_user_once!("Indexes specified via `--extra-index-url` will not be persisted to the script; use `--index` instead.");
886+
} else {
887+
warn_user_once!("Indexes specified via `--extra-index-url` will not be persisted to the `pyproject.toml` file; use `--index` instead.");
888+
}
889+
}
890+
845891
Self {
846892
locked,
847893
frozen,
@@ -856,6 +902,7 @@ impl AddSettings {
856902
package,
857903
script,
858904
python,
905+
indexes,
859906
editable: flag(editable, no_editable),
860907
extras: extra.unwrap_or_default(),
861908
refresh: Refresh::from(refresh),

0 commit comments

Comments
 (0)