From bf6e3e6acb12929b34afb9d85bc3ee839c8282a3 Mon Sep 17 00:00:00 2001 From: Todd Zurlinden Date: Tue, 18 Nov 2025 10:08:26 -0500 Subject: [PATCH 01/12] Initial commit for bugfix on tmp files not recompiling when changed locally From ef38938ab58293b2fbb426638454a66822e02ac8 Mon Sep 17 00:00:00 2001 From: Todd Zurlinden Date: Wed, 3 Dec 2025 14:30:10 -0500 Subject: [PATCH 02/12] Use soruce_file as common source for compilation across the different methods for writing files. This ensures the hash is always compared to the user-defined .model file while compilation occurs on a common source file --- tests/testthat/test-writeTemp.R | 185 ++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 tests/testthat/test-writeTemp.R diff --git a/tests/testthat/test-writeTemp.R b/tests/testthat/test-writeTemp.R new file mode 100644 index 0000000..2bfba78 --- /dev/null +++ b/tests/testthat/test-writeTemp.R @@ -0,0 +1,185 @@ +# Comprehensive tests for writeTemp bug fix covering two key scenarios: +# Scenario 1: Local .model file with writeTemp=TRUE - change detection and recompilation +# Scenario 2: mString models - change detection when temp file is modified + +testthat::test_that("writeTemp=TRUE with local file: change detection and recompilation", { + # Create a model file outside of tempdir to properly test writeTemp behavior + test_dir <- file.path(dirname(tempdir()), "test_mcsim_writeTemp") + dir.create(test_dir, showWarnings = FALSE, recursive = TRUE) + original_model_path <- file.path(test_dir, "test_model.model") + + # Create a simple test model + model_content <- c( + "States = {Q_central};", + "Inputs = {Q_input};", + "Outputs = {Q_out};", + "", + "Initialize {", + " Q_central = 0;", + "}", + "", + "Dynamics {", + " Q_out = Q_central;", + " dt(Q_central) = Q_input - 0.1 * Q_central;", + "}", + "", + "End." + ) + writeLines(model_content, original_model_path) + + # Test 1: Create model with writeTemp=TRUE using local file (Scenario 1) + model <- createModel(mName = file.path(test_dir, "test_model"), writeTemp = TRUE) + + # Verify paths are set correctly + testthat::expect_true(file.exists(model$paths$model_file)) + testthat::expect_equal(model$paths$model_file, original_model_path) + testthat::expect_true(grepl(tempdir(), model$paths$source_file)) + + # Hash file should be alongside model file, not in temp directory + expected_hash_file <- file.path(test_dir, "test_model_model.md5") + testthat::expect_equal(model$paths$hash_file, expected_hash_file) + + # Test 2: Load model - should create hash file + testthat::expect_false(file.exists(model$paths$hash_file)) + model$loadModel() + testthat::expect_true(file.exists(model$paths$hash_file)) + + # Test 3: Modify original file + modified_content <- c( + "States = {y};", + "y0 = 0;", + "m = 0.2;", # Changed from 0.1 to 0.2 + "Initialize {", + " y = y0;", + "}", + "Dynamics {", + " dt(y) = m;", + "}", + "End." + ) + writeLines(modified_content, original_model_path) + + # Test 4: Load model again - should detect change and recompile (Scenario 1 verification) + # First, get the current hash + old_hash <- readLines(model$paths$hash_file, n = 1) + + # Load model again - this should: + # 1. Detect model_file has changed (via hash comparison) + # 2. Update source_file (working copy) from model_file + # 3. Recompile the updated source_file + # 4. Create new hash from the compiled source_file + model$loadModel() + + # Verify hash was updated (indicates recompilation occurred) + new_hash <- readLines(model$paths$hash_file, n = 1) + testthat::expect_false(old_hash == new_hash) + + # Verify working copy was updated with new content from model_file + working_content <- readLines(model$paths$source_file) + testthat::expect_true("m = 0.2;" %in% working_content) + + # Cleanup + model$cleanup(deleteModel = TRUE) + if (file.exists(model$paths$hash_file)) { + file.remove(model$paths$hash_file) + } + if (file.exists(original_model_path)) { + file.remove(original_model_path) + } + # Clean up test directory + unlink(test_dir, recursive = TRUE) +}) + +testthat::test_that("writeTemp=FALSE behavior unchanged", { + # Create a temporary model file to test with + temp_dir <- tempdir() + model_path <- file.path(temp_dir, "test_model_false.model") + + # Create a simple test model using intro.Rmd nomenclature + model_content <- c( + "States = {y};", + "y0 = 0;", + "m = 0.1;", + "Initialize {", + " y = y0;", + "}", + "Dynamics {", + " dt(y) = m;", + "}", + "End." + ) + writeLines(model_content, model_path) + + # Test 1: Create model with writeTemp=FALSE + model <- createModel(mName = file.path(temp_dir, "test_model_false"), writeTemp = FALSE) + + # Verify source_file and model_file are the same + testthat::expect_equal(model$paths$source_file, model$paths$model_file) + testthat::expect_equal(model$paths$model_file, model_path) + + # Hash file should be in same directory as model file + expected_hash_file <- file.path(temp_dir, "test_model_false_model.md5") + testthat::expect_equal(model$paths$hash_file, expected_hash_file) + + # Test 2: Load model - should work as before + model$loadModel() + testthat::expect_true(file.exists(model$paths$hash_file)) + + # Cleanup + model$cleanup(deleteModel = TRUE) + if (file.exists(model$paths$hash_file)) { + file.remove(model$paths$hash_file) + } +}) + +testthat::test_that("mString behavior and change detection", { + # Test model specification as string using intro.Rmd nomenclature + model_string <- paste(c( + "States = {y};", + "y0 = 0;", + "m = 0.1;", + "Initialize {", + " y = y0;", + "}", + "Dynamics {", + " dt(y) = m;", + "}", + "End." + ), collapse = "\n") + + # Test 1: Create model with mString (writeTemp must be TRUE) + model <- createModel(mString = model_string, writeTemp = TRUE) + + # For mString, source_file and model_file should be the same (both temp files) + testthat::expect_equal(model$paths$source_file, model$paths$model_file) + testthat::expect_true(grepl(tempdir(), model$paths$model_file)) + + # Hash file should be with the temp file + testthat::expect_true(grepl(tempdir(), model$paths$hash_file)) + + # Test 2: Load model and capture initial hash + model$loadModel() + testthat::expect_true(file.exists(model$paths$hash_file)) + original_hash <- readLines(model$paths$hash_file, n = 1) + + # Test 3: Simulate change to mString-created temp file (Scenario 2 verification) + # Since mString creates a temp file, we test change detection by modifying that temp file + # This verifies hash-based change detection works for mString models + temp_file_content <- readLines(model$paths$model_file) + modified_content <- gsub("m = 0.1;", "m = 0.2;", temp_file_content) + writeLines(modified_content, model$paths$model_file) + + # Reload model - should detect change and recompile + model$loadModel() + new_hash <- readLines(model$paths$hash_file, n = 1) + testthat::expect_false(original_hash == new_hash) + + # Test 4: Verify mString + writeTemp=FALSE still throws error + testthat::expect_error( + createModel(mString = model_string, writeTemp = FALSE), + "The value of writeTemp must be TRUE when creating a Model object using a model specification string" + ) + + # Cleanup + model$cleanup(deleteModel = TRUE) +}) \ No newline at end of file From 027817defd00a3ddae219769d38d4b232d59581f Mon Sep 17 00:00:00 2001 From: Todd Zurlinden Date: Wed, 3 Dec 2025 14:31:44 -0500 Subject: [PATCH 03/12] Edited R files for bugfix --- R/MCSim_model.R | 54 ++++++++++++++++++++++++++++++++++++++++------ R/compileModel.R | 3 ++- R/fileHasChanged.R | 2 +- 3 files changed, 50 insertions(+), 9 deletions(-) diff --git a/R/MCSim_model.R b/R/MCSim_model.R index 851fd57..403af8d 100644 --- a/R/MCSim_model.R +++ b/R/MCSim_model.R @@ -47,26 +47,41 @@ Model <- setRefClass("Model", if (length(mName) > 0 & length(mString) > 0) { stop("Cannot create a Model object using both a file name (mName) and a model specification string (mString). Provide only one of these arguments.") } + # Track user's model file for proper change detection + model_file_path <- NULL if (length(mString) > 0) { if (writeTemp == FALSE) { stop("The value of writeTemp must be TRUE when creating a Model object using a model specification string (mstring).") } file <- tempfile(pattern = "mcsimmod_", fileext = ".model") writeLines(mString, file) + # For mString, model and working files are the same + model_file_path <- file } else { if (writeTemp == TRUE) { - source_file <- normalizePath(paste0(mName, ".model")) + model_file <- normalizePath(paste0(mName, ".model")) + model_file_path <- model_file # Store user's model file path temp_directory <- tempdir() - file <- file.path(temp_directory, basename(source_file)) - file_copied <- file.copy(from = source_file, to = file) + file <- file.path(temp_directory, basename(model_file)) + file_copied <- file.copy(from = model_file, to = file) } else { file <- normalizePath(paste0(mName, ".model")) + model_file_path <- file # writeTemp=FALSE: model and working are same } } mList <- .fixPath(file) mName <<- mList$mName mPath <- mList$mPath + # Determine hash file location based on model file + if (writeTemp == TRUE && length(mString) == 0) { + # For writeTemp=TRUE with mName, store hash alongside user's model file + model_mList <- .fixPath(model_file_path) + hash_file_path <- file.path(model_mList$mPath, paste0(model_mList$mName, "_model.md5")) + } else { + # For writeTemp=FALSE or mString cases, store hash with working files + hash_file_path <- file.path(mPath, paste0(mName, "_model.md5")) + } paths <<- list( dll_name = paste0(mName, "_model"), @@ -74,17 +89,35 @@ Model <- setRefClass("Model", o_file = file.path(mPath, paste0(mName, "_model.o")), dll_file = file.path(mPath, paste0(mName, "_model", .Platform$dynlib.ext)), inits_file = file.path(mPath, paste0(mName, "_model_inits.R")), - model_file = file.path(mPath, paste0(mName, ".model")), - hash_file = file.path(mPath, paste0(mName, "_model.md5")) + source_file = file.path(mPath, paste0(mName, ".model")), + model_file = model_file_path, + hash_file = hash_file_path ) }, loadModel = function(force = FALSE) { "Translate (if necessary) the model specification text to C, compile (if necessary) the resulting C file to create a dynamic link library (DLL) file (on Windows) or a shared object (SO) file (on Unix), and then load all essential information about the Model object into memory (for use in the current R session)." hash_exists <- file.exists(paths$hash_file) if (hash_exists) { + # Check changes against user's model file, not working copy hash_has_changed <- .fileHasChanged(paths$model_file, paths$hash_file) + + # If model file changed and we're using temp directory, update working copy + if (hash_has_changed && writeTemp == TRUE && length(mString) == 0) { + file_copied <- file.copy(from = paths$model_file, to = paths$source_file, overwrite = TRUE) + if (!file_copied) { + stop("Failed to update working file from model file: ", paths$model_file) + } + } } else { hash_has_changed <- TRUE + + # If no hash exists and we're using temp directory, ensure working copy is current + if (writeTemp == TRUE && length(mString) == 0 && !identical(paths$model_file, paths$source_file)) { + file_copied <- file.copy(from = paths$model_file, to = paths$source_file, overwrite = TRUE) + if (!file_copied) { + stop("Failed to update working file from model file: ", paths$model_file) + } + } } # Conditions for compiling a model: @@ -97,8 +130,15 @@ Model <- setRefClass("Model", # match the previously saved hash, indicating that the model # specification file has been changed since the last translation and # compiling. - if (!file.exists(paths$dll_file) | (force) | (!hash_exists) | (hash_exists & hash_has_changed)) { - compileModel(paths$model_file, paths$c_file, paths$dll_name, paths$dll_file, hash_file = paths$hash_file, verbose_output = verboseOutput) + if (!file.exists(paths$dll_file) | (force) | (!hash_exists) | (hash_has_changed)) { + # When writeTemp = TRUE and model file has changed, update the working copy + if (writeTemp && hash_has_changed && !identical(paths$model_file, paths$source_file)) { + file.copy(from = paths$model_file, to = paths$source_file, overwrite = TRUE) + } + + # Call compileModel - always compile and hash the working copy (source_file) + compileModel(paths$source_file, paths$c_file, paths$dll_name, paths$dll_file, + hash_file = paths$hash_file, verbose_output = verboseOutput) } # Load the compiled model (DLL). diff --git a/R/compileModel.R b/R/compileModel.R index ff6204a..2e9e993 100644 --- a/R/compileModel.R +++ b/R/compileModel.R @@ -113,7 +113,8 @@ compileModel <- function(model_file, c_file, dll_name, dll_file, hash_file = NUL # If hash file name was provided, create a hash (md5 sum) for the model file # and print a message about its location. if (!is.null(hash_file)) { - file_hash <- as.character(md5sum(model_file)) + # Always hash the model_file (the file that gets compiled) + file_hash <- as.character(tools::md5sum(model_file)) write(file_hash, file = hash_file) message( "Hash created and saved in the file ", normalizePath(hash_file), diff --git a/R/fileHasChanged.R b/R/fileHasChanged.R index fe93f0a..26d9810 100644 --- a/R/fileHasChanged.R +++ b/R/fileHasChanged.R @@ -5,7 +5,7 @@ .fileHasChanged <- function(model_file, hash_file) { # Calculate hash for current model file - current_hash <- as.character(md5sum(model_file)) + current_hash <- as.character(tools::md5sum(model_file)) # Read saved hash saved_hash <- readLines(hash_file, n = 1) From fa179c586ab8fb7845032ba153e4d70b9539e200 Mon Sep 17 00:00:00 2001 From: Todd Zurlinden Date: Wed, 3 Dec 2025 14:37:26 -0500 Subject: [PATCH 04/12] run styler --- R/MCSim_model.R | 13 ++++---- tests/testthat/test-writeTemp.R | 56 ++++++++++++++++----------------- 2 files changed, 35 insertions(+), 34 deletions(-) diff --git a/R/MCSim_model.R b/R/MCSim_model.R index d41b1df..c2b53da 100644 --- a/R/MCSim_model.R +++ b/R/MCSim_model.R @@ -60,13 +60,13 @@ Model <- setRefClass("Model", } else { if (writeTemp == TRUE) { model_file <- normalizePath(paste0(mName, ".model")) - model_file_path <- model_file # Store user's model file path + model_file_path <- model_file # Store user's model file path temp_directory <- tempdir() file <- file.path(temp_directory, basename(model_file)) file_copied <- file.copy(from = model_file, to = file) } else { file <- normalizePath(paste0(mName, ".model")) - model_file_path <- file # writeTemp=FALSE: model and working are same + model_file_path <- file # writeTemp=FALSE: model and working are same } } mList <- .fixPath(file) @@ -110,7 +110,7 @@ Model <- setRefClass("Model", } } else { hash_has_changed <- TRUE - + # If no hash exists and we're using temp directory, ensure working copy is current if (writeTemp == TRUE && length(mString) == 0 && !identical(paths$model_file, paths$source_file)) { file_copied <- file.copy(from = paths$model_file, to = paths$source_file, overwrite = TRUE) @@ -135,10 +135,11 @@ Model <- setRefClass("Model", if (writeTemp && hash_has_changed && !identical(paths$model_file, paths$source_file)) { file.copy(from = paths$model_file, to = paths$source_file, overwrite = TRUE) } - + # Call compileModel - always compile and hash the working copy (source_file) - compileModel(paths$source_file, paths$c_file, paths$dll_name, paths$dll_file, - hash_file = paths$hash_file, verbose_output = verboseOutput) + compileModel(paths$source_file, paths$c_file, paths$dll_name, paths$dll_file, + hash_file = paths$hash_file, verbose_output = verboseOutput + ) } # Load the compiled model (DLL). diff --git a/tests/testthat/test-writeTemp.R b/tests/testthat/test-writeTemp.R index 2bfba78..614a0e5 100644 --- a/tests/testthat/test-writeTemp.R +++ b/tests/testthat/test-writeTemp.R @@ -7,7 +7,7 @@ testthat::test_that("writeTemp=TRUE with local file: change detection and recomp test_dir <- file.path(dirname(tempdir()), "test_mcsim_writeTemp") dir.create(test_dir, showWarnings = FALSE, recursive = TRUE) original_model_path <- file.path(test_dir, "test_model.model") - + # Create a simple test model model_content <- c( "States = {Q_central};", @@ -26,29 +26,29 @@ testthat::test_that("writeTemp=TRUE with local file: change detection and recomp "End." ) writeLines(model_content, original_model_path) - + # Test 1: Create model with writeTemp=TRUE using local file (Scenario 1) model <- createModel(mName = file.path(test_dir, "test_model"), writeTemp = TRUE) - + # Verify paths are set correctly testthat::expect_true(file.exists(model$paths$model_file)) testthat::expect_equal(model$paths$model_file, original_model_path) testthat::expect_true(grepl(tempdir(), model$paths$source_file)) - + # Hash file should be alongside model file, not in temp directory expected_hash_file <- file.path(test_dir, "test_model_model.md5") testthat::expect_equal(model$paths$hash_file, expected_hash_file) - + # Test 2: Load model - should create hash file testthat::expect_false(file.exists(model$paths$hash_file)) model$loadModel() testthat::expect_true(file.exists(model$paths$hash_file)) - + # Test 3: Modify original file modified_content <- c( "States = {y};", "y0 = 0;", - "m = 0.2;", # Changed from 0.1 to 0.2 + "m = 0.2;", # Changed from 0.1 to 0.2 "Initialize {", " y = y0;", "}", @@ -58,26 +58,26 @@ testthat::test_that("writeTemp=TRUE with local file: change detection and recomp "End." ) writeLines(modified_content, original_model_path) - + # Test 4: Load model again - should detect change and recompile (Scenario 1 verification) # First, get the current hash old_hash <- readLines(model$paths$hash_file, n = 1) - + # Load model again - this should: # 1. Detect model_file has changed (via hash comparison) - # 2. Update source_file (working copy) from model_file + # 2. Update source_file (working copy) from model_file # 3. Recompile the updated source_file # 4. Create new hash from the compiled source_file model$loadModel() - + # Verify hash was updated (indicates recompilation occurred) new_hash <- readLines(model$paths$hash_file, n = 1) testthat::expect_false(old_hash == new_hash) - + # Verify working copy was updated with new content from model_file working_content <- readLines(model$paths$source_file) testthat::expect_true("m = 0.2;" %in% working_content) - + # Cleanup model$cleanup(deleteModel = TRUE) if (file.exists(model$paths$hash_file)) { @@ -94,7 +94,7 @@ testthat::test_that("writeTemp=FALSE behavior unchanged", { # Create a temporary model file to test with temp_dir <- tempdir() model_path <- file.path(temp_dir, "test_model_false.model") - + # Create a simple test model using intro.Rmd nomenclature model_content <- c( "States = {y};", @@ -109,22 +109,22 @@ testthat::test_that("writeTemp=FALSE behavior unchanged", { "End." ) writeLines(model_content, model_path) - + # Test 1: Create model with writeTemp=FALSE model <- createModel(mName = file.path(temp_dir, "test_model_false"), writeTemp = FALSE) - + # Verify source_file and model_file are the same testthat::expect_equal(model$paths$source_file, model$paths$model_file) testthat::expect_equal(model$paths$model_file, model_path) - + # Hash file should be in same directory as model file expected_hash_file <- file.path(temp_dir, "test_model_false_model.md5") testthat::expect_equal(model$paths$hash_file, expected_hash_file) - + # Test 2: Load model - should work as before model$loadModel() testthat::expect_true(file.exists(model$paths$hash_file)) - + # Cleanup model$cleanup(deleteModel = TRUE) if (file.exists(model$paths$hash_file)) { @@ -146,40 +146,40 @@ testthat::test_that("mString behavior and change detection", { "}", "End." ), collapse = "\n") - + # Test 1: Create model with mString (writeTemp must be TRUE) model <- createModel(mString = model_string, writeTemp = TRUE) - + # For mString, source_file and model_file should be the same (both temp files) testthat::expect_equal(model$paths$source_file, model$paths$model_file) testthat::expect_true(grepl(tempdir(), model$paths$model_file)) - + # Hash file should be with the temp file testthat::expect_true(grepl(tempdir(), model$paths$hash_file)) - + # Test 2: Load model and capture initial hash model$loadModel() testthat::expect_true(file.exists(model$paths$hash_file)) original_hash <- readLines(model$paths$hash_file, n = 1) - + # Test 3: Simulate change to mString-created temp file (Scenario 2 verification) # Since mString creates a temp file, we test change detection by modifying that temp file # This verifies hash-based change detection works for mString models temp_file_content <- readLines(model$paths$model_file) modified_content <- gsub("m = 0.1;", "m = 0.2;", temp_file_content) writeLines(modified_content, model$paths$model_file) - + # Reload model - should detect change and recompile model$loadModel() new_hash <- readLines(model$paths$hash_file, n = 1) testthat::expect_false(original_hash == new_hash) - + # Test 4: Verify mString + writeTemp=FALSE still throws error testthat::expect_error( createModel(mString = model_string, writeTemp = FALSE), "The value of writeTemp must be TRUE when creating a Model object using a model specification string" ) - + # Cleanup model$cleanup(deleteModel = TRUE) -}) \ No newline at end of file +}) From 2d54406279c1a32e0a4cda2d2783fa2b5f941e32 Mon Sep 17 00:00:00 2001 From: Todd Zurlinden Date: Wed, 3 Dec 2025 14:50:34 -0500 Subject: [PATCH 05/12] Edits to compile model to allow windows to read C file --- R/compileModel.R | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/R/compileModel.R b/R/compileModel.R index 383178a..e96304a 100644 --- a/R/compileModel.R +++ b/R/compileModel.R @@ -37,6 +37,11 @@ compileModel <- function(model_file, c_file, dll_name, dll_file, hash_file = NUL sink() close(text_conn) mod_output <- paste(mod_output, collapse = "\n") + + # Add a small delay on Windows to ensure files are fully written + if (.Platform$OS.type == "windows") { + Sys.sleep(0.1) + } # Save the translator output to a file. if (!verbose_output) { @@ -98,8 +103,17 @@ compileModel <- function(model_file, c_file, dll_name, dll_file, hash_file = NUL # Code to update C source file using model-specific names for objects. - # Read the original C source file. - lines <- readLines(c_file) + # Check if C file was created successfully + if (!file.exists(c_file)) { + stop("C file was not created: ", c_file) + } + + # Read the original C source file with error handling + tryCatch({ + lines <- readLines(c_file) + }, error = function(e) { + stop("Failed to read C file '", c_file, "': ", e$message) + }) # Find and replace C object names with model-specific names. item_to_replace <- c( @@ -120,7 +134,7 @@ compileModel <- function(model_file, c_file, dll_name, dll_file, hash_file = NUL "event", "root" ) - for (idx in seq(length(item_to_replace))) { + for (idx in seq_along(item_to_replace)) { lines <- gsub( paste0("\\b", item_to_replace[idx], "\\b"), paste0(item_to_replace[idx], "_", model_name), @@ -140,8 +154,17 @@ compileModel <- function(model_file, c_file, dll_name, dll_file, hash_file = NUL paste0(model_name, "_model_inits.R") ) - # Read the original inits R source file. - lines <- readLines(inits_file) + # Check if inits file was created successfully + if (!file.exists(inits_file)) { + stop("Inits R file was not created: ", inits_file) + } + + # Read the original inits R source file with error handling + tryCatch({ + lines <- readLines(inits_file) + }, error = function(e) { + stop("Failed to read inits R file '", inits_file, "': ", e$message) + }) # Find and replace R and C object names with model-specific names. item_to_replace <- c( @@ -151,7 +174,7 @@ compileModel <- function(model_file, c_file, dll_name, dll_file, hash_file = NUL "initStates", "initState" ) - for (idx in seq(length(item_to_replace))) { + for (idx in seq_along(item_to_replace)) { lines <- gsub( paste0("\\b", item_to_replace[idx], "\\b"), paste0(item_to_replace[idx], "_", model_name), From d6ce0a5031ec0615dc687ce5792f3c9898097f38 Mon Sep 17 00:00:00 2001 From: Todd Zurlinden Date: Wed, 3 Dec 2025 14:53:33 -0500 Subject: [PATCH 06/12] run styler --- R/compileModel.R | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/R/compileModel.R b/R/compileModel.R index e96304a..6fe92fe 100644 --- a/R/compileModel.R +++ b/R/compileModel.R @@ -37,7 +37,7 @@ compileModel <- function(model_file, c_file, dll_name, dll_file, hash_file = NUL sink() close(text_conn) mod_output <- paste(mod_output, collapse = "\n") - + # Add a small delay on Windows to ensure files are fully written if (.Platform$OS.type == "windows") { Sys.sleep(0.1) @@ -109,11 +109,14 @@ compileModel <- function(model_file, c_file, dll_name, dll_file, hash_file = NUL } # Read the original C source file with error handling - tryCatch({ - lines <- readLines(c_file) - }, error = function(e) { - stop("Failed to read C file '", c_file, "': ", e$message) - }) + tryCatch( + { + lines <- readLines(c_file) + }, + error = function(e) { + stop("Failed to read C file '", c_file, "': ", e$message) + } + ) # Find and replace C object names with model-specific names. item_to_replace <- c( @@ -160,11 +163,14 @@ compileModel <- function(model_file, c_file, dll_name, dll_file, hash_file = NUL } # Read the original inits R source file with error handling - tryCatch({ - lines <- readLines(inits_file) - }, error = function(e) { - stop("Failed to read inits R file '", inits_file, "': ", e$message) - }) + tryCatch( + { + lines <- readLines(inits_file) + }, + error = function(e) { + stop("Failed to read inits R file '", inits_file, "': ", e$message) + } + ) # Find and replace R and C object names with model-specific names. item_to_replace <- c( From cd8cc8ad9b216a56512c7fa4a8b90bd0eae7694e Mon Sep 17 00:00:00 2001 From: Todd Zurlinden Date: Wed, 3 Dec 2025 15:02:19 -0500 Subject: [PATCH 07/12] Modify windows path builds --- R/compileModel.R | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/R/compileModel.R b/R/compileModel.R index 6fe92fe..4148035 100644 --- a/R/compileModel.R +++ b/R/compileModel.R @@ -15,6 +15,16 @@ #' @useDynLib MCSimMod, .registration=TRUE #' @export compileModel <- function(model_file, c_file, dll_name, dll_file, hash_file = NULL, verbose_output = FALSE) { + # Normalize paths for Windows compatibility + if (.Platform$OS.type == "windows") { + model_file <- normalizePath(model_file, winslash = "/", mustWork = TRUE) + # For c_file, normalize the directory and rebuild the path + c_dir <- dirname(c_file) + if (dir.exists(c_dir)) { + c_file <- file.path(normalizePath(c_dir, winslash = "/"), basename(c_file)) + } + } + # Unload DLL if it has been loaded. mList <- .fixPath(model_file) model_name <- mList$mName @@ -33,7 +43,16 @@ compileModel <- function(model_file, c_file, dll_name, dll_file, hash_file = NUL # specification file (ending with ".model"). Write translator output to the # text connection. sink(text_conn) - .C("c_mod", model_file, c_file) + tryCatch( + { + .C("c_mod", model_file, c_file) + }, + error = function(e) { + sink() + close(text_conn) + stop("MCSim translator failed: ", e$message) + } + ) sink() close(text_conn) mod_output <- paste(mod_output, collapse = "\n") @@ -105,7 +124,16 @@ compileModel <- function(model_file, c_file, dll_name, dll_file, hash_file = NUL # Check if C file was created successfully if (!file.exists(c_file)) { - stop("C file was not created: ", c_file) + # Provide diagnostic information for debugging + inits_file <- sub("\\.c$", "_inits.R", c_file) + diagnostics <- paste0( + "C file was not created: ", c_file, "\n", + "Model file: ", model_file, " (exists: ", file.exists(model_file), ")\n", + "Expected inits file: ", inits_file, " (exists: ", file.exists(inits_file), ")\n", + "Working directory: ", getwd(), "\n", + "MCSim output:\n", mod_output + ) + stop(diagnostics) } # Read the original C source file with error handling From eaebecc371dc927782b26cac8e9a69ad4a37b876 Mon Sep 17 00:00:00 2001 From: Todd Zurlinden Date: Wed, 3 Dec 2025 15:11:16 -0500 Subject: [PATCH 08/12] Fix file writing on Windows --- R/MCSim_model.R | 17 ++++++++++++++++- R/compileModel.R | 18 +++++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/R/MCSim_model.R b/R/MCSim_model.R index c2b53da..70e5230 100644 --- a/R/MCSim_model.R +++ b/R/MCSim_model.R @@ -54,7 +54,22 @@ Model <- setRefClass("Model", stop("The value of writeTemp must be TRUE when creating a Model object using a model specification string (mstring).") } file <- tempfile(pattern = "mcsimmod_", fileext = ".model") - writeLines(mString, file) + + # Write model string to file with error handling and ensure it's flushed + tryCatch({ + writeLines(mString, file) + # On Windows, ensure file is flushed to disk + if (.Platform$OS.type == "windows") { + Sys.sleep(0.01) # Small delay to ensure write completes + } + # Verify file was written correctly + if (!file.exists(file) || file.size(file) == 0) { + stop("Failed to write model file or file is empty: ", file) + } + }, error = function(e) { + stop("Failed to create model file from mString: ", e$message) + }) + # For mString, model and working files are the same model_file_path <- file } else { diff --git a/R/compileModel.R b/R/compileModel.R index 4148035..914916f 100644 --- a/R/compileModel.R +++ b/R/compileModel.R @@ -126,11 +126,27 @@ compileModel <- function(model_file, c_file, dll_name, dll_file, hash_file = NUL if (!file.exists(c_file)) { # Provide diagnostic information for debugging inits_file <- sub("\\.c$", "_inits.R", c_file) + + # Try to read model file content for diagnosis + model_content <- "Could not read model file" + if (file.exists(model_file)) { + tryCatch({ + model_content <- paste(readLines(model_file), collapse = "\n") + if (nchar(model_content) == 0) { + model_content <- "[Model file is empty]" + } + }, error = function(e) { + model_content <- paste("Error reading model file:", e$message) + }) + } + diagnostics <- paste0( "C file was not created: ", c_file, "\n", - "Model file: ", model_file, " (exists: ", file.exists(model_file), ")\n", + "Model file: ", model_file, " (exists: ", file.exists(model_file), + ", size: ", ifelse(file.exists(model_file), file.size(model_file), "N/A"), " bytes)\n", "Expected inits file: ", inits_file, " (exists: ", file.exists(inits_file), ")\n", "Working directory: ", getwd(), "\n", + "Model file content:\n", model_content, "\n", "MCSim output:\n", mod_output ) stop(diagnostics) From 3db361404cf462de49ebbeccdac740ba23c700c9 Mon Sep 17 00:00:00 2001 From: Todd Zurlinden Date: Wed, 3 Dec 2025 15:12:06 -0500 Subject: [PATCH 09/12] run styler --- R/MCSim_model.R | 31 +++++++++++++++++-------------- R/compileModel.R | 23 +++++++++++++---------- 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/R/MCSim_model.R b/R/MCSim_model.R index 70e5230..ae9bf6d 100644 --- a/R/MCSim_model.R +++ b/R/MCSim_model.R @@ -54,22 +54,25 @@ Model <- setRefClass("Model", stop("The value of writeTemp must be TRUE when creating a Model object using a model specification string (mstring).") } file <- tempfile(pattern = "mcsimmod_", fileext = ".model") - + # Write model string to file with error handling and ensure it's flushed - tryCatch({ - writeLines(mString, file) - # On Windows, ensure file is flushed to disk - if (.Platform$OS.type == "windows") { - Sys.sleep(0.01) # Small delay to ensure write completes - } - # Verify file was written correctly - if (!file.exists(file) || file.size(file) == 0) { - stop("Failed to write model file or file is empty: ", file) + tryCatch( + { + writeLines(mString, file) + # On Windows, ensure file is flushed to disk + if (.Platform$OS.type == "windows") { + Sys.sleep(0.01) # Small delay to ensure write completes + } + # Verify file was written correctly + if (!file.exists(file) || file.size(file) == 0) { + stop("Failed to write model file or file is empty: ", file) + } + }, + error = function(e) { + stop("Failed to create model file from mString: ", e$message) } - }, error = function(e) { - stop("Failed to create model file from mString: ", e$message) - }) - + ) + # For mString, model and working files are the same model_file_path <- file } else { diff --git a/R/compileModel.R b/R/compileModel.R index 914916f..88b70ee 100644 --- a/R/compileModel.R +++ b/R/compileModel.R @@ -126,23 +126,26 @@ compileModel <- function(model_file, c_file, dll_name, dll_file, hash_file = NUL if (!file.exists(c_file)) { # Provide diagnostic information for debugging inits_file <- sub("\\.c$", "_inits.R", c_file) - + # Try to read model file content for diagnosis model_content <- "Could not read model file" if (file.exists(model_file)) { - tryCatch({ - model_content <- paste(readLines(model_file), collapse = "\n") - if (nchar(model_content) == 0) { - model_content <- "[Model file is empty]" + tryCatch( + { + model_content <- paste(readLines(model_file), collapse = "\n") + if (nchar(model_content) == 0) { + model_content <- "[Model file is empty]" + } + }, + error = function(e) { + model_content <- paste("Error reading model file:", e$message) } - }, error = function(e) { - model_content <- paste("Error reading model file:", e$message) - }) + ) } - + diagnostics <- paste0( "C file was not created: ", c_file, "\n", - "Model file: ", model_file, " (exists: ", file.exists(model_file), + "Model file: ", model_file, " (exists: ", file.exists(model_file), ", size: ", ifelse(file.exists(model_file), file.size(model_file), "N/A"), " bytes)\n", "Expected inits file: ", inits_file, " (exists: ", file.exists(inits_file), ")\n", "Working directory: ", getwd(), "\n", From cd4342140310c03e43fc4821ca1b391a581f9a06 Mon Sep 17 00:00:00 2001 From: Todd Zurlinden Date: Wed, 3 Dec 2025 15:43:47 -0500 Subject: [PATCH 10/12] Use normalizePath consistently to be compatible across platforms --- R/compileModel.R | 4 ++-- tests/testthat/test-compareHash.R | 2 +- tests/testthat/test-sim.R | 6 +++--- tests/testthat/test-writeTemp.R | 12 ++++++------ vignettes/events_demo.Rmd | 2 +- vignettes/inputs_demo.Rmd | 4 ++-- vignettes/model_specification.Rmd | 4 ++-- vignettes/newt_cool_demo.Rmd | 4 ++-- vignettes/pbpk_demo.Rmd | 4 ++-- vignettes/pk1_demo.Rmd | 2 +- vignettes/pred_prey_demo.Rmd | 4 ++-- 11 files changed, 24 insertions(+), 24 deletions(-) diff --git a/R/compileModel.R b/R/compileModel.R index 88b70ee..784dac8 100644 --- a/R/compileModel.R +++ b/R/compileModel.R @@ -17,11 +17,11 @@ compileModel <- function(model_file, c_file, dll_name, dll_file, hash_file = NULL, verbose_output = FALSE) { # Normalize paths for Windows compatibility if (.Platform$OS.type == "windows") { - model_file <- normalizePath(model_file, winslash = "/", mustWork = TRUE) + model_file <- normalizePath(model_file, mustWork = TRUE) # For c_file, normalize the directory and rebuild the path c_dir <- dirname(c_file) if (dir.exists(c_dir)) { - c_file <- file.path(normalizePath(c_dir, winslash = "/"), basename(c_file)) + c_file <- file.path(normalizePath(c_dir), basename(c_file)) } } diff --git a/tests/testthat/test-compareHash.R b/tests/testthat/test-compareHash.R index 84f5833..c154f3e 100644 --- a/tests/testthat/test-compareHash.R +++ b/tests/testthat/test-compareHash.R @@ -4,7 +4,7 @@ testthat::test_that("test_compareHash", { # Test to make sure changing the file returns a changed path dir.create(file.path(tempdir(), "testDir")) - mName <- tempfile(pattern = "mcsimmod_", tmpdir = file.path(tempdir(), "testDir")) + mName <- normalizePath(tempfile(pattern = "mcsimmod_", tmpdir = file.path(tempdir(), "testDir")), mustWork = FALSE) mString <- readLines(file.path(testthat::test_path(), "data", "exponential.model")) writeLines(mString, paste0(mName, ".model")) diff --git a/tests/testthat/test-sim.R b/tests/testthat/test-sim.R index e2549bd..d57dbff 100644 --- a/tests/testthat/test-sim.R +++ b/tests/testthat/test-sim.R @@ -21,7 +21,7 @@ testthat::test_that("Model$localModel", { }) testthat::test_that("Model$relativeModel", { - mName <- file.path(testthat::test_path(), "data", "exponential") + mName <- normalizePath(file.path(testthat::test_path(), "data", "exponential"), mustWork = FALSE) testthat::expect_true(file.exists(paste0(mName, ".model"))) model <- createModel(mName) @@ -45,8 +45,8 @@ testthat::test_that("Model$absoluteModel", { # Use absolute path of temp directory, # Test to make sure changing the file returns a changed path - dir.create(file.path(tempdir(), "testDir")) - mName <- tempfile(pattern = "mcsimmod_", tmpdir = file.path(tempdir(), "testDir")) + dir.create(file.path(tempdir(), "testDir"), showWarnings = FALSE) + mName <- normalizePath(tempfile(pattern = "mcsimmod_", tmpdir = file.path(tempdir(), "testDir")), mustWork = FALSE) mString <- readLines(file.path(testthat::test_path(), "data", "exponential.model")) writeLines(mString, paste0(mName, ".model")) diff --git a/tests/testthat/test-writeTemp.R b/tests/testthat/test-writeTemp.R index 614a0e5..a5be4c9 100644 --- a/tests/testthat/test-writeTemp.R +++ b/tests/testthat/test-writeTemp.R @@ -4,9 +4,9 @@ testthat::test_that("writeTemp=TRUE with local file: change detection and recompilation", { # Create a model file outside of tempdir to properly test writeTemp behavior - test_dir <- file.path(dirname(tempdir()), "test_mcsim_writeTemp") + test_dir <- normalizePath(file.path(dirname(tempdir()), "test_mcsim_writeTemp"), mustWork = FALSE) dir.create(test_dir, showWarnings = FALSE, recursive = TRUE) - original_model_path <- file.path(test_dir, "test_model.model") + original_model_path <- normalizePath(file.path(test_dir, "test_model.model"), mustWork = FALSE) # Create a simple test model model_content <- c( @@ -28,7 +28,7 @@ testthat::test_that("writeTemp=TRUE with local file: change detection and recomp writeLines(model_content, original_model_path) # Test 1: Create model with writeTemp=TRUE using local file (Scenario 1) - model <- createModel(mName = file.path(test_dir, "test_model"), writeTemp = TRUE) + model <- createModel(mName = normalizePath(file.path(test_dir, "test_model"), mustWork = FALSE), writeTemp = TRUE) # Verify paths are set correctly testthat::expect_true(file.exists(model$paths$model_file)) @@ -92,8 +92,8 @@ testthat::test_that("writeTemp=TRUE with local file: change detection and recomp testthat::test_that("writeTemp=FALSE behavior unchanged", { # Create a temporary model file to test with - temp_dir <- tempdir() - model_path <- file.path(temp_dir, "test_model_false.model") + temp_dir <- normalizePath(tempdir()) + model_path <- normalizePath(file.path(temp_dir, "test_model_false.model"), mustWork = FALSE) # Create a simple test model using intro.Rmd nomenclature model_content <- c( @@ -111,7 +111,7 @@ testthat::test_that("writeTemp=FALSE behavior unchanged", { writeLines(model_content, model_path) # Test 1: Create model with writeTemp=FALSE - model <- createModel(mName = file.path(temp_dir, "test_model_false"), writeTemp = FALSE) + model <- createModel(mName = normalizePath(file.path(temp_dir, "test_model_false"), mustWork = FALSE), writeTemp = FALSE) # Verify source_file and model_file are the same testthat::expect_equal(model$paths$source_file, model$paths$model_file) diff --git a/vignettes/events_demo.Rmd b/vignettes/events_demo.Rmd index 8a978af..161f310 100644 --- a/vignettes/events_demo.Rmd +++ b/vignettes/events_demo.Rmd @@ -67,7 +67,7 @@ Using the following commands, we create a model object (i.e., an instance of the ```{r, results = 'hide'} # Get the full name of the package directory that contains the example MCSim # model specification file. -mod_path <- file.path(system.file(package = "MCSimMod"), "extdata") +mod_path <- normalizePath(file.path(system.file(package = "MCSimMod"), "extdata")) # Create a model object using the example MCSim model specification file # "pk1.model" included in the MCSimMod package. diff --git a/vignettes/inputs_demo.Rmd b/vignettes/inputs_demo.Rmd index a8bee9b..15467ca 100644 --- a/vignettes/inputs_demo.Rmd +++ b/vignettes/inputs_demo.Rmd @@ -71,11 +71,11 @@ Using the following commands, we create a model object (i.e., an instance of the ```{r, results = 'hide'} # Get the full name of the package directory that contains the example MCSim # model specification file. -mod_path <- file.path(system.file(package = "MCSimMod"), "extdata") +mod_path <- normalizePath(file.path(system.file(package = "MCSimMod"), "extdata")) # Create a model object using the example MCSim model specification file # "pk1_input.model" included in the MCSimMod package. -pk1_mod_name <- file.path(mod_path, "pk1_input") +pk1_input_mod_name <- file.path(mod_path, "pk1_input") pk1_mod <- createModel(pk1_mod_name) ``` diff --git a/vignettes/model_specification.Rmd b/vignettes/model_specification.Rmd index 33b8910..49b3da4 100644 --- a/vignettes/model_specification.Rmd +++ b/vignettes/model_specification.Rmd @@ -114,11 +114,11 @@ Using the following commands, we create an exponential model object (i.e., an in ```{r, results = 'hide'} # Get the full name of the package directory that contains the example MCSim # model specification file. -mod_path <- file.path(system.file(package = "MCSimMod"), "extdata") +mod_path <- normalizePath(file.path(system.file(package = "MCSimMod"), "extdata")) # Create a model object using the example MCSim model specification file # "exponential.model" included in the MCSimMod package. -exp_mod_name <- file.path(mod_path, "exponential") +exponential_mod_name <- file.path(mod_path, "exponential") exp_mod <- createModel(exp_mod_name) ``` diff --git a/vignettes/newt_cool_demo.Rmd b/vignettes/newt_cool_demo.Rmd index 10fc9e0..1e80d09 100644 --- a/vignettes/newt_cool_demo.Rmd +++ b/vignettes/newt_cool_demo.Rmd @@ -44,11 +44,11 @@ Using the following commands, we create a model object (i.e., an instance of the ```{r, results = 'hide'} # Get the full name of the package directory that contains the example MCSim # model specification file. -mod_path <- file.path(system.file(package = "MCSimMod"), "extdata") +mod_path <- normalizePath(file.path(system.file(package = "MCSimMod"), "extdata")) # Create a model object using the example MCSim model specification file # "newt_cool.model" included in the MCSimMod package. -newt_mod_name <- file.path(mod_path, "newt_cool") +newt_cool_mod_name <- file.path(mod_path, "newt_cool") newt_mod <- createModel(newt_mod_name) ``` diff --git a/vignettes/pbpk_demo.Rmd b/vignettes/pbpk_demo.Rmd index 8725764..d96bda9 100644 --- a/vignettes/pbpk_demo.Rmd +++ b/vignettes/pbpk_demo.Rmd @@ -153,11 +153,11 @@ Using the following commands, we create a model object (i.e., an instance of the ```{r, eval = FALSE} # Get the full name of the package directory that contains the example MCSim # model specification file. -mod_path <- file.path(system.file(package = "MCSimMod"), "extdata") +mod_path <- normalizePath(file.path(system.file(package = "MCSimMod"), "extdata")) # Create a model object using the example MCSim model specification file # "pbpk_simple.model" included in the MCSimMod package. -pbpk_mod_name <- file.path(mod_path, "pbpk_simple") +pbpk_simple_mod_name <- file.path(mod_path, "pbpk_simple") pbpk_mod <- createModel(pbpk_mod_name) ``` diff --git a/vignettes/pk1_demo.Rmd b/vignettes/pk1_demo.Rmd index 16a4c8b..f85603e 100644 --- a/vignettes/pk1_demo.Rmd +++ b/vignettes/pk1_demo.Rmd @@ -63,7 +63,7 @@ Using the following commands, we create a model object (i.e., an instance of the ```{r, results = 'hide'} # Get the full name of the package directory that contains the example MCSim # model specification file. -mod_path <- file.path(system.file(package = "MCSimMod"), "extdata") +mod_path <- normalizePath(file.path(system.file(package = "MCSimMod"), "extdata")) # Create a model object using the example MCSim model specification file # "pk1.model" included in the MCSimMod package. diff --git a/vignettes/pred_prey_demo.Rmd b/vignettes/pred_prey_demo.Rmd index f0a908c..0de379a 100644 --- a/vignettes/pred_prey_demo.Rmd +++ b/vignettes/pred_prey_demo.Rmd @@ -44,11 +44,11 @@ Using the following commands, we create a model object (i.e., an instance of the ```{r, results = 'hide'} # Get the full name of the package directory that contains the example MCSim # model specification file. -mod_path <- file.path(system.file(package = "MCSimMod"), "extdata") +mod_path <- normalizePath(file.path(system.file(package = "MCSimMod"), "extdata")) # Create a model object using the example MCSim model specification file # "pred_prey.model" included in the MCSimMod package. -pp_mod_name <- file.path(mod_path, "pred_prey") +pred_prey_mod_name <- file.path(mod_path, "pred_prey") pp_mod <- createModel(pp_mod_name) ``` From 0ee8fdf8a69457a15bd5851cc256972f270402f8 Mon Sep 17 00:00:00 2001 From: "AA\\tzurlind" Date: Wed, 3 Dec 2025 16:44:04 -0500 Subject: [PATCH 11/12] Fix issue with windows paths on temp files --- R/MCSim_model.R | 45 ++++++++++++++++++++++++++++++++++++++------- R/compileModel.R | 13 +++++++++---- R/fixPath.R | 19 ++++++++++++++++--- 3 files changed, 63 insertions(+), 14 deletions(-) diff --git a/R/MCSim_model.R b/R/MCSim_model.R index ae9bf6d..49f2aa2 100644 --- a/R/MCSim_model.R +++ b/R/MCSim_model.R @@ -41,12 +41,26 @@ Model <- setRefClass("Model", initialize = function(...) { "Initialize the Model object using an MCSim model specification file (mName) or an MCSim model specification string (mString)." callSuper(...) + + # Validate input arguments first if (length(mName) == 0 & length(mString) == 0) { stop("To create a Model object, supply either a file name (mName) or a model specification string (mString).") } if (length(mName) > 0 & length(mString) > 0) { stop("Cannot create a Model object using both a file name (mName) and a model specification string (mString). Provide only one of these arguments.") } + + # Set intelligent defaults based on input type + if (length(writeTemp) == 0) { + if (length(mString) > 0) { + writeTemp <<- TRUE # mString always requires temp files + } else { + writeTemp <<- FALSE # mName defaults to local file handling + } + } + if (length(verboseOutput) == 0) { + verboseOutput <<- FALSE + } # Track user's model file for proper change detection model_file_path <- NULL if (length(mString) > 0) { @@ -58,18 +72,35 @@ Model <- setRefClass("Model", # Write model string to file with error handling and ensure it's flushed tryCatch( { - writeLines(mString, file) - # On Windows, ensure file is flushed to disk + # Validate mString before writing + if (length(mString) == 0 || all(nchar(mString) == 0)) { + stop("mString is empty or contains no content") + } + + # Use cat for more reliable file writing in all contexts + cat(paste(mString, collapse = "\n"), "\n", file = file, sep = "") + + # On Windows, ensure file is fully written to disk if (.Platform$OS.type == "windows") { - Sys.sleep(0.01) # Small delay to ensure write completes + Sys.sleep(0.1) # Increased delay for package build context } + # Verify file was written correctly - if (!file.exists(file) || file.size(file) == 0) { - stop("Failed to write model file or file is empty: ", file) + if (!file.exists(file)) { + stop("Model file was not created: ", file) + } + + # Basic validation that file has content + file_size <- file.size(file) + if (is.na(file_size) || file_size == 0) { + stop("Model file is empty after writing: ", file, " (size: ", file_size, ")") } }, error = function(e) { - stop("Failed to create model file from mString: ", e$message) + # Enhanced error message with context + stop("Failed to create model file from mString: ", e$message, + " (mString length: ", length(mString), + ", mString chars: ", sum(nchar(mString)), ")") } ) @@ -107,7 +138,7 @@ Model <- setRefClass("Model", o_file = file.path(mPath, paste0(mName, "_model.o")), dll_file = file.path(mPath, paste0(mName, "_model", .Platform$dynlib.ext)), inits_file = file.path(mPath, paste0(mName, "_model_inits.R")), - source_file = file.path(mPath, paste0(mName, ".model")), + source_file = file, # Use the actual file path (tempfile for mString, or copied file for mName) model_file = model_file_path, hash_file = hash_file_path ) diff --git a/R/compileModel.R b/R/compileModel.R index 88b70ee..e210988 100644 --- a/R/compileModel.R +++ b/R/compileModel.R @@ -18,10 +18,11 @@ compileModel <- function(model_file, c_file, dll_name, dll_file, hash_file = NUL # Normalize paths for Windows compatibility if (.Platform$OS.type == "windows") { model_file <- normalizePath(model_file, winslash = "/", mustWork = TRUE) - # For c_file, normalize the directory and rebuild the path - c_dir <- dirname(c_file) - if (dir.exists(c_dir)) { - c_file <- file.path(normalizePath(c_dir, winslash = "/"), basename(c_file)) + # For c_file, ensure proper path normalization + c_file <- normalizePath(c_file, winslash = "/", mustWork = FALSE) + dll_file <- normalizePath(dll_file, winslash = "/", mustWork = FALSE) + if (!is.null(hash_file)) { + hash_file <- normalizePath(hash_file, winslash = "/", mustWork = FALSE) } } @@ -243,6 +244,10 @@ compileModel <- function(model_file, c_file, dll_name, dll_file, hash_file = NUL # machine code file (ending with ".dll" or ".so"). Write compiler output # to a character string. r_path <- file.path(R.home("bin"), "R") + if (.Platform$OS.type == "windows") { + r_path <- normalizePath(r_path, winslash = "/") + c_file <- normalizePath(c_file, winslash = "/") + } compiler_output <- system(paste( shQuote(r_path), "CMD SHLIB", shQuote(c_file) diff --git a/R/fixPath.R b/R/fixPath.R index d2b1998..e3f3917 100644 --- a/R/fixPath.R +++ b/R/fixPath.R @@ -8,12 +8,25 @@ new.mName <- strsplit(basename(file), "[.]")[[1]][1] new.mPath <- dirname(file) if (.Platform$OS.type == "windows") { - new.mPath <- gsub("\\\\", "/", utils::shortPathName(new.mPath)) + # Normalize path and convert backslashes to forward slashes + new.mPath <- normalizePath(new.mPath, winslash = "/", mustWork = FALSE) + # Only use shortPathName if there are spaces and it's needed + if (grepl(" ", new.mPath)) { + tryCatch({ + short_path <- utils::shortPathName(new.mPath) + if (short_path != "" && short_path != new.mPath) { + new.mPath <- gsub("\\\\", "/", short_path) + } + }, error = function(e) { + # If shortPathName fails, keep the original path + # The compilation might still work with quoted paths + }) + } } has_space <- grepl(" ", new.mPath) - if (has_space == T) { - stop("Error: User-defined directory has space which will throw error for .dll/.so compilation") + if (has_space == TRUE) { + warning("Directory path contains spaces which may cause compilation issues: ", new.mPath) } return(list("mPath" = new.mPath, "mName" = new.mName)) From 4d5e1609ce6bcd3f69966686e3fbbd63d48032da Mon Sep 17 00:00:00 2001 From: "AA\\tzurlind" Date: Wed, 3 Dec 2025 16:47:30 -0500 Subject: [PATCH 12/12] run styler --- R/MCSim_model.R | 26 ++++++++++++++------------ R/fixPath.R | 19 +++++++++++-------- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/R/MCSim_model.R b/R/MCSim_model.R index 49f2aa2..19e715b 100644 --- a/R/MCSim_model.R +++ b/R/MCSim_model.R @@ -41,7 +41,7 @@ Model <- setRefClass("Model", initialize = function(...) { "Initialize the Model object using an MCSim model specification file (mName) or an MCSim model specification string (mString)." callSuper(...) - + # Validate input arguments first if (length(mName) == 0 & length(mString) == 0) { stop("To create a Model object, supply either a file name (mName) or a model specification string (mString).") @@ -49,13 +49,13 @@ Model <- setRefClass("Model", if (length(mName) > 0 & length(mString) > 0) { stop("Cannot create a Model object using both a file name (mName) and a model specification string (mString). Provide only one of these arguments.") } - + # Set intelligent defaults based on input type if (length(writeTemp) == 0) { if (length(mString) > 0) { - writeTemp <<- TRUE # mString always requires temp files + writeTemp <<- TRUE # mString always requires temp files } else { - writeTemp <<- FALSE # mName defaults to local file handling + writeTemp <<- FALSE # mName defaults to local file handling } } if (length(verboseOutput) == 0) { @@ -76,20 +76,20 @@ Model <- setRefClass("Model", if (length(mString) == 0 || all(nchar(mString) == 0)) { stop("mString is empty or contains no content") } - + # Use cat for more reliable file writing in all contexts cat(paste(mString, collapse = "\n"), "\n", file = file, sep = "") - + # On Windows, ensure file is fully written to disk if (.Platform$OS.type == "windows") { Sys.sleep(0.1) # Increased delay for package build context } - + # Verify file was written correctly if (!file.exists(file)) { stop("Model file was not created: ", file) } - + # Basic validation that file has content file_size <- file.size(file) if (is.na(file_size) || file_size == 0) { @@ -98,9 +98,11 @@ Model <- setRefClass("Model", }, error = function(e) { # Enhanced error message with context - stop("Failed to create model file from mString: ", e$message, - " (mString length: ", length(mString), - ", mString chars: ", sum(nchar(mString)), ")") + stop( + "Failed to create model file from mString: ", e$message, + " (mString length: ", length(mString), + ", mString chars: ", sum(nchar(mString)), ")" + ) } ) @@ -138,7 +140,7 @@ Model <- setRefClass("Model", o_file = file.path(mPath, paste0(mName, "_model.o")), dll_file = file.path(mPath, paste0(mName, "_model", .Platform$dynlib.ext)), inits_file = file.path(mPath, paste0(mName, "_model_inits.R")), - source_file = file, # Use the actual file path (tempfile for mString, or copied file for mName) + source_file = file, # Use the actual file path (tempfile for mString, or copied file for mName) model_file = model_file_path, hash_file = hash_file_path ) diff --git a/R/fixPath.R b/R/fixPath.R index e3f3917..517d979 100644 --- a/R/fixPath.R +++ b/R/fixPath.R @@ -12,15 +12,18 @@ new.mPath <- normalizePath(new.mPath, winslash = "/", mustWork = FALSE) # Only use shortPathName if there are spaces and it's needed if (grepl(" ", new.mPath)) { - tryCatch({ - short_path <- utils::shortPathName(new.mPath) - if (short_path != "" && short_path != new.mPath) { - new.mPath <- gsub("\\\\", "/", short_path) + tryCatch( + { + short_path <- utils::shortPathName(new.mPath) + if (short_path != "" && short_path != new.mPath) { + new.mPath <- gsub("\\\\", "/", short_path) + } + }, + error = function(e) { + # If shortPathName fails, keep the original path + # The compilation might still work with quoted paths } - }, error = function(e) { - # If shortPathName fails, keep the original path - # The compilation might still work with quoted paths - }) + ) } }