Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add bootstrapping and isolation of development Python versions #1105

Merged
merged 11 commits into from
Jan 26, 2024

Conversation

zanieb
Copy link
Member

@zanieb zanieb commented Jan 25, 2024

Replaces #1068 and #1070 which were more complicated than I wanted.

  • Introduces a .python-versions file which defines the Python versions needed for development
  • Adds a Bash script at scripts/bootstrap/install which installs the required Python versions from python-build-standalone to ./bin
  • Checks in a versions.json file with metadata about available versions on each platform and a fetch-version Python script derived from rye for updating the versions
  • Updates CI to use these Python builds instead of the setup-python action
  • Updates to the latest packse scenarios which require Python 3.8+ instead of 3.7+ since we cannot use 3.7 anymore and includes new test coverage of patch Python version requests
  • Adds a PUFFIN_PYTHON_PATH variable to prevent lookup of system Python versions for isolation during development

Tested on Linux (via CI) and macOS (locally) — presumably it will be a bit more complicated to do proper Windows support.

@zanieb zanieb added the testing Internal testing of behavior label Jan 25, 2024
Comment on lines +220 to +235
pub fn find_executable<R: AsRef<OsStr> + Into<OsString> + Copy>(
requested: R,
) -> Result<PathBuf, Error> {
if let Some(isolated) = std::env::var_os("PUFFIN_PYTHON_PATH") {
if let Ok(cwd) = std::env::current_dir() {
which::which_in(requested, Some(isolated), cwd)
.map_err(|err| Error::Which(requested.into(), err))
} else {
which::which_in_global(requested, Some(isolated))
.map_err(|err| Error::Which(requested.into(), err))
.and_then(|mut paths| paths.next().ok_or(Error::PythonNotFound))
}
} else {
which::which(requested).map_err(|err| Error::Which(requested.into(), err))
}
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to improve the error handling here in a follow-up

scripts/bootstrap/install Outdated Show resolved Hide resolved
scripts/bootstrap/install Outdated Show resolved Hide resolved
@zanieb zanieb changed the title Add simple bootstrapping of Python versions Add bootstrapping and isolation of Python versions Jan 25, 2024
@zanieb zanieb changed the title Add bootstrapping and isolation of Python versions Add bootstrapping and isolation of development Python versions Jan 25, 2024
pub fn find_executable<R: AsRef<OsStr> + Into<OsString> + Copy>(
requested: R,
) -> Result<PathBuf, Error> {
if let Some(isolated) = std::env::var_os("PUFFIN_PYTHON_PATH") {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I worry about this name being too close to PYTHONPATH which has a very different meaning.

Considering PUFFIN_PYTHON_EXECUTABLE_PATH but geesh.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm okay with PUFFIN_PYTHON_EXECUTABLE_PATH.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll address this once I merge this full stack to avoid annoying rebases

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"path" is so overloaded anyway already

.envrc Show resolved Hide resolved
Copy link
Member

@charliermarsh charliermarsh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very impressive.

crates/puffin-interpreter/src/python_query.rs Outdated Show resolved Hide resolved
crates/puffin-interpreter/src/python_query.rs Outdated Show resolved Hide resolved
scripts/bootstrap/install Outdated Show resolved Hide resolved
scripts/bootstrap/install Outdated Show resolved Hide resolved
scripts/bootstrap/install Outdated Show resolved Hide resolved
scripts/bootstrap/fetch-versions Outdated Show resolved Hide resolved
"pgo+lto",
"lto",
"pgo",
]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One question though - does this download one interpreter per version? Or multiple? How does it choose which one?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This just generates the version.json file which has links to downloads. I believe the flavor preferences section here is used to select a single interpreter flavor per combination of OS, architecture, and Python version.

scripts/bootstrap/install Outdated Show resolved Hide resolved
rm "$this_dir/$filename"
done

echo "Done!"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know that I feel qualified to review this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As long as you test it I'm not too worried about the implementation.

CONTRIBUTING.md Outdated Show resolved Hide resolved
Copy link
Member

@konstin konstin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The install script errors on windows 11 git bash:

$ scripts/bootstrap/install.sh
which: no grealpath in (/mingw64/bin:/usr/bin:/c/Users/Konstantin/bin:/c/Python37/Scripts:/c/Python37:/c/Program Files (x86)/Intel/iCLS Client:/c/Program Files/Intel/iCLS Client:/c/Windows/system32:/c/Windows:/c/Windows/System32/Wbem:/c/Windows/System32/WindowsPowerShell/v1.0:/c/Program Files (x86)/Intel/Intel(R) Management Engine Components/DAL:/c/Program Files/Intel/Intel(R) Management Engine Components/DAL:/c/Program Files (x86)/Intel/Intel(R) Management Engine Components/IPT:/c/Program Files/Intel/Intel(R) Management Engine Components/IPT:/c/Program Files/Intel/WiFi/bin:/c/Program Files/Common Files/Intel/WirelessCommon:/c/Windows/System32/OpenSSH:/cmd:/c/Program Files/nodejs:/c/ProgramData/chocolatey/bin:/c/Program Files/PowerShell/7:/c/Users/Konstantin/AppData/Local/Programs/Python/Python312/Scripts:/c/Users/Konstantin/AppData/Local/Programs/Python/Python312:/c/Users/Konstantin/AppData/Local/Programs/Python/Python38/Scripts:/c/Users/Konstantin/AppData/Local/Programs/Python/Python38:/c/Users/Konstantin/.cargo/bin:/c/Users/Konstantin/AppData/Local/Microsoft/WindowsApps:/c/Users/Konstantin/AppData/Local/JetBrains/Toolbox/scripts:/c/users/konstantin/.local/bin:/c/Users/Konstantin/AppData/Roaming/npm:/c/Users/Konstantin/AppData/Local/Microsoft/WindowsApps:/c/Users/Konstantin/AppData/Local/Microsoft/WinGet/Packages/BurntSushi.ripgrep.MSVC_Microsoft.Winget.Source_8wekyb3d8bbwe/ripgrep-14.1.0-x86_64-pc-windows-msvc:/c/Users/Konstantin/AppData/Local/Microsoft/WinGet/Packages/jqlang.jq_Microsoft.Winget.Source_8wekyb3d8bbwe)
Installing cpython-3.8.12-mingw64_nt-10.0-22621-x86_64
No matching download for cpython-3.8.12-mingw64_nt-10.0-22621-x86_64

Comment on lines +46 to +57
Then add the Python binaries to your path:

```
export PATH=$PWD/bin:$PATH
```

We also strongly recommend setting the `PUFFIN_PYTHON_PATH` variable during development; this will prevent your
system Python versions from being found during tests:

```
export PUFFIN_PYTHON_PATH=$PWD/bin
```
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be something like

PATH=$PWD/bin:$PATH PUFFIN_PYTHON_PATH=$PWD/bin cargo nextest run ...

Seems cumbersome to always having to copy those two commands before doing anything when not using direnv

pub fn find_executable<R: AsRef<OsStr> + Into<OsString> + Copy>(
requested: R,
) -> Result<PathBuf, Error> {
if let Some(isolated) = std::env::var_os("PUFFIN_PYTHON_PATH") {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"path" is so overloaded anyway already

crates/puffin-interpreter/src/lib.rs Outdated Show resolved Hide resolved
.map(str::parse::<u8>)
.collect::<Result<Vec<_>, _>>();
if let Ok(versions) = versions {
// `-p 3.10` or `-p 3.10.1`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function docstring needs to be updated

crates/puffin-interpreter/src/python_query.rs Outdated Show resolved Hide resolved
@@ -0,0 +1,101 @@
#!/usr/bin/env bash
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This script introduces a jq dependency which needs to be documented. For windows:

winget install jqlang.jq

or https://jqlang.github.io/jq/download/.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additionally, I'm a fan of explicitly checking for dependencies used in a shell script if any outside of core tools are needed. For example, see https://github.com/BurntSushi/dotfiles/blob/2431b0d87614eabc0f022fd3617dcb0e5a5b4de4/bin/backup#L315-L322 and https://github.com/BurntSushi/dotfiles/blob/2431b0d87614eabc0f022fd3617dcb0e5a5b4de4/bin/backup#L341.

It's a big quality of life improvement because it avoids the scenario of getting half-way through execution and then getting a "jq not found" error.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also write this in Python to avoid adding new dependencies. And it might work on Windows. (Although I see some symlinking below...)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah thanks missed that dependency in my list and I'm happy to add explicit checks.

I wrote the whole thing in Python first at #1068 — but it was annoying that it had more Python requirements and I really didn't like bootstrapping Python development with Python.

Copy link
Member

@BurntSushi BurntSushi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Does this mean I don't need pyenv any more?

@@ -0,0 +1,3 @@
PATH=$PWD/bin:$PATH
export PUFFIN_PYTHON_PATH=$PWD/bin
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should these variables be quoted?

(It would be odd for these to require quoting on a Unix system, but if this gets used on Windows, spaces are much more common there IIRC.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about the envrc syntax, but probably.

run: cargo nextest run --workspace --all-features --status-level skip --failure-output immediate-final --no-fail-fast -j 12
run: |
export PATH=$PWD/bin:$PATH
export PUFFIN_PYTHON_PATH=$PWD/bin
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you use direnv here instead?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure

"""
)
_suffix_re = re.compile(
r"""(?x)^(.*?)-(%s)$"""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can just be "-(%s)$", although you would need to use _suffix_re.search(...) instead of _suffix_re.match(...) below.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is copied directly from Rye, hesitant to change?

@@ -0,0 +1,101 @@
#!/usr/bin/env bash
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additionally, I'm a fan of explicitly checking for dependencies used in a shell script if any outside of core tools are needed. For example, see https://github.com/BurntSushi/dotfiles/blob/2431b0d87614eabc0f022fd3617dcb0e5a5b4de4/bin/backup#L315-L322 and https://github.com/BurntSushi/dotfiles/blob/2431b0d87614eabc0f022fd3617dcb0e5a5b4de4/bin/backup#L341.

It's a big quality of life improvement because it avoids the scenario of getting half-way through execution and then getting a "jq not found" error.

@@ -0,0 +1,101 @@
#!/usr/bin/env bash
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also write this in Python to avoid adding new dependencies. And it might work on Windows. (Although I see some symlinking below...)


# Determine system metadata
os=$(uname | tr '[:upper:]' '[:lower:]')
arch=$(arch)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

arch isn't a standard command. (It's not on my system for example.) I think it is Debian specific. uname --machine might be what you're looking for?

Copy link
Member Author

@zanieb zanieb Jan 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 weird its present on macOS and Ubuntu but can change

@zanieb
Copy link
Member Author

zanieb commented Jan 26, 2024

I plan on addressing this round of feedback then merging since I have a stack on top of this and we really need to codify these versions. The bin folder can be moved elsewhere if you want to use these versions of Python globally on your system (and avoid setting variables when using the project). I don't think we'll have Windows support initially since I can't test it, but I'm happy to collaborate on it. I can work on it via GitHub Actions in a follow-up if needed.

Does this mean I don't need pyenv any more?

That's the goal!

@zanieb zanieb merged commit 21577ad into main Jan 26, 2024
5 checks passed
@zanieb zanieb deleted the zb/bootstrap-python branch January 26, 2024 18:12
zanieb added a commit that referenced this pull request Jan 26, 2024
…rsions (#1106)

In #1040 we broke the pip
compile scenarios designed to test failure when a required Python
version is not available — resolution succeeded because all of the
Python versions were available in CI. Following #1105 we have the
ability to isolate tests from Python versions available in the system.
Here, we limit the scenarios to only the Python version in the current
environment, restoring our ability to test the error messages.

With astral-sh/packse#95, we will be able to
specify scenarios with access to additional system Python versions. This
will allow us to include test coverage where resolution can succeed by
using a version available elsewhere on the system. See #1111 for this
follow-up.
zanieb added a commit that referenced this pull request Jan 26, 2024
#1131 shows that `direnv` installation is _most_ of the CI overhead
introduced by #1105.

Instead of using `direnv`, let's just use a simple `.env` file that can
be loaded with `source` or [`direnv`'s `dotenv`
directive](https://direnv.net/man/direnv-stdlib.1.html#codedotenv-ltdotenvpathgtcode).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
testing Internal testing of behavior
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants