From 01133aa9250a3e83414dd1c508eda1e274c8f558 Mon Sep 17 00:00:00 2001 From: krupashah20 Date: Tue, 7 Apr 2026 23:57:27 +0530 Subject: [PATCH 01/18] refactor: add write_job_sh helper to PEcAn.utils and update template model --- base/utils/R/write_job_sh.R | 21 +++++++++++++++++++++ models/template/R/write.config.MODEL.R | 3 +-- 2 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 base/utils/R/write_job_sh.R diff --git a/base/utils/R/write_job_sh.R b/base/utils/R/write_job_sh.R new file mode 100644 index 00000000000..0111d09191f --- /dev/null +++ b/base/utils/R/write_job_sh.R @@ -0,0 +1,21 @@ +#' Write a job.sh script for a PEcAn model run +#' +#' This helper function writes the job.sh shell script used to execute +#' a model run. It centralizes duplicated logic previously found in +#' individual write.config.* functions across model packages. +#' +#' @param rundir character. Path to the run directory +#' @param run.id character. The run ID +#' @param jobsh character vector. Lines of the job.sh script content +#' @param chmod logical. Whether to make job.sh executable. Default TRUE +#' +#' @return invisible path to the written job.sh file +#' @export +write_job_sh <- function(rundir, run.id, jobsh, chmod = TRUE) { + job_path <- file.path(rundir, run.id, "job.sh") + writeLines(jobsh, con = job_path) + if (chmod) { + Sys.chmod(job_path) + } + invisible(job_path) +} \ No newline at end of file diff --git a/models/template/R/write.config.MODEL.R b/models/template/R/write.config.MODEL.R index 32aac0ab9fe..ce787b36921 100644 --- a/models/template/R/write.config.MODEL.R +++ b/models/template/R/write.config.MODEL.R @@ -69,8 +69,7 @@ write.config.MODEL <- function(defaults, trait.values, settings, run.id) { jobsh <- gsub("@BINARY@", settings$model$binary, jobsh) - writeLines(jobsh, con = file.path(settings$rundir, run.id, "job.sh")) - Sys.chmod(file.path(settings$rundir, run.id, "job.sh")) + PEcAn.utils::write_job_sh(settings$rundir, run.id, jobsh) #----------------------------------------------------------------------- ### Edit a templated config file for runs From ba7c6f94bf05c74409de736ff1b863868e3d8ede Mon Sep 17 00:00:00 2001 From: krupashah20 Date: Wed, 8 Apr 2026 21:20:41 +0530 Subject: [PATCH 02/18] refactor: use write_job_sh helper in SIPNET model --- models/sipnet/R/write.configs.SIPNET.R | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/models/sipnet/R/write.configs.SIPNET.R b/models/sipnet/R/write.configs.SIPNET.R index 70993936d40..794850e6894 100755 --- a/models/sipnet/R/write.configs.SIPNET.R +++ b/models/sipnet/R/write.configs.SIPNET.R @@ -224,8 +224,7 @@ write.config.SIPNET <- function(defaults, trait.values, settings, run.id, inputs } jobsh <- gsub("@DELETE.RAW@", settings$model$delete.raw, jobsh) - writeLines(jobsh, con = file.path(settings$rundir, run.id, "job.sh")) - Sys.chmod(file.path(settings$rundir, run.id, "job.sh")) + PEcAn.utils::write_job_sh(settings$rundir, run.id, jobsh) ### Copy event file From bc7ec4a31e68991db6ba2bd7a8eb0112d0c8aec1 Mon Sep 17 00:00:00 2001 From: krupashah20 Date: Wed, 8 Apr 2026 21:24:38 +0530 Subject: [PATCH 03/18] refactor: use write_job_sh helper in RothC model --- models/rothc/R/write.config.RothC.R | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/models/rothc/R/write.config.RothC.R b/models/rothc/R/write.config.RothC.R index 452396cfad4..cf3a0b22c52 100644 --- a/models/rothc/R/write.config.RothC.R +++ b/models/rothc/R/write.config.RothC.R @@ -75,8 +75,7 @@ write.config.RothC <- function(defaults, trait.values, settings, run.id) { jobsh <- gsub("@BINARY@", settings$model$binary, jobsh) - writeLines(jobsh, con = file.path(settings$rundir, run.id, "job.sh")) - Sys.chmod(file.path(settings$rundir, run.id, "job.sh")) + PEcAn.utils::write_job_sh(settings$rundir, run.id, jobsh) #----------------------------------------------------------------------- ### Edit a templated config file for runs From 20bfbc58b937e93264862f5651c28ed33aa95bbc Mon Sep 17 00:00:00 2001 From: krupashah20 Date: Wed, 8 Apr 2026 21:27:39 +0530 Subject: [PATCH 04/18] refactor: use write_job_sh helper in SIBCASA model --- models/sibcasa/R/write.config.SIBCASA.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/models/sibcasa/R/write.config.SIBCASA.R b/models/sibcasa/R/write.config.SIBCASA.R index 1b390c4dee7..f8bf2f6495e 100644 --- a/models/sibcasa/R/write.config.SIBCASA.R +++ b/models/sibcasa/R/write.config.SIBCASA.R @@ -1,4 +1,5 @@ + #-------------------------------------------------------------------------------------------------# #' Writes a SIBCASA config file. #' @@ -60,8 +61,7 @@ write.config.SIBCASA <- function(defaults, trait.values, settings, run.id) { jobsh <- gsub("@BINARY@", settings$model$binary, jobsh) - writeLines(jobsh, con = file.path(settings$rundir, run.id, "job.sh")) - Sys.chmod(file.path(settings$rundir, run.id, "job.sh")) + PEcAn.utils::write_job_sh(settings$rundir, run.id, jobsh) #----------------------------------------------------------------------- ### Edit a templated config file for runs From cfd8e02a34afdcc352c6e51bd0df527f5b15b70f Mon Sep 17 00:00:00 2001 From: krupashah20 Date: Wed, 8 Apr 2026 21:30:25 +0530 Subject: [PATCH 05/18] refactor: use write_job_sh helper in ED model --- models/ed/R/write.configs.ed.R | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/models/ed/R/write.configs.ed.R b/models/ed/R/write.configs.ed.R index a0da36fb94d..aa832fecdb1 100644 --- a/models/ed/R/write.configs.ed.R +++ b/models/ed/R/write.configs.ed.R @@ -122,8 +122,7 @@ write.config.ED2 <- function(trait.values, settings, run.id, defaults = settings jobsh <- write.config.jobsh.ED2(settings = settings, run.id = run.id) - writeLines(jobsh, con = file.path(settings$rundir, run.id, "job.sh")) - Sys.chmod(file.path(settings$rundir, run.id, "job.sh")) + PEcAn.utils::write_job_sh(settings$rundir, run.id, jobsh) ## Write ED2 config.xml file xml <- write.config.xml.ED2(defaults = defaults, settings = settings, trait.values = trait.values) From 44e7c8d8a25e645f3d11b8f5b78ca080e19696be Mon Sep 17 00:00:00 2001 From: krupashah20 Date: Wed, 8 Apr 2026 21:32:34 +0530 Subject: [PATCH 06/18] refactor: use write_job_sh helper in FATES model --- models/fates/R/write.configs.FATES.R | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/models/fates/R/write.configs.FATES.R b/models/fates/R/write.configs.FATES.R index 871b324fa45..f7634b8183d 100644 --- a/models/fates/R/write.configs.FATES.R +++ b/models/fates/R/write.configs.FATES.R @@ -189,8 +189,7 @@ write.config.FATES <- function(defaults, trait.values, settings, run.id){ # jobsh <- gsub('@SITE_MET@', settings$run$inputs$met$path, jobsh) ## FOR FIRST STEP, CAN USE DEFAULT - writeLines(jobsh, con=file.path(settings$rundir, run.id, "job.sh")) - Sys.chmod(file.path(settings$rundir, run.id, "job.sh")) + PEcAn.utils::write_job_sh(settings$rundir, run.id, jobsh) # # ## Write PARAMETER file From ca2afbabf231f67af96c6b6721d94d57db47acbb Mon Sep 17 00:00:00 2001 From: krupashah20 Date: Wed, 8 Apr 2026 21:36:35 +0530 Subject: [PATCH 07/18] refactor: use write_job_sh helper in GDAY model --- models/gday/R/write.config.GDAY.R | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/models/gday/R/write.config.GDAY.R b/models/gday/R/write.config.GDAY.R index cd7267327c0..f1cac9f3773 100644 --- a/models/gday/R/write.config.GDAY.R +++ b/models/gday/R/write.config.GDAY.R @@ -95,6 +95,5 @@ write.config.GDAY <- function(defaults, trait.values, settings, run.id) { jobsh <- gsub("@BINARY@", settings$model$binary, jobsh) - writeLines(jobsh, con = file.path(settings$rundir, run.id, "job.sh")) - Sys.chmod(file.path(settings$rundir, run.id, "job.sh")) + PEcAn.utils::write_job_sh(settings$rundir, run.id, jobsh) } # write.config.GDAY From 0af77162b52afd6ed68e5911fb0b2888d13c6e2d Mon Sep 17 00:00:00 2001 From: krupashah20 Date: Wed, 8 Apr 2026 21:39:01 +0530 Subject: [PATCH 08/18] refactor: use write_job_sh helper in JULES model --- models/jules/R/write.config.JULES.R | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/models/jules/R/write.config.JULES.R b/models/jules/R/write.config.JULES.R index 5add178de66..6200267f8d5 100644 --- a/models/jules/R/write.config.JULES.R +++ b/models/jules/R/write.config.JULES.R @@ -64,8 +64,7 @@ write.config.JULES <- function(defaults, trait.values, settings, run.id) { jobsh <- gsub("@RUNDIR@", rundir, jobsh) jobsh <- gsub("@RUNID@", run.id, jobsh) jobsh <- gsub("@BINARY@", settings$model$binary, jobsh) - writeLines(jobsh, con = file.path(local.rundir, "job.sh")) - Sys.chmod(file.path(local.rundir, "job.sh")) + PEcAn.utils::write_job_sh(local.rundir, run.id, jobsh) #----------------------------------------------------------------------- ### Copy templated NAMELIST files to local rundir From 19d8e0c158516567313c16000a58096652c8b902 Mon Sep 17 00:00:00 2001 From: krupashah20 Date: Wed, 8 Apr 2026 21:41:01 +0530 Subject: [PATCH 09/18] refactor: use write_job_sh helper in LDNDC model --- models/ldndc/R/write.config.LDNDC.R | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/models/ldndc/R/write.config.LDNDC.R b/models/ldndc/R/write.config.LDNDC.R index 6f13bd29ae2..5285268c1f0 100644 --- a/models/ldndc/R/write.config.LDNDC.R +++ b/models/ldndc/R/write.config.LDNDC.R @@ -133,8 +133,7 @@ write.config.LDNDC <- function(defaults, trait.values, settings, run.id) { jobsh <- gsub("@DELETE.RAW@", settings$model$delete.raw, jobsh) # Write job.sh file to rundir - writeLines(jobsh, con = file.path(settings$rundir, run.id, "job.sh")) - Sys.chmod(file.path(rundir, "job.sh")) # Permissions + PEcAn.utils::write_job_sh(settings$rundir, run.id, jobsh) From 239de3088d296e0125adae21df253b2baca8c62d Mon Sep 17 00:00:00 2001 From: krupashah20 Date: Wed, 8 Apr 2026 21:43:22 +0530 Subject: [PATCH 10/18] refactor: use write_job_sh helper in LINKAGES model --- models/linkages/R/write.config.LINKAGES.R | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/models/linkages/R/write.config.LINKAGES.R b/models/linkages/R/write.config.LINKAGES.R index 43c300a33ca..0b710ac601b 100644 --- a/models/linkages/R/write.config.LINKAGES.R +++ b/models/linkages/R/write.config.LINKAGES.R @@ -295,6 +295,5 @@ write.config.LINKAGES <- function(defaults = NULL, trait.values, settings, run.i pft_names <- unlist(sapply(settings$pfts, `[[`, "name")) pft_names <- paste0("pft_names = c('", paste(pft_names, collapse = "','"), "')") jobsh <- gsub("@PFT_NAMES@", pft_names, jobsh) - writeLines(jobsh, con = file.path(settings$rundir, run.id, "job.sh")) - Sys.chmod(file.path(settings$rundir, run.id, "job.sh")) + PEcAn.utils::write_job_sh(settings$rundir, run.id, jobsh) } # write.config.LINKAGES From 98266a608a95ab4302f8260385657e91ea6c3ef8 Mon Sep 17 00:00:00 2001 From: krupashah20 Date: Wed, 8 Apr 2026 21:45:03 +0530 Subject: [PATCH 11/18] refactor: use write_job_sh helper in LPJGUESS model --- models/lpjguess/R/write.config.LPJGUESS.R | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/models/lpjguess/R/write.config.LPJGUESS.R b/models/lpjguess/R/write.config.LPJGUESS.R index 9786deeafcd..be8cb2f8a6c 100644 --- a/models/lpjguess/R/write.config.LPJGUESS.R +++ b/models/lpjguess/R/write.config.LPJGUESS.R @@ -71,8 +71,7 @@ write.config.LPJGUESS <- function(defaults, trait.values, settings, run.id, rest jobsh <- gsub("@BINARY@", settings$model$binary, jobsh) jobsh <- gsub("@INSFILE@", settings$model$insfile, jobsh) - writeLines(jobsh, con = file.path(settings$rundir, run.id, "job.sh")) - Sys.chmod(file.path(settings$rundir, run.id, "job.sh")) + PEcAn.utils::write_job_sh(settings$rundir, run.id, jobsh) } # write.config.LPJGUESS # ==================================================================================================# From a2c2b1bff29530090afc76db933fc2b3e58436b9 Mon Sep 17 00:00:00 2001 From: krupashah20 Date: Wed, 8 Apr 2026 21:47:29 +0530 Subject: [PATCH 12/18] refactor: use write_job_sh helper in MAAT model --- models/maat/R/write.config.MAAT.R | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/models/maat/R/write.config.MAAT.R b/models/maat/R/write.config.MAAT.R index 825cefe061f..b632329862f 100644 --- a/models/maat/R/write.config.MAAT.R +++ b/models/maat/R/write.config.MAAT.R @@ -243,8 +243,7 @@ write.config.MAAT <- function(defaults = NULL, trait.values, settings, run.id) { } #End if/else # Write the job.sh script - writeLines(jobsh, con = file.path(settings$rundir, run.id, "job.sh")) - Sys.chmod(file.path(settings$rundir, run.id, "job.sh")) + PEcAn.utils::write_job_sh(settings$rundir, run.id, jobsh) } # write.config.MAAT ##-------------------------------------------------------------------------------------------------# From c30719ac7c2428103a3273cb7aa06b0b49bace73 Mon Sep 17 00:00:00 2001 From: krupashah20 Date: Wed, 8 Apr 2026 21:49:13 +0530 Subject: [PATCH 13/18] refactor: use write_job_sh helper in MAESPA model --- models/maespa/R/write.config.MAESPA.R | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/models/maespa/R/write.config.MAESPA.R b/models/maespa/R/write.config.MAESPA.R index 6adfd849eea..aca82158939 100755 --- a/models/maespa/R/write.config.MAESPA.R +++ b/models/maespa/R/write.config.MAESPA.R @@ -144,6 +144,5 @@ write.config.MAESPA <- function(defaults, trait.values, settings, run.id) { jobsh <- gsub("@BINARY@", settings$model$binary, jobsh) - writeLines(jobsh, con = file.path(settings$rundir, run.id, "job.sh")) - Sys.chmod(file.path(settings$rundir, run.id, "job.sh")) + PEcAn.utils::write_job_sh(settings$rundir, run.id, jobsh) } # write.config.MAESPA From 831e3d722643a2d728dc6cff1a6ef64fab1d82e6 Mon Sep 17 00:00:00 2001 From: krupashah20 Date: Wed, 8 Apr 2026 21:51:13 +0530 Subject: [PATCH 14/18] refactor: use write_job_sh helper in PEPRMT model --- models/peprmt/R/write.configs.peprmt.R | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/models/peprmt/R/write.configs.peprmt.R b/models/peprmt/R/write.configs.peprmt.R index b6f0a8ff2d6..519bbdf7f04 100644 --- a/models/peprmt/R/write.configs.peprmt.R +++ b/models/peprmt/R/write.configs.peprmt.R @@ -105,6 +105,5 @@ write.config.PEPRMT <- function(defaults, trait.values, settings, run.id) { dplyr::filter(.data$site == settings$run$site$id) utils::write.csv(run_data, file.path(rundir, "run_data.csv"), row.names = FALSE) - writeLines(jobsh, con = file.path(rundir, "job.sh")) - Sys.chmod(file.path(rundir, "job.sh")) + PEcAn.utils::write_job_sh(rundir, run.id, jobsh) } # write.config.PEPRMT From a3daaf582f41d2cf789d80404c9a48c0954873d7 Mon Sep 17 00:00:00 2001 From: krupashah20 Date: Wed, 8 Apr 2026 21:58:06 +0530 Subject: [PATCH 15/18] refactor: use write_job_sh helper in PRELES model --- models/preles/R/write.config.PRELES.R | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/models/preles/R/write.config.PRELES.R b/models/preles/R/write.config.PRELES.R index 42e8433c241..8a009be52a7 100644 --- a/models/preles/R/write.config.PRELES.R +++ b/models/preles/R/write.config.PRELES.R @@ -34,6 +34,5 @@ write.config.PRELES <- function(defaults, trait.values, settings, run.id) { "'",settings$run$end.date,"') ", '" | R --vanilla' ) - writeLines(jobsh, con = file.path(settings$rundir, run.id, "job.sh")) - Sys.chmod(file.path(settings$rundir, run.id, "job.sh")) + PEcAn.utils::write_job_sh(settings$rundir, run.id, jobsh) } # write.config.PRELES From 5c3afdd9184c6b20db7b3ec9e737d40c8622cdcc Mon Sep 17 00:00:00 2001 From: krupashah20 Date: Thu, 9 Apr 2026 08:58:30 +0530 Subject: [PATCH 16/18] refactor: use write_job_sh helper in STICS model --- models/stics/R/write.config.STICS.R | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/models/stics/R/write.config.STICS.R b/models/stics/R/write.config.STICS.R index a25063193cb..4f13a8fccd9 100644 --- a/models/stics/R/write.config.STICS.R +++ b/models/stics/R/write.config.STICS.R @@ -919,8 +919,7 @@ write.config.STICS <- function(defaults, trait.values, settings, run.id) { jobsh <- gsub("@MODFILE@", paste0("mod_s", basename(usmdirs[1]), ".sti"), jobsh) jobsh <- gsub("@STICSEXE@", stics_exe, jobsh) - writeLines(jobsh, con = file.path(settings$rundir, run.id, "job.sh")) - Sys.chmod(file.path(settings$rundir, run.id, "job.sh")) + PEcAn.utils::write_job_sh(settings$rundir, run.id, jobsh) } # write.config.STICS From e149556a65daec917cdeac869da0e97541daf819 Mon Sep 17 00:00:00 2001 From: krupashah20 Date: Thu, 9 Apr 2026 09:00:36 +0530 Subject: [PATCH 17/18] refactor: use write_job_sh helper in ED2 restart --- models/ed/R/write_restart.ED2.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/ed/R/write_restart.ED2.R b/models/ed/R/write_restart.ED2.R index 3a3611ad607..3492a8d8b3b 100644 --- a/models/ed/R/write_restart.ED2.R +++ b/models/ed/R/write_restart.ED2.R @@ -268,7 +268,7 @@ write_restart.ED2 <- function(outdir, runid, start.time, stop.time, mod2cf_string <- gsub(begin_from, begin_to, mod2cf_string) # e.g. change from (...'1961/01/01', '1963/01/01'...) to (...'1962/01/01', '1963/01/01'...) jobsh[mod2cf_line] <- mod2cf_string - writeLines(jobsh, file.path(rundir, runid, "job.sh")) + PEcAn.utils::write_job_sh(rundir, runid, jobsh) PEcAn.logger::logger.info("Finished --", runid) From 3a55657dd2637c94f1fa840ee288a956cf1b93f4 Mon Sep 17 00:00:00 2001 From: krupashah20 Date: Thu, 9 Apr 2026 09:16:22 +0530 Subject: [PATCH 18/18] test: add unit tests for write_job_sh helper function --- base/utils/tests/testthat/test.write_job_sh.R | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 base/utils/tests/testthat/test.write_job_sh.R diff --git a/base/utils/tests/testthat/test.write_job_sh.R b/base/utils/tests/testthat/test.write_job_sh.R new file mode 100644 index 00000000000..d979089e499 --- /dev/null +++ b/base/utils/tests/testthat/test.write_job_sh.R @@ -0,0 +1,45 @@ +test_that("write_job_sh creates job.sh file", { + # Create a temporary directory for testing + tmpdir <- tempfile() + dir.create(tmpdir) + run.id <- "test_run_001" + dir.create(file.path(tmpdir, run.id)) + + # Define simple job script content + jobsh <- c( + "#!/bin/bash", + "echo 'running model'" + ) + + # Call the helper function + result <- write_job_sh(tmpdir, run.id, jobsh) + + # Test 1 - file exists + expect_true(file.exists(file.path(tmpdir, run.id, "job.sh"))) + + # Test 2 - file content is correct + written <- readLines(file.path(tmpdir, run.id, "job.sh")) + expect_equal(written, jobsh) + + # Test 3 - function returns the path invisibly + expect_equal(result, file.path(tmpdir, run.id, "job.sh")) + + # Cleanup + unlink(tmpdir, recursive = TRUE) +}) + +test_that("write_job_sh chmod parameter works", { + tmpdir <- tempfile() + dir.create(tmpdir) + run.id <- "test_run_002" + dir.create(file.path(tmpdir, run.id)) + + jobsh <- c("#!/bin/bash", "./model") + + # Test with chmod = FALSE + write_job_sh(tmpdir, run.id, jobsh, chmod = FALSE) + expect_true(file.exists(file.path(tmpdir, run.id, "job.sh"))) + + # Cleanup + unlink(tmpdir, recursive = TRUE) +}) \ No newline at end of file