diff --git a/DESCRIPTION b/DESCRIPTION index 9ab36f98c..9641932fe 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: tinytex Type: Package Title: Helper Functions to Install and Maintain TeX Live, and Compile LaTeX Documents -Version: 0.58.6 +Version: 0.58.7 Authors@R: c( person("Yihui", "Xie", role = c("aut", "cre", "cph"), email = "xie@yihui.name", comment = c(ORCID = "0000-0003-0645-5666")), person(given = "Posit Software, PBC", role = c("cph", "fnd")), diff --git a/LICENSE b/LICENSE index 353c85d0c..a3834cb47 100644 --- a/LICENSE +++ b/LICENSE @@ -1,2 +1,2 @@ -YEAR: 2017-2024 +YEAR: 2017-2026 COPYRIGHT HOLDER: Yihui Xie and Posit Software, PBC diff --git a/R/install.R b/R/install.R index cb22df8ea..bc98ed440 100644 --- a/R/install.R +++ b/R/install.R @@ -33,8 +33,8 @@ #' installed. By default, a vector of all currently installed LaTeX packages #' if an existing installation of TinyTeX is found. If you want a fresh #' installation, you may use \code{extra_packages = NULL}. -#' @param add_path Whether to run the command \command{tlmgr path add} to add -#' the bin path of TeX Live to the system environment variable \var{PATH}. +#' @param add_path Whether to add the bin path of TeX Live to the system +#' environment variable \var{PATH}. See \code{\link{tlmgr_path}()}. #' @references See the TinyTeX documentation (\url{https://yihui.org/tinytex/}) #' for the default installation directories on different platforms. #' @note If you really want to disable the installation, you may set the @@ -113,7 +113,6 @@ install_tinytex = function( switch( os, 'unix' = { - check_local_bin() if (os_index != 3 && !any(dir_exists(c('~/bin', '~/.local/bin')))) on.exit(message( 'You may have to restart your system after installing TinyTeX to make sure ', '~/bin appears in your PATH variable (https://github.com/rstudio/tinytex/issues/16).' @@ -290,25 +289,43 @@ win_app_dir = function(s) { # test if path is pure ASCII and has no spaces valid_path = function(x) grepl('^[!-~]+$', x) -# check if /usr/local/bin on macOS is writable -check_local_bin = function() { - if (os_index != 3 || is_writable(p <- '/usr/local/bin')) return() - message( - 'The directory ', p, ' is not writable. I recommend that you make it writable. ', - 'See https://github.com/rstudio/tinytex/issues/24 for more info.' +osascript = function(cmd) { + message("Requesting admin privilege to run: sudo ", cmd) + escaped = gsub('"', '\\"', cmd, fixed = TRUE) + ret = system(sprintf( + "/usr/bin/osascript -e 'do shell script \"%s\" with administrator privileges'", escaped + )) + if (ret != 0) warning( + "Please run the above command in your Terminal (password required).", call. = FALSE ) - if (!dir_exists(p)) osascript(paste('mkdir -p', p)) - user = system2('whoami', stdout = TRUE) - osascript(sprintf('chown -R %s:admin %s', user, p)) + ret } -osascript = function(cmd) { - if (system(sprintf( - "/usr/bin/osascript -e 'do shell script \"%s\" with administrator privileges'", cmd - )) != 0) warning( - "Please run this command in your Terminal (password required):\n sudo ", - cmd, call. = FALSE - ) +# on macOS, if the user doesn't have write permission to /usr/local/bin, we use +# /etc/paths.d instead +use_paths_d = function() { + is_macos() && file.access('/usr/local/bin', 2) != 0 +} + +# add/remove TinyTeX's bin path to/from /etc/paths.d/TinyTeX on macOS; +# if adding and the file already contains the desired path, skip the operation +macos_path = function(dir = NULL, action = 'add') { + paths_file = '/etc/paths.d/TinyTeX' + add = action == 'add' + cmd = if (add) { + if (is.null(dir) || dir == '') return(1L) + if (file.exists(paths_file) && + identical(readLines(paths_file, warn = FALSE), dir)) + return(0L) + tmp = tempfile() + writeLines(dir, tmp) + sprintf('cp "%s" "%s"', tmp, paths_file) + } else { + sprintf('rm -f "%s"', paths_file) + } + ret = osascript(cmd) + if (add && ret == 0) unlink(tmp) + ret } install_tinytex_source = function(repo = '', dir, version, add_path, extra_packages) { @@ -587,9 +604,9 @@ download_installer = function(file, version) { #' #' The function \code{copy_tinytex()} copies the existing TinyTeX installation #' to another directory (e.g., a portable device like a USB stick). The function -#' \code{use_tinytex()} runs \command{tlmgr path add} to add the copy of TinyTeX -#' in an existing folder to the \code{PATH} variable of the current system, so -#' that you can use utilities such as \command{tlmgr} and \command{pdflatex}, +#' \code{use_tinytex()} adds the copy of TinyTeX in an existing folder to the +#' \code{PATH} variable of the current system via \code{\link{tlmgr_path}()}, +#' so that you can use utilities such as \command{tlmgr} and \command{pdflatex}, #' etc. #' @param from The root directory of the TinyTeX installation. For #' \code{copy_tinytex()}, the default value \code{tinytex_root()} should be a @@ -632,7 +649,8 @@ use_tinytex = function(from = select_dir('Select TinyTeX Directory')) { if (length(d) != 1) stop("The directory '", from, "' does not contain TinyTeX.") p = file.path(d, 'tlmgr') if (os == 'windows') p = paste0(p, '.bat') - if (system2(p, c('path', 'add')) != 0) stop( + ret = if (use_paths_d()) macos_path(normalizePath(d)) else system2(p, c('path', 'add')) + if (ret != 0) warning( "Failed to add '", d, "' to your system's environment variable PATH. You may ", "consider the fallback approach, i.e., set options(tinytex.tlmgr.path = '", p, "')." ) diff --git a/R/tlmgr.R b/R/tlmgr.R index 3e94540e2..bd3fbcc32 100644 --- a/R/tlmgr.R +++ b/R/tlmgr.R @@ -59,12 +59,15 @@ tlmgr = function(args = character(), usermode = FALSE, ..., .quiet = FALSE) { # check if it is necessary to add ~/Library/TinyTeX/bin/*/ to PATH #' @importFrom xfun is_linux is_unix is_macos is_windows with_ext -tweak_path = function() { - # check tlmgr exists under the default installation dir of TinyTeX, or the - # global option tinytex.tlmgr.path +# return the bin directory of TinyTeX (empty string if not found) +find_tinytex_bin = function() { f = getOption('tinytex.tlmgr.path', find_tlmgr(extra = TRUE)) - if (length(f) == 0 || !file_test('-x', f)) return() - bin = normalizePath(dirname(f)) + if (length(f) == 0 || !file_test('-x', f)) '' else normalizePath(dirname(f)) +} + +tweak_path = function() { + bin = find_tinytex_bin() + if (bin == '') return() # if the pdftex from TinyTeX is already on PATH, no need to adjust the PATH if ((p <- Sys.which('pdftex')) != '') { p2 = with_ext(file.path(bin, 'pdftex'), xfun::file_ext(p)) @@ -258,13 +261,19 @@ delete_tlpdb_files = function() { )) } -#' @param action On Unix, add/remove symlinks of binaries to/from the system's -#' \code{PATH}. On Windows, add/remove the path to the TeXLive binary +#' @param action On macOS, if \file{/usr/local/bin} is not writable, add/remove +#' the TinyTeX bin path to/from \file{/etc/paths.d/TinyTeX}; otherwise (or on +#' other Unix systems), add/remove symlinks of binaries to/from the system's +#' \code{PATH}. On Windows, add/remove the path to the TeX Live binary #' directory to/from the system environment variable \code{PATH}. #' @rdname tlmgr #' @export -tlmgr_path = function(action = c('add', 'remove')) - tlmgr(c('path', match.arg(action)), .quiet = TRUE) +tlmgr_path = function(action = c('add', 'remove')) { + action = match.arg(action) + if (use_paths_d()) { + invisible(macos_path(find_tinytex_bin(), action)) + } else tlmgr(c('path', action), .quiet = TRUE) +} #' @rdname tlmgr #' @export diff --git a/man/copy_tinytex.Rd b/man/copy_tinytex.Rd index 96eca0b78..d4db5842a 100644 --- a/man/copy_tinytex.Rd +++ b/man/copy_tinytex.Rd @@ -30,9 +30,9 @@ TinyTeX after copying it.} \description{ The function \code{copy_tinytex()} copies the existing TinyTeX installation to another directory (e.g., a portable device like a USB stick). The function -\code{use_tinytex()} runs \command{tlmgr path add} to add the copy of TinyTeX -in an existing folder to the \code{PATH} variable of the current system, so -that you can use utilities such as \command{tlmgr} and \command{pdflatex}, +\code{use_tinytex()} adds the copy of TinyTeX in an existing folder to the +\code{PATH} variable of the current system via \code{\link{tlmgr_path}()}, +so that you can use utilities such as \command{tlmgr} and \command{pdflatex}, etc. } \note{ diff --git a/man/install_tinytex.Rd b/man/install_tinytex.Rd index 6e3c4ae3c..b32f82a59 100644 --- a/man/install_tinytex.Rd +++ b/man/install_tinytex.Rd @@ -58,8 +58,8 @@ installed. By default, a vector of all currently installed LaTeX packages if an existing installation of TinyTeX is found. If you want a fresh installation, you may use \code{extra_packages = NULL}.} -\item{add_path}{Whether to run the command \command{tlmgr path add} to add -the bin path of TeX Live to the system environment variable \var{PATH}.} +\item{add_path}{Whether to add the bin path of TeX Live to the system +environment variable \var{PATH}. See \code{\link{tlmgr_path}()}.} \item{packages}{Whether to reinstall all currently installed packages.} diff --git a/man/tlmgr.Rd b/man/tlmgr.Rd index ddc25be75..7169c9121 100644 --- a/man/tlmgr.Rd +++ b/man/tlmgr.Rd @@ -101,8 +101,10 @@ format and hyphenation files after updating \pkg{tlmgr}.} (where \verb{HASH} is an MD5 hash) under the \file{tlpkg} directory of the root directory of TeX Live after updating.} -\item{action}{On Unix, add/remove symlinks of binaries to/from the system's -\code{PATH}. On Windows, add/remove the path to the TeXLive binary +\item{action}{On macOS, if \file{/usr/local/bin} is not writable, add/remove +the TinyTeX bin path to/from \file{/etc/paths.d/TinyTeX}; otherwise (or on +other Unix systems), add/remove symlinks of binaries to/from the system's +\code{PATH}. On Windows, add/remove the path to the TeX Live binary directory to/from the system environment variable \code{PATH}.} \item{url}{The URL of the CTAN mirror. If \code{NULL}, show the current diff --git a/tools/install-bin-unix.sh b/tools/install-bin-unix.sh index 116b4774f..a7be1830f 100755 --- a/tools/install-bin-unix.sh +++ b/tools/install-bin-unix.sh @@ -124,17 +124,29 @@ fi [ $OSNAME != "Darwin" ] && ./tlmgr option sys_bin $BINDIR ./tlmgr postaction install script xetex # GH issue #313 -if [ $OSNAME = 'Darwin' ]; then - # create the dir if it doesn't exist - if [ ! -d /usr/local/bin ]; then - echo "Admin privilege (password) is required to create the directory /usr/local/bin:" - sudo mkdir -p /usr/local/bin - fi - # change owner of the dir - if [ ! -w /usr/local/bin ]; then - echo "Admin privilege (password) is required to make /usr/local/bin writable:" - sudo chown -R `whoami`:admin /usr/local/bin +NO_PATH=0 +for arg in "$@"; do + case "$arg" in --no-path) NO_PATH=1 ;; esac +done + +if [ $NO_PATH -eq 0 ]; then + if [ $OSNAME = 'Darwin' ]; then + if [ -w /usr/local/bin ]; then + ./tlmgr path add + elif ! grep -qxF "$(pwd)" /etc/paths.d/TinyTeX 2>/dev/null; then + # add TinyTeX's bin path to /etc/paths.d instead of creating symlinks in /usr/local/bin + # (at this point we are in $TEXDIR/bin/*/ after the `cd` above) + echo "Admin privilege (password) is required to set up the PATH for TinyTeX:" + if printf '%s\n' "$(pwd)" | sudo tee /etc/paths.d/TinyTeX > /dev/null; then + # /etc/paths.d is not picked up until the shell is restarted; export PATH now + export PATH="$PATH:$(pwd)" + else + echo "To set up PATH manually, run the following command and add it to your shell startup profile (e.g. ~/.zshrc):" + echo " export PATH=\$PATH:$(pwd)" + export PATH="$PATH:$(pwd)" + fi + fi + else + ./tlmgr path add fi fi - -./tlmgr path add diff --git a/tools/install-windows.ps1 b/tools/install-windows.ps1 index 628bc6149..406d42018 100644 --- a/tools/install-windows.ps1 +++ b/tools/install-windows.ps1 @@ -24,7 +24,7 @@ Add-Content tinytex.profile 'TEXMFVAR $TEXMFSYSVAR' # download the custom package list Invoke-WebRequest 'https://tinytex.yihui.org/pkgs-custom.txt' -OutFile pkgs-custom.txt -# an automated installation of TeXLive (infrastructure only) +# an automated installation of TeX Live (infrastructure only) cd install-tl-* (Get-Content install-tl-windows.bat) -notmatch '^\s*pause\s*$' | Set-Content install-tl-windows.bat mkdir TinyTeX @@ -39,7 +39,7 @@ ni .tinytex | Out-Null del install-tl.log, install-tl, install-tl-windows.bat -ErrorAction SilentlyContinue cd .. -# TeXLive installed to ./TinyTeX; move it to APPDATA +# TeX Live installed to ./TinyTeX; move it to APPDATA rd $env:APPDATA\TinyTeX -r -fo -ErrorAction SilentlyContinue rd $env:APPDATA\TinyTeX -r -fo -ErrorAction SilentlyContinue move TinyTeX $env:APPDATA