R/install.R

Defines functions accept_license_prompt cleanup_installation install_system_image sysimage_do_replace sysimage_save_replace_command sysimage_replace_prefs_file sysimage_load_install_path sysimage_save_install_path sysimage_path_prefs_file sysimage_default_install_dir get_iai_versions get_iai_version_info iai_download_url get_latest_iai_version install_julia julia_path_prefs_file get_prefs_dir julia_default_depot julia_latest_version julia_default_install_dir

Documented in cleanup_installation install_julia install_system_image

APPNAME <- "InterpretableAI"

# Install Julia to our directory rather than JuliaCall by default
julia_default_install_dir <- function() {
  file.path(path.expand(rappdirs::user_data_dir(APPNAME)), "julia")
}
# Filter list of stable versions to get the most recent that has an IAI release
julia_latest_version <- function() {
  url <- "https://julialang-s3.julialang.org/bin/versions.json"
  file <- tempfile()
  utils::download.file(url, file)
  versions <- rjson::fromJSON(file = file)

  iai_versions <- get_iai_version_info()

  max(intersect(
      names(Filter(function(v) v$stable, versions)),
      names(iai_versions)
  ))
}



# IMPORTANT: these functions deliberately don't use Julia to find the depot path
#            so that the prefs path can be detected without loading Julia. This
#            is needed so that we can load prefs and process anything that needs
#            to be done before initializing Julia (eg loading the sysimage path)
julia_default_depot <- function() {
  key <- if (Sys.info()["sysname"] == "Windows") {
    "USERPROFILE"
  } else {
    "HOME"
  }
  return(file.path(Sys.getenv(key), ".julia"))
}
get_prefs_dir <- function() {
  depot <- Sys.getenv("JULIA_DEPOT_PATH", unset = julia_default_depot())
  prefs <- file.path(depot, "prefs")
  dir.create(prefs, recursive = TRUE, showWarnings = FALSE)
  prefs
}
# The location of the file saved by `JuliaCall::julia_save_install_dir`
julia_path_prefs_file <- function() {
  file.path(get_prefs_dir(), "JuliaCall")
}


#' Download and install Julia automatically.
#'
#' @param version The version of Julia to install (e.g. \code{"1.6.3"}).
#'                Defaults to \code{"latest"}, which will install the most
#'                recent stable release.
#' @param prefix The directory where Julia will be installed. Defaults to a
#'               location determined by
#'               \href{https://www.rdocumentation.org/packages/rappdirs/topics/user_data_dir}{\code{rappdirs::user_data_dir}}.
#'
#' @examples \dontrun{iai::install_julia()}
#'
#' @export
install_julia <- function(version = "latest",
                          prefix = julia_default_install_dir()) {
  if (version == "latest") {
    version <- julia_latest_version() # nocov
  }
  JuliaCall::install_julia(version = version, prefix = prefix)
}


#' @importFrom utils tail
get_latest_iai_version <- function(versions) {
  vs <- names(versions)
  tail(vs[vs != "dev"], n = 1)
}


iai_download_url <- function(iai_versions, version) {
  if (startsWith(version, "v")) {
    version <- substr(version, 2, nchar(version))
  }

  v <- iai_versions[[version]]
  if (is.null(v)) {
    available <- paste(names(iai_versions), collapse = ", ")
    stop(paste("IAI version ", version, " not available for this version of ",
               "Julia. Available versions are: ", available, sep = ""))
  }
  v
}


get_iai_version_info <- function() {
  url <- "https://docs.interpretable.ai/versions.json"
  file <- tempfile()
  utils::download.file(url, file)
  versions <- rjson::fromJSON(file = file)

  sysname <- Sys.info()["sysname"]
  sysmachine <- Sys.info()["machine"]

  os <- if (sysname == "Linux") {
    "linux"
  } else if (sysname == "Darwin") {
    ifelse(sysmachine == "arm64", "macos_aarch64", "macos")
  } else if (sysname == "Windows") {
    "win64"
  } else {
    stop("Unknown or unsupported OS") # nocov
  }
  return(versions[[os]])
}

get_iai_versions <- function(julia_version) {
  info <- get_iai_version_info()
  return(info[[julia_version]])
}

sysimage_default_install_dir <- function() {
  file.path(path.expand(rappdirs::user_data_dir(APPNAME)), "sysimage")
}
sysimage_path_prefs_file <- function() {
  file.path(get_prefs_dir(), "IAI")
}
sysimage_save_install_path <- function(path) {
  # Save sysimg path so that it can be used automatically in future
  cat(path, file = sysimage_path_prefs_file())
}
sysimage_load_install_path <- function() {
  path <- sysimage_path_prefs_file()
  if (file.exists(path)) {
    readChar(path, 256)
  } else {
    NULL
  }
}


sysimage_replace_prefs_file <- function() {
  # Temporary file to save our replace command in
  file.path(get_prefs_dir(), "IAI-replacedefault")
}
sysimage_save_replace_command <- function(image_path, target_path) {
  # Save the original path of the image and the new target path so that we can
  # process the copy during the next R session before Julia is initialized
  writeLines(c(image_path, target_path), con = sysimage_replace_prefs_file())
}
sysimage_do_replace <- function(image_path, target_path) {
  # Copy the image to the new path
  file.remove(target_path)
  file.copy(image_path, target_path)
  message(paste("Replacing default system image at", target_path,
                "with IAI system image"))
}


