R/rtools.R

Defines functions rtools_url rtools_needed is.rtools rtools is_compatible installed_version check_rtools is_ucrt has_rtools

Documented in check_rtools has_rtools rtools_needed

#' Is Rtools installed?
#'
#' To build binary packages on windows, Rtools (found at
#' \url{https://CRAN.R-project.org/bin/windows/Rtools/}) needs to be on
#' the path. The default installation process does not add it, so this
#' script finds it (looking first on the path, then in the registry).
#' It also checks that the version of rtools matches the version of R.
#' `has_rtools()` determines if Rtools is installed, caching the results.
#' Afterward, run `rtools_path()` to find out where it's installed.
#'
#' @section Acknowledgements:
#'   This code borrows heavily from RStudio's code for finding Rtools.
#'   Thanks JJ!
#' @param debug If `TRUE`, will print out extra information useful for
#'   debugging. If `FALSE`, it will use result cached from a previous run.
#' @return Either a visible `TRUE` if rtools is found, or an invisible
#'   `FALSE` with a diagnostic [message()].
#'   As a side-effect the internal package variable `rtools_path` is
#'   updated to the paths to rtools binaries.
#' @keywords internal
#' @export
#' @examples
#' has_rtools()
has_rtools <- function(debug = FALSE) {
  if (!debug && rtools_path_is_set()) {
    return(!identical(rtools_path(), ""))
  }

  if (!is_windows()) {
    return(FALSE)
  }

  # R 4.4.0 or later on ARM64
  if (getRversion() >= "4.4.0" && grepl("aarch", R.version$platform)) {
    rtools44_aarch64_home <- Sys.getenv("RTOOLS44_AARCH64_HOME", "C:\rtools44-aarch64")
    if (file.exists(file.path(rtools44_aarch64_home, "usr", "bin"))) {
      if (debug) {
        cat("Found in Rtools 4.4 (aarch64) installation folder\n")
      }
      rtools_path_set(rtools(rtools44_aarch64_home, "4.4"))
      return(TRUE)
    }
  }

  # R 4.4.0 or later
  if (getRversion() >= "4.4.0") {
    rtools44_home <- Sys.getenv("RTOOLS44_HOME", "C:\\rtools44")
    if (file.exists(file.path(rtools44_home, "usr", "bin"))) {
      if (debug) {
        cat("Found in Rtools 4.4 installation folder\n")
      }
      rtools_path_set(rtools(rtools44_home, "4.4"))
      return(TRUE)
    }
  }

  # R 4.3.0 or later on ARM64
  if (getRversion() >= "4.3.0" && getRversion() < "4.4.0" &&
      grepl("aarch", R.version$platform)) {
    rtools43_aarch64_home <- Sys.getenv("RTOOLS43_AARCH64_HOME", "C:\rtools43-aarch64")
    if (file.exists(file.path(rtools43_aarch64_home, "usr", "bin"))) {
      if (debug) {
        cat("Found in Rtools 4.3 (aarch64) installation folder\n")
      }
      rtools_path_set(rtools(rtools43_aarch64_home, "4.3"))
      return(TRUE)
    }
  }

  # R 4.3.0 or later
  if (getRversion() >= "4.3.0" && getRversion() < "4.4.0") {
    rtools43_home <- Sys.getenv("RTOOLS43_HOME", "C:\\rtools43")
    if (file.exists(file.path(rtools43_home, "usr", "bin"))) {
      if (debug) {
        cat("Found in Rtools 4.3 installation folder\n")
      }
      rtools_path_set(rtools(rtools43_home, "4.3"))
      return(TRUE)
    }
  }

  # R 4.2.x or later and ucrt?
  ucrt <- is_ucrt()
  if (getRversion() >= "4.2.0" && getRversion() < "4.3.0") {
    if (ucrt) {
      rtools42_home <- Sys.getenv("RTOOLS42_HOME", "C:\\rtools42")
      if (file.exists(file.path(rtools42_home, "usr", "bin"))) {
        if (debug) {
          cat("Found in Rtools 4.2 installation folder\n")
        }
        rtools_path_set(rtools(rtools42_home, "4.2"))
        return(TRUE)
      }
    }
  }

  # In R 4.0 we can use RTOOLS40_HOME, recent versions of Rtools40 work fine
  # with ucrt as well, currently.
  if (getRversion() >= "4.0.0" && getRversion() < "4.3.0") {
    rtools40_home <- Sys.getenv("RTOOLS40_HOME", "C:\\rtools40")
    fld <- if (ucrt) "ucrt64" else "usr"
    if (file.exists(file.path(rtools40_home, fld, "bin"))) {
      if (debug) {
        cat("Found in Rtools 4.0 installation folder\n")
      }
      rtools_path_set(rtools(rtools40_home, "4.0"))
      return(TRUE)
    }
  }

  # First, R CMD config CC --------------------------------------------
  # This does not work if 'make' is not yet on the path
  from_config <- scan_config_for_rtools(debug)
  if (is_compatible(from_config)) {
    if (debug) {
      cat("Found compatible gcc from R CMD config CC\n")
    }
    rtools_path_set(from_config)
    return(TRUE)
  }

  # Next, try the path ------------------------------------------------
  from_path <- scan_path_for_rtools(debug)
  if (is_compatible(from_path)) {
    if (debug) {
      cat("Found compatible gcc on path\n")
    }
    rtools_path_set(from_path)
    return(TRUE)
  }

  if (!is.null(from_path)) {
    # Installed
    if (is.null(from_path$version)) {
      # but not from rtools
      if (debug) {
        cat("gcc and ls on path, assuming set up is correct\n")
      }
      return(TRUE)
    } else {
      # Installed, but not compatible
      needed <- rtools_needed()
      message(
        "WARNING: Rtools ", from_path$version, " found on the path",
        " at ", from_path$path, " is not compatible with R ", getRversion(), ".\n\n",
        "Please download and install ", needed, " from ", rtools_url(needed),
        ", remove the incompatible version from your PATH."
      )
      return(invisible(FALSE))
    }
  }

  # Next, try the registry --------------------------------------------------
  registry_candidates <- scan_registry_for_rtools(debug)

  if (length(registry_candidates) == 0) {
    # Not on path or in registry, so not installled
    needed <- rtools_needed()
    message(
      "WARNING: Rtools is required to build R packages, but is not ",
      "currently installed.\n\n",
      "Please download and install ", needed, " from ", rtools_url(needed), "."
    )
    return(invisible(FALSE))
  }

  from_registry <- Find(is_compatible, registry_candidates, right = TRUE)
  if (is.null(from_registry)) {
    # In registry, but not compatible.
    versions <- vapply(registry_candidates, function(x) x$version, character(1))
    needed <- rtools_needed()
    message(
      "WARNING: Rtools is required to build R packages, but no version ",
      "of Rtools compatible with R ", getRversion(), " was found. ",
      "(Only the following incompatible version(s) of Rtools were found: ",
      paste(versions, collapse = ", "), ")\n\n",
      "Please download and install ", needed, " from ", rtools_url(needed), "."
    )
    return(invisible(FALSE))
  }

  # On Rtools 3.x do an extra check if the installed version is accurate.
  # With rtools40 this is no longer needed (it doesn't have a Version.txt)
  if (isTRUE(from_registry$version < "4")) {
    installed_ver <- installed_version(from_registry$path, debug = debug)
    if (is.null(installed_ver)) {
      # Previously installed version now deleted
      needed <- rtools_needed()
      message(
        "WARNING: Rtools is required to build R packages, but the ",
        "version of Rtools previously installed in ", from_registry$path,
        " has been deleted.\n\n",
        "Please download and install ", needed, " from ", rtools_url(needed), "."
      )
      return(invisible(FALSE))
    }

    if (installed_ver != from_registry$version) {
      # Installed version doesn't match registry version
      needed <- rtools_needed()
      message(
        "WARNING: Rtools is required to build R packages, but no version ",
        "of Rtools compatible with R ", getRversion(), " was found. ",
        "Rtools ", from_registry$version, " was previously installed in ",
        from_registry$path, " but now that directory contains Rtools ",
        installed_ver, ".\n\n",
        "Please download and install ", needed, " from ", rtools_url(needed), "."
      )
      return(invisible(FALSE))
    }
  }

  # Otherwise it must be ok :)

  # Recently Rtools is versioned properly
  from_registry$version <- sub(
    "^([0-9]+[.][0-9]+)[.].*$", "\\1",
    from_registry$version
  )
  rtools_path_set(from_registry)
  TRUE
}

