Repository: railwayapp/nixpacks
Pull request #464 (2022-08-29 14:02:51)

Allow installing Python relative deps
This PR allows installing Python relative deps by adding all files to the container before installing. With the Python dep cached installing is very quick.

Fixes https://github.com/railwayapp/nixpacks/issues/373



Patch:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 15c99f1..4ca399b 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -137,6 +137,21 @@ jobs:
         tag_name: ${{ needs.create-release.outputs.nixpacks_version }}
         files: nixpacks-${{ needs.create-release.outputs.nixpacks_version }}-${{ matrix.target }}*
 
+    - name: Install cargo-deb
+      if: matrix.target == 'x86_64-unknown-linux-musl'
+      run: cargo install cargo-deb
+
+    - name: Generate .deb package file
+      if: matrix.target == 'x86_64-unknown-linux-musl'
+      run: cargo deb --target x86_64-unknown-linux-musl --output nixpacks-${{ needs.create-release.outputs.nixpacks_version }}-amd64.deb
+
+    - name: Upload .deb package file
+      if: matrix.target == 'x86_64-unknown-linux-musl'
+      uses: svenstaro/upload-release-action@v2
+      with:
+        tag: ${{ needs.create-release.outputs.nixpacks_version }}
+        file: nixpacks-${{ needs.create-release.outputs.nixpacks_version }}-amd64.deb
+
     - name: Set SHA
       if: matrix.config.os == 'macos-latest'
       id: shasum
diff --git a/Cargo.toml b/Cargo.toml
index e6d8bf2..79ff3bf 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -52,3 +52,8 @@ path-slash = "0.2.1"
 dotenv-parser = "0.1.3"
 insta = { version = "1.18.2", features = ["redactions", "json"] }
 test-helper = { path = "./test-helper" }
