Lightweight project-local wrapper for uv.
The ./uv script ensures a matching uv binary is available in ./.uv/bin, downloading it from GitHub releases when
needed, then runs it with --project set to the current repository.
I wrote this wrapper out of necessity for my personal and my work-related projects. I am using it in multiple projects together with my colleagues, and it works fine for us. But most likely I will not put too much effort into it anymore, unless I personally need more features than it has now.
So, if you need more supported platforms or features, please open an issue or even better a PR, and we can take a look together.
This project is licensed under the BSD Zero Clause License (0BSD).
You are free to copy, modify, and use any code from this repository for any purpose, including commercial use. You do not need to include this license text or provide attribution when reusing code snippets. However, I would strongly recommend at least keeping the version information in the wrapper script so that users know exactly which version of the wrapper they are using.
In short: Copy, paste, and use it however you like.
- Keep
uvmanaged per project. - Avoid requiring global
uvinstallation. - Pin the
uvversion in source control via.uv-version. - Handle concurrent first-run installs safely with a lock.
The wrapper is written as a Bash script, so you need Bash to execute it.
On Linux this should not be a problem, since Bash is pre-installed on many distributions. If you face problems with different Shells like Zsh or Dash, please open an issue, so we can try to rewrite the wrapper into a more POSIX-Shell compliant format.
On Windows, you can use the Git-Bash to execute the script, which is included in the "Git-for-Windows" installer. Or you
can use a dedicated environment like WSL or MSYS2 to run Bash. Be extra careful when you download the wrapper or copy
and paste the content into a new file. Windows editors or shells like the PowerShell on Windows tend to create files
with UTF-16 encoding and use CRLF line endings. This can render the file unusable by Bash, with errors like:
bash: ./uv: cannot execute binary file: Exec format error. Please make sure the wrapper script uses UTF-8 encoding
and LF line endings.
To make life easier for Windows users, a DosBatch file is also provided, so to speak, as a wrapper for the actual wrapper Bash script.
This uv.bat does not actually do anything itself:
- It searches all
gitexecutables available in the PATH. - It looks for a
bash.exein the same directory asgit.exe(or more correctly, it goes up a directory and from there looks forbin\bash.exe; this is neede because Git can be installed in different locations, butbash.exeusually resides in thebinsubfolder). - If
bash.exeis found, call it with.\uvand the rest of the command line arguments.
So, the real logic is still in the Bash script; the DosBatch script merely looks for a way to execute the Bash script. That means, if you want to use the DosBatch file, you still need to have the Bash script alongside it, and you need Git-Bash installed.
-
Copy or download the wrapper to your project:
cd your/project curl -sSL https://raw.githubusercontent.com/FloGa/uv-wrapper/refs/heads/main/uv >uv # If you also want to call uv via a bat file, download the bat-to-bash wrapper: curl -sSL https://raw.githubusercontent.com/FloGa/uv-wrapper/refs/heads/main/uv.bat >uv.bat
-
Make sure the wrapper is executable:
chmod +x ./uv
-
Run
uvcommands through the wrapper:./uv --version ./uv sync ./uv run python -V
If you prefer a DosBatch file on Windows, you can instead use the following in a CMD or PowerShell (see "About the Programming Language"):
.\uv.bat --version .\uv.bat sync .\uv.bat run python -V
On first run, the wrapper downloads and verifies the configured
uvrelease. If no.uv-versionfile is present, the wrapper will download the latest release from GitHub and create a.uv-versionfile with the pinned version. -
Check
.uv-versionanduvinto source control, for example, Git:git add .uv-version uv git commit -m "Add uv wrapper"
- OS:
- Linux
- Windows (requires a Bash executable like the
Git-Bash, see "About the Programming Language"
- Architecture:
x86_64aarch64
- Tools available in
PATH:curlsha256sum- plus archive tools:
- Linux:
tar,gunzip - Windows:
unzip
- Linux:
Version pinning is done via .uv-version. To use a different version, simply change the value in .uv-version. On the
next call to ./uv, the wrapper will download the specified version.
If .uv-version is empty or missing, the wrapper resolves the latest release from GitHub and writes it back to
.uv-version. So, to quickly switch to the latest release, simply remove .uv-version and let it re-create by the
wrapper.
-
Detect OS and architecture.
-
Validate required tools are installed.
-
Acquire an install lock at
./.uv/.install.lock. -
Download release archive and
.sha256from GitHub. -
Verify checksum.
-
Unpack and place the binary in
./.uv/bin. -
Execute:
./.uv/bin/uv --project "<repo-dir>" <args>
./.uv/bin/- installeduvbinary (uvoruv.exe)./.uv/temp/- temporary extraction directory (removed after install)./.uv/.install.lock/- transient lock directory used during install
Unsupported architecture/Unsupported operating system- The current wrapper only supports the OS/arch combinations listed above. For other combinations, please open an issue or PR.
- Missing required command (
curl,sha256sum, etc.)- Install the missing programs and rerun
./uv.
- Install the missing programs and rerun
- Stale lock detected
- The script automatically removes stale lock files if the recorded PID is no longer running.
- Version mismatch or corrupted install
- Remove
./.uvand rerun./uv. - This might happen under certain circumstances, like when you are working with your repository in different "
operating systems" in parallel – for example, you use your repository in Windows and via WSL. Since WSL (Linux)
needs a different binary than Windows, the wrapper will be confused and might not work as expected. My
recommendation is, stick to one operating system and avoid switching back and forth. If you absolutely need to,
make sure to remove the
./.uvdirectory and maybe even the virtual Python environment in./.venvto ensure a clean state when./uvis run the next time.
- Remove
- When executing
./uv, I get an error message like:bash: ./uv: cannot execute binary file: Exec format error- This is most likely caused by a wrong file encoding and/or line endings. See the "About the Programming Language" section for more information.
- In short: Make sure the
uvwrapper script usesUTF-8encoding andLFline endings, so it can be executed in Bash.
-
Why not use
uvdirectly?Fair point. Installing uv is not too difficult and is well-documented. However, sometimes even this might stop people from using a Python project. "Yuck, another piece of software I need to install to make this run. No, thanks!"
This wrapper provides a true "out-of-the-box" experience, without the need to install anything beforehand (well, except for some basic tools like
curlandsha256sumthat are most probably already installed anyway). -
Do I need to check the
.uvdirectory into version control?No, absolutely not! The wrapper will automatically download and run the
uvbinary if it is not present. The wrapper automatically creates a.gitignorein the.uvdirectory to make Git ignore this whole directory. -
Do I need to check the
.uv-versionfile into version control?It is not strictly necessary, but I would highly recommend it to have the most reproducible experience. If the file is missing, the wrapper will download the latest release from GitHub and write the version number to the file.
-
So, if I should check in the
.uv-versionfile, then why do you have it in your.gitignorein this repository?Ah, good catch! This repository does not really use uv in the first place, it is just a means to distribute the wrapper. Therefore, I have put the
.uv-versionfile into.gitignore, so I can test the functionality and avoid accidentally committing it.