diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index cd48a0e..7abe708 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -3,43 +3,67 @@ on: branches: [main, master] pull_request: branches: [main, master] + name: R-CMD-check + jobs: R-CMD-check: runs-on: ${{ matrix.os }} - name: ${{ matrix.os }} (${{ matrix.r }}) + name: ${{ matrix.os }} (${{ matrix.r }}) - ${{ matrix.backend }} + strategy: fail-fast: false matrix: include: - - { os: "ubuntu-latest", python: "3.13", r: "release" } - - { os: "windows-latest", python: "3.13", r: "release" } - - { os: "macOS-latest", python: "3.13", r: "release" } - - { os: "ubuntu-latest", python: "3.10", r: "oldrel-1" } - - { os: "ubuntu-latest", python: "3.10", r: "oldrel-2" } - - { os: "ubuntu-latest", python: "3.10", r: "oldrel-3" } + - { os: "ubuntu-latest", python: "3.13", r: "release", backend: "venv" } + - { os: "ubuntu-latest", python: "3.13", r: "release", backend: "conda" } + + - { os: "windows-latest", python: "3.13", r: "release", backend: "venv" } + - { os: "windows-latest", python: "3.13", r: "release", backend: "conda" } + + - { os: "macOS-latest", python: "3.13", r: "release", backend: "venv" } + - { os: "macOS-latest", python: "3.13", r: "release", backend: "conda" } + + - { os: "ubuntu-latest", python: "3.10", r: "oldrel-1", backend: "venv" } + - { os: "ubuntu-latest", python: "3.10", r: "oldrel-2", backend: "venv" } + - { os: "ubuntu-latest", python: "3.10", r: "oldrel-3", backend: "venv" } + env: GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} R_KEEP_PKG_SOURCE: yes MPLBACKEND: Agg + ACRO_USE_CONDA: ${{ matrix.backend == 'conda' && 'true' || 'false' }} + steps: - name: Checkout ACRO uses: actions/checkout@v4 + + - name: Setup Miniconda + if: matrix.backend == 'conda' + uses: conda-incubator/setup-miniconda@v3 + with: + python-version: ${{ matrix.python }} + auto-activate-base: false + - name: Setup Python uses: actions/setup-python@v6 with: python-version: ${{ matrix.python }} + - name: Setup pandoc uses: r-lib/actions/setup-pandoc@v2 + - name: Setup R uses: r-lib/actions/setup-r@v2 with: r-version: ${{ matrix.r }} + - name: Install R dependencies uses: r-lib/actions/setup-r-dependencies@v2 with: extra-packages: any::rcmdcheck needs: check + - name: Check R Package uses: r-lib/actions/check-r-package@v2 with: diff --git a/R/acro_init.R b/R/acro_init.R index 76e4e3b..327255e 100644 --- a/R/acro_init.R +++ b/R/acro_init.R @@ -1,12 +1,96 @@ +# Globals ----------------------------------------------------------------- +acro_venv <- "r-acro" +acro_pkg <- "acro==0.4.11" +ch <- "conda-forge" + + +# Internal helper: resolve Python executable +## nocov start +get_python <- function() { + python <- Sys.which("python3") + if (python == "") { + python <- Sys.which("python") + if (python == "") { + stop("Python not found in PATH. Please ensure Python is installed.") + } + } + return(python) +} +## nocov end + + +# Internal helper: install ACRO in a Conda environment +## nocov start +install_conda <- function(envname) { # nocov + if (!reticulate::condaenv_exists(envname = envname, conda = "auto")) { + reticulate::conda_create(envname = envname, python_version = "3.12", channel = ch) + reticulate::conda_install(envname = envname, packages = acro_pkg, channel = ch) + } +} +## nocov end + + +# Internal helper: install ACRO in a Python virtual environment +install_venv <- function(envname = acro_venv) { + if (!reticulate::virtualenv_exists(envname)) { + python <- get_python() + + reticulate::virtualenv_create( + envname = envname, + python = python, + force = TRUE, + packages = NULL + ) + + reticulate::py_install(acro_pkg, envname = envname) + } +} + + +# Internal helper: resolve whether Conda should be used +get_use_conda <- function(use_conda = NULL) { + if (is.null(use_conda)) { + use_conda <- tolower(Sys.getenv("ACRO_USE_CONDA")) %in% c("1", "true", "yes") + } + use_conda <- isTRUE(use_conda) # default FALSE + + if (use_conda && is.null(reticulate::conda_binary())) { # nocov + stop("Conda requested but no conda installation found", call. = FALSE) # nocov + } + + return(use_conda) +} + + #' Initialise an ACRO object #' +#' @param config Name of a yaml configuration file with safe parameters. #' @param suppress Whether to automatically apply suppression. +#' @param envname Name of the Python environment to use. +#' @param use_conda Whether to use a Conda environment. +#' If `NULL`, looks for environment variable `ACRO_USE_CONDA`, +#' defaults to `FALSE` if unset. #' -#' @return No return value, called for side effects +#' @return Invisibly returns the ACRO object, which is used internally. #' @export +acro_init <- function(config = "default", suppress = FALSE, envname = acro_venv, use_conda = NULL) { + # define the environment + use_conda <- get_use_conda(use_conda) -acro_init <- function(suppress = FALSE) { - create_virtualenv() + # initialise the environment + if (!reticulate::py_available(initialize = FALSE)) { + if (use_conda) { # nocov + install_conda(envname) # nocov + reticulate::use_condaenv(envname, required = TRUE) # nocov + } else { + install_venv(envname) + reticulate::use_virtualenv(envname, required = TRUE) + } + } + + # import the acro package and instantiate an object acro <- reticulate::import("acro", delay_load = TRUE) - acroEnv$ac <- acro$ACRO(suppress = suppress) + acroEnv$ac <- acro$ACRO(config = config, suppress = suppress) + + invisible(acroEnv$ac) } diff --git a/R/create_virtualenv.R b/R/create_virtualenv.R deleted file mode 100644 index 55fdd5a..0000000 --- a/R/create_virtualenv.R +++ /dev/null @@ -1,55 +0,0 @@ -acro_venv <- "r-acro" -acro_package <- "acro==0.4.11" -python_version <- ">=3.10" - -#' Install acro -#' -#' @param envname the name of the Python virtual environment -#' @param python the path to Python executable -#' @param ... Any other parameters. -#' -#' @return No return value, called for side effects -install_acro <- function(envname = "r-acro", python = NULL, ...) { - # Get Python executable if not provided - if (is.null(python)) { - python <- Sys.which("python3") - if (python == "") { - python <- Sys.which("python") # nocov - if (python == "") { # nocov - stop("Python not found in PATH. Please ensure Python is installed and accessible.") # nocov - } # nocov - } - } - - # create Python virtual environment - reticulate::virtualenv_create( - envname = envname, - python = python, - force = TRUE, - packages = NULL - ) - - # install Python acro - reticulate::py_install(acro_package, envname = envname) -} - -#' Create a python virtual environment -#' -#' @param ... Any other parameters. -#' -#' @return No return value, called for side effects -create_virtualenv <- function(...) { - # Get Python executable path - python_path <- Sys.which("python3") - if (python_path == "") { - python_path <- Sys.which("python") # nocov - } - - # ensure a virtual environment exists - if (!reticulate::virtualenv_exists(acro_venv)) { - install_acro(envname = acro_venv, python = python_path) - } - - # activate virtual environment - reticulate::use_virtualenv(acro_venv, required = TRUE) -} diff --git a/man/acro_init.Rd b/man/acro_init.Rd index 819ec44..1e4cb99 100644 --- a/man/acro_init.Rd +++ b/man/acro_init.Rd @@ -4,13 +4,26 @@ \alias{acro_init} \title{Initialise an ACRO object} \usage{ -acro_init(suppress = FALSE) +acro_init( + config = "default", + suppress = FALSE, + envname = acro_venv, + use_conda = NULL +) } \arguments{ +\item{config}{Name of a yaml configuration file with safe parameters.} + \item{suppress}{Whether to automatically apply suppression.} + +\item{envname}{Name of the Python environment to use.} + +\item{use_conda}{Whether to use a Conda environment. +If \code{NULL}, looks for environment variable \code{ACRO_USE_CONDA}, +defaults to \code{FALSE} if unset.} } \value{ -No return value, called for side effects +Invisibly returns the ACRO object, which is used internally. } \description{ Initialise an ACRO object diff --git a/man/create_virtualenv.Rd b/man/create_virtualenv.Rd deleted file mode 100644 index bd1bea5..0000000 --- a/man/create_virtualenv.Rd +++ /dev/null @@ -1,17 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/create_virtualenv.R -\name{create_virtualenv} -\alias{create_virtualenv} -\title{Create a python virtual environment} -\usage{ -create_virtualenv(...) -} -\arguments{ -\item{...}{Any other parameters.} -} -\value{ -No return value, called for side effects -} -\description{ -Create a python virtual environment -} diff --git a/man/install_acro.Rd b/man/install_acro.Rd deleted file mode 100644 index f9ea752..0000000 --- a/man/install_acro.Rd +++ /dev/null @@ -1,21 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/create_virtualenv.R -\name{install_acro} -\alias{install_acro} -\title{Install acro} -\usage{ -install_acro(envname = "r-acro", python = NULL, ...) -} -\arguments{ -\item{envname}{the name of the Python virtual environment} - -\item{python}{the path to Python executable} - -\item{...}{Any other parameters.} -} -\value{ -No return value, called for side effects -} -\description{ -Install acro -} diff --git a/tests/testthat/test-create_virtualenv.R b/tests/testthat/test-create_virtualenv.R deleted file mode 100644 index c4ac98c..0000000 --- a/tests/testthat/test-create_virtualenv.R +++ /dev/null @@ -1,18 +0,0 @@ -test_that("find_python returns a valid path", { - testthat::skip_on_cran() - python_path <- Sys.which("python3") - if (python_path == "") { - python_path <- Sys.which("python") - } - expect_true(python_path != "") -}) - -test_that("install_acro function exists and is callable", { - testthat::skip_on_cran() - expect_true(is.function(install_acro)) -}) - -test_that("create_virtualenv function exists and is callable", { - testthat::skip_on_cran() - expect_true(is.function(create_virtualenv)) -}) diff --git a/tests/testthat/test-install_acro.R b/tests/testthat/test-install_acro.R index 5ea92f4..51a4d72 100644 --- a/tests/testthat/test-install_acro.R +++ b/tests/testthat/test-install_acro.R @@ -4,7 +4,7 @@ test_that("install_acro installs 'acro' in the specified environment", { test_envname <- "test-acro-env" # Run the install_acro function - install_acro(envname = test_envname) + install_venv(envname = test_envname) # Check if 'acro' is installed in the test environment is_installed <- reticulate::py_module_available("acro")