+
+[package.metadata.deb]
+# https://packages.debian.org/stretch
+section = "devel"
+
diff --git a/docs/pages/docs/install.md b/docs/pages/docs/install.md
index f29b60b..f89b29d 100644
--- a/docs/pages/docs/install.md
+++ b/docs/pages/docs/install.md
@@ -20,6 +20,16 @@ Download Nixpacks from GH releases and install automatically
 curl -sSL https://nixpacks.com/install.sh | bash
 ```
 
+## Debian (and derivatives like Ubuntu)
+
+Nixpacks provides a `.deb` file provided in each [release](https://github.com/railwayapp/nixpacks/releases).
+
+```sh
+# Replace {VERSION} with the version that you want to install
+curl -LO https://github.com/railwayapp/nixpacks/releases/download/v{VERSION}/nixpacks-v{VERSION}-amd64.deb
+sudo dpkg -i nixpacks-v{VERSION}-amd64.deb
+```
+
 ## Scoop
 
 Install Nixpacks from Scoop using the [official bucket](https://github.com/ScoopInstaller/Main/blob/master/bucket/nixpacks.json) (Windows Only)
diff --git a/src/nixpacks/environment.rs b/src/nixpacks/environment.rs
index 5499e03..701144b 100644
--- a/src/nixpacks/environment.rs
+++ b/src/nixpacks/environment.rs
@@ -1,4 +1,5 @@
-use anyhow::{bail, Result};
+use anyhow::Result;
+use regex::Regex;
 use std::{collections::BTreeMap, env};
 
 pub type EnvironmentVariables = BTreeMap<String, String>;
@@ -16,19 +17,22 @@ impl Environment {
     pub fn from_envs(envs: Vec<&str>) -> Result<Environment> {
         let mut environment = Environment::default();
         for env in envs {
-            let v: Vec<&str> = env.split('=').collect();
-            if v.len() == 1 {
-                // Pull the variable from the current environment
-                let name = v[0];
+            let matches = Regex::new(r"([A-Za-z0-9_-]*)(?:=?)([\s\S]*)")
+                .unwrap()
+                .captures(env)
+                .unwrap();
+            if matches.get(2).unwrap().as_str() == "" {
+                // No value, pull from the current environment
+                let name = matches.get(1).unwrap().as_str();
                 if let Ok(value) = env::var(name) {
-                    // Variable is set
                     environment.set_variable(name.to_string(), value);
                 }
-            } else if v.len() > 2 {
-                bail!("Unable to parse variable string");
             } else {
                 // Use provided name, value pair
-                environment.set_variable(v[0].to_string(), v[1].to_string());
+                environment.set_variable(
+                    matches.get(1).unwrap().as_str().to_string(),
+                    matches.get(2).unwrap().as_str().to_string(),
+                );
             }
         }
 
@@ -87,8 +91,9 @@ mod tests {
     }
 
     #[test]
-    fn test_create_invalid_environment() {
-        assert!(Environment::from_envs(vec!["INVALID=ENV=CONFIG"]).is_err());
+    fn test_create_equals_sign_parsing() {
+        let environment = Environment::from_envs(vec!["INVALID=ENV=CONFIG"]).unwrap();
+        assert_eq!(environment.get_variable("INVALID"), Some("ENV=CONFIG"));
     }
 
     #[test]
diff --git a/src/nixpacks/plan/generator.rs b/src/nixpacks/plan/generator.rs
index 7dd7157..b68fb56 100644
--- a/src/nixpacks/plan/generator.rs
+++ b/src/nixpacks/plan/generator.rs
@@ -7,9 +7,9 @@ use anyhow::{Context, Ok, Result};
 use std::collections::HashMap;
 
 // This line is automatically updated.
-// Last Modified: 2022-08-22 17:06:26 UTC+0000
-// https://github.com/NixOS/nixpkgs/commit/54060e816971276da05970a983487a25810c38a7
-pub const NIXPKGS_ARCHIVE: &str = "54060e816971276da05970a983487a25810c38a7";
+// Last Modified: 2022-08-29 17:07:50 UTC+0000
+// https://github.com/NixOS/nixpkgs/commit/0e304ff0d9db453a4b230e9386418fd974d5804a
+pub const NIXPKGS_ARCHIVE: &str = "0e304ff0d9db453a4b230e9386418fd974d5804a";
 
 #[derive(Clone, Default, Debug)]
 pub struct GeneratePlanOptions {
diff --git a/src/providers/go.rs b/src/providers/go.rs
index 5213aca..08e8760 100644
--- a/src/providers/go.rs
+++ b/src/providers/go.rs
@@ -35,7 +35,7 @@ impl Provider for GolangProvider {
         plan.add_phase(Phase::setup(Some(vec![Pkg::new(&nix_pkg)])));
 
         if app.includes_file("go.mod") {
-            let mut install = Phase::install(Some("go get".to_string()));
+            let mut install = Phase::install(Some("go mod download".to_string()));
             install.add_cache_directory(GO_BUILD_CACHE_DIR.to_string());
             plan.add_phase(install);
         }
diff --git a/src/providers/python.rs b/src/providers/python.rs
index 3c63907..eafb7c2 100644
--- a/src/providers/python.rs
+++ b/src/providers/python.rs
@@ -104,7 +104,7 @@ impl PythonProvider {
 
         // Numpy needs some C headers to be available
         // stdenv.cc.cc.lib -> https://discourse.nixos.org/t/nixos-with-poetry-installed-pandas-libstdc-so-6-cannot-open-shared-object-file/8442/3
-        if PythonProvider::uses_numpy(app)? {
+        if PythonProvider::uses_dep(app, "numpy")? || PythonProvider::uses_dep(app, "pandas")? {
             setup_phase.add_pkgs_libs(vec!["zlib".to_string(), "stdenv.cc.cc.lib".to_string()]);
         }
 
@@ -122,9 +122,7 @@ impl PythonProvider {
                 create_env, activate_env
             )));
 
-            install_phase.add_file_dependency("requirements.txt".to_string());
             install_phase.add_path(format!("{}/bin", env_loc));
-
             install_phase.add_cache_directory(PIP_CACHE_DIR.to_string());
 
             return Ok(Some(install_phase));
@@ -136,8 +134,6 @@ impl PythonProvider {
                     create_env, activate_env, install_poetry
                 )));
 
-                install_phase.add_file_dependency("poetry.lock".to_string());
-                install_phase.add_file_dependency("pyproject.toml".to_string());
                 install_phase.add_path(format!("{}/bin", env_loc));
 
                 install_phase.add_cache_directory(PIP_CACHE_DIR.to_string());
@@ -327,20 +323,20 @@ impl PythonProvider {
         ))
     }
 
-    fn uses_numpy(app: &App) -> Result<bool> {
-        let requirements_numpy = app.includes_file("requirements.txt")
+    fn uses_dep(app: &App, dep: &str) -> Result<bool> {
+        let requirements_usage = app.includes_file("requirements.txt")
             && app
                 .read_file("requirements.txt")?
                 .to_lowercase()
-                .contains("numpy");
+                .contains(dep);
 
-        let project_numpy = app.includes_file("pyproject.toml")
+        let pyproject_usage = app.includes_file("pyproject.toml")
             && app
                 .read_file("pyproject.toml")?
                 .to_lowercase()
-                .contains("numpy");
+                .contains(dep);
 
-        Ok(requirements_numpy || project_numpy)
+        Ok(requirements_usage || pyproject_usage)
     }
 }
 
@@ -407,12 +403,14 @@ mod test {
 
     #[test]
     fn test_numpy_detection() -> Result<()> {
-        assert!(!PythonProvider::uses_numpy(&App::new(
-            "./examples/python"
-        )?)?,);
-        assert!(PythonProvider::uses_numpy(&App::new(
-            "./examples/python-numpy"
-        )?)?,);
+        assert!(!PythonProvider::uses_dep(
+            &App::new("./examples/python",)?,
+            "numpy"
+        )?,);
+        assert!(PythonProvider::uses_dep(
+            &App::new("./examples/python-numpy",)?,
+            "numpy"
+        )?,);
         Ok(())
     }
 }


Test patch:
diff --git a/tests/snapshots/generate_plan_tests__go_custom_version.snap b/tests/snapshots/generate_plan_tests__go_custom_version.snap
index ab2caa4..2bbdbb4 100644
--- a/tests/snapshots/generate_plan_tests__go_custom_version.snap
+++ b/tests/snapshots/generate_plan_tests__go_custom_version.snap
@@ -15,7 +15,7 @@ expression: plan
         "setup"
       ],
       "commands": [
-        "go get"
+        "go mod download"
       ],
       "cacheDirectories": [
         "/root/.cache/go-build"
diff --git a/tests/snapshots/generate_plan_tests__go_gin.snap b/tests/snapshots/generate_plan_tests__go_gin.snap
index ab2caa4..2bbdbb4 100644
--- a/tests/snapshots/generate_plan_tests__go_gin.snap
+++ b/tests/snapshots/generate_plan_tests__go_gin.snap
@@ -15,7 +15,7 @@ expression: plan
         "setup"
       ],
       "commands": [
-        "go get"
+        "go mod download"
       ],
       "cacheDirectories": [
         "/root/.cache/go-build"
diff --git a/tests/snapshots/generate_plan_tests__go_mod.snap b/tests/snapshots/generate_plan_tests__go_mod.snap
index bfd52c6..1e3511e 100644
--- a/tests/snapshots/generate_plan_tests__go_mod.snap
+++ b/tests/snapshots/generate_plan_tests__go_mod.snap
@@ -15,7 +15,7 @@ expression: plan
         "setup"
       ],
       "commands": [
-        "go get"
+        "go mod download"
       ],
       "cacheDirectories": [
         "/root/.cache/go-build"
diff --git a/tests/snapshots/generate_plan_tests__python.snap b/tests/snapshots/generate_plan_tests__python.snap
index 7d06c43..3fb2e00 100644
--- a/tests/snapshots/generate_plan_tests__python.snap
+++ b/tests/snapshots/generate_plan_tests__python.snap
@@ -22,9 +22,6 @@ expression: plan
       "commands": [
         "python -m venv /opt/venv && . /opt/venv/bin/activate && pip install -r requirements.txt"
       ],
-      "onlyIncludeFiles": [
-        "requirements.txt"
-      ],
       "cacheDirectories": [
         "/root/.cache/pip"
       ],
diff --git a/tests/snapshots/generate_plan_tests__python_django.snap b/tests/snapshots/generate_plan_tests__python_django.snap
index 13a7930..46e8335 100644
--- a/tests/snapshots/generate_plan_tests__python_django.snap
+++ b/tests/snapshots/generate_plan_tests__python_django.snap
@@ -28,9 +28,6 @@ expression: plan
       "commands": [
         "python -m venv /opt/venv && . /opt/venv/bin/activate && pip install -r requirements.txt"
       ],
-      "onlyIncludeFiles": [
-        "requirements.txt"
-      ],
       "cacheDirectories": [
         "/root/.cache/pip"
       ],
diff --git a/tests/snapshots/generate_plan_tests__python_numpy.snap b/tests/snapshots/generate_plan_tests__python_numpy.snap
index 0b76e25..bcbe113 100644
--- a/tests/snapshots/generate_plan_tests__python_numpy.snap
+++ b/tests/snapshots/generate_plan_tests__python_numpy.snap
@@ -26,9 +26,6 @@ expression: plan
       "commands": [
         "python -m venv /opt/venv && . /opt/venv/bin/activate && pip install -r requirements.txt"
       ],
-      "onlyIncludeFiles": [
-        "requirements.txt"
-      ],
       "cacheDirectories": [
         "/root/.cache/pip"
       ],
diff --git a/tests/snapshots/generate_plan_tests__python_poetry.snap b/tests/snapshots/generate_plan_tests__python_poetry.snap
index 8df3f41..6d8794c 100644
--- a/tests/snapshots/generate_plan_tests__python_poetry.snap
+++ b/tests/snapshots/generate_plan_tests__python_poetry.snap
@@ -25,10 +25,6 @@ expression: plan
       "commands": [
         "python -m venv /opt/venv && . /opt/venv/bin/activate && pip install poetry==$NIXPACKS_POETRY_VERSION && poetry install --no-dev --no-interaction --no-ansi"
       ],
-      "onlyIncludeFiles": [
-        "poetry.lock",
-        "pyproject.toml"
-      ],
       "cacheDirectories": [
         "/root/.cache/pip"
       ],
diff --git a/tests/snapshots/generate_plan_tests__python_procfile.snap b/tests/snapshots/generate_plan_tests__python_procfile.snap
index d7c2ca4..242396d 100644
--- a/tests/snapshots/generate_plan_tests__python_procfile.snap
+++ b/tests/snapshots/generate_plan_tests__python_procfile.snap
@@ -22,9 +22,6 @@ expression: plan
       "commands": [
         "python -m venv /opt/venv && . /opt/venv/bin/activate && pip install -r requirements.txt"
       ],
-      "onlyIncludeFiles": [
-        "requirements.txt"
-      ],
       "cacheDirectories": [
         "/root/.cache/pip"
       ],