#' Download and install the IAI system image automatically.
#'
#' @param version The version of the IAI system image to install (e.g.
#'                \code{"2.1.0"}). Defaults to \code{"latest"}, which will
#'                install the most recent release.
#' @param replace_default Whether to replace the default Julia system image with
#'                        the downloaded IAI system image. Defaults to
#'                        \code{FALSE}.
#' @param prefix The directory where the IAI system image will be installed.
#'               Defaults to a location determined by
#'               \href{https://www.rdocumentation.org/packages/rappdirs/topics/user_data_dir}{\code{rappdirs::user_data_dir}}.
#' @param accept_license Set to \code{TRUE} to confirm that you agree to the
#'                       \href{https://docs.interpretable.ai/End_User_License_Agreement.pdf}{End User License Agreement}
#'                       and skip the interactive confirmation dialog.
#'
#' @examples \dontrun{iai::install_system_image()}
#'
#' @export
install_system_image <- function(version = "latest", replace_default = FALSE,
                                 prefix = sysimage_default_install_dir(),
                                 accept_license = FALSE) {
  if (!accept_license && !accept_license_prompt()) {
    stop("The license agreement was not accepted, aborting installation")
  }

  iai_run_julia_setup()
  julia_version <- JuliaCall::julia_eval("string(VERSION)")
  iai_versions <- get_iai_versions(julia_version)

  if (version == "latest") {
    version <- get_latest_iai_version(iai_versions)
  }

  url <- iai_download_url(iai_versions, version)

  file <- tempfile()
  tryCatch({
    utils::download.file(url, file)
  }, error = function(err) { # nocov start
    stop(paste("Error downloading IAI system image ", version, " for Julia v",
               julia_version, ". ",
               "This version may not exist, or there could be network ",
               "issues. It might be resolved by re-running ",
               "`install_system_image`.",
               sep = ""))
  }) # nocov end

  if (version != "dev") {
    version <- paste("v", version, sep = "")
  }
  dest <- file.path(prefix, version)
  utils::unzip(file, exdir = dest)

  sysname <- Sys.info()["sysname"]
  image_name <- if (sysname == "Linux") {
    "sys.so"
  } else if (sysname == "Darwin") {
    "sys.dylib"
  } else if (sysname == "Windows") {
    "sys.dll"
  } else {
    stop("Unknown or unsupported OS") # nocov
  }
  image_path <- file.path(dest, image_name)

  sysimage_save_install_path(image_path)
  message(paste("Installed IAI system image to", dest))

  # Run init step to fix packages to right versions (in case JuliaCall installed
  # incompatible versions before IAI was added)
  # On Windows, Julia stdout won't show in RGui/RStudio, so we need to run the
  # command using `system` from R so that the output is shown
  cmd <- paste(
      '"', JuliaCall::julia_eval("Base.julia_cmd()[1]"), '" ',
      '--sysimage="', image_path, '" ',
      '-e nothing',
  sep = "")
  exitcode <- system(cmd)
  stopifnot(exitcode == 0)

  if (replace_default) {
    target_path <- file.path(
        JuliaCall::julia_eval("unsafe_string(Base.JLOptions().julia_bindir)"),
        "../lib/julia",
        image_name
    )
    # Windows can't replace the current sysimg as it is loaded into this session
    # so we save a command to run later
    if (sysname == "Windows") {
      sysimage_save_replace_command(image_path, target_path)
    } else {
      sysimage_do_replace(image_path, target_path)
    }
  }

  # Need to restart R to load with the system image before IAI can be used
  pkg.env$needs_restart <- TRUE
  invisible(TRUE)
}


#' Remove all traces of automatic Julia/IAI installation
#'
#' Removes files created by \code{\link{install_julia}} and
#' \code{\link{install_system_image}}
#'
#' @examples \dontrun{iai::cleanup_installation()}
#'
#' @export
cleanup_installation <- function() {
  files <- c(julia_path_prefs_file(), sysimage_path_prefs_file(),
             sysimage_replace_prefs_file())
  for (f in files) {
    if (file.exists(f)) {
      file.remove(f)
    }
  }

  paths <- c(julia_default_install_dir(), sysimage_default_install_dir())
  for (p in paths) {
    if (dir.exists(p)) {
      unlink(p, recursive = TRUE) # nocov
    }
  }
}


#' @importFrom utils askYesNo
accept_license_prompt <- function() {
  if (interactive()) { # nocov start
    message(paste("In order to continue the installation process, please",
                  "review the license agreement."))
    invisible(readline(prompt = "Press [ENTER] to continue..."))

    url <- "https://docs.interpretable.ai/End_User_License_Agreement.md"
    file <- tempfile()
    tryCatch({
      utils::download.file(url, file, quiet = TRUE)
    }, error = function(err) {
      stop("Error downloading license agreement")
    })

    file.show(file)
    rm(file)

    isTRUE(askYesNo("Do you accept the license terms?", default = FALSE))

  } else { # nocov end
    message(paste("R is not running in interactive mode, so cannot show",
                  "license confirmation dialog. Please run in an interactive R",
                  "session, or pass `accept_license = TRUE` to",
                  "`install_system_image`."))
    return(FALSE)
  }
}

Try the iai package in your browser

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

iai documentation built on July 9, 2023, 5:41 p.m.