Skip to content

Commit aa8d19a

Browse files
committed
Add index URLs when provided via uv add --index / --default-index
1 parent 01177b4 commit aa8d19a

6 files changed

Lines changed: 790 additions & 3 deletions

File tree

crates/uv-cli/src/lib.rs

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

771775
/// 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
@@ -3,7 +3,8 @@ use std::path::Path;
33
use std::str::FromStr;
44
use std::{fmt, mem};
55
use thiserror::Error;
6-
use toml_edit::{Array, DocumentMut, Item, RawString, Table, TomlError, Value};
6+
use toml_edit::{Array, ArrayOfTables, DocumentMut, Item, RawString, Table, TomlError, Value};
7+
use uv_distribution_types::Index;
78
use uv_fs::PortablePath;
89
use uv_pep440::{Version, VersionSpecifier, VersionSpecifiers};
910
use uv_pep508::{ExtraName, MarkerTree, PackageName, Requirement, VersionOrUrl};
@@ -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
@@ -16,7 +16,7 @@ use uv_configuration::{
1616
};
1717
use uv_dispatch::BuildDispatch;
1818
use uv_distribution::DistributionDatabase;
19-
use uv_distribution_types::UnresolvedRequirement;
19+
use uv_distribution_types::{Index, UnresolvedRequirement};
2020
use uv_fs::Simplified;
2121
use uv_git::{GitReference, GIT_STORE};
2222
use uv_normalize::PackageName;
@@ -57,6 +57,7 @@ pub(crate) async fn add(
5757
editable: Option<bool>,
5858
dependency_type: DependencyType,
5959
raw_sources: bool,
60+
indexes: Vec<Index>,
6061
rev: Option<String>,
6162
tag: Option<String>,
6263
branch: Option<String>,
@@ -486,6 +487,13 @@ pub(crate) async fn add(
486487
});
487488
}
488489

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

491499
// 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)