From 46d8a629e02a598b91cdc1fbfec64f0d4f5c2016 Mon Sep 17 00:00:00 2001 From: Jake Runzer Date: Thu, 5 May 2022 23:33:09 -0400 Subject: [PATCH 1/2] support python 2.7 --- examples/python-2/.python-version | 1 + examples/python-2/main.py | 1 + src/nixpacks/mod.rs | 9 ++---- src/providers/npm.rs | 2 +- src/providers/python.rs | 53 +++++++++++++++++++++++-------- tests/docker_run_tests.rs | 11 +++++-- 6 files changed, 53 insertions(+), 24 deletions(-) create mode 100644 examples/python-2/.python-version create mode 100644 examples/python-2/main.py diff --git a/examples/python-2/.python-version b/examples/python-2/.python-version new file mode 100644 index 000000000..1effb0034 --- /dev/null +++ b/examples/python-2/.python-version @@ -0,0 +1 @@ +2.7 diff --git a/examples/python-2/main.py b/examples/python-2/main.py new file mode 100644 index 000000000..23c4e4a03 --- /dev/null +++ b/examples/python-2/main.py @@ -0,0 +1 @@ +print "Hello from Python 2" diff --git a/src/nixpacks/mod.rs b/src/nixpacks/mod.rs index faf0e757b..5cc3c402a 100644 --- a/src/nixpacks/mod.rs +++ b/src/nixpacks/mod.rs @@ -216,13 +216,8 @@ impl<'a> AppBuilder<'a> { let env_var_pkgs = self .environment .get_config_variable("PKGS") - .map(|pkg_string| { - pkg_string - .split(" ") - .map(|s| Pkg::new(s)) - .collect::>() - }) - .unwrap_or(Vec::new()); + .map(|pkg_string| pkg_string.split(' ').map(Pkg::new).collect::>()) + .unwrap_or_default(); // Add custom user packages let mut pkgs = [self.options.custom_pkgs.clone(), env_var_pkgs].concat(); diff --git a/src/providers/npm.rs b/src/providers/npm.rs index b345271b6..e1d398202 100644 --- a/src/providers/npm.rs +++ b/src/providers/npm.rs @@ -118,7 +118,7 @@ impl NpmProvider { .as_ref() .and_then(|engines| engines.get("node")); - let node_version = env_node_version.or(pkg_node_version); + let node_version = pkg_node_version.or(env_node_version); let node_version = match node_version { Some(node_version) => node_version, diff --git a/src/providers/python.rs b/src/providers/python.rs index 915a2c6ba..cc275c947 100644 --- a/src/providers/python.rs +++ b/src/providers/python.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; -use anyhow::{Context, Result}; +use anyhow::{bail, Context, Result}; + use serde::Deserialize; use crate::{ @@ -15,7 +16,10 @@ use crate::{ use super::Provider; +pub const DEFAULT_PYTHON_PKG_NAME: &'static &str = &"python38"; + pub struct PythonProvider {} + impl Provider for PythonProvider { fn name(&self) -> &str { "python" @@ -27,12 +31,9 @@ impl Provider for PythonProvider { || app.includes_file("pyproject.toml")) } - fn setup( - &self, - _app: &App, - _env: &crate::nixpacks::environment::Environment, - ) -> Result> { - Ok(Some(SetupPhase::new(vec![Pkg::new("python38")]))) + fn setup(&self, app: &App, env: &Environment) -> Result> { + let pkg = PythonProvider::get_nix_python_packages(app, env)?; + Ok(Some(SetupPhase::new(vec![pkg]))) } fn install(&self, app: &App, _env: &Environment) -> Result> { @@ -63,7 +64,7 @@ impl Provider for PythonProvider { fn start(&self, app: &App, _env: &Environment) -> Result> { if app.includes_file("pyproject.toml") { - if let Ok(meta) = self.parse_pyproject(app) { + if let Ok(meta) = PythonProvider::parse_pyproject(app) { if let Some(entry_point) = meta.entry_point { return Ok(Some(StartPhase::new(match entry_point { EntryPoint::Command(cmd) => cmd, @@ -110,7 +111,30 @@ enum EntryPoint { } impl PythonProvider { - fn read_pyproject(&self, app: &App) -> Result> { + fn get_nix_python_packages(app: &App, env: &Environment) -> Result { + let mut custom_version = env + .get_config_variable("PYTHON_VERSION") + .map(|s| s.to_string()); + + if custom_version.is_none() && app.includes_file(".python-version") { + custom_version = Some(app.read_file(".python-version")?); + } + + match custom_version.map(|s| s.trim().to_string()) { + Some(custom_version) => { + if custom_version == "2" || custom_version == "2.7" { + Ok(Pkg::new("python27Full")) + } else if custom_version == "3" || custom_version == "3.8" { + Ok(Pkg::new(DEFAULT_PYTHON_PKG_NAME)) + } else { + bail!("Only the latest version of Python `2` or `3` are supported.") + } + } + None => Ok(Pkg::new(DEFAULT_PYTHON_PKG_NAME)), + } + } + + fn read_pyproject(app: &App) -> Result> { if app.includes_file("pyproject.toml") { return Ok(Some( app.read_toml("pyproject.toml") @@ -119,7 +143,8 @@ impl PythonProvider { } Ok(None) } - fn parse_project(&self, project: &PyProject) -> ProjectMeta { + + fn parse_project(project: &PyProject) -> ProjectMeta { let project_name = project .project .as_ref() @@ -148,10 +173,10 @@ impl PythonProvider { entry_point, } } - fn parse_pyproject(&self, app: &App) -> Result { - Ok(self.parse_project( - &(self - .read_pyproject(app)? + + fn parse_pyproject(app: &App) -> Result { + Ok(PythonProvider::parse_project( + &(PythonProvider::read_pyproject(app)? .ok_or_else(|| anyhow::anyhow!("failed to load pyproject.toml"))?), )) } diff --git a/tests/docker_run_tests.rs b/tests/docker_run_tests.rs index 0b612024a..e1b9f3356 100644 --- a/tests/docker_run_tests.rs +++ b/tests/docker_run_tests.rs @@ -21,7 +21,7 @@ fn get_container_ids_from_image(image: String) -> String { String::from_utf8_lossy(&output.stdout).to_string() } -fn stop_containers(container_id: &String) { +fn stop_containers(container_id: &str) { Command::new("docker") .arg("stop") .arg(container_id) @@ -31,7 +31,7 @@ fn stop_containers(container_id: &String) { .expect("failed to execute docker stop"); } -fn remove_containers(container_id: &String) { +fn remove_containers(container_id: &str) { Command::new("docker") .arg("rm") .arg(container_id) @@ -145,6 +145,13 @@ fn test_python() { assert!(output.contains("Hello from Python")); } +#[test] +fn test_python_2() { + let name = simple_build("./examples/python-2"); + let output = run_image(name); + assert!(output.contains("Hello from Python 2")); +} + #[test] fn test_deno() { let name = simple_build("./examples/deno"); From 5ff30d8cecc4bbf96c0890032bf92fa758a2273f Mon Sep 17 00:00:00 2001 From: Jake Runzer Date: Thu, 5 May 2022 23:38:12 -0400 Subject: [PATCH 2/2] python unit tests --- src/providers/python.rs | 53 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/src/providers/python.rs b/src/providers/python.rs index cc275c947..dcf0e8012 100644 --- a/src/providers/python.rs +++ b/src/providers/python.rs @@ -32,7 +32,7 @@ impl Provider for PythonProvider { } fn setup(&self, app: &App, env: &Environment) -> Result> { - let pkg = PythonProvider::get_nix_python_packages(app, env)?; + let pkg = PythonProvider::get_nix_python_package(app, env)?; Ok(Some(SetupPhase::new(vec![pkg]))) } @@ -111,7 +111,7 @@ enum EntryPoint { } impl PythonProvider { - fn get_nix_python_packages(app: &App, env: &Environment) -> Result { + fn get_nix_python_package(app: &App, env: &Environment) -> Result { let mut custom_version = env .get_config_variable("PYTHON_VERSION") .map(|s| s.to_string()); @@ -181,3 +181,52 @@ impl PythonProvider { )) } } + +#[cfg(test)] +mod test { + use super::*; + use crate::nixpacks::{app::App, environment::Environment, nix::Pkg}; + use std::collections::HashMap; + + #[test] + fn test_no_version() -> Result<()> { + assert_eq!( + PythonProvider::get_nix_python_package( + &App::new("./examples/python")?, + &Environment::default() + )?, + Pkg::new(DEFAULT_PYTHON_PKG_NAME) + ); + + Ok(()) + } + + #[test] + fn test_custom_version() -> Result<()> { + assert_eq!( + PythonProvider::get_nix_python_package( + &App::new("./examples/python-2")?, + &Environment::default() + )?, + Pkg::new("python27Full") + ); + + Ok(()) + } + + #[test] + fn test_version_from_environment_variable() -> Result<()> { + assert_eq!( + PythonProvider::get_nix_python_package( + &App::new("./examples/python")?, + &Environment::new(HashMap::from([( + "NIXPACKS_PYTHON_VERSION".to_string(), + "2.7".to_string() + )])) + )?, + Pkg::new("python27Full") + ); + + Ok(()) + } +}