Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
7bfab85
fork: preserve URLs in uv.lock across re-locks
harupy Apr 24, 2026
9a09fc9
fork-release: trigger per-PR, tag releases pr-<number>
harupy Apr 24, 2026
a129013
fork-release: use pr-<number> as release title
harupy Apr 24, 2026
e81972f
fork-release: force-move tag in place instead of delete+recreate
harupy Apr 24, 2026
d8fdd1f
fork-release: SHA-pin actions and add concurrency group
harupy Apr 24, 2026
8edf734
fork: skip uv.lock write when URL preservation makes it a no-op
harupy Apr 24, 2026
a2489ae
fork: rewrite PackageId in every Dependency too, not just Package.id
harupy Apr 24, 2026
219f296
fork: apply rustfmt to url_preservation.rs
harupy Apr 24, 2026
af32c90
fork: rebuild Lock.by_id after URL rewrite; clean up install tmp dir
harupy Apr 24, 2026
ae673e7
fork: skip remote-index URL check in satisfies() for URL preservation
harupy Apr 27, 2026
413bd1f
fork: normalize newly added packages to canonical registry URL
harupy Apr 27, 2026
33f8af8
fork: replace previous-lock URL inference with UV_PYPI_PROXIES env var
harupy Apr 27, 2026
e48fa2a
fork: simplify mod.rs — restore upstream code, inject canonical URLs
harupy Apr 27, 2026
34eccb6
fork: replace internal URL with example.com in docs
harupy Apr 27, 2026
2254345
fork: fix UrlString construction — use SmallString::from for &str
harupy Apr 27, 2026
4f78fca
fork-release: add UV_PYPI_PROXIES setup instructions to release notes
harupy Apr 27, 2026
fb0a97e
fork: rename UV_PYPI_PROXIES to UV_INDEX_PROXIES
harupy Apr 27, 2026
f1654e6
fork: also rewrite proxy URLs on Unchanged lock results
harupy Apr 27, 2026
5672067
fork: resolve canonical URLs back to proxy for uv sync
harupy Apr 27, 2026
25f1257
fork: add debug logging for proxy URL resolution
harupy Apr 27, 2026
8448d0b
fork: apply UV_INDEX_PROXIES to default index for build resolution
harupy Apr 27, 2026
bb83b9a
fork: reduce proxy URL log noise — use trace for per-package, debug f…
harupy Apr 27, 2026
ffe4a3f
fork: apply UV_INDEX_PROXIES to IndexUrls::default_index() too
harupy Apr 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 165 additions & 0 deletions .github/workflows/fork-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
# fork: publish per-PR uv binaries (linux x86_64, macOS aarch64).
# Each PR has one release tagged `pr-<number>`; assets overwrite on each push.
name: Fork release

on:
pull_request:
types: [opened, synchronize, reopened]

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: true

permissions:
contents: write

env:
CARGO_INCREMENTAL: 0
CARGO_NET_RETRY: 10
CARGO_TERM_COLOR: always
RUSTUP_MAX_RETRIES: 10

jobs:
build-linux-x86_64:
name: Build linux x86_64
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
- name: Build uv + uvx
run: cargo build --profile release --bin uv --bin uvx
- name: Package tarball
run: |
mkdir -p dist
tar -czvf dist/uv-x86_64-unknown-linux-gnu.tar.gz \
-C target/release uv uvx
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: uv-x86_64-unknown-linux-gnu
path: dist/uv-x86_64-unknown-linux-gnu.tar.gz
retention-days: 1

build-macos-aarch64:
name: Build macOS aarch64
runs-on: macos-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
- name: Build uv + uvx
run: cargo build --profile release --bin uv --bin uvx
- name: Package tarball
run: |
mkdir -p dist
tar -czvf dist/uv-aarch64-apple-darwin.tar.gz \
-C target/release uv uvx
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: uv-aarch64-apple-darwin
path: dist/uv-aarch64-apple-darwin.tar.gz
retention-days: 1

publish-release:
name: Publish PR release
needs: [build-linux-x86_64, build-macos-aarch64]
runs-on: ubuntu-latest
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
PR_TITLE: ${{ github.event.pull_request.title }}
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: dist
merge-multiple: true
- name: Write release notes
id: notes
run: |
TAG="pr-${PR_NUMBER}"
SHA_SHORT="$(echo "$PR_HEAD_SHA" | cut -c1-7)"
DATE="$(date -u +'%Y-%m-%dT%H:%M:%SZ')"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "sha_short=$SHA_SHORT" >> "$GITHUB_OUTPUT"
cat > RELEASE_NOTES.md <<EOF
# uv (mlflow fork) — PR #${PR_NUMBER}

