├── .gitattributes ├── inst └── rminiconda │ └── README.md ├── tests ├── testthat.R └── testthat │ └── test-install.R ├── .gitignore ├── .Rbuildignore ├── .travis.yml ├── man ├── list_installations.Rd ├── remove_miniconda.Rd ├── get_python_version.Rd ├── test_miniconda.Rd ├── is_miniconda_installed.Rd ├── find_miniconda_pip.Rd ├── find_miniconda_python.Rd ├── rminiconda_pip_uninstall.Rd ├── rminiconda_pip_install.Rd ├── get_miniconda_path.Rd └── install_miniconda.Rd ├── rminiconda.Rproj ├── NAMESPACE ├── DESCRIPTION ├── appveyor.yml ├── README.md ├── R └── rminiconda.R └── LICENSE /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | data/* binary 3 | src/* text=lf 4 | R/* text=lf 5 | -------------------------------------------------------------------------------- /inst/rminiconda/README.md: -------------------------------------------------------------------------------- 1 | This is where miniconda will go if it is user-writable. 2 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(rminiconda) 3 | 4 | test_check("rminiconda") 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rhistory 2 | .RData 3 | .Rproj.user 4 | inst/rminiconda/* 5 | !inst/rminiconda/README.md 6 | -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^appveyor\.yml$ 2 | ^\.travis\.yml$ 3 | ^.*\.Rproj$ 4 | ^\.Rproj\.user$ 5 | inst/rminiconda/general 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # R for travis: see documentation at https://docs.travis-ci.com/user/languages/r 2 | 3 | language: R 4 | sudo: false 5 | cache: packages 6 | -------------------------------------------------------------------------------- /tests/testthat/test-install.R: -------------------------------------------------------------------------------- 1 | context("test-install") 2 | 3 | test_that("Python 3 install works...", { 4 | install_miniconda() 5 | remove_miniconda() 6 | }) 7 | 8 | # test_that("Python 2 install works...", { 9 | # install_miniconda(version = 2) 10 | # remove_miniconda() 11 | # }) 12 | -------------------------------------------------------------------------------- /man/list_installations.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/rminiconda.R 3 | \name{list_installations} 4 | \alias{list_installations} 5 | \title{List all miniconda installations} 6 | \usage{ 7 | list_installations() 8 | } 9 | \description{ 10 | List all miniconda installations 11 | } 12 | -------------------------------------------------------------------------------- /rminiconda.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | 17 | BuildType: Package 18 | PackageUseDevtools: Yes 19 | PackageInstallArgs: --no-multiarch --with-keep.source 20 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(find_miniconda_pip) 4 | export(find_miniconda_python) 5 | export(get_miniconda_path) 6 | export(get_python_version) 7 | export(install_miniconda) 8 | export(is_miniconda_installed) 9 | export(list_installations) 10 | export(remove_miniconda) 11 | export(rminiconda_pip_install) 12 | export(rminiconda_pip_uninstall) 13 | export(test_miniconda) 14 | importFrom(utils,download.file) 15 | -------------------------------------------------------------------------------- /man/remove_miniconda.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/rminiconda.R 3 | \name{remove_miniconda} 4 | \alias{remove_miniconda} 5 | \title{Remove an "rminiconda" miniconda installation} 6 | \usage{ 7 | remove_miniconda(name = "general", path = get_miniconda_path()) 8 | } 9 | \arguments{ 10 | \item{name}{The name of the miniconda installation.} 11 | 12 | \item{path}{The base directory where all "rminiconda" miniconda installations are located.} 13 | } 14 | \description{ 15 | Remove an "rminiconda" miniconda installation 16 | } 17 | -------------------------------------------------------------------------------- /man/get_python_version.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/rminiconda.R 3 | \name{get_python_version} 4 | \alias{get_python_version} 5 | \title{Get the Python version of a miniconda installation} 6 | \usage{ 7 | get_python_version(name, path = get_miniconda_path()) 8 | } 9 | \arguments{ 10 | \item{name}{The name of the miniconda installation.} 11 | 12 | \item{path}{The base directory where all "rminiconda" miniconda installations are located.} 13 | } 14 | \description{ 15 | Get the Python version of a miniconda installation 16 | } 17 | -------------------------------------------------------------------------------- /man/test_miniconda.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/rminiconda.R 3 | \name{test_miniconda} 4 | \alias{test_miniconda} 5 | \title{Run a simple "hello world" test of a miniconda installation} 6 | \usage{ 7 | test_miniconda(name, path = get_miniconda_path()) 8 | } 9 | \arguments{ 10 | \item{name}{The name of the miniconda installation.} 11 | 12 | \item{path}{The base directory where all "rminiconda" miniconda installations are located.} 13 | } 14 | \description{ 15 | Run a simple "hello world" test of a miniconda installation 16 | } 17 | -------------------------------------------------------------------------------- /man/is_miniconda_installed.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/rminiconda.R 3 | \name{is_miniconda_installed} 4 | \alias{is_miniconda_installed} 5 | \title{Check if an rminiconda installation exists} 6 | \usage{ 7 | is_miniconda_installed(name = "general", path = get_miniconda_path()) 8 | } 9 | \arguments{ 10 | \item{name}{The name of the miniconda installation.} 11 | 12 | \item{path}{The base directory where all "rminiconda" miniconda installations are located.} 13 | } 14 | \description{ 15 | Check if an rminiconda installation exists 16 | } 17 | -------------------------------------------------------------------------------- /man/find_miniconda_pip.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/rminiconda.R 3 | \name{find_miniconda_pip} 4 | \alias{find_miniconda_pip} 5 | \title{Find the pip binary executable for an rminiconda installation} 6 | \usage{ 7 | find_miniconda_pip(name = "general", path = get_miniconda_path()) 8 | } 9 | \arguments{ 10 | \item{name}{The name of the miniconda installation.} 11 | 12 | \item{path}{The base directory where all "rminiconda" miniconda installations are located.} 13 | } 14 | \description{ 15 | Find the pip binary executable for an rminiconda installation 16 | } 17 | -------------------------------------------------------------------------------- /man/find_miniconda_python.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/rminiconda.R 3 | \name{find_miniconda_python} 4 | \alias{find_miniconda_python} 5 | \title{Find the python binary executable for an rminiconda installation} 6 | \usage{ 7 | find_miniconda_python(name = "general", path = get_miniconda_path()) 8 | } 9 | \arguments{ 10 | \item{name}{The name of the miniconda installation.} 11 | 12 | \item{path}{The base directory where all "rminiconda" miniconda installations are located.} 13 | } 14 | \description{ 15 | Find the python binary executable for an rminiconda installation 16 | } 17 | -------------------------------------------------------------------------------- /man/rminiconda_pip_uninstall.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/rminiconda.R 3 | \name{rminiconda_pip_uninstall} 4 | \alias{rminiconda_pip_uninstall} 5 | \title{Utility function to pip unininstall a package in an rminiconda installation} 6 | \usage{ 7 | rminiconda_pip_uninstall(pkg_name, name, args = "") 8 | } 9 | \arguments{ 10 | \item{pkg_name}{The name of the pip package to unininstall.} 11 | 12 | \item{name}{The name of the miniconda installation.} 13 | 14 | \item{args}{Optional string specifying additional arguments to pip} 15 | } 16 | \description{ 17 | Utility function to pip unininstall a package in an rminiconda installation 18 | } 19 | -------------------------------------------------------------------------------- /man/rminiconda_pip_install.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/rminiconda.R 3 | \name{rminiconda_pip_install} 4 | \alias{rminiconda_pip_install} 5 | \title{Utility function to pip install a package in an rminiconda installation} 6 | \usage{ 7 | rminiconda_pip_install(pkg_name, name, args = "") 8 | } 9 | \arguments{ 10 | \item{pkg_name}{The name of the pip package to install.} 11 | 12 | \item{name}{The name of the miniconda installation.} 13 | 14 | \item{args}{Optional string specifying additional arguments to pip, e.g. "-i https://test.pypi.org/simple/"} 15 | } 16 | \description{ 17 | Utility function to pip install a package in an rminiconda installation 18 | } 19 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: rminiconda 2 | Title: Maintain Isolated "miniconda" Python Installations 3 | Version: 0.0.1 4 | Authors@R: c( 5 | person("Ryan", "Hafen", email = "rhafen@gmail.com", role = c("aut", "cre")), 6 | person("Anaconda Inc.", role = c("aut", "cph"), 7 | comment = "Miniconda Distribution" 8 | )) 9 | Description: This package provides utilities for installing an isolated "miniconda" Python environment. It is intended mainly for use with the reticulate package, with the particular use case of allowing R users to use R packages that wrap Python libraries without them having to worry about maintaining a Python environment. 10 | License: Apache License 2.0 | file LICENSE 11 | Encoding: UTF-8 12 | LazyData: true 13 | URL: https://github.com/ki-tools/rminiconda 14 | BugReports: https://github.com/ki-tools/rminiconda/issues 15 | Suggests: 16 | testthat 17 | RoxygenNote: 6.1.1 18 | -------------------------------------------------------------------------------- /man/get_miniconda_path.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/rminiconda.R 3 | \name{get_miniconda_path} 4 | \alias{get_miniconda_path} 5 | \title{Get the path for where all "rminiconda" miniconda installations are located} 6 | \usage{ 7 | get_miniconda_path() 8 | } 9 | \description{ 10 | Get the path for where all "rminiconda" miniconda installations are located 11 | } 12 | \details{ 13 | The goal of rminiconda is to provide isolated installations of Python via miniconda that the user doesn't have to worry about. Because of this, the intention is to have a default location for the installations that is outside the user's view. 14 | 15 | By default, the path will be the installed "rminiconda" package directory, if writable by the user. If not, the "fallback" path will be a "rminiconda" directory in the user's home directory. If you would like to use a different directory for your rminiconda installations, set an environment variable \code{R_MINICONDA_PATH}. 16 | } 17 | -------------------------------------------------------------------------------- /man/install_miniconda.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/rminiconda.R 3 | \name{install_miniconda} 4 | \alias{install_miniconda} 5 | \title{Install miniconda} 6 | \usage{ 7 | install_miniconda(version = 3, path = get_miniconda_path(), 8 | name = "general") 9 | } 10 | \arguments{ 11 | \item{version}{The major version number of Python (2 or 3). The latest version of the specified major version will be installed.} 12 | 13 | \item{path}{The base directory where all "rminiconda" miniconda installations are located (see \code{\link{get_miniconda_path}} for more information).} 14 | 15 | \item{name}{The name of the installation.} 16 | } 17 | \value{ 18 | \code{NULL} (miniconda is installed to a system directory). 19 | } 20 | \description{ 21 | Download the miniconda installer and run it. 22 | } 23 | \details{ 24 | The \code{name} can be thought of as a project name with which to associate your miniconda installation. The miniconda installation will go in \code{{path}/{name}}. You can have different installations for different purposes. 25 | } 26 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # DO NOT CHANGE the "init" and "install" sections below 2 | 3 | platform: x64 4 | 5 | environment: 6 | R_ARCH: x64 7 | PKGTYPE: binary 8 | 9 | # Download script file from GitHub 10 | init: 11 | ps: | 12 | $ErrorActionPreference = "Stop" 13 | Invoke-WebRequest http://raw.github.com/krlmlr/r-appveyor/master/scripts/appveyor-tool.ps1 -OutFile "..\appveyor-tool.ps1" 14 | Import-Module '..\appveyor-tool.ps1' 15 | 16 | install: 17 | ps: Bootstrap 18 | 19 | cache: 20 | - C:\RLibrary 21 | 22 | # Adapt as necessary starting from here 23 | 24 | build_script: 25 | - travis-tool.sh install_deps 26 | 27 | test_script: 28 | - travis-tool.sh run_tests 29 | 30 | on_failure: 31 | - 7z a failure.zip *.Rcheck\* 32 | - appveyor PushArtifact failure.zip 33 | 34 | artifacts: 35 | - path: '*.Rcheck\**\*.log' 36 | name: Logs 37 | 38 | - path: '*.Rcheck\**\*.out' 39 | name: Logs 40 | 41 | - path: '*.Rcheck\**\*.fail' 42 | name: Logs 43 | 44 | - path: '*.Rcheck\**\*.Rout' 45 | name: Logs 46 | 47 | - path: '\*_*.tar.gz' 48 | name: Bits 49 | 50 | - path: '\*_*.zip' 51 | name: Bits 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rminiconda 2 | 3 | [![Travis build status](https://travis-ci.org/hafen/rminiconda.svg?branch=master)](https://travis-ci.org/hafen/rminiconda) 4 | [![AppVeyor build status](https://ci.appveyor.com/api/projects/status/github/hafen/rminiconda?branch=master&svg=true)](https://ci.appveyor.com/project/hafen/rminiconda) 5 | 6 | This R package provides utilities for installing isolated "miniconda" Python environments. 7 | 8 | It is intended mainly for use with the [reticulate](https://rstudio.github.io/reticulate/) package, with the particular use case of enabling R package developers to write R packages that wrap Python libraries without their users having to worry about installing or configuring anything outside of R. 9 | 10 | ## Motivation 11 | 12 | The amazing [reticulate](https://rstudio.github.io/reticulate/) package opens up access to many data analysis methods implemented in Python without needing to leave R. One major hurdle, however, is that for users to use reticulate, they must have a properly installed and configured Python environment with the right Python packages installed, etc. 13 | 14 | While reticulate provides some great utilities for finding Python, installing Python packages, and maintaining Python environments, it is inevitable that at some point a user will need to do something manually in their system outside of R to get their Python environment installed or configured properly (I have encountered this countless times...). This expectation is fine for many users, but for R package developers who are wrapping Python packages but want a completely seamless experience for their users, this is less than ideal. 15 | 16 | The rminiconda package provides a simple R function that installs [miniconda](https://docs.conda.io/en/latest/miniconda.html) in an isolated, "namespaced" location that you can fully customize for your particular use case. It also provides utilities for making this installation and configuration part of an R package setup. The miniconda Python installations provided by rminiconda do not interfere with any other Python installation on your system. It works on Linux, MacOS, and Windows. 17 | 18 | ## Install 19 | 20 | You can install rminiconda from github with: 21 | 22 | ``` r 23 | # install.packages("remotes") # if not installed 24 | remotes::install_github("hafen/rminiconda") 25 | ``` 26 | 27 | ## Standalone Usage 28 | 29 | If you want to install an isolated miniconda for your own uses, you can simply call `install_miniconda()`. 30 | 31 | ``` r 32 | rminiconda::install_miniconda(name = "my_python") 33 | ``` 34 | 35 | This will place an isolated miniconda installation in a directory called `"my_python` in a base directory that houses all miniconda installations installed through rminiconda. The base directory is determined based on the operating system: 36 | 37 | - Windows: `%APPDATA%\rminiconda` 38 | - Linux: `~/.rminiconda` 39 | - MaxOS: `~/Library/rminiconda` 40 | 41 | You can specify for this installation to be used with reticulate with the following: 42 | 43 | ```r 44 | py <- rminiconda::find_miniconda_python("my_python") 45 | reticulate::use_python(py, required = TRUE) 46 | ``` 47 | 48 | You can install either Python version 2 or 3 with the `version` argument. Also, you can maintain as many miniconda installations as you would like by using different names for each one. 49 | 50 | ```r 51 | rminiconda::install_miniconda(version = 2, name = "my_python2") 52 | ``` 53 | 54 | Note that currently rminiconda only installs the latest miniconda for Python 2 and Python 3. Installing specific Python versions may be supported in the future. 55 | 56 | ## Usage in an R Package 57 | 58 | If you are writing an R package that depends on a Python library but you don't want your users to worry about any aspect of Python installation and configuration, you can use rminiconda to configure your users's environment for them. 59 | 60 | Suppose, for example, that you want to wrap functionality in the Python [shap](https://github.com/slundberg/shap) package in your own R package (Note that this has already been done with [shapper](https://github.com/ModelOriented/shapper) - this is just an example). Suppose you have named this package "shapr". A recipe for using rminiconda as part of your package might look something like this: 61 | 62 | ```r 63 | #' @import rminiconda 64 | .onLoad <- function(libname, pkgname) { 65 | # Undesirable side-effects but if not unset, can lead to config issues 66 | Sys.setenv(PYTHONHOME = "") 67 | Sys.setenv(PYTHONPATH = "") 68 | is_configured() 69 | } 70 | 71 | # Check to see if the shapr Python environment has been configured 72 | is_configured <- function() { 73 | # Should also check that the required packages are installed 74 | if (!rminiconda::is_miniconda_installed("shapr")) { 75 | message("It appears that shapr has not been configured...") 76 | message("Run 'shapr_configure()' for a one-time setup.") 77 | return (FALSE) 78 | } else { 79 | py <- rminiconda::find_miniconda_python("shapr") 80 | reticulate::use_python(py, required = TRUE) 81 | return (TRUE) 82 | } 83 | } 84 | 85 | #' One-time configuration of environment for shapr 86 | #' 87 | #' @details This installs an isolated Python distribution along with required dependencies so that the shapr R package can seamlessly wrap the shap Python package. 88 | #' @export 89 | shapr_configure <- function() { 90 | # Install isolated miniconda 91 | if (!rminiconda::is_miniconda_installed("shapr")) 92 | rminiconda::install_miniconda(version = 3, name = "shapr") 93 | # Install python packages 94 | py <- rminiconda::find_miniconda_python("shapr") 95 | rminiconda::rminiconda_pip_install("shap", "shapr") 96 | reticulate::use_python(py, required = TRUE) 97 | } 98 | ``` 99 | 100 | You might optionally want to check to see if the user already has a non-rminiconda Python environment properly configured and use that in that case. 101 | 102 | You might have a collection of R packages that wrap Python libraries, for example, maybe relating to different parts of an ML pipeline, and "shapr" is just one of them. In that case you could use a common Python installation namespace across all packages, such as "ml-pipeline", and use that across all of your package configurations. 103 | 104 | ## Development Status 105 | 106 | I'm interested to see the general level of interest in the existence of a package such as this and welcome feedback and discussion with those who know more than I do in this area to help it get a "production-ready" stamp of approval. Please use [Github issues](https://github.com/hafen/rminiconda/issues) to engage. 107 | 108 | ## Acknowledgements 109 | 110 | Thanks to Andrew Elgert for discussions on approaches for Python installation. 111 | -------------------------------------------------------------------------------- /R/rminiconda.R: -------------------------------------------------------------------------------- 1 | #' Install miniconda 2 | #' 3 | #' Download the miniconda installer and run it. 4 | #' 5 | #' @param version The major version number of Python (2 or 3). The latest version of the specified major version will be installed. 6 | #' @param path The base directory where all "rminiconda" miniconda installations are located (see \code{\link{get_miniconda_path}} for more information). 7 | #' @param name The name of the installation. 8 | #' @return \code{NULL} (miniconda is installed to a system directory). 9 | #' @details The \code{name} can be thought of as a project name with which to associate your miniconda installation. The miniconda installation will go in \code{{path}/{name}}. You can have different installations for different purposes. 10 | #' @importFrom utils download.file 11 | #' @export 12 | install_miniconda <- function(version = 3, 13 | path = get_miniconda_path(), 14 | name = "general") { 15 | 16 | ## Set up paths 17 | dest_path <- normalizePath(file.path(path, name), mustWork = FALSE) 18 | if (dir.exists(dest_path)) 19 | stop("An installation already exists at:\n", dest_path, "\n", 20 | "If you'd like to install a fresh version, first run:\n", 21 | paste0("remove_miniconda(path = \"", path, "\", name = \"", name, "\")")) 22 | 23 | message("Using path for conda installation:\n ", dest_path) 24 | 25 | ## Work in a temporary directory and move back to original wd on exit 26 | owd <- setwd(tempdir()) 27 | on.exit(setwd(owd), add = TRUE) 28 | 29 | ## Check version 30 | # For now, it's just latest Python 2 or 3. 31 | # It could be exact Python version but not now because we would 32 | # have to map exact conda versions to exact Python versions. 33 | # https://repo.anaconda.com/miniconda/ 34 | if (!version %in% c(2, 3)) 35 | stop("'version' must be 2 or 3.") 36 | 37 | base_url <- "https://repo.anaconda.com/miniconda/" 38 | arch <- paste0("x86", ifelse(.Machine$sizeof.pointer == 8, "_64", "")) 39 | 40 | if (is_windows()) { 41 | inst_file <- sprintf("Miniconda%s-latest-Windows-%s.exe", version, arch) 42 | inst_cmd <- inst_file 43 | inst_args <- sprintf(" /InstallationType=JustMe /RegisterPython=0 /S /D=%s", 44 | dest_path) 45 | } else if (is_osx()) { 46 | inst_file <- sprintf("Miniconda%s-latest-MacOSX-%s.sh", version, arch) 47 | inst_cmd <- "bash" 48 | inst_args <- sprintf(" %s -b -p \"%s\"", inst_file, dest_path) 49 | } else if (is_linux()) { 50 | inst_file <- sprintf("Miniconda%s-latest-Linux-%s.sh", version, arch) 51 | inst_cmd <- "bash" 52 | inst_args <- sprintf(" %s -b -p %s", inst_file, dest_path) 53 | } else { 54 | # Unsupported platform, like Solaris 55 | message("Sorry, this platform is not supported.") 56 | return(invisible()) 57 | } 58 | ## Download 59 | message("Downloading miniconda installer...") 60 | message("Source: ", paste0(base_url, inst_file)) 61 | message("Destination: ", dest_path) 62 | dl_res <- utils::download.file(paste0(base_url, inst_file), inst_file, 63 | mode = "wb") 64 | if (dl_res != 0 || !file.exists(inst_file)) 65 | stop("There was an issue downloading the file\n", 66 | paste0(base_url, inst_file), 67 | "\n", 68 | "Please check your version number.", 69 | call. = FALSE) 70 | 71 | message("By installing, you accept the Conda license:") 72 | message(" https://conda.io/en/latest/license.html") 73 | 74 | ## Install 75 | message("Installing isolated miniconda distribution...") 76 | inst_res <- system2(inst_cmd, inst_args) 77 | if (inst_res != 0) 78 | stop("There was a problem installing miniconda.", call. = FALSE) 79 | 80 | ## Check installation 81 | res <- test_miniconda(name, path = path) 82 | if (res != "hello world") 83 | stop("Installation was not successful.", call. = FALSE) 84 | 85 | py_version <- get_python_version(name, path = path) 86 | writeLines(c(py_version, inst_file), file.path(dest_path, "info.txt")) 87 | 88 | message("miniconda installation successful!") 89 | invisible(TRUE) 90 | } 91 | 92 | #' Run a simple "hello world" test of a miniconda installation 93 | #' @param name The name of the miniconda installation. 94 | #' @param path The base directory where all "rminiconda" miniconda installations are located. 95 | #' @export 96 | test_miniconda <- function(name, path = get_miniconda_path()) { 97 | python_bin <- find_miniconda_python(name, path) 98 | 99 | try(system2(python_bin, " -c \"print('hello world')\"", 100 | stdout = TRUE, stderr = TRUE), silent = TRUE) 101 | } 102 | 103 | #' Get the Python version of a miniconda installation 104 | #' @param name The name of the miniconda installation. 105 | #' @param path The base directory where all "rminiconda" miniconda installations are located. 106 | #' @export 107 | get_python_version <- function(name, path = get_miniconda_path()) { 108 | python_bin <- find_miniconda_python(name, path) 109 | lines <- c( 110 | "import sys", 111 | "ver = sys.version_info", 112 | "print(str(ver.major) + '.' + str(ver.minor) + '.' + str(ver.micro))" 113 | ) 114 | 115 | try(system2(python_bin, paste0( 116 | " -c \"", paste(lines, collapse = "; "), "\""), 117 | stdout = TRUE, stderr = TRUE), silent = TRUE) 118 | } 119 | 120 | #' Get the path for where all "rminiconda" miniconda installations are located 121 | #' @details The goal of rminiconda is to provide isolated installations of Python via miniconda that the user doesn't have to worry about. Because of this, the intention is to have a default location for the installations that is outside the user's view. 122 | #' 123 | #' By default, the path will be the installed "rminiconda" package directory, if writable by the user. If not, the "fallback" path will be a "rminiconda" directory in the user's home directory. If you would like to use a different directory for your rminiconda installations, set an environment variable \code{R_MINICONDA_PATH}. 124 | #' @export 125 | get_miniconda_path <- function() { 126 | path <- Sys.getenv("R_MINICONDA_PATH") 127 | if (path != "") { 128 | path <- file.path(path) 129 | if (!dir.exists(path)) 130 | dir.create(path) 131 | return(path) 132 | } 133 | 134 | if (is_windows()) { 135 | path <- Sys.getenv("APPDATA") 136 | if (path == "") 137 | stop("Environment variable 'APPDATA' not set.") 138 | path <- file.path(path, "rminiconda") 139 | if (!dir.exists(path)) 140 | dir.create(path) 141 | return(path) 142 | } 143 | 144 | if (is_linux()) { 145 | path <- file.path(path.expand("~"), ".rminiconda") 146 | if (!dir.exists(path)) 147 | dir.create(path) 148 | return(path) 149 | } 150 | 151 | if (is_osx()) { 152 | path <- file.path(path.expand("~/Library"), "rminiconda") 153 | if (!dir.exists(path)) 154 | dir.create(path) 155 | return(path) 156 | } 157 | 158 | # if (file.access(path, mode = 2) == 0) # writeable 159 | 160 | stop("Not a supported OS.") 161 | } 162 | 163 | #' Find the python binary executable for an rminiconda installation 164 | #' @param name The name of the miniconda installation. 165 | #' @param path The base directory where all "rminiconda" miniconda installations are located. 166 | #' @export 167 | find_miniconda_python <- function( 168 | name = "general", path = get_miniconda_path()) { 169 | if (is_windows()) { 170 | normalizePath(file.path(path, name, "python.exe")) 171 | } else { 172 | normalizePath(file.path(path, name, "bin", "python")) 173 | } 174 | } 175 | 176 | #' Check if an rminiconda installation exists 177 | #' @param name The name of the miniconda installation. 178 | #' @param path The base directory where all "rminiconda" miniconda installations are located. 179 | #' @export 180 | is_miniconda_installed <- function( 181 | name = "general", path = get_miniconda_path()) { 182 | py <- suppressWarnings(find_miniconda_python(name, path)) 183 | file.exists(py) 184 | } 185 | 186 | #' Find the pip binary executable for an rminiconda installation 187 | #' @param name The name of the miniconda installation. 188 | #' @param path The base directory where all "rminiconda" miniconda installations are located. 189 | #' @export 190 | find_miniconda_pip <- function( 191 | name = "general", path = get_miniconda_path()) { 192 | if (is_windows()) { 193 | normalizePath(file.path(path, name, "Scripts", "pip.exe")) 194 | } else { 195 | normalizePath(file.path(path, name, "bin", "pip")) 196 | } 197 | } 198 | 199 | #' Remove an "rminiconda" miniconda installation 200 | #' @param name The name of the miniconda installation. 201 | #' @param path The base directory where all "rminiconda" miniconda installations are located. 202 | #' @export 203 | remove_miniconda <- function( 204 | name = "general", path = get_miniconda_path()) { 205 | pth <- file.path(path, name) 206 | if (!dir.exists(pth)) { 207 | message("There is not a miniconda installation at:\n", pth) 208 | return (invisible(FALSE)) 209 | } 210 | 211 | if (!file.exists(file.path(pth, "bin", "python"))) { 212 | message("The supplied path does not contain a miniconda installation:\n", 213 | pth) 214 | return (invisible(FALSE)) 215 | } 216 | 217 | message( 218 | "You are about to remove the following directory:\n ", 219 | pth, "\n", 220 | "Are you sure you want to do this? (Y/n) ") 221 | ans <- tolower(readline()) 222 | if (ans == "y" || ans == "") { 223 | message("Removing miniconda installation, '", name, "'...") 224 | unlink(pth, recursive = TRUE) 225 | } 226 | } 227 | 228 | #' List all miniconda installations 229 | #' @export 230 | list_installations <- function() { 231 | path <- get_miniconda_path() 232 | dirs <- list.dirs(path, recursive = FALSE) 233 | if (length(dirs) == 0) { 234 | message("No miniconda installations found in the rminiconda directory:\n", 235 | path) 236 | } else { 237 | lapply(dirs, function(dr) { 238 | ver <- readLines(file.path(dr, "info.txt"))[1] 239 | message(paste0(basename(dr), " (Python ", ver, "):")) 240 | message(paste0(" ", dr)) 241 | }) 242 | } 243 | invisible() 244 | } 245 | 246 | #' Utility function to pip install a package in an rminiconda installation 247 | #' 248 | #' @param pkg_name The name of the pip package to install. 249 | #' @param name The name of the miniconda installation. 250 | #' @param args Optional string specifying additional arguments to pip, e.g. "-i https://test.pypi.org/simple/" 251 | #' @export 252 | rminiconda_pip_install <- function(pkg_name, name, args = "") { 253 | pip <- find_miniconda_pip(name) 254 | args <- paste0(" install ", pkg_name, " ", args) 255 | res <- system2(pip, args) 256 | if (res != 0) 257 | warning("There was an issue installing Python module '", name, "'.") 258 | } 259 | 260 | #' Utility function to pip unininstall a package in an rminiconda installation 261 | #' 262 | #' @param pkg_name The name of the pip package to unininstall. 263 | #' @param name The name of the miniconda installation. 264 | #' @param args Optional string specifying additional arguments to pip 265 | #' @export 266 | rminiconda_pip_uninstall <- function(pkg_name, name, args = "") { 267 | pip <- find_miniconda_pip(name) 268 | args <- paste0(" uninstall ", pkg_name, " ", args) 269 | res <- system2(pip, args) 270 | if (res != 0) 271 | warning("There was an issue installing Python module '", name, "'.") 272 | } 273 | 274 | is_windows <- function() .Platform$OS.type == "windows" 275 | is_osx <- function() Sys.info()[["sysname"]] == "Darwin" 276 | is_linux <- function() Sys.info()[["sysname"]] == "Linux" 277 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright (C) 2019 by Bill and Melinda Gates Foundation 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------