Skip to content

Commit

Permalink
feat: asdf support (#1116)
Browse files Browse the repository at this point in the history
* feat: add asdf support to python + poetry

* feat: initial asdf poetry and python extraction

* fix: remove old poetry version code

* style: formatting

* style: more linting fixes

* log if tool-versions does not match expectations

* tests for asdf

* fmt

* fmt

* fmt

* fixing badly merged changes

* adding snapshot
  • Loading branch information
iloveitaly authored Jul 6, 2024
1 parent 9fae921 commit d927e08
Show file tree
Hide file tree
Showing 9 changed files with 306 additions and 2 deletions.
2 changes: 2 additions & 0 deletions examples/python-asdf-poetry/.tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
python 3.12.3
poetry 1.8.2
9 changes: 9 additions & 0 deletions examples/python-asdf-poetry/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import sys
import subprocess

print(sys.version)

poetry_version = subprocess.run(
["poetry", "--version"], capture_output=True, text=True
).stdout.strip()
print(poetry_version)
129 changes: 129 additions & 0 deletions examples/python-asdf-poetry/poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions examples/python-asdf-poetry/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[tool.poetry]
name = "python-asdf-poetry"
version = "0.1.0"
description = ""
authors = ["iloveitaly"]

[tool.poetry.dependencies]
python = "^3.10"

[tool.poetry.dev-dependencies]
pytest = "^5.2"

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
45 changes: 45 additions & 0 deletions src/nixpacks/asdf.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use regex::Regex;
use std::collections::HashMap;

pub fn parse_tool_versions_content(file_content: &str) -> HashMap<String, String> {
let re = Regex::new(r"\s+").unwrap();
file_content
.lines()
.map(str::trim)
.filter(|line| !line.starts_with('#'))
.filter_map(|line| {
let parts: Vec<&str> = re.splitn(line, 2).collect();
if parts.len() == 2 {
Some((parts[0].to_string(), parts[1].to_string()))
} else {
None
}
})
.collect()
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_parse_tool_versions_content() {
let file_content = "\
# This is a comment
python 3.10.4
poetry 1.7.1
# exact ruby version required for homebrew
ruby\t3.1.4
direnv 2.32.3 ";
let versions = parse_tool_versions_content(file_content);

let expected = HashMap::from([
("direnv".to_string(), "2.32.3".to_string()),
("python".to_string(), "3.10.4".to_string()),
("ruby".to_string(), "3.1.4".to_string()),
("poetry".to_string(), "1.7.1".to_string()),
]);

assert_eq!(versions, expected);
}
}
1 change: 1 addition & 0 deletions src/nixpacks/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod app;
pub mod asdf;
pub mod builder;
pub mod environment;
mod files;
Expand Down
46 changes: 44 additions & 2 deletions src/providers/python.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::{
chain,
nixpacks::{
app::App,
asdf::parse_tool_versions_content,
environment::{Environment, EnvironmentVariables},
plan::{
phase::{Phase, StartPhase},
Expand Down Expand Up @@ -73,12 +74,24 @@ impl Provider for PythonProvider {
plan.add_variables(PythonProvider::default_python_environment_variables());

if app.includes_file("poetry.lock") {
let mut version = POETRY_VERSION.to_string();

if app.includes_file(".tool-versions") {
let file_content = &app.read_file(".tool-versions")?;

if let Some(poetry_version) =
PythonProvider::parse_tool_versions_poetry_version(file_content)?
{
println!("Using poetry version from .tool-versions: {poetry_version}");
version = poetry_version;
}
}

plan.add_variables(EnvironmentVariables::from([(
"NIXPACKS_POETRY_VERSION".to_string(),
POETRY_VERSION.to_string(),
version,
)]));
}

if app.includes_file("pdm.lock") {
plan.add_variables(EnvironmentVariables::from([(
"NIXPACKS_PDM_VERSION".to_string(),
Expand Down Expand Up @@ -302,6 +315,32 @@ impl PythonProvider {
.map(|m| m.get(2).unwrap().as_str().to_string()))
}

fn parse_tool_versions_python_version(file_content: &str) -> Result<Option<String>> {
let asdf_versions = parse_tool_versions_content(file_content);

// the python version can only specify a major.minor version right now, and not a patch version
// however, in asdf a patch version is specified, so we need to strip it
Ok(asdf_versions.get("python").map(|s| {
let parts: Vec<&str> = s.split('.').collect();

if parts.len() == 3 {
// this is the expected result, but will be unexpected to users
println!("Patch version detected in .tool-versions, but not supported in nixpkgs.");
} else if parts.len() == 2 {
println!("Expected a version string in the format x.y.z from .tool-versions");
} else {
println!("Could not find a version string in the format x.y.z or x.y from .tool-versions");
}

format!("{}.{}", parts[0], parts[1])
}))
}

fn parse_tool_versions_poetry_version(file_content: &str) -> Result<Option<String>> {
let asdf_versions = parse_tool_versions_content(file_content);
Ok(asdf_versions.get("poetry").cloned())
}

fn default_python_environment_variables() -> EnvironmentVariables {
let python_variables = vec![
("PYTHONFAULTHANDLER", "1"),
Expand Down Expand Up @@ -342,6 +381,9 @@ impl PythonProvider {
} else if app.includes_file("Pipfile") {
let file_content = &app.read_file("Pipfile")?;
custom_version = PythonProvider::parse_pipfile_python_version(file_content)?;
} else if app.includes_file(".tool-versions") {
let file_content = &app.read_file(".tool-versions")?;
custom_version = PythonProvider::parse_tool_versions_python_version(file_content)?;
}

// If it's still none, return default
Expand Down
9 changes: 9 additions & 0 deletions tests/docker_run_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,15 @@ async fn test_python_2() {
assert!(output.contains("Hello from Python 2"));
}

#[tokio::test]
async fn test_python_asdf_poetry() {
let name = simple_build("./examples/python-asdf-poetry").await;
let output = run_image(&name, None).await;

assert!(output.contains("3.12.3"), "{}", output);
assert!(output.contains("Poetry (version 1.8.2)"), "{}", output);
}

#[tokio::test]
async fn test_django() {
// Create the network
Expand Down
52 changes: 52 additions & 0 deletions tests/snapshots/generate_plan_tests__python_asdf_poetry.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
---
source: tests/generate_plan_tests.rs
expression: plan
---
{
"providers": [],
"buildImage": "[build_image]",
"variables": {
"NIXPACKS_METADATA": "python,poetry",
"NIXPACKS_POETRY_VERSION": "1.8.2",
"PIP_DEFAULT_TIMEOUT": "100",
"PIP_DISABLE_PIP_VERSION_CHECK": "1",
"PIP_NO_CACHE_DIR": "1",
"PYTHONDONTWRITEBYTECODE": "1",
"PYTHONFAULTHANDLER": "1",
"PYTHONHASHSEED": "random",
"PYTHONUNBUFFERED": "1"
},
"phases": {
"install": {
"name": "install",
"dependsOn": [
"setup"
],
"cmds": [
"python -m venv --copies /opt/venv && . /opt/venv/bin/activate && pip install poetry==$NIXPACKS_POETRY_VERSION && poetry install --no-dev --no-interaction --no-ansi"
],
"cacheDirectories": [
"/root/.cache/pip"
],
"paths": [
"/opt/venv/bin"
]
},
"setup": {
"name": "setup",
"nixPkgs": [
"python312",
"gcc"
],
"nixLibs": [
"zlib",
"stdenv.cc.cc.lib"
],
"nixOverlays": [],
"nixpkgsArchive": "[archive]"
}
},
"start": {
"cmd": "python main.py"
}
}

0 comments on commit d927e08

Please sign in to comment.