Binaries built from [PR #${PR_NUMBER}](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/pull/${PR_NUMBER}).
Assets are overwritten on every push to the PR.

- Commit: \`${SHA_SHORT}\` (\`${PR_HEAD_SHA}\`)
- Built: ${DATE}

## Fork behavior

This fork rewrites proxy registry URLs in \`uv.lock\` to their canonical counterparts
via the \`UV_INDEX_PROXIES\` environment variable. This prevents noisy diffs and keeps
the lockfile portable across environments that use different PyPI mirrors.
See [astral-sh/uv#6349](https://github.com/astral-sh/uv/issues/6349).

## Setup

Set \`UV_INDEX_PROXIES\` with \`canonical:proxy\` mappings (comma-separated for multiple):

\`\`\`bash
export UV_INDEX_PROXIES=https://pypi.org/simple:https://your-pypi-proxy.example.com/simple
\`\`\`

Then use \`uv lock\`, \`uv add\`, etc. as normal. The lockfile will always contain the
canonical URL (\`https://pypi.org/simple\`) regardless of which mirror resolved the package.

## Install

### Linux (x86_64)

\`\`\`bash
tmpdir="\$(mktemp -d)" && trap "rm -rf \"\$tmpdir\"" EXIT
curl -L -o "\$tmpdir/uv.tar.gz" \\
${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/releases/download/${TAG}/uv-x86_64-unknown-linux-gnu.tar.gz
tar -xzf "\$tmpdir/uv.tar.gz" -C "\$tmpdir"
install -m 0755 "\$tmpdir/uv" "\$tmpdir/uvx" "\$HOME/.local/bin/"
\`\`\`

### macOS (Apple Silicon)

\`\`\`bash
tmpdir="\$(mktemp -d)" && trap "rm -rf \"\$tmpdir\"" EXIT
curl -L -o "\$tmpdir/uv.tar.gz" \\
${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/releases/download/${TAG}/uv-aarch64-apple-darwin.tar.gz
tar -xzf "\$tmpdir/uv.tar.gz" -C "\$tmpdir"
install -m 0755 "\$tmpdir/uv" "\$tmpdir/uvx" "\$HOME/.local/bin/"
# Clear the quarantine flag added by Safari/curl on downloaded binaries:
xattr -d com.apple.quarantine "\$HOME/.local/bin/uv" "\$HOME/.local/bin/uvx" 2>/dev/null || true
\`\`\`

Make sure \`\$HOME/.local/bin\` is on your \`PATH\`.
EOF
- name: Upsert PR release
run: |
TAG="${{ steps.notes.outputs.tag }}"
if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
# Force-move the existing tag to the new PR head SHA, then refresh
# notes and assets in place.
gh api --method PATCH "repos/$GITHUB_REPOSITORY/git/refs/tags/$TAG" \
-f sha="$PR_HEAD_SHA" -F force=true >/dev/null
gh release edit "$TAG" --repo "$GITHUB_REPOSITORY" \
--title "$TAG" \
--notes-file RELEASE_NOTES.md
gh release upload "$TAG" --repo "$GITHUB_REPOSITORY" --clobber \
dist/uv-x86_64-unknown-linux-gnu.tar.gz \
dist/uv-aarch64-apple-darwin.tar.gz
else
gh release create "$TAG" \
--repo "$GITHUB_REPOSITORY" \
--target "$PR_HEAD_SHA" \
--title "$TAG" \
--notes-file RELEASE_NOTES.md \
dist/uv-x86_64-unknown-linux-gnu.tar.gz \
dist/uv-aarch64-apple-darwin.tar.gz
fi
33 changes: 33 additions & 0 deletions crates/uv-distribution-types/src/index_url.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,32 @@ static DEFAULT_INDEX: LazyLock<Index> = LazyLock::new(|| {
))))
});

// fork: default index resolved through UV_INDEX_PROXIES so that
// build-system.requires resolution uses the proxy; see astral-sh/uv#6349.
static DEFAULT_INDEX_PROXIED: LazyLock<Option<Index>> = LazyLock::new(|| {
let value = std::env::var("UV_INDEX_PROXIES").ok()?;
let pypi = PYPI_URL.to_string();
for entry in value.split(',') {
let entry = entry.trim();
let delimiter_pos = entry
.find(":https://")
.or_else(|| entry.find(":http://"))
.filter(|&pos| pos > 0)?;
let canonical = entry[..delimiter_pos].trim();
let proxy = entry[delimiter_pos + 1..].trim();
if canonical == pypi {
let proxy_url = DisplaySafeUrl::parse(proxy).ok()?;
tracing::debug!(
"Resolving default index `{canonical}` to proxy `{proxy}` for build resolution"
);
return Some(Index::from_index_url(IndexUrl::Url(Arc::new(
VerbatimUrl::from_url(proxy_url),
))));
}
}
None
});

/// The URL of an index to use for fetching packages (e.g., PyPI).
#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
pub enum IndexUrl {
Expand Down Expand Up @@ -313,6 +339,10 @@ impl<'a> IndexLocations {
.iter()
.filter(move |index| index.name.as_ref().is_none_or(|name| seen.insert(name)))
.find(|index| index.default)
// fork: prefer proxy-resolved default index so that
// build-system.requires resolution uses the proxy;
// see astral-sh/uv#6349.
.or_else(|| DEFAULT_INDEX_PROXIED.as_ref())
.or_else(|| Some(&DEFAULT_INDEX))
}
}
Expand Down Expand Up @@ -520,6 +550,9 @@ impl<'a> IndexUrls {
.iter()
.filter(move |index| index.name.as_ref().is_none_or(|name| seen.insert(name)))
.find(|index| index.default)
// fork: prefer proxy-resolved default index;
// see astral-sh/uv#6349.
.or_else(|| DEFAULT_INDEX_PROXIED.as_ref())
.or_else(|| Some(&DEFAULT_INDEX))
}
}
Expand Down
23 changes: 20 additions & 3 deletions crates/uv-resolver/src/lock/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ mod export;
mod installable;
mod map;
mod tree;
// fork: rewrite proxy registry URLs via UV_INDEX_PROXIES; see astral-sh/uv#6349.
mod url_preservation;

/// The current version of the lockfile format.
pub const VERSION: u32 = 1;
Expand Down Expand Up @@ -1837,6 +1839,12 @@ impl Lock {
.collect::<BTreeSet<_>>()
});

// fork: add canonical URLs from UV_INDEX_PROXIES so that the
// satisfies check recognizes them as valid remote indexes.
if let Some(remotes) = remotes.as_mut() {
url_preservation::canonical_urls(remotes);
}

let mut locals = indexes.map(|locations| {
locations
.allowed_indexes()
Expand Down Expand Up @@ -3270,8 +3278,11 @@ impl Package {
zstd: None,
});

// fork: resolve canonical URL back to proxy for fetching;
// see astral-sh/uv#6349.
let resolved_url = url_preservation::proxy_url(url);
let index = IndexUrl::from(VerbatimUrl::from_url(
url.to_url().map_err(LockErrorKind::InvalidUrl)?,
resolved_url.to_url().map_err(LockErrorKind::InvalidUrl)?,
));

let reg_dist = RegistrySourceDist {
Expand Down Expand Up @@ -3566,8 +3577,11 @@ impl Package {
pub fn index(&self, root: &Path) -> Result<Option<IndexUrl>, LockError> {
match &self.id.source {
Source::Registry(RegistrySource::Url(url)) => {
// fork: resolve canonical URL back to proxy for fetching;
// see astral-sh/uv#6349.
let resolved_url = url_preservation::proxy_url(url);
let index = IndexUrl::from(VerbatimUrl::from_url(
url.to_url().map_err(LockErrorKind::InvalidUrl)?,
resolved_url.to_url().map_err(LockErrorKind::InvalidUrl)?,
));
Ok(Some(index))
}
Expand Down Expand Up @@ -5054,8 +5068,11 @@ impl Wheel {
})
.map(Box::new),
});
// fork: resolve canonical URL back to proxy for fetching;
// see astral-sh/uv#6349.
let resolved_url = url_preservation::proxy_url(url);
let index = IndexUrl::from(VerbatimUrl::from_url(
url.to_url().map_err(LockErrorKind::InvalidUrl)?,
resolved_url.to_url().map_err(LockErrorKind::InvalidUrl)?,
));
Ok(RegistryBuiltWheel {
filename,
Expand Down
Loading
Loading