Repository: railwayapp/nixpacks
Pull request #627 (2022-10-20 15:43:18)

Fix usage of cache dirs env vars
This PR
- Fixes the usage of the `NIXPACKS_INSTALL_CACHE_DIRS` and `NIXPACKS_BUILD_CACHE_DIRS` env vars
- Allows using a ` ` (space) or a `,` (comma) as a separator for list based config variables
- Adds a test for creating a build plan from environment variables



Patch:
diff --git a/src/nixpacks/plan/mod.rs b/src/nixpacks/plan/mod.rs
index e5076bc..8591759 100644
--- a/src/nixpacks/plan/mod.rs
+++ b/src/nixpacks/plan/mod.rs
@@ -190,19 +190,13 @@ impl BuildPlan {
         let mut uses_setup = false;
 
         if let Some(pkg_string) = env.get_config_variable("PKGS") {
-            let mut pkgs = pkg_string
-                .split(' ')
-                .map(std::string::ToString::to_string)
-                .collect::<Vec<_>>();
+            let mut pkgs = split_env_string(pkg_string.as_str());
             pkgs.push("...".to_string());
             setup.nix_pkgs = Some(pkgs);
             uses_setup = true;
         }
         if let Some(apt_string) = env.get_config_variable("APT_PKGS") {
-            let mut apts = apt_string
-                .split(' ')
-                .map(std::string::ToString::to_string)
-                .collect::<Vec<_>>();
+            let mut apts = split_env_string(apt_string.as_str());
             apts.push("...".to_string());
             setup.apt_pkgs = Some(apts);
             uses_setup = true;
@@ -223,13 +217,27 @@ impl BuildPlan {
 
         // Install
         if let Some(cmd_string) = env.get_config_variable("INSTALL_CMD") {
-            let install = Phase::install(Some(cmd_string));
+            let mut install = Phase::install(Some(cmd_string));
+
+            if let Some(cache_dirs) = env.get_config_variable("INSTALL_CACHE_DIRS") {
+                split_env_string(cache_dirs.as_str())
+                    .iter()
+                    .for_each(|dir| install.add_cache_directory(dir));
+            }
+
             phases.push(install);
         }
 
         // Build
         if let Some(cmd_string) = env.get_config_variable("BUILD_CMD") {
-            let build = Phase::build(Some(cmd_string));
+            let mut build = Phase::build(Some(cmd_string));
+
+            if let Some(cache_dirs) = env.get_config_variable("BUILD_CACHE_DIRS") {
+                split_env_string(cache_dirs.as_str())
+                    .iter()
+                    .for_each(|dir| build.add_cache_directory(dir));
+            }
+
             phases.push(build);
         }
 
@@ -292,10 +300,58 @@ impl topological_sort::TopItem for (String, Phase) {
     }
 }
 
+fn split_env_string(s: &str) -> Vec<String> {
+    s.split([' ', ','])
+        .map(std::string::ToString::to_string)
+        .filter(|s| !s.is_empty())
+        .collect::<Vec<_>>()
+}
+
 #[cfg(test)]
 mod test {
     use super::*;
 
+    #[test]
+    fn get_plan_from_environment() {
+        let env = Environment::from_envs(vec![
+            "NIXPACKS_PKGS=cowsay sl",
+            "NIXPACKS_APT_PKGS=foo,bar",
+            "NIXPACKS_LIBS=my-lib",
+            "NIXPACKS_INSTALL_CMD=yarn install",
+            "NIXPACKS_INSTALL_CACHE_DIRS=install/cache/dir",
+            "NIXPACKS_BUILD_CMD=yarn build",
+            "NIXPACKS_BUILD_CACHE_DIRS=build/cache/dir",
+            "NIXPACKS_START_CMD=yarn start",
+        ])
+        .unwrap();
+        let env_plan = BuildPlan::from_environment(&env);
+
+        let result = BuildPlan::from_toml(
+            r#"
+            [phases.setup]
+            nixPkgs = ["cowsay", "sl", "..."]
+            aptPkgs = ["foo", "bar", "..."]
+            nixLibs = ["my-lib", "..."]
+
+            [phases.install]
+            cmds = ["yarn install"]
+            cacheDirectories = ["install/cache/dir"]
+            dependsOn = ["setup"]
+
+            [phases.build]
+            cmds = ["yarn build"]
+            cacheDirectories = ["build/cache/dir"]
+            dependsOn = ["install"]
+
+            [start]
+            cmd = "yarn start"
+            "#,
+        )
+        .unwrap();
+
+        assert_eq!(result, env_plan);
+    }
+
     #[test]
     fn test_get_phases_with_dependencies() {
         let setup = Phase::new("setup");
@@ -311,7 +367,6 @@ mod test {
 
         let plan = BuildPlan::new(&vec![setup, install, build, another], None);
 
-        // let phases = topological_sort(plan.get_phases_with_dependencies("build")).unwrap();
         let build_phase = plan.get_phases_with_dependencies("build");
         let phases = build_phase.values();
 
@@ -341,4 +396,16 @@ mod test {
         );
         assert!(plan.get_phase("setup").unwrap().nixpkgs_archive.is_some());
     }
+
+    #[test]
+    fn test_split_env_string() {
+        assert_eq!(
+            split_env_string("nodejs yarn"),
+            vec!["nodejs".to_string(), "yarn".to_string()]
+        );
+        assert_eq!(
+            split_env_string("nodejs, yarn"),
+            vec!["nodejs".to_string(), "yarn".to_string()]
+        );
+    }
 }


Test patch:
diff --git a/tests/snapshots/generate_plan_tests__config_from_environment_variables.snap b/tests/snapshots/generate_plan_tests__config_from_environment_variables.snap
index 1c7e4b0..94a5693 100644
--- a/tests/snapshots/generate_plan_tests__config_from_environment_variables.snap
+++ b/tests/snapshots/generate_plan_tests__config_from_environment_variables.snap
@@ -22,6 +22,10 @@ expression: plan
       ],
       "cmds": [
         "build"
+      ],
+      "cacheDirectories": [
+        "/build",
+        "barbaz"
       ]
     },
     "install": {
@@ -31,6 +35,10 @@ expression: plan
       ],
       "cmds": [
         "install"
+      ],
+      "cacheDirectories": [
+        "/tmp",
+        "foobar"
       ]
     },
     "setup": {