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
d0ee572
fork: preserve URLs in uv.lock across re-locks
harupy Apr 24, 2026
f22f8f1
fork-release: trigger per-PR, tag releases pr-<number>
harupy Apr 24, 2026
96df320
fork-release: use pr-<number> as release title
harupy Apr 24, 2026
42680a1
fork-release: force-move tag in place instead of delete+recreate
harupy Apr 24, 2026
32d8e2d
fork-release: SHA-pin actions and add concurrency group
harupy Apr 24, 2026
d13bcac
fork: skip uv.lock write when URL preservation makes it a no-op
harupy Apr 24, 2026
e242c34
fork: rewrite PackageId in every Dependency too, not just Package.id
harupy Apr 24, 2026
1a5290e
fork: apply rustfmt to url_preservation.rs
harupy Apr 24, 2026
6639b6f
fork: rebuild Lock.by_id after URL rewrite; clean up install tmp dir
harupy Apr 24, 2026
b74f184
fork: skip remote-index URL check in satisfies() for URL preservation
harupy Apr 27, 2026
cd50045
fork: normalize newly added packages to canonical registry URL
harupy Apr 27, 2026
6304dc2
fork: replace previous-lock URL inference with UV_PYPI_PROXIES env var
harupy Apr 27, 2026
e601821
fork: simplify mod.rs — restore upstream code, inject canonical URLs
harupy Apr 27, 2026
0b3e70b
fork: replace internal URL with example.com in docs
harupy Apr 27, 2026
0755589
fork: fix UrlString construction — use SmallString::from for &str
harupy Apr 27, 2026
16af18d
fork-release: add UV_PYPI_PROXIES setup instructions to release notes
harupy Apr 27, 2026
c24b038
fork: rename UV_PYPI_PROXIES to UV_INDEX_PROXIES
harupy Apr 27, 2026
895fc45
fork: also rewrite proxy URLs on Unchanged lock results
harupy Apr 27, 2026
b062568
fork: resolve canonical URLs back to proxy for uv sync
harupy Apr 27, 2026
49be384
fork: add debug logging for proxy URL resolution
harupy Apr 27, 2026
26ce1c5
fork: apply UV_INDEX_PROXIES to default index for build resolution
harupy Apr 27, 2026
b9eb485
fork: reduce proxy URL log noise — use trace for per-package, debug f…
harupy Apr 27, 2026
475d6e5
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 @@ -1806,6 +1808,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 @@ -3122,8 +3130,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 @@ -3418,8 +3429,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 @@ -4906,8 +4920,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