diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 07ae00b..4c5d6c1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -2,6 +2,8 @@ on: push: branches: - main + tags: + - 'v*' # Trigger on version tags pull_request: branches: - main @@ -11,25 +13,29 @@ on: # run testing on the first of each month 5am ET / 9am UTC - cron: '0 9 1 * *' +# Set minimal permissions for all jobs (read-only) +permissions: + contents: read + actions: read + jobs: R-build: strategy: fail-fast: false matrix: R: [ '4.4.3', '4.5.3', '4.6.0' ] - os: [ 'macos-15-intel', 'ubuntu-latest', 'windows-latest'] + os: [ 'macos-15-intel', 'ubuntu-24.04', 'windows-latest'] runs-on: ${{ matrix.os }} name: ${{ matrix.R }} ${{ matrix.os }} build - env: - R_LIBS: ${{ github.workspace }}/Rlibs steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} - - name: Setup R - uses: r-lib/actions/setup-r@v2 + + - name: Setup R (also sets the R_LIBS_USER environment variable) + uses: r-lib/actions/setup-r@a51a8012b0aab7c32ef9d19bf54da93f3254335e # v2.12.0 with: r-version: ${{ matrix.R }} - name: System Dependencies @@ -41,7 +47,6 @@ jobs: - name: Configuration Information shell: bash run: | - mkdir -p "$R_LIBS" cmake --version if [[ "$RUNNER_OS" == "Windows" ]]; then ls -d /c/rtools* 2>/dev/null || echo "No rtools found in /c/" @@ -55,11 +60,153 @@ jobs: - name: Install R packages shell: bash run: | - R -e "install.packages(c('remotes'), lib=Sys.getenv('R_LIBS'), repos='https://cloud.r-project.org/')" + R -e "install.packages(c('remotes'), lib=Sys.getenv('R_LIBS_USER'), repos='https://cloud.r-project.org/')" - name: Build and test shell: bash env: ITK_GLOBAL_DEFAULT_NUMBER_OF_THREADS: 2 run: | - R -e "Sys.setenv(MAKEJ=2); remotes::install_git(c('.'), lib=Sys.getenv('R_LIBS'))" + R -e "Sys.setenv(MAKEJ=2); remotes::install_git(c('.'), lib=Sys.getenv('R_LIBS_USER'), upgrade='never', INSTALL_opts='--build')" R -e "library(SimpleITK); Version()" + - name: Rename package artifact + # The original artifact name is SimpleITK_.zip (windows), + # SimpleITK_.tgz (macOS), or SimpleITK_.tar.gz (Linux). + # This is irrespective of the R version. We need to rename them so that they are + # unique. Otherwise we could not upload artifacts for different + # R versions because the names collide. + id: rename_package + shell: bash + run: | + # canonical approach to collecting build artifacts in a directory for upload + mkdir -p artifacts + + # remotes::install_git(..., INSTALL_opts='--build') writes a valid package + # file into the current working directory (GITHUB_WORKSPACE). + PKG_PATH=$(find "${GITHUB_WORKSPACE}" -maxdepth 1 -type f \ + \( -name 'SimpleITK_*.tgz' -o -name 'SimpleITK_*.zip' -o -name 'SimpleITK_*.tar.gz' \) \ + | head -n 1) + + if [[ -z "${PKG_PATH}" ]]; then + echo "No built package artifact found in ${GITHUB_WORKSPACE}." + exit 1 + fi + + # package naming is SimpleITK_${PKG_VERSION}_R${R_VERSION_SHORT}_${OS_ARCHIVE_EXT} + R_VERSION_SHORT=$(echo "${{ matrix.R }}" | cut -d'.' -f1,2) + PKG_VERSION=$(Rscript -e "cat(read.dcf('DESCRIPTION', 'Version')[1])") + + if [[ "$RUNNER_OS" == "macOS" ]]; then + PKG_NAME="SimpleITK_${PKG_VERSION}_R${R_VERSION_SHORT}_macos-x86_64.tgz" + elif [[ "$RUNNER_OS" == "Linux" ]]; then + PKG_NAME="SimpleITK_${PKG_VERSION}_R${R_VERSION_SHORT}_linux-x86_64.tar.gz" + elif [[ "$RUNNER_OS" == "Windows" ]]; then + PKG_NAME="SimpleITK_${PKG_VERSION}_R${R_VERSION_SHORT}_windows-x86_64.zip" + else + echo "Unsupported OS: $RUNNER_OS" + exit 1 + fi + + mv "${PKG_PATH}" "${GITHUB_WORKSPACE}/artifacts/${PKG_NAME}" + ls -lh artifacts/ + + # Export PKG_NAME as output for use in upload step + echo "pkg_name=${PKG_NAME}" >> $GITHUB_OUTPUT + - name: Upload binary package + if: steps.rename_package.outcome == 'success' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: ${{ steps.rename_package.outputs.pkg_name }} + path: artifacts/* + retention-days: 30 + + create-release: + name: Create GitHub Draft Release + # Only run this job for tag pushes after the R-build job completes + # successfully. + if: startsWith(github.ref, 'refs/tags/v') + needs: R-build + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1 + + - name: Verify tag matches SITK_TARGET + id: verify_tag + run: | + SITK_TARGET=$(grep '^SITK_TARGET:' DESCRIPTION | awk '{print $2}') + TAG_NAME="${{ github.ref_name }}" + if [ "${TAG_NAME}" != "${SITK_TARGET}" ]; then + echo "Tag ${TAG_NAME} does not match SITK_TARGET ${SITK_TARGET}" + echo "Skipping draft release creation." + echo "draft_release=false" >> $GITHUB_OUTPUT + else + echo "draft_release=true" >> $GITHUB_OUTPUT + fi + + - name: Download all artifacts + if: steps.verify_tag.outputs.draft_release == 'true' + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + path: release-artifacts + pattern: SimpleITK_* + merge-multiple: true + + - name: Display downloaded artifacts + if: steps.verify_tag.outputs.draft_release == 'true' + run: | + echo "Downloaded artifacts:" + ls -lhR release-artifacts/ + + # This action automatically creates the release if it doesn't exist, + # or updates it if it does. + - name: Create or Update Draft Release + if: steps.verify_tag.outputs.draft_release == 'true' + uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + draft: true + name: SimpleITK ${{ github.ref_name }} R Package Release + body: | + **Please review and test the packages before publishing this release. Then remove this line and make it public.** + + Detailed release notes are available on the [main SimpleITK repository](https://github.com/SimpleITK/SimpleITK/releases/${{ github.ref_name }}). + + To install SimpleITK we use a Foyer helper package that downloads the appropriate binary from the GitHub release assets. This is a two step process. + + To install the latest SimpleITK version to your primary library directory, first element of `.libPaths()`, run the following: + + ```r + # install the SimpleITK foyer package + install.packages( + "SimpleITK.foyer", + repos = c("https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}/"), + type = "source" + ) + + # Use foyer to install SimpleITK + library(SimpleITK.foyer) + install_simpleitk() + ``` + Now you can load the SimpleITK library as usual: + + ```r + library(SimpleITK) + ``` + + The `install_simpleitk` function provides finer installation control such as SimpleITK version, library installation location and more. To see all options: + + ```r + help(install_simpleitk) + ``` + + If you directly download the package artifact from this release page, before you install the package you will need to first unzip the file. Then rename it to `SimpleITK_.zip` (windows), `SimpleITK_.tgz` (macOS), or `SimpleITK_.tar.gz` (Linux) to match the expected file name format. + + files: | + release-artifacts/* + fail_on_unmatched_files: true diff --git a/.github/workflows/update_gh_cran.yml b/.github/workflows/update_gh_cran.yml new file mode 100644 index 0000000..5d80979 --- /dev/null +++ b/.github/workflows/update_gh_cran.yml @@ -0,0 +1,46 @@ +name: Update CRAN Repository + +on: + release: + types: [published] + +permissions: + contents: write + +concurrency: + group: update-cran-repository + cancel-in-progress: false + +jobs: + update-repo: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + ref: main + + - name: Set up R + uses: r-lib/actions/setup-r@a51a8012b0aab7c32ef9d19bf54da93f3254335e # v2.12.0 + + - name: Build Foyer Package + run: | + Rscript update_cran_repo.R \ + --foyer_dir SimpleITK_Foyer \ + --output_dir /tmp/cran_output \ + --repo_url https://github.com/${{ github.repository }} \ + --tag ${{ github.event.release.tag_name }} + + - name: Deploy to gh-pages + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git checkout gh-pages + rm -rf src + cp -r /tmp/cran_output/src . + git add src + if ! git diff --cached --quiet; then + git commit -m "Update CRAN-like repository for release ${{ github.event.release.tag_name }}" + git push + fi diff --git a/DESCRIPTION b/DESCRIPTION index 2198dcd..18ab584 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -13,7 +13,7 @@ Authors@R: c(person("Richard", "Beare", role = c("aut", "cre"), email = "blowekamp@mail.nih.gov"), person("Ziv", "Yaniv", role="aut", email = "zivyaniv@nih.gov")) -Author: Richard Beare, Bradley Lowekamp, Ziv Yaniv plus loads of others +Author: Richard Beare, Bradley Lowekamp, Ziv Yaniv, The Insight Software Consortium and the ITK user and developer communities. Depends: R (>= 4.0) Imports: methods, desc diff --git a/SimpleITK_Foyer/DESCRIPTION b/SimpleITK_Foyer/DESCRIPTION new file mode 100644 index 0000000..68f260b --- /dev/null +++ b/SimpleITK_Foyer/DESCRIPTION @@ -0,0 +1,25 @@ +Package: SimpleITK.foyer +Version: 0.0.0 +Title: Lightweight Installer for SimpleITK +Authors@R: c(person("Richard", "Beare", role = "aut", + email = "Richard.Beare@ieee.org"), + person("Bradley", "Lowekamp", role = "aut", + email = "blowekamp@mail.nih.gov"), + person("Ziv", "Yaniv", role = c("aut", "cre"), + email = "zivyaniv@nih.gov")) +Description: Provides a lightweight installer for the SimpleITK package, which + is an interface to the Insight Toolkit (ITK) for medical image segmentation + and registration. After installing this package, call install_simpleitk(). + to download and install the pre-built binary packages for your platform + from the SimpleITKRInstaller GitHub releases. Due to the large size of the compiled + package, the actual functionality is distributed as platform-specific binaries + on GitHub. +License: Apache License (>= 2) +URL: https://github.com/SimpleITK/SimpleITKRInstaller +BugReports: https://github.com/SimpleITK/SimpleITKRInstaller/issues +Encoding: UTF-8 +Depends: R (>= 4.0) +Imports: utils +SystemRequirements: Internet connection for downloading binary packages +Maintainer: Ziv Yaniv +Config/roxygen2/version: 8.0.0 diff --git a/SimpleITK_Foyer/NAMESPACE b/SimpleITK_Foyer/NAMESPACE new file mode 100644 index 0000000..0099eb0 --- /dev/null +++ b/SimpleITK_Foyer/NAMESPACE @@ -0,0 +1,3 @@ +export(install_simpleitk) +importFrom(utils, install.packages) +importFrom(utils, download.file) diff --git a/SimpleITK_Foyer/R/install.R b/SimpleITK_Foyer/R/install.R new file mode 100644 index 0000000..17ad46f --- /dev/null +++ b/SimpleITK_Foyer/R/install.R @@ -0,0 +1,184 @@ +# Base URLs for SimpleITK binary releases. +# These values are updated by the build workflow when generating the foyer package. +.sitk_releases_base_url <- "https://github.com/SimpleITK/SimpleITKRInstaller/releases/download" +.sitk_releases_page_url <- "https://github.com/SimpleITK/SimpleITKRInstaller/releases" +.sitk_repo_url <- "https://github.com/SimpleITK/SimpleITKRInstaller" + +#' Install SimpleITK Binary Package +#' +#' Downloads and installs the pre-built SimpleITK binary package for your +#' platform and R version from GitHub releases. +#' +#' @param version Character string specifying the SimpleITK version to install. +#' Defaults to the version of this foyer package. +#' @param lib Character string specifying the library path where the package +#' should be installed. Defaults to the first element of \code{.libPaths()}. +#' @param repos Character string specifying alternative repository URL. +#' By default, uses GitHub releases. +#' @param force Logical. If \code{TRUE}, forces reinstallation even if the +#' package is already installed. +#' @param quiet Logical. If \code{TRUE}, suppresses progress messages. +#' +#' @return Invisibly returns \code{TRUE} if installation succeeds, +#' \code{FALSE} otherwise. +#' +#' @details +#' This function detects your operating system, R version, and architecture, +#' then downloads the appropriate pre-built binary package from the SimpleITK +#' GitHub releases. The binary packages are built by the SimpleITK project +#' and hosted on GitHub releases. +#' +#' Supported platforms: +#' \itemize{ +#' \item Windows x86_64 (R >= 4.0) +#' \item macOS x86_64 (R >= 4.0) +#' \item macOS ARM64 (R >= 4.0) +#' \item Linux x86_64 (R >= 4.0) +#' } +#' +#' @examples +#' \dontrun{ +#' # Install SimpleITK for your platform +#' install_simpleitk() +#' +#' # Install a specific version +#' install_simpleitk(version = "2.5.0") +#' +#' # Force reinstallation +#' install_simpleitk(force = TRUE) +#' } +#' +#' @export +install_simpleitk <- function(version = NULL, + lib = .libPaths()[1], + repos = NULL, + force = FALSE, + quiet = FALSE) { + + # Check if already installed + if (!force && requireNamespace("SimpleITK", quietly = TRUE)) { + if (!quiet) { + message("SimpleITK is already installed. Use force = TRUE to reinstall.") + } + return(invisible(TRUE)) + } + + # Get version from DESCRIPTION if not specified + if (is.null(version)) { + version <- utils::packageDescription("SimpleITK.foyer", fields = "Version") + if (is.na(version)) { + stop("Cannot determine SimpleITK version. Please specify version parameter.") + } + } + + # Get R version (major.minor only) + r_version <- paste(R.version$major, + strsplit(R.version$minor, "\\.")[[1]][1], + sep = ".") + + # Detect platform + platform_info <- get_platform_info() + + if (is.null(platform_info)) { + stop("Unsupported platform. SimpleITK binaries are only available for ", + "Windows (x86_64), macOS (x86_64, ARM64), and Linux (x86_64).") + } + + # Construct download URL + if (is.null(repos)) { + tag <- paste0("v", version) + filename <- sprintf("SimpleITK_%s_R%s_%s.%s", + version, r_version, + platform_info$platform, + platform_info$extension) + download_url <- file.path(.sitk_releases_base_url, tag, filename) + } else { + download_url <- file.path(repos, sprintf("SimpleITK_%s.%s", + version, + platform_info$extension)) + } + + if (!quiet) { + message("Downloading SimpleITK ", version, " for ", platform_info$platform, + " (R ", r_version, ")...") + message("URL: ", download_url) + } + + # Create temporary directory and file with proper naming + # R expects package files to be named: PackageName_Version.extension + temp_dir <- tempfile() + dir.create(temp_dir, showWarnings = FALSE) + on.exit(unlink(temp_dir, recursive = TRUE), add = TRUE) + + pkg_filename <- sprintf("SimpleITK_%s.%s", version, platform_info$extension) + temp_file <- file.path(temp_dir, pkg_filename) + + # Download the binary + tryCatch({ + download.file(download_url, temp_file, mode = "wb", quiet = quiet) + }, error = function(e) { + stop("Failed to download SimpleITK binary package.\n", + "URL: ", download_url, "\n", + "Error: ", conditionMessage(e), "\n", + "Please check that:\n", + " 1. You have an internet connection\n", + " 2. The specified version (", version, ") has pre-built binaries\n", + " 3. A binary exists for your platform and R version\n", + "Available releases: ", .sitk_releases_page_url) + }) + + if (!quiet) { + message("Installing SimpleITK package...") + } + + # Install the binary package + tryCatch({ + install.packages(temp_file, repos = NULL, type = "source", lib = lib, + quiet = quiet) + + if (!quiet) { + message("SimpleITK successfully installed!") + message("Load it with: library(SimpleITK)") + } + return(invisible(TRUE)) + + }, error = function(e) { + stop("Failed to install SimpleITK binary package.\n", + "Error: ", conditionMessage(e), "\n", + "You may need to build from source instead, see ", .sitk_repo_url, ".\n") + }) +} + + +#' Get Platform Information +#' +#' @return A list with platform and extension, or NULL if unsupported +#' @keywords internal +get_platform_info <- function() { + os <- Sys.info()["sysname"] + arch <- Sys.info()["machine"] + + # Support both x86_64 and ARM64 architectures + # Note: Windows may report "x86-64" (with hyphen) or "x86_64" (with underscore) + is_x86_64 <- grepl("x86[_-]64|amd64", arch, ignore.case = TRUE) + is_arm64 <- grepl("arm64|aarch64", arch, ignore.case = TRUE) + + if (!is_x86_64 && !is_arm64) { + return(NULL) + } + + if (os == "Windows") { + return(list(platform = "windows-x86_64", extension = "zip")) + } else if (os == "Darwin") { + # macOS: distinguish between x86_64 and ARM64 + if (is_arm64) { + return(list(platform = "macos-arm64", extension = "tgz")) + } else { + return(list(platform = "macos-x86_64", extension = "tgz")) + } + } else if (os == "Linux") { + return(list(platform = "linux-x86_64", extension = "tar.gz")) + } else { + return(NULL) + } +} diff --git a/SimpleITK_Foyer/R/zzz.R b/SimpleITK_Foyer/R/zzz.R new file mode 100644 index 0000000..4c5575d --- /dev/null +++ b/SimpleITK_Foyer/R/zzz.R @@ -0,0 +1,29 @@ +#' @keywords internal +.onAttach <- function(libname, pkgname) { + # Check if the full SimpleITK binary is installed + full_pkg_installed <- tryCatch({ + # Check if the package exists and has the actual SimpleITK functionality + pkg_path <- system.file(package = "SimpleITK", lib.loc = libname) + if (pkg_path != "") { + # Check if it has the core SimpleITK functions (not just this foyer) + ns <- loadNamespace("SimpleITK") + exists("Image", envir = ns, mode = "function") + } else { + FALSE + } + }, error = function(e) FALSE) + + if (!full_pkg_installed) { + packageStartupMessage( + "================================================================================\n", + "Welcome to SimpleITK installer!\n\n", + "The SimpleITK package is not yet installed.\n", + "To download and install the latest pre-built binary for your platform, run:\n\n", + " install_simpleitk()\n\n", + "This will download a platform-specific binary from GitHub releases.\n", + "After installation, load the package with: library(SimpleITK)\n", + "To download and install a specific SimpleITK version, use: install_simpleitk(version = 'x.y.z')\n", + "================================================================================" + ) + } +} diff --git a/SimpleITK_Foyer/README.md b/SimpleITK_Foyer/README.md new file mode 100644 index 0000000..bb3a00c --- /dev/null +++ b/SimpleITK_Foyer/README.md @@ -0,0 +1,43 @@ +# SimpleITK Foyer Package + +This is a lightweight installer package for SimpleITK, providing easy access to pre-built SimpleITK binaries for R. + +## Installation + +Install the SimpleITK foyer package from GitHub Pages: +```r +install.packages("SimpleITK.foyer", + repos = "https://SimpleITK.github.io/SimpleITKRInstaller", + type = "source") +``` + +Then download and install the **latest** binary package: +```r +library(SimpleITK.foyer) +install_simpleitk() +``` + +or a specific version: +```r +library(SimpleITK.foyer) +install_simpleitk(version = "2.5.5") +``` + +Old versions may not be available, previously not distributed as binaries. In this case you will need to build SimpleITK locally [using the SimpleITKRInstaller](https://github.com/SimpleITK/SimpleITKRInstaller). + +After installtion, use SimpleITK as usual: + +```r +library(SimpleITK) + +img <- ReadImage("path/to/image.dcm") +``` + +## Rational for using Foyer Package + +The full SimpleITK package is quite large (~40-50 MB depending on platform) and requires significant compilation time when built from source. To make it easier for users, we distribute: + +1. **This lightweight foyer package** - hosted on GitHub Pages as a CRAN-like repository +2. **Pre-built binaries on GitHub Releases** - the actual SimpleITK package + +The foyer package installs instantly and provides the `install_simpleitk()` function which downloads the correct pre-built binary for your platform. diff --git a/SimpleITK_Foyer/man/Version.Rd b/SimpleITK_Foyer/man/Version.Rd new file mode 100644 index 0000000..ea4b6d3 --- /dev/null +++ b/SimpleITK_Foyer/man/Version.Rd @@ -0,0 +1,15 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/install.R +\name{Version} +\alias{Version} +\title{SimpleITK Version Information (Stub)} +\usage{ +Version() +} +\value{ +NULL if SimpleITK is not installed, otherwise calls the real Version function +} +\description{ +This is a stub function that will be replaced by the actual function +once the full SimpleITK package is installed via \code{install_simpleitk()}. +} diff --git a/SimpleITK_Foyer/man/get_platform_info.Rd b/SimpleITK_Foyer/man/get_platform_info.Rd new file mode 100644 index 0000000..2bbcd3d --- /dev/null +++ b/SimpleITK_Foyer/man/get_platform_info.Rd @@ -0,0 +1,15 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/install.R +\name{get_platform_info} +\alias{get_platform_info} +\title{Get Platform Information} +\usage{ +get_platform_info() +} +\value{ +A list with platform and extension, or NULL if unsupported +} +\description{ +Get Platform Information +} +\keyword{internal} diff --git a/SimpleITK_Foyer/man/install_simpleitk.Rd b/SimpleITK_Foyer/man/install_simpleitk.Rd new file mode 100644 index 0000000..05aef6e --- /dev/null +++ b/SimpleITK_Foyer/man/install_simpleitk.Rd @@ -0,0 +1,64 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/install.R +\name{install_simpleitk} +\alias{install_simpleitk} +\title{Install SimpleITK Binary Package} +\usage{ +install_simpleitk( + version = NULL, + lib = .libPaths()[1], + repos = NULL, + force = FALSE, + quiet = FALSE +) +} +\arguments{ +\item{version}{Character string specifying the SimpleITK version to install. +Defaults to the version of this foyer package.} + +\item{lib}{Character string specifying the library path where the package +should be installed. Defaults to the first element of \code{.libPaths()}.} + +\item{repos}{Character string specifying alternative repository URL. +By default, uses GitHub releases.} + +\item{force}{Logical. If \code{TRUE}, forces reinstallation even if the +package is already installed.} + +\item{quiet}{Logical. If \code{TRUE}, suppresses progress messages.} +} +\value{ +Invisibly returns \code{TRUE} if installation succeeds, + \code{FALSE} otherwise. +} +\description{ +Downloads and installs the pre-built SimpleITK binary package for your +platform and R version from GitHub releases. +} +\details{ +This function detects your operating system, R version, and architecture, +then downloads the appropriate pre-built binary package from the SimpleITK +GitHub releases. The binary packages are built by the SimpleITK project +and hosted at: \url{https://github.com/SimpleITK/SimpleITKRInstaller/releases} + +Supported platforms: +\itemize{ + \item Windows x86_64 (R >= 4.0) + \item macOS x86_64 (R >= 4.0) + \item macOS ARM64 (R >= 4.0) + \item Linux x86_64 (R >= 4.0) +} +} +\examples{ +\dontrun{ +# Install SimpleITK for your platform +install_simpleitk() + +# Install a specific version +install_simpleitk(version = "2.5.0") + +# Force reinstallation +install_simpleitk(force = TRUE) +} + +} diff --git a/configure b/configure index 9caa5ee..7afdfb9 100755 --- a/configure +++ b/configure @@ -54,9 +54,14 @@ mkdir -p SITK mkdir -p Build && cd Build && cmake \ + -D "CMAKE_CXX_FLAGS:STRING=-fvisibility=hidden -fvisibility-inlines-hidden ${CFLAGS}" \ + -D "CMAKE_C_FLAGS:STRING=-fvisibility=hidden ${CFLAGS}" \ + -DITK_C_OPTIMIZATION_FLAGS:STRING="" \ + -DITK_CXX_OPTIMIZATION_FLAGS:STRING="" \ -DWRAP_DEFAULT=OFF\ -DWRAP_R=ON \ -DSimpleITK_BUILD_DISTRIBUTE=ON \ + -DSimpleITK_BUILD_STRIP:BOOL=ON \ -DBUILD_EXAMPLES=OFF \ -DBUILD_TESTING=OFF \ -DCMAKE_BUILD_TYPE=MinSizeRel \ diff --git a/configure.win b/configure.win index d2fd253..7cc5e69 100755 --- a/configure.win +++ b/configure.win @@ -83,9 +83,14 @@ export MAKEJ # Configure with Unix Makefiles (Rtools toolchain) cmake -G "Unix Makefiles" \ + -D "CMAKE_CXX_FLAGS:STRING=-fvisibility=hidden -fvisibility-inlines-hidden ${CFLAGS}" \ + -D "CMAKE_C_FLAGS:STRING=-fvisibility=hidden ${CFLAGS}" \ + -DITK_C_OPTIMIZATION_FLAGS:STRING="" \ + -DITK_CXX_OPTIMIZATION_FLAGS:STRING="" \ -DWRAP_DEFAULT=OFF \ -DWRAP_R=ON \ -DSimpleITK_BUILD_DISTRIBUTE=ON \ + -DSimpleITK_BUILD_STRIP:BOOL=ON \ -DBUILD_EXAMPLES=OFF \ -DBUILD_TESTING=OFF \ -DITK_SKIP_PATH_LENGTH_CHECK=ON \ diff --git a/update_cran_repo.R b/update_cran_repo.R new file mode 100644 index 0000000..aec53b7 --- /dev/null +++ b/update_cran_repo.R @@ -0,0 +1,158 @@ +#!/usr/bin/env Rscript + +# Script to build the SimpleITK foyer package and deploy it to a CRAN-like +# repository structure on gh-pages. The foyer is a lightweight source package +# that, once installed, downloads the actual SimpleITK binary from GitHub +# releases for the user's platform. +# +# The gh-pages CRAN-like structure (source packages only): +# +# src/ +# └── contrib/ +# ├── PACKAGES +# ├── PACKAGES.gz +# ├── PACKAGES.rds +# └── SimpleITK.foyer_.tar.gz (updated with each release) +# +# The foyer package is version-aware and updated with each release. +# Users install it once from a fixed URL, then use install_simpleitk(version=...) +# to download any specific binary version from GitHub Releases. +# +# Usage: +# Rscript update_cran_repo.R --foyer_dir --output_dir --repo_url --tag +# +# Arguments: +# --foyer_dir Directory containing the foyer package template +# --output_dir Output directory for the CRAN-like structure (e.g., /tmp/cran_output) +# --repo_url URL of the GitHub repository hosting binary releases +# (e.g., https://github.com/SimpleITK/SimpleITKRInstaller) +# --tag Release tag name (e.g., v2.5.5) +# +# Example: +# Rscript update_cran_repo.R \ +# --foyer_dir SimpleITK_Foyer \ +# --output_dir /tmp/cran_output \ +# --repo_url https://github.com/SimpleITK/SimpleITKRInstaller \ +# --tag v2.5.5 + +library(tools) + +# Parse command-line arguments in --key value format +parse_args <- function(args, required_args = NULL) { + parsed <- list() + i <- 1 + while (i <= length(args)) { + if (startsWith(args[i], "--")) { + key <- sub("^--", "", args[i]) + if (i < length(args) && !startsWith(args[i + 1], "--")) { + parsed[[key]] <- args[i + 1] + i <- i + 2 + } else { + stop(sprintf("Missing or invalid value for argument: --%s", key)) + } + } else { + stop(sprintf("Unexpected argument format: %s (expected a key starting with --)", args[i])) + } + } + if (!is.null(required_args)) { + missing_args <- setdiff(required_args, names(parsed)) + if (length(missing_args) > 0) { + stop("Missing required arguments: ", paste(missing_args, collapse = ", ")) + } + } + return(parsed) +} + +# Parse command line arguments +args <- commandArgs(trailingOnly = TRUE) +parsed_args <- parse_args(args, required_args = c("foyer_dir", "output_dir", "repo_url", "tag")) + +foyer_dir <- parsed_args[["foyer_dir"]] +output_dir <- parsed_args[["output_dir"]] +repo_url <- parsed_args[["repo_url"]] +tag <- parsed_args[["tag"]] +version <- sub("^v", "", tag) + +# Validate foyer directory exists +if (!dir.exists(foyer_dir)) { + stop("Foyer directory does not exist: ", foyer_dir) +} + +# Copy foyer template to a temp directory to avoid modifying the original +build_dir <- tempdir() +foyer_copy <- file.path(build_dir, "SimpleITK") +if (dir.exists(foyer_copy)) unlink(foyer_copy, recursive = TRUE) +dir.create(foyer_copy, recursive = TRUE) +file.copy(list.files(foyer_dir, full.names = TRUE), foyer_copy, recursive = TRUE) + +# 1. Update DESCRIPTION version +desc_path <- file.path(foyer_copy, "DESCRIPTION") +desc_lines <- readLines(desc_path) +desc_lines <- sub("^Version:.*", sprintf("Version: %s", version), desc_lines) +# Add/update Date field +date_line <- sprintf("Date: %s", Sys.Date()) +if (any(grepl("^Date:", desc_lines))) { + desc_lines <- sub("^Date:.*", date_line, desc_lines) +} else { + # Insert Date after Version + version_idx <- grep("^Version:", desc_lines) + desc_lines <- append(desc_lines, date_line, after = version_idx) +} +writeLines(desc_lines, desc_path) + +# 2. Update repository URLs in R/install.R +install_r_path <- file.path(foyer_copy, "R", "install.R") +install_lines <- readLines(install_r_path) +releases_url <- paste0(repo_url, "/releases/download") +releases_page <- paste0(repo_url, "/releases") + +# Update the three URL variables +install_lines <- sub( + '^(\\.sitk_releases_base_url <- ").*(")', + paste0("\\1", releases_url, "\\2"), + install_lines +) + +install_lines <- sub( + '^(\\.sitk_releases_page_url <- ").*(")', + paste0("\\1", releases_page, "\\2"), + install_lines +) + +install_lines <- sub( + '^(\\.sitk_repo_url <- ").*(")', + paste0("\\1", repo_url, "\\2"), + install_lines +) + +writeLines(install_lines, install_r_path) + +# 3. Build the source package +message("Building foyer package...") +# Set tar options to avoid uid/gid warnings when R CMD build creates the tarball. +# --no-same-owner prevents tar from trying to preserve file ownership information, +# which can cause warnings if the original uid/gid values don't exist in the build +# environment. The package installs correctly regardless of these attributes. +Sys.setenv(R_BUILD_TAR = "tar --no-same-owner") +build_output <- system2("R", args = c("CMD", "build", "--no-manual", foyer_copy), + stdout = TRUE, stderr = TRUE) +cat(build_output, sep = "\n") + +tarball <- sprintf("SimpleITK.foyer_%s.tar.gz", version) +if (!file.exists(tarball)) { + stop("R CMD build failed. Expected tarball not found: ", tarball) +} + +# 4. Create CRAN-like directory structure +dest_dir <- file.path(output_dir, "src", "contrib") +dir.create(dest_dir, recursive = TRUE, showWarnings = FALSE) +file.rename(tarball, file.path(dest_dir, tarball)) + +# 5. Generate PACKAGES files +write_PACKAGES(dest_dir, type = "source") + +message("CRAN-like repository created at: ", output_dir) +message(" ", file.path(dest_dir, tarball)) +message(" ", file.path(dest_dir, tarball)) + +