diff --git a/DESCRIPTION b/DESCRIPTION index 5117b403..a7df8147 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: golem Title: A Framework for Robust Shiny Applications -Version: 0.3.2.9004 +Version: 0.3.3.9000 Authors@R: c(person(given = "Colin", family = "Fay", @@ -54,8 +54,8 @@ Imports: config, crayon, desc, - fs, here, + fs, htmltools, rlang, rstudioapi, @@ -66,7 +66,7 @@ Imports: Suggests: covr, devtools, - dockerfiler (>= 0.1.4), + dockerfiler (>= 0.2.0), knitr, pkgload, pkgbuild, @@ -82,11 +82,11 @@ Suggests: testthat, tools, withr, - attachment + attachment (>= 0.2.5) VignetteBuilder: knitr Config/testthat/edition: 3 Encoding: UTF-8 Language: en-US Roxygen: list(markdown = TRUE) -RoxygenNote: 7.1.2 +RoxygenNote: 7.2.1 diff --git a/NAMESPACE b/NAMESPACE index 468dedd2..484bc596 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -5,6 +5,9 @@ export(add_css_file) export(add_dockerfile) export(add_dockerfile_heroku) export(add_dockerfile_shinyproxy) +export(add_dockerfile_with_renv) +export(add_dockerfile_with_renv_heroku) +export(add_dockerfile_with_renv_shinyproxy) export(add_fct) export(add_html_template) export(add_js_file) @@ -37,6 +40,7 @@ export(expect_shinytag) export(expect_shinytaglist) export(favicon) export(fill_desc) +export(get_current_config) export(get_golem_name) export(get_golem_options) export(get_golem_version) diff --git a/NEWS.md b/NEWS.md index 4fc05971..13a672bb 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,6 @@ > Notes: the # between parenthesis referes to the related issue on GitHub, and the @ refers to an external contributor solving this issue. -# golem 0.3.2.9000+ +# golem 0.3.3.9000+ ### Soft deprecated @@ -25,6 +25,15 @@ ## Internal changes +# golem 0.3.3 + +## New functions + ++ `add_dockerfile_with_renv()`, `add_dockerfile_with_renv_heroku()` and `add_dockerfile_with_renv_shinyproxy()` build Dockerfiles that rely on `{renv}` + +### Soft deprecated + ++ `add_dockerfile`, `add_dockerfile_shinyproxy()` and `add_dockerfile_heroku()` now recommend to switch to their `_with_renv_` counterpart # golem 0.3.2 (CRAN VERSION) diff --git a/R/add_dockerfiles.R b/R/add_dockerfiles.R index a94e9c00..b3857371 100644 --- a/R/add_dockerfiles.R +++ b/R/add_dockerfiles.R @@ -1,7 +1,29 @@ +talk_once <- function(.f, msg = "") { + talk <- TRUE + function(...) { + if (talk) { + talk <<- FALSE + cat_red_bullet(msg) + } + .f(...) + } +} + +talk_once <- function(.f, msg = "") { + talk <- TRUE + function(...) { + if (talk) { + talk <<- FALSE + cat_red_bullet(msg) + } + .f(...) + } +} + #' Create a Dockerfile for your App #' -#' Build a container containing your Shiny App. `add_dockerfile()` creates -#' a generic Dockerfile, while `add_dockerfile_shinyproxy()` and +#' Build a container containing your Shiny App. `add_dockerfile()` and `add_dockerfile_with_renv()` and `add_dockerfile_with_renv()` creates +#' a generic Dockerfile, while `add_dockerfile_shinyproxy()`, `add_dockerfile_with_renv_shinyproxy()` , `add_dockerfile_with_renv_shinyproxy()` and #' `add_dockerfile_heroku()` creates platform specific Dockerfile. #' #' @inheritParams add_module @@ -9,7 +31,12 @@ #' @param path path to the DESCRIPTION file to use as an input. #' @param output name of the Dockerfile output. #' @param from The FROM of the Dockerfile. Default is -#' FROM rocker/r-ver:`R.Version()$major`.`R.Version()$minor`. +#' +#' FROM rocker/verse +#' +#' without renv.lock file passed +#' `R.Version()$major`.`R.Version()$minor` is used as tag +#' #' @param as The AS of the Dockerfile. Default it NULL. #' @param port The `options('shiny.port')` on which to run the App. #' Default is 80. @@ -18,13 +45,12 @@ #' @param sysreqs boolean. If TRUE, the Dockerfile will contain sysreq installation. #' @param repos character. The URL(s) of the repositories to use for `options("repos")`. #' @param expand boolean. If `TRUE` each system requirement will have its own `RUN` line. -#' @param open boolean. Should the Dockerfile be open after creation? Default is `TRUE`. +#' @param open boolean. Should the Dockerfile/README/README be open after creation? Default is `TRUE`. #' @param build_golem_from_source boolean. If `TRUE` no tar.gz is created and #' the Dockerfile directly mount the source folder. #' @param update_tar_gz boolean. If `TRUE` and `build_golem_from_source` is also `TRUE`, #' an updated tar.gz is created. #' @param extra_sysreqs character vector. Extra debian system requirements. -#' Will be installed with apt-get install. #' #' @export #' @rdname dockerfiles @@ -40,10 +66,46 @@ #' if (interactive()) { #' add_dockerfile() #' } +#' # Crete a 'deploy' folder containing everything needed to deploy +#' # the golem using docker based on {renv} +#' if (interactive()) { +#' add_dockerfile_with_renv( +#' # lockfile = "renv.lock", # uncomment to use existing renv.lock file +#' output_dir = "deploy" +#' ) +#' } +#' # Crete a 'deploy' folder containing everything needed to deploy +#' # the golem using docker based on {renv} +#' if (interactive()) { +#' add_dockerfile_with_renv( +#' # lockfile = "renv.lock", # uncomment to use existing renv.lock file +#' output_dir = "deploy" +#' ) +#' } #' # Add a Dockerfile for ShinyProxy #' if (interactive()) { #' add_dockerfile_shinyproxy() #' } +#' +#' # Crete a 'deploy' folder containing everything needed to deploy +#' # the golem with ShinyProxy using docker based on {renv} +#' if (interactive()) { +#' add_dockerfile_with_renv( +#' # lockfile = "renv.lock",# uncomment to use existing renv.lock file +#' output_dir = "deploy" +#' ) +#' } +#' +#' +#' # Crete a 'deploy' folder containing everything needed to deploy +#' # the golem with ShinyProxy using docker based on {renv} +#' if (interactive()) { +#' add_dockerfile_with_renv( +#' # lockfile = "renv.lock",# uncomment to use existing renv.lock file +#' output_dir = "deploy" +#' ) +#' } +#' #' # Add a Dockerfile for Heroku #' if (interactive()) { #' add_dockerfile_heroku() @@ -55,7 +117,7 @@ add_dockerfile <- function( output = "Dockerfile", pkg = get_golem_wd(), from = paste0( - "rocker/r-ver:", + "rocker/verse:", R.Version()$major, ".", R.Version()$minor @@ -71,56 +133,98 @@ add_dockerfile <- function( build_golem_from_source = TRUE, extra_sysreqs = NULL ) { - rlang::check_installed( - "dockerfiler", - "to create Dockerfile using {golem}.", - version = "0.1.4" - ) - - where <- path(pkg, output) - - usethis::use_build_ignore(path_file(where)) - - dock <- dockerfiler::dock_from_desc( + add_dockerfile_( path = path, - FROM = from, - AS = as, + output = output, + pkg = pkg, + from = from, + as = as, + port = port, + host = host, sysreqs = sysreqs, repos = repos, expand = expand, - build_from_source = build_golem_from_source, + open = open, update_tar_gz = update_tar_gz, + build_golem_from_source = build_golem_from_source, extra_sysreqs = extra_sysreqs ) +} + +add_dockerfile_ <- talk_once( + function( + path = "DESCRIPTION", + output = "Dockerfile", + pkg = get_golem_wd(), + from = paste0( + "rocker/verse:", + R.Version()$major, + ".", + R.Version()$minor + ), + as = NULL, + port = 80, + host = "0.0.0.0", + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + open = TRUE, + update_tar_gz = TRUE, + build_golem_from_source = TRUE, + extra_sysreqs = NULL + ) { + rlang::check_installed( + "dockerfiler", + version = "0.2.0", + reason = "to build a Dockerfile." + ) - dock$EXPOSE(port) + where <- path(pkg, output) - dock$CMD( - sprintf( - "R -e \"options('shiny.port'=%s,shiny.host='%s');%s::run_app()\"", - port, - host, - read.dcf(path)[1] + usethis::use_build_ignore(path_file(where)) + + dock <- dockerfiler::dock_from_desc( + path = path, + FROM = from, + AS = as, + sysreqs = sysreqs, + repos = repos, + expand = expand, + build_from_source = build_golem_from_source, + update_tar_gz = update_tar_gz, + extra_sysreqs = extra_sysreqs ) - ) - dock$write(output) + dock$EXPOSE(port) + + dock$CMD( + sprintf( + "R -e \"options('shiny.port'=%s,shiny.host='%s');%s::run_app()\"", + port, + host, + read.dcf(path)[1] + ) + ) - if (open) { - if (rstudioapi::isAvailable() & rstudioapi::hasFun("navigateToFile")) { - rstudioapi::navigateToFile(output) - } else { - try(file.edit(output)) + dock$write(output) + + if (open) { + if (rstudioapi::isAvailable() & rstudioapi::hasFun("navigateToFile")) { + rstudioapi::navigateToFile(output) + } else { + try(file.edit(output)) + } } - } - alert_build( - path = path, - output = output, - build_golem_from_source = build_golem_from_source - ) + alert_build( + path = path, + output = output, + build_golem_from_source = build_golem_from_source + ) - return(invisible(dock)) -} + return(invisible(dock)) + }, + "golem::add_dockerfile() is not recommended anymore.\nPlease use golem::add_dockerfile_with_renv() instead." +) #' @export #' @rdname dockerfiles @@ -130,7 +234,7 @@ add_dockerfile_shinyproxy <- function( output = "Dockerfile", pkg = get_golem_wd(), from = paste0( - "rocker/r-ver:", + "rocker/verse:", R.Version()$major, ".", R.Version()$minor @@ -144,50 +248,87 @@ add_dockerfile_shinyproxy <- function( build_golem_from_source = TRUE, extra_sysreqs = NULL ) { - rlang::check_installed( - "dockerfiler", - "to create Dockerfile using {golem}.", - version = "0.1.4" - ) - - where <- path(pkg, output) - - usethis::use_build_ignore(output) - - dock <- dockerfiler::dock_from_desc( + add_dockerfile_shinyproxy_( path = path, - FROM = from, - AS = as, + output = output, + pkg = pkg, + from = from, + as = as, sysreqs = sysreqs, repos = repos, expand = expand, - build_from_source = build_golem_from_source, + open = open, update_tar_gz = update_tar_gz, + build_golem_from_source = build_golem_from_source, extra_sysreqs = extra_sysreqs ) +} - dock$EXPOSE(3838) - dock$CMD(sprintf( - " [\"R\", \"-e\", \"options('shiny.port'=3838,shiny.host='0.0.0.0');%s::run_app()\"]", - read.dcf(path)[1] - )) - dock$write(output) +add_dockerfile_shinyproxy_ <- talk_once( + function( + path = "DESCRIPTION", + output = "Dockerfile", + pkg = get_golem_wd(), + from = paste0( + "rocker/verse:", + R.Version()$major, + ".", + R.Version()$minor + ), + as = NULL, + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + open = TRUE, + update_tar_gz = TRUE, + build_golem_from_source = TRUE, + extra_sysreqs = NULL + ) { + rlang::check_installed( + "dockerfiler", + version = "0.2.0", + reason = "to build a Dockerfile." + ) + where <- path(pkg, output) + + usethis::use_build_ignore(output) + + dock <- dockerfiler::dock_from_desc( + path = path, + FROM = from, + AS = as, + sysreqs = sysreqs, + repos = repos, + expand = expand, + build_from_source = build_golem_from_source, + update_tar_gz = update_tar_gz, + extra_sysreqs = extra_sysreqs + ) - if (open) { - if (rstudioapi::isAvailable() & rstudioapi::hasFun("navigateToFile")) { - rstudioapi::navigateToFile(output) - } else { - try(file.edit(output)) + dock$EXPOSE(3838) + dock$CMD(sprintf( + " [\"R\", \"-e\", \"options('shiny.port'=3838,shiny.host='0.0.0.0');%s::run_app()\"]", + read.dcf(path)[1] + )) + dock$write(output) + + if (open) { + if (rstudioapi::isAvailable() & rstudioapi::hasFun("navigateToFile")) { + rstudioapi::navigateToFile(output) + } else { + try(file.edit(output)) + } } - } - alert_build( - path, - output, - build_golem_from_source = build_golem_from_source - ) + alert_build( + path, + output, + build_golem_from_source = build_golem_from_source + ) - return(invisible(dock)) -} + return(invisible(dock)) + }, + "golem::add_dockerfile_shinyproxy() is not recommended anymore.\nPlease use golem::add_dockerfile_with_renv_shinyproxy() instead." +) #' @export #' @rdname dockerfiles @@ -197,7 +338,7 @@ add_dockerfile_heroku <- function( output = "Dockerfile", pkg = get_golem_wd(), from = paste0( - "rocker/r-ver:", + "rocker/verse:", R.Version()$major, ".", R.Version()$minor @@ -211,80 +352,119 @@ add_dockerfile_heroku <- function( build_golem_from_source = TRUE, extra_sysreqs = NULL ) { - rlang::check_installed( - "dockerfiler", - "to create Dockerfile using {golem}.", - version = "0.1.4" - ) - - where <- path(pkg, output) - - usethis::use_build_ignore(output) - - dock <- dockerfiler::dock_from_desc( + add_dockerfile_heroku_( path = path, - FROM = from, - AS = as, + output = output, + pkg = pkg, + from = from, + as = as, sysreqs = sysreqs, repos = repos, expand = expand, - build_from_source = build_golem_from_source, + open = open, update_tar_gz = update_tar_gz, + build_golem_from_source = build_golem_from_source, extra_sysreqs = extra_sysreqs ) +} - dock$CMD( - sprintf( - "R -e \"options('shiny.port'=$PORT,shiny.host='0.0.0.0');%s::run_app()\"", - read.dcf(path)[1] +add_dockerfile_heroku_ <- talk_once( + function( + path = "DESCRIPTION", + output = "Dockerfile", + pkg = get_golem_wd(), + from = paste0( + "rocker/verse:", + R.Version()$major, + ".", + R.Version()$minor + ), + as = NULL, + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + open = TRUE, + update_tar_gz = TRUE, + build_golem_from_source = TRUE, + extra_sysreqs = NULL + ) { + rlang::check_installed( + "dockerfiler", + version = "0.2.0", + reason = "to build a Dockerfile." ) - ) - dock$write(output) + where <- path(pkg, output) - alert_build( - path = path, - output = output, - build_golem_from_source = build_golem_from_source - ) + usethis::use_build_ignore(output) - apps_h <- gsub( - "\\.", - "-", - sprintf( - "%s-%s", - read.dcf(path)[1], - read.dcf(path)[1, ][["Version"]] + dock <- dockerfiler::dock_from_desc( + path = path, + FROM = from, + AS = as, + sysreqs = sysreqs, + repos = repos, + expand = expand, + build_from_source = build_golem_from_source, + update_tar_gz = update_tar_gz, + extra_sysreqs = extra_sysreqs ) - ) - cat_rule("From your command line, run:") - cat_line("heroku container:login") - cat_line( - sprintf("heroku create %s", apps_h) - ) - cat_line( - sprintf("heroku container:push web --app %s", apps_h) - ) - cat_line( - sprintf("heroku container:release web --app %s", apps_h) - ) - cat_line( - sprintf("heroku open --app %s", apps_h) - ) - cat_red_bullet("Be sure to have the heroku CLI installed.") - cat_red_bullet( - sprintf("You can replace %s with another app name.", apps_h) - ) - if (open) { - if (rstudioapi::isAvailable() & rstudioapi::hasFun("navigateToFile")) { - rstudioapi::navigateToFile(output) - } else { - try(file.edit(output)) + dock$CMD( + sprintf( + "R -e \"options('shiny.port'=$PORT,shiny.host='0.0.0.0');%s::run_app()\"", + read.dcf(path)[1] + ) + ) + dock$write(output) + + alert_build( + path = path, + output = output, + build_golem_from_source = build_golem_from_source + ) + + apps_h <- gsub( + "\\.", + "-", + sprintf( + "%s-%s", + read.dcf(path)[1], + read.dcf(path)[1, ][["Version"]] + ) + ) + + cat_rule("From your command line, run:") + cat_line("heroku container:login") + cat_line( + sprintf("heroku create %s", apps_h) + ) + cat_line( + sprintf("heroku container:push web --app %s", apps_h) + ) + cat_line( + sprintf("heroku container:release web --app %s", apps_h) + ) + cat_line( + sprintf("heroku open --app %s", apps_h) + ) + cat_red_bullet("Be sure to have the heroku CLI installed.") + cat_red_bullet( + sprintf("You can replace %s with another app name.", apps_h) + ) + if (open) { + if (rstudioapi::isAvailable() & rstudioapi::hasFun("navigateToFile")) { + rstudioapi::navigateToFile(output) + } else { + try(file.edit(output)) + } } - } - usethis::use_build_ignore(files = output) - return(invisible(dock)) -} + usethis::use_build_ignore(files = output) + return(invisible(dock)) + }, + " +golem::add_dockerfile_heroku() is not recommended anymore.\nPlease use golem::add_dockerfile_with_renv_heroku() instead. +" +) alert_build <- function( path, diff --git a/R/add_dockerfiles_renv.R b/R/add_dockerfiles_renv.R new file mode 100644 index 00000000..f6639bdf --- /dev/null +++ b/R/add_dockerfiles_renv.R @@ -0,0 +1,341 @@ +add_dockerfile_with_renv_ <- function( + source_folder = ".", + lockfile = NULL, + output_dir = fs::path(tempdir(), "deploy"), + distro = "focal", + FROM = "rocker/verse", + AS = NULL, + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + extra_sysreqs = NULL, + update_tar_gz = TRUE + # build_golem_from_source = TRUE, +) { + rlang::check_installed( + "renv", + reason = "to build a Dockerfile." + ) + rlang::check_installed( + "dockerfiler", + version = "0.2.0", + reason = "to build a Dockerfile." + ) + rlang::check_installed( + "attachment", + version = "0.2.5", + reason = "to build a Dockerfile." + ) + + # Small hack to prevent warning from rlang::lang() in tests + # This should be managed in {attempt} later on + x <- suppressWarnings({ + rlang::lang(print) + }) + + dir.create(output_dir) + + # add output_dir in Rbuildignore if the output is inside the golem + if (normalizePath(dirname(output_dir)) == normalizePath(source_folder)) { + usethis::use_build_ignore(output_dir) + } + + if (is.null(lockfile)) { + lockfile <- attachment::create_renv_for_prod(path = source_folder, output = file.path(output_dir, "renv.lock.prod")) + } + + file.copy(from = lockfile, to = output_dir) + + socle <- dockerfiler::dock_from_renv( + lockfile = lockfile, + distro = distro, + FROM = FROM, + repos = repos, + AS = AS, + sysreqs = sysreqs, + expand = expand, + extra_sysreqs = extra_sysreqs + ) + + socle$write(as = file.path(output_dir, "Dockerfile_base")) + + my_dock <- dockerfiler::Dockerfile$new(FROM = paste0(golem::get_golem_name(), "_base")) + + my_dock$COPY("renv.lock.prod", "renv.lock") + + my_dock$RUN("R -e 'renv::restore()'") + + if (update_tar_gz) { + old_version <- list.files(path = output_dir, pattern = paste0(golem::get_golem_name(), "_*.*.tar.gz"), full.names = TRUE) + # file.remove(old_version) + if (length(old_version) > 0) { + lapply(old_version, file.remove) + lapply(old_version, unlink, force = TRUE) + cat_red_bullet( + sprintf( + "%s were removed from folder", + paste( + old_version, + collapse = ", " + ) + ) + ) + } + + if ( + isTRUE( + requireNamespace( + "pkgbuild", + quietly = TRUE + ) + ) + ) { + out <- pkgbuild::build( + path = ".", + dest_path = output_dir, + vignettes = FALSE + ) + if (missing(out)) { + cat_red_bullet("Error during tar.gz building") + } else { + cat_green_tick( + sprintf( + " %s created.", + out + ) + ) + } + } else { + stop("please install {pkgbuild}") + } + } + + # we use an already built tar.gz file + my_dock$COPY( + from = + paste0(golem::get_golem_name(), "_*.tar.gz"), + to = "/app.tar.gz" + ) + my_dock$RUN("R -e 'remotes::install_local(\"/app.tar.gz\",upgrade=\"never\")'") + my_dock$RUN("rm /app.tar.gz") + my_dock +} + +#' @param source_folder path to the Package/golem source folder to deploy. +#' default is current folder '.' +#' @param lockfile path to the renv.lock file to use. default is `NULL` +#' @param output_dir folder to export everything deployment related. +#' @param distro One of "focal", "bionic", "xenial", "centos7", or "centos8". +#' See available distributions at https://hub.docker.com/r/rstudio/r-base/. +#' @param dockerfile_cmd What is the CMD to add to the Dockerfile. If NULL, the default, +#' the CMD will be `R -e "options('shiny.port'={port},shiny.host='{host}');{appname}::run_app()\` +#' @inheritParams add_dockerfile +#' @rdname dockerfiles +#' @export +add_dockerfile_with_renv <- function( + source_folder = ".", + lockfile = NULL, + output_dir = fs::path(tempdir(), "deploy"), + distro = "focal", + from = "rocker/verse", + as = NULL, + sysreqs = TRUE, + port = 80, + host = "0.0.0.0", + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + open = TRUE, + extra_sysreqs = NULL, + update_tar_gz = TRUE, + dockerfile_cmd = NULL +) { + base_dock <- add_dockerfile_with_renv_( + source_folder = source_folder, + lockfile = lockfile, + output_dir = output_dir, + distro = distro, + FROM = from, + AS = as, + sysreqs = sysreqs, + repos = repos, + expand = expand, + extra_sysreqs = extra_sysreqs, + update_tar_gz = update_tar_gz + ) + if (!is.null(port)) { + base_dock$EXPOSE(port) + } + if (is.null(dockerfile_cmd)) { + dockerfile_cmd <- sprintf( + "R -e \"options('shiny.port'=%s,shiny.host='%s');%s::run_app()\"", + port, + host, + golem::get_golem_name() + ) + } + base_dock$CMD( + dockerfile_cmd + ) + base_dock + base_dock$write(as = file.path(output_dir, "Dockerfile")) + + out <- sprintf( + "docker build -f Dockerfile_base --progress=plain -t %s . +docker build -f Dockerfile --progress=plain -t %s . +docker run -p %s:%s %s +# then go to 127.0.0.1:%s", + paste0(golem::get_golem_name(), "_base"), + paste0(golem::get_golem_name(), ":latest"), + port, + port, + paste0(golem::get_golem_name(), ":latest"), + port + ) + + cat(out, file = file.path(output_dir, "README")) + + open_or_go_to( + where = file.path(output_dir, "README"), + open_file = open + ) +} + +#' @inheritParams add_dockerfile +#' @rdname dockerfiles +#' @export +#' @export +add_dockerfile_with_renv_shinyproxy <- function( + source_folder = ".", + lockfile = NULL, + output_dir = fs::path(tempdir(), "deploy"), + distro = "focal", + from = "rocker/verse", + as = NULL, + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + extra_sysreqs = NULL, + open = TRUE, + update_tar_gz = TRUE +) { + add_dockerfile_with_renv( + source_folder = source_folder, + lockfile = lockfile, + output_dir = output_dir, + distro = distro, + from = from, + as = as, + sysreqs = sysreqs, + repos = repos, + expand = expand, + port = 3838, + host = "0.0.0.0", + extra_sysreqs = extra_sysreqs, + update_tar_gz = update_tar_gz, + open = open, + dockerfile_cmd = sprintf( + "R -e \"options('shiny.port'=3838,shiny.host='0.0.0.0');%s::run_app()\"", + golem::get_golem_name() + ) + ) +} + +#' @inheritParams add_dockerfile +#' @rdname dockerfiles +#' @export +#' @export +add_dockerfile_with_renv_heroku <- function( + source_folder = ".", + lockfile = NULL, + output_dir = fs::path(tempdir(), "deploy"), + distro = "focal", + from = "rocker/verse", + as = NULL, + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + extra_sysreqs = NULL, + open = TRUE, + update_tar_gz = TRUE +) { + add_dockerfile_with_renv( + source_folder = source_folder, + lockfile = lockfile, + output_dir = output_dir, + distro = distro, + from = from, + as = as, + sysreqs = sysreqs, + repos = repos, + expand = expand, + port = NULL, + host = "0.0.0.0", + extra_sysreqs = extra_sysreqs, + update_tar_gz = update_tar_gz, + open = FALSE, + dockerfile_cmd = sprintf( + "R -e \"options('shiny.port'=$PORT,shiny.host='0.0.0.0');%s::run_app()\"", + golem::get_golem_name() + ) + ) + + apps_h <- gsub( + "\\.", + "-", + sprintf( + "%s-%s", + golem::get_golem_name(), + golem::get_golem_version() + ) + ) + + readme_output <- file.path(output_dir, "README") + + write_there <- function(...) { + write(..., file = readme_output, append = TRUE) + } + + write_there("From your command line, run:\n") + + write_there( + sprintf( + "docker build -f Dockerfile_base --progress=plain -t %s .", + paste0(golem::get_golem_name(), "_base") + ) + ) + + write_there( + sprintf( + "docker build -f Dockerfile --progress=plain -t %s .\n", + paste0(golem::get_golem_name(), ":latest") + ) + ) + + write_there("Then, to push on heroku:\n") + + write_there("heroku container:login") + write_there( + sprintf("heroku create %s", apps_h) + ) + write_there( + sprintf("heroku container:push web --app %s", apps_h) + ) + write_there( + sprintf("heroku container:release web --app %s", apps_h) + ) + write_there( + sprintf("heroku open --app %s\n", apps_h) + ) + write_there("> Be sure to have the heroku CLI installed.") + + write_there( + sprintf("> You can replace %s with another app name.", apps_h) + ) + + # The open is deported here just to be sure + # That we open the README once it has been populated + open_or_go_to( + where = readme_output, + open_file = open + ) +} diff --git a/R/config.R b/R/config.R index 64438199..4f2ac5cc 100644 --- a/R/config.R +++ b/R/config.R @@ -1,10 +1,24 @@ +# This file contains everything related to the +# manipulation of the golem-config file + +# We first need something to guess where the file +# is. 99.99% of the time it will be +# ./inst/golem-config.yml but if for some reason +# you're somewhere else, functions still need to +# work + #' @importFrom attempt attempt is_try_error #' @importFrom fs path path_abs guess_where_config <- function( path = ".", file = "inst/golem-config.yml" ) { - # Trying the path + # We'll try to guess where the path + # to the golem-config file is + + # This one should be correct in 99% of the case + # If we don't change the default values of the params. + # => current directory /inst/golem-config.yml ret_path <- path( path, file @@ -12,18 +26,21 @@ guess_where_config <- function( if (file_exists(ret_path)) { return(path_abs(ret_path)) } - # Trying maybe in the wd + + # Maybe for some reason we are in inst/ ret_path <- "golem-config.yml" if (file_exists(ret_path)) { return(path_abs(ret_path)) } - # Trying with pkgpath + + # Trying with pkg_path ret_path <- attempt({ path( golem::pkg_path(), "inst/golem-config.yml" ) }) + if ( !is_try_error(ret_path) & file_exists(ret_path) @@ -35,14 +52,22 @@ guess_where_config <- function( return(NULL) } +#' Get the path to the current config File +#' +#' This function tries to guess where the golem-config file is located. +#' If it can't find it, this function asks the +#' user if they want to set the golem skeleton. +#' #' @importFrom fs file_copy path -get_current_config <- function( - path = ".", - set_options = TRUE -) { +#' +#' @param path Path to start looking for the config +#' +#' @export +get_current_config <- function(path = ".") { # We check wether we can guess where the config file is path_conf <- guess_where_config(path) + # We default to inst/ if this doesn't exist if (is.null(path_conf)) { path_conf <- path( @@ -55,7 +80,7 @@ get_current_config <- function( if (interactive()) { ask <- yesno( sprintf( - "The %s file doesn't exist, create?", + "The %s file doesn't exist.\nIt's possible that you might not be in a {golem} based project.\n Do you want to create the {golem} files?", basename(path_conf) ) ) @@ -86,9 +111,7 @@ get_current_config <- function( "shinyexample", golem::pkg_name() ) - if (set_options) { - set_golem_options() - } + # TODO This should also create the dev folder } else { stop( sprintf( @@ -104,6 +127,9 @@ get_current_config <- function( ) } +# This function changes the name of the +# package in app_config when you need to +# set the {golem} name change_app_config_name <- function( name, path = get_golem_wd() @@ -121,58 +147,3 @@ change_app_config_name <- function( ) write(app_config, pth) } - - -# find and tag expressions in a yaml -# used internally in `amend_golem_config` -#' @importFrom utils modifyList -find_and_tag_exprs <- function(conf_path) { - conf <- yaml::read_yaml(conf_path, eval.expr = FALSE) - conf.eval <- yaml::read_yaml(conf_path, eval.expr = TRUE) - - expr_list <- lapply(names(conf), function(x) { - conf[[x]][!conf[[x]] %in% conf.eval[[x]]] - }) - names(expr_list) <- names(conf) - expr_list <- Filter(function(x) length(x) > 0, expr_list) - add_expr_tag <- function(tag) { - attr(tag[[1]], "tag") <- "!expr" - tag - } - tagged_exprs <- lapply(expr_list, add_expr_tag) - modifyList(conf, tagged_exprs) -} - -#' Amend golem config file -#' -#' @param key key of the value to add in `config` -#' @inheritParams config::get -#' @inheritParams add_module -#' @inheritParams set_golem_options -#' -#' @export -#' @importFrom yaml read_yaml write_yaml -#' @importFrom attempt stop_if -#' -#' @return Used for side effects. -amend_golem_config <- function( - key, - value, - config = "default", - pkg = get_golem_wd(), - talkative = TRUE -) { - conf_path <- get_current_config(pkg) - stop_if( - conf_path, - is.null, - "Unable to retrieve golem config file." - ) - conf <- find_and_tag_exprs(conf_path) - conf[[config]][[key]] <- value - write_yaml( - conf, - conf_path - ) - invisible(TRUE) -} diff --git a/R/create_golem.R b/R/create_golem.R index 35e9e8f2..566ede49 100644 --- a/R/create_golem.R +++ b/R/create_golem.R @@ -1,3 +1,36 @@ +replace_package_name <- function( + copied_files, + package_name, + path_to_golem +) { + # Going through copied files to replace package name + for (f in copied_files) { + copied_file <- file.path(path_to_golem, f) + + if (grepl("^REMOVEME", f)) { + file.rename( + from = copied_file, + to = file.path(path_to_golem, gsub("REMOVEME", "", f)) + ) + copied_file <- file.path(path_to_golem, gsub("REMOVEME", "", f)) + } + + if (!grepl("ico$", copied_file)) { + try( + { + replace_word( + file = copied_file, + pattern = "shinyexample", + replace = package_name + ) + }, + silent = TRUE + ) + } + } +} + + #' Create a package for a Shiny App using `{golem}` #' #' @param path Name of the folder to create the package in. @@ -42,7 +75,7 @@ create_golem <- function( with_git = FALSE, ... ) { - path <- normalizePath(path, mustWork = FALSE) + path_to_golem <- normalizePath(path, mustWork = FALSE) if (check_name) { cat_rule("Checking package name") @@ -51,7 +84,7 @@ create_golem <- function( } - if (dir.exists(path)) { + if (dir.exists(path_to_golem)) { if (!isTRUE(overwrite)) { stop( paste( @@ -68,7 +101,7 @@ create_golem <- function( } else { cat_rule("Creating dir") usethis::create_project( - path = path, + path = path_to_golem, open = FALSE, ) cat_green_tick("Created package directory") @@ -79,7 +112,11 @@ create_golem <- function( from <- golem_sys("shinyexample") # Copy over whole directory - dir_copy(path = from, new_path = path, overwrite = TRUE) + dir_copy( + path = from, + new_path = path_to_golem, + overwrite = TRUE + ) # Listing copied files ***from source directory*** copied_files <- list.files( @@ -89,59 +126,30 @@ create_golem <- function( recursive = TRUE ) - # Going through copied files to replace package name - for (f in copied_files) { - copied_file <- file.path(path, f) - - if (grepl("^REMOVEME", f)) { - file.rename( - from = copied_file, - to = file.path(path, gsub("REMOVEME", "", f)) - ) - copied_file <- file.path(path, gsub("REMOVEME", "", f)) - } - - if (!grepl("ico$", copied_file)) { - try( - { - replace_word( - file = copied_file, - pattern = "shinyexample", - replace = package_name - ) - }, - silent = TRUE - ) - } - } + replace_package_name( + copied_files, + package_name, + path_to_golem + ) cat_green_tick("Copied app skeleton") - - cat_rule("Setting the default config") - - yml_path <- file.path(path, "inst/golem-config.yml") - conf <- yaml::read_yaml(yml_path, eval.expr = TRUE) - yaml_golem_wd <- "here::here()" - attr(yaml_golem_wd, "tag") <- "!expr" - conf$dev$golem_wd <- yaml_golem_wd - conf$default$golem_name <- package_name - conf$default$golem_version <- "0.0.0.9000" - write_yaml(conf, yml_path) - - cat_green_tick("Configured app") - + old <- setwd(path_to_golem) cat_rule("Running project hook function") - old <- setwd(path) # TODO fix # for some weird reason test() fails here when using golem::create_golem # and I don't have time to search why rn if (substitute(project_hook) == "golem::project_hook") { project_hook <- getFromNamespace("project_hook", "golem") } - project_hook(path = path, package_name = package_name, ...) + project_hook( + path = path_to_golem, + package_name = package_name, + ... + ) + setwd(old) cat_green_tick("All set") @@ -150,8 +158,8 @@ create_golem <- function( if (isTRUE(without_comments)) { files <- list.files( path = c( - file.path(path, "dev"), - file.path(path, "R") + file.path(path_to_golem, "dev"), + file.path(path_to_golem, "R") ), full.names = TRUE ) @@ -164,7 +172,7 @@ create_golem <- function( if (isTRUE(with_git)) { cat_rule("Initializing git repository") git_output <- system( - command = paste("git init", path), + command = paste("git init", path_to_golem), ignore.stdout = TRUE, ignore.stderr = TRUE ) @@ -176,7 +184,7 @@ create_golem <- function( } - old <- setwd(path) + old <- setwd(path_to_golem) use_latest_dependencies() # No .Rprofile for now @@ -197,14 +205,14 @@ create_golem <- function( setwd(old) - cat_rule("Done") + cat_line( paste0( "A new golem named ", package_name, " was created at ", - normalizePath(path), + path_to_golem, " .\n", "To continue working on your app, start editing the 01_start.R file." ) @@ -219,10 +227,9 @@ create_golem <- function( } } - return( invisible( - normalizePath(path) + path_to_golem ) ) } diff --git a/R/desc.R b/R/desc.R index dd120fa9..b26da92d 100644 --- a/R/desc.R +++ b/R/desc.R @@ -69,10 +69,10 @@ fill_desc <- function( ) set_golem_version( version = "0.0.0.9000", - path = path + pkg = path ) desc$set( - Package = pkg_name + Package = as.character(pkg_name) ) change_app_config_name( name = pkg_name, diff --git a/R/golem-yaml-get.R b/R/golem-yaml-get.R new file mode 100644 index 00000000..5eaa7781 --- /dev/null +++ b/R/golem-yaml-get.R @@ -0,0 +1,104 @@ + +#' @importFrom config get +get_golem_things <- function( + value, + config = Sys.getenv( + "GOLEM_CONFIG_ACTIVE", + Sys.getenv( + "R_CONFIG_ACTIVE", + "default" + ) + ), + use_parent = TRUE, + path +) { + conf_path <- get_current_config( + path + ) + stop_if( + conf_path, + is.null, + "Unable to retrieve golem config file." + ) + config::get( + value = value, + config = config, + file = conf_path, + use_parent = TRUE + ) +} + +#' @export +#' @importFrom fs path_abs +#' @rdname golem_opts +get_golem_wd <- function( + use_parent = TRUE, + pkg = golem::pkg_path() +) { + path <- path_abs(pkg) + + pth <- get_golem_things( + value = "golem_wd", + config = "dev", + use_parent = use_parent, + path = path + ) + if (is.null(pth)) { + pth <- golem::pkg_path() + } + return(pth) +} + +#' @export +#' @importFrom fs path_abs +#' @rdname golem_opts +get_golem_name <- function( + config = Sys.getenv( + "GOLEM_CONFIG_ACTIVE", + Sys.getenv( + "R_CONFIG_ACTIVE", + "default" + ) + ), + use_parent = TRUE, + pkg = golem::pkg_path() +) { + path <- path_abs(pkg) + nm <- get_golem_things( + value = "golem_name", + config = config, + use_parent = use_parent, + path = path + ) + if (is.null(nm)) { + nm <- golem::pkg_name() + } + return(nm) +} + +#' @export +#' @importFrom fs path_abs +#' @rdname golem_opts +get_golem_version <- function( + config = Sys.getenv( + "GOLEM_CONFIG_ACTIVE", + Sys.getenv( + "R_CONFIG_ACTIVE", + "default" + ) + ), + use_parent = TRUE, + pkg = golem::pkg_path() +) { + path <- path_abs(pkg) + vers <- get_golem_things( + value = "golem_version", + config = config, + use_parent = use_parent, + path = path + ) + if (is.null(vers)) { + vers <- golem::pkg_version() + } + return(vers) +} diff --git a/R/golem-yaml-set.R b/R/golem-yaml-set.R new file mode 100644 index 00000000..e74e4d9f --- /dev/null +++ b/R/golem-yaml-set.R @@ -0,0 +1,133 @@ +#' @export +#' @importFrom fs path_abs +#' @rdname golem_opts +set_golem_wd <- function( + golem_wd = golem::pkg_path(), + pkg = golem::pkg_path(), + talkative = TRUE +) { + + if ( + golem_wd == "golem::pkg_path()" | + normalizePath(golem_wd) == normalizePath(golem::pkg_path()) + ) { + golem_yaml_path <- "golem::pkg_path()" + attr(golem_yaml_path, "tag") <- "!expr" + } else { + golem_yaml_path <- path_abs(golem_wd) + } + + amend_golem_config( + key = "golem_wd", + value = golem_yaml_path, + config = "dev", + pkg = pkg, + talkative = talkative + ) + + invisible(path) +} + +#' @export +#' @rdname golem_opts +#' @importFrom fs path_abs +set_golem_name <- function( + name = golem::pkg_name(), + pkg = golem::pkg_path(), + talkative = TRUE +) { + path <- path_abs(pkg) + + # Changing in YAML + amend_golem_config( + key = "golem_name", + value = name, + config = "default", + pkg = pkg, + talkative = talkative + ) + # Changing in app-config.R + change_app_config_name( + name = name, + path = path + ) + + # Changing in DESCRIPTION + desc <- desc::description$new( + file = fs::path( + path, + "DESCRIPTION" + ) + ) + desc$set( + Package = name + ) + desc$write( + file = "DESCRIPTION" + ) + + invisible(name) +} + +#' @export +#' @rdname golem_opts +#' @importFrom fs path_abs +set_golem_version <- function( + version = golem::pkg_version(), + pkg = golem::pkg_path(), + talkative = TRUE +) { + path <- path_abs(pkg) + + # Changing in YAML + amend_golem_config( + key = "golem_version", + value = as.character(version), + config = "default", + pkg = pkg, + talkative = talkative + ) + + desc <- desc::description$new( + file = fs::path( + path, + "DESCRIPTION" + ) + ) + desc$set_version( + version = version + ) + desc$write( + file = "DESCRIPTION" + ) + + invisible(version) +} + + +#' Set things in golem config +#' +#' @param key,value entries in the yaml +#' @param path path to the config file +#' @param talkative Should things be printed? +#' @param config config context to write to +#' +#' @noRd +#' +# set_golem_things <- function( +# key, +# value, +# config_file, +# talkative = TRUE, +# config = "default" +# ) { +# amend_golem_config( +# key = key, +# value = value, +# config = config, +# pkg = config_file, +# talkative = talkative +# ) + +# invisible(path) +# } diff --git a/R/golem-yaml-utils.R b/R/golem-yaml-utils.R new file mode 100644 index 00000000..a6f61463 --- /dev/null +++ b/R/golem-yaml-utils.R @@ -0,0 +1,110 @@ +# This file contains all the necessary functions +# required to manipualte the golem-config file. + +# find and tag expressions in a yaml +# used internally in `amend_golem_config` +# This is an utilitary function to prevent +# the !expr from being lost in translation +# when manipulating the yaml +# +#' @importFrom utils modifyList +find_and_tag_exprs <- function(conf_path) { + conf <- yaml::read_yaml( + conf_path, + eval.expr = FALSE + ) + conf.eval <- yaml::read_yaml( + conf_path, + eval.expr = TRUE + ) + + expr_list <- lapply( + names(conf), + function(x) { + conf[[x]][!conf[[x]] %in% conf.eval[[x]]] + } + ) + + names(expr_list) <- names(conf) + + expr_list <- Filter( + function(x) length(x) > 0, + expr_list + ) + + add_expr_tag <- function(tag) { + attr(tag[[1]], "tag") <- "!expr" + tag + } + tagged_exprs <- lapply( + expr_list, + add_expr_tag + ) + + modifyList( + conf, + tagged_exprs + ) +} + +#' Amend golem config file +#' +#' @param key key of the value to add in `config` +#' @inheritParams config::get +#' @inheritParams add_module +#' @inheritParams set_golem_options +#' +#' @export +#' @importFrom yaml read_yaml write_yaml +#' @importFrom attempt stop_if +#' +#' @return Used for side effects. +amend_golem_config <- function( + key, + value, + config = "default", + pkg = golem::pkg_path(), + talkative = TRUE +) { + conf_path <- get_current_config(pkg) + + stop_if( + conf_path, + is.null, + "Unable to retrieve golem config file." + ) + + cat_if_talk <- function( + ..., + fun = cat_green_tick + ) { + if (talkative) { + fun(...) + } + } + + cat_if_talk( + sprintf( + "Setting `%s` to %s", + key, + value + ) + ) + + if (key == "golem_wd") { + cat_if_talk( + "You can change golem working directory with set_golem_wd('path/to/wd')", + fun = cat_line + ) + } + + conf <- find_and_tag_exprs(conf_path) + conf[[config]][[key]] <- value + + write_yaml( + conf, + conf_path + ) + + invisible(TRUE) +} diff --git a/R/options.R b/R/options.R deleted file mode 100644 index 134eb2a9..00000000 --- a/R/options.R +++ /dev/null @@ -1,333 +0,0 @@ -#' `{golem}` options -#' -#' Set and get a series of options to be used with `{golem}`. -#' These options are found inside the `golem-config.yml` file, found in most cases -#' inside the `inst` folder. -#' -#' @section Set Functions: -#' + `set_golem_options()` sets all the options, with the defaults from the functions below. -#' + `set_golem_wd()` defaults to `here::here()`, which is the package root when starting a golem. -#' + `set_golem_name()` defaults `golem::pkg_name()` -#' + `set_golem_version()` defaults `golem::pkg_version()` -#' -#' @section Get Functions: -#' Reads the information from `golem-config.yml` -#' + `get_golem_wd()` -#' + `get_golem_name()` -#' + `get_golem_version()` -#' -#' @param golem_name Name of the current golem. -#' @param golem_version Version of the current golem. -#' @param golem_wd Working directory of the current golem package. -#' @param app_prod Is the `{golem}` in prod mode? -#' @param path The path to set the golem working directory. -#' Note that it will be passed to `normalizePath`. -#' @param talkative Should the messages be printed to the console? -#' @param name The name of the app -#' @param version The version of the app -#' @inheritParams config::get -#' -#' @rdname golem_opts -#' -#' @export -#' @importFrom attempt stop_if_not -#' @importFrom yaml read_yaml write_yaml -#' @importFrom usethis proj_set -#' -#' @return Used for side-effects for the setters, and values from the -#' config in the getters. -set_golem_options <- function( - golem_name = golem::pkg_name(), - golem_version = golem::pkg_version(), - golem_wd = golem::pkg_path(), - app_prod = FALSE, - talkative = TRUE -) { - change_app_config_name( - name = golem_name, - path = golem_wd - ) - - cat_if_talk <- function(..., fun = cat_green_tick) { - if (talkative) { - fun(...) - } - } - - conf_path <- get_current_config( - golem_wd, - set_options = FALSE - ) - - stop_if( - conf_path, - is.null, - "Unable to retrieve golem config file." - ) - - cat_if_talk( - "Setting {golem} options in `golem-config.yml`", - fun = cli::cat_rule - ) - - conf <- read_yaml(conf_path, eval.expr = TRUE) - - # Setting wd - if (golem_wd == here::here()) { - path <- "here::here()" - attr(path, "tag") <- "!expr" - } else { - path <- golem_wd - } - - cat_if_talk( - sprintf( - "Setting `golem_wd` to %s", - path - ) - ) - cat_if_talk( - "You can change golem working directory with set_golem_wd('path/to/wd')", - fun = cat_line - ) - - conf$dev$golem_wd <- path - - # Setting name of the golem - cat_if_talk( - sprintf( - "Setting `golem_name` to %s", - golem_name - ) - ) - conf$default$golem_name <- golem_name - - # Setting golem_version - cat_if_talk( - sprintf( - "Setting `golem_version` to %s", - golem_version - ) - ) - conf$default$golem_version <- as.character(golem_version) - - # Setting app_prod - cat_if_talk( - sprintf( - "Setting `app_prod` to %s", - app_prod - ) - ) - conf$default$app_prod <- app_prod - - # Export - write_yaml( - conf, - conf_path - ) - - cat_if_talk( - "Setting {usethis} project as `golem_wd`", - fun = cli::cat_rule - ) - proj_set(golem_wd) -} - -#' @importFrom yaml read_yaml write_yaml -set_golem_things <- function( - key, - value, - path, - talkative, - config = "default" -) { - conf_path <- get_current_config(path, set_options = FALSE) - stop_if( - conf_path, - is.null, - "Unable to retrieve golem config file." - ) - cat_if_talk <- function(..., fun = cat_green_tick) { - if (talkative) { - fun(...) - } - } - - cat_if_talk( - sprintf( - "Setting `%s` to %s", - key, - value - ) - ) - - conf <- read_yaml(conf_path, eval.expr = TRUE) - conf[[config]][[key]] <- value - write_yaml( - conf, - conf_path - ) - - invisible(path) -} - -#' @export -#' @rdname golem_opts -#' @importFrom fs path_abs -set_golem_wd <- function( - path = golem::pkg_path(), - talkative = TRUE -) { - path <- path_abs(path) - # Setting wd - - if (path == here::here()) { - path <- "here::here()" - attr(path, "tag") <- "!expr" - } - - set_golem_things( - "golem_wd", - path, - path, - talkative = talkative, - config = "dev" - ) - - invisible(path) -} - -#' @export -#' @rdname golem_opts -#' @importFrom fs path_abs -set_golem_name <- function( - name = golem::pkg_name(), - path = golem::pkg_path(), - talkative = TRUE -) { - path <- path_abs(path) - # Changing in YAML - set_golem_things( - "golem_name", - name, - path, - talkative = talkative - ) - # Changing in app-config.R - change_app_config_name( - name = name, - path = path - ) - - # Changing in DESCRIPTION - desc <- desc::description$new( - file = fs::path( - path, - "DESCRIPTION" - ) - ) - desc$set( - Package = name - ) - desc$write( - file = "DESCRIPTION" - ) - - invisible(name) -} - -#' @export -#' @rdname golem_opts -#' @importFrom fs path_abs -set_golem_version <- function( - version = golem::pkg_version(), - path = golem::pkg_path(), - talkative = TRUE -) { - path <- path_abs(path) - set_golem_things( - "golem_version", - as.character(version), - path, - talkative = talkative - ) - desc <- desc::description$new(file = fs::path(path, "DESCRIPTION")) - desc$set_version( - version = version - ) - desc$write( - file = "DESCRIPTION" - ) - - invisible(version) -} - -#' @importFrom config get -get_golem_things <- function( - value, - config = Sys.getenv("R_CONFIG_ACTIVE", "default"), - use_parent = TRUE, - path -) { - conf_path <- get_current_config(path, set_options = TRUE) - stop_if( - conf_path, - is.null, - "Unable to retrieve golem config file." - ) - config::get( - value = value, - config = config, - file = conf_path, - use_parent = TRUE - ) -} - - -#' @export -#' @rdname golem_opts -get_golem_wd <- function( - use_parent = TRUE, - path = golem::pkg_path() -) { - get_golem_things( - value = "golem_wd", - config = "dev", - use_parent = use_parent, - path = path - ) -} - -#' @export -#' @rdname golem_opts -get_golem_name <- function( - config = Sys.getenv("R_CONFIG_ACTIVE", "default"), - use_parent = TRUE, - path = golem::pkg_path() -) { - nm <- get_golem_things( - value = "golem_name", - config = config, - use_parent = use_parent, - path = path - ) - if (is.null(nm)) { - nm <- golem::pkg_name() - } - nm -} - -#' @export -#' @rdname golem_opts -get_golem_version <- function( - config = Sys.getenv("R_CONFIG_ACTIVE", "default"), - use_parent = TRUE, - path = golem::pkg_path() -) { - get_golem_things( - value = "golem_version", - config = config, - use_parent = use_parent, - path = path - ) -} diff --git a/R/pkg_tools.R b/R/pkg_tools.R index f746c646..8cc9d09a 100644 --- a/R/pkg_tools.R +++ b/R/pkg_tools.R @@ -3,7 +3,8 @@ daf_desc <- function( path = ".", entry ) { - unlist( + as.character( + unlist( unname( as.data.frame( read.dcf( @@ -14,6 +15,7 @@ daf_desc <- function( )[entry] ) ) + ) } #' Package tools @@ -22,8 +24,6 @@ daf_desc <- function( #' inside your project while developing #' #' @param path Path to use to read the DESCRIPTION -#' @param depth Number of parent directory to visit before -#' deciding we are not in a package. #' #' @export #' @rdname pkg_tools @@ -37,28 +37,7 @@ pkg_version <- function(path = ".") { } #' @export #' @rdname pkg_tools -pkg_path <- function( - path = ".", - depth = 3 -) { - # We use depth 3 bcs it's the deepest you can be - # i.e. inst/app/www/ - path <- normalizePath(path) - if (file.exists(file.path(path, "DESCRIPTION"))) { - return(path) - } - for (i in 1:depth) { - path <- dirname(path) - if ( - file.exists( - file.path( - path, - "DESCRIPTION" - ) - ) - ) { - return(path) - } - } - stop("Unable to locate the package path.") +pkg_path <- function(path = ".") { + rlang::check_installed("here") + here::here() } diff --git a/R/set_golem_options.R b/R/set_golem_options.R new file mode 100644 index 00000000..bf9d59e8 --- /dev/null +++ b/R/set_golem_options.R @@ -0,0 +1,100 @@ +#' `{golem}` options +#' +#' Set and get a series of options to be used with `{golem}`. +#' These options are found inside the `golem-config.yml` file, found in most cases +#' inside the `inst` folder. +#' +#' @section Set Functions: +#' + `set_golem_options()` sets all the options, with the defaults from the functions below. +#' + `set_golem_wd()` defaults to `golem::golem_wd()`, which is the package root when starting a golem. +#' + `set_golem_name()` defaults `golem::pkg_name()` +#' + `set_golem_version()` defaults `golem::pkg_version()` +#' +#' @section Get Functions: +#' Reads the information from `golem-config.yml` +#' + `get_golem_wd()` +#' + `get_golem_name()` +#' + `get_golem_version()` +#' +#' @param golem_name Name of the current golem. +#' @param golem_version Version of the current golem. +#' @param golem_wd Working directory of the current golem package. +#' @param app_prod Is the `{golem}` in prod mode? +#' @param pkg The path to set the golem working directory. +#' Note that it will be passed to `normalizePath`. +#' @param talkative Should the messages be printed to the console? +#' @param name The name of the app +#' @param version The version of the app +#' @param config_file path to the {golem} config file +#' @inheritParams config::get +#' +#' @rdname golem_opts +#' +#' @export +#' @importFrom attempt stop_if_not +#' @importFrom usethis proj_set +#' +#' @return Used for side-effects for the setters, and values from the +#' config in the getters. +set_golem_options <- function( + golem_name = golem::pkg_name(), + golem_version = golem::pkg_version(), + golem_wd = golem::pkg_path(), + app_prod = FALSE, + talkative = TRUE, + config_file = golem::get_current_config(golem_wd) +) { + + # TODO here we'll run the + # golem_install_dev_pkg() function + + if (talkative) { + cli::cat_rule( + "Setting {golem} options in `golem-config.yml`" + ) + } + + # Let's do this in the order of the + # parameters + # Setting name of the golem + set_golem_name( + name = golem_name, + pkg = golem_wd, + talkative = talkative + ) + + # Let's start with wd + # Basically here the idea is to be able + # to keep the wd as an expr if it is the + # same as golem::pkg_path(), otherwise + # we use the explicit path + + set_golem_wd( + pkg = golem_wd, + talkative = talkative + ) + + # Setting golem_version + set_golem_version( + version = golem_version, + pkg = golem_wd, + talkative = talkative + ) + + # Setting app_prod + amend_golem_config( + "app_prod", + app_prod, + pkg = golem_wd, + talkative = talkative + ) + + # This part is for {usethis} and {here} + if (talkative) { + cli::cat_rule( + "Setting {usethis} project as `golem_wd`" + ) + } + + proj_set(golem_wd) +} diff --git a/R/utils.R b/R/utils.R index 92f2fdff..ad70b6e3 100644 --- a/R/utils.R +++ b/R/utils.R @@ -17,8 +17,8 @@ darkgrey <- function(x) { } #' @importFrom fs dir_exists file_exists -dir_not_exist <- Negate(dir_exists) -file_not_exist <- Negate(file_exists) +dir_not_exist <- Negate(fs::dir_exists) +file_not_exist <- Negate(fs::file_exists) #' @importFrom fs dir_create file_create create_if_needed <- function( @@ -452,4 +452,4 @@ is_existing_module <- function(module) { existing_module_files ) module %in% existing_module_names -} \ No newline at end of file +} diff --git a/README.Rmd b/README.Rmd index c06f5466..61060376 100644 --- a/README.Rmd +++ b/README.Rmd @@ -75,7 +75,7 @@ This package is part of a series of tools for Shiny, which includes: - `r emo::flag("France")` [Introduction to {golem}](https://youtu.be/6qI4NzxlAFU) - rstudio::conf(2020) : [Production-grade Shiny Apps with golem](https://www.rstudio.com/resources/rstudioconf-2020/production-grade-shiny-apps-with-golem/) - `r emo::flag("France")` Rencontres R 2021 : [ Conception d'applications Shiny avec {golem}](https://www.youtube.com/watch?v=0f5Me1PFGDs) - +- `r emo::flag("France")` [ Déploiement d'une application {shiny} dans docker avec {renv} et {golem}](https://www.youtube.com/watch?v=diCG4t76k78) @@ -88,7 +88,7 @@ This package is part of a series of tools for Shiny, which includes: These are examples from the community. Please note that they may not necessarily be written in a canonical fashion and may have been written with different versions of `{golem}` or `{shiny}`. - -- +- - - diff --git a/README.md b/README.md index 8ceefc47..d241396b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ - [![Lifecycle: @@ -24,33 +23,33 @@ You’re reading the doc about version : ``` r desc::desc_get_version() -#> [1] '0.3.2' +#> [1] '0.3.3.9000' ``` ## Tool series This package is part of a series of tools for Shiny, which includes: - - `{golem}` - - - `{shinipsum}` - - - `{fakir}` - - - `{shinysnippets}` - +- `{golem}` - +- `{shinipsum}` - +- `{fakir}` - +- `{shinysnippets}` - ## Resources ### The Book : - - - - [paper version of the book “Engineering Production-Grade Shiny +- +- [paper version of the book “Engineering Production-Grade Shiny Apps”](https://www.routledge.com/Engineering-Production-Grade-Shiny-Apps/Fay-Rochette-Guyader-Girard/p/book/9780367466022) ### Blog posts : *Building Big Shiny Apps* - - Part 1: +- Part 1: - - Part 2: +- Part 2: [*Make a Fitness App from @@ -58,34 +57,36 @@ scratch*](https://towardsdatascience.com/production-grade-r-shiny-with-golem-pro ### Slide decks - - useR\! 2019 : [A Framework for Building Robust & Production Ready +- useR! 2019 : [A Framework for Building Robust & Production Ready Shiny Apps](https://github.com/VincentGuyader/user2019/raw/master/golem_Vincent_Guyader_USER!2019.pdf) - - ThinkR x RStudio Roadshow,Paris : [Production-grade Shiny Apps with +- ThinkR x RStudio Roadshow,Paris : [Production-grade Shiny Apps with {golem}](https://speakerdeck.com/colinfay/production-grade-shiny-apps-with-golem) - - rstudio::conf(2020) : [Production-grade Shiny Apps with +- rstudio::conf(2020) : [Production-grade Shiny Apps with golem](https://speakerdeck.com/colinfay/rstudio-conf-2020-production-grade-shiny-apps-with-golem) - - barcelonar (2019-12-03) : [Engineering Production-Grade Shiny Apps +- barcelonar (2019-12-03) : [Engineering Production-Grade Shiny Apps with {golem}](https://www.barcelonar.org/presentations/BarcelonaR_Building_Production_Grade_Shiny_Apps_with_golem.pdf) ### Video - - [{golem} and Effective Shiny Development +- [{golem} and Effective Shiny Development Methods](https://www.youtube.com/watch?v=OU1-CkSVdTI) - - [Hands-on demonstration of +- [Hands-on demonstration of {golem}](https://www.youtube.com/watch?v=3-p9XLvoJV0) - - useR\! 2019 : [A Framework for Building Robust & Production Ready +- useR! 2019 : [A Framework for Building Robust & Production Ready Shiny Apps](https://youtu.be/tCAan6smrjs) - - 🇫🇷 [Introduction to {golem}](https://youtu.be/6qI4NzxlAFU) - - rstudio::conf(2020) : [Production-grade Shiny Apps with +- 🇫🇷 [Introduction to {golem}](https://youtu.be/6qI4NzxlAFU) +- rstudio::conf(2020) : [Production-grade Shiny Apps with golem](https://www.rstudio.com/resources/rstudioconf-2020/production-grade-shiny-apps-with-golem/) - - 🇫🇷 Rencontres R 2021 : [Conception d’applications Shiny avec +- 🇫🇷 Rencontres R 2021 : [Conception d’applications Shiny avec {golem}](https://www.youtube.com/watch?v=0f5Me1PFGDs) +- 🇫🇷 [Déploiement d’une application {shiny} dans docker avec {renv} et + {golem}](https://www.youtube.com/watch?v=diCG4t76k78) ### Cheatsheet - - [{golem} cheatsheet](https://thinkr.fr/golem_cheatsheet_v0.1.pdf) +- [{golem} cheatsheet](https://thinkr.fr/golem_cheatsheet_v0.1.pdf) ### Examples apps @@ -93,31 +94,27 @@ These are examples from the community. Please note that they may not necessarily be written in a canonical fashion and may have been written with different versions of `{golem}` or `{shiny}`. - - - - - - - - +- +- +- +- You can also find apps at: - - - - +- +- ## Installation - - You can install the stable version from CRAN with: - - +- You can install the stable version from CRAN with: ``` r install.packages("golem") ``` - - You can install the development version from +- You can install the development version from [GitHub](https://github.com/Thinkr-open/golem) with: - - ``` r # install.packages("remotes") remotes::install_github("Thinkr-open/golem") @@ -125,8 +122,7 @@ remotes::install_github("Thinkr-open/golem") ## Launch the project -Create a new package with the project -template: +Create a new package with the project template: diff --git a/inst/mantests/build.R b/inst/mantests/build.R index 8257554c..d17fec96 100644 --- a/inst/mantests/build.R +++ b/inst/mantests/build.R @@ -248,7 +248,7 @@ withr::with_tempdir({ golem::set_golem_options() expect_equal( golem::get_golem_wd(), - here::here() + golem::pkg_path() ) expect_equal( golem::get_golem_name(), @@ -299,7 +299,7 @@ withr::with_tempdir({ dir.exists("tests") ) - golem::use_recommended_deps() + # golem::use_recommended_deps() golem::use_utils_ui(with_test = TRUE) expect_true( @@ -432,4 +432,4 @@ withr::with_tempdir({ # unlink(temp_lib, TRUE, TRUE) cli::cat_rule("Completed") -}) \ No newline at end of file +}) diff --git a/inst/shinyexample/dev/02_dev.R b/inst/shinyexample/dev/02_dev.R index 82478726..67699ea7 100644 --- a/inst/shinyexample/dev/02_dev.R +++ b/inst/shinyexample/dev/02_dev.R @@ -15,6 +15,7 @@ ## Dependencies ---- ## Amend DESCRIPTION with dependencies read from package code parsing +## install.package('attachment') # if needed. attachment::att_amend_desc() ## Add modules ---- diff --git a/inst/shinyexample/dev/03_deploy.R b/inst/shinyexample/dev/03_deploy.R index 464d59fd..2f9595ef 100644 --- a/inst/shinyexample/dev/03_deploy.R +++ b/inst/shinyexample/dev/03_deploy.R @@ -33,10 +33,8 @@ golem::add_shinyserver_file() ## Docker ---- ## If you want to deploy via a generic Dockerfile -golem::add_dockerfile() +golem::add_dockerfile_with_renv() ## If you want to deploy to ShinyProxy -golem::add_dockerfile_shinyproxy() +golem::add_dockerfile_with_renv_shinyproxy() -## If you want to deploy to Heroku -golem::add_dockerfile_heroku() diff --git a/inst/shinyexample/inst/golem-config.yml b/inst/shinyexample/inst/golem-config.yml index 6ce2c787..3e66f3fb 100644 --- a/inst/shinyexample/inst/golem-config.yml +++ b/inst/shinyexample/inst/golem-config.yml @@ -1,11 +1,11 @@ -default: +default: golem_name: shinyexample golem_version: 0.0.0.9000 app_prod: no -production: +production: app_prod: yes - -dev: - golem_wd: !exp here::here() + +dev: + golem_wd: !expr golem::pkg_path() diff --git a/man/amend_golem_config.Rd b/man/amend_golem_config.Rd index 208f95a3..1c803ebb 100644 --- a/man/amend_golem_config.Rd +++ b/man/amend_golem_config.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/config.R +% Please edit documentation in R/golem-yaml-utils.R \name{amend_golem_config} \alias{amend_golem_config} \title{Amend golem config file} @@ -8,7 +8,7 @@ amend_golem_config( key, value, config = "default", - pkg = get_golem_wd(), + pkg = golem::pkg_path(), talkative = TRUE ) } diff --git a/man/dockerfiles.Rd b/man/dockerfiles.Rd index bbeb97b4..dfd5715b 100644 --- a/man/dockerfiles.Rd +++ b/man/dockerfiles.Rd @@ -1,16 +1,19 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/add_dockerfiles.R +% Please edit documentation in R/add_dockerfiles.R, R/add_dockerfiles_renv.R \name{add_dockerfile} \alias{add_dockerfile} \alias{add_dockerfile_shinyproxy} \alias{add_dockerfile_heroku} +\alias{add_dockerfile_with_renv} +\alias{add_dockerfile_with_renv_shinyproxy} +\alias{add_dockerfile_with_renv_heroku} \title{Create a Dockerfile for your App} \usage{ add_dockerfile( path = "DESCRIPTION", output = "Dockerfile", pkg = get_golem_wd(), - from = paste0("rocker/r-ver:", R.Version()$major, ".", R.Version()$minor), + from = paste0("rocker/verse:", R.Version()$major, ".", R.Version()$minor), as = NULL, port = 80, host = "0.0.0.0", @@ -27,7 +30,7 @@ add_dockerfile_shinyproxy( path = "DESCRIPTION", output = "Dockerfile", pkg = get_golem_wd(), - from = paste0("rocker/r-ver:", R.Version()$major, ".", R.Version()$minor), + from = paste0("rocker/verse:", R.Version()$major, ".", R.Version()$minor), as = NULL, sysreqs = TRUE, repos = c(CRAN = "https://cran.rstudio.com/"), @@ -42,7 +45,7 @@ add_dockerfile_heroku( path = "DESCRIPTION", output = "Dockerfile", pkg = get_golem_wd(), - from = paste0("rocker/r-ver:", R.Version()$major, ".", R.Version()$minor), + from = paste0("rocker/verse:", R.Version()$major, ".", R.Version()$minor), as = NULL, sysreqs = TRUE, repos = c(CRAN = "https://cran.rstudio.com/"), @@ -52,6 +55,54 @@ add_dockerfile_heroku( build_golem_from_source = TRUE, extra_sysreqs = NULL ) + +add_dockerfile_with_renv( + source_folder = ".", + lockfile = NULL, + output_dir = fs::path(tempdir(), "deploy"), + distro = "focal", + from = "rocker/verse", + as = NULL, + sysreqs = TRUE, + port = 80, + host = "0.0.0.0", + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + open = TRUE, + extra_sysreqs = NULL, + update_tar_gz = TRUE, + dockerfile_cmd = NULL +) + +add_dockerfile_with_renv_shinyproxy( + source_folder = ".", + lockfile = NULL, + output_dir = fs::path(tempdir(), "deploy"), + distro = "focal", + from = "rocker/verse", + as = NULL, + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + extra_sysreqs = NULL, + open = TRUE, + update_tar_gz = TRUE +) + +add_dockerfile_with_renv_heroku( + source_folder = ".", + lockfile = NULL, + output_dir = fs::path(tempdir(), "deploy"), + distro = "focal", + from = "rocker/verse", + as = NULL, + sysreqs = TRUE, + repos = c(CRAN = "https://cran.rstudio.com/"), + expand = FALSE, + extra_sysreqs = NULL, + open = TRUE, + update_tar_gz = TRUE +) } \arguments{ \item{path}{path to the DESCRIPTION file to use as an input.} @@ -61,7 +112,12 @@ add_dockerfile_heroku( \item{pkg}{Path to the root of the package. Default is \code{get_golem_wd()}.} \item{from}{The FROM of the Dockerfile. Default is -FROM rocker/r-ver:\code{R.Version()$major}.\code{R.Version()$minor}.} + +\if{html}{\out{
}}\preformatted{FROM rocker/verse + +without renv.lock file passed +`R.Version()$major`.`R.Version()$minor` is used as tag +}\if{html}{\out{
}}} \item{as}{The AS of the Dockerfile. Default it NULL.} @@ -77,7 +133,7 @@ Default is 0.0.0.0.} \item{expand}{boolean. If \code{TRUE} each system requirement will have its own \code{RUN} line.} -\item{open}{boolean. Should the Dockerfile be open after creation? Default is \code{TRUE}.} +\item{open}{boolean. Should the Dockerfile/README/README be open after creation? Default is \code{TRUE}.} \item{update_tar_gz}{boolean. If \code{TRUE} and \code{build_golem_from_source} is also \code{TRUE}, an updated tar.gz is created.} @@ -85,15 +141,27 @@ an updated tar.gz is created.} \item{build_golem_from_source}{boolean. If \code{TRUE} no tar.gz is created and the Dockerfile directly mount the source folder.} -\item{extra_sysreqs}{character vector. Extra debian system requirements. -Will be installed with apt-get install.} +\item{extra_sysreqs}{character vector. Extra debian system requirements.} + +\item{source_folder}{path to the Package/golem source folder to deploy. +default is current folder '.'} + +\item{lockfile}{path to the renv.lock file to use. default is \code{NULL}} + +\item{output_dir}{folder to export everything deployment related.} + +\item{distro}{One of "focal", "bionic", "xenial", "centos7", or "centos8". +See available distributions at https://hub.docker.com/r/rstudio/r-base/.} + +\item{dockerfile_cmd}{What is the CMD to add to the Dockerfile. If NULL, the default, +the CMD will be \verb{R -e "options('shiny.port'=\{port\},shiny.host='\{host\}');\{appname\}::run_app()\\}} } \value{ The \code{{dockerfiler}} object, invisibly. } \description{ -Build a container containing your Shiny App. \code{add_dockerfile()} creates -a generic Dockerfile, while \code{add_dockerfile_shinyproxy()} and +Build a container containing your Shiny App. \code{add_dockerfile()} and \code{add_dockerfile_with_renv()} and \code{add_dockerfile_with_renv()} creates +a generic Dockerfile, while \code{add_dockerfile_shinyproxy()}, \code{add_dockerfile_with_renv_shinyproxy()} , \code{add_dockerfile_with_renv_shinyproxy()} and \code{add_dockerfile_heroku()} creates platform specific Dockerfile. } \examples{ @@ -102,10 +170,46 @@ a generic Dockerfile, while \code{add_dockerfile_shinyproxy()} and if (interactive()) { add_dockerfile() } +# Crete a 'deploy' folder containing everything needed to deploy +# the golem using docker based on {renv} +if (interactive()) { + add_dockerfile_with_renv( + # lockfile = "renv.lock", # uncomment to use existing renv.lock file + output_dir = "deploy" + ) +} +# Crete a 'deploy' folder containing everything needed to deploy +# the golem using docker based on {renv} +if (interactive()) { + add_dockerfile_with_renv( + # lockfile = "renv.lock", # uncomment to use existing renv.lock file + output_dir = "deploy" + ) +} # Add a Dockerfile for ShinyProxy if (interactive()) { add_dockerfile_shinyproxy() } + +# Crete a 'deploy' folder containing everything needed to deploy +# the golem with ShinyProxy using docker based on {renv} +if (interactive()) { + add_dockerfile_with_renv( + # lockfile = "renv.lock",# uncomment to use existing renv.lock file + output_dir = "deploy" + ) +} + + +# Crete a 'deploy' folder containing everything needed to deploy +# the golem with ShinyProxy using docker based on {renv} +if (interactive()) { + add_dockerfile_with_renv( + # lockfile = "renv.lock",# uncomment to use existing renv.lock file + output_dir = "deploy" + ) +} + # Add a Dockerfile for Heroku if (interactive()) { add_dockerfile_heroku() diff --git a/man/document_and_reload.Rd b/man/document_and_reload.Rd index 8d0c84ec..95b60561 100644 --- a/man/document_and_reload.Rd +++ b/man/document_and_reload.Rd @@ -24,7 +24,7 @@ which defaults to \code{c("collate", "namespace", "rd")}.} \item{load_code}{A function used to load all the R code in the package directory. The default, \code{NULL}, uses the strategy defined by -the \code{load} roxygen option, which defaults to \code{\link[roxygen2:load]{load_pkgload()}}. +the \code{load} roxygen option, which defaults to \code{\link[roxygen2:load_pkgload]{load_pkgload()}}. See \link[roxygen2]{load} for more details.} \item{clean}{If \code{TRUE}, roxygen will delete all files previously diff --git a/man/figures/logo.png b/man/figures/logo.png deleted file mode 100644 index a149d478..00000000 Binary files a/man/figures/logo.png and /dev/null differ diff --git a/man/get_current_config.Rd b/man/get_current_config.Rd new file mode 100644 index 00000000..e2ed0da8 --- /dev/null +++ b/man/get_current_config.Rd @@ -0,0 +1,16 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/config.R +\name{get_current_config} +\alias{get_current_config} +\title{Get the path to the current config File} +\usage{ +get_current_config(path = ".") +} +\arguments{ +\item{path}{Path to start looking for the config} +} +\description{ +This function tries to guess where the golem-config file is located. +If it can't find it, this function asks the +user if they want to set the golem skeleton. +} diff --git a/man/golem_opts.Rd b/man/golem_opts.Rd index fa7de33a..e6c115bf 100644 --- a/man/golem_opts.Rd +++ b/man/golem_opts.Rd @@ -1,75 +1,83 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/options.R -\name{set_golem_options} -\alias{set_golem_options} -\alias{set_golem_wd} -\alias{set_golem_name} -\alias{set_golem_version} +% Please edit documentation in R/golem-yaml-get.R, R/golem-yaml-set.R, +% R/set_golem_options.R +\name{get_golem_wd} \alias{get_golem_wd} \alias{get_golem_name} \alias{get_golem_version} +\alias{set_golem_wd} +\alias{set_golem_name} +\alias{set_golem_version} +\alias{set_golem_options} \title{\code{{golem}} options} \usage{ -set_golem_options( - golem_name = golem::pkg_name(), - golem_version = golem::pkg_version(), +get_golem_wd(use_parent = TRUE, pkg = golem::pkg_path()) + +get_golem_name( + config = Sys.getenv("GOLEM_CONFIG_ACTIVE", Sys.getenv("R_CONFIG_ACTIVE", "default")), + use_parent = TRUE, + pkg = golem::pkg_path() +) + +get_golem_version( + config = Sys.getenv("GOLEM_CONFIG_ACTIVE", Sys.getenv("R_CONFIG_ACTIVE", "default")), + use_parent = TRUE, + pkg = golem::pkg_path() +) + +set_golem_wd( golem_wd = golem::pkg_path(), - app_prod = FALSE, + pkg = golem::pkg_path(), talkative = TRUE ) -set_golem_wd(path = golem::pkg_path(), talkative = TRUE) - set_golem_name( name = golem::pkg_name(), - path = golem::pkg_path(), + pkg = golem::pkg_path(), talkative = TRUE ) set_golem_version( version = golem::pkg_version(), - path = golem::pkg_path(), + pkg = golem::pkg_path(), talkative = TRUE ) -get_golem_wd(use_parent = TRUE, path = golem::pkg_path()) - -get_golem_name( - config = Sys.getenv("R_CONFIG_ACTIVE", "default"), - use_parent = TRUE, - path = golem::pkg_path() -) - -get_golem_version( - config = Sys.getenv("R_CONFIG_ACTIVE", "default"), - use_parent = TRUE, - path = golem::pkg_path() +set_golem_options( + golem_name = golem::pkg_name(), + golem_version = golem::pkg_version(), + golem_wd = golem::pkg_path(), + app_prod = FALSE, + talkative = TRUE, + config_file = golem::get_current_config(golem_wd) ) } \arguments{ -\item{golem_name}{Name of the current golem.} +\item{use_parent}{\code{TRUE} to scan parent directories for +configuration files if the specified config file isn't found.} -\item{golem_version}{Version of the current golem.} +\item{pkg}{The path to set the golem working directory. +Note that it will be passed to \code{normalizePath}.} -\item{golem_wd}{Working directory of the current golem package.} +\item{config}{Name of configuration to read from. Defaults to +the value of the \code{R_CONFIG_ACTIVE} environment variable +("default" if the variable does not exist).} -\item{app_prod}{Is the \code{{golem}} in prod mode?} +\item{golem_wd}{Working directory of the current golem package.} \item{talkative}{Should the messages be printed to the console?} -\item{path}{The path to set the golem working directory. -Note that it will be passed to \code{normalizePath}.} - \item{name}{The name of the app} \item{version}{The version of the app} -\item{use_parent}{\code{TRUE} to scan parent directories for -configuration files if the specified config file isn't found.} +\item{golem_name}{Name of the current golem.} -\item{config}{Name of configuration to read from. Defaults to -the value of the \code{R_CONFIG_ACTIVE} environment variable -("default" if the variable does not exist).} +\item{golem_version}{Version of the current golem.} + +\item{app_prod}{Is the \code{{golem}} in prod mode?} + +\item{config_file}{path to the {golem} config file} } \value{ Used for side-effects for the setters, and values from the @@ -84,7 +92,7 @@ inside the \code{inst} folder. \itemize{ \item \code{set_golem_options()} sets all the options, with the defaults from the functions below. -\item \code{set_golem_wd()} defaults to \code{here::here()}, which is the package root when starting a golem. +\item \code{set_golem_wd()} defaults to \code{golem::golem_wd()}, which is the package root when starting a golem. \item \code{set_golem_name()} defaults \code{golem::pkg_name()} \item \code{set_golem_version()} defaults \code{golem::pkg_version()} } diff --git a/man/pkg_tools.Rd b/man/pkg_tools.Rd index 14f802ab..6103d946 100644 --- a/man/pkg_tools.Rd +++ b/man/pkg_tools.Rd @@ -10,13 +10,10 @@ pkg_name(path = ".") pkg_version(path = ".") -pkg_path(path = ".", depth = 3) +pkg_path(path = ".") } \arguments{ \item{path}{Path to use to read the DESCRIPTION} - -\item{depth}{Number of parent directory to visit before -deciding we are not in a package.} } \description{ These are functions to help you navigate diff --git a/tests/testthat/helper-config.R b/tests/testthat/helper-config.R index d9af3ab9..564d5e41 100644 --- a/tests/testthat/helper-config.R +++ b/tests/testthat/helper-config.R @@ -3,6 +3,12 @@ library(golem) library(withr) old_usethis.quiet <- getOption("usethis.quiet") options("usethis.quiet" = TRUE) +# Small hack to prevent warning from rlang::lang() in tests +# This should be managed in {attempt} later on +x <- suppressWarnings({ + rlang::lang(print) +}) + ### Funs remove_file <- function(path) { if (file.exists(path)) unlink(path, force = TRUE) @@ -56,13 +62,15 @@ fakename <- sprintf( gsub("[ :-]", "", Sys.time()) ) + +## random dir +randir <- paste0(sample(safe_let(), 10, TRUE), collapse = "") + tpdir <- normalizePath(tempdir()) unlink(file.path(tpdir, fakename), recursive = TRUE) create_golem(file.path(tpdir, fakename), open = FALSE) pkg <- file.path(tpdir, fakename) -## random dir -randir <- paste0(sample(safe_let(), 10, TRUE), collapse = "") fp <- file.path("inst/app", randir) dir.create(file.path(pkg, fp), recursive = TRUE) @@ -73,6 +81,8 @@ rand_name <- function() { withr::with_dir(pkg, { set_golem_options() usethis::proj_set(pkg) - orig_test <- set_golem_wd(pkg) + orig_test <- set_golem_wd( + pkg = pkg, + ) usethis::use_mit_license("Golem") }) diff --git a/tests/testthat/test-add_deploy_helpers.R b/tests/testthat/test-add_deploy_helpers.R index 8aef7553..7057e65a 100644 --- a/tests/testthat/test-add_deploy_helpers.R +++ b/tests/testthat/test-add_deploy_helpers.R @@ -76,60 +76,8 @@ test_that("add_dockerfiles repos variation", { } }) }) -test_that("add_dockerfiles multi repos", { - skip_if_not_installed("dockerfiler", "0.1.4") - - repos <- c( - bioc1 = "https://bioconductor.org/packages/3.10/data/annotation", - bioc2 = "https://bioconductor.org/packages/3.10/data/experiment", - CRAN = "https://cran.rstudio.com" - ) - - - with_dir(pkg, { - for (fun in list( - add_dockerfile, - add_dockerfile_heroku, - add_dockerfile_shinyproxy - )) { - burn_after_reading( - "Dockerfile", - { - output <- testthat::capture_output( - fun( - pkg = pkg, - sysreqs = FALSE, - open = FALSE, - repos = repos, - output = "Dockerfile" - ) - ) - - expect_exists("Dockerfile") - - - test <- stringr::str_detect( - output, - "Dockerfile created at Dockerfile" - ) - expect_true(test) - to_find <- "RUN echo \"options(repos = c(bioc1 = 'https://bioconductor.org/packages/3.10/data/annotation', bioc2 = 'https://bioconductor.org/packages/3.10/data/experiment', CRAN = 'https://cran.rstudio.com'), download.file.method = 'libcurl', Ncpus = 4)\" >> /usr/local/lib/R/etc/Rprofile.site" - # for R <= 3.4 - to_find_old <- "RUN echo \"options(repos = structure(c('https://bioconductor.org/packages/3.10/data/annotation', 'https://bioconductor.org/packages/3.10/data/experiment', 'https://cran.rstudio.com'), .Names = c('bioc1', 'bioc2', 'CRAN')), download.file.method = 'libcurl', Ncpus = 4)\" >> /usr/local/lib/R/etc/Rprofile.site" - - expect_true( - sum( - readLines(con = "Dockerfile") %in% c(to_find, to_find_old) - ) == 1 - ) - } - ) - } - }) -}) - test_that("add_rstudio_files", { with_dir(pkg, { for (fun in list( diff --git a/tests/testthat/test-config.R b/tests/testthat/test-config.R index c5d52490..fd553276 100644 --- a/tests/testthat/test-config.R +++ b/tests/testthat/test-config.R @@ -1,8 +1,24 @@ test_that("config works", { with_dir(pkg, { - expect_equal(get_golem_name(), fakename) - expect_equal(get_golem_version(), "0.0.0.9000") - expect_equal(normalizePath(get_golem_wd(), mustWork = FALSE), normalizePath(pkg, mustWork = FALSE)) + # We'll try to be sure that + # golem_wd: !expr golem::pkg_path() + # is kept along the way + expect_equal( + tail(readLines("inst/golem-config.yml"), 1), + " golem_wd: !expr golem::pkg_path()" + ) + expect_equal( + get_golem_name(), + fakename + ) + expect_equal( + get_golem_version(), + "0.0.0.9000" + ) + expect_equal( + normalizePath(get_golem_wd(), mustWork = FALSE), + normalizePath(pkg, mustWork = FALSE) + ) amend_golem_config( key = "where", value = "indev" @@ -12,25 +28,51 @@ test_that("config works", { value = "inprod", config = "production" ) - - expect_equal(config::get("where", file = "inst/golem-config.yml"), "indev") - expect_equal(config::get("where", config = "production", file = "inst/golem-config.yml"), "inprod") + expect_equal( + tail(readLines("inst/golem-config.yml"), 1), + " golem_wd: !expr golem::pkg_path()" + ) + expect_equal( + config::get("where", file = "inst/golem-config.yml"), + "indev" + ) + expect_equal( + config::get("where", config = "production", file = "inst/golem-config.yml"), + "inprod" + ) where_conf <- withr::with_envvar( c("R_CONFIG_ACTIVE" = "production"), { config::get("where", file = "inst/golem-config.yml") } ) - expect_equal(where_conf, "inprod") + expect_equal( + where_conf, + "inprod" + ) set_golem_name("plop") - expect_equal(get_golem_name(), "plop") + expect_equal( + get_golem_name(), + "plop" + ) set_golem_name(fakename) set_golem_version("0.0.0.9001") - expect_equal(get_golem_version(), "0.0.0.9001") + expect_equal( + get_golem_version(), + "0.0.0.9001" + ) set_golem_version("0.0.0.9000") set_golem_wd(normalizePath("inst")) - expect_equal(normalizePath(get_golem_wd()), normalizePath("inst")) + expect_equal( + normalizePath(get_golem_wd()), + normalizePath("inst") + ) set_golem_wd(pkg) + # Be sure that after setting the stuff the wd is still here::here() + expect_equal( + tail(readLines("inst/golem-config.yml"), 1), + " golem_wd: !expr golem::pkg_path()" + ) }) }) diff --git a/tests/testthat/test-renv_stuff.R b/tests/testthat/test-renv_stuff.R new file mode 100644 index 00000000..43d74c15 --- /dev/null +++ b/tests/testthat/test-renv_stuff.R @@ -0,0 +1,33 @@ +test_that("add_dockerfiles_renv and add_dockerfile_with_renv_shinyproxy all output file are present", { + skip_if_not_installed("dockerfiler", "0.2.0") + + with_dir(pkg, { + for (fun in list( + add_dockerfile_with_renv, + add_dockerfile_with_renv_shinyproxy, + add_dockerfile_with_renv_heroku + )) { + deploy_folder <- file.path( + tempdir(), + make.names( + paste0( + "deploy", + round( + runif(1, min = 0, max = 99999) + ) + ) + ) + ) + + fun(output_dir = deploy_folder, open = FALSE) + + expect_exists(file.path(deploy_folder, "Dockerfile")) + expect_exists(file.path(deploy_folder, "Dockerfile_base")) + expect_exists(file.path(deploy_folder, "README")) + expect_exists(file.path(deploy_folder, "renv.lock.prod")) + + expect_length(list.files(path = deploy_folder, pattern = "tar.gz$"), 1) + unlink(deploy_folder, force = TRUE, recursive = TRUE) + } + }) +}) diff --git a/vignettes/a_start.Rmd b/vignettes/a_start.Rmd index 002671ec..18e474e0 100644 --- a/vignettes/a_start.Rmd +++ b/vignettes/a_start.Rmd @@ -1,5 +1,5 @@ --- -title: "Getting Started with {golem}" +title: "a_start" author: "Colin Fay" date: "`r Sys.Date()`" output: rmarkdown::html_vignette @@ -77,7 +77,8 @@ Once you've got that, a new RStudio project will be launched. Here is the struct z <- capture.output(fs::dir_tree(x)) z <- z[-1] w <- lapply( - z, function(x) { + z, + function(x) { cat(x, "\n") } ) @@ -122,7 +123,7 @@ golem::fill_desc( ) ``` -About [the DESCRIPTION file](https://r-pkgs.org/description.html). +About [the DESCRIPTION file](https://r-pkgs.org/Metadata.html#sec-description). ### Add `{golem}` options diff --git a/vignettes/b_dev.Rmd b/vignettes/b_dev.Rmd index aaa3dd82..c0b41100 100644 --- a/vignettes/b_dev.Rmd +++ b/vignettes/b_dev.Rmd @@ -1,5 +1,5 @@ --- -title: "Day to Day Dev with {golem}" +title: "b_dev" author: "Colin Fay" date: "`r Sys.Date()`" output: rmarkdown::html_vignette @@ -45,7 +45,7 @@ Note that the `{attachment}` package should be installed on your machine. attachment::att_amend_desc() ``` -About [package dependencies](https://r-pkgs.org/namespace.html). +About [package dependencies](https://r-pkgs.org/Metadata.html#sec-namespace). ### Add modules @@ -139,7 +139,7 @@ Add more tests to your application: usethis::use_test("app") ``` -About [testing a package](https://r-pkgs.org/tests.html). +About [testing a package](https://r-pkgs.org/testing-basics.html). ## Documentation diff --git a/vignettes/c_deploy.Rmd b/vignettes/c_deploy.Rmd index 4021cd03..5ff5b4c7 100644 --- a/vignettes/c_deploy.Rmd +++ b/vignettes/c_deploy.Rmd @@ -1,5 +1,5 @@ --- -title: "Deploying Apps with {golem}" +title: "c_deploy" author: "Colin Fay" date: "`r Sys.Date()`" output: rmarkdown::html_vignette @@ -69,6 +69,10 @@ golem::add_shinyserver_file() ### Docker +#### without using {renv} + + + ```{r} # If you want to deploy via a generic Dockerfile golem::add_dockerfile() @@ -80,3 +84,75 @@ golem::add_dockerfile_shinyproxy() golem::add_dockerfile_heroku() ``` +#### using {renv} + + +#### CASE 1 : you didn't use renv during developpment process + + +> this functions will create a "deploy" folder containing : + +```{txt} +deploy/ ++-- Dockerfile ++-- Dockerfile_base ++-- yourgolem_0.0.0.9000.tar.gz ++-- README +\-- renv.lock.prod +``` + +then follow the README file + + +```{r} +# If you want to deploy via a generic Dockerfile +golem::add_dockerfile_with_renv(output_dir = "deploy") + +# If you want to deploy to ShinyProxy +golem::add_dockerfile_with_renv_shinyproxy(output_dir = "deploy") + +``` + +If you would like to use {renv} during developpement, you can init a renv.lock file with + +```{r} +attachment::create_renv_for_dev(dev_pkg = c("renv", "devtools", "roxygen2", + "usethis", "pkgload", "testthat", "remotes", "covr", "attachment", + "pak", "dockerfiler","golem")) +``` +an activate {renv} with + +```{r} +renv::activate() +``` + + + + + +#### CASE 2 : you already have a renv.lock file for your project + + +```{r} + +# If you want to deploy via a generic Dockerfile +golem::add_dockerfile_with_renv(output_dir = "deploy",lockfile = "renv.lock") + +# If you want to deploy to ShinyProxy +golem::add_dockerfile_with_renv_shinyproxy(output_dir = "deploy",lockfile = "renv.lock") + + +``` + +> this functions will create a "deploy" folder containing : + +```{txt} +deploy/ ++-- Dockerfile ++-- Dockerfile_base ++-- yourgolem_0.0.0.9000.tar.gz ++-- README +\-- renv.lock.prod +``` + +then follow the README file \ No newline at end of file diff --git a/vignettes/d_js.Rmd b/vignettes/d_js.Rmd index 72629009..757b3a42 100644 --- a/vignettes/d_js.Rmd +++ b/vignettes/d_js.Rmd @@ -1,5 +1,5 @@ --- -title: "Using {golem} js functions" +title: "d_js" author: "Colin Fay" date: "`r Sys.Date()`" output: rmarkdown::html_vignette diff --git a/vignettes/e_config.Rmd b/vignettes/e_config.Rmd index 5339ec26..5b583ea4 100644 --- a/vignettes/e_config.Rmd +++ b/vignettes/e_config.Rmd @@ -1,5 +1,5 @@ --- -title: "Using golem config" +title: "e_config" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{config} @@ -27,6 +27,10 @@ knitr::opts_knit$set(root.dir = x) library(golem) ``` +```{r echo = FALSE} +old <- setwd(x) +``` + ## About `inst/golem-config.yml` When you start a new `{golem}` application, you'll find a file called `golem-config.yml` in the `inst/` folder. @@ -39,7 +43,7 @@ This config file is based on the [`{config}`](https://github.com/rstudio/config) Here is what the default config file looks like: -```{r echo = FALSE, comment= ""} +```{r echo = FALSE, comment= "", } cat( sep = "\n", readLines( @@ -60,6 +64,15 @@ These options are globally set with: set_golem_options() ``` +```{r echo = FALSE, comment= "", } +cat( + sep = "\n", + readLines( + "inst/golem-config.yml" + ) +) +``` + The functions reading the options in this config file are: ```{r} @@ -76,6 +89,15 @@ set_golem_wd(".") set_golem_version("0.0.1") ``` +```{r echo = FALSE, comment= "", } +cat( + sep = "\n", + readLines( + "inst/golem-config.yml" + ) +) +``` + ## Using `golem-config` @@ -95,6 +117,7 @@ amend_golem_config( ) ``` + Will result in a `golem-config.yml` file as such: ```{r echo = FALSE, comment= ""} @@ -143,4 +166,8 @@ The idea is also that the `golem-config.yml` file is shareable across `{golem}` ## Note for `{golem}` < 0.2.0 users -If you've built an app with `{golem}` before the version 0.2.0, this config file doesn't exist: you'll be prompted to create it if you update a newer version of `{golem}`. \ No newline at end of file +If you've built an app with `{golem}` before the version 0.2.0, this config file doesn't exist: you'll be prompted to create it if you update a newer version of `{golem}`. + +```{r echo = FALSE} +setwd(old) +``` \ No newline at end of file diff --git a/vignettes/f_extending_golem.Rmd b/vignettes/f_extending_golem.Rmd index d9f6b087..5f14c655 100644 --- a/vignettes/f_extending_golem.Rmd +++ b/vignettes/f_extending_golem.Rmd @@ -1,5 +1,5 @@ --- -title: "Extending {golem}" +title: "f_extending_golem" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{f_extending_golem} @@ -10,7 +10,7 @@ vignette: > ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, - comment = "#>", + comment = "#>", eval = FALSE ) ``` @@ -70,8 +70,8 @@ These parameters might not be used inside your own hook, but __they need to be s Here is an example of a function that can be used to remove the `dev/` folder: ```{r} -no_dev <- function(path, package_name, ...){ - fs::dir_delete("dev") +no_dev <- function(path, package_name, ...) { + fs::dir_delete("dev") } create_golem("ici", project_hook = no_dev) ``` @@ -79,22 +79,20 @@ create_golem("ici", project_hook = no_dev) This one will create a CSS: ```{r} -new_css <- function(path, package_name, ...){ - +new_css <- function(path, package_name, ...) { css_path <- fs::path_abs("inst/app/www/custom.css") - + fs::file_create(css_path) - - write_there <- function(...){ + + write_there <- function(...) { write(..., file = css_path, append = TRUE) } - + write_there("body {") write_there(" background-color:red;") write_there("}") - + cli::cat_bullet("CSS generated") - } create_golem("ici", project_hook = new_css) @@ -137,16 +135,16 @@ These parameters might not be used inside your own function, but __they need to ### Example ```{r} -my_tmpl <- function(name, path, export, ...){ - # Define a template that only write the name of the - # module in the file - write(name, path) +my_tmpl <- function(name, path, export, ...) { + # Define a template that only write the name of the + # module in the file + write(name, path) } golem::add_module(name = "custom", module_template = my_tmpl) -my_other_tmpl <- function(name, path, ...){ - # Copy and paste a file from somewhere else - file.copy(..., path) +my_other_tmpl <- function(name, path, ...) { + # Copy and paste a file from somewhere else + file.copy(..., path) } golem::add_module(name = "custom", module_template = my_other_tmpl) ``` @@ -186,13 +184,13 @@ These parameters might not be used inside your own function, but __they need to ### Example ```{r} -my_tmpl <- function(path, ...){ - # Define a template that only write the name of the - # module in the file - write_there <- function(...){ +my_tmpl <- function(path, ...) { + # Define a template that only write the name of the + # module in the file + write_there <- function(...) { write(..., file = path, append = TRUE) } - + write_there("body {") write_there(" background-color:red;") write_there("}") diff --git a/vignettes/z_golem_cheatsheet.Rmd b/vignettes/z_golem_cheatsheet.Rmd index 4c1c9199..10c3967b 100644 --- a/vignettes/z_golem_cheatsheet.Rmd +++ b/vignettes/z_golem_cheatsheet.Rmd @@ -1,5 +1,5 @@ --- -title: "{golem} cheatsheet" +title: "e_golem_cheatsheet" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{e_golem_cheatsheet}