- 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
316 lines
9.9 KiB
R
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")
|
|
}
|
|
}
|