SmartCane/r_app/package_manager.R
Timon 4d6439d5e0 Add reproducible R package management system
- Add renv.lock with exact package versions for reproducibility
- Add package_manager.R for automated package installation/updates
- Add extract_current_versions.R for package discovery
- Configure renv environment for team collaboration

All team members can now run source('r_app/package_manager.R') to get identical package environment
2025-06-24 14:51:22 +02:00

316 lines
9.9 KiB
R

#' Package Manager for SmartCane Project
#'
#' This script manages R package versions across development, testing, and production environments.
#' It uses renv for reproducible environments and ensures consistent package versions.
#'
#' Usage:
#' source("package_manager.R")
#'
#' Author: SmartCane Team
#' Date: 2025-06-24
# =============================================================================
# CONFIGURATION
# =============================================================================
# Package requirements with your current working versions
REQUIRED_PACKAGES <- list(
# Core data manipulation
"dplyr" = "1.1.4",
"here" = "1.0.1",
"lubridate" = "1.9.4",
"readr" = "2.1.5",
"readxl" = "1.4.5",
"stringr" = "1.5.1",
"tidyr" = "1.3.1",
"purrr" = "1.0.2",
"magrittr" = "2.0.0", # Adding this as it's commonly used
# Spatial data
"exactextractr" = "0.10.0",
"raster" = "3.6.32",
"sf" = "1.0.19",
"terra" = "1.8.43", # CRITICAL: for raster processing
# Visualization - CRITICAL: tmap v4 for new syntax
"ggplot2" = "3.5.1",
"tmap" = "4.0", # CRITICAL: for tm_scale_continuous() syntax
"gridExtra" = "2.3",
# Reporting
"knitr" = "1.50",
"rmarkdown" = "2.21.0", # Adding this as it's needed for reports
# Tidyverse meta-package
"tidyverse" = "2.0.0",
# Machine Learning & Statistics
"caret" = "7.0.1",
"CAST" = "1.0.3",
"randomForest" = "4.7.1.2",
"rsample" = "1.3.0",
# Parallel processing
"furrr" = "0.3.1",
"future" = "1.40.0",
"progressr" = "0.15.1",
# Other utilities
"reshape2" = "1.4.4",
"zoo" = "1.8.13"
)
# Log file setup
LOG_FILE <- file.path(getwd(), "package_manager.log")
START_TIME <- Sys.time()
# =============================================================================
# UTILITY FUNCTIONS
# =============================================================================
#' Log message to both console and file
log_message <- function(message, level = "INFO") {
timestamp <- format(Sys.time(), "%Y-%m-%d %H:%M:%S")
formatted_msg <- sprintf("[%s] %s - %s", level, timestamp, message)
# Print to console
cat(formatted_msg, "\n")
# Write to log file
cat(formatted_msg, "\n", file = LOG_FILE, append = TRUE)
}
#' Check if package is installed
is_package_installed <- function(package) {
package %in% rownames(installed.packages())
}
#' Get installed package version
get_package_version <- function(package) {
if (is_package_installed(package)) {
as.character(packageVersion(package))
} else {
NULL
}
}
#' Compare version strings (returns TRUE if installed >= required)
version_meets_requirement <- function(installed, required) {
if (is.null(installed)) return(FALSE)
utils::compareVersion(installed, required) >= 0
}
#' Install or update package to minimum version
install_or_update_package <- function(package, required_version) {
current_version <- get_package_version(package)
if (is.null(current_version)) {
log_message(sprintf("Installing %s (required: >= %s)", package, required_version))
tryCatch({
install.packages(package, dependencies = TRUE, quiet = TRUE)
new_version <- get_package_version(package)
log_message(sprintf("✓ Installed %s version %s", package, new_version), "SUCCESS")
return(TRUE)
}, error = function(e) {
log_message(sprintf("✗ Failed to install %s: %s", package, e$message), "ERROR")
return(FALSE)
})
} else if (!version_meets_requirement(current_version, required_version)) {
log_message(sprintf("Updating %s from %s to >= %s", package, current_version, required_version))
tryCatch({
install.packages(package, dependencies = TRUE, quiet = TRUE)
new_version <- get_package_version(package)
if (version_meets_requirement(new_version, required_version)) {
log_message(sprintf("✓ Updated %s to version %s", package, new_version), "SUCCESS")
return(TRUE)
} else {
log_message(sprintf("⚠ %s updated to %s but still below required %s", package, new_version, required_version), "WARNING")
return(FALSE)
}
}, error = function(e) {
log_message(sprintf("✗ Failed to update %s: %s", package, e$message), "ERROR")
return(FALSE)
})
} else {
log_message(sprintf("✓ %s version %s meets requirement (>= %s)", package, current_version, required_version))
return(TRUE)
}
}
# =============================================================================
# MAIN PACKAGE MANAGEMENT FUNCTIONS
# =============================================================================
#' Initialize renv if not already initialized
initialize_renv <- function() {
log_message("Checking renv initialization...")
if (!file.exists("renv.lock")) {
log_message("Initializing renv for the first time...")
if (!requireNamespace("renv", quietly = TRUE)) {
log_message("Installing renv...")
install.packages("renv")
}
renv::init()
log_message("✓ renv initialized", "SUCCESS")
} else {
log_message("✓ renv already initialized")
# Check if renv is already active by looking at the library path
if (!requireNamespace("renv", quietly = TRUE)) {
install.packages("renv")
}
# Check if we're already using the renv project library
lib_paths <- .libPaths()
if (!any(grepl("renv", lib_paths))) {
log_message("Activating renv...")
renv::activate()
log_message("✓ renv activated")
} else {
log_message("✓ renv already active")
}
}
}
#' Check and install all required packages
manage_packages <- function() {
log_message("=== PACKAGE MANAGEMENT STARTED ===")
log_message(sprintf("R version: %s", R.version.string))
success_count <- 0
failure_count <- 0
for (package in names(REQUIRED_PACKAGES)) {
required_version <- REQUIRED_PACKAGES[[package]]
if (install_or_update_package(package, required_version)) {
success_count <- success_count + 1
} else {
failure_count <- failure_count + 1
}
}
log_message(sprintf("Package management complete: %d success, %d failures", success_count, failure_count))
if (failure_count > 0) {
log_message("Some packages failed to install/update. Check log for details.", "WARNING")
}
return(failure_count == 0)
}
#' Update renv lockfile with current package versions
update_lockfile <- function() {
log_message("Updating renv lockfile...")
tryCatch({
renv::snapshot(prompt = FALSE)
log_message("✓ renv lockfile updated", "SUCCESS")
}, error = function(e) {
log_message(sprintf("✗ Failed to update lockfile: %s", e$message), "ERROR")
})
}
#' Generate package report
generate_package_report <- function() {
log_message("=== PACKAGE REPORT ===")
# Check each required package
for (package in names(REQUIRED_PACKAGES)) {
required_version <- REQUIRED_PACKAGES[[package]]
current_version <- get_package_version(package)
if (is.null(current_version)) {
status <- "❌ NOT INSTALLED"
} else if (version_meets_requirement(current_version, required_version)) {
status <- "✅ OK"
} else {
status <- "⚠️ VERSION TOO OLD"
}
log_message(sprintf("%-20s | Required: >= %-8s | Installed: %-8s | %s",
package, required_version,
ifelse(is.null(current_version), "NONE", current_version),
status))
}
log_message("=== END PACKAGE REPORT ===")
}
#' Main function to run complete package management
run_package_manager <- function() {
# Initialize log
cat("", file = LOG_FILE) # Clear log file
log_message("SmartCane Project - Package Manager Started")
log_message(sprintf("Working directory: %s", getwd()))
# Step 1: Initialize renv
initialize_renv()
# Step 2: Generate initial report
log_message("\n=== INITIAL STATE ===")
generate_package_report()
# Step 3: Manage packages
log_message("\n=== PACKAGE INSTALLATION/UPDATES ===")
success <- manage_packages()
# Step 4: Update lockfile if successful
if (success) {
update_lockfile()
}
# Step 5: Generate final report
log_message("\n=== FINAL STATE ===")
generate_package_report()
# Summary
end_time <- Sys.time()
duration <- round(as.numeric(difftime(end_time, START_TIME, units = "secs")), 2)
log_message(sprintf("Package management completed in %s seconds", duration))
log_message(sprintf("Log saved to: %s", LOG_FILE))
if (success) {
log_message("🎉 All packages successfully managed!", "SUCCESS")
log_message("📋 Next steps:")
log_message(" 1. Test your R scripts to ensure everything works")
log_message(" 2. Commit renv.lock to version control")
log_message(" 3. Share this script with your team")
} else {
log_message("⚠️ Some issues occurred. Check the log for details.", "WARNING")
log_message("💡 You may need to:")
log_message(" 1. Update R to a newer version")
log_message(" 2. Install system dependencies")
log_message(" 3. Check your internet connection")
}
return(success)
}
# =============================================================================
# EXECUTION
# =============================================================================
# Only run if script is sourced directly (not when loaded as module)
if (!exists("PACKAGE_MANAGER_LOADED")) {
PACKAGE_MANAGER_LOADED <- TRUE
cat("🚀 SmartCane Package Manager\n")
cat("============================\n")
cat("This will check and install/update all required R packages.\n")
cat("Log file:", LOG_FILE, "\n\n")
# Ask for confirmation
response <- readline("Continue? (y/N): ")
if (tolower(substr(response, 1, 1)) == "y") {
result <- run_package_manager()
if (result) {
cat("\n✅ Package management completed successfully!\n")
} else {
cat("\n❌ Package management completed with errors. Check the log.\n")
}
} else {
cat("❌ Package management cancelled.\n")
}
}