is_ucrt <- function() {
  identical(R.Version()$crt, "ucrt")
}

#' @rdname has_rtools
#' @usage NULL
#' @export
find_rtools <- has_rtools

#' @rdname has_rtools
#' @usage NULL
#' @export
setup_rtools <- has_rtools

#' @export
#' @rdname has_rtools
check_rtools <- function(debug = FALSE) {
  if (is_windows() && !has_rtools(debug = debug)) {
    stop("Rtools is not installed.", call. = FALSE)
  }

  TRUE
}

installed_version <- function(path, debug) {
  if (!file.exists(file.path(path, "Rtools.txt"))) {
    return(NULL)
  }

  # Find the version path
  version_path <- file.path(path, "VERSION.txt")
  if (debug) {
    cat("VERSION.txt\n")
    cat(readLines(version_path), "\n")
  }
  if (!file.exists(version_path)) {
    return(NULL)
  }

  # Rtools is in the path -- now crack the VERSION file
  contents <- NULL
  try(contents <- readLines(version_path), silent = TRUE)
  if (is.null(contents)) {
    return(NULL)
  }

  # Extract the version
  contents <- gsub("^\\s+|\\s+$", "", contents)
  version_re <- "Rtools version (\\d\\.\\d+)\\.[0-9.]+$"

  if (!grepl(version_re, contents)) {
    return(NULL)
  }

  m <- regexec(version_re, contents)
  regmatches(contents, m)[[1]][2]
}

is_compatible <- function(rtools) {
  if (is.null(rtools)) {
    return(FALSE)
  }
  if (is.null(rtools$version)) {
    return(FALSE)
  }

  stopifnot(is.rtools(rtools))
  version <- rtools$version
  version <- sub("^([0-9]+[.][0-9]+)[.].*$", "\\1", version)
  info <- version_info[[version]]
  if (is.null(info)) {
    return(FALSE)
  }

  r_version <- getRversion()
  r_version >= info$version_min && r_version <= info$version_max
}

rtools <- function(path, version, ...) {
  structure(list(version = version, path = path, ...), class = "rtools")
}
is.rtools <- function(x) inherits(x, "rtools")

#' Retrieve a text string with the rtools version needed
#'
#' @keywords internal
#' @export
rtools_needed <- function(r_version = getRversion()) {
  vi <- version_info
  vi$custom <- NULL

  for (i in rev(seq_along(vi))) {
    version <- names(vi)[i]
    info <- vi[[i]]
    ok <- r_version >= info$version_min && r_version <= info$version_max
    if (ok) {
      return(paste("Rtools", version))
    }
  }
  "the appropriate version of Rtools"
}

rtools_url <- function(needed) {
  "https://cran.r-project.org/bin/windows/Rtools/"
}

Try the pkgbuild package in your browser

Any scripts or data that you put into this service are public.

pkgbuild documentation built on Oct. 30, 2024, 9:08 a.m.