├── .gitattributes ├── vignettes ├── .gitignore └── ghactions.Rmd ├── tests ├── testthat │ ├── test_setup.R │ ├── workflows │ │ ├── unnamed.yaml │ │ ├── named.yml │ │ ├── run_multiline.yml │ │ ├── on_multiple.yml │ │ ├── on_schedule.yml │ │ ├── rscript_string.yml │ │ ├── container_simple.yml │ │ ├── on_push_path.yml │ │ ├── job.yml │ │ ├── matrix_strategy.yml │ │ ├── rcmdcheck.yml │ │ ├── covr.yml │ │ ├── on_push_filter.yml │ │ ├── steps_first.yml │ │ ├── steps_with.yml │ │ ├── ghpages.yml │ │ ├── container_adv.yml │ │ ├── matrix_in_exclude.yml │ │ ├── services.yml │ │ ├── install_deps.yml │ │ └── rsync.yml │ ├── test_io.R │ ├── test_steps.R │ └── test_syntax.R └── testthat.R ├── logo.png ├── tic.R ├── R ├── ghactions-package.R ├── helpers.R ├── workflows.R ├── io.R ├── setup.R ├── steps.R └── syntax.R ├── pkgdown └── favicon │ ├── favicon.ico │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── apple-touch-icon.png │ ├── apple-touch-icon-120x120.png │ ├── apple-touch-icon-152x152.png │ ├── apple-touch-icon-180x180.png │ ├── apple-touch-icon-60x60.png │ └── apple-touch-icon-76x76.png ├── man ├── is_github_actions.Rd ├── checkout.Rd ├── edit_workflow.Rd ├── ghactions_events.Rd ├── ghactions_vms.Rd ├── pkg_dev.Rd ├── ghactions.Rd ├── use_ghactions.Rd ├── gh_matrix.Rd ├── use_ghactions_badge.Rd ├── strategy.Rd ├── container.Rd ├── workflow.Rd ├── io.Rd ├── website.Rd ├── step.Rd ├── install_deps.Rd ├── on.Rd ├── job.Rd ├── rscript.Rd └── deploy_static.Rd ├── ghactions.Rproj ├── .Rbuildignore ├── NAMESPACE ├── .gitignore ├── LICENSE ├── .github └── workflows │ ├── main-old.yml │ └── main.yml ├── DESCRIPTION ├── _pkgdown.yml └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | *.Rd binary 2 | -------------------------------------------------------------------------------- /vignettes/.gitignore: -------------------------------------------------------------------------------- 1 | /*.html 2 | *.R 3 | -------------------------------------------------------------------------------- /tests/testthat/test_setup.R: -------------------------------------------------------------------------------- 1 | context("Setup") 2 | -------------------------------------------------------------------------------- /tests/testthat/workflows/unnamed.yaml: -------------------------------------------------------------------------------- 1 | 'on': push 2 | -------------------------------------------------------------------------------- /tests/testthat/workflows/named.yml: -------------------------------------------------------------------------------- 1 | name: foo 2 | 'on': push 3 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxheld83/ghactions/HEAD/logo.png -------------------------------------------------------------------------------- /tests/testthat/workflows/run_multiline.yml: -------------------------------------------------------------------------------- 1 | - run: |- 2 | foo 3 | bar 4 | -------------------------------------------------------------------------------- /tests/testthat/workflows/on_multiple.yml: -------------------------------------------------------------------------------- 1 | 'on': 2 | - push 3 | - pull_request 4 | -------------------------------------------------------------------------------- /tests/testthat/workflows/on_schedule.yml: -------------------------------------------------------------------------------- 1 | 'on': 2 | schedule: 3 | cron: '*/15 * * * *' 4 | -------------------------------------------------------------------------------- /tic.R: -------------------------------------------------------------------------------- 1 | # installs dependencies, runs R CMD check, runs covr::codecov() 2 | do_package_checks() 3 | -------------------------------------------------------------------------------- /tests/testthat/workflows/rscript_string.yml: -------------------------------------------------------------------------------- 1 | run: |- 2 | Rscript -e "1+1" 3 | Rscript -e "2+2" 4 | -------------------------------------------------------------------------------- /R/ghactions-package.R: -------------------------------------------------------------------------------- 1 | #' @name ghactions 2 | #' @docType package 3 | #' @keywords internal 4 | "_PACKAGE" 5 | -------------------------------------------------------------------------------- /pkgdown/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxheld83/ghactions/HEAD/pkgdown/favicon/favicon.ico -------------------------------------------------------------------------------- /tests/testthat/workflows/container_simple.yml: -------------------------------------------------------------------------------- 1 | ship: 2 | runs-on: ubuntu-18.04 3 | container: node:10.16-jessie 4 | -------------------------------------------------------------------------------- /tests/testthat/workflows/on_push_path.yml: -------------------------------------------------------------------------------- 1 | 'on': 2 | push: 3 | paths: 4 | - '*' 5 | - '!*.js' 6 | -------------------------------------------------------------------------------- /pkgdown/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxheld83/ghactions/HEAD/pkgdown/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxheld83/ghactions/HEAD/pkgdown/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxheld83/ghactions/HEAD/pkgdown/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /tests/testthat/workflows/job.yml: -------------------------------------------------------------------------------- 1 | some_job: 2 | name: bar 3 | needs: 4 | - zap 5 | - zong 6 | runs-on: ubuntu-18.04 7 | -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxheld83/ghactions/HEAD/pkgdown/favicon/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxheld83/ghactions/HEAD/pkgdown/favicon/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxheld83/ghactions/HEAD/pkgdown/favicon/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxheld83/ghactions/HEAD/pkgdown/favicon/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxheld83/ghactions/HEAD/pkgdown/favicon/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /tests/testthat/workflows/matrix_strategy.yml: -------------------------------------------------------------------------------- 1 | matrix: 2 | node: 3 | - 6 4 | - 8 5 | - 10 6 | fail-fast: yes 7 | max-parallel: 3.0 8 | -------------------------------------------------------------------------------- /tests/testthat/workflows/rcmdcheck.yml: -------------------------------------------------------------------------------- 1 | name: Check Package 2 | run: Rscript -e "rcmdcheck::rcmdcheck(error_on = 'error', check_dir = 'check')" 3 | -------------------------------------------------------------------------------- /tests/testthat/workflows/covr.yml: -------------------------------------------------------------------------------- 1 | name: Run Code Coverage 2 | run: Rscript -e "covr::codecov(quiet = FALSE, commit = '$GITHUB_SHA', branch = '$GITHUB_REF')" 3 | -------------------------------------------------------------------------------- /tests/testthat/workflows/on_push_filter.yml: -------------------------------------------------------------------------------- 1 | 'on': 2 | push: 3 | branches: 4 | - master 5 | - releases/* 6 | tags: 7 | - v1 8 | - v1.0 9 | -------------------------------------------------------------------------------- /tests/testthat/workflows/steps_first.yml: -------------------------------------------------------------------------------- 1 | - name: My first step 2 | uses: ./.github/actions/my-action 3 | - name: My backup step 4 | if: failure() 5 | uses: actions/heroku@master 6 | -------------------------------------------------------------------------------- /tests/testthat/workflows/steps_with.yml: -------------------------------------------------------------------------------- 1 | - name: My first step 2 | uses: actions/hello_world@master 3 | with: 4 | first_name: Mona 5 | middle_name: The 6 | last_name: Octocat 7 | -------------------------------------------------------------------------------- /R/helpers.R: -------------------------------------------------------------------------------- 1 | #' Test whether the runtime is GitHub actions 2 | #' 3 | #' @export 4 | #' 5 | #' @family helpers 6 | is_github_actions <- function() { 7 | fs::file_exists("/github/workflow/event.json") 8 | } 9 | -------------------------------------------------------------------------------- /tests/testthat/workflows/ghpages.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to GitHub Pages 2 | if: github.ref == 'refs/heads/master' 3 | uses: maxheld83/ghpages@v0.2.0 4 | env: 5 | BUILD_DIR: $DEPLOY_PATH 6 | GH_PAT: ${{ secrets.GH_PAT }} 7 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(ghactions) 3 | 4 | if (requireNamespace("xml2")) { 5 | test_check("ghactions", reporter = MultiReporter$new(reporters = list(JunitReporter$new(file = "test-results.xml"), CheckReporter$new()))) 6 | } else { 7 | test_check("ghactions") 8 | } 9 | -------------------------------------------------------------------------------- /tests/testthat/workflows/container_adv.yml: -------------------------------------------------------------------------------- 1 | my_job: 2 | runs-on: ubuntu-18.04 3 | container: 4 | image: node:10.16-jessie 5 | env: 6 | NODE_ENV: development 7 | ports: 8 | - 80 9 | volumes: 10 | - my_docker_volume:/volume_mount 11 | options: --cpus 1 12 | -------------------------------------------------------------------------------- /tests/testthat/workflows/matrix_in_exclude.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - macOS-10.14 3 | - windows-2016 4 | - ubuntu-18.04 5 | node: 6 | - 4 7 | - 6 8 | - 8 9 | - 10 10 | include: 11 | - os: windows-2016 12 | node: 4 13 | npm: 2 14 | - os: macOS-10.14 15 | node: 4 16 | npm: 2 17 | -------------------------------------------------------------------------------- /tests/testthat/workflows/services.yml: -------------------------------------------------------------------------------- 1 | my_job: 2 | runs-on: ubuntu-18.04 3 | services: 4 | nginx: 5 | image: nginx 6 | env: 7 | NGINX_PORT: 80 8 | ports: 9 | - 8080:80 10 | redis: 11 | image: redis 12 | ports: 13 | - 6379/tcp 14 | -------------------------------------------------------------------------------- /tests/testthat/workflows/install_deps.yml: -------------------------------------------------------------------------------- 1 | name: Install Package Dependencies 2 | run: |- 3 | Rscript -e "install.packages('remotes', repos = 'https://demo.rstudiopm.com/all/__linux__/bionic/latest')" 4 | Rscript -e "remotes::install_deps(dependencies = TRUE, repos = 'https://demo.rstudiopm.com/all/__linux__/bionic/latest')" 5 | -------------------------------------------------------------------------------- /man/is_github_actions.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/helpers.R 3 | \name{is_github_actions} 4 | \alias{is_github_actions} 5 | \title{Test whether the runtime is GitHub actions} 6 | \usage{ 7 | is_github_actions() 8 | } 9 | \description{ 10 | Test whether the runtime is GitHub actions 11 | } 12 | \concept{helpers} 13 | -------------------------------------------------------------------------------- /tests/testthat/test_io.R: -------------------------------------------------------------------------------- 1 | # I/O ==== 2 | context("io") 3 | 4 | test_that("workflows are read in", { 5 | workflows <- read_workflows(path = "workflows") 6 | expect_equal( 7 | object = workflows$`workflows/named.yml`$name, 8 | expected = "foo" 9 | ) 10 | expect_equal( 11 | object = workflows$`workflows/unnamed.yaml`$name, 12 | expected = "workflows/unnamed.yaml" 13 | ) 14 | }) 15 | -------------------------------------------------------------------------------- /ghactions.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: No 4 | SaveWorkspace: No 5 | AlwaysSaveHistory: No 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: knitr 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | 18 | BuildType: Package 19 | PackageUseDevtools: Yes 20 | PackageInstallArgs: --no-multiarch --with-keep.source 21 | PackageRoxygenize: rd,collate,namespace,vignette 22 | -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | .deploy_dir 2 | ^\.github$ 3 | ^docs$ 4 | ^_pkgdown\.yml$ 5 | ^.*\.Rproj$ 6 | ^\.Rproj\.user$ 7 | .config 8 | .rstudio 9 | ^Dockerfile$ 10 | logo.* 11 | ^actions$ 12 | .tar.gz 13 | ^pkgdown$ 14 | ^man-roxygen$ 15 | vignettes/ 16 | pkgwf.gif 17 | 18 | # tokens to run ghactions locally with act 19 | .secrets/ 20 | .envrc 21 | Makefile 22 | .dockerignore 23 | 24 | assets/ 25 | pres_files/ 26 | pres.html 27 | pres.Rmd 28 | ^azure-pipelines\.yml$ 29 | ^\.ccache$ 30 | ^clang-.* 31 | ^tic\.R$ 32 | ^gfortran.* 33 | -------------------------------------------------------------------------------- /man/checkout.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/steps.R 3 | \name{checkout} 4 | \alias{checkout} 5 | \title{Create a step to checkout a repository} 6 | \usage{ 7 | checkout() 8 | } 9 | \description{ 10 | Create a step to checkout a repository 11 | } 12 | \seealso{ 13 | Other steps: 14 | \code{\link{deploy_static}}, 15 | \code{\link{install_deps}()}, 16 | \code{\link{pkg_dev}}, 17 | \code{\link{rscript}()} 18 | 19 | Other installation: 20 | \code{\link{install_deps}()} 21 | } 22 | \concept{installation} 23 | \concept{steps} 24 | -------------------------------------------------------------------------------- /man/edit_workflow.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/setup.R 3 | \name{edit_workflow} 4 | \alias{edit_workflow} 5 | \title{Open configuration files} 6 | \usage{ 7 | edit_workflow() 8 | } 9 | \value{ 10 | Path to the file, invisibly. 11 | } 12 | \description{ 13 | Open \code{.github/workflows/main} configuration file for GitHub actions. 14 | See \code{\link[usethis:edit]{usethis::edit()}} for details. 15 | } 16 | \seealso{ 17 | Other setup: 18 | \code{\link{use_ghactions_badge}()}, 19 | \code{\link{use_ghactions}()} 20 | } 21 | \concept{setup} 22 | -------------------------------------------------------------------------------- /tests/testthat/workflows/rsync.yml: -------------------------------------------------------------------------------- 1 | if: github.ref == 'refs/heads/master' 2 | uses: maxheld83/rsync@v0.1.1 3 | with: 4 | args: $GITHUB_WORKSPACE/$DEPLOY_PATH/ pfs400wm@karli.rrze.uni-erlangen.de:/proj/websource/docs/FAU/fakultaet/phil/www.datascience.phil.fau.de/websource/denkzeug 5 | env: 6 | HOST_NAME: karli.rrze.uni-erlangen.de 7 | HOST_IP: 131.188.16.138 8 | HOST_FINGERPRINT: ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFHJVSekYKuF5pMKyHe1jS9mUkXMWoqNQe0TTs2sY1OQj379e6eqVSqGZe+9dKWzL5MRFpIiySRKgvxuHhaPQU4= 9 | SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} 10 | SSH_PUBLIC_KEY: ${{ secrets.SSH_PUBLIC_KEY }} 11 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(checkout) 4 | export(container) 5 | export(edit_workflow) 6 | export(firebase) 7 | export(gh_matrix) 8 | export(ghactions_events) 9 | export(ghactions_vms) 10 | export(ghpages) 11 | export(install_deps) 12 | export(is_github_actions) 13 | export(job) 14 | export(netlify) 15 | export(on) 16 | export(on_pull_request) 17 | export(on_push) 18 | export(on_schedule) 19 | export(read_workflow) 20 | export(read_workflows) 21 | export(rscript) 22 | export(rsync) 23 | export(step) 24 | export(strategy) 25 | export(use_ghactions) 26 | export(use_ghactions_badge) 27 | export(website) 28 | export(workflow) 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # macos system files 2 | *.DS_Store 3 | 4 | .deploy_dir 5 | # History files 6 | .Rhistory 7 | .Rapp.history 8 | 9 | # Session Data files 10 | .RData 11 | # Example code in package build process 12 | *-Ex.R 13 | # Output files from R CMD build 14 | /*.tar.gz 15 | # Output files from R CMD check 16 | /*.Rcheck/ 17 | # RStudio files 18 | .Rproj.user/ 19 | # produced vignettes 20 | vignettes/*.html 21 | vignettes/*.pdf 22 | # OAuth2 token, see https://github.com/hadley/httr/releases/tag/v0.3 23 | .httr-oauth 24 | # knitr and R markdown default cache directories 25 | /*_cache/ 26 | /cache/ 27 | # Temporary files created by R markdown 28 | *.utf8.md 29 | *.knit.md 30 | # Shiny token, see https://shiny.rstudio.com/articles/shinyapps.html 31 | rsconnect/ 32 | .Rproj.user 33 | # docker image artifacts 34 | .rstudio/ 35 | # pkgdown 36 | /docs 37 | # tokens to run ghactions locally with act 38 | /.secrets 39 | 40 | /pres_files 41 | /pres.html 42 | -------------------------------------------------------------------------------- /man/ghactions_events.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/syntax.R 3 | \docType{data} 4 | \name{ghactions_events} 5 | \alias{ghactions_events} 6 | \title{Supported events to trigger GitHub actions} 7 | \format{ 8 | An object of class \code{character} of length 28. 9 | } 10 | \usage{ 11 | ghactions_events 12 | } 13 | \description{ 14 | You can trigger GitHub actions from these events. 15 | List is taken from \href{https://developer.github.com/actions/creating-workflows/workflow-configuration-options/#events-supported-in-workflow-files}{official spec}. 16 | } 17 | \examples{ 18 | ghactions_events 19 | 20 | } 21 | \seealso{ 22 | Other syntax: 23 | \code{\link{container}()}, 24 | \code{\link{gh_matrix}()}, 25 | \code{\link{ghactions_vms}}, 26 | \code{\link{job}()}, 27 | \code{\link{on}()}, 28 | \code{\link{step}()}, 29 | \code{\link{strategy}()}, 30 | \code{\link{workflow}()} 31 | } 32 | \concept{syntax} 33 | \keyword{datasets} 34 | -------------------------------------------------------------------------------- /man/ghactions_vms.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/syntax.R 3 | \docType{data} 4 | \name{ghactions_vms} 5 | \alias{ghactions_vms} 6 | \title{Virtual machines \href{https://help.github.com/en/articles/workflow-syntax-for-github-actions#jobsjob_idruns-on}{available} on GitHub Actions} 7 | \format{ 8 | An object of class \code{character} of length 8. 9 | } 10 | \usage{ 11 | ghactions_vms 12 | } 13 | \description{ 14 | Virtual machines \href{https://help.github.com/en/articles/workflow-syntax-for-github-actions#jobsjob_idruns-on}{available} on GitHub Actions 15 | } 16 | \examples{ 17 | ghactions_vms 18 | 19 | } 20 | \seealso{ 21 | Other syntax: 22 | \code{\link{container}()}, 23 | \code{\link{gh_matrix}()}, 24 | \code{\link{ghactions_events}}, 25 | \code{\link{job}()}, 26 | \code{\link{on}()}, 27 | \code{\link{step}()}, 28 | \code{\link{strategy}()}, 29 | \code{\link{workflow}()} 30 | } 31 | \concept{syntax} 32 | \keyword{datasets} 33 | -------------------------------------------------------------------------------- /man/pkg_dev.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/steps.R 3 | \name{pkg_dev} 4 | \alias{pkg_dev} 5 | \alias{rcmd_check} 6 | \alias{covr} 7 | \title{CI/CD steps for a package at the repository root} 8 | \usage{ 9 | rcmd_check(name = "Check Package") 10 | 11 | covr(name = "Run Code Coverage") 12 | } 13 | \arguments{ 14 | \item{name}{\verb{[character(1)]} 15 | giving additional options for the step. 16 | Multiline strings are not supported. 17 | Defaults to \code{NULL}.} 18 | } 19 | \description{ 20 | CI/CD steps for a package at the repository root 21 | } 22 | \section{Functions}{ 23 | \itemize{ 24 | \item \code{rcmd_check}: \code{\link[rcmdcheck:rcmdcheck]{rcmdcheck::rcmdcheck()}} 25 | 26 | \item \code{covr}: \code{\link[covr:codecov]{covr::codecov()}} 27 | }} 28 | 29 | \seealso{ 30 | Other steps: 31 | \code{\link{checkout}()}, 32 | \code{\link{deploy_static}}, 33 | \code{\link{install_deps}()}, 34 | \code{\link{rscript}()} 35 | } 36 | \concept{pkg_development} 37 | \concept{steps} 38 | -------------------------------------------------------------------------------- /man/ghactions.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/ghactions-package.R 3 | \docType{package} 4 | \name{ghactions} 5 | \alias{ghactions} 6 | \alias{ghactions-package} 7 | \title{ghactions: GitHub actions for R} 8 | \description{ 9 | Provides templating functions with sensible defaults to use GitHub Actions for R projects. 10 | } 11 | \seealso{ 12 | Useful links: 13 | \itemize{ 14 | \item \url{https://github.com/r-lib/ghactions} 15 | \item Report bugs at \url{https://github.com/r-lib/ghactions/issues} 16 | } 17 | 18 | } 19 | \author{ 20 | \strong{Maintainer}: Max Held \email{info@maxheld.de} (\href{https://orcid.org/0000-0002-4703-5388}{ORCID}) 21 | 22 | Authors: 23 | \itemize{ 24 | \item Jim Hester \email{james.f.hester@gmail.com} (\href{https://orcid.org/0000-0002-2739-7082}{ORCID}) 25 | } 26 | 27 | Other contributors: 28 | \itemize{ 29 | \item Verena Held \email{info@verenaheld.in} [contributor] 30 | \item RStudio Inc. [copyright holder, funder] 31 | } 32 | 33 | } 34 | \keyword{internal} 35 | -------------------------------------------------------------------------------- /man/use_ghactions.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/setup.R 3 | \name{use_ghactions} 4 | \alias{use_ghactions} 5 | \title{Workflow automation with GitHub Actions} 6 | \usage{ 7 | use_ghactions(workflow = website()) 8 | } 9 | \arguments{ 10 | \item{workflow}{\verb{[list(list())]} 11 | A named list as created by one of the \code{\link[=workflow]{workflow()}} functions. 12 | Defaults to \code{\link[=website]{website()}}.} 13 | } 14 | \value{ 15 | A logical vector indicating if file was modified. 16 | } 17 | \description{ 18 | Sets up workflow automation, including continuous integration and deployment (CI/CD) for different kinds of R projects on GitHub actions. 19 | This function 20 | \itemize{ 21 | \item transforms a list into the GitHub actions syntax, 22 | \item writes it out to \verb{.github/workflows/} in your repository. 23 | } 24 | } 25 | \examples{ 26 | \dontrun{ 27 | use_ghactions(workflow = website()) 28 | } 29 | } 30 | \seealso{ 31 | Other setup: 32 | \code{\link{edit_workflow}()}, 33 | \code{\link{use_ghactions_badge}()} 34 | } 35 | \concept{setup} 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 RStudio, Maximilian Held 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /man/gh_matrix.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/syntax.R 3 | \name{gh_matrix} 4 | \alias{gh_matrix} 5 | \title{Create nested list for the \href{https://help.github.com/en/articles/workflow-syntax-for-github-actions#jobsjob_idstrategy}{matrix} field in \code{\link[=strategy]{strategy()}}} 6 | \usage{ 7 | gh_matrix(..., exclude = NULL, include = NULL) 8 | } 9 | \arguments{ 10 | \item{...}{\verb{[character()]} 11 | giving values for variable for the matrix build.} 12 | 13 | \item{exclude, include}{\verb{[list(list(character(1)))]} 14 | giving unnamed lists of combinations of variables to ex- or include. 15 | Defaults to \code{NULL}.} 16 | } 17 | \description{ 18 | Create nested list for the \href{https://help.github.com/en/articles/workflow-syntax-for-github-actions#jobsjob_idstrategy}{matrix} field in \code{\link[=strategy]{strategy()}} 19 | } 20 | \seealso{ 21 | Other syntax: 22 | \code{\link{container}()}, 23 | \code{\link{ghactions_events}}, 24 | \code{\link{ghactions_vms}}, 25 | \code{\link{job}()}, 26 | \code{\link{on}()}, 27 | \code{\link{step}()}, 28 | \code{\link{strategy}()}, 29 | \code{\link{workflow}()} 30 | } 31 | \concept{syntax} 32 | -------------------------------------------------------------------------------- /man/use_ghactions_badge.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/setup.R 3 | \name{use_ghactions_badge} 4 | \alias{use_ghactions_badge} 5 | \title{README badges} 6 | \usage{ 7 | use_ghactions_badge(workflow_name = NULL, badge_name = "Actions Status") 8 | } 9 | \arguments{ 10 | \item{workflow_name}{\verb{[character(1)]} 11 | Giving the name of the workflow as specified in the \href{https://help.github.com/en/articles/workflow-syntax-for-github-actions#name}{\verb{name:}} field of your \verb{*.yml}. 12 | If no \verb{name: } is given, this is the file path of the \verb{*.yml} from the repository root. 13 | Defaults to \code{NULL}, in which case the first workflow in the first \verb{*.yml} at \verb{.github/workflows/} is used.} 14 | 15 | \item{badge_name}{Badge name. Used in error message and alt text} 16 | } 17 | \description{ 18 | These helpers produce the markdown text you need in your README to include 19 | badges that report information, such as the CRAN version or test coverage, 20 | and link out to relevant external resources. To add badges automatically 21 | ensure your badge block starts with a line containing only 22 | \verb{} and ends with a line containing only 23 | \verb{}. 24 | } 25 | \seealso{ 26 | Other setup: 27 | \code{\link{edit_workflow}()}, 28 | \code{\link{use_ghactions}()} 29 | } 30 | \concept{setup} 31 | -------------------------------------------------------------------------------- /man/strategy.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/syntax.R 3 | \name{strategy} 4 | \alias{strategy} 5 | \title{Create nested list for the \href{https://help.github.com/en/articles/workflow-syntax-for-github-actions#jobsjob_idstrategy}{strategy} field in \code{\link[=job]{job()}}} 6 | \usage{ 7 | strategy(matrix = NULL, `fail-fast` = NULL, `max-parallel` = NULL) 8 | } 9 | \arguments{ 10 | \item{matrix}{\verb{[list(list(c()))]} 11 | giving the values for each variable for the matrix build. 12 | See \code{\link[=gh_matrix]{gh_matrix()}} for additional options. 13 | Defaults to \code{NULL}.} 14 | 15 | \item{fail-fast}{\verb{[logical()]} 16 | giving whether GitHub should cancel all in-progress jobs if any matrix job fails. 17 | Defaults to \code{NULL}.} 18 | 19 | \item{max-parallel}{\verb{[integer(1)]} 20 | giving the maximum number of jobs to run simultaneously when using a matrix job strategy.} 21 | } 22 | \description{ 23 | Create nested list for the \href{https://help.github.com/en/articles/workflow-syntax-for-github-actions#jobsjob_idstrategy}{strategy} field in \code{\link[=job]{job()}} 24 | } 25 | \seealso{ 26 | Other syntax: 27 | \code{\link{container}()}, 28 | \code{\link{gh_matrix}()}, 29 | \code{\link{ghactions_events}}, 30 | \code{\link{ghactions_vms}}, 31 | \code{\link{job}()}, 32 | \code{\link{on}()}, 33 | \code{\link{step}()}, 34 | \code{\link{workflow}()} 35 | } 36 | \concept{syntax} 37 | -------------------------------------------------------------------------------- /man/container.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/syntax.R 3 | \name{container} 4 | \alias{container} 5 | \title{Create nested list for the \href{https://help.github.com/en/articles/workflow-syntax-for-github-actions#jobsjob_idcontainer}{container} field in \code{\link[=job]{job()}}} 6 | \usage{ 7 | container(image, env = NULL, ports = NULL, volumes = NULL, options = NULL) 8 | } 9 | \arguments{ 10 | \item{image}{\verb{[character(1)]} 11 | giving the published docker image to use as the container to run the action.} 12 | 13 | \item{env}{\verb{[list()]} 14 | giving environment variables for the container as a \emph{named} list. 15 | Defaults to \code{NULL}.} 16 | 17 | \item{ports, volumes}{\verb{[list()]} 18 | giving ports to expose, and volumes for the container to use as an \emph{unnamed} list. 19 | Defaults to \code{NULL}.} 20 | 21 | \item{options}{\verb{[character()]} 22 | giving additional options. 23 | Defaults to \code{NULL}.} 24 | } 25 | \description{ 26 | Create nested list for the \href{https://help.github.com/en/articles/workflow-syntax-for-github-actions#jobsjob_idcontainer}{container} field in \code{\link[=job]{job()}} 27 | } 28 | \seealso{ 29 | Other syntax: 30 | \code{\link{gh_matrix}()}, 31 | \code{\link{ghactions_events}}, 32 | \code{\link{ghactions_vms}}, 33 | \code{\link{job}()}, 34 | \code{\link{on}()}, 35 | \code{\link{step}()}, 36 | \code{\link{strategy}()}, 37 | \code{\link{workflow}()} 38 | } 39 | \concept{syntax} 40 | -------------------------------------------------------------------------------- /.github/workflows/main-old.yml: -------------------------------------------------------------------------------- 1 | name: CICD 2 | 3 | "on": 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-18.04 10 | container: 11 | image: rstudio/r-base:3.6-bionic 12 | steps: 13 | - name: Checkout Repository 14 | uses: actions/checkout@v1 15 | - name: Install System Dependencies 16 | run: | 17 | sudo apt-get update && apt-get install -y --no-install-recommends \ 18 | libxml2-dev \ 19 | libssl-dev \ 20 | libcurl4-openssl-dev 21 | - name: Install Package Dependencies 22 | run: |- 23 | Rscript -e "install.packages('remotes', repos = 'https://demo.rstudiopm.com/all/__linux__/bionic/latest')" 24 | Rscript -e "remotes::install_deps(dependencies = TRUE, repos = 'https://demo.rstudiopm.com/all/__linux__/bionic/latest')" 25 | - name: Check Package 26 | run: Rscript -e "rcmdcheck::rcmdcheck(error_on = 'error', check_dir = 'check')" 27 | - name: Run Code Coverage 28 | run: Rscript -e "covr::codecov(quiet = FALSE, commit = '$GITHUB_SHA', branch = '$GITHUB_REF', token = '$${{ secrets.CODECOV_TOKEN }}')" 29 | - name: Document Package 30 | run: | 31 | Rscript -e "pkgdown::build_site(override = list(devel = FALSE, external = FALSE))" 32 | - name: Deploy to GitHub Pages 33 | if: github.ref == 'refs/heads/master' 34 | uses: maxheld83/ghpages@v0.2.0 35 | env: 36 | BUILD_DIR: docs 37 | GH_PAT: ${{ secrets.GH_PAT }} 38 | -------------------------------------------------------------------------------- /tests/testthat/test_steps.R: -------------------------------------------------------------------------------- 1 | context("scripts") 2 | 3 | test_that("rscript works", { 4 | expect_known_output( 5 | object = write_workflow( 6 | rscript(expr = c("1+1", "2+2")) 7 | ), 8 | file = "workflows/rscript_string.yml" 9 | ) 10 | }) 11 | 12 | 13 | context("deployment") 14 | 15 | test_that("example rsync step is correct", { 16 | expect_known_output( 17 | object = write_workflow( 18 | # convenient example known to work 19 | rsync_fau( 20 | dest = "/proj/websource/docs/FAU/fakultaet/phil/www.datascience.phil.fau.de/websource/denkzeug" 21 | ) 22 | ), 23 | file = "workflows/rsync.yml" 24 | ) 25 | }) 26 | 27 | test_that("example github pages is correct", { 28 | expect_known_output( 29 | object = write_workflow( 30 | ghpages() 31 | ), 32 | file = "workflows/ghpages.yml" 33 | ) 34 | }) 35 | 36 | 37 | context("installation") 38 | 39 | test_that("install_deps step is correct", { 40 | expect_known_output( 41 | object = write_workflow( 42 | install_deps() 43 | ), 44 | file = "workflows/install_deps.yml" 45 | ) 46 | }) 47 | 48 | 49 | context("pkg dev") 50 | 51 | test_that("rcmd check step is correct", { 52 | expect_known_output( 53 | object = write_workflow( 54 | x = rcmd_check() 55 | ), 56 | file = "workflows/rcmdcheck.yml" 57 | ) 58 | }) 59 | 60 | test_that("covr step is correct", { 61 | expect_known_output( 62 | object = write_workflow( 63 | x = covr() 64 | ), 65 | file = "workflows/covr.yml" 66 | ) 67 | }) 68 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: ghactions 2 | Title: GitHub actions for R 3 | Version: 0.5.0 4 | Authors@R: 5 | c( 6 | person( 7 | given = "Max", 8 | family = "Held", 9 | role = c("aut", "cre"), 10 | email = "info@maxheld.de", 11 | comment = c(ORCID = "0000-0002-4703-5388") 12 | ), 13 | person( 14 | given = "Jim", 15 | family = "Hester", 16 | role = c("aut"), 17 | email = "james.f.hester@gmail.com", 18 | comment = c(ORCID = "0000-0002-2739-7082") 19 | ), 20 | person( 21 | given = "Verena", 22 | family = "Held", 23 | email = "info@verenaheld.in", 24 | role = "ctb" 25 | ), 26 | person( 27 | given = "RStudio Inc.", 28 | role = c("cph", "fnd") 29 | ) 30 | ) 31 | Description: Provides templating functions with sensible defaults to use GitHub Actions for R projects. 32 | License: MIT + file LICENSE 33 | URL: https://github.com/r-lib/ghactions 34 | BugReports: https://github.com/r-lib/ghactions/issues 35 | Depends: 36 | R (>= 3.5.0) 37 | Imports: 38 | checkmate (>= 1.8.1), 39 | desc (>= 1.2.0), 40 | fs (>= 1.2.6), 41 | gh (>= 1.0.1), 42 | glue (>= 1.3.0), 43 | purrr (>= 0.3.0), 44 | readr (>= 1.3.1), 45 | rlang (>= 0.3.1), 46 | usethis (>= 1.5.0), 47 | yaml (>= 2.2.0) 48 | Suggests: 49 | devtools (>= 2.0.2), 50 | pkgdown (>= 1.4.1), 51 | testthat (>= 2.1.0), 52 | knitr (>= 1.25), 53 | covr (>= 3.3.1), 54 | roxygen2 (>= 6.1.1), 55 | xml2 (>= 1.2.2) 56 | VignetteBuilder: 57 | knitr 58 | Encoding: UTF-8 59 | Language: en-US 60 | LazyData: true 61 | Roxygen: list(markdown = TRUE) 62 | RoxygenNote: 7.1.0.9000 63 | -------------------------------------------------------------------------------- /R/workflows.R: -------------------------------------------------------------------------------- 1 | #' Render and deploy an [rmarkdown](https://rmarkdown.rstudio.com) project 2 | #' 3 | #' This workflow renders some Rmarkdown via its (custom) site generator and deploys the result. 4 | #' Suitable for: 5 | #' - [RMarkdown websites](https://rmarkdown.rstudio.com/lesson-13.html) 6 | #' - [Bookdown websites](https://bookdown.org) 7 | #' - [Blogdown websites](https://bookdown.org/yihui/blogdown/) (**experimental**) 8 | #' 9 | #' @details 10 | #' [rmarkdown::render_site()] returns the directory to which outputs have been rendered. 11 | #' This workflow saves that directory in the environment variable `DEPLOY_PATH`, which is the default expected by [ghpages()] and other deployment methods. 12 | #' 13 | #' @param deploy `[list(1)]` 14 | #' giving a list of deploy functions. 15 | #' 16 | #' @inheritParams workflow 17 | #' 18 | #' @family workflows 19 | #' 20 | #' @export 21 | website <- function(name = "Render and Deploy RMarkdown Website", 22 | deploy = list(ghpages()), 23 | on = c("push", "pull_request")) { 24 | workflow( 25 | name = name, 26 | jobs = job( 27 | id = "build", 28 | `runs-on` = "ubuntu-18.04", 29 | container = "rocker/verse:latest", 30 | steps = c( 31 | list( 32 | checkout(), 33 | install_deps(), 34 | step( 35 | name = "Render Site", 36 | run = c( 37 | "Rscript -e \"rmarkdown::render_site(encoding = \'UTF-8\')\"", 38 | "echo \"::set-env name=DEPLOY_PATH::$(Rscript -e \"cat(rmarkdown::site_config()[[\'output_dir\']])\")\"" 39 | ) 40 | ) 41 | ), 42 | deploy 43 | ) 44 | ) 45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /man/workflow.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/syntax.R 3 | \name{workflow} 4 | \alias{workflow} 5 | \title{Create nested list for a \href{https://help.github.com/en/articles/workflow-syntax-for-github-actions}{workflow block}} 6 | \usage{ 7 | workflow(name = NULL, on = "push", jobs = NULL) 8 | } 9 | \arguments{ 10 | \item{name}{\verb{[character(1)]} 11 | giving the \href{https://help.github.com/en/articles/workflow-syntax-for-github-actions#name}{name} of the workflow. 12 | Defaults to \code{NULL}, for no name, in which case GitHub will use the file name.} 13 | 14 | \item{on}{\verb{[character()]} 15 | giving the \href{https://help.github.com/en/articles/events-that-trigger-workflows}{GitHub Event} on which to trigger the workflow. 16 | Must be a subset of \link{ghactions_events}. 17 | Defaults to \code{"push"}, in which case the workflow is triggered on every push event. 18 | Can also be a named list as returned by \code{\link[=on]{on()}} for additional filters.} 19 | 20 | \item{jobs}{\verb{[list()]} 21 | giving a \emph{named} list of jobs, with each list element as returned by \code{\link[=job]{job()}}.} 22 | } 23 | \description{ 24 | Create nested list for a \href{https://help.github.com/en/articles/workflow-syntax-for-github-actions}{workflow block} 25 | } 26 | \examples{ 27 | workflow( 28 | name = "Render", 29 | on = "push", 30 | jobs = NULL 31 | ) 32 | 33 | } 34 | \seealso{ 35 | Other syntax: 36 | \code{\link{container}()}, 37 | \code{\link{gh_matrix}()}, 38 | \code{\link{ghactions_events}}, 39 | \code{\link{ghactions_vms}}, 40 | \code{\link{job}()}, 41 | \code{\link{on}()}, 42 | \code{\link{step}()}, 43 | \code{\link{strategy}()} 44 | } 45 | \concept{syntax} 46 | -------------------------------------------------------------------------------- /man/io.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/io.R 3 | \name{io} 4 | \alias{io} 5 | \alias{write_workflow} 6 | \alias{r2yaml} 7 | \alias{read_workflows} 8 | \alias{read_workflow} 9 | \title{Reading and writing GitHub Actions workflow files} 10 | \usage{ 11 | write_workflow(x, file = stdout(), ...) 12 | 13 | r2yaml(x) 14 | 15 | read_workflows(path = ".github/workflows") 16 | 17 | read_workflow(file, ...) 18 | } 19 | \arguments{ 20 | \item{x}{\verb{[list()]} 21 | as created by the workflow functions.} 22 | 23 | \item{file}{either a character string naming a file or a \link{connection} 24 | open for writing} 25 | 26 | \item{...}{arguments to \code{\link[yaml]{as.yaml}}} 27 | 28 | \item{path}{\verb{[character()]} giving the directory from the repository root where to find GitHub Actions workflows. 29 | Defaults to \code{".github/workflows"}.} 30 | } 31 | \value{ 32 | \verb{[list()]} of lists from yaml. 33 | } 34 | \description{ 35 | Reading and writing GitHub Actions workflow files 36 | } 37 | \details{ 38 | It is not necessary to escape characters with special meaning in yaml; the underlying \code{\link[yaml:write_yaml]{yaml::write_yaml()}} does this automatically. 39 | 40 | If a workflow is \emph{not} \verb{name:}d, the file name will be used as a \verb{name: }, as per the \href{https://help.github.com/en/articles/workflow-syntax-for-github-actions#name}{GitHub Actions documentation}. 41 | } 42 | \section{Functions}{ 43 | \itemize{ 44 | \item \code{write_workflow}: Write \emph{one} GitHub Actions workflow to file. 45 | 46 | \item \code{r2yaml}: Convert R list to YAML string. 47 | 48 | \item \code{read_workflows}: Read in \emph{one or more} GitHub Actions workflows from file(s). 49 | 50 | \item \code{read_workflow}: Read in \emph{one} GitHub Actions workflow from a file. 51 | }} 52 | 53 | \concept{io} 54 | -------------------------------------------------------------------------------- /man/website.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/workflows.R 3 | \name{website} 4 | \alias{website} 5 | \title{Render and deploy an \href{https://rmarkdown.rstudio.com}{rmarkdown} project} 6 | \usage{ 7 | website( 8 | name = "Render and Deploy RMarkdown Website", 9 | deploy = list(ghpages()), 10 | on = c("push", "pull_request") 11 | ) 12 | } 13 | \arguments{ 14 | \item{name}{\verb{[character(1)]} 15 | giving the \href{https://help.github.com/en/articles/workflow-syntax-for-github-actions#name}{name} of the workflow. 16 | Defaults to \code{NULL}, for no name, in which case GitHub will use the file name.} 17 | 18 | \item{deploy}{\verb{[list(1)]} 19 | giving a list of deploy functions.} 20 | 21 | \item{on}{\verb{[character()]} 22 | giving the \href{https://help.github.com/en/articles/events-that-trigger-workflows}{GitHub Event} on which to trigger the workflow. 23 | Must be a subset of \link{ghactions_events}. 24 | Defaults to \code{"push"}, in which case the workflow is triggered on every push event. 25 | Can also be a named list as returned by \code{\link[=on]{on()}} for additional filters.} 26 | } 27 | \description{ 28 | This workflow renders some Rmarkdown via its (custom) site generator and deploys the result. 29 | Suitable for: 30 | \itemize{ 31 | \item \href{https://rmarkdown.rstudio.com/lesson-13.html}{RMarkdown websites} 32 | \item \href{https://bookdown.org}{Bookdown websites} 33 | \item \href{https://bookdown.org/yihui/blogdown/}{Blogdown websites} (\strong{experimental}) 34 | } 35 | } 36 | \details{ 37 | \code{\link[rmarkdown:render_site]{rmarkdown::render_site()}} returns the directory to which outputs have been rendered. 38 | This workflow saves that directory in the environment variable \code{DEPLOY_PATH}, which is the default expected by \code{\link[=ghpages]{ghpages()}} and other deployment methods. 39 | } 40 | \concept{workflows} 41 | -------------------------------------------------------------------------------- /man/step.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/syntax.R 3 | \name{step} 4 | \alias{step} 5 | \title{Create nested list for \emph{one} \href{https://help.github.com/en/articles/workflow-syntax-for-github-actions#jobs}{job}} 6 | \usage{ 7 | step( 8 | name = NULL, 9 | id = NULL, 10 | `if` = NULL, 11 | uses = NULL, 12 | run = NULL, 13 | shell = NULL, 14 | with = NULL, 15 | env = NULL, 16 | `working-directory` = NULL, 17 | `continue-on-error` = NULL, 18 | `timeout-minutes` = NULL 19 | ) 20 | } 21 | \arguments{ 22 | \item{id, if, name, uses, shell}{\verb{[character(1)]} 23 | giving additional options for the step. 24 | Multiline strings are not supported. 25 | Defaults to \code{NULL}.} 26 | 27 | \item{run}{\verb{[character()]} 28 | giving commands to run. 29 | Will be turned into a multiline string. 30 | Defaults to \code{NULL}.} 31 | 32 | \item{with, env}{\verb{[list()]} 33 | giving a named list of additional parameters. 34 | Defaults to \code{NULL}.} 35 | 36 | \item{working-directory}{\verb{[character(1)]} 37 | giving the default working directory. 38 | Defaults to \code{NULL}.} 39 | 40 | \item{continue-on-error}{\verb{[logical(1)]} 41 | giving whether to allow a job to pass when this step fails. 42 | Defaults to \code{NULL}.} 43 | 44 | \item{timeout-minutes}{\verb{[integer(1)]} 45 | giving the maximum number of minutes to run the step before killing the process. 46 | Defaults to \code{NULL}.} 47 | } 48 | \description{ 49 | Create nested list for \emph{one} \href{https://help.github.com/en/articles/workflow-syntax-for-github-actions#jobs}{job} 50 | } 51 | \seealso{ 52 | Other syntax: 53 | \code{\link{container}()}, 54 | \code{\link{gh_matrix}()}, 55 | \code{\link{ghactions_events}}, 56 | \code{\link{ghactions_vms}}, 57 | \code{\link{job}()}, 58 | \code{\link{on}()}, 59 | \code{\link{strategy}()}, 60 | \code{\link{workflow}()} 61 | } 62 | \concept{syntax} 63 | -------------------------------------------------------------------------------- /_pkgdown.yml: -------------------------------------------------------------------------------- 1 | url: https://www.maxheld.de/ghactions/ 2 | authors: 3 | Max Held: 4 | href: http://www.maxheld.de 5 | Jim Hester: 6 | href: https://jimhester.com/ 7 | Verena Held: 8 | href: http://www.verenaheld.in 9 | development: 10 | mode: auto 11 | destination: "." 12 | reference: 13 | - title: "Setup" 14 | desc: > 15 | Functions to help you get started with GitHub Actions. 16 | contents: 17 | - has_concept("setup") 18 | - edit_workflow 19 | - title: "I/O" 20 | desc: > 21 | Reading and writing GitHub Actions from/to `*.yml`s. 22 | contents: 23 | - has_concept("io") 24 | - title: "Workflows" 25 | desc: > 26 | Functions with GitHub Actions workflows for common R projects with sensible defaults. 27 | contents: 28 | - has_concept("workflows") 29 | - title: "Steps" 30 | desc: > 31 | Functions to write steps. 32 | This includes wrappers for external GitHub actions. 33 | contents: 34 | - has_concept("steps") 35 | - title: "Syntax" 36 | desc: > 37 | Functions to generate *blocks* of GitHub actions syntax. 38 | For internal purposes, not recommended for package users. 39 | contents: 40 | - has_concept("syntax") 41 | - title: "Helpers" 42 | desc: > 43 | Helper functions. 44 | contents: 45 | - has_concept("helpers") 46 | articles: 47 | - title: "Overview" 48 | contents: 49 | - ghactions 50 | navbar: 51 | title: "ghactions" 52 | left: 53 | - icon: fa-home 54 | href: "index.html" 55 | - text: "Get Started" 56 | href: "articles/ghactions.html" 57 | - text: "Reference" 58 | href: "reference/index.html" 59 | right: 60 | - icon: fa-github 61 | href: https://github.com/r-lib/ghactions 62 | theme: cosmo 63 | template: 64 | params: 65 | bootswatch: cosmo 66 | docsearch: 67 | api_key: 8f10ced26cbc746213d4922f059ceb49 68 | index_name: ghactions 69 | -------------------------------------------------------------------------------- /man/install_deps.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/steps.R 3 | \name{install_deps} 4 | \alias{install_deps} 5 | \title{Create a step to install R package dependencies} 6 | \usage{ 7 | install_deps(name = "Install Package Dependencies", ...) 8 | } 9 | \arguments{ 10 | \item{name}{\verb{[character(1)]} 11 | giving additional options for the step. 12 | Multiline strings are not supported. 13 | Defaults to \code{NULL}.} 14 | 15 | \item{...}{ 16 | Arguments passed on to \code{\link[=step]{step}} 17 | \describe{ 18 | \item{\code{id}}{\verb{[character(1)]} 19 | giving additional options for the step. 20 | Multiline strings are not supported. 21 | Defaults to \code{NULL}.} 22 | \item{\code{if}}{\verb{[character(1)]} 23 | giving additional options for the step. 24 | Multiline strings are not supported. 25 | Defaults to \code{NULL}.} 26 | \item{\code{shell}}{\verb{[character(1)]} 27 | giving additional options for the step. 28 | Multiline strings are not supported. 29 | Defaults to \code{NULL}.} 30 | \item{\code{with}}{\verb{[list()]} 31 | giving a named list of additional parameters. 32 | Defaults to \code{NULL}.} 33 | \item{\code{env}}{\verb{[list()]} 34 | giving a named list of additional parameters. 35 | Defaults to \code{NULL}.} 36 | \item{\code{working-directory}}{\verb{[character(1)]} 37 | giving the default working directory. 38 | Defaults to \code{NULL}.} 39 | \item{\code{continue-on-error}}{\verb{[logical(1)]} 40 | giving whether to allow a job to pass when this step fails. 41 | Defaults to \code{NULL}.} 42 | \item{\code{timeout-minutes}}{\verb{[integer(1)]} 43 | giving the maximum number of minutes to run the step before killing the process. 44 | Defaults to \code{NULL}.} 45 | }} 46 | } 47 | \description{ 48 | Installs R package dependencies from a \code{DESCRIPTION} at the repository root. 49 | } 50 | \seealso{ 51 | Other steps: 52 | \code{\link{checkout}()}, 53 | \code{\link{deploy_static}}, 54 | \code{\link{pkg_dev}}, 55 | \code{\link{rscript}()} 56 | 57 | Other installation: 58 | \code{\link{checkout}()} 59 | } 60 | \concept{installation} 61 | \concept{steps} 62 | -------------------------------------------------------------------------------- /man/on.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/syntax.R 3 | \name{on} 4 | \alias{on} 5 | \alias{on_push} 6 | \alias{on_pull_request} 7 | \alias{on_schedule} 8 | \title{Create nested list for an \verb{on:} field} 9 | \usage{ 10 | on(event, ...) 11 | 12 | on_push(tags = NULL, branches = NULL, paths = NULL) 13 | 14 | on_pull_request(tags = NULL, branches = NULL, paths = NULL) 15 | 16 | on_schedule(cron = NULL) 17 | } 18 | \arguments{ 19 | \item{event}{\verb{[character(1)]} 20 | giving the event on which to filter. 21 | Must be \emph{one} of \code{c("push", "pull_request", "schedule")}.} 22 | 23 | \item{...}{\verb{[character()]} 24 | giving the filters on which to run. 25 | Must correspond to the filters allowed by \code{event}.} 26 | 27 | \item{tags, branches, paths}{\verb{[character()]} 28 | giving the \href{https://help.github.com/en/articles/workflow-syntax-for-github-actions#onpushpull_requesttagsbranches}{tags, branches} or \href{https://help.github.com/en/articles/workflow-syntax-for-github-actions#onpushpull_requestpaths}{modified paths} on which to run the workflow. 29 | Defaults to \code{NULL} for no additional filters.} 30 | 31 | \item{cron}{\verb{[character(1)]} 32 | giving UTC times using \href{https://pubs.opengroup.org/onlinepubs/9699919799/utilities/crontab.html#tag_20_25_07}{POSIX cron syntax}.} 33 | } 34 | \description{ 35 | Create nested list for an \verb{on:} field 36 | } 37 | \details{ 38 | See the \href{https://help.github.com/en/articles/workflow-syntax-for-github-actions}{GitHub Actions workflow syntax} for details. 39 | } 40 | \section{Functions}{ 41 | \itemize{ 42 | \item \code{on_push}: filter on push event 43 | 44 | \item \code{on_pull_request}: filter on pull request 45 | 46 | \item \code{on_schedule}: filter on schedule 47 | }} 48 | 49 | \examples{ 50 | on( 51 | event = "push", 52 | branches = c("master", "releases/*") 53 | ) 54 | } 55 | \seealso{ 56 | Other syntax: 57 | \code{\link{container}()}, 58 | \code{\link{gh_matrix}()}, 59 | \code{\link{ghactions_events}}, 60 | \code{\link{ghactions_vms}}, 61 | \code{\link{job}()}, 62 | \code{\link{step}()}, 63 | \code{\link{strategy}()}, 64 | \code{\link{workflow}()} 65 | } 66 | \concept{syntax} 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GitHub Actions for R 2 | 3 | 4 | [![Actions Status](https://github.com/maxheld83/ghactions/workflows/.github/workflows/main.yml/badge.svg)](https://github.com/maxheld83/ghactions/actions) 5 | [![tic](https://github.com/maxheld83/ghactions/workflows/tic/badge.svg?branch=master)](https://github.com/maxheld83/ghactions/actions) 6 | [![codecov](https://codecov.io/gh/maxheld83/ghactions/branch/master/graph/badge.svg)](https://codecov.io/gh/maxheld83/ghactions) 7 | [![lifecycle](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://www.tidyverse.org/lifecycle/#experimental) 8 | [![CRAN status](https://www.r-pkg.org/badges/version/ghactions)](https://cran.r-project.org/package=ghactions) 9 | [![License: MIT](https://img.shields.io/github/license/r-lib/ghactions.svg?style=flat)](https://opensource.org/licenses/MIT) 10 | 11 | 12 | [GitHub Actions](https://github.com/features/actions) are a new workflow automation feature of the popular code repository host GitHub. 13 | 14 | This package helps R users get started quickly with GitHub Actions: 15 | 16 | 1. It provides **workflow** templates for common R projects (packages, RMarkdown, ...) with sensible defaults. 17 | 2. It wraps and curates relevant existing **external actions**, such as those to deploy to GitHub Pages or Netlify. 18 | 3. It exposes the GitHub Actions workflow **syntax** and lets you write GitHub Actions `*.yml`s from R. 19 | (Which isn't saying that *should* be doing that.) 20 | 21 | ## Installation 22 | 23 | To install, run: 24 | 25 | ```r 26 | remotes::install_github("maxheld83/ghactions") 27 | ``` 28 | 29 | Because you're likely only to ever use it *once*, **you need not take on ghactions as a dependency in your projects.** 30 | 31 | ## Quick Start 32 | 33 | GitHub actions just requires a special file in a special directory at the root of your repository to work: `.github/workflows/main.yml`. 34 | 35 | To quickly set up such a file for frequently used project kinds, run: 36 | 37 | ```r 38 | ghactions::use_ghactions(workflow = ghactions::website()) 39 | ``` 40 | 41 | See the documentation for implied defaults and alternatives. 42 | 43 | Then push to GitHub and go to the actions tab in your repository. 44 | -------------------------------------------------------------------------------- /man/job.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/syntax.R 3 | \name{job} 4 | \alias{job} 5 | \title{Create nested list for \emph{one} \href{https://help.github.com/en/articles/workflow-syntax-for-github-actions#jobs}{job}} 6 | \usage{ 7 | job( 8 | id, 9 | name = NULL, 10 | needs = NULL, 11 | `runs-on` = "ubuntu-18.04", 12 | steps = NULL, 13 | timeout_minutes = NULL, 14 | strategy = NULL, 15 | container = NULL, 16 | services = NULL 17 | ) 18 | } 19 | \arguments{ 20 | \item{id, name}{\verb{[character(1)]} 21 | giving additional options for the job. 22 | Defaults to \code{NULL}.} 23 | 24 | \item{needs}{\verb{[character()]} 25 | giving the jobs that must complete successfully before this job is run. 26 | Defaults to \code{NULL} for no dependencies.} 27 | 28 | \item{runs-on}{\verb{[character(1)]} 29 | giving the type of virtual host machine to run the job on. 30 | Defaults to \code{"ubuntu-18.04"}. 31 | Must be one of \link{ghactions_vms}.} 32 | 33 | \item{steps}{\verb{[list()]} 34 | giving an \emph{unnamed} list of steps, with each element as returned by \code{\link[=step]{step()}}. 35 | Defaults to \code{NULL}.} 36 | 37 | \item{timeout_minutes}{\verb{[integer(1)]} 38 | giving the maximum number of minutes to let a workflow run before GitHub automatically cancels it. 39 | Defaults to \code{NULL}.} 40 | 41 | \item{strategy}{\verb{[list()]} 42 | giving a named list as returned by \code{\link[=strategy]{strategy()}}. 43 | Defaults to \code{NULL}.} 44 | 45 | \item{container}{\verb{[character(1)]}/\verb{[list()]} 46 | giving a published container image. 47 | For advanced options, use \code{\link[=container]{container()}}. 48 | Defaults to \code{NULL}.} 49 | 50 | \item{services}{\verb{[list()]} 51 | giving additional containers to host services for a job in a workflow in a \emph{named} list. 52 | Use \code{\link[=container]{container()}} to construct the list elements. 53 | Defaults to \code{NULL}.} 54 | } 55 | \description{ 56 | Create nested list for \emph{one} \href{https://help.github.com/en/articles/workflow-syntax-for-github-actions#jobs}{job} 57 | } 58 | \seealso{ 59 | Other syntax: 60 | \code{\link{container}()}, 61 | \code{\link{gh_matrix}()}, 62 | \code{\link{ghactions_events}}, 63 | \code{\link{ghactions_vms}}, 64 | \code{\link{on}()}, 65 | \code{\link{step}()}, 66 | \code{\link{strategy}()}, 67 | \code{\link{workflow}()} 68 | } 69 | \concept{syntax} 70 | -------------------------------------------------------------------------------- /R/io.R: -------------------------------------------------------------------------------- 1 | #' Reading and writing GitHub Actions workflow files 2 | #' 3 | #' @param x `[list()]` 4 | #' as created by the workflow functions. 5 | #' 6 | #' @family io 7 | #' 8 | #' @return `[list()]` of lists from yaml. 9 | #' 10 | #' @details 11 | #' It is not necessary to escape characters with special meaning in yaml; the underlying [yaml::write_yaml()] does this automatically. 12 | #' 13 | #' @name io 14 | NULL 15 | 16 | 17 | #' @describeIn io Write *one* GitHub Actions workflow to file. 18 | #' 19 | #' @inheritParams yaml::write_yaml 20 | write_workflow <- function(x, file = stdout(), ...) { 21 | yaml::write_yaml( 22 | x = x, 23 | file = file, 24 | # cosmetic change, but github docs are intended 25 | indent.mapping.sequence = TRUE, 26 | ... 27 | ) 28 | } 29 | 30 | #' @describeIn io Convert R list to YAML string. 31 | #' 32 | #' @inheritParams write_workflow 33 | r2yaml <- function(x) { 34 | # writing out and recapturing is a bit weird, but this way yaml is written exactly as in yaml pkg 35 | utils::capture.output(write_workflow(x)) 36 | } 37 | 38 | 39 | #' @describeIn io Read in *one or more* GitHub Actions workflows from file(s). 40 | #' 41 | #' @param path `[character()]` giving the directory from the repository root where to find GitHub Actions workflows. 42 | #' Defaults to `".github/workflows"`. 43 | #' 44 | #' @export 45 | read_workflows <- function(path = ".github/workflows") { 46 | usethis::local_project() # make sure we are in the project dir 47 | checkmate::assert_directory_exists(x = path) 48 | # files are relative from root, but oddly, that is what github actions uses as default names 49 | # so we'll also use the full rel path here at least until https://github.com/r-lib/ghactions/issues/346 50 | files <- fs::dir_ls( 51 | path = path, 52 | recurse = FALSE, 53 | regexp = ".*\\.(yml|yaml)$" # can be both! 54 | ) 55 | if (length(files) == 0) { 56 | stop( 57 | "There are no yaml files at ", 58 | path, 59 | ". Perhaps GitHub Actions has not been set up?" 60 | ) 61 | } 62 | 63 | purrr::map(.x = files, .f = read_workflow) 64 | } 65 | 66 | #' @describeIn io Read in *one* GitHub Actions workflow from a file. 67 | #' 68 | #' @inheritParams yaml::read_yaml 69 | #' 70 | #' @details 71 | #' If a workflow is *not* `name:`d, the file name will be used as a `name: `, as per the [GitHub Actions documentation](https://help.github.com/en/articles/workflow-syntax-for-github-actions#name). 72 | #' 73 | #' @export 74 | read_workflow <- function(file, ...) { 75 | x <- yaml::read_yaml(file = file, ...) 76 | if (is.null(x$name)) { 77 | x$name <- file 78 | } 79 | x 80 | } 81 | -------------------------------------------------------------------------------- /man/rscript.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/steps.R 3 | \name{rscript} 4 | \alias{rscript} 5 | \title{Create a step to run \link[utils:Rscript]{utils::Rscript}} 6 | \usage{ 7 | rscript(options = NULL, expr = NULL, file = NULL, args = NULL, ...) 8 | } 9 | \arguments{ 10 | \item{options}{a list of options, all beginning with \samp{--}. These 11 | can be any of the options of the standard \R front-end, and also those 12 | described in the details.} 13 | 14 | \item{expr}{\R expression(s), properly quoted.} 15 | 16 | \item{file}{the name of a file containing \R commands. \samp{-} 17 | indicates \file{stdin}.} 18 | 19 | \item{args}{arguments to be passed to the script in \code{file}.} 20 | 21 | \item{...}{ 22 | Arguments passed on to \code{\link[=step]{step}} 23 | \describe{ 24 | \item{\code{id}}{\verb{[character(1)]} 25 | giving additional options for the step. 26 | Multiline strings are not supported. 27 | Defaults to \code{NULL}.} 28 | \item{\code{if}}{\verb{[character(1)]} 29 | giving additional options for the step. 30 | Multiline strings are not supported. 31 | Defaults to \code{NULL}.} 32 | \item{\code{name}}{\verb{[character(1)]} 33 | giving additional options for the step. 34 | Multiline strings are not supported. 35 | Defaults to \code{NULL}.} 36 | \item{\code{uses}}{\verb{[character(1)]} 37 | giving additional options for the step. 38 | Multiline strings are not supported. 39 | Defaults to \code{NULL}.} 40 | \item{\code{shell}}{\verb{[character(1)]} 41 | giving additional options for the step. 42 | Multiline strings are not supported. 43 | Defaults to \code{NULL}.} 44 | \item{\code{with}}{\verb{[list()]} 45 | giving a named list of additional parameters. 46 | Defaults to \code{NULL}.} 47 | \item{\code{env}}{\verb{[list()]} 48 | giving a named list of additional parameters. 49 | Defaults to \code{NULL}.} 50 | \item{\code{working-directory}}{\verb{[character(1)]} 51 | giving the default working directory. 52 | Defaults to \code{NULL}.} 53 | \item{\code{continue-on-error}}{\verb{[logical(1)]} 54 | giving whether to allow a job to pass when this step fails. 55 | Defaults to \code{NULL}.} 56 | \item{\code{timeout-minutes}}{\verb{[integer(1)]} 57 | giving the maximum number of minutes to run the step before killing the process. 58 | Defaults to \code{NULL}.} 59 | }} 60 | } 61 | \description{ 62 | Create a step to run \link[utils:Rscript]{utils::Rscript} 63 | } 64 | \seealso{ 65 | Other steps: 66 | \code{\link{checkout}()}, 67 | \code{\link{deploy_static}}, 68 | \code{\link{install_deps}()}, 69 | \code{\link{pkg_dev}} 70 | } 71 | \concept{script} 72 | \concept{steps} 73 | -------------------------------------------------------------------------------- /R/setup.R: -------------------------------------------------------------------------------- 1 | #' Workflow automation with GitHub Actions 2 | #' 3 | #' Sets up workflow automation, including continuous integration and deployment (CI/CD) for different kinds of R projects on GitHub actions. 4 | #' This function 5 | #' - transforms a list into the GitHub actions syntax, 6 | #' - writes it out to `.github/workflows/` in your repository. 7 | #' 8 | #' @param workflow `[list(list())]` 9 | #' A named list as created by one of the [workflow()] functions. 10 | #' Defaults to [website()]. 11 | #' 12 | #' @inherit usethis::use_template return 13 | #' 14 | #' @family setup 15 | #' 16 | #' @examples 17 | #' \dontrun{ 18 | #' use_ghactions(workflow = website()) 19 | #' } 20 | #' @export 21 | use_ghactions <- function(workflow = website()) { 22 | # input validation ==== 23 | checkmate::assert_list( 24 | x = workflow, 25 | any.missing = FALSE, 26 | names = "named", 27 | null.ok = FALSE 28 | ) 29 | 30 | # check conditions ==== 31 | # 32 | # TODO would be better to use usethis::check_uses_github, but currently not exported. see https://github.com/maxheld83/ghactions/issues/46 33 | tryCatch( 34 | expr = gh::gh_tree_remote(), 35 | error = function(cnd) { 36 | usethis::ui_stop( 37 | c("This project does not have a GitHub remote configured as {usethis::ui_value('origin')}.", 38 | "Do you need to run {usethis::ui_code('usethis::use_github()')}?" 39 | ) 40 | ) 41 | } 42 | ) 43 | 44 | # body ==== 45 | 46 | # write out to disc 47 | # this is modelled on use_template, but because we already have the full string in above res, we don't need to go through whisker/mustache again 48 | usethis::use_directory(path = ".github/workflows", ignore = TRUE) 49 | 50 | new <- usethis::write_over( 51 | path = ".github/workflows/main.yml", 52 | lines = r2yaml(workflow), 53 | quiet = TRUE 54 | ) 55 | 56 | if (new) { 57 | usethis::ui_done(x = "GitHub actions is set up and ready to go.") 58 | usethis::ui_todo(x = "Commit and push the changes.") 59 | # TODO maybe automatically open webpage via browse_ghactions here 60 | usethis::ui_todo( 61 | x = "Visit the actions tab of your repository on github.com to check the results." 62 | ) 63 | } 64 | 65 | # return true/false for changed files as in original use_template 66 | invisible(new) 67 | } 68 | 69 | 70 | #' README badges 71 | #' 72 | #' @inherit usethis::use_badge description 73 | #' @inheritParams usethis::use_badge 74 | #' 75 | #' @param workflow_name `[character(1)]` 76 | #' Giving the name of the workflow as specified in the [`name:`](https://help.github.com/en/articles/workflow-syntax-for-github-actions#name) field of your `*.yml`. 77 | #' If no `name: ` is given, this is the file path of the `*.yml` from the repository root. 78 | #' Defaults to `NULL`, in which case the first workflow in the first `*.yml` at `.github/workflows/` is used. 79 | #' 80 | #' @family setup 81 | #' 82 | #' @export 83 | use_ghactions_badge <- function(workflow_name = NULL, 84 | badge_name = "Actions Status") { 85 | # input validation 86 | workflows <- read_workflows() 87 | checkmate::assert_choice( 88 | x = workflow_name, 89 | choices = purrr::map_chr(.x = workflows, "name"), 90 | null.ok = TRUE 91 | ) 92 | 93 | # compute inputs 94 | if (is.null(workflow_name)) { 95 | workflow_name <- workflows[[1]]$name 96 | } 97 | workflow_name <- utils::URLencode(workflow_name) 98 | reposlug <- glue::glue( 99 | '{gh::gh_tree_remote()$username}/{gh::gh_tree_remote()$repo}' 100 | ) 101 | 102 | # write out 103 | usethis::use_badge( 104 | href = glue::glue('https://github.com/{reposlug}/actions'), 105 | src = glue::glue('https://github.com/{reposlug}/workflows/{workflow_name}/badge.svg'), 106 | badge_name = badge_name 107 | ) 108 | } 109 | 110 | 111 | #' Open configuration files 112 | #' 113 | #' @description Open `.github/workflows/main` configuration file for GitHub actions. 114 | #' See [usethis::edit()] for details. 115 | #' 116 | #' @family setup 117 | #' 118 | #' @inherit usethis::edit return 119 | #' 120 | #' @export 121 | edit_workflow <- function() { 122 | path <- usethis::proj_path(".github", "workflows", "main") 123 | usethis::ui_todo("Commit and push for the changes to take effect.") 124 | invisible(usethis::edit_file(path)) 125 | } 126 | -------------------------------------------------------------------------------- /vignettes/ghactions.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Using GitHub Actions in R" 3 | author: "Maximilian Held" 4 | date: "`r Sys.Date()`" 5 | output: rmarkdown::html_document 6 | vignette: > 7 | %\VignetteIndexEntry{ghactions} 8 | %\VignetteEngine{knitr::rmarkdown} 9 | %\VignetteEncoding{UTF-8} 10 | --- 11 | 12 | ```{r setup, include = FALSE} 13 | knitr::opts_chunk$set( 14 | collapse = TRUE, 15 | comment = "#>", 16 | eval = FALSE 17 | ) 18 | ``` 19 | 20 | Continuous integration and delivery (CI/CD) have evolved as software development best practices, and they also strengthen reproducibility in (data) science. 21 | By programmatically triggering CI/CD, output versions (say, a PDF report or compiled binaries) are bound to a particular version (a `git commit`) of the source. 22 | 23 | For widespread adoption of this best practice in the R community, CI/CD needs to be simple, fast, and easy to reason about if things go wrong. 24 | 25 | [GitHub actions](https://github.com/features/actions) is a new workflow automation feature of the popular code repository host GitHub. 26 | 27 | 28 | ## Workflow Automation as Code 29 | 30 | One of the great innovations in the CI/CD space in the past couple of years has been to express such development workflows *as code*. 31 | As such, it can be shared, versioned and tested. 32 | 33 | This is most powerful, when common parts of such code can be factored out. 34 | 35 | GitHub Actions offers only one way in which you can do that: 36 | You can use container or JavaScript actions to abstract away the complexity of the `runs: ` field for a *particular step* in your workflow. 37 | That can be quite helpful, especially when you are interacting with external services or complicated architectures. 38 | Container actions, in addition, allow you to provide arbitrary computing environments (docker images) for a step, while a JavaScript action will use the underlying virtual machine. 39 | 40 | What GitHub Actions does (currently) *not* let you do is factor out common patterns *across* steps, or even jobs. 41 | This is frequently the case for projects from the same language (here: R) or communities (here: tidyverse), who might all be relying on very similar, if not identical workflows. 42 | 43 | Microsoft's Azure Pipelines has an elegant way to share such yaml-templates, even while passing down parameters, but GitHub Actions does not offer this (yet). 44 | 45 | Instead, you get *this* package, which will provide you with hopefully sensible workflows. 46 | 47 | However, because these aren't linked dynamically from your repos, there will be no easy way to update or maintain them. 48 | 49 | 50 | ## Limitations 51 | 52 | GitHub Actions does not currently offer caching; if your package has many dependencies, your build times might be quite long. 53 | 54 | 55 | ## What This Package *Doesn't* Do 56 | 57 | The ghactions package is quite limited, and deliberately so: 58 | GitHub actions already provides most of the things we might want, and in a cross-platform way: 59 | 60 | - a succinct and human-readable text file representation (YAML) used to model code automation workflows, 61 | - a convenient model and marketplace to share actions, 62 | - and even some templates for frequently used projects. 63 | 64 | This package does not intend to solve these problems *again*, nor to completely wrap GitHub Actions in R. 65 | It's really just a glorified collection of templates to get you started quickly. 66 | 67 | If you need something more advanced, chances are you're going to want to edit your workflows yourself. 68 | It's quite easy to use, and we'll try to gather and share best practices in this repository. 69 | 70 | 71 | ## Thanks 72 | 73 | This package is also heavily modeled on, and indebted to the [usethis](https://usethis.r-lib.org) package by [Jenny Bryan](https://jennybryan.org) and [Hadley Wickham](http://hadley.nz). 74 | 75 | 76 | ## Related Work 77 | 78 | There are plenty of other proven ways to run CI/CD for R. 79 | Many rely on [R support on TravisCI](https://docs.travis-ci.com/user/languages/r/), maintained by [Jeroen Ooms](https://github.com/jeroen) and [Jim Hester](https://www.jimhester.com). 80 | The [travis](https://ropenscilabs.github.io/travis/) and [tic](https://ropenscilabs.github.io/tic/) packages make it easier to work with them. 81 | You can use [AppVeyor](http://appveyor.com)'s Windows-based system via the [r-appveyor](https://github.com/krlmlr/r-appveyor) package. 82 | 83 | For serious, cross-platform testing of packages, there's the [r-hub](http://r-hub.io) project. 84 | -------------------------------------------------------------------------------- /tests/testthat/test_syntax.R: -------------------------------------------------------------------------------- 1 | # workflows ==== 2 | # these examples are based on https://help.github.com/en/articles/workflow-syntax-for-github-actions, but do look slightly different 3 | context("workflows") 4 | 5 | test_that("can be written out", { 6 | # 'on' needs to be escaped in all of the below test cases for some reason; doesn't hurt ghactions 7 | expect_known_output( 8 | object = write_workflow(workflow(name = "foo")), 9 | file = "workflows/named.yml" 10 | ) 11 | expect_known_output( 12 | object = write_workflow(workflow()), 13 | file = "workflows/unnamed.yaml" 14 | ) 15 | expect_known_output( 16 | # sequences must be separate lines; doesn't hurt ghactions 17 | object = write_workflow(workflow(on = c("push", "pull_request"))), 18 | file = "workflows/on_multiple.yml" 19 | ) 20 | expect_known_output( 21 | object = write_workflow( 22 | workflow( 23 | on = on( 24 | event = "push", 25 | branches = c("master", "releases/*"), 26 | tags = c("v1", "v1.0") 27 | ) 28 | ) 29 | ), 30 | file = "workflows/on_push_filter.yml" 31 | ) 32 | expect_known_output( 33 | object = write_workflow( 34 | workflow( 35 | on = on( 36 | event = "push", 37 | paths = c("*", "!*.js") 38 | ) 39 | ) 40 | ), 41 | file = "workflows/on_push_path.yml" 42 | ) 43 | expect_known_output( 44 | object = write_workflow( 45 | workflow( 46 | on = on( 47 | event = "schedule", 48 | cron = "*/15 * * * *" 49 | ) 50 | ) 51 | ), 52 | file = "workflows/on_schedule.yml" 53 | ) 54 | }) 55 | 56 | 57 | # jobs ==== 58 | context("jobs") 59 | 60 | test_that("can be written out", { 61 | expect_known_output( 62 | object = write_workflow( 63 | job( 64 | id = "some_job", 65 | name = "bar", 66 | needs = c("zap", "zong") 67 | ) 68 | ), 69 | file = "workflows/job.yml" 70 | ) 71 | }) 72 | 73 | test_that("support matrix strategies", { 74 | expect_known_output( 75 | object = write_workflow( 76 | strategy( 77 | matrix = list( 78 | node = c(6L, 8L, 10L) 79 | ), 80 | `fail-fast` = TRUE, 81 | `max-parallel` = 3 82 | ) 83 | ), 84 | file = "workflows/matrix_strategy.yml" 85 | ) 86 | }) 87 | 88 | test_that("support matrix inclusions/exclusions", { 89 | expect_known_output( 90 | object = write_workflow( 91 | gh_matrix( 92 | os = c("macOS-10.14", "windows-2016", "ubuntu-18.04"), 93 | node = c(4L, 6L, 8L, 10L), 94 | include = list( 95 | list( 96 | os = "windows-2016", 97 | node = 4L, 98 | npm = 2L 99 | ), 100 | list( 101 | os = "macOS-10.14", 102 | node = 4L, 103 | npm = 2L 104 | ) 105 | ) 106 | ) 107 | ), 108 | file = "workflows/matrix_in_exclude.yml" 109 | ) 110 | }) 111 | 112 | test_that("support simple containers", { 113 | expect_known_output( 114 | object = write_workflow( 115 | job( 116 | id = "ship", 117 | container = "node:10.16-jessie" 118 | ) 119 | ), 120 | file = "workflows/container_simple.yml" 121 | ) 122 | }) 123 | 124 | test_that("support advanced containers", { 125 | expect_known_output( 126 | object = write_workflow( 127 | job( 128 | id = "my_job", 129 | container = container( 130 | image = "node:10.16-jessie", 131 | env = list(NODE_ENV = "development"), 132 | ports = list(80L), 133 | volumes = list("my_docker_volume:/volume_mount"), 134 | options = "--cpus 1" 135 | ) 136 | ) 137 | ), 138 | file = "workflows/container_adv.yml" 139 | ) 140 | }) 141 | 142 | test_that("support additional services", { 143 | expect_known_output( 144 | object = write_workflow( 145 | job( 146 | id = "my_job", 147 | services = list( 148 | nginx = container( 149 | image = "nginx", 150 | ports = list("8080:80"), 151 | env = list(NGINX_PORT = (80L)), 152 | ), 153 | redis = container( 154 | image = "redis", 155 | ports = list("6379/tcp") 156 | ) 157 | ) 158 | ) 159 | ), 160 | file = "workflows/services.yml" 161 | ) 162 | }) 163 | 164 | 165 | # steps ==== 166 | context("steps") 167 | 168 | test_that("can be written out", { 169 | expect_known_output( 170 | object = write_workflow( 171 | list( 172 | step( 173 | name = "My first step", 174 | uses = "./.github/actions/my-action", 175 | ), 176 | step( 177 | name = "My backup step", 178 | `if` = "failure()", 179 | uses = "actions/heroku@master" 180 | ) 181 | ) 182 | ), 183 | file = "workflows/steps_first.yml" 184 | ) 185 | expect_known_output( 186 | object = write_workflow( 187 | list( 188 | step( 189 | name = "My first step", 190 | uses = "actions/hello_world@master", 191 | with = list( 192 | first_name = "Mona", 193 | middle_name = "The", 194 | last_name = "Octocat" 195 | ) 196 | ) 197 | ) 198 | ), 199 | file = "workflows/steps_with.yml" 200 | ) 201 | }) 202 | 203 | test_that("support multiline runs", { 204 | expect_known_output( 205 | object = write_workflow( 206 | list(step(run = c("foo", "bar"))) 207 | ), 208 | file = "workflows/run_multiline.yml" 209 | ) 210 | }) 211 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | ## tic GitHub Actions template: linux-macos-windows-deploy 2 | ## revision date: 2020-11-14 3 | on: 4 | workflow_dispatch: 5 | push: 6 | pull_request: 7 | # for now, CRON jobs only run on the default branch of the repo (i.e. usually on master) 8 | # schedule: 9 | # # * is a special character in YAML so you have to quote this string 10 | # - cron: "0 4 * * *" 11 | 12 | name: tic 13 | 14 | jobs: 15 | all: 16 | runs-on: ${{ matrix.config.os }} 17 | 18 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 19 | 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | config: 24 | # use a different tic template type if you do not want to build on all listed platforms 25 | - { os: windows-latest, r: "release" } 26 | - { os: macOS-latest, r: "release", pkgdown: "true", latex: "true" } 27 | - { os: ubuntu-latest, r: "devel" } 28 | - { os: ubuntu-latest, r: "release" } 29 | 30 | env: 31 | # otherwise remotes::fun() errors cause the build to fail. Example: Unavailability of binaries 32 | R_REMOTES_NO_ERRORS_FROM_WARNINGS: true 33 | CRAN: ${{ matrix.config.cran }} 34 | # make sure to run `tic::use_ghactions_deploy()` to set up deployment 35 | TIC_DEPLOY_KEY: ${{ secrets.TIC_DEPLOY_KEY }} 36 | # prevent rgl issues because no X11 display is available 37 | RGL_USE_NULL: true 38 | # if you use bookdown or blogdown, replace "PKGDOWN" by the respective 39 | # capitalized term. This also might need to be done in tic.R 40 | BUILD_PKGDOWN: ${{ matrix.config.pkgdown }} 41 | # macOS >= 10.15.4 linking 42 | SDKROOT: /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk 43 | # use GITHUB_TOKEN from GitHub to workaround rate limits in {remotes} 44 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 45 | 46 | steps: 47 | - uses: actions/checkout@v2.3.4 48 | 49 | - uses: r-lib/actions/setup-r@master 50 | with: 51 | r-version: ${{ matrix.config.r }} 52 | Ncpus: 4 53 | 54 | # LaTeX. Installation time: 55 | # Linux: ~ 1 min 56 | # macOS: ~ 1 min 30s 57 | # Windows: never finishes 58 | - uses: r-lib/actions/setup-tinytex@master 59 | if: matrix.config.latex == 'true' 60 | 61 | - uses: r-lib/actions/setup-pandoc@master 62 | 63 | # set date/week for use in cache creation 64 | # https://github.community/t5/GitHub-Actions/How-to-set-and-access-a-Workflow-variable/m-p/42970 65 | # - cache R packages daily 66 | - name: "[Cache] Prepare daily timestamp for cache" 67 | if: runner.os != 'Windows' 68 | id: date 69 | run: echo "::set-output name=date::$(date '+%d-%m')" 70 | 71 | - name: "[Cache] Cache R packages" 72 | if: runner.os != 'Windows' 73 | uses: pat-s/always-upload-cache@v2.1.3 74 | with: 75 | path: ${{ env.R_LIBS_USER }} 76 | key: ${{ runner.os }}-r-${{ matrix.config.r }}-${{steps.date.outputs.date}} 77 | restore-keys: ${{ runner.os }}-r-${{ matrix.config.r }}-${{steps.date.outputs.date}} 78 | 79 | # for some strange Windows reason this step and the next one need to be decoupled 80 | - name: "[Stage] Prepare" 81 | run: | 82 | Rscript -e "if (!requireNamespace('remotes')) install.packages('remotes', type = 'source')" 83 | Rscript -e "if (getRversion() < '3.2' && !requireNamespace('curl')) install.packages('curl', type = 'source')" 84 | 85 | - name: "[Stage] [Linux] Install curl" 86 | if: runner.os == 'Linux' 87 | run: sudo apt install libcurl4-openssl-dev 88 | 89 | - name: "[Stage] [macOS] Install system libs for pkgdown" 90 | if: runner.os == 'macOS' && matrix.config.pkgdown != '' 91 | run: brew install harfbuzz fribidi 92 | 93 | - name: "[Stage] [Linux] Install system libs for pkgdown" 94 | if: runner.os == 'Linux' && matrix.config.pkgdown != '' 95 | run: sudo apt install libharfbuzz-dev libfribidi-dev 96 | 97 | - name: "[Stage] Install" 98 | if: matrix.config.os != 'macOS-latest' || matrix.config.r != 'devel' 99 | run: Rscript -e "remotes::install_github('ropensci/tic')" -e "print(tic::dsl_load())" -e "tic::prepare_all_stages()" -e "tic::before_install()" -e "tic::install()" 100 | 101 | # macOS devel needs its own stage because we need to work with an option to suppress the usage of binaries 102 | - name: "[Stage] Prepare & Install (macOS-devel)" 103 | if: matrix.config.os == 'macOS-latest' && matrix.config.r == 'devel' 104 | run: | 105 | echo -e 'options(Ncpus = 4, pkgType = "source", repos = structure(c(CRAN = "https://cloud.r-project.org/")))' > $HOME/.Rprofile 106 | Rscript -e "remotes::install_github('ropensci/tic')" -e "print(tic::dsl_load())" -e "tic::prepare_all_stages()" -e "tic::before_install()" -e "tic::install()" 107 | 108 | - name: "[Stage] Script" 109 | run: Rscript -e 'tic::script()' 110 | 111 | - name: "[Stage] After Success" 112 | if: matrix.config.os == 'macOS-latest' && matrix.config.r == 'release' 113 | run: Rscript -e "tic::after_success()" 114 | 115 | - name: "[Stage] Upload R CMD check artifacts" 116 | if: failure() 117 | uses: actions/upload-artifact@v2.2.1 118 | with: 119 | name: ${{ runner.os }}-r${{ matrix.config.r }}-results 120 | path: check 121 | - name: "[Stage] Before Deploy" 122 | run: | 123 | Rscript -e "tic::before_deploy()" 124 | 125 | - name: "[Stage] Deploy" 126 | run: Rscript -e "tic::deploy()" 127 | 128 | - name: "[Stage] After Deploy" 129 | run: Rscript -e "tic::after_deploy()" 130 | -------------------------------------------------------------------------------- /man/deploy_static.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/steps.R 3 | \name{deploy_static} 4 | \alias{deploy_static} 5 | \alias{ghpages} 6 | \alias{rsync} 7 | \alias{netlify} 8 | \alias{firebase} 9 | \title{Create an (action) step to deploy static assets} 10 | \usage{ 11 | ghpages( 12 | src = "$DEPLOY_PATH", 13 | name = "Deploy to GitHub Pages", 14 | `if` = "github.ref == 'refs/heads/master'", 15 | ... 16 | ) 17 | 18 | rsync( 19 | src = "$DEPLOY_PATH", 20 | name = "Deploy via RSync", 21 | `if` = "github.ref == 'refs/heads/master'", 22 | HOST_NAME, 23 | HOST_IP, 24 | HOST_FINGERPRINT, 25 | user, 26 | dest, 27 | env = NULL, 28 | with = NULL, 29 | ... 30 | ) 31 | 32 | netlify( 33 | src = "$DEPLOY_PATH", 34 | name = "Deploy to Netlify", 35 | `if` = "github.ref == 'refs/heads/master'", 36 | prod = TRUE, 37 | with = NULL, 38 | env = NULL, 39 | site, 40 | ... 41 | ) 42 | 43 | firebase( 44 | name = "Deploy to Firebase", 45 | `if` = "github.ref == 'refs/heads/master'", 46 | PROJECT_ID = NULL, 47 | with = NULL, 48 | env = NULL, 49 | ... 50 | ) 51 | } 52 | \arguments{ 53 | \item{src}{\verb{[character(1)]} 54 | giving the path relative from your \verb{/github/workspace} to the directory to be published \strong{without trailing slash}. 55 | Defaults to \code{"$DEPLOY_PATH"}, an environment variable containing the path set by \code{\link[=website]{website()}}.} 56 | 57 | \item{name}{\verb{[character(1)]} 58 | giving addtional options for the step. 59 | Multiline strings are not supported. 60 | Defaults to a name for the deploy step.} 61 | 62 | \item{if}{\verb{[character(1)]} 63 | giving additional options for the step. 64 | Multiline strings are not supported. 65 | Defaults to \code{"github.ref == 'refs/heads/master'"} to only deploy from branch \code{master}.} 66 | 67 | \item{...}{ 68 | Arguments passed on to \code{\link[=step]{step}} 69 | \describe{ 70 | \item{\code{id}}{\verb{[character(1)]} 71 | giving additional options for the step. 72 | Multiline strings are not supported. 73 | Defaults to \code{NULL}.} 74 | \item{\code{working-directory}}{\verb{[character(1)]} 75 | giving the default working directory. 76 | Defaults to \code{NULL}.} 77 | \item{\code{continue-on-error}}{\verb{[logical(1)]} 78 | giving whether to allow a job to pass when this step fails. 79 | Defaults to \code{NULL}.} 80 | \item{\code{timeout-minutes}}{\verb{[integer(1)]} 81 | giving the maximum number of minutes to run the step before killing the process. 82 | Defaults to \code{NULL}.} 83 | }} 84 | 85 | \item{HOST_NAME}{\verb{[character(1)]} 86 | giving the name of the server you wish to deploy to, such as \code{foo.example.com}.} 87 | 88 | \item{HOST_IP}{\verb{[character(1)]} 89 | giving the IP of the server you wish to deploy to, such as \verb{111.111.11.111}.} 90 | 91 | \item{HOST_FINGERPRINT}{\verb{[character(1)]} 92 | giving the fingerprint of the server you wish to deploy to, can have different formats.} 93 | 94 | \item{user}{\verb{[character(1)]} 95 | giving the user at the target \code{HOST_NAME}.} 96 | 97 | \item{dest}{\verb{[character(1)]} 98 | giving the directory from the root of the \code{HOST_NAME} target to write to.} 99 | 100 | \item{env}{\verb{[list()]} 101 | giving a named list of additional parameters. 102 | Defaults to \code{NULL}.} 103 | 104 | \item{with}{\verb{[list()]} 105 | giving a named list of additional parameters. 106 | Defaults to \code{NULL}.} 107 | 108 | \item{prod}{\verb{[logical(1)]} 109 | giving whether the deploy should be to production.} 110 | 111 | \item{site}{\verb{[character(1)]} 112 | giving a site ID to deploy to.} 113 | 114 | \item{PROJECT_ID}{\verb{[character(1)]} 115 | giving a specific project to use for all commands, not required if you specify a project in your `.firebaserc`` file.} 116 | } 117 | \description{ 118 | Create an (action) step to deploy static assets 119 | } 120 | \section{Functions}{ 121 | \itemize{ 122 | \item \code{ghpages}: Wraps the external \href{https://github.com/maxheld83/ghpages/}{ghpages action} to deploy to \href{https://pages.github.com}{GitHub Pages}. 123 | 124 | \item \code{rsync}: Wraps the external \href{https://github.com/maxheld83/rsync/}{rsync action} to deploy via \href{https://rsync.samba.org}{Rsync} over SSH. 125 | 126 | \item \code{netlify}: Wraps the external \href{https://github.com/netlify/actions}{netlify cli action} to deploy to \href{https://www.netlify.com}{Netlify}. 127 | 128 | \item \code{firebase}: Wraps the external \href{https://github.com/w9jds/firebase-action}{Google Firebase CLI action} to deploy to \href{http://firebase.google.com}{Google Firebase}. 129 | }} 130 | 131 | \section{GitHub Pages}{ 132 | 133 | \strong{Remember to provide a GitHub personal access token secret named \code{GH_PAT} to the GitHub UI.} 134 | \enumerate{ 135 | \item Set up a new PAT. 136 | You can use \code{\link[usethis:browse_github_token]{usethis::browse_github_pat()}} to get to the right page. 137 | Remember that this PAT is \emph{not} for your local machine, but for GitHub actions. 138 | \item Copy the PAT to your clipboard. 139 | \item Go to the settings of your repository, and paste the PAT as a secret. 140 | The secret must be called \code{GH_PAT}. 141 | } 142 | } 143 | 144 | \section{RSync}{ 145 | 146 | \strong{Remember to provide \code{SSH_PRIVATE_KEY} and \code{SSH_PUBLIC_KEY} as secrets to the GitHub UI.}. 147 | } 148 | 149 | \section{Netlify}{ 150 | 151 | \strong{Remember to provide \code{NETLIFY_AUTH_TOKEN} and \code{NETLIFY_SITE_ID} (optional) as secrets to the GitHub UI.} 152 | } 153 | 154 | \section{Google Firebase}{ 155 | 156 | \strong{Remember to provide \code{FIREBASE_TOKEN} as a secret to the GitHub UI.} 157 | 158 | Configuration details other than \code{PROJECT_ID} are read from the \code{firebase.json} at the root of your repository. 159 | 160 | Because firebase gets the deploy directory from a \code{firebase.json} file, it cannot use \verb{$DEPLOY_DIR}. 161 | Manually edit your \code{firebase.json} to provide the deploy path. 162 | } 163 | 164 | \seealso{ 165 | Other steps: 166 | \code{\link{checkout}()}, 167 | \code{\link{install_deps}()}, 168 | \code{\link{pkg_dev}}, 169 | \code{\link{rscript}()} 170 | } 171 | \concept{actions} 172 | \concept{static deployment} 173 | \concept{steps} 174 | -------------------------------------------------------------------------------- /R/steps.R: -------------------------------------------------------------------------------- 1 | # script ==== 2 | 3 | #' Create a step to run [utils::Rscript] 4 | #' 5 | #' @inheritParams step 6 | #' 7 | #' @inheritParams utils::Rscript 8 | #' 9 | #' @inheritDotParams step -run 10 | #' 11 | #' @family steps 12 | #' @family script 13 | #' 14 | #' @export 15 | rscript <- function(options = NULL, 16 | expr = NULL, 17 | file = NULL, 18 | args = NULL, 19 | ...) { 20 | # input validation ==== 21 | checkmate::assert_character( 22 | x = options, 23 | pattern = "^[--]", # options must always start with -- as per RScript docs 24 | any.missing = FALSE, 25 | null.ok = TRUE 26 | ) 27 | if (is.null(file)) { 28 | checkmate::assert_character(x = expr, any.missing = FALSE, null.ok = TRUE) 29 | } else { 30 | checkmate::assert_null(x = expr) # there can only be expr OR file 31 | checkmate::assert_file_exists( 32 | x = file, 33 | extension = c("r", "R") 34 | ) 35 | } 36 | checkmate::assert_character( 37 | x = args, 38 | any.missing = FALSE, 39 | null.ok = TRUE 40 | ) 41 | 42 | # create run 43 | if (is.null(expr)) { 44 | run <- glue::glue_collapse(x = c("Rscript", options, file, args), sep = " ") 45 | } else { 46 | run <- purrr::map_chr(.x = expr, .f = function(x) { 47 | glue::glue_collapse( 48 | x = c( 49 | "Rscript", 50 | options, 51 | glue::glue("-e \"{x}\"") 52 | ), 53 | sep = " ") 54 | }) 55 | } 56 | 57 | # create step ==== 58 | step( 59 | run = run, 60 | ... 61 | ) 62 | } 63 | 64 | 65 | # static deployment ==== 66 | 67 | #' Create an (action) step to deploy static assets 68 | #' 69 | #' @param src `[character(1)]` 70 | #' giving the path relative from your `/github/workspace` to the directory to be published **without trailing slash**. 71 | #' Defaults to `"$DEPLOY_PATH"`, an environment variable containing the path set by [website()]. 72 | #' 73 | #' @param name `[character(1)]` 74 | #' giving addtional options for the step. 75 | #' Multiline strings are not supported. 76 | #' Defaults to a name for the deploy step. 77 | #' 78 | #' @param if `[character(1)]` 79 | #' giving additional options for the step. 80 | #' Multiline strings are not supported. 81 | #' Defaults to `"github.ref == 'refs/heads/master'"` to only deploy from branch `master`. 82 | #' 83 | #' @inheritParams step 84 | #' 85 | #' @inheritDotParams step -run -uses -name -env -with -shell 86 | #' 87 | #' @family steps 88 | #' @family actions 89 | #' @family static deployment 90 | #' 91 | #' @name deploy_static 92 | NULL 93 | 94 | #' @describeIn deploy_static Wraps the external [ghpages action](https://github.com/maxheld83/ghpages/) to deploy to [GitHub Pages](https://pages.github.com). 95 | #' 96 | #' @section GitHub Pages: 97 | #' **Remember to provide a GitHub personal access token secret named `GH_PAT` to the GitHub UI.** 98 | #' 1. Set up a new PAT. 99 | #' You can use [usethis::browse_github_pat()] to get to the right page. 100 | #' Remember that this PAT is *not* for your local machine, but for GitHub actions. 101 | #' 2. Copy the PAT to your clipboard. 102 | #' 3. Go to the settings of your repository, and paste the PAT as a secret. 103 | #' The secret must be called `GH_PAT`. 104 | #' 105 | #' @export 106 | ghpages <- function(src = "$DEPLOY_PATH", 107 | name = "Deploy to GitHub Pages", 108 | `if` = "github.ref == 'refs/heads/master'", 109 | ...) { 110 | checkmate::assert_string(x = src, na.ok = FALSE, null.ok = FALSE) 111 | 112 | step( 113 | name = name, 114 | `if` = `if`, 115 | uses = "maxheld83/ghpages@v0.2.0", 116 | env = list( 117 | BUILD_DIR = src, 118 | GH_PAT = "${{ secrets.GH_PAT }}" 119 | ), 120 | ... 121 | ) 122 | } 123 | 124 | #' @describeIn deploy_static Wraps the external [rsync action](https://github.com/maxheld83/rsync/) to deploy via [Rsync](https://rsync.samba.org) over SSH. 125 | #' 126 | #' @param HOST_NAME `[character(1)]` 127 | #' giving the name of the server you wish to deploy to, such as `foo.example.com`. 128 | #' 129 | #' @param HOST_IP `[character(1)]` 130 | #' giving the IP of the server you wish to deploy to, such as `111.111.11.111`. 131 | #' 132 | #' @param HOST_FINGERPRINT `[character(1)]` 133 | #' giving the fingerprint of the server you wish to deploy to, can have different formats. 134 | #' 135 | #' @param user `[character(1)]` 136 | #' giving the user at the target `HOST_NAME`. 137 | #' 138 | #' @param dest `[character(1)]` 139 | #' giving the directory from the root of the `HOST_NAME` target to write to. 140 | #' 141 | #' @section RSync: 142 | #' **Remember to provide `SSH_PRIVATE_KEY` and `SSH_PUBLIC_KEY` as secrets to the GitHub UI.**. 143 | #' 144 | #' @export 145 | rsync <- function(src = "$DEPLOY_PATH", 146 | name = "Deploy via RSync", 147 | `if` = "github.ref == 'refs/heads/master'", 148 | HOST_NAME, 149 | HOST_IP, 150 | HOST_FINGERPRINT, 151 | user, 152 | dest, 153 | env = NULL, 154 | with = NULL, 155 | ...) { 156 | # input validation 157 | purrr::map( 158 | .x = list(HOST_NAME, HOST_IP, HOST_FINGERPRINT, src, user, dest), 159 | .f = checkmate::assert_string, 160 | na.ok = FALSE, 161 | null.ok = FALSE 162 | ) 163 | 164 | args <- glue::glue( 165 | "$GITHUB_WORKSPACE/{src}/", # source 166 | "{user}@{HOST_NAME}:{dest}", # target and destination 167 | .sep = " " 168 | ) 169 | 170 | step( 171 | uses = "maxheld83/rsync@v0.1.1", 172 | `if` = `if`, 173 | env = c( 174 | env, 175 | list( 176 | HOST_NAME = HOST_NAME, 177 | HOST_IP = HOST_IP, 178 | HOST_FINGERPRINT = HOST_FINGERPRINT, 179 | SSH_PRIVATE_KEY = "${{ secrets.SSH_PRIVATE_KEY }}", 180 | SSH_PUBLIC_KEY = "${{ secrets.SSH_PUBLIC_KEY }}" 181 | ) 182 | ), 183 | with = c( 184 | with, 185 | list(args = args) 186 | ), 187 | ... 188 | ) 189 | } 190 | 191 | 192 | rsync_fau <- function(src = "$DEPLOY_PATH", 193 | dest = fs::path("/proj/websource/docs/FAU/fakultaet/phil/www.datascience.phil.fau.de/websource", gh::gh_tree_remote()$repo), 194 | user = "pfs400wm", 195 | ...) { 196 | rsync( 197 | HOST_NAME = "karli.rrze.uni-erlangen.de", 198 | HOST_IP = "131.188.16.138", 199 | HOST_FINGERPRINT = "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFHJVSekYKuF5pMKyHe1jS9mUkXMWoqNQe0TTs2sY1OQj379e6eqVSqGZe+9dKWzL5MRFpIiySRKgvxuHhaPQU4=", 200 | user = user, 201 | src = src, 202 | dest = dest, 203 | name = "Deploy Website", 204 | ... 205 | ) 206 | } 207 | 208 | 209 | #' @describeIn deploy_static Wraps the external [netlify cli action](https://github.com/netlify/actions) to deploy to [Netlify](https://www.netlify.com). 210 | #' 211 | #' @section Netlify: 212 | #' **Remember to provide `NETLIFY_AUTH_TOKEN` and `NETLIFY_SITE_ID` (optional) as secrets to the GitHub UI.** 213 | #' 214 | #' @param prod `[logical(1)]` 215 | #' giving whether the deploy should be to production. 216 | #' 217 | #' @param site `[character(1)]` 218 | #' giving a site ID to deploy to. 219 | #' 220 | #' @export 221 | netlify <- function(src = "$DEPLOY_PATH", 222 | name = "Deploy to Netlify", 223 | `if` = "github.ref == 'refs/heads/master'", 224 | prod = TRUE, 225 | with = NULL, 226 | env = NULL, 227 | site, 228 | ...) { 229 | checkmate::assert_string(x = src, na.ok = FALSE, null.ok = FALSE) 230 | checkmate::assert_string(x = site, na.ok = FALSE, null.ok = FALSE) 231 | checkmate::assert_flag(x = prod, na.ok = FALSE, null.ok = FALSE) 232 | 233 | # prepare args 234 | args <- c( 235 | glue::glue('--dir {src}'), 236 | if (prod) "prod" else NULL, 237 | glue::glue('--site {site}') 238 | ) 239 | 240 | step( 241 | name = name, 242 | `if` = `if`, 243 | uses = "netlify/actions/cli@645ae7398cf5b912a3fa1eb0b88618301aaa85d0", 244 | env = c( 245 | env, 246 | list( 247 | NETLIFY_AUTH_TOKEN = "${{ secrets.NETLIFY_AUTH_TOKEN }}", 248 | NETLIFY_SITE_ID = "${{ secrets.NETLIFY_SITE_ID }}" 249 | ) 250 | ), 251 | with = c( 252 | with, 253 | list(args = args) 254 | ), 255 | ... 256 | ) 257 | } 258 | 259 | #' @describeIn deploy_static Wraps the external [Google Firebase CLI action](https://github.com/w9jds/firebase-action) to deploy to [Google Firebase](http://firebase.google.com). 260 | #' 261 | #' @param PROJECT_ID `[character(1)]` 262 | #' giving a specific project to use for all commands, not required if you specify a project in your `.firebaserc`` file. 263 | #' 264 | #' @section Google Firebase: 265 | #' **Remember to provide `FIREBASE_TOKEN` as a secret to the GitHub UI.** 266 | #' 267 | #' Configuration details other than `PROJECT_ID` are read from the `firebase.json` at the root of your repository. 268 | #' 269 | #' Because firebase gets the deploy directory from a `firebase.json` file, it cannot use `$DEPLOY_DIR`. 270 | #' Manually edit your `firebase.json` to provide the deploy path. 271 | # tracked in https://github.com/maxheld83/ghactions/issues/80 272 | #' 273 | #' @export 274 | firebase <- function(name = "Deploy to Firebase", 275 | `if` = "github.ref == 'refs/heads/master'", 276 | PROJECT_ID = NULL, 277 | with = NULL, 278 | env = NULL, 279 | ...) { 280 | checkmate::assert_string(x = PROJECT_ID, null.ok = TRUE) 281 | 282 | step( 283 | name = name, 284 | `if` = `if`, 285 | uses = "w9jds/firebase-action@v1.0.1", 286 | env = c( 287 | env, 288 | list( 289 | FIREBASE_TOKEN = "${{ secrets.FIREBASE_TOKEN }}", 290 | PROJECT_ID = PROJECT_ID 291 | ) 292 | ), 293 | with = c( 294 | with, 295 | list(args = "deploy --only hosting") 296 | ), 297 | ... 298 | ) 299 | } 300 | 301 | 302 | # installation ==== 303 | 304 | #' Create a step to checkout a repository 305 | #' 306 | #' @family steps 307 | #' @family installation 308 | #' 309 | #' @export 310 | checkout <- function() { 311 | step( 312 | name = "Checkout Repository", 313 | uses = "actions/checkout@master" 314 | ) 315 | } 316 | 317 | # this should always closely follow https://github.com/r-lib/r-azure-pipelines/blob/master/templates/pkg-install_dependencies.yml 318 | 319 | #' Create a step to install R package dependencies 320 | #' 321 | #' Installs R package dependencies from a `DESCRIPTION` at the repository root. 322 | #' 323 | #' @inheritParams step 324 | #' @param ... Passed to [rscript()] 325 | #' 326 | #' @inheritDotParams step -run -uses 327 | #' 328 | #' @family steps 329 | #' @family installation 330 | #' 331 | #' @export 332 | install_deps <- function(name = "Install Package Dependencies", ...) { 333 | rscript( 334 | name = name, 335 | expr = c( 336 | "install.packages('remotes', repos = 'https://demo.rstudiopm.com/all/__linux__/bionic/latest')", 337 | "remotes::install_deps(dependencies = TRUE, repos = 'https://demo.rstudiopm.com/all/__linux__/bionic/latest')" 338 | ), 339 | ... 340 | ) 341 | } 342 | 343 | 344 | # pkg dev ==== 345 | 346 | # all of the below should closely follow https://github.com/r-lib/r-azure-pipelines 347 | 348 | #' CI/CD steps for a package at the repository root 349 | #' 350 | #' @name pkg_dev 351 | #' 352 | #' @inheritParams step 353 | #' 354 | #' @family steps 355 | #' @family pkg_development 356 | NULL 357 | 358 | #' @describeIn pkg_dev [rcmdcheck::rcmdcheck()] 359 | rcmd_check <- function(name = "Check Package") { 360 | rscript( 361 | name = name, 362 | expr = "rcmdcheck::rcmdcheck(error_on = 'error', check_dir = 'check')" 363 | ) 364 | } 365 | 366 | #' @describeIn pkg_dev [covr::codecov()] 367 | covr <- function(name = "Run Code Coverage") { 368 | rscript( 369 | name = name, 370 | expr = "covr::codecov(quiet = FALSE, commit = '$GITHUB_SHA', branch = '$GITHUB_REF')" 371 | ) 372 | } 373 | 374 | -------------------------------------------------------------------------------- /R/syntax.R: -------------------------------------------------------------------------------- 1 | # Workflows ==== 2 | #' Create nested list for a [workflow block](https://help.github.com/en/articles/workflow-syntax-for-github-actions) 3 | #' 4 | #' @param name `[character(1)]` 5 | #' giving the [name](https://help.github.com/en/articles/workflow-syntax-for-github-actions#name) of the workflow. 6 | #' Defaults to `NULL`, for no name, in which case GitHub will use the file name. 7 | #' 8 | #' @param on `[character()]` 9 | #' giving the [GitHub Event](https://help.github.com/en/articles/events-that-trigger-workflows) on which to trigger the workflow. 10 | #' Must be a subset of [ghactions_events]. 11 | #' Defaults to `"push"`, in which case the workflow is triggered on every push event. 12 | #' Can also be a named list as returned by [on()] for additional filters. 13 | #' 14 | #' @param jobs `[list()]` 15 | #' giving a *named* list of jobs, with each list element as returned by [job()]. 16 | #' 17 | #' @examples 18 | #' workflow( 19 | #' name = "Render", 20 | #' on = "push", 21 | #' jobs = NULL 22 | #' ) 23 | #' 24 | #' @family syntax 25 | #' 26 | #' @export 27 | workflow <- function(name = NULL, on = "push", jobs = NULL) { 28 | checkmate::assert_string(x = name, null.ok = TRUE, na.ok = FALSE) 29 | if (is.character(on)) { 30 | checkmate::assert_subset( 31 | x = on, 32 | choices = ghactions_events, 33 | empty.ok = FALSE 34 | ) 35 | } else { 36 | checkmate::assert_list( 37 | x = on, 38 | any.missing = FALSE, 39 | names = "named" 40 | ) 41 | } 42 | checkmate::assert_list( 43 | x = jobs, 44 | any.missing = FALSE, 45 | null.ok = TRUE, 46 | names = "unique" 47 | ) 48 | 49 | purrr::compact(as.list(environment())) 50 | } 51 | 52 | 53 | #' Create nested list for an `on:` field 54 | #' 55 | #' @param event `[character(1)]` 56 | #' giving the event on which to filter. 57 | #' Must be *one* of `c("push", "pull_request", "schedule")`. 58 | #' 59 | #' @param ... `[character()]` 60 | #' giving the filters on which to run. 61 | #' Must correspond to the filters allowed by `event`. 62 | #' 63 | #' @details 64 | #' See the [GitHub Actions workflow syntax](https://help.github.com/en/articles/workflow-syntax-for-github-actions) for details. 65 | #' 66 | #' @export 67 | #' 68 | #' @family syntax 69 | #' 70 | #' @examples 71 | #' on( 72 | #' event = "push", 73 | #' branches = c("master", "releases/*") 74 | #' ) 75 | on <- function(event, ...) { 76 | checkmate::assert_choice( 77 | x = event, 78 | choices = c("push", "pull_request", "schedule") 79 | ) 80 | rlang::set_names(x = list(purrr::compact(list(...))), nm = event) 81 | } 82 | 83 | #' @describeIn on filter on push event 84 | #' 85 | #' @param tags,branches,paths `[character()]` 86 | #' giving the [tags, branches](https://help.github.com/en/articles/workflow-syntax-for-github-actions#onpushpull_requesttagsbranches) or [modified paths](https://help.github.com/en/articles/workflow-syntax-for-github-actions#onpushpull_requestpaths) on which to run the workflow. 87 | #' Defaults to `NULL` for no additional filters. 88 | #' 89 | #' @export 90 | on_push <- function(tags = NULL, branches = NULL, paths = NULL) { 91 | on(event = "push", tags = tags, branches = branches, paths = paths) 92 | } 93 | 94 | #' @describeIn on filter on pull request 95 | #' 96 | #' @export 97 | on_pull_request <- function(tags = NULL, branches = NULL, paths = NULL) { 98 | on(event = "pull_request", tags = tags, branches = branches, paths = paths) 99 | } 100 | 101 | #' @describeIn on filter on schedule 102 | #' 103 | #' @param cron `[character(1)]` 104 | #' giving UTC times using [POSIX cron syntax](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/crontab.html#tag_20_25_07). 105 | #' 106 | #' @export 107 | on_schedule <- function(cron = NULL) { 108 | on(event = "schedule", cron = cron) 109 | } 110 | 111 | 112 | #' Supported events to trigger GitHub actions 113 | #' 114 | #' You can trigger GitHub actions from these events. 115 | #' List is taken from [official spec](https://developer.github.com/actions/creating-workflows/workflow-configuration-options/#events-supported-in-workflow-files). 116 | #' 117 | #' @family syntax 118 | #' 119 | #' @examples 120 | #' ghactions_events 121 | #' 122 | #' @export 123 | ghactions_events <- c( 124 | "check_run", 125 | "check_suite", 126 | "commit_comment", 127 | "create", 128 | "delete", 129 | "deployment", 130 | "deployment_status", 131 | "fork", 132 | "gollum", 133 | "issue_comment", 134 | "issues", 135 | "label", 136 | "member", 137 | "milestone", 138 | "page_build", 139 | "project", 140 | "project_card", 141 | "project_column", 142 | "public", 143 | "pull_request", 144 | "pull_request_review_comment", 145 | "pull_request_review", 146 | "push", 147 | "repository_dispatch", 148 | "release", 149 | "schedule", 150 | "status", 151 | "watch" 152 | ) 153 | 154 | 155 | # Jobs ==== 156 | 157 | #' Create nested list for *one* [job](https://help.github.com/en/articles/workflow-syntax-for-github-actions#jobs) 158 | #' 159 | #' @param id,name `[character(1)]` 160 | #' giving additional options for the job. 161 | #' Defaults to `NULL`. 162 | #' 163 | #' @param needs `[character()]` 164 | #' giving the jobs that must complete successfully before this job is run. 165 | #' Defaults to `NULL` for no dependencies. 166 | #' 167 | #' @param runs-on `[character(1)]` 168 | #' giving the type of virtual host machine to run the job on. 169 | #' Defaults to `"ubuntu-18.04"`. 170 | #' Must be one of [ghactions_vms]. 171 | #' 172 | #' @param steps `[list()]` 173 | #' giving an *unnamed* list of steps, with each element as returned by [step()]. 174 | #' Defaults to `NULL`. 175 | #' 176 | #' @param timeout_minutes `[integer(1)]` 177 | #' giving the maximum number of minutes to let a workflow run before GitHub automatically cancels it. 178 | #' Defaults to `NULL`. 179 | #' 180 | #' @param strategy `[list()]` 181 | #' giving a named list as returned by [strategy()]. 182 | #' Defaults to `NULL`. 183 | #' 184 | #' @param container `[character(1)]`/`[list()]` 185 | #' giving a published container image. 186 | #' For advanced options, use [container()]. 187 | #' Defaults to `NULL`. 188 | #' 189 | #' @param services `[list()]` 190 | #' giving additional containers to host services for a job in a workflow in a *named* list. 191 | #' Use [container()] to construct the list elements. 192 | #' Defaults to `NULL`. 193 | #' 194 | #' @family syntax 195 | #' 196 | #' @export 197 | job <- function(id, 198 | name = NULL, 199 | needs = NULL, 200 | `runs-on` = "ubuntu-18.04", 201 | steps = NULL, 202 | timeout_minutes = NULL, 203 | strategy = NULL, 204 | container = NULL, 205 | services = NULL) { 206 | checkmate::assert_string(x = id, na.ok = FALSE) 207 | checkmate::assert_string(x = name, na.ok = FALSE, null.ok = TRUE) 208 | checkmate::assert_character( 209 | x = needs, 210 | any.missing = FALSE, 211 | unique = TRUE, 212 | null.ok = TRUE 213 | ) 214 | checkmate::assert_choice( 215 | x = `runs-on`, 216 | choices = ghactions_vms, 217 | null.ok = FALSE 218 | ) 219 | checkmate::assert_list( 220 | x = steps, 221 | null.ok = TRUE, 222 | names = "unnamed" 223 | ) 224 | checkmate::assert_scalar( 225 | x = timeout_minutes, 226 | na.ok = FALSE, 227 | null.ok = TRUE 228 | ) 229 | checkmate::assert_list( 230 | x = strategy, 231 | any.missing = FALSE, 232 | names = "unique", 233 | null.ok = TRUE 234 | ) 235 | if (is.character(container)) { 236 | checkmate::assert_string(x = container, na.ok = FALSE, null.ok = TRUE) 237 | } else { 238 | checkmate::assert_list( 239 | x = container, 240 | any.missing = FALSE, 241 | null.ok = TRUE, 242 | names = "unique" 243 | ) 244 | } 245 | checkmate::assert_list( 246 | x = services, 247 | any.missing = FALSE, 248 | null.ok = TRUE, 249 | names = "unique" 250 | ) 251 | 252 | res <- as.list(environment()) 253 | res$id <- NULL # that's the name of the list, not *in* the list 254 | res <- purrr::compact(res) 255 | rlang::set_names(x = list(res), nm = id) 256 | } 257 | 258 | 259 | #' Create nested list for the [strategy](https://help.github.com/en/articles/workflow-syntax-for-github-actions#jobsjob_idstrategy) field in [job()] 260 | #' 261 | #' @param matrix `[list(list(c()))]` 262 | #' giving the values for each variable for the matrix build. 263 | #' See [gh_matrix()] for additional options. 264 | #' Defaults to `NULL`. 265 | #' 266 | #' @param fail-fast `[logical()]` 267 | #' giving whether GitHub should cancel all in-progress jobs if any matrix job fails. 268 | #' Defaults to `NULL`. 269 | #' 270 | #' @param max-parallel `[integer(1)]` 271 | #' giving the maximum number of jobs to run simultaneously when using a matrix job strategy. 272 | #' 273 | #' @family syntax 274 | #' 275 | #' @export 276 | strategy <- function(matrix = NULL, `fail-fast` = NULL, `max-parallel` = NULL) { 277 | checkmate::assert_list( 278 | x = matrix, 279 | types = "atomicvector", 280 | any.missing = FALSE, 281 | names = "unique", 282 | null.ok = TRUE 283 | ) 284 | checkmate::assert_flag( 285 | x = `fail-fast`, 286 | na.ok = FALSE, 287 | null.ok = TRUE 288 | ) 289 | checkmate::assert_scalar( 290 | x = `max-parallel`, 291 | na.ok = FALSE, 292 | null.ok = TRUE 293 | ) 294 | 295 | purrr::compact(as.list(environment())) 296 | } 297 | 298 | 299 | #' Create nested list for the [matrix](https://help.github.com/en/articles/workflow-syntax-for-github-actions#jobsjob_idstrategy) field in [strategy()] 300 | #' 301 | #' @param ... `[character()]` 302 | #' giving values for variable for the matrix build. 303 | #' 304 | #' @param exclude,include `[list(list(character(1)))]` 305 | #' giving unnamed lists of combinations of variables to ex- or include. 306 | #' Defaults to `NULL`. 307 | #' 308 | #' @export 309 | #' 310 | #' @family syntax 311 | gh_matrix <- function(..., exclude = NULL, include = NULL) { 312 | checkmate::assert_list( 313 | x = exclude, 314 | types = "character", 315 | any.missing = FALSE, 316 | names = "unnamed", 317 | null.ok = TRUE 318 | ) 319 | 320 | purrr::compact(c(list(...), as.list(environment()))) 321 | } 322 | 323 | 324 | #' Create nested list for the [container](https://help.github.com/en/articles/workflow-syntax-for-github-actions#jobsjob_idcontainer) field in [job()] 325 | #' 326 | #' @param image `[character(1)]` 327 | #' giving the published docker image to use as the container to run the action. 328 | #' 329 | 330 | #' @param env `[list()]` 331 | #' giving environment variables for the container as a *named* list. 332 | #' Defaults to `NULL`. 333 | #' 334 | #' @param ports,volumes `[list()]` 335 | #' giving ports to expose, and volumes for the container to use as an *unnamed* list. 336 | #' Defaults to `NULL`. 337 | #' 338 | #' @param options `[character()]` 339 | #' giving additional options. 340 | #' Defaults to `NULL`. 341 | #' 342 | #' @family syntax 343 | #' 344 | #' @export 345 | container <- function(image, 346 | env = NULL, 347 | ports = NULL, 348 | volumes = NULL, 349 | options = NULL) { 350 | checkmate::assert_string(x = image, na.ok = FALSE) 351 | checkmate::assert_list( 352 | x = env, 353 | types = "atomicvector", 354 | any.missing = FALSE, 355 | names = "unique", 356 | null.ok = TRUE 357 | ) 358 | purrr::walk( 359 | .x = list(ports, volumes), 360 | .f = checkmate::assert_list, 361 | any.missing = FALSE, 362 | null.ok = TRUE, 363 | names = "unnamed" 364 | ) 365 | checkmate::assert_character(x = options, null.ok = TRUE) 366 | 367 | purrr::compact(as.list(environment())) 368 | } 369 | 370 | 371 | #' @title Virtual machines [available](https://help.github.com/en/articles/workflow-syntax-for-github-actions#jobsjob_idruns-on) on GitHub Actions 372 | #' 373 | #' @family syntax 374 | #' 375 | #' @examples 376 | #' ghactions_vms 377 | #' 378 | #' @export 379 | ghactions_vms <- c( 380 | "ubuntu-latest", 381 | "ubuntu-18.04", 382 | "ubuntu-16.04", 383 | "windows-latest", 384 | "windows-2019", 385 | "windows-2016", 386 | "macOS-latest", 387 | "macOS-10.14" 388 | ) 389 | 390 | 391 | # Steps ==== 392 | 393 | #' Create nested list for *one* [job](https://help.github.com/en/articles/workflow-syntax-for-github-actions#jobs) 394 | #' 395 | #' @param id,if,name,uses,shell `[character(1)]` 396 | #' giving additional options for the step. 397 | #' Multiline strings are not supported. 398 | #' Defaults to `NULL`. 399 | #' 400 | #' @param run `[character()]` 401 | #' giving commands to run. 402 | #' Will be turned into a multiline string. 403 | #' Defaults to `NULL`. 404 | #' 405 | #' @param with,env `[list()]` 406 | #' giving a named list of additional parameters. 407 | #' Defaults to `NULL`. 408 | #' 409 | #' @param working-directory `[character(1)]` 410 | #' giving the default working directory. 411 | #' Defaults to `NULL`. 412 | #' 413 | #' @param continue-on-error `[logical(1)]` 414 | #' giving whether to allow a job to pass when this step fails. 415 | #' Defaults to `NULL`. 416 | #' 417 | #' @param timeout-minutes `[integer(1)]` 418 | #' giving the maximum number of minutes to run the step before killing the process. 419 | #' Defaults to `NULL`. 420 | #' 421 | #' @family syntax 422 | #' 423 | #' @export 424 | step <- function(name = NULL, 425 | id = NULL, 426 | `if` = NULL, 427 | uses = NULL, 428 | run = NULL, 429 | shell = NULL, 430 | with = NULL, 431 | env = NULL, 432 | `working-directory` = NULL, 433 | `continue-on-error` = NULL, 434 | `timeout-minutes` = NULL) { 435 | purrr::walk( 436 | .x = list(id, `if`, name, uses, shell, `working-directory`), 437 | .f = checkmate::assert_string, 438 | na.ok = FALSE, 439 | null.ok = TRUE 440 | ) 441 | checkmate::assert_character(x = run, any.missing = FALSE, null.ok = TRUE) 442 | purrr::walk( 443 | .x = list(with, env), 444 | .f = checkmate::assert_list, 445 | any.missing = FALSE, 446 | null.ok = TRUE, 447 | names = "unique" 448 | ) 449 | checkmate::assert_flag(x = `continue-on-error`, na.ok = FALSE, null.ok = TRUE) 450 | checkmate::assert_scalar(x = `timeout-minutes`, na.ok = FALSE, null.ok = TRUE) 451 | 452 | # linebreaks for run 453 | run <- glue::glue_collapse(x = run, sep = "\n", last = "\n") 454 | 455 | purrr::compact(as.list(environment())) 456 | } 457 | --------------------------------------------------------------------------------