├── .Rbuildignore ├── .github ├── .gitignore ├── CODE_OF_CONDUCT.md └── workflows │ ├── R-CMD-check.yaml │ ├── pkgdown.yaml │ ├── pr-commands.yaml │ ├── rtools.yaml │ └── test-coverage.yaml ├── .gitignore ├── .vscode ├── extensions.json └── settings.json ├── DESCRIPTION ├── LICENSE ├── LICENSE.md ├── NAMESPACE ├── NEWS.md ├── R ├── build-bg.R ├── build-tools.R ├── build.R ├── c-registration.R ├── cache.R ├── callback.R ├── compile-dll.R ├── compiler-flags.R ├── compiler.R ├── exclude.R ├── find-package-root.R ├── has_src.R ├── latex.R ├── pkgbuild-package.R ├── rcmd.R ├── rtools-cache.R ├── rtools-config.R ├── rtools-metadata.R ├── rtools-path.R ├── rtools-registry.R ├── rtools.R ├── styles.R ├── time.R ├── utils.R ├── with-debug.R └── withr.R ├── README.md ├── _pkgdown.yml ├── air.toml ├── codecov.yml ├── cran-comments.md ├── man ├── build.Rd ├── clean_dll.Rd ├── compile_dll.Rd ├── compiler_flags.Rd ├── has_build_tools.Rd ├── has_compiler.Rd ├── has_latex.Rd ├── has_rtools.Rd ├── needs_compile.Rd ├── pkg_has_src.Rd ├── pkg_links_to_rcpp.Rd ├── pkgbuild-package.Rd ├── pkgbuild_process.Rd ├── rcmd_build_tools.Rd ├── rtools_needed.Rd ├── with_debug.Rd └── without_compiler.Rd ├── pkgbuild.Rproj └── tests ├── build-tools.R ├── testthat.R └── testthat ├── _snaps ├── archives.md ├── build-process.md ├── build.md ├── compiler-flags.md ├── compiler.md ├── exclude.md ├── find-package-root.md ├── new │ └── build.md ├── old │ └── build.md ├── style.md └── utils.md ├── fixtures ├── testDummy_0.1.tar.gz ├── testWithSrc_0.1.tar.gz ├── xxx.gz ├── xxx.tar.gz └── xxx.zip ├── helper.R ├── test-archives.R ├── test-build-process.R ├── test-build.R ├── test-build_tools.R ├── test-c-registration.R ├── test-compile_dll.R ├── test-compiler-flags.R ├── test-compiler.R ├── test-exclude.R ├── test-find-package-root.R ├── test-rtools.R ├── test-style.R ├── test-utils.R ├── test-withr.R ├── testDummy ├── DESCRIPTION ├── NAMESPACE └── R │ ├── a.R │ └── b.R ├── testInstDoc ├── DESCRIPTION ├── NAMESPACE ├── R │ ├── a.R │ └── b.R ├── inst │ └── doc │ │ └── keep.me └── vignettes │ └── test.Rmd └── testWithSrc ├── DESCRIPTION ├── NAMESPACE ├── R ├── a.R └── b.R └── src └── add1.c /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^revdep$ 2 | ^.*\.Rproj$ 3 | ^\.Rproj\.user$ 4 | ^\.travis\.yml$ 5 | ^codecov\.yml$ 6 | ^appveyor\.yml$ 7 | ^\.httr-oauth$ 8 | ^cran-comments\.md$ 9 | ^CRAN-RELEASE$ 10 | ^pkgbuild.*\.tar\.gz$ 11 | ^script\.R$ 12 | ^\.github$ 13 | ^LICENSE\.md$ 14 | ^_pkgdown\.yml$ 15 | ^docs$ 16 | ^pkgdown$ 17 | ^dev-lib$ 18 | ^[\.]?air\.toml$ 19 | ^\.vscode$ 20 | -------------------------------------------------------------------------------- /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, caste, color, religion, or sexual 10 | identity and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the overall 26 | community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or advances of 31 | any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email address, 35 | without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at codeofconduct@posit.co. 63 | All complaints will be reviewed and investigated promptly and fairly. 64 | 65 | All community leaders are obligated to respect the privacy and security of the 66 | reporter of any incident. 67 | 68 | ## Enforcement Guidelines 69 | 70 | Community leaders will follow these Community Impact Guidelines in determining 71 | the consequences for any action they deem in violation of this Code of Conduct: 72 | 73 | ### 1. Correction 74 | 75 | **Community Impact**: Use of inappropriate language or other behavior deemed 76 | unprofessional or unwelcome in the community. 77 | 78 | **Consequence**: A private, written warning from community leaders, providing 79 | clarity around the nature of the violation and an explanation of why the 80 | behavior was inappropriate. A public apology may be requested. 81 | 82 | ### 2. Warning 83 | 84 | **Community Impact**: A violation through a single incident or series of 85 | actions. 86 | 87 | **Consequence**: A warning with consequences for continued behavior. No 88 | interaction with the people involved, including unsolicited interaction with 89 | those enforcing the Code of Conduct, for a specified period of time. This 90 | includes avoiding interactions in community spaces as well as external channels 91 | like social media. Violating these terms may lead to a temporary or permanent 92 | ban. 93 | 94 | ### 3. Temporary Ban 95 | 96 | **Community Impact**: A serious violation of community standards, including 97 | sustained inappropriate behavior. 98 | 99 | **Consequence**: A temporary ban from any sort of interaction or public 100 | communication with the community for a specified period of time. No public or 101 | private interaction with the people involved, including unsolicited interaction 102 | with those enforcing the Code of Conduct, is allowed during this period. 103 | Violating these terms may lead to a permanent ban. 104 | 105 | ### 4. Permanent Ban 106 | 107 | **Community Impact**: Demonstrating a pattern of violation of community 108 | standards, including sustained inappropriate behavior, harassment of an 109 | individual, or aggression toward or disparagement of classes of individuals. 110 | 111 | **Consequence**: A permanent ban from any sort of public interaction within the 112 | community. 113 | 114 | ## Attribution 115 | 116 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 117 | version 2.1, available at 118 | . 119 | 120 | Community Impact Guidelines were inspired by 121 | [Mozilla's code of conduct enforcement ladder][https://github.com/mozilla/inclusion]. 122 | 123 | For answers to common questions about this code of conduct, see the FAQ at 124 | . Translations are available at . 125 | 126 | [homepage]: https://www.contributor-covenant.org 127 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | # 4 | # NOTE: This workflow is overkill for most R packages and 5 | # check-standard.yaml is likely a better choice. 6 | # usethis::use_github_action("check-standard") will install it. 7 | on: 8 | push: 9 | branches: [main, master] 10 | pull_request: 11 | 12 | name: R-CMD-check.yaml 13 | 14 | permissions: read-all 15 | 16 | jobs: 17 | R-CMD-check: 18 | runs-on: ${{ matrix.config.os }} 19 | 20 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 21 | 22 | strategy: 23 | fail-fast: false 24 | matrix: 25 | config: 26 | - {os: macos-latest, r: 'release'} 27 | 28 | - {os: windows-latest, r: 'release'} 29 | # use 4.0 or 4.1 to check with rtools40's older compiler 30 | - {os: windows-latest, r: 'oldrel-4'} 31 | 32 | - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} 33 | - {os: ubuntu-latest, r: 'release'} 34 | - {os: ubuntu-latest, r: 'oldrel-1'} 35 | - {os: ubuntu-latest, r: 'oldrel-2'} 36 | - {os: ubuntu-latest, r: 'oldrel-3'} 37 | - {os: ubuntu-latest, r: 'oldrel-4'} 38 | 39 | env: 40 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 41 | R_KEEP_PKG_SOURCE: yes 42 | 43 | steps: 44 | - uses: actions/checkout@v4 45 | 46 | - uses: r-lib/actions/setup-pandoc@v2 47 | 48 | - uses: r-lib/actions/setup-r@v2 49 | with: 50 | r-version: ${{ matrix.config.r }} 51 | http-user-agent: ${{ matrix.config.http-user-agent }} 52 | use-public-rspm: true 53 | 54 | - uses: r-lib/actions/setup-r-dependencies@v2 55 | with: 56 | extra-packages: any::rcmdcheck 57 | needs: check 58 | 59 | - uses: r-lib/actions/check-r-package@v2 60 | with: 61 | upload-snapshots: true 62 | build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")' 63 | -------------------------------------------------------------------------------- /.github/workflows/pkgdown.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | release: 8 | types: [published] 9 | workflow_dispatch: 10 | 11 | name: pkgdown.yaml 12 | 13 | permissions: read-all 14 | 15 | jobs: 16 | pkgdown: 17 | runs-on: ubuntu-latest 18 | # Only restrict concurrency for non-PR jobs 19 | concurrency: 20 | group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} 21 | env: 22 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 23 | permissions: 24 | contents: write 25 | steps: 26 | - uses: actions/checkout@v4 27 | 28 | - uses: r-lib/actions/setup-pandoc@v2 29 | 30 | - uses: r-lib/actions/setup-r@v2 31 | with: 32 | use-public-rspm: true 33 | 34 | - uses: r-lib/actions/setup-r-dependencies@v2 35 | with: 36 | extra-packages: any::pkgdown, local::. 37 | needs: website 38 | 39 | - name: Build site 40 | run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) 41 | shell: Rscript {0} 42 | 43 | - name: Deploy to GitHub pages 🚀 44 | if: github.event_name != 'pull_request' 45 | uses: JamesIves/github-pages-deploy-action@v4.5.0 46 | with: 47 | clean: false 48 | branch: gh-pages 49 | folder: docs 50 | -------------------------------------------------------------------------------- /.github/workflows/pr-commands.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | issue_comment: 5 | types: [created] 6 | 7 | name: pr-commands.yaml 8 | 9 | permissions: read-all 10 | 11 | jobs: 12 | document: 13 | if: ${{ github.event.issue.pull_request && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER') && startsWith(github.event.comment.body, '/document') }} 14 | name: document 15 | runs-on: ubuntu-latest 16 | env: 17 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 18 | permissions: 19 | contents: write 20 | steps: 21 | - uses: actions/checkout@v4 22 | 23 | - uses: r-lib/actions/pr-fetch@v2 24 | with: 25 | repo-token: ${{ secrets.GITHUB_TOKEN }} 26 | 27 | - uses: r-lib/actions/setup-r@v2 28 | with: 29 | use-public-rspm: true 30 | 31 | - uses: r-lib/actions/setup-r-dependencies@v2 32 | with: 33 | extra-packages: any::roxygen2 34 | needs: pr-document 35 | 36 | - name: Document 37 | run: roxygen2::roxygenise() 38 | shell: Rscript {0} 39 | 40 | - name: commit 41 | run: | 42 | git config --local user.name "$GITHUB_ACTOR" 43 | git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com" 44 | git add man/\* NAMESPACE 45 | git commit -m 'Document' 46 | 47 | - uses: r-lib/actions/pr-push@v2 48 | with: 49 | repo-token: ${{ secrets.GITHUB_TOKEN }} 50 | 51 | style: 52 | if: ${{ github.event.issue.pull_request && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER') && startsWith(github.event.comment.body, '/style') }} 53 | name: style 54 | runs-on: ubuntu-latest 55 | env: 56 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 57 | permissions: 58 | contents: write 59 | steps: 60 | - uses: actions/checkout@v4 61 | 62 | - uses: r-lib/actions/pr-fetch@v2 63 | with: 64 | repo-token: ${{ secrets.GITHUB_TOKEN }} 65 | 66 | - uses: r-lib/actions/setup-r@v2 67 | 68 | - name: Install dependencies 69 | run: install.packages("styler") 70 | shell: Rscript {0} 71 | 72 | - name: Style 73 | run: styler::style_pkg() 74 | shell: Rscript {0} 75 | 76 | - name: commit 77 | run: | 78 | git config --local user.name "$GITHUB_ACTOR" 79 | git config --local user.email "$GITHUB_ACTOR@users.noreply.github.com" 80 | git add \*.R 81 | git commit -m 'Style' 82 | 83 | - uses: r-lib/actions/pr-push@v2 84 | with: 85 | repo-token: ${{ secrets.GITHUB_TOKEN }} 86 | -------------------------------------------------------------------------------- /.github/workflows/rtools.yaml: -------------------------------------------------------------------------------- 1 | name: rtools.yaml 2 | 3 | on: 4 | push: 5 | branches: main 6 | pull_request: 7 | workflow_dispatch: 8 | 9 | permissions: read-all 10 | 11 | jobs: 12 | pkgbuild: 13 | runs-on: windows-latest 14 | name: pkgbuild - ${{ matrix.config.r }} 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | config: 19 | - { r: 'devel' } 20 | - { r: 'next' } 21 | - { r: 'release' } 22 | - { r: 'oldrel/1' } 23 | - { r: 'oldrel/2' } 24 | - { r: 'oldrel/3' } 25 | - { r: 'oldrel/4' } 26 | - { r: 'oldrel/5' } 27 | 28 | steps: 29 | - name: Install rig 30 | run: | 31 | Invoke-WebRequest -Uri https://github.com/r-lib/rig/releases/download/v0.7.0/rig-windows-0.7.0.exe -OutFile rig-installer.exe 32 | Start-Process ".\rig-installer.exe" -ArgumentList "/verysilent /suppressmsgboxes" -Wait -NoNewWindow 33 | del rig-installer.exe 34 | 35 | - name: Remove pre-installed R 36 | run: | 37 | $rver = (rig list --json).replace("\", "\\") | jq -r .[0].name 38 | mv "C:\Program Files\R/R-$rver" "C:\Program Files\bak" 39 | mv C:\rtools* C:\bakrtoolsbak 40 | rig system clean-registry 41 | rig ls 42 | rig system rtools ls 43 | 44 | - name: Install R 45 | run: | 46 | $URL = (Invoke-WebRequest https://api.r-hub.io/rversions/resolve/${{ matrix.config.r }}/windows | Select-Object -Expand Content | jq -r .url) 47 | Invoke-WebRequest $URL -OutFile R-installer.exe 48 | Start-Process ".\R-installer.exe" -ArgumentList "/verysilent /suppressmsgboxes" -Wait -NoNewWindow 49 | del R-installer.exe 50 | 51 | - name: Install Rtools 52 | run: | 53 | $URL = (Invoke-WebRequest https://api.r-hub.io/rversions/resolve/${{ matrix.config.r }}/windows | Select-Object -Expand Content | jq -r .rtools_url) 54 | Invoke-WebRequest $URL -OutFile Rtools.exe 55 | Start-Process ".\Rtools.exe" -ArgumentList "/verysilent /suppressmsgboxes" -Wait -NoNewWindow 56 | 57 | - name: Put R on the path 58 | run: | 59 | $rver = (rig list --json).replace("\", "\\") | jq -r .[0].name 60 | rig default $rver 61 | rig system make-links 62 | rig ls 63 | rig system rtools ls 64 | 65 | - name: Configure RSPM 66 | run: | 67 | writeLines( 68 | "options(repos = c(RSPM = 'https://packagemanager.posit.co/cran/latest'))", 69 | "~/.Rprofile" 70 | ) 71 | shell: Rscript {0} 72 | 73 | - uses: actions/checkout@v4 74 | - uses: r-hub/actions/debug-shell@v1 75 | - uses: r-lib/actions/setup-r-dependencies@v2 76 | with: 77 | dependencies: '"hard"' 78 | cache-version: 'rtools-${{ matrix.config.r }}' 79 | install-pandoc: false 80 | 81 | - name: Environment 82 | run: | 83 | Sys.getenv() 84 | shell: Rscript {0} 85 | 86 | - name: Install pkgbuild 87 | run: | 88 | cd .. 89 | RS CMD build pkgbuild 90 | $file = (dir pkgbuild_*.tar.gz).name 91 | RS CMD INSTALL $file 92 | 93 | - name: Vanilla 94 | run: | 95 | if (getRversion() >= "4.2.0") { 96 | if (getRversion() < "4.3.0") { 97 | # this might be a bug in R or Rtools or in jpeg 98 | Sys.setenv("PKG_CONFIG_PATH" = "/c/rtools42/x86_64-w64-mingw32.static.posix/lib/pkgconfig") 99 | } 100 | srcjpeg <- download.packages("jpeg", ".")[,2] 101 | install.packages(srcjpeg, repos = NULL, type = "source") 102 | } 103 | shell: Rscript {0} 104 | 105 | - name: Rtools finding 106 | run: | 107 | ok <- pkgbuild::find_rtools(debug = TRUE) 108 | if (!ok) stop("No Rtools. :(") 109 | shell: Rscript {0} 110 | 111 | - name: Test pkgbuild 112 | run: | 113 | srcjpeg <- download.packages("jpeg", ".")[,2] 114 | binjpeg <- pkgbuild::build(srcjpeg, binary = TRUE) 115 | install.packages(binjpeg, repos = NULL) 116 | library(jpeg) 117 | shell: Rscript {0} 118 | -------------------------------------------------------------------------------- /.github/workflows/test-coverage.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | 8 | name: test-coverage.yaml 9 | 10 | permissions: read-all 11 | 12 | jobs: 13 | test-coverage: 14 | runs-on: ubuntu-latest 15 | env: 16 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | 21 | - uses: r-lib/actions/setup-r@v2 22 | with: 23 | use-public-rspm: true 24 | 25 | - uses: r-lib/actions/setup-r-dependencies@v2 26 | with: 27 | extra-packages: any::covr, any::xml2 28 | needs: coverage 29 | 30 | - name: Test coverage 31 | run: | 32 | cov <- covr::package_coverage( 33 | quiet = FALSE, 34 | clean = FALSE, 35 | install_path = file.path(normalizePath(Sys.getenv("RUNNER_TEMP"), winslash = "/"), "package") 36 | ) 37 | print(cov) 38 | covr::to_cobertura(cov) 39 | shell: Rscript {0} 40 | 41 | - uses: codecov/codecov-action@v5 42 | with: 43 | # Fail if error if not on PR, or if on PR and token is given 44 | fail_ci_if_error: ${{ github.event_name != 'pull_request' || secrets.CODECOV_TOKEN }} 45 | files: ./cobertura.xml 46 | plugins: noop 47 | disable_search: true 48 | token: ${{ secrets.CODECOV_TOKEN }} 49 | 50 | - name: Show testthat output 51 | if: always() 52 | run: | 53 | ## -------------------------------------------------------------------- 54 | find '${{ runner.temp }}/package' -name 'testthat.Rout*' -exec cat '{}' \; || true 55 | shell: bash 56 | 57 | - name: Upload test results 58 | if: failure() 59 | uses: actions/upload-artifact@v4 60 | with: 61 | name: coverage-test-failures 62 | path: ${{ runner.temp }}/package 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .httr-oauth 5 | script.R 6 | /revdep 7 | docs 8 | /dev-lib 9 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "Posit.air-vscode" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[r]": { 3 | "editor.formatOnSave": true, 4 | "editor.defaultFormatter": "Posit.air-vscode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: pkgbuild 2 | Title: Find Tools Needed to Build R Packages 3 | Version: 1.4.8.9000 4 | Authors@R: c( 5 | person("Hadley", "Wickham", role = "aut"), 6 | person("Jim", "Hester", role = "aut"), 7 | person("Gábor", "Csárdi", , "csardi.gabor@gmail.com", role = c("aut", "cre")), 8 | person("Posit Software, PBC", role = c("cph", "fnd"), 9 | comment = c(ROR = "03wc8by49")) 10 | ) 11 | Description: Provides functions used to build R packages. Locates 12 | compilers needed to build R packages on various platforms and ensures 13 | the PATH is configured appropriately so R can use them. 14 | License: MIT + file LICENSE 15 | URL: https://github.com/r-lib/pkgbuild, https://pkgbuild.r-lib.org 16 | BugReports: https://github.com/r-lib/pkgbuild/issues 17 | Depends: 18 | R (>= 3.5) 19 | Imports: 20 | callr (>= 3.2.0), 21 | cli (>= 3.4.0), 22 | desc, 23 | processx, 24 | R6 25 | Suggests: 26 | covr, 27 | cpp11, 28 | knitr, 29 | Rcpp, 30 | rmarkdown, 31 | testthat (>= 3.2.0), 32 | withr (>= 2.3.0) 33 | Config/Needs/website: tidyverse/tidytemplate 34 | Config/testthat/edition: 3 35 | Config/usethis/last-upkeep: 2025-04-30 36 | Encoding: UTF-8 37 | Roxygen: list(markdown = TRUE) 38 | RoxygenNote: 7.3.2 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2025 2 | COPYRIGHT HOLDER: pkgbuild authors 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2025 pkgbuild authors 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 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(build) 4 | export(check_build_tools) 5 | export(check_compiler) 6 | export(check_latex) 7 | export(check_rtools) 8 | export(clean_dll) 9 | export(compile_dll) 10 | export(compiler_flags) 11 | export(find_rtools) 12 | export(has_build_tools) 13 | export(has_compiler) 14 | export(has_devel) 15 | export(has_latex) 16 | export(has_rtools) 17 | export(local_build_tools) 18 | export(needs_compile) 19 | export(pkg_has_src) 20 | export(pkg_links_to_cpp11) 21 | export(pkg_links_to_rcpp) 22 | export(pkgbuild_process) 23 | export(rcmd_build_tools) 24 | export(rtools_needed) 25 | export(rtools_path) 26 | export(setup_rtools) 27 | export(with_build_tools) 28 | export(with_debug) 29 | export(with_latex) 30 | export(without_cache) 31 | export(without_compiler) 32 | export(without_latex) 33 | importFrom(R6,R6Class) 34 | importFrom(utils,head) 35 | importFrom(utils,tail) 36 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # pkgbuild (development version) 2 | 3 | # pkgbuild 1.4.8 4 | 5 | * New `Config/build/never-clean` `DESCRIPTION` option to avoid adding 6 | `--preclean` to `R CMD INSTALL` (e.g., when header files have changed) 7 | (@krlmlr, #204). 8 | 9 | * `has_rtools()` & co. now work correctly on aarch64 Windows, when 10 | `RTOOLS45_AARCH64_HOME` is not set (@remlapmot, #203). 11 | 12 | * `pkg_build()` and `pkgbuild_process` now work corrently when building 13 | binary packages from non-standard file names (#208). 14 | 15 | # pkgbuild 1.4.7 16 | 17 | * pkgbuild now supports R 4.5.x and Rtools45. 18 | 19 | * `has_build_tools()` (and related functions) now do not explicitly check 20 | for Rtools on Windows and R 4.3.0 and later, but rather they try to 21 | compile a simple package, like on Unix, for #199. 22 | 23 | # pkgbuild 1.4.6 24 | 25 | * No changes. 26 | 27 | # pkgbuild 1.4.5 28 | 29 | * pkgbuild now does a better job at finding Rtools 4.3 and 4.4 if they 30 | were not installed from an installer. 31 | 32 | * pkgbuild now detects Rtools correctly from the Windows registry 33 | again for Rtools 4.3 and 4.4 34 | 35 | # pkgbuild 1.4.4 36 | 37 | * pkgbuild now supports R 4.4.x and Rtools44 (#183). 38 | 39 | # pkgbuild 1.4.3 40 | 41 | * pkgbuild now does not need the crayon, rprojroot and prettyunits 42 | packages. 43 | 44 | # pkgbuild 1.4.2 45 | 46 | * Running `bootstrap.R` now works with `pkgbuild_process`, so it also works 47 | from pak (https://github.com/r-lib/pak/issues/508). 48 | 49 | # pkgbuild 1.4.1 50 | 51 | * New `Config/build/extra-sources` `DESCRIPTION` option to make pkgbuild aware 52 | of extra source files to consider in `needs_compile()`. 53 | 54 | * New `Config/build/bootstrap` `DESCRIPTION` option. Set it to `TRUE` to run 55 | `Rscript bootstrap.R` in the package root prior to building the source 56 | package (#157, @paleolimbot). 57 | 58 | * pkgbuild now supports Rtools43. 59 | 60 | * pkgbuild now always _appends_ its extra compiler flags to the ones that 61 | already exist in the system and/or user `Makevars` files (#156). 62 | 63 | # pkgbuild 1.4.0 64 | 65 | * pkgbuild can now avoid copying large package directories when building a 66 | source package. See the `PKG_BUILD_COPY_METHOD` environment variable in 67 | `?build` or the package README (#59). 68 | 69 | This is currently an experimental feature, and feedback is 70 | appreciated. 71 | 72 | * `R CMD build` warnings can now be turned into errors, by setting the 73 | `pkg.build_stop_for_warnings` option to `TRUE` or by setting the 74 | `PKG_BUILD_STOP_FOR_WARNINGS` environment variable to `true` (#114). 75 | 76 | * `need_compile()` now knows about Rust source code files, i.e. `Cargo.toml` 77 | and `*.rs` (#115). 78 | 79 | * Now `pkgbuild::build()` will not clean up `inst/doc` by default if the 80 | `Config/build/clean-inst-doc` entry in `DESCRIPTION` is set to `FALSE` (#128). 81 | 82 | * New `PKG_BUILD_COLOR_DIAGNOSTICS` environment variable to opt out from 83 | colored compiler output (#141). 84 | 85 | * pkgbuild now works with a full XCode installation if the XCode Command 86 | Line Tools are not installed, on macOS, in RStudio (#103). 87 | 88 | # pkgbuild 1.3.1 89 | 90 | * Accept Rtools40 for R 4.2, it works well, as long as the PATH includes 91 | both `${RTOOLS40_HOME}/usr/bin` and `${RTOOLS40_HOME}/ucrt64/bin`. 92 | E.g. `~/.Renviron` should contain now 93 | ``` 94 | PATH="${RTOOLS40_HOME}\usr\bin;${RTOOLS40_HOME}\ucrt64\bin;${PATH}" 95 | ``` 96 | to make Rtools40 work with both R 4.2.x (devel currently) and R 4.1.x and 97 | R 4.0.x. 98 | 99 | # pkgbuild 1.3.0 100 | 101 | * pkgbuild now supports Rtools 4.2. 102 | 103 | * pkgbuild now returns the correct path for R 3.x (#96). 104 | 105 | * `build()` now always returns the path of the built package (#108). 106 | 107 | * pkgbuild output now looks better in `.Rmd` documents and in general in non-dynamic terminals. You can also force dynamic and non-dynamic output now (#64). 108 | 109 | * pkgbuild does not build the PDF manual now if `pdflatex` is not installed, even if `manual = TRUE` (#123). 110 | 111 | # pkgbuild 1.2.1 112 | 113 | * Gábor Csárdi is now the maintainer. 114 | 115 | * `build_setup_source` now considers both command-line build arguments, as 116 | well as parameters `vignettes` or `manual` when conditionally executing 117 | flag-dependent behaviors (@dgkf, #120) 118 | 119 | # pkgbuild 1.2.0 120 | 121 | * pkgbuild is now licensed as MIT (#106) 122 | * `compile_dll()` gains a `debug` argument for more control over the compile options used (@richfitz, #100) 123 | * `pkgbuild_process()` and `build()` now use colored compiler diagnostics if supported (#102) 124 | * Avoid documentation link ambiguity in R 4.1 (#105) 125 | 126 | # pkgbuild 1.1.0 127 | 128 | * `compile_dll()` now supports automatic cpp11 registration if the package links to cpp11. 129 | * `rtools_needed` returns correct version instead of "custom" (@burgerga, #97) 130 | 131 | # pkgbuild 1.0.8 132 | 133 | * Fixes for capability RStudio 1.2. and Rtools 40, R 4.0.0 134 | 135 | # pkgbuild 1.0.7 136 | 137 | * Additional fixes for Rtools 40 138 | 139 | # pkgbuild 1.0.6 140 | 141 | * Support for RTools 40 and custom msys2 toolchains that are explicitly set 142 | using the `CC` Makevars (#40). 143 | 144 | # pkgbuild 1.0.5 145 | 146 | * `check_build_tools()` gains a `quiet` argument, to control when the message 147 | is displayed. The message is no longer displayed when `check_build_tools()` 148 | is called internally by pkgbuild functions. (#83) 149 | 150 | # pkgbuild 1.0.4 151 | 152 | * `build()` gains a `clean_doc` argument, to control if the `inst/doc` 153 | directory is cleaned before building. (#79, #75) 154 | 155 | * `build()` and `pkgbuild_process` now have standard output and error are 156 | correctly interleaved, by redirecting the standard error of build process 157 | to the standard output (@gaborcsardi, #78). 158 | 159 | * `check_build_tools()` now has a more helpful error message which points you 160 | towards ways to debug the issue (#68). 161 | 162 | * `pkgbuild_process` now do not set custom compiler flags, and it uses 163 | the user's `Makevars` file (@gaborcsardi, #76). 164 | 165 | * `rtools_path()` now returns `NA` on non-windows systems and also works when 166 | `has_rtools()` has not been run previously (#74). 167 | 168 | # pkgbuild 1.0.3 169 | 170 | * Tests which wrote to the package library are now skipped on CRAN. 171 | 172 | * `build()` can now build a tar.gz file directly (#55) 173 | 174 | # pkgbuild 1.0.2 175 | 176 | * `build()` and `compile_dll()` gain a `register_routines` argument, to 177 | automatically register C routines with 178 | `tools::package_native_routines_registration_skeleton()` (#50) 179 | 180 | * `build()` will now warn if trying to build packages on R versions <= 3.4.2 on 181 | Windows with a space in the R installation directory (#49) 182 | 183 | * `build()` will now message if a build contains long paths, which are unsupported on windows 184 | (#48) 185 | 186 | * `compile_dll()` no longer doubles output, a regression caused by the styling callback. 187 | (https://github.com/r-lib/devtools/issues/1877) 188 | 189 | * `build()` output is now styled like that in the rcmdcheck package 190 | (https://github.com/r-lib/devtools/issues/1874). 191 | 192 | * `build()` no longer sets compile flags (#46) 193 | 194 | # pkgbuild 1.0.1 195 | 196 | * Preliminary support for rtools 4.0 (#40) 197 | 198 | * `compile_dll()` now does not supply compiler flags if there is an existing 199 | user defined Makevars file. 200 | 201 | * `local_build_tools()` function added to provide a deferred equivalent to 202 | `with_build_tools()`. So you can add rtools to the PATH until the end of a 203 | function body. 204 | 205 | # pkgbuild 1.0.0 206 | 207 | * Add metadata to support Rtools 3.5 (#38). 208 | 209 | * `build()` only uses the `--no-resave-data` argument in `R CMD build` 210 | if the `--resave-data` argument wasn't supplied by the user 211 | (@theGreatWhiteShark, #26) 212 | 213 | * `build()` now cleans existing vignette files in `inst/doc` if they exist. (#10) 214 | 215 | * `clean_dll()` also deletes `symbols.rds` which is created when `compile_dll()` 216 | is run inside of `R CMD check`. 217 | 218 | * First argument of all functions is now `path` rather than `pkg`. 219 | -------------------------------------------------------------------------------- /R/build-bg.R: -------------------------------------------------------------------------------- 1 | #' Build package in the background 2 | #' 3 | #' This R6 class is a counterpart of the [build()] function, and 4 | #' represents a background process that builds an R package. 5 | #' 6 | #' @section Usage: 7 | #' ``` 8 | #' bp <- pkgbuild_process$new(path = ".", dest_path = NULL, 9 | #' binary = FALSE, vignettes = TRUE, manual = FALSE, args = NULL) 10 | #' bp$get_dest_path() 11 | #' ``` 12 | #' 13 | #' Other methods are inherited from [callr::rcmd_process] and 14 | #' `processx::process`. 15 | #' 16 | #' @section Arguments: 17 | #' See the corresponding arguments of [build()]. 18 | #' 19 | #' @section Details: 20 | #' Most methods are inherited from [callr::rcmd_process] and 21 | #' `processx::process`. 22 | #' 23 | #' `bp$get_dest_path()` returns the path to the built package. 24 | #' 25 | #' @section Examples: 26 | #' ``` 27 | #' ## Here we are just waiting, but in a more realistic example, you 28 | #' ## would probably run some other code instead... 29 | #' bp <- pkgbuild_process$new("mypackage", dest_path = tempdir()) 30 | #' bp$is_alive() 31 | #' bp$get_pid() 32 | #' bp$wait() 33 | #' bp$read_all_output_lines() 34 | #' bp$read_all_error_lines() 35 | #' bp$get_exit_status() 36 | #' bp$get_dest_path() 37 | #' ``` 38 | #' 39 | #' @importFrom R6 R6Class 40 | #' @name pkgbuild_process 41 | NULL 42 | 43 | #' @export 44 | 45 | pkgbuild_process <- R6Class( 46 | "pkgbuild_process", 47 | inherit = callr::rcmd_process, 48 | public = list( 49 | initialize = function( 50 | path = ".", 51 | dest_path = NULL, 52 | binary = FALSE, 53 | vignettes = TRUE, 54 | manual = FALSE, 55 | clean_doc = NULL, 56 | args = NULL, 57 | needs_compilation = pkg_has_src(path), 58 | compile_attributes = FALSE, 59 | register_routines = FALSE, 60 | quiet = FALSE 61 | ) { 62 | rcb_init( 63 | self, 64 | private, 65 | super, 66 | path, 67 | dest_path, 68 | binary, 69 | vignettes, 70 | manual, 71 | clean_doc, 72 | args, 73 | needs_compilation, 74 | compile_attributes, 75 | register_routines, 76 | quiet 77 | ) 78 | }, 79 | is_incomplete_error = function() FALSE, 80 | read_all_error = function() "", 81 | read_all_error_lines = function() character(), 82 | read_error = function(n = -1) "", 83 | read_error_lines = function(n = -1) character(), 84 | get_dest_path = function() private$dest_path, 85 | get_built_file = function() { 86 | if (self$is_alive()) stop("Still alive") 87 | if (self$get_exit_status() != 0) stop("Build process failed") 88 | 89 | ## Already copied? 90 | if (!is.null(private$out_file)) { 91 | return(private$out_file) 92 | } 93 | 94 | ## No, copy, and remove temp dir, order is important here! 95 | file_name <- dir(private$out_dir) 96 | tmp_file <- file.path(private$out_dir, file_name) 97 | file.copy(tmp_file, private$dest_path, overwrite = TRUE) 98 | private$out_file <- file.path(private$dest_path, file_name) 99 | unlink(private$out_dir, recursive = TRUE) 100 | private$out_file 101 | }, 102 | kill = function(...) { 103 | unlink(private$makevars_file) 104 | super$kill(...) 105 | } 106 | ), 107 | private = list( 108 | finalize = function() { 109 | unlink(private$makevars_file) 110 | super$kill() 111 | }, 112 | path = NULL, 113 | dest_path = NULL, 114 | out_dir = NULL, 115 | out_file = NULL, 116 | makevars_file = NULL 117 | ) 118 | ) 119 | 120 | rcb_init <- function( 121 | self, 122 | private, 123 | super, 124 | path, 125 | dest_path, 126 | binary, 127 | vignettes, 128 | manual, 129 | clean_doc, 130 | args, 131 | needs_compilation, 132 | compile_attributes, 133 | register_routines, 134 | quiet 135 | ) { 136 | options <- build_setup( 137 | path, 138 | dest_path, 139 | binary, 140 | vignettes, 141 | manual, 142 | clean_doc, 143 | args, 144 | needs_compilation, 145 | compile_attributes, 146 | register_routines, 147 | quiet 148 | ) 149 | 150 | private$path <- options$path 151 | private$dest_path <- options$dest_path 152 | private$out_dir <- options$out_dir 153 | private$makevars_file <- tempfile() 154 | 155 | ## Build tools already checked in setup 156 | 157 | withr_set_makevars( 158 | compiler_flags(debug = FALSE), 159 | new_path = private$makevars_file 160 | ) 161 | withr_with_envvar( 162 | c("R_MAKEVARS_USER" = private$makevars_file), 163 | { 164 | options <- callr::rcmd_process_options( 165 | cmd = options$cmd, 166 | cmdargs = c(options$path, options$args), 167 | wd = options$out_dir, 168 | stderr = "2>&1" 169 | ) 170 | 171 | super$initialize(options) 172 | 173 | invisible(self) 174 | } 175 | ) 176 | } 177 | -------------------------------------------------------------------------------- /R/build-tools.R: -------------------------------------------------------------------------------- 1 | #' Are build tools are available? 2 | #' 3 | #' `has_build_tools` returns a logical, `check_build_tools` throws 4 | #' an error. `with_build_tools` checks that build tools are available, 5 | #' then runs `code` in an correctly staged environment. 6 | #' If run interactively from RStudio, and the build tools are not 7 | #' available these functions will trigger an automated install. 8 | #' 9 | #' Errors like `running command 10 | #' '"C:/PROGRA~1/R/R-34~1.2/bin/x64/R" CMD config CC' had status 127` 11 | #' indicate the code expected Rtools to be on the system PATH. You can 12 | #' then verify you have rtools installed with `has_build_tools()` and 13 | #' temporarily add Rtools to the PATH `with_build_tools({ code })`. 14 | #' 15 | #' It is possible to add Rtools to your system PATH manually; you can use 16 | #' [rtools_path()] to show the installed location. However because this 17 | #' requires manual updating when a new version of Rtools is installed and the 18 | #' binaries in Rtools may conflict with existing binaries elsewhere on the PATH it 19 | #' is better practice to use `with_build_tools()` as needed. 20 | #' @inheritParams has_rtools 21 | #' @param quiet if `TRUE` suppresses output from this function. 22 | #' @export 23 | #' @seealso has_rtools 24 | #' @examples 25 | #' has_build_tools(debug = TRUE) 26 | #' check_build_tools() 27 | has_build_tools <- function(debug = FALSE) { 28 | check <- getOption("buildtools.check", NULL) 29 | # we do this from R 4.3.0, because some people might still use 30 | # Rtools40 for R 4.2.x, and we don't want to break their config 31 | has <- if (is_windows() && getRversion() >= "4.3.0") { 32 | has_compiler(debug = debug) 33 | } else if (is_windows()) { 34 | has_rtools(debug = debug) 35 | } else { 36 | has_compiler(debug = debug) 37 | } 38 | 39 | if (!has && !is.null(check)) { 40 | return(check("Building R package from source")) 41 | } 42 | 43 | if (!has && is_windows() && getRversion() >= "4.3.0") { 44 | message( 45 | "WARNING: Rtools is required to build R packages, but is not ", 46 | "currently installed.\n\n", 47 | "Please download and install the appropriate version of Rtools for ", 48 | getRversion(), 49 | " from\n", 50 | rtools_url(), 51 | "." 52 | ) 53 | } 54 | 55 | has 56 | } 57 | 58 | #' @export 59 | #' @rdname has_build_tools 60 | check_build_tools <- function(debug = FALSE, quiet = FALSE) { 61 | if (!has_build_tools(debug = debug)) { 62 | stop( 63 | "Could not find tools necessary to compile a package\n", 64 | "Call `pkgbuild::check_build_tools(debug = TRUE)` to diagnose the problem.", 65 | call. = FALSE 66 | ) 67 | } else if (!isTRUE(quiet)) { 68 | message("Your system is ready to build packages!") 69 | } 70 | 71 | invisible(TRUE) 72 | } 73 | 74 | #' @export 75 | #' @rdname has_build_tools 76 | #' @param code Code to rerun in environment where build tools are guaranteed to 77 | #' exist. 78 | #' @param required If `TRUE`, and build tools are not available, 79 | #' will throw an error. Otherwise will attempt to run `code` without 80 | #' them. 81 | with_build_tools <- function(code, debug = FALSE, required = TRUE) { 82 | if (required) { 83 | check_build_tools(debug = debug, quiet = TRUE) 84 | } 85 | 86 | if (has_rtools()) { 87 | withr_with_path(rtools_path(), code) 88 | } else { 89 | code 90 | } 91 | } 92 | 93 | #' @rdname has_build_tools 94 | #' @param .local_envir The environment to use for scoping. 95 | #' @export 96 | local_build_tools <- function( 97 | debug = FALSE, 98 | required = TRUE, 99 | .local_envir = parent.frame() 100 | ) { 101 | if (required) { 102 | check_build_tools(debug = debug, quiet = TRUE) 103 | } 104 | 105 | if (has_rtools()) { 106 | withr_local_path(rtools_path(), .local_envir = .local_envir) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /R/build.R: -------------------------------------------------------------------------------- 1 | #' Build package 2 | #' 3 | #' Building converts a package source directory into a single bundled file. 4 | #' If `binary = FALSE` this creates a `tar.gz` package that can 5 | #' be installed on any platform, provided they have a full development 6 | #' environment (although packages without source code can typically be 7 | #' installed out of the box). If `binary = TRUE`, the package will have 8 | #' a platform specific extension (e.g. `.zip` for windows), and will 9 | #' only be installable on the current platform, but no development 10 | #' environment is needed. 11 | #' 12 | #' ## Configuration 13 | #' 14 | #' ### `DESCRIPTION` entries 15 | #' 16 | #' * `Config/build/clean-inst-doc` can be set to `FALSE` to avoid cleaning up 17 | #' `inst/doc` when building a source package. Set it to `TRUE` to force a 18 | #' cleanup. See the `clean_doc` argument. 19 | #' 20 | #' * `Config/build/copy-method` can be used to avoid copying large 21 | #' directories in `R CMD build`. It works by copying (or linking) the 22 | #' files of the package to a temporary directory, leaving out the 23 | #' (possibly large) files that are not part of the package. Possible 24 | #' values: 25 | #' 26 | #' - `none`: pkgbuild does not copy the package tree. This is the default. 27 | #' - `copy`: the package files are copied to a temporary directory before 28 | #' ` R CMD build`. 29 | #' - `link`: the package files are symbolic linked to a temporary 30 | #' directory before `R CMD build`. Windows does not have symbolic 31 | #' links, so on Windows this is equivalent to `copy`. 32 | #' 33 | #' You can also use the `pkg.build_copy_method` option or the 34 | #' `PKG_BUILD_COPY_METHOD` environment variable to set the copy method. 35 | #' The option is consulted first, then the `DESCRIPTION` entry, then the 36 | #' environment variable. 37 | #' 38 | #' * `Config/build/extra-sources` can be used to define extra source files 39 | #' for pkgbuild to decide whether a package DLL needs to be recompiled in 40 | #' `needs_compile()`. The syntax is a comma separated list of file names, 41 | #' or globs. (See [utils::glob2rx()].) E.g. `src/rust/src/*.rs` or 42 | #' `configure*`. 43 | #' 44 | #' * `Config/build/bootstrap` can be set to `TRUE` to run 45 | #' `Rscript bootstrap.R` in the source directory prior to running subsequent 46 | #' build steps. 47 | #' 48 | #' * `Config/build/never-clean` can be set to `TRUE` to never add `--preclean` 49 | #' to `R CMD INSTALL`, e.g., when header files have changed. 50 | #' This helps avoiding rebuilds that can take long for very large C/C++ codebases 51 | #' and can lead to build failures if object files are out of sync with header files. 52 | #' Control the dependencies between object files and header files 53 | #' by adding `include file.d` to `Makevars` for each `file.c` or `file.cpp` source file. 54 | #' 55 | #' ### Options 56 | #' 57 | #' * `pkg.build_copy_method`: use this option to avoid copying large 58 | #' directories when building a package. See possible values above, at the 59 | #' `Config/build/copy-method` `DESCRIPTION` entry. 60 | #' 61 | #' * `pkg.build_stop_for_warnings`: if it is set to `TRUE`, then pkgbuild 62 | #' will stop for `R CMD build` errors. It takes precedence over the 63 | #' `PKG_BUILD_STOP_FOR_WARNINGS` environment variable. 64 | #' 65 | #' ### Environment variables 66 | #' 67 | #' * `PKG_BUILD_COLOR_DIAGNOSTICS`: set it to `false` to opt out of colored 68 | #' compiler diagnostics. Set it to `true` to force colored compiler 69 | #' diagnostics. 70 | #' 71 | #' * `PKG_BUILD_COPY_METHOD`: use this environment variable to avoid copying 72 | #' large directories when building a package. See possible values above, 73 | #' at the `Config/build/copy-method` `DESCRIPTION` entry. 74 | #' 75 | #' will stop for `R CMD build` errors. The `pkg.build_stop_for_warnings` 76 | #' option takes precedence over this environment variable. 77 | #' 78 | #' @param path Path to a package, or within a package. 79 | #' @param dest_path path in which to produce package. If it is an existing 80 | #' directory, then the output file is placed in `dest_path` and named 81 | #' according to the current R conversions (e.g. `.zip` for Windows binary 82 | #' packages, `.tgz` for macOS binary packages, etc). 83 | #' If it is an existing file, then it will be overwritten. 84 | #' If `dest_path` does not exist, then it is used as a file name. 85 | #' If `NULL`, it defaults to the parent directory of the package. 86 | #' @param binary Produce a binary (`--binary`) or source ( 87 | #' `--no-manual --no-resave-data`) version of the package. 88 | #' @param vignettes,manual For source packages: if `FALSE`, don't build PDF 89 | #' vignettes (`--no-build-vignettes`) or manual (`--no-manual`). 90 | #' @param args An optional character vector of additional command 91 | #' line arguments to be passed to `R CMD build` if `binary = FALSE`, 92 | #' or `R CMD install` if `binary = TRUE`. 93 | #' @param quiet if `TRUE` suppresses output from this function. 94 | #' @param needs_compilation Usually only needed if the packages has 95 | #' C/C++/Fortran code. By default this is autodetected. 96 | #' @param compile_attributes if `TRUE` and the package uses Rcpp, call 97 | #' [Rcpp::compileAttributes()] before building the package. It is ignored 98 | #' if package does not need compilation. 99 | #' @param register_routines if `TRUE` and the package does not use Rcpp, call 100 | #' register routines with 101 | #' `tools::package_native_routine_registration_skeleton()` before building 102 | #' the package. It is ignored if package does not need compilation. 103 | #' @param clean_doc If `TRUE`, clean the files in `inst/doc` before building 104 | #' the package. If `NULL` and the `Config/build/clean-inst-doc` entry is 105 | #' present in `DESCRIPTION`, then that is used. Otherwise, if `NULL`, 106 | #' and interactive, ask to remove the files prior to cleaning. In most 107 | #' cases cleaning the files is the correct behavior to avoid stale 108 | #' vignette outputs in the built package. 109 | #' @export 110 | #' @return a string giving the location (including file name) of the built 111 | #' package 112 | build <- function( 113 | path = ".", 114 | dest_path = NULL, 115 | binary = FALSE, 116 | vignettes = TRUE, 117 | manual = FALSE, 118 | clean_doc = NULL, 119 | args = NULL, 120 | quiet = FALSE, 121 | needs_compilation = pkg_has_src(path), 122 | compile_attributes = FALSE, 123 | register_routines = FALSE 124 | ) { 125 | options <- build_setup( 126 | path, 127 | dest_path, 128 | binary, 129 | vignettes, 130 | manual, 131 | clean_doc, 132 | args, 133 | needs_compilation, 134 | compile_attributes, 135 | register_routines, 136 | quiet 137 | ) 138 | on.exit(unlink(options$out_dir, recursive = TRUE), add = TRUE) 139 | 140 | withr_with_makevars( 141 | compiler_flags(debug = FALSE), 142 | { 143 | output <- withr_with_temp_libpaths( 144 | rcmd_build_tools( 145 | options$cmd, 146 | c(options$path, options$args), 147 | wd = options$out_dir, 148 | fail_on_status = TRUE, 149 | required = FALSE, # already checked in setup 150 | quiet = quiet 151 | ) 152 | ) 153 | 154 | if ( 155 | should_stop_for_warnings() && 156 | grepl("\n\\s*warning:", output$stdout, ignore.case = TRUE) 157 | ) { 158 | cli::cli_alert_warning( 159 | "Stopping as requested for a warning during {.code R CMD build}." 160 | ) 161 | if (quiet) { 162 | cli::cli_alert_warning("The full output is printed below.") 163 | cli::cli_verbatim(output$stdout) 164 | } 165 | stop("converted from `R CMD build` warning.") 166 | } 167 | 168 | out_file <- dir(options$out_dir) 169 | file.copy( 170 | file.path(options$out_dir, out_file), 171 | options$dest_path, 172 | overwrite = TRUE 173 | ) 174 | 175 | if (is_dir(options$dest_path)) { 176 | file.path(options$dest_path, out_file) 177 | } else { 178 | options$dest_path 179 | } 180 | } 181 | ) 182 | } 183 | 184 | build_setup <- function( 185 | path, 186 | dest_path, 187 | binary, 188 | vignettes, 189 | manual, 190 | clean_doc, 191 | args, 192 | needs_compilation, 193 | compile_attributes, 194 | register_routines, 195 | quiet 196 | ) { 197 | if (!file.exists(path)) { 198 | stop("`path` must exist", call. = FALSE) 199 | } 200 | if (!is_dir(path)) { 201 | if (!binary) stop("`binary` must be TRUE for package files", call. = FALSE) 202 | if (compile_attributes) { 203 | stop( 204 | "`compile_attributes` must be FALSE for package files", 205 | call. = FALSE 206 | ) 207 | } 208 | if (register_routines) { 209 | stop("`register_routines` must be FALSE for package files", call. = FALSE) 210 | } 211 | } else { 212 | path <- pkg_path(path) 213 | } 214 | 215 | if (is.null(dest_path)) { 216 | dest_path <- dirname(path) 217 | } 218 | 219 | if (binary) { 220 | build_setup_binary(path, dest_path, args, needs_compilation) 221 | } else { 222 | build_setup_source( 223 | path, 224 | dest_path, 225 | vignettes, 226 | manual, 227 | clean_doc, 228 | args, 229 | needs_compilation, 230 | compile_attributes, 231 | register_routines, 232 | quiet 233 | ) 234 | } 235 | } 236 | 237 | build_setup_binary <- function(path, dest_path, args, needs_compilation) { 238 | if (needs_compilation) { 239 | check_build_tools(quiet = TRUE) 240 | } 241 | 242 | # Build in temporary directory and then copy to final location 243 | out_dir <- tempfile() 244 | dir.create(out_dir) 245 | 246 | list( 247 | cmd = "INSTALL", 248 | path = normalizePath(path), 249 | args = c("--build", args), 250 | out_dir = out_dir, 251 | dest_path = dest_path 252 | ) 253 | } 254 | 255 | build_setup_source <- function( 256 | path, 257 | dest_path, 258 | vignettes, 259 | manual, 260 | clean_doc, 261 | args, 262 | needs_compilation, 263 | compile_attributes, 264 | register_routines, 265 | quiet 266 | ) { 267 | bootstrap_file <- file.path(path, "bootstrap.R") 268 | run_bootstrap <- isTRUE(get_desc_config_flag(path, "bootstrap")) 269 | if (file.exists(bootstrap_file) && run_bootstrap) { 270 | if (!quiet) message("Running bootstrap.R...") 271 | 272 | callr::rscript( 273 | bootstrap_file, 274 | wd = path, 275 | stderr = "2>&1", 276 | show = !quiet 277 | ) 278 | } 279 | 280 | if (needs_compilation) { 281 | update_registration(path, compile_attributes, register_routines, quiet) 282 | } 283 | 284 | if (!("--resave-data" %in% args)) { 285 | args <- c(args, "--no-resave-data") 286 | } 287 | 288 | if (!manual) { 289 | args <- unique(c(args, "--no-manual")) 290 | } 291 | 292 | if (!vignettes) { 293 | args <- unique(c(args, "--no-build-vignettes")) 294 | } 295 | 296 | no_manual <- "--no-manual" %in% args 297 | if (!no_manual && !has_latex()) { 298 | message("pdflatex not found! Not building PDF manual.") 299 | manual <- FALSE 300 | } 301 | 302 | if (needs_compilation && (vignettes || manual)) { 303 | check_build_tools(quiet = TRUE) 304 | } 305 | 306 | build_vignettes <- !("--no-build-vignettes" %in% args) 307 | if (is.null(clean_doc)) { 308 | clean_doc <- get_desc_config_flag(path, "clean-inst-doc") 309 | } 310 | if (build_vignettes && (is.null(clean_doc) || isTRUE(clean_doc))) { 311 | doc_dir <- file.path(path, "inst", "doc") 312 | if (dir.exists(doc_dir)) { 313 | if (is.null(clean_doc) && interactive()) { 314 | message( 315 | "Building the package will delete...\n '", 316 | doc_dir, 317 | "'\nAre you sure?" 318 | ) 319 | res <- utils::menu(c("Yes", "No")) 320 | if (res == 2) { 321 | return() 322 | } 323 | } 324 | unlink(doc_dir, recursive = TRUE) 325 | } 326 | } 327 | 328 | # Build in temporary directory and then copy to final location 329 | out_dir <- tempfile() 330 | dir.create(out_dir) 331 | 332 | copy_method <- get_copy_method(path) 333 | 334 | if (copy_method != "none") { 335 | pkgname <- desc::desc_get("Package", path) 336 | tmppath <- tempfile("build-") 337 | copy_package_tree(path, tmppath, pkgname) 338 | path <- file.path(tmppath, pkgname) 339 | } 340 | 341 | list( 342 | cmd = "build", 343 | path = normalizePath(path), 344 | args = args, 345 | out_dir = out_dir, 346 | dest_path = dest_path 347 | ) 348 | } 349 | -------------------------------------------------------------------------------- /R/c-registration.R: -------------------------------------------------------------------------------- 1 | update_registration <- function( 2 | path, 3 | compile_attributes, 4 | register_routines, 5 | quiet 6 | ) { 7 | if (compile_attributes) { 8 | if (pkg_links_to_cpp11(path)) { 9 | cpp11::cpp_register(path, quiet = quiet) 10 | } else if (pkg_links_to_rcpp(path)) { 11 | unlink(file.path(path, c("R/RcppExports.R", "src/RcppExports.cpp"))) 12 | Rcpp::compileAttributes(path, verbose = !quiet) 13 | } 14 | } else if (register_routines) { 15 | update_c_registration(path) 16 | check_namespace_registration(path) 17 | } 18 | } 19 | 20 | update_c_registration <- function(path) { 21 | path <- pkg_path(path) 22 | 23 | pkgbuild_init_file <- file.path(path, "src", "init.c") 24 | 25 | should_update <- !file.exists(pkgbuild_init_file) || 26 | any(grepl("generated by pkgbuild", readLines(pkgbuild_init_file))) 27 | 28 | if (!should_update) { 29 | return(invisible(character())) 30 | } 31 | 32 | # package_native_routine_registration_skeleton is not available before R 3.4 33 | if (getRversion() < "3.4.0") { 34 | return(invisible(character())) 35 | } 36 | 37 | con <- textConnection(NULL, "w") 38 | tools::package_native_routine_registration_skeleton( 39 | path, 40 | con = con, 41 | character_only = FALSE 42 | ) 43 | lines <- textConnectionValue(con) 44 | close(con) 45 | 46 | if (length(lines) == 0) { 47 | return(invisible(lines)) 48 | } 49 | 50 | if (!file.exists(pkgbuild_init_file)) { 51 | lines <- remove_fixme(lines) 52 | } else { 53 | current_lines <- readLines(pkgbuild_init_file) 54 | 55 | current_range <- pkgbuild_generated_section(current_lines) 56 | 57 | new_range <- tools_generated_section(lines) 58 | 59 | lines <- c( 60 | current_lines[seq(1, min(current_range) - 1)], 61 | lines[new_range], 62 | current_lines[seq(max(current_range) + 1, length(current_lines))] 63 | ) 64 | } 65 | 66 | lines <- add_generation_message(lines) 67 | writeLines(lines, pkgbuild_init_file) 68 | 69 | invisible(lines) 70 | } 71 | 72 | remove_fixme <- function(lines) { 73 | fixme_loc <- grep("/* FIXME: ", lines, fixed = TRUE) 74 | lines <- lines[-seq(fixme_loc, fixme_loc + 2)] 75 | 76 | lines 77 | } 78 | 79 | tools_generated_section <- function(lines) { 80 | start_loc <- grep("/* .Call calls */", lines, fixed = TRUE) 81 | end_loc <- grep("};", lines, fixed = TRUE) 82 | 83 | seq(start_loc, end_loc) 84 | } 85 | 86 | pkgbuild_generated_section <- function(lines) { 87 | start_loc <- grep( 88 | "/* Section generated by pkgbuild, do not edit */", 89 | lines, 90 | fixed = TRUE 91 | ) 92 | end_loc <- grep( 93 | "/* End section generated by pkgbuild */", 94 | lines, 95 | fixed = TRUE 96 | ) 97 | 98 | seq(start_loc, end_loc) 99 | } 100 | 101 | add_generation_message <- function(lines) { 102 | start_loc <- grep("/* .Call calls */", lines, fixed = TRUE) 103 | end_loc <- grep("};", lines, fixed = TRUE) 104 | 105 | if (end_loc <= start_loc) { 106 | stop("Malformed init.c format") 107 | } 108 | 109 | lines <- append( 110 | lines, 111 | "/* Section generated by pkgbuild, do not edit */", 112 | after = start_loc - 1 113 | ) 114 | 115 | lines <- append( 116 | lines, 117 | "/* End section generated by pkgbuild */", 118 | after = end_loc + 1 119 | ) 120 | 121 | lines 122 | } 123 | 124 | check_namespace_registration <- function(path) { 125 | path <- pkg_path(path) 126 | 127 | namespace_file <- file.path(path, "NAMESPACE") 128 | 129 | if (!file.exists(namespace_file)) { 130 | warning("NAMESPACE file missing", immediate. = TRUE) 131 | } 132 | 133 | pkg_namespace <- readLines(namespace_file, warn = FALSE) 134 | has_registration <- any(grepl( 135 | "^[[:space:]]*useDynLib.*[.]registration[[:space:]]*=[[:space:]]*TRUE", 136 | pkg_namespace 137 | )) 138 | 139 | if (!has_registration) { 140 | warning( 141 | immediate. = TRUE, 142 | call. = FALSE, 143 | sprintf( 144 | "NAMESPACE missing native routine registration: 145 | * Add `#' @useDynLib %s, .registration = TRUE` to R files. 146 | * Run `devtools::document()`", 147 | pkg_name(path) 148 | ) 149 | ) 150 | } 151 | } 152 | 153 | #' Test if a package path is linking to Rcpp or cpp11 154 | #' 155 | #' @inheritParams build 156 | #' @export 157 | #' @keywords internal 158 | pkg_links_to_rcpp <- function(path) { 159 | path <- pkg_path(path) 160 | 161 | deps <- desc::desc_get_deps(file.path(path, "DESCRIPTION")) 162 | 163 | any(deps$type == "LinkingTo" & deps$package == "Rcpp") 164 | } 165 | 166 | #' @rdname pkg_links_to_rcpp 167 | #' @keywords internal 168 | #' @export 169 | pkg_links_to_cpp11 <- function(path) { 170 | path <- pkg_path(path) 171 | 172 | desc <- desc::desc(file = file.path(path, "DESCRIPTION")) 173 | deps <- desc$get_deps() 174 | 175 | desc$get_field("Package") == "cpp11" || 176 | any(deps$type == "LinkingTo" & deps$package == "cpp11") 177 | } 178 | -------------------------------------------------------------------------------- /R/cache.R: -------------------------------------------------------------------------------- 1 | # Need to check for existence so load_all doesn't override known rtools location 2 | if (!exists("cache")) { 3 | cache <- new.env(parent = emptyenv()) 4 | } 5 | 6 | cache_get <- function(name) { 7 | get(name, envir = cache) 8 | } 9 | 10 | cache_exists <- function(name) { 11 | exists(name, envir = cache) 12 | } 13 | 14 | cache_set <- function(name, value) { 15 | assign(name, value, envir = cache) 16 | } 17 | 18 | cache_remove <- function(name) { 19 | rm(list = name, envir = cache) 20 | } 21 | 22 | 23 | cache_reset <- function() { 24 | rm(list = ls(envir = cache), envir = cache) 25 | } 26 | -------------------------------------------------------------------------------- /R/callback.R: -------------------------------------------------------------------------------- 1 | #' @importFrom utils head tail 2 | 3 | # This is adapted from https://github.com/r-lib/rcmdcheck/blob/7ee14764c2b17ee2c2f4131a9e19d1b56a66ed0f/R/callback.R 4 | block_callback <- function(quiet) { 5 | partial_line <- "" 6 | 7 | state <- "OK" 8 | should_time <- FALSE 9 | line_started <- Sys.time() 10 | now <- NULL 11 | prev_line <- "" 12 | 13 | no <- function(x, what = "") { 14 | pattern <- paste0(" \\.\\.\\. ", what, "$") 15 | sub("^\\* ", "", sub(pattern, "", x)) 16 | } 17 | 18 | time_if_long <- function() { 19 | elapsed <- now - line_started 20 | if (elapsed > as.difftime(1 / 3, units = "secs")) { 21 | style(timing = paste0(" (", format_time$pretty_dt(elapsed), ")")) 22 | } else { 23 | "" 24 | } 25 | } 26 | 27 | do_line <- function(x) { 28 | should_time <<- FALSE 29 | now <<- Sys.time() 30 | 31 | ## Test mode is special. It will change the 'state' back to 'OK', 32 | ## once it is done. 33 | xx <- if (is_new_check(x)) { 34 | do_new_check(x) 35 | } else if (grepl("^Status: ", x)) { 36 | ## We just skip the status, it is printed out anyway, as the return 37 | ## value 38 | NA_character_ 39 | } else { 40 | do_continuation(x) 41 | } 42 | 43 | prev_line <<- x 44 | 45 | ## NA_character_ can omit output 46 | if (is.na(xx)) { 47 | return() 48 | } 49 | 50 | if (should_time) xx <- style(xx, timing = time_if_long()) 51 | 52 | line_started <<- now 53 | 54 | cat(xx, "\n", sep = "") 55 | flush(stdout()) 56 | } 57 | 58 | do_new_check <- function(x) { 59 | should_time <<- TRUE 60 | if (grepl(" \\.\\.\\. OK\\s*$", x)) { 61 | state <<- "OK" 62 | style(ok = cli::symbol$tick, " ", pale = no(x, "OK")) 63 | } else if (grepl(" \\.\\.\\. NOTE\\s*$", x)) { 64 | state <<- "NOTE" 65 | style(note = c("N ", no(x, "NOTE"))) 66 | } else if (grepl(" \\.\\.\\. WARNING\\s*$", x)) { 67 | state <<- "WARNING" 68 | style(warn = c("W ", no(x, "WARNING"))) 69 | } else if (grepl(" \\.\\.\\. ERROR\\s*$", x)) { 70 | state <<- "ERROR" 71 | style(err = c("E ", no(x, "ERROR"))) 72 | } else if (grepl("^\\* checking tests \\.\\.\\.[ ]?$", x)) { 73 | state <<- "tests" 74 | style(pale = c(cli::symbol$line, " ", no(x))) 75 | } else if (grepl("^\\* DONE\\s*$", x)) { 76 | state <<- "OK" 77 | NA_character_ 78 | } else { 79 | style(pale = c(cli::symbol$line, " ", no(x))) 80 | } 81 | } 82 | 83 | do_continuation <- function(x) { 84 | paste0(" ", x) 85 | } 86 | 87 | function(x) { 88 | if (quiet) { 89 | return() 90 | } 91 | 92 | x <- paste0(partial_line, x) 93 | partial_line <<- "" 94 | lines <- strsplit(x, "\r?\n")[[1]] 95 | if (last_char(x) != "\n") { 96 | partial_line <<- tail(lines, 1) 97 | lines <- head(lines, -1) 98 | } 99 | cat(" \r") 100 | lapply(lines, do_line) 101 | cat0(sub("^[\\* ]", " ", partial_line), "\r") 102 | } 103 | } 104 | 105 | is_new_check <- function(x) { 106 | grepl("^\\* ", x) 107 | } 108 | 109 | simple_callback <- function(quiet) { 110 | function(x) { 111 | if (quiet) { 112 | return() 113 | } 114 | cat(x) 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /R/compile-dll.R: -------------------------------------------------------------------------------- 1 | #' Compile a .dll/.so from source. 2 | #' 3 | #' `compile_dll` performs a fake R CMD install so code that 4 | #' works here should work with a regular install (and vice versa). 5 | #' During compilation, debug flags are set with 6 | #' \code{\link{compiler_flags}(TRUE)}. 7 | #' 8 | #' Invisibly returns the names of the DLL. 9 | #' 10 | #' ## Configuration 11 | #' 12 | #' ### Options 13 | #' 14 | #' * `pkg.build_extra_flags`: set this to `FALSE` to to opt out from adding 15 | #' debug compiler flags in `compile_dll()`. Takes precedence over the 16 | #' `PKG_BUILD_EXTRA_FLAGS` environment variable. Possible values: 17 | #' 18 | #' - `TRUE`: add extra flags, 19 | #' - `FALSE`: do not add extra flags, 20 | #' - `"missing"`: add extra flags if the user does not have a 21 | #' `$HOME/.R/Makevars` file. 22 | #' 23 | #' ### Environment variables 24 | #' 25 | #' * `PKG_BUILD_EXTRA_FLAGS`: set this to `false` to to opt out from adding 26 | #' debug compiler flags in `compile_dll()`. The `pkg.build_extra_flags` option 27 | #' takes precedence over this environment variable. Possible values: 28 | #' 29 | #' - `"true"`: add extra flags, 30 | #' - `"false"`: do not add extra flags, 31 | #' - `"missing"`: add extra flags if the user does not have a 32 | #' `$HOME/.R/Makevars` file. 33 | #' 34 | #' @note If this is used to compile code that uses Rcpp, you will need to 35 | #' add the following line to your `Makevars` file so that it 36 | #' knows where to find the Rcpp headers: 37 | #' ``` 38 | #' PKG_CPPFLAGS=`$(R_HOME)/bin/Rscript -e 'Rcpp:::CxxFlags()'` 39 | #' ``` 40 | #' 41 | #' @inheritParams build 42 | #' @param force If `TRUE`, for compilation even if [needs_compile()] is 43 | #' `FALSE`. 44 | #' @param debug If `TRUE`, and if no user Makevars is found, then the build 45 | #' runs without optimisation (`-O0`) and with debug symbols (`-g`). See 46 | #' [compiler_flags()] for details. If you have a user Makevars (e.g., 47 | #' `~/.R/Makevars`) then this argument is ignored. 48 | #' @seealso [clean_dll()] to delete the compiled files. 49 | #' @export 50 | compile_dll <- function( 51 | path = ".", 52 | force = FALSE, 53 | compile_attributes = pkg_links_to_cpp11(path) || pkg_links_to_rcpp(path), 54 | register_routines = FALSE, 55 | quiet = FALSE, 56 | debug = TRUE 57 | ) { 58 | path <- pkg_path(path) 59 | 60 | if (!needs_compile(path) && !isTRUE(force)) { 61 | return(invisible()) 62 | } 63 | 64 | check_build_tools(quiet = TRUE) 65 | update_registration(path, compile_attributes, register_routines, quiet) 66 | 67 | # Mock install the package to generate the DLL 68 | xflags <- should_add_compiler_flags() 69 | if (!quiet) { 70 | cli::cli_alert_info(c( 71 | "Re-compiling {.pkg {pkg_name(path)}}", 72 | if (debug && xflags) " (debug build)" 73 | )) 74 | } 75 | 76 | install_dir <- tempfile("devtools_install_") 77 | dir.create(install_dir) 78 | 79 | build <- function() { 80 | install_min( 81 | path, 82 | dest = install_dir, 83 | components = "libs", 84 | args = if (needs_clean(path)) "--preclean", 85 | quiet = quiet 86 | ) 87 | 88 | invisible(dll_path(file.path(install_dir, pkg_name(path)))) 89 | } 90 | 91 | if (xflags) { 92 | withr_with_makevars(compiler_flags(debug), { 93 | if (debug) { 94 | withr_with_envvar(c(DEBUG = "true"), build()) 95 | } else { 96 | build() 97 | } 98 | }) 99 | } else { 100 | build() 101 | } 102 | } 103 | 104 | #' Remove compiled objects from /src/ directory 105 | #' 106 | #' Invisibly returns the names of the deleted files. 107 | #' 108 | #' @inheritParams build 109 | #' @seealso [compile_dll()] 110 | #' @export 111 | clean_dll <- function(path = ".") { 112 | path <- pkg_path(path) 113 | 114 | # Clean out the /src/ directory and children: 115 | # * individual object files 116 | # * overall package definition file 117 | # * symbols.rds (added when run inside R CMD check) 118 | pattern <- sprintf( 119 | "\\.(o|sl|so|dylib|a|dll)$|(%s\\.def)$|^symbols.rds$", 120 | pkg_name(path) 121 | ) 122 | files <- dir( 123 | file.path(path, "src"), 124 | pattern = pattern, 125 | full.names = TRUE, 126 | recursive = TRUE 127 | ) 128 | unlink(files) 129 | 130 | invisible(files) 131 | } 132 | 133 | # Returns the full path and name of the DLL file 134 | dll_path <- function(path = ".") { 135 | name <- paste(pkg_name(path), .Platform$dynlib.ext, sep = "") 136 | file.path(path, "src", name) 137 | } 138 | 139 | mtime <- function(x) { 140 | x <- x[file.exists(x)] 141 | if (length(x) == 0) { 142 | return(NULL) 143 | } 144 | max(file.info(x)$mtime) 145 | } 146 | 147 | globs <- function(path = ".", x) { 148 | old <- getwd() 149 | on.exit(setwd(old), add = TRUE) 150 | setwd(path) 151 | Sys.glob(x) 152 | } 153 | 154 | # List all source files in the package 155 | sources <- function(path = ".") { 156 | srcdir <- file.path(path, "src") 157 | src <- c( 158 | dir(srcdir, "\\.(c.*|f|rs)$", recursive = TRUE, full.names = TRUE), 159 | dir(srcdir, "^Cargo\\.toml$", recursive = TRUE, full.names = TRUE) 160 | ) 161 | extra <- desc::desc_get("Config/build/extra-sources", path) 162 | 163 | if (!is.na(extra)) { 164 | glb <- trimws(strsplit(extra, ",", fixed = TRUE)[[1]]) 165 | xs <- file.path(path, globs(path, glb)) 166 | xfls <- unlist(lapply( 167 | xs, 168 | dir, 169 | recursive = TRUE, 170 | full.names = TRUE 171 | )) 172 | src <- c(src, xs, xfls) 173 | } 174 | src 175 | } 176 | 177 | # List all header files in the package 178 | headers <- function(path = ".") { 179 | incldir <- file.path(path, "inst", "include") 180 | srcdir <- file.path(path, "src") 181 | 182 | c( 183 | dir(srcdir, "^Makevars.*$", recursive = TRUE, full.names = TRUE), 184 | dir(srcdir, "\\.h.*$", recursive = TRUE, full.names = TRUE), 185 | dir(incldir, "\\.h.*$", recursive = TRUE, full.names = TRUE) 186 | ) 187 | } 188 | 189 | 190 | #' Does the package need recompiling? 191 | #' (i.e. is there a source or header file newer than the dll) 192 | #' @inheritParams build 193 | #' @keywords internal 194 | #' @export 195 | needs_compile <- function(path = ".") { 196 | source <- mtime(c(sources(path), headers(path))) 197 | # no source files, so doesn't need compile 198 | if (is.null(source)) { 199 | return(FALSE) 200 | } 201 | 202 | dll <- mtime(dll_path(path)) 203 | # no dll, so needs compile 204 | if (is.null(dll)) { 205 | return(TRUE) 206 | } 207 | 208 | source > dll 209 | } 210 | 211 | # Does the package need a clean compile? 212 | # (i.e. is there a header or Makevars newer than the dll) 213 | needs_clean <- function(path = ".") { 214 | never_clean <- get_desc_config_flag(path, "never-clean") 215 | if (isTRUE(never_clean)) { 216 | return(FALSE) 217 | } 218 | 219 | headers <- mtime(headers(path)) 220 | # no headers, so never needs clean compile 221 | if (is.null(headers)) { 222 | return(FALSE) 223 | } 224 | 225 | dll <- mtime(dll_path(path)) 226 | # no dll, so needs compile 227 | if (is.null(dll)) { 228 | return(TRUE) 229 | } 230 | 231 | headers > dll 232 | } 233 | 234 | install_min <- function( 235 | path = ".", 236 | dest, 237 | components = NULL, 238 | args = NULL, 239 | quiet = FALSE 240 | ) { 241 | stopifnot(is.character(dest), length(dest) == 1, file.exists(dest)) 242 | 243 | poss <- c("R", "data", "help", "demo", "inst", "docs", "exec", "libs") 244 | if (!is.null(components)) { 245 | components <- match.arg(components, poss, several.ok = TRUE) 246 | } 247 | no <- setdiff(poss, components) 248 | no_args <- paste0("--no-", no) 249 | 250 | rcmd_build_tools( 251 | "INSTALL", 252 | c( 253 | path, 254 | paste("--library=", dest, sep = ""), 255 | no_args, 256 | "--no-multiarch", 257 | "--no-test-load", 258 | args 259 | ), 260 | fail_on_status = TRUE, 261 | quiet = quiet 262 | ) 263 | 264 | invisible(file.path(dest, pkg_name(path))) 265 | } 266 | -------------------------------------------------------------------------------- /R/compiler-flags.R: -------------------------------------------------------------------------------- 1 | #' Default compiler flags used by devtools. 2 | #' 3 | #' These default flags enforce good coding practice by ensuring that 4 | #' \env{CFLAGS} and \env{CXXFLAGS} are set to `-Wall -pedantic`. 5 | #' These tests are run by cran and are generally considered to be good practice. 6 | #' 7 | #' By default [compile_dll()] is run with `compiler_flags(TRUE)`, 8 | #' and check with `compiler_flags(FALSE)`. If you want to avoid the 9 | #' possible performance penalty from the debug flags, install the package. 10 | #' 11 | #' @param debug If `TRUE` adds `-g -O0` to all flags 12 | #' (Adding \env{FFLAGS} and \env{FCFLAGS}) 13 | #' @family debugging flags 14 | #' @export 15 | #' @examples 16 | #' compiler_flags() 17 | #' compiler_flags(TRUE) 18 | compiler_flags <- function(debug = FALSE) { 19 | res <- 20 | if (Sys.info()[["sysname"]] == "SunOS") { 21 | c( 22 | CFLAGS = "-g", 23 | CXXFLAGS = "-g", 24 | CXX11FLAGS = "-g", 25 | CXX14FLAGS = "-g", 26 | CXX17FLAGS = "-g", 27 | CXX20FLAGS = "-g" 28 | ) 29 | } else if (debug) { 30 | c( 31 | CFLAGS = "-UNDEBUG -Wall -pedantic -g -O0", 32 | CXXFLAGS = "-UNDEBUG -Wall -pedantic -g -O0", 33 | CXX11FLAGS = "-UNDEBUG -Wall -pedantic -g -O0", 34 | CXX14FLAGS = "-UNDEBUG -Wall -pedantic -g -O0", 35 | CXX17FLAGS = "-UNDEBUG -Wall -pedantic -g -O0", 36 | CXX20FLAGS = "-UNDEBUG -Wall -pedantic -g -O0", 37 | FFLAGS = "-g -O0", 38 | FCFLAGS = "-g -O0" 39 | ) 40 | } else { 41 | c( 42 | CFLAGS = "-Wall -pedantic", 43 | CXXFLAGS = "-Wall -pedantic", 44 | CXX11FLAGS = "-Wall -pedantic", 45 | CXX14FLAGS = "-Wall -pedantic", 46 | CXX17FLAGS = "-Wall -pedantic", 47 | CXX20FLAGS = "-Wall -pedantic" 48 | ) 49 | } 50 | 51 | if (cli::num_ansi_colors() > 1 && has_compiler_colored_diagnostics()) { 52 | flags <- c( 53 | "CFLAGS", 54 | "CXXFLAGS", 55 | "CXX11FLAGS", 56 | "CXX14FLAGS", 57 | "CXX17FLAGS", 58 | "CXX20FLAGS" 59 | ) 60 | res[flags] <- paste(res[flags], "-fdiagnostics-color=always") 61 | } 62 | res 63 | } 64 | 65 | has_compiler_colored_diagnostics <- function() { 66 | val <- interpret_envvar_flag("PKG_BUILD_COLOR_DIAGNOSTICS", NA_character_) 67 | if (!is.na(val)) { 68 | return(val) 69 | } 70 | 71 | if (cache_exists("has_compiler_colored_diagnostics")) { 72 | return(cache_get("has_compiler_colored_diagnostics")) 73 | } 74 | 75 | # We cannot use the existing has_compiler setting, because it may not have 76 | # run with -fdiagnostics-color=always 77 | if (cache_exists("has_compiler")) { 78 | old <- cache_get("has_compiler") 79 | cache_remove("has_compiler") 80 | on.exit(cache_set("has_compiler", old)) 81 | } else { 82 | on.exit(cache_remove("has_compiler")) 83 | } 84 | 85 | res <- withr_with_makevars( 86 | c(CFLAGS = "-fdiagnostics-color=always"), 87 | has_compiler() 88 | ) 89 | 90 | cache_set("has_compiler_colored_diagnostics", res) 91 | res 92 | } 93 | -------------------------------------------------------------------------------- /R/compiler.R: -------------------------------------------------------------------------------- 1 | #' Is a compiler available? 2 | #' 3 | #' @description 4 | #' These functions check if a small C file can be compiled, linked, loaded 5 | #' and executed. 6 | #' 7 | #' `has_compiler()` and `has_devel()` return `TRUE` or `FALSE`. 8 | #' `check_compiler()` and `check_devel()` 9 | #' throw an error if you don't have developer tools installed. 10 | #' If the `"pkgbuild.has_compiler"` option is set to `TRUE` or `FALSE`, 11 | #' no check is carried out, and the value of the option is used. 12 | #' 13 | #' The implementation is based on a suggestion by Simon Urbanek. 14 | #' End-users (particularly those on Windows) should generally run 15 | #' [check_build_tools()] rather than [check_compiler()]. 16 | #' 17 | #' 18 | #' @export 19 | #' @inheritParams has_rtools 20 | #' @seealso [check_build_tools()] 21 | #' @examples 22 | #' has_compiler() 23 | #' check_compiler() 24 | #' 25 | #' with_build_tools(has_compiler()) 26 | has_compiler <- function(debug = FALSE) { 27 | res <- getOption("pkgbuild.has_compiler") 28 | if (!is.null(res)) { 29 | if (is_flag(res)) return(res) 30 | stop(cli::format_error(c( 31 | "", 32 | "!" = "Invalid {.code pkgbuild.has_compiler} option.", 33 | "i" = "It must be {.code TRUE} or {.code FALSE}, not {.type {res}}." 34 | ))) 35 | } 36 | 37 | if (!debug && cache_exists("has_compiler")) { 38 | return(cache_get("has_compiler")) 39 | } 40 | 41 | foo_path <- file.path(tempdir(), "foo.c") 42 | cat("void foo(int *bar) { *bar=1; }\n", file = foo_path) 43 | on.exit(unlink(foo_path)) 44 | 45 | res <- tryCatch( 46 | { 47 | if (debug) { 48 | message("Trying to compile a simple C file") 49 | } 50 | 51 | callr::rcmd_safe( 52 | "SHLIB", 53 | "foo.c", 54 | wd = tempdir(), 55 | show = debug, 56 | echo = debug, 57 | fail_on_status = TRUE, 58 | stderr = "2>&1" 59 | ) 60 | 61 | if (debug) { 62 | message("") 63 | } 64 | dylib <- file.path(tempdir(), paste0("foo", .Platform$dynlib.ext)) 65 | on.exit(unlink(dylib), add = TRUE) 66 | 67 | dll <- dyn.load(dylib) 68 | on.exit(dyn.unload(dylib), add = TRUE) 69 | 70 | .C(dll$foo, 0L)[[1]] == 1L 71 | }, 72 | error = function(e) { 73 | FALSE 74 | } 75 | ) 76 | 77 | cache_set("has_compiler", res) 78 | res 79 | } 80 | 81 | #' @export 82 | #' @rdname has_compiler 83 | check_compiler <- function(debug = FALSE) { 84 | if (!has_compiler(debug)) { 85 | stop("Failed to compile C code", call. = FALSE) 86 | } 87 | 88 | TRUE 89 | } 90 | 91 | #' @export 92 | #' @rdname has_compiler 93 | #' @usage NULL 94 | has_devel <- check_build_tools 95 | 96 | # The checking code looks for the objects in the package namespace, so defining 97 | # dll here removes the following NOTE 98 | # Registration problem: 99 | # Evaluating 'dll$foo' during check gives error 100 | # 'object 'dll' not found': 101 | # .C(dll$foo, 0L) 102 | # See https://github.com/wch/r-source/blob/d4e8fc9832f35f3c63f2201e7a35fbded5b5e14c/src/library/tools/R/QC.R#L1950-L1980 103 | # Setting the class is needed to avoid a note about returning the wrong class. 104 | # The local object is found first in the actual call, so current behavior is 105 | # unchanged. 106 | dll <- list(foo = structure(list(), class = "NativeSymbolInfo")) 107 | -------------------------------------------------------------------------------- /R/exclude.R: -------------------------------------------------------------------------------- 1 | copy_package_tree <- function( 2 | path = ".", 3 | dest, 4 | pkgname = desc::desc_get("Package", path) 5 | ) { 6 | if (!file.exists(dest)) mkdirp(dest) 7 | 8 | pkgdir <- file.path(dest, pkgname) 9 | if (file.exists(pkgdir)) { 10 | stop(cli::format_error(c( 11 | "Cannot copy package tree to {.path {dest}}", 12 | i = "Directory {.path {pkgdir}} already exists, and did not want 13 | to overwrite." 14 | ))) 15 | } 16 | 17 | mkdirp(pkgdir) 18 | 19 | paths <- build_files(path, pkgname) 20 | 21 | method <- get_copy_method(path) 22 | 23 | for (i in seq_len(nrow(paths))) { 24 | # excluded, skip 25 | if (paths$exclude[i]) next 26 | if (paths$isdir[i] && paths$trimmed[i]) { 27 | # trimmed directory, only create directory 28 | # we do not try to update the mode of the directory, as it is not 29 | # very important for R packages, and it might fail on some file 30 | # systems. 31 | mkdirp(file.path(pkgdir, paths$path[i])) 32 | } else { 33 | # not a directory, or a non-trimmed directory, recurse 34 | src <- paths$realpath[i] 35 | tgt <- file.path(pkgdir, paths$path[i]) 36 | if (method == "link") { 37 | file.symlink(src, tgt) 38 | } else if (paths$isdir[i]) { 39 | cp(src, dirname(tgt), recursive = TRUE) 40 | } else { 41 | cp(src, tgt) 42 | } 43 | } 44 | } 45 | 46 | # TODO: should we return a trimming summary? 47 | invisible() 48 | } 49 | 50 | get_copy_method <- function(path = ".") { 51 | method <- getOption("pkg.build_copy_method", NA_character_) 52 | values <- c("none", "copy", "link") 53 | check_method <- function(method) { 54 | # no symlinks on Windows 55 | if (method == "link" && is_windows()) method <- "copy" 56 | if (method %in% values) return(method) 57 | stop(cli::format_error(c( 58 | "Invalid {.code pkg.build_copy_method} value: {.val {method}}.", 59 | i = "It must be one of {.str {values}}." 60 | ))) 61 | } 62 | if (!is_string(method) && !is_na(method)) { 63 | stop(cli::format_error(c( 64 | "Invalid {.code pkg.build_copy_method} value.", 65 | i = "It must be a string, but it is {.type {method}}." 66 | ))) 67 | } 68 | if (!is.na(method)) return(check_method(method)) 69 | 70 | method <- desc::desc_get("Config/build/copy-method", path) 71 | if (!is.na(method)) return(check_method(method)) 72 | 73 | method <- Sys.getenv("PKG_BUILD_COPY_METHOD", "none") 74 | check_method(method) 75 | } 76 | 77 | build_files <- function( 78 | path = ".", 79 | pkgname = desc::desc_get("Package", path) 80 | ) { 81 | path <- normalizePath(path) 82 | 83 | # patterns in .Rbuildignore 84 | ign_file <- file.path(path, ".Rbuildignore") 85 | ign <- if (file.exists(ign_file)) { 86 | readLines(ign_file, warn = FALSE) 87 | } else { 88 | character() 89 | } 90 | ign <- ign[nzchar(ign)] 91 | # make it case insensitive, that's how R matches them 92 | if (length(ign)) ign <- paste0("(?i)", ign) 93 | 94 | ptrn <- c(ign, re_exclude(pkgname)) 95 | ptrn_dir <- re_exclude_dir(pkgname) 96 | 97 | # filter at the top level first, so we don't need to enumerate these 98 | top <- dir(path, include.dirs = TRUE, all.files = TRUE, no.. = TRUE) 99 | 100 | # now filter top 101 | realtop <- file.path(path, top) 102 | topfls <- data.frame( 103 | stringsAsFactors = FALSE, 104 | path = top, 105 | realpath = realtop, 106 | exclude = logical(length(top)), 107 | isdir = file.info(realtop)$isdir 108 | ) 109 | topfls <- exclude(path, topfls, ptrn, ptrn_dir) 110 | 111 | # now create the rest of the files 112 | sub <- unlist(lapply( 113 | topfls$path[topfls$isdir & !topfls$exclude], 114 | function(t) { 115 | tf <- dir( 116 | file.path(path, t), 117 | include.dirs = TRUE, 118 | all.files = TRUE, 119 | recursive = TRUE 120 | ) 121 | tf <- file.path(t, tf) 122 | } 123 | )) 124 | 125 | realsub <- file.path(path, sub) 126 | subfls <- data.frame( 127 | stringsAsFactors = FALSE, 128 | path = sub, 129 | realpath = realsub, 130 | exclude = logical(length(sub)), 131 | isdir = file.info(realsub)$isdir 132 | ) 133 | subfls <- exclude(path, subfls, ptrn, ptrn_dir) 134 | 135 | allfls <- rbind(topfls, subfls) 136 | 137 | # Always keep this, so base R can do its own filtering, just in case 138 | # it changes compared to ours 139 | allfls$exclude[allfls$path == ".Rbuildignore"] <- FALSE 140 | 141 | allfls <- exclude_downstream(allfls) 142 | 143 | allfls 144 | } 145 | 146 | re_exclude <- function(pkg) { 147 | c( 148 | paste0( 149 | "(?i)", # these are case insensitive 150 | c( 151 | "(^|/)\\.DS_Store$", # by macOS finder 152 | "^\\.RData$", # .RData at / 153 | "~$", 154 | "\\.bak$", 155 | "\\.swp$", # backup files 156 | "(^|/)\\.#[^/]*$", 157 | "(^|/)#[^/]*#$", # more backup files (Emacs) 158 | 159 | "^config\\.(cache|log|status)$", # leftover by autoconf 160 | "(^|/)autom4te\\.cache$", 161 | 162 | "^src/.*\\.d$", 163 | "^src/Makedeps$", # INSTALL leftover on Windows 164 | 165 | "^inst/doc/Rplots\\.(ps|pdf)$" # Sweave leftover 166 | ) 167 | ), 168 | 169 | "(^|/)\\._[^/]*$", # macOS resource forks 170 | 171 | paste0( 172 | # hidden files 173 | "(^|/)\\.", 174 | c( 175 | "Renviron", 176 | "Rprofile", 177 | "Rproj.user", 178 | "Rhistory", 179 | "Rapp.history", 180 | "tex", 181 | "log", 182 | "aux", 183 | "pdf", 184 | "png", 185 | "backups", 186 | "cvsignore", 187 | "cproject", 188 | "directory", 189 | "dropbox", 190 | "exrc", 191 | "gdb.history", 192 | "gitattributes", 193 | "github", 194 | "gitignore", 195 | "gitmodules", 196 | "hgignore", 197 | "hgtags", 198 | "htaccess", 199 | "latex2html-init", 200 | "project", 201 | "seed", 202 | "settings", 203 | "tm_properties" 204 | ), 205 | "$" 206 | ), 207 | 208 | paste0( 209 | "(^|/)", 210 | pkg, 211 | "_[0-9.-]+", 212 | "\\.(tar\\.gz|tar|tar\\.bz2|tar\\.xz|tgz|zip)", 213 | "$" 214 | ) 215 | ) 216 | } 217 | 218 | re_exclude_dir <- function(pkg) { 219 | c( 220 | "^revdep$", # revdepcheck 221 | paste0( 222 | # VC 223 | "(^|/)", 224 | c( 225 | "CVS", 226 | ".svn", 227 | ".arch-ids", 228 | ".bzr", 229 | ".git", 230 | ".hg", 231 | "_darcs", 232 | ".metadata" 233 | ), 234 | "$" 235 | ), 236 | 237 | "(^|/)[^/]*[Oo]ld$", 238 | "(^|/)[^/]*\\.Rcheck", 239 | 240 | "^src.*/\\.deps$" 241 | ) 242 | } 243 | 244 | exclude <- function(root, paths, patterns, patterns_dir) { 245 | ex <- logical(nrow(paths)) 246 | for (p in patterns) { 247 | ex <- ex | grepl(p, paths$path, perl = TRUE) 248 | } 249 | 250 | # additional regexes for directories, we don't need to check 'ex' 251 | wdirs <- !ex & paths$isdir 252 | 253 | paths_dirs <- paths$path[wdirs] 254 | dex <- logical(length(paths_dirs)) 255 | for (p in patterns_dir) { 256 | dex <- dex | grepl(p, paths_dirs, perl = TRUE) 257 | } 258 | ex[wdirs][dex] <- TRUE 259 | 260 | paths$exclude <- ex 261 | paths 262 | } 263 | 264 | exclude_downstream <- function(paths) { 265 | # We don't actually need this now, but we could use it to optimize, 266 | # because we could trim only the subsequent elements after a directory. 267 | paths <- paths[order(paths$path), ] 268 | 269 | # We need to take each excluded directory, and remove all paths in them 270 | exdirs <- paste0(paths$path[paths$isdir & paths$exclude], "/") 271 | del <- logical(nrow(paths)) 272 | for (ed in exdirs) { 273 | del <- del | startsWith(paths$path, ed) 274 | } 275 | paths <- paths[!del, ] 276 | 277 | # Now we mark the subdirectories that can be copied as is, i.e. none of 278 | # their downstream contents are excluded 279 | indirs <- which(paths$isdir & !paths$exclude) 280 | trm <- logical(nrow(paths)) 281 | for (id in indirs) { 282 | trm[id] <- any( 283 | startsWith(paths$path, paste0(paths$path[id], "/")) & paths$exclude 284 | ) 285 | } 286 | paths$trimmed <- trm 287 | 288 | # We can remove the subdirectories of non-trimmed directories as well 289 | fulldirs <- paste0(paths$path[paths$isdir & !trm], "/") 290 | del2 <- logical(nrow(paths)) 291 | for (fd in fulldirs) { 292 | del2 <- del2 | startsWith(paths$path, fd) 293 | } 294 | paths <- paths[!del2, ] 295 | 296 | rownames(paths) <- NULL 297 | paths 298 | } 299 | 300 | cp <- local({ 301 | wind <- NULL 302 | cpargs <- NULL 303 | function(src, tgt, recursive = FALSE) { 304 | if (is.null(wind)) wind <<- is_windows() 305 | if (wind) { 306 | if (!file.copy(src, tgt, recursive = recursive, copy.date = TRUE)) { 307 | stop(cli::format_error(c( 308 | "Could not copy package files.", 309 | i = "Failed to copy {.path {src}} to {.path {tgt}}." 310 | ))) 311 | } 312 | } else { 313 | if (is.null(cpargs)) cpargs <<- detect_cp_args() 314 | ret <- processx::run( 315 | "cp", 316 | c(cpargs, src, tgt), 317 | stderr = "2>&1", 318 | error_on_status = FALSE 319 | ) 320 | if (ret$status != 0) { 321 | stop(cli::format_error(c( 322 | "Could not copy package files.", 323 | i = "Failed to copy {.path {src}} to {.path {tgt}}.", 324 | i = "{.code cp} output:", 325 | " " = verb_for_cli(ret$stdout) 326 | ))) 327 | } 328 | } 329 | } 330 | }) 331 | 332 | detect_cp_args <- function() { 333 | # we do this in a tempdir, because it might create a file a called 334 | # `--preserve=timestamps` 335 | dir.create(tmp <- tempfile()) 336 | old <- getwd() 337 | on.exit( 338 | { 339 | setwd(old) 340 | unlink(tmp, recursive = TRUE) 341 | }, 342 | add = TRUE 343 | ) 344 | setwd(tmp) 345 | f1 <- basename(tempfile()) 346 | f2 <- basename(tempfile()) 347 | file.create(f1) 348 | tryCatch( 349 | suppressWarnings(processx::run("cp", c("--preserve=timestamps", f1, f2))), 350 | error = function(e) e 351 | ) 352 | if (file.exists(f2)) { 353 | c("-LR", "--preserve=timestamps") 354 | } else { 355 | "-pLR" 356 | } 357 | } 358 | -------------------------------------------------------------------------------- /R/find-package-root.R: -------------------------------------------------------------------------------- 1 | find_package_root <- function(path = ".") { 2 | is_root <- function(path) { 3 | identical( 4 | normalizePath(path, winslash = "/"), 5 | normalizePath(dirname(path), winslash = "/") 6 | ) 7 | } 8 | 9 | if (!file.exists(path)) { 10 | stop("Path does not exist: ", path) 11 | } 12 | cur_path <- normalizePath(path, winslash = "/") 13 | errmsg <- paste0( 14 | "Could not find R package in `", 15 | path, 16 | "` or its parent directories." 17 | ) 18 | max_depth <- 100 19 | for (i in 1:max_depth) { 20 | dsc_path <- file.path(cur_path, "DESCRIPTION") 21 | if ( 22 | file.exists(dsc_path) && 23 | any(grepl("^Package: ", readLines(dsc_path))) 24 | ) { 25 | return(cur_path) 26 | } else if (is_root(cur_path)) { 27 | stop(errmsg) 28 | } else { 29 | cur_path <- dirname(cur_path) 30 | } 31 | } 32 | stop(errmsg, " Checked ", max_depth, " parent directories.") # nocov 33 | } 34 | -------------------------------------------------------------------------------- /R/has_src.R: -------------------------------------------------------------------------------- 1 | #' Does a source package have `src/` directory? 2 | #' 3 | #' If it does, you definitely need build tools. 4 | #' 5 | #' @param path Path to package (or directory within package). 6 | #' @export 7 | pkg_has_src <- function(path = ".") { 8 | if (is_dir(path)) { 9 | src_path <- file.path(pkg_path(path), "src") 10 | file.exists(src_path) 11 | } else { 12 | tryCatch( 13 | { 14 | files <- if (is_zip_file(path)) { 15 | utils::unzip(path, list = TRUE)$Name 16 | } else if (is_tar_gz_file(path)) { 17 | utils::untar(path, list = TRUE) 18 | } else { 19 | stop("not a zip or tar.gz file") 20 | } 21 | 22 | if (!any(grepl("^[^/]+/DESCRIPTION$", files))) { 23 | stop("no DESCRIPTION file") 24 | } 25 | 26 | any(grepl("^[^/]+/src/?$", files)) 27 | }, 28 | error = function(e) { 29 | e$message <- paste( 30 | path, 31 | "is not a valid package archive file,", 32 | e$message 33 | ) 34 | stop(e) 35 | } 36 | ) 37 | } 38 | } 39 | 40 | is_zip_file <- function(file) { 41 | buf <- readBin(file, what = "raw", n = 4) 42 | length(buf) == 4 && 43 | buf[1] == 0x50 && 44 | buf[2] == 0x4b && 45 | (buf[3] == 0x03 || buf[3] == 0x05 || buf[5] == 0x07) && 46 | (buf[4] == 0x04 || buf[4] == 0x06 || buf[4] == 0x08) 47 | } 48 | 49 | is_gz_file <- function(file) { 50 | buf <- readBin(file, what = "raw", n = 3) 51 | length(buf) == 3 && 52 | buf[1] == 0x1f && 53 | buf[2] == 0x8b && 54 | buf[3] == 0x08 55 | } 56 | 57 | is_tar_gz_file <- function(file) { 58 | if (!is_gz_file(file)) { 59 | return(FALSE) 60 | } 61 | con <- gzfile(file, open = "rb") 62 | on.exit(close(con)) 63 | buf <- readBin(con, what = "raw", n = 262) 64 | length(buf) == 262 && 65 | buf[258] == 0x75 && 66 | buf[259] == 0x73 && 67 | buf[260] == 0x74 && 68 | buf[261] == 0x61 && 69 | buf[262] == 0x72 70 | } 71 | -------------------------------------------------------------------------------- /R/latex.R: -------------------------------------------------------------------------------- 1 | #' Is latex installed? 2 | #' 3 | #' Checks for presence of pdflatex on path. 4 | #' 5 | #' @export 6 | has_latex <- function() { 7 | if (!is.null(fix <- getOption("PKGBUILD_TEST_FIXTURE_HAS_LATEX"))) { 8 | return(fix) 9 | } 10 | 11 | nzchar(Sys.which("pdflatex")) 12 | } 13 | 14 | #' @export 15 | #' @rdname has_latex 16 | check_latex <- function() { 17 | if (!has_latex()) { 18 | stop("LaTeX not installed (pdflatex not found)", call. = FALSE) 19 | } 20 | 21 | TRUE 22 | } 23 | -------------------------------------------------------------------------------- /R/pkgbuild-package.R: -------------------------------------------------------------------------------- 1 | #' @keywords internal 2 | "_PACKAGE" 3 | 4 | ## usethis namespace: start 5 | ## usethis namespace: end 6 | NULL 7 | 8 | .onLoad <- function(libname, pkgname) { 9 | set_pkg_config_path() 10 | } 11 | 12 | set_pkg_config_path <- function() { 13 | if ( 14 | Sys.getenv("PKG_BUILD_IGNORE_PKG_CONFIG_PATH") != "true" && 15 | .Platform$OS.type == "windows" && 16 | Sys.which("pkg-config") != "" && 17 | getRversion()[, 1:2] == "4.2" && 18 | Sys.which("gcc") != "" && 19 | grepl( 20 | "/rtools42/", 21 | normalizePath(Sys.which("gcc"), winslash = "/"), 22 | ignore.case = TRUE 23 | ) && 24 | !grepl("/rtools42", Sys.getenv("PKG_CONFIG_PATH"), ignore.case = TRUE) 25 | ) { 26 | old <- Sys.getenv("PKG_CONFIG_PATH") 27 | new <- paste0( 28 | "/c/rtools42/x86_64-w64-mingw32.static.posix/lib/pkgconfig", 29 | ":", 30 | old 31 | ) 32 | message("Updating PKG_CONFIG_PATH.\nOld: ", old, "\nNew: ", new) 33 | Sys.setenv(PKG_CONFIG_PATH = new) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /R/rcmd.R: -------------------------------------------------------------------------------- 1 | #' Call R CMD 'command' with build tools active 2 | #' 3 | #' This is a wrapper around `callr::rcmd_safe()` that checks 4 | #' that you have build tools available, and on Windows, automatically sets 5 | #' the path to include Rtools. 6 | #' 7 | #' @param ... Parameters passed on to `rcmd_safe`. 8 | #' @param env Additional environment variables to set. The defaults from 9 | #' `callr::rcmd_safe_env()` are always set. 10 | #' @inheritParams with_build_tools 11 | #' @inheritParams build 12 | #' @export 13 | #' @examples 14 | #' # These env vars are always set 15 | #' callr::rcmd_safe_env() 16 | #' 17 | #' if (has_build_tools()) { 18 | #' rcmd_build_tools("CONFIG", "CC")$stdout 19 | #' rcmd_build_tools("CC", "--version")$stdout 20 | #' } 21 | rcmd_build_tools <- function( 22 | ..., 23 | env = character(), 24 | required = TRUE, 25 | quiet = FALSE 26 | ) { 27 | env <- c(callr::rcmd_safe_env(), env) 28 | 29 | if (!quiet) { 30 | cli::cat_rule(paste0("R CMD ", ..1), col = "cyan") 31 | } 32 | 33 | warn_for_potential_errors() 34 | 35 | callback <- if (cli::is_dynamic_tty()) { 36 | block_callback(quiet) 37 | } else { 38 | simple_callback(quiet) 39 | } 40 | 41 | res <- with_build_tools( 42 | { 43 | withCallingHandlers( 44 | callr::rcmd_safe( 45 | ..., 46 | env = env, 47 | spinner = FALSE, 48 | show = FALSE, 49 | echo = FALSE, 50 | block_callback = callback, 51 | stderr = "2>&1" 52 | ), 53 | error = function(e) { 54 | if (!quiet) e$echo <- TRUE 55 | asNamespace("callr")$err$throw(e) 56 | } 57 | ) 58 | }, 59 | required = required 60 | ) 61 | 62 | msg_for_long_paths(res) 63 | 64 | invisible(res) 65 | } 66 | 67 | msg_for_long_paths <- function(output) { 68 | if ( 69 | is_windows() && 70 | any(grepl("over-long path length", output$stdout)) 71 | ) { 72 | message( 73 | "\nIt seems that this package contains files with very long paths.\n", 74 | "This is not supported on most Windows versions. Please contact the\n", 75 | "package authors and tell them about this. See this GitHub issue\n", 76 | "for more details: https://github.com/r-lib/remotes/issues/84\n" 77 | ) 78 | } 79 | } 80 | 81 | warn_for_potential_errors <- function() { 82 | if (is_windows() && grepl(" ", R.home()) && getRversion() <= "3.4.2") { 83 | warning( 84 | immediate. = TRUE, 85 | "\n!!! Building will probably fail!\n", 86 | "This version of R has trouble with building packages if\n", 87 | "the R HOME directory (currently '", 88 | R.home(), 89 | "')\n", 90 | "has space characters. Possible workarounds include:\n", 91 | "- installing R to the C: drive,\n", 92 | "- installing it into a path without a space, or\n", 93 | "- creating a drive letter for R HOME via the `subst` windows command, and\n", 94 | " starting R from the new drive.\n", 95 | "See also https://github.com/r-lib/remotes/issues/98\n" 96 | ) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /R/rtools-cache.R: -------------------------------------------------------------------------------- 1 | #' @export 2 | #' @rdname has_rtools 3 | rtools_path <- function() { 4 | if (!is_windows()) { 5 | return(NA_character_) 6 | } 7 | 8 | if (!rtools_path_is_set()) { 9 | has_rtools() 10 | } 11 | 12 | cache_get("rtools_path") 13 | } 14 | 15 | rtools_path_is_set <- function() { 16 | cache_exists("rtools_path") 17 | } 18 | 19 | rtools_path_set <- function(rtools) { 20 | stopifnot(is.rtools(rtools)) 21 | path <- file.path(rtools$path, version_info[[rtools$version]]$path) 22 | 23 | # If using gcc49 and _without_ a valid BINPREF already set 24 | # Do NOT set BINPREF anymore for R 4.0 / rtools40 25 | if (!is_R4() && using_gcc49() && is.null(rtools$valid_binpref)) { 26 | Sys.setenv(BINPREF = file.path(rtools$path, "mingw_$(WIN)", "bin", "/")) 27 | } 28 | 29 | cache_set("rtools_path", path) 30 | } 31 | 32 | using_gcc49 <- function() { 33 | grepl("4.9.3", Sys.getenv("R_COMPILED_BY"), fixed = TRUE) 34 | } 35 | -------------------------------------------------------------------------------- /R/rtools-config.R: -------------------------------------------------------------------------------- 1 | # First check if gcc set by BINPREF/CC is valid and use that if so 2 | scan_config_for_rtools <- function(debug = FALSE) { 3 | if (debug) { 4 | cat("Scanning R CMD config CC...\n") 5 | } 6 | 7 | if (!is_R4() && !using_gcc49()) { 8 | return() 9 | } 10 | 11 | cc_path <- gsub("\n", "", callr::rcmd_safe("config", "CC")$stdout) 12 | # remove '-m64' from tail if it exists 13 | cc_path <- sub("[[:space:]]+-m[[:digit:]]+$", "", cc_path) 14 | 15 | if (debug) { 16 | cat("cc_path:", cc_path, "\n") 17 | } 18 | 19 | cc_path <- find_arch_exe(cc_path, debug = debug) 20 | if (cc_path == "") { 21 | NULL 22 | } else { 23 | install_path <- dirname(dirname(dirname(cc_path))) 24 | if (debug) { 25 | cat("install_path:", install_path, "\n") 26 | } 27 | rtools(install_path, "custom", valid_binpref = TRUE) 28 | } 29 | } 30 | 31 | is_R4 <- function() { 32 | R.Version()$major >= "4" 33 | } 34 | -------------------------------------------------------------------------------- /R/rtools-metadata.R: -------------------------------------------------------------------------------- 1 | version_info <- list( 2 | "2.11" = list( 3 | version_min = "2.10.0", 4 | version_max = "2.11.1", 5 | path = c("bin", "perl/bin", "MinGW/bin") 6 | ), 7 | "2.12" = list( 8 | version_min = "2.12.0", 9 | version_max = "2.12.2", 10 | path = c("bin", "perl/bin", "MinGW/bin", "MinGW64/bin") 11 | ), 12 | "2.13" = list( 13 | version_min = "2.13.0", 14 | version_max = "2.13.2", 15 | path = c("bin", "MinGW/bin", "MinGW64/bin") 16 | ), 17 | "2.14" = list( 18 | version_min = "2.13.0", 19 | version_max = "2.14.2", 20 | path = c("bin", "MinGW/bin", "MinGW64/bin") 21 | ), 22 | "2.15" = list( 23 | version_min = "2.14.2", 24 | version_max = "2.15.1", 25 | path = c("bin", "gcc-4.6.3/bin") 26 | ), 27 | "2.16" = list( 28 | version_min = "2.15.2", 29 | version_max = "3.0.0", 30 | path = c("bin", "gcc-4.6.3/bin") 31 | ), 32 | "3.0" = list( 33 | version_min = "2.15.2", 34 | version_max = "3.0.99", 35 | path = c("bin", "gcc-4.6.3/bin") 36 | ), 37 | "3.1" = list( 38 | version_min = "3.0.0", 39 | version_max = "3.1.99", 40 | path = c("bin", "gcc-4.6.3/bin") 41 | ), 42 | "3.2" = list( 43 | version_min = "3.1.0", 44 | version_max = "3.2.99", 45 | path = c("bin", "gcc-4.6.3/bin") 46 | ), 47 | "3.3" = list( 48 | version_min = "3.2.0", 49 | version_max = "3.3.99", 50 | path = if (using_gcc49()) { 51 | "bin" 52 | } else { 53 | c("bin", "gcc-4.6.3/bin") 54 | } 55 | ), 56 | "3.4" = list( 57 | version_min = "3.3.0", 58 | version_max = "3.6.3", 59 | path = "bin" 60 | ), 61 | "3.5" = list( 62 | version_min = "3.3.0", 63 | version_max = "3.6.3", 64 | path = "bin" 65 | ), 66 | "4.0" = list( 67 | version_min = "4.0.0", 68 | version_max = "4.1.99", 69 | path = c("usr/bin", "ucrt64/bin") 70 | ), 71 | "4.2" = list( 72 | version_min = "4.2.0", 73 | version_max = "4.2.99", 74 | path = "usr/bin" 75 | ), 76 | "4.3" = list( 77 | version_min = "4.3.0", 78 | version_max = "4.3.99", 79 | path = "usr/bin" 80 | ), 81 | "4.4" = list( 82 | version_min = "4.4.0", 83 | version_max = "4.4.99", 84 | path = "usr/bin" 85 | ), 86 | "4.5" = list( 87 | version_min = "4.5.0", 88 | version_max = "99.99.99", 89 | path = character() 90 | ), 91 | "custom" = list( 92 | version_min = "2.10.0", 93 | version_max = "99.99.99", 94 | path = if (getRversion() >= "4.4.0") { 95 | character() 96 | } else if (getRversion() >= "4.0.0") { 97 | "usr/bin" 98 | } else { 99 | "bin" 100 | } 101 | ) 102 | ) 103 | -------------------------------------------------------------------------------- /R/rtools-path.R: -------------------------------------------------------------------------------- 1 | scan_path_for_rtools <- function(debug = FALSE) { 2 | if (debug) { 3 | cat("Scanning path...\n") 4 | } 5 | 6 | # Next looks for ls and gcc on path 7 | ls_path <- Sys.which("ls") 8 | if (ls_path == "") { 9 | return(NULL) 10 | } 11 | if (debug) { 12 | cat("ls:", ls_path, "\n") 13 | } 14 | 15 | # We have a candidate install_path 16 | install_path <- dirname(dirname(ls_path)) 17 | # ls is one more directory down on recent rtools 18 | if (basename(install_path) == "usr") { 19 | install_path <- dirname(install_path) 20 | } 21 | 22 | gcc_path <- Sys.which("gcc") 23 | if (debug) { 24 | cat("gcc_path:", gcc_path, "\n") 25 | } 26 | 27 | if (gcc_path != "") { 28 | # Check both candidate install paths are same 29 | install_path2 <- dirname(dirname(dirname(gcc_path))) 30 | if (tolower(install_path2) != tolower(install_path)) { 31 | return(NULL) 32 | } 33 | } else { 34 | # Maybe isn't on path, but is in default location 35 | gcc_default <- find_arch_exe( 36 | file.path(install_path, paste0("mingw_", gcc_arch()), "bin", "gcc.exe"), 37 | debug = debug 38 | ) 39 | if (gcc_default == "") { 40 | return(NULL) 41 | } 42 | } 43 | 44 | version <- installed_version(install_path, debug = debug) 45 | if (debug) { 46 | cat("Version:", version, "\n") 47 | } 48 | 49 | rtools(install_path, version) 50 | } 51 | 52 | find_arch_exe <- function(path, debug = FALSE) { 53 | # Convert unix path to Windows 54 | if (grepl("^/", path)) { 55 | path <- convert_unix_path(path) 56 | } 57 | 58 | full_path <- Sys.which(path) 59 | 60 | if (nchar(full_path) == 0) { 61 | if (debug) { 62 | cat("'", path, "' does not exist\n", sep = "") 63 | } 64 | return("") 65 | } 66 | 67 | # Then check architecture matches 68 | file_info <- file.info(full_path) 69 | if (file_info$exe != paste0("win", gcc_arch())) { 70 | if (debug) { 71 | cat(" Architecture doesn't match\n") 72 | } 73 | return("") 74 | } 75 | 76 | full_path 77 | } 78 | 79 | # This assumes cygpath is on your PATH, 80 | # but it should be if you are using /foo/bar paths in R CMD config, 81 | # so we don't need to handle this 82 | convert_unix_path <- function(path) { 83 | system2("cygpath", c("-m", path), stdout = TRUE) 84 | } 85 | -------------------------------------------------------------------------------- /R/rtools-registry.R: -------------------------------------------------------------------------------- 1 | read_registry <- function(...) { 2 | tryCatch( 3 | utils::readRegistry(...), 4 | error = function(e) NULL 5 | ) 6 | } 7 | 8 | scan_registry_for_rtools <- function(debug = FALSE) { 9 | if (debug) cat("Scanning registry...\n") 10 | 11 | keys <- c( 12 | read_registry( 13 | "SOFTWARE\\R-core\\Rtools", 14 | hive = "HCU", 15 | view = "32-bit", 16 | maxdepth = 2 17 | ), 18 | read_registry( 19 | "SOFTWARE\\R-core\\Rtools", 20 | hive = "HLM", 21 | view = "32-bit", 22 | maxdepth = 2 23 | ), 24 | read_registry( 25 | "SOFTWARE\\R-core\\Rtools", 26 | hive = "HLM", 27 | view = "64-bit", 28 | maxdepth = 2 29 | ) 30 | ) 31 | 32 | if (is.null(keys)) { 33 | return(NULL) 34 | } 35 | 36 | rts <- vector("list", length(keys)) 37 | 38 | for (i in seq_along(keys)) { 39 | version <- names(keys)[[i]] 40 | key <- keys[[version]] 41 | if (!is.list(key) || is.null(key$InstallPath)) next 42 | install_path <- normalizePath( 43 | key$InstallPath, 44 | mustWork = FALSE, 45 | winslash = "/" 46 | ) 47 | 48 | if (debug) cat("Found", install_path, "for", version, "\n") 49 | rts[[i]] <- rtools(install_path, version) 50 | } 51 | 52 | Filter(Negate(is.null), rts) 53 | } 54 | -------------------------------------------------------------------------------- /R/rtools.R: -------------------------------------------------------------------------------- 1 | #' Is Rtools installed? 2 | #' 3 | #' To build binary packages on windows, Rtools (found at 4 | #' \url{https://CRAN.R-project.org/bin/windows/Rtools/}) needs to be on 5 | #' the path. The default installation process does not add it, so this 6 | #' script finds it (looking first on the path, then in the registry). 7 | #' It also checks that the version of rtools matches the version of R. 8 | #' `has_rtools()` determines if Rtools is installed, caching the results. 9 | #' Afterward, run `rtools_path()` to find out where it's installed. 10 | #' 11 | #' @section Acknowledgements: 12 | #' This code borrows heavily from RStudio's code for finding Rtools. 13 | #' Thanks JJ! 14 | #' @param debug If `TRUE`, will print out extra information useful for 15 | #' debugging. If `FALSE`, it will use result cached from a previous run. 16 | #' @return Either a visible `TRUE` if rtools is found, or an invisible 17 | #' `FALSE` with a diagnostic [message()]. 18 | #' As a side-effect the internal package variable `rtools_path` is 19 | #' updated to the paths to rtools binaries. 20 | #' @keywords internal 21 | #' @export 22 | #' @examples 23 | #' has_rtools() 24 | has_rtools <- function(debug = FALSE) { 25 | if (!debug && rtools_path_is_set()) { 26 | return(!identical(rtools_path(), "")) 27 | } 28 | 29 | if (!is_windows()) { 30 | return(FALSE) 31 | } 32 | 33 | # R 4.5.0 or later on ARM64 34 | if (getRversion() >= "4.5.0" && grepl("aarch", R.version$platform)) { 35 | rtools45_aarch64_home <- Sys.getenv( 36 | "RTOOLS45_AARCH64_HOME", 37 | "C:\\rtools45-aarch64" 38 | ) 39 | if (file.exists(file.path(rtools45_aarch64_home, "usr", "bin"))) { 40 | if (debug) { 41 | cat("Found in Rtools 4.5 (aarch64) installation folder\n") 42 | } 43 | rtools_path_set(rtools(rtools45_aarch64_home, "4.5")) 44 | return(TRUE) 45 | } 46 | } 47 | 48 | # R 4.5.0 or later 49 | if (getRversion() >= "4.5.0" && !grepl("aarch", R.version$platform)) { 50 | rtools45_home <- Sys.getenv("RTOOLS45_HOME", "C:\\rtools45") 51 | if (file.exists(file.path(rtools45_home, "usr", "bin"))) { 52 | if (debug) { 53 | cat("Found in Rtools 4.5 installation folder\n") 54 | } 55 | rtools_path_set(rtools(rtools45_home, "4.5")) 56 | return(TRUE) 57 | } 58 | } 59 | 60 | # R 4.4.0 or later on ARM64 61 | if ( 62 | getRversion() >= "4.4.0" && 63 | getRversion() < "4.5.0" && 64 | grepl("aarch", R.version$platform) 65 | ) { 66 | rtools44_aarch64_home <- Sys.getenv( 67 | "RTOOLS44_AARCH64_HOME", 68 | "C:\\rtools44-aarch64" 69 | ) 70 | if (file.exists(file.path(rtools44_aarch64_home, "usr", "bin"))) { 71 | if (debug) { 72 | cat("Found in Rtools 4.4 (aarch64) installation folder\n") 73 | } 74 | rtools_path_set(rtools(rtools44_aarch64_home, "4.4")) 75 | return(TRUE) 76 | } 77 | } 78 | 79 | # R 4.4.0 or later 80 | if ( 81 | getRversion() >= "4.4.0" && 82 | getRversion() < "4.5.0" && 83 | !grepl("aarch", R.version$platform) 84 | ) { 85 | rtools44_home <- Sys.getenv("RTOOLS44_HOME", "C:\\rtools44") 86 | if (file.exists(file.path(rtools44_home, "usr", "bin"))) { 87 | if (debug) { 88 | cat("Found in Rtools 4.4 installation folder\n") 89 | } 90 | rtools_path_set(rtools(rtools44_home, "4.4")) 91 | return(TRUE) 92 | } 93 | } 94 | 95 | # R 4.3.0 or later on ARM64 96 | if ( 97 | getRversion() >= "4.3.0" && 98 | getRversion() < "4.4.0" && 99 | grepl("aarch", R.version$platform) 100 | ) { 101 | rtools43_aarch64_home <- Sys.getenv( 102 | "RTOOLS43_AARCH64_HOME", 103 | "C:\\rtools43-aarch64" 104 | ) 105 | if (file.exists(file.path(rtools43_aarch64_home, "usr", "bin"))) { 106 | if (debug) { 107 | cat("Found in Rtools 4.3 (aarch64) installation folder\n") 108 | } 109 | rtools_path_set(rtools(rtools43_aarch64_home, "4.3")) 110 | return(TRUE) 111 | } 112 | } 113 | 114 | # R 4.3.0 or later 115 | if ( 116 | getRversion() >= "4.3.0" && 117 | getRversion() < "4.4.0" && 118 | !grepl("aarch", R.version$platform) 119 | ) { 120 | rtools43_home <- Sys.getenv("RTOOLS43_HOME", "C:\\rtools43") 121 | if (file.exists(file.path(rtools43_home, "usr", "bin"))) { 122 | if (debug) { 123 | cat("Found in Rtools 4.3 installation folder\n") 124 | } 125 | rtools_path_set(rtools(rtools43_home, "4.3")) 126 | return(TRUE) 127 | } 128 | } 129 | 130 | # R 4.2.x or later and ucrt? 131 | ucrt <- is_ucrt() 132 | if (getRversion() >= "4.2.0" && getRversion() < "4.3.0") { 133 | if (ucrt) { 134 | rtools42_home <- Sys.getenv("RTOOLS42_HOME", "C:\\rtools42") 135 | if (file.exists(file.path(rtools42_home, "usr", "bin"))) { 136 | if (debug) { 137 | cat("Found in Rtools 4.2 installation folder\n") 138 | } 139 | rtools_path_set(rtools(rtools42_home, "4.2")) 140 | return(TRUE) 141 | } 142 | } 143 | } 144 | 145 | # In R 4.0 we can use RTOOLS40_HOME, recent versions of Rtools40 work fine 146 | # with ucrt as well, currently. 147 | if (getRversion() >= "4.0.0" && getRversion() < "4.3.0") { 148 | rtools40_home <- Sys.getenv("RTOOLS40_HOME", "C:\\rtools40") 149 | fld <- if (ucrt) "ucrt64" else "usr" 150 | if (file.exists(file.path(rtools40_home, fld, "bin"))) { 151 | if (debug) { 152 | cat("Found in Rtools 4.0 installation folder\n") 153 | } 154 | rtools_path_set(rtools(rtools40_home, "4.0")) 155 | return(TRUE) 156 | } 157 | } 158 | 159 | # First, R CMD config CC -------------------------------------------- 160 | # This does not work if 'make' is not yet on the path 161 | from_config <- scan_config_for_rtools(debug) 162 | if (is_compatible(from_config)) { 163 | if (debug) { 164 | cat("Found compatible gcc from R CMD config CC\n") 165 | } 166 | rtools_path_set(from_config) 167 | return(TRUE) 168 | } 169 | 170 | # Next, try the path ------------------------------------------------ 171 | from_path <- scan_path_for_rtools(debug) 172 | if (is_compatible(from_path)) { 173 | if (debug) { 174 | cat("Found compatible gcc on path\n") 175 | } 176 | rtools_path_set(from_path) 177 | return(TRUE) 178 | } 179 | 180 | if (!is.null(from_path)) { 181 | # Installed 182 | if (is.null(from_path$version)) { 183 | # but not from rtools 184 | if (debug) { 185 | cat("gcc and ls on path, assuming set up is correct\n") 186 | } 187 | return(TRUE) 188 | } else { 189 | # Installed, but not compatible 190 | needed <- rtools_needed() 191 | message( 192 | "WARNING: Rtools ", 193 | from_path$version, 194 | " found on the path", 195 | " at ", 196 | from_path$path, 197 | " is not compatible with R ", 198 | getRversion(), 199 | ".\n\n", 200 | "Please download and install ", 201 | needed, 202 | " from ", 203 | rtools_url(needed), 204 | ", remove the incompatible version from your PATH." 205 | ) 206 | return(invisible(FALSE)) 207 | } 208 | } 209 | 210 | # Next, try the registry -------------------------------------------------- 211 | registry_candidates <- scan_registry_for_rtools(debug) 212 | 213 | if (length(registry_candidates) == 0) { 214 | # Not on path or in registry, so not installled 215 | needed <- rtools_needed() 216 | message( 217 | "WARNING: Rtools is required to build R packages, but is not ", 218 | "currently installed.\n\n", 219 | "Please download and install ", 220 | needed, 221 | " from ", 222 | rtools_url(needed), 223 | "." 224 | ) 225 | return(invisible(FALSE)) 226 | } 227 | 228 | from_registry <- Find(is_compatible, registry_candidates, right = TRUE) 229 | if (is.null(from_registry)) { 230 | # In registry, but not compatible. 231 | versions <- vapply(registry_candidates, function(x) x$version, character(1)) 232 | needed <- rtools_needed() 233 | message( 234 | "WARNING: Rtools is required to build R packages, but no version ", 235 | "of Rtools compatible with R ", 236 | getRversion(), 237 | " was found. ", 238 | "(Only the following incompatible version(s) of Rtools were found: ", 239 | paste(versions, collapse = ", "), 240 | ")\n\n", 241 | "Please download and install ", 242 | needed, 243 | " from ", 244 | rtools_url(needed), 245 | "." 246 | ) 247 | return(invisible(FALSE)) 248 | } 249 | 250 | # On Rtools 3.x do an extra check if the installed version is accurate. 251 | # With rtools40 this is no longer needed (it doesn't have a Version.txt) 252 | if (isTRUE(from_registry$version < "4")) { 253 | installed_ver <- installed_version(from_registry$path, debug = debug) 254 | if (is.null(installed_ver)) { 255 | # Previously installed version now deleted 256 | needed <- rtools_needed() 257 | message( 258 | "WARNING: Rtools is required to build R packages, but the ", 259 | "version of Rtools previously installed in ", 260 | from_registry$path, 261 | " has been deleted.\n\n", 262 | "Please download and install ", 263 | needed, 264 | " from ", 265 | rtools_url(needed), 266 | "." 267 | ) 268 | return(invisible(FALSE)) 269 | } 270 | 271 | if (installed_ver != from_registry$version) { 272 | # Installed version doesn't match registry version 273 | needed <- rtools_needed() 274 | message( 275 | "WARNING: Rtools is required to build R packages, but no version ", 276 | "of Rtools compatible with R ", 277 | getRversion(), 278 | " was found. ", 279 | "Rtools ", 280 | from_registry$version, 281 | " was previously installed in ", 282 | from_registry$path, 283 | " but now that directory contains Rtools ", 284 | installed_ver, 285 | ".\n\n", 286 | "Please download and install ", 287 | needed, 288 | " from ", 289 | rtools_url(needed), 290 | "." 291 | ) 292 | return(invisible(FALSE)) 293 | } 294 | } 295 | 296 | # Otherwise it must be ok :) 297 | 298 | # Recently Rtools is versioned properly 299 | from_registry$version <- sub( 300 | "^([0-9]+[.][0-9]+)[.].*$", 301 | "\\1", 302 | from_registry$version 303 | ) 304 | rtools_path_set(from_registry) 305 | TRUE 306 | } 307 | 308 | is_ucrt <- function() { 309 | identical(R.Version()$crt, "ucrt") 310 | } 311 | 312 | #' @rdname has_rtools 313 | #' @usage NULL 314 | #' @export 315 | find_rtools <- has_rtools 316 | 317 | #' @rdname has_rtools 318 | #' @usage NULL 319 | #' @export 320 | setup_rtools <- has_rtools 321 | 322 | #' @export 323 | #' @rdname has_rtools 324 | check_rtools <- function(debug = FALSE) { 325 | if (is_windows() && !has_rtools(debug = debug)) { 326 | stop("Rtools is not installed.", call. = FALSE) 327 | } 328 | 329 | TRUE 330 | } 331 | 332 | installed_version <- function(path, debug) { 333 | if (!file.exists(file.path(path, "Rtools.txt"))) { 334 | return(NULL) 335 | } 336 | 337 | # Find the version path 338 | version_path <- file.path(path, "VERSION.txt") 339 | if (debug) { 340 | cat("VERSION.txt\n") 341 | cat(readLines(version_path), "\n") 342 | } 343 | if (!file.exists(version_path)) { 344 | return(NULL) 345 | } 346 | 347 | # Rtools is in the path -- now crack the VERSION file 348 | contents <- NULL 349 | try(contents <- readLines(version_path), silent = TRUE) 350 | if (is.null(contents)) { 351 | return(NULL) 352 | } 353 | 354 | # Extract the version 355 | contents <- gsub("^\\s+|\\s+$", "", contents) 356 | version_re <- "Rtools version (\\d\\.\\d+)\\.[0-9.]+$" 357 | 358 | if (!grepl(version_re, contents)) { 359 | return(NULL) 360 | } 361 | 362 | m <- regexec(version_re, contents) 363 | regmatches(contents, m)[[1]][2] 364 | } 365 | 366 | is_compatible <- function(rtools) { 367 | if (is.null(rtools)) { 368 | return(FALSE) 369 | } 370 | if (is.null(rtools$version)) { 371 | return(FALSE) 372 | } 373 | 374 | stopifnot(is.rtools(rtools)) 375 | version <- rtools$version 376 | version <- sub("^([0-9]+[.][0-9]+)[.].*$", "\\1", version) 377 | info <- version_info[[version]] 378 | if (is.null(info)) { 379 | return(FALSE) 380 | } 381 | 382 | r_version <- getRversion() 383 | r_version >= info$version_min && r_version <= info$version_max 384 | } 385 | 386 | rtools <- function(path, version, ...) { 387 | structure(list(version = version, path = path, ...), class = "rtools") 388 | } 389 | is.rtools <- function(x) inherits(x, "rtools") 390 | 391 | #' Retrieve a text string with the rtools version needed 392 | #' 393 | #' @keywords internal 394 | #' @export 395 | rtools_needed <- function(r_version = getRversion()) { 396 | vi <- version_info 397 | vi$custom <- NULL 398 | 399 | for (i in rev(seq_along(vi))) { 400 | version <- names(vi)[i] 401 | info <- vi[[i]] 402 | ok <- r_version >= info$version_min && r_version <= info$version_max 403 | if (ok) { 404 | return(paste("Rtools", version)) 405 | } 406 | } 407 | "the appropriate version of Rtools" 408 | } 409 | 410 | rtools_url <- function(needed) { 411 | "https://cran.r-project.org/bin/windows/Rtools/" 412 | } 413 | -------------------------------------------------------------------------------- /R/styles.R: -------------------------------------------------------------------------------- 1 | # This is from https://github.com/r-lib/rcmdcheck/blob/7ee14764c2b17ee2c2f4131a9e19d1b56a66ed0f/R/styles.R 2 | style <- function(..., sep = "") { 3 | args <- list(...) 4 | st <- names(args) 5 | 6 | styles <- list( 7 | "ok" = cli::col_green, 8 | "note" = cli::make_ansi_style("orange"), 9 | "warn" = function(x) cli::style_bold(cli::make_ansi_style("orange")(x)), 10 | "err" = cli::col_red, 11 | "pale" = cli::make_ansi_style("darkgrey"), 12 | "timing" = cli::make_ansi_style("cyan") 13 | ) 14 | 15 | nms <- names(args) 16 | x <- lapply(seq_along(args), function(i) { 17 | if (nzchar(nms[i])) styles[[nms[i]]](args[[i]]) else args[[i]] 18 | }) 19 | 20 | paste(unlist(x), collapse = sep) 21 | } 22 | -------------------------------------------------------------------------------- /R/time.R: -------------------------------------------------------------------------------- 1 | format_time <- local({ 2 | assert_diff_time <- function(x) { 3 | stopifnot(inherits(x, "difftime")) 4 | } 5 | 6 | parse_ms <- function(ms) { 7 | stopifnot(is.numeric(ms)) 8 | 9 | data.frame( 10 | days = floor(ms / 86400000), 11 | hours = floor((ms / 3600000) %% 24), 12 | minutes = floor((ms / 60000) %% 60), 13 | seconds = round((ms / 1000) %% 60, 1) 14 | ) 15 | } 16 | 17 | first_positive <- function(x) which(x > 0)[1] 18 | 19 | trim <- function(x) gsub("^\\s+|\\s+$", "", x) 20 | 21 | pretty_ms <- function(ms, compact = FALSE) { 22 | stopifnot(is.numeric(ms)) 23 | 24 | parsed <- t(parse_ms(ms)) 25 | 26 | if (compact) { 27 | units <- c("d", "h", "m", "s") 28 | parsed2 <- parsed 29 | parsed2[] <- paste0(parsed, units) 30 | idx <- cbind( 31 | apply(parsed, 2, first_positive), 32 | seq_len(length(ms)) 33 | ) 34 | tmp <- paste0("~", parsed2[idx]) 35 | 36 | # handle NAs 37 | tmp[is.na(parsed2[idx])] <- NA_character_ 38 | tmp 39 | } else { 40 | ## Exact for small ones 41 | exact <- paste0(ceiling(ms), "ms") 42 | exact[is.na(ms)] <- NA_character_ 43 | 44 | ## Approximate for others, in seconds 45 | merge_pieces <- function(pieces) { 46 | ## handle NAs 47 | if (all(is.na(pieces))) { 48 | return(NA_character_) 49 | } 50 | 51 | ## handle non-NAs 52 | paste0( 53 | if (pieces[1]) paste0(pieces[1], "d "), 54 | if (pieces[2]) paste0(pieces[2], "h "), 55 | if (pieces[3]) paste0(pieces[3], "m "), 56 | if (pieces[4]) paste0(pieces[4], "s ") 57 | ) 58 | } 59 | approx <- trim(apply(parsed, 2, merge_pieces)) 60 | 61 | ifelse(ms < 1000, exact, approx) 62 | } 63 | } 64 | 65 | pretty_sec <- function(sec, compact = FALSE) { 66 | pretty_ms(sec * 1000, compact = compact) 67 | } 68 | 69 | pretty_dt <- function(dt, compact = FALSE) { 70 | assert_diff_time(dt) 71 | 72 | units(dt) <- "secs" 73 | 74 | pretty_sec(as.vector(dt), compact = compact) 75 | } 76 | 77 | structure( 78 | list( 79 | .internal = environment(), 80 | pretty_ms = pretty_ms, 81 | pretty_sec = pretty_sec, 82 | pretty_dt = pretty_dt 83 | ), 84 | class = c("standalone_time", "standalone") 85 | ) 86 | }) 87 | -------------------------------------------------------------------------------- /R/utils.R: -------------------------------------------------------------------------------- 1 | dir.exists <- function(x) { 2 | res <- file.exists(x) & file.info(x)$isdir 3 | stats::setNames(res, x) 4 | } 5 | 6 | pkg_path <- function(path = ".") { 7 | find_package_root(path) 8 | } 9 | 10 | pkg_name <- function(path = ".") { 11 | desc::desc_get("Package", pkg_path(path))[[1]] 12 | } 13 | 14 | gcc_arch <- function() { 15 | if (Sys.getenv("R_ARCH") == "/i386") "32" else "64" 16 | } 17 | 18 | is_windows <- function() { 19 | .Platform$OS.type == "windows" 20 | } 21 | 22 | is_string <- function(x) { 23 | is.character(x) && length(x) == 1 && !is.na(x) 24 | } 25 | 26 | is_na <- function(x) { 27 | identical(x, NA) || 28 | identical(x, NA_integer_) || 29 | identical(x, NA_character_) || 30 | identical(x, NA_real_) || 31 | identical(x, NA_complex_) 32 | } 33 | 34 | is_dir <- function(x) { 35 | isTRUE(file.info(x)$isdir) 36 | } 37 | 38 | # This is tools::makevars_user, provided here for backwards compatibility with older versions of R 39 | makevars_user <- function() { 40 | m <- character() 41 | if (.Platform$OS.type == "windows") { 42 | if (!is.na(f <- Sys.getenv("R_MAKEVARS_USER", NA_character_))) { 43 | if (file.exists(f)) { 44 | m <- f 45 | } 46 | } else if ( 47 | (Sys.getenv("R_ARCH") == "/x64") && 48 | file.exists(f <- path.expand("~/.R/Makevars.win64")) 49 | ) { 50 | m <- f 51 | } else if (file.exists(f <- path.expand("~/.R/Makevars.win"))) { 52 | m <- f 53 | } else if (file.exists(f <- path.expand("~/.R/Makevars"))) { 54 | m <- f 55 | } 56 | } else { 57 | if (!is.na(f <- Sys.getenv("R_MAKEVARS_USER", NA_character_))) { 58 | if (file.exists(f)) { 59 | m <- f 60 | } 61 | } else if ( 62 | file.exists( 63 | f <- path.expand(paste0( 64 | "~/.R/Makevars-", 65 | Sys.getenv("R_PLATFORM") 66 | )) 67 | ) 68 | ) { 69 | m <- f 70 | } else if (file.exists(f <- path.expand("~/.R/Makevars"))) { 71 | m <- f 72 | } 73 | } 74 | m 75 | } 76 | 77 | last_char <- function(x) { 78 | l <- nchar(x) 79 | substr(x, l, l) 80 | } 81 | 82 | cat0 <- function(..., sep = "") { 83 | cat(..., sep = "") 84 | } 85 | 86 | is_flag <- function(x) { 87 | is.logical(x) && length(x) == 1 && !is.na(x) 88 | } 89 | 90 | flag_true_values <- c("true", "yes", "on", "1") 91 | flag_false_values <- c("false", "no", "off", "0") 92 | 93 | interpret_envvar_flag <- function(name, default = "false") { 94 | env <- tolower(Sys.getenv(name, default)) 95 | if (env %in% flag_true_values) { 96 | return(TRUE) 97 | } 98 | if (env %in% flag_false_values) { 99 | return(FALSE) 100 | } 101 | if (is.na(env)) { 102 | return(NA) 103 | } 104 | 105 | stop(cli::format_error( 106 | "The {.envvar {name}} environment variable must be {.code true} or 107 | {.code false}, if set." 108 | )) 109 | } 110 | 111 | get_config_flag_value <- function(name, default = FALSE) { 112 | option_name <- paste0("pkg.build_", tolower(name)) 113 | opt <- getOption(option_name, NULL) 114 | if (!is.null(opt)) { 115 | if (!is_flag(opt)) { 116 | stop(cli::format_error( 117 | "The {.code {option_name}} option must be {.code TRUE} or 118 | {.code FALSE}, if set." 119 | )) 120 | } 121 | return(opt) 122 | } 123 | 124 | envvar_name <- paste0("PKG_BUILD_", toupper(name)) 125 | interpret_envvar_flag(envvar_name, default = tolower(as.character(default))) 126 | } 127 | 128 | should_stop_for_warnings <- function() { 129 | get_config_flag_value("stop_for_warnings") 130 | } 131 | 132 | isFALSE <- function(x) { 133 | is.logical(x) && length(x) == 1L && !is.na(x) && !x 134 | } 135 | 136 | should_add_compiler_flags <- function() { 137 | val <- getOption("pkg.build_extra_flags", NULL) 138 | if (isTRUE(val)) { 139 | return(TRUE) 140 | } 141 | if (isFALSE(val)) { 142 | return(FALSE) 143 | } 144 | if (identical(val, "missing")) { 145 | return(length(makevars_user()) == 0) 146 | } 147 | if (!is.null(val)) { 148 | if (!is_string(val)) { 149 | stop(cli::format_error(c( 150 | "Invalid {.code pkg.build_extra_flags} option.", 151 | i = "It must be {.code TRUE}, {.code FALSE} or {.str missing}, not 152 | {.type {val}}." 153 | ))) 154 | } else { 155 | stop(cli::format_error(c( 156 | "Invalid {.code pkg_build_extra_flags} option.", 157 | i = "It must be {.code TRUE}, {.code FALSE} or {.str missing}, not 158 | {.str {val}}." 159 | ))) 160 | } 161 | } 162 | 163 | val <- Sys.getenv("PKG_BUILD_EXTRA_FLAGS", "true") 164 | if (val %in% flag_true_values) { 165 | return(TRUE) 166 | } 167 | if (val %in% flag_false_values) { 168 | return(FALSE) 169 | } 170 | if (val %in% "missing") { 171 | return(length(makevars_user()) == 0) 172 | } 173 | 174 | stop(cli::format_error(c( 175 | "Invalid {.envvar PKG_BUILD_EXTRA_FLAGS} environment variable.", 176 | i = "Must be one of {.code true}, {.code false} or {.code missing}." 177 | ))) 178 | } 179 | 180 | get_desc_config_flag <- function(path, name) { 181 | name <- paste0("Config/build/", name) 182 | val <- desc::desc_get(name, file = path) 183 | if (is.na(val)) { 184 | return(NULL) 185 | } 186 | lval <- tolower(val) 187 | if (lval %in% flag_true_values) { 188 | return(TRUE) 189 | } 190 | if (lval %in% flag_false_values) { 191 | return(FALSE) 192 | } 193 | 194 | stop(cli::format_error( 195 | "The {.code {name}} entry in {.path DESCRIPTION} must be {.code TRUE} 196 | or {.code FALSE}.", 197 | "i" = "It is {.val {val}}." 198 | )) 199 | } 200 | 201 | mkdirp <- function(path, mode = NULL) { 202 | if (file.exists(path)) { 203 | if (file.info(path)$isdir) { 204 | if (is.null(mode)) { 205 | return() 206 | } 207 | mode <- as.octmode(mode) 208 | emode <- as.octmode(file.info(path)$mode) 209 | if (emode == mode) { 210 | return() 211 | } 212 | ret <- Sys.chmod(path, mode, use_umask = FALSE) 213 | if (!ret) { 214 | stop(cli::format_error(c( 215 | "Path {.path {path}} exists, but could not update mode to 216 | {.code {mode}} from {.code {emode}}." 217 | ))) 218 | } 219 | return() 220 | } 221 | stop(cli::format_error(c( 222 | "Could not create directory {.path {path}}.", 223 | i = "Path already exists, but it is not a directory." 224 | ))) 225 | } 226 | 227 | if (is.null(mode)) mode <- "0777" 228 | wrg <- NULL 229 | withCallingHandlers( 230 | ret <- dir.create( 231 | path, 232 | showWarnings = TRUE, 233 | recursive = TRUE, 234 | mode = mode 235 | ), 236 | warning = function(w) { 237 | wrg <<- w 238 | if (!is.null(findRestart("muffleWarning"))) { 239 | invokeRestart("muffleWarning") 240 | } 241 | } 242 | ) 243 | 244 | if (!ret) { 245 | stop(cli::format_error(c( 246 | "Could not create directory {.path {path}}.", 247 | i = if (!is.null(wrg)) { 248 | "From {.fn dir.create}: {conditionMessage(wrg)}." 249 | } else { 250 | "For reasons unknown." 251 | } 252 | ))) 253 | } 254 | } 255 | 256 | verb_for_cli <- function(x) { 257 | x <- gsub("\n", "\f", x, fixed = TRUE) 258 | x <- gsub(" ", "\u00a0", x, fixed = TRUE) 259 | x 260 | } 261 | -------------------------------------------------------------------------------- /R/with-debug.R: -------------------------------------------------------------------------------- 1 | #' Temporarily set debugging compilation flags. 2 | #' 3 | #' @param code to execute. 4 | #' @param CFLAGS flags for compiling C code 5 | #' @param CXXFLAGS flags for compiling C++ code 6 | #' @param FFLAGS flags for compiling Fortran code. 7 | #' @param FCFLAGS flags for Fortran 9x code. 8 | #' @inheritParams compiler_flags 9 | #' @family debugging flags 10 | #' @export 11 | #' @examples 12 | #' flags <- names(compiler_flags(TRUE)) 13 | #' with_debug(Sys.getenv(flags)) 14 | #' \dontrun{ 15 | #' install("mypkg") 16 | #' with_debug(install("mypkg")) 17 | #' } 18 | with_debug <- function( 19 | code, 20 | CFLAGS = NULL, 21 | CXXFLAGS = NULL, 22 | FFLAGS = NULL, 23 | FCFLAGS = NULL, 24 | debug = TRUE 25 | ) { 26 | defaults <- compiler_flags(debug = debug) 27 | flags <- c( 28 | CFLAGS = CFLAGS, 29 | CXXFLAGS = CXXFLAGS, 30 | FFLAGS = FFLAGS, 31 | FCFLAGS = FCFLAGS 32 | ) 33 | 34 | flags <- unlist(utils::modifyList(as.list(defaults), as.list(flags))) 35 | 36 | withr_with_makevars(flags, code) 37 | } 38 | 39 | #' Tools for testing pkgbuild 40 | #' 41 | #' `with_compiler` temporarily disables code compilation by setting 42 | #' `CC`, `CXX`, makevars to `test`. `without_cache` 43 | #' resets the cache before and after running `code`. 44 | #' 45 | #' @param code Code to execute with broken compilers 46 | #' @export 47 | without_compiler <- function(code) { 48 | flags <- c( 49 | CC = "test", 50 | CXX = "test", 51 | CXX11 = "test", 52 | FC = "test" 53 | ) 54 | 55 | if (is_windows()) { 56 | without_cache({ 57 | cache_set("rtools_path", "") 58 | withr_with_makevars(flags, code) 59 | }) 60 | } else { 61 | without_cache({ 62 | withr_with_makevars(flags, code) 63 | }) 64 | } 65 | } 66 | 67 | 68 | #' @export 69 | #' @rdname without_compiler 70 | without_cache <- function(code) { 71 | cache_reset() 72 | on.exit(cache_reset()) 73 | 74 | code 75 | } 76 | 77 | 78 | #' @export 79 | #' @rdname without_compiler 80 | without_latex <- function(code) { 81 | withr_with_options(list(PKGBUILD_TEST_FIXTURE_HAS_LATEX = FALSE), code) 82 | } 83 | 84 | 85 | #' @export 86 | #' @rdname without_compiler 87 | with_latex <- function(code) { 88 | withr_with_options(list(PKGBUILD_TEST_FIXTURE_HAS_LATEX = TRUE), code) 89 | } 90 | -------------------------------------------------------------------------------- /R/withr.R: -------------------------------------------------------------------------------- 1 | withr_with_makevars <- function(new, code, path = makevars_user()) { 2 | makevars_file <- tempfile() 3 | on.exit(unlink(makevars_file), add = TRUE) 4 | force(path) 5 | withr_with_envvar(c(R_MAKEVARS_USER = makevars_file), { 6 | withr_set_makevars(new, path, makevars_file) 7 | force(code) 8 | }) 9 | } 10 | 11 | withr_set_makevars <- function( 12 | variables, 13 | old_path = withr_makevars_user(), 14 | new_path = tempfile() 15 | ) { 16 | if (length(variables) == 0) { 17 | return() 18 | } 19 | stopifnot(withr_is_named(variables)) 20 | old <- NULL 21 | if (length(old_path) == 1 && file.exists(old_path)) { 22 | lines <- readLines(old_path) 23 | old <- lines 24 | lines <- c(old, paste(names(variables), variables, sep = " += ")) 25 | } else { 26 | lines <- paste(names(variables), variables, sep = " += ") 27 | } 28 | if (!identical(old, lines)) { 29 | writeLines(con = new_path, lines) 30 | } 31 | old 32 | } 33 | 34 | withr_makevars_user <- function() { 35 | tools::makevars_user() 36 | } 37 | 38 | withr_is_named <- function(x) { 39 | !is.null(names(x)) && all(names(x) != "") 40 | } 41 | 42 | # ------------------------------------------------------------------------- 43 | 44 | withr_get_envvar <- function(envs, action = "replace") { 45 | envs <- withr_as_envvars(envs) 46 | Sys.getenv(names(envs), names = TRUE, unset = NA) 47 | } 48 | 49 | withr_set_envvar <- function(envs, action = "replace") { 50 | envs <- withr_as_envvars(envs) 51 | stopifnot(is.character(action), length(action) == 1) 52 | action <- match.arg(action, c("replace", "prefix", "suffix")) 53 | if (length(envs) == 0) { 54 | return() 55 | } 56 | old <- Sys.getenv(names(envs), names = TRUE, unset = NA) 57 | set <- !is.na(envs) 58 | both_set <- set & !is.na(old) 59 | if (any(both_set)) { 60 | if (action == "prefix") { 61 | envs[both_set] <- paste(envs[both_set], old[both_set]) 62 | } else if (action == "suffix") { 63 | envs[both_set] <- paste(old[both_set], envs[both_set]) 64 | } 65 | } 66 | if (any(set)) do.call("Sys.setenv", as.list(envs[set])) 67 | if (any(!set)) Sys.unsetenv(names(envs)[!set]) 68 | invisible(old) 69 | } 70 | 71 | withr_with_envvar <- function(new, code, action = "replace") { 72 | old <- withr_get_envvar(envs = new, action = action) 73 | on.exit(withr_set_envvar(old)) 74 | withr_set_envvar(envs = new, action = action) 75 | force(code) 76 | } 77 | 78 | withr_as_envvars <- function(envs) { 79 | if (length(envs) == 0) { 80 | return(envs) 81 | } 82 | stopifnot(withr_is_named(envs)) 83 | envs[withr_vlapply(envs, is.null)] <- NA 84 | envs <- envs[!duplicated(names(envs), fromLast = TRUE)] 85 | envs 86 | } 87 | 88 | withr_vlapply <- function(X, FUN, ...) { 89 | vapply(X, FUN, FUN.VALUE = logical(1), ...) 90 | } 91 | 92 | withr_is_named <- function(x) { 93 | !is.null(names(x)) && all(names(x) != "") 94 | } 95 | 96 | # ------------------------------------------------------------------------- 97 | 98 | withr_with_temp_libpaths <- function(code, action = "prefix") { 99 | old <- .libPaths() 100 | on.exit(.libPaths(old)) 101 | withr_set_temp_libpaths(action = action) 102 | force(code) 103 | } 104 | 105 | withr_set_temp_libpaths <- function(action = "prefix") { 106 | paths <- tempfile("temp_libpath") 107 | dir.create(paths) 108 | withr_set_libpaths(paths, action = action) 109 | } 110 | 111 | withr_set_libpaths <- function(paths, action = "replace") { 112 | paths <- withr_as_character(paths) 113 | paths <- normalizePath(paths, mustWork = TRUE) 114 | old <- .libPaths() 115 | paths <- withr_merge_new(old, paths, action) 116 | .libPaths(paths) 117 | invisible(old) 118 | } 119 | 120 | withr_merge_new <- function(old, new, action, merge_fun = c) { 121 | action <- match.arg(action, c("replace", "prefix", "suffix")) 122 | if (action == "suffix") { 123 | new <- merge_fun(old, new) 124 | } else if (action == "prefix") { 125 | new <- merge_fun(new, old) 126 | } 127 | new 128 | } 129 | 130 | withr_as_character <- function(x) { 131 | nms <- names(x) 132 | res <- as.character(x) 133 | names(res) <- nms 134 | res 135 | } 136 | 137 | # ------------------------------------------------------------------------- 138 | 139 | withr_with_options <- function(new, code) { 140 | old <- withr_set_options(new_options = new) 141 | on.exit(withr_reset_options(old)) 142 | force(code) 143 | } 144 | 145 | withr_set_options <- function(new_options) { 146 | do.call(options, as.list(new_options)) 147 | } 148 | 149 | withr_reset_options <- function(old_options) { 150 | options(old_options) 151 | } 152 | 153 | # ------------------------------------------------------------------------- 154 | 155 | withr_with_path <- function( 156 | new, 157 | code, 158 | action = c("prefix", "suffix", "replace") 159 | ) { 160 | old <- withr_get_path(path = new, action = action) 161 | on.exit((function(old) withr_set_path(old, "replace"))(old)) 162 | withr_set_path(path = new, action = action) 163 | force(code) 164 | } 165 | 166 | withr_set_path <- function(path, action = c("prefix", "suffix", "replace")) { 167 | action <- match.arg(action) 168 | path <- withr_as_character(path) 169 | path <- normalizePath(path, mustWork = FALSE) 170 | old <- withr_get_path() 171 | path <- withr_merge_new(old, path, action) 172 | path <- paste(path, collapse = .Platform$path.sep) 173 | Sys.setenv(PATH = path) 174 | invisible(old) 175 | } 176 | 177 | withr_merge_new <- function(old, new, action, merge_fun = c) { 178 | action <- match.arg(action, c("replace", "prefix", "suffix")) 179 | if (action == "suffix") { 180 | new <- merge_fun(old, new) 181 | } else if (action == "prefix") { 182 | new <- merge_fun(new, old) 183 | } 184 | new 185 | } 186 | 187 | withr_get_path <- function(...) { 188 | strsplit(Sys.getenv("PATH"), .Platform$path.sep)[[1]] 189 | } 190 | 191 | withr_local_path <- function( 192 | new = list(), 193 | action = c("prefix", "suffix", "replace"), 194 | .local_envir = parent.frame() 195 | ) { 196 | old <- withr_get_path(path = new, action = action) 197 | withr_defer( 198 | (function(old) withr_set_path(old, "replace"))(old), 199 | frame = .local_envir 200 | ) 201 | withr_set_path(path = new, action = action) 202 | invisible(old) 203 | } 204 | 205 | # ------------------------------------------------------------------------- 206 | 207 | withr_defer <- function(expr, frame = parent.frame(), after = FALSE) { 208 | thunk <- as.call(list(function() expr)) 209 | do.call(on.exit, list(thunk, add = TRUE, after = after), envir = frame) 210 | } 211 | 212 | # ------------------------------------------------------------------------- 213 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pkgbuild 2 | 3 | 4 | [![R-CMD-check](https://github.com/r-lib/pkgbuild/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/r-lib/pkgbuild/actions/workflows/R-CMD-check.yaml) 5 | [![Codecov test coverage](https://codecov.io/gh/r-lib/pkgbuild/graph/badge.svg)](https://app.codecov.io/gh/r-lib/pkgbuild) 6 | 7 | 8 | The goal of pkgbuild is to make it easy to build packages with compiled code. It provides tools to configure your R session, and check that everything is working ok. If you are using RStudio, it also helps you trigger automatic install of the build tools. 9 | 10 | ## Installation 11 | 12 | Install the released version from CRAN 13 | 14 | ```r 15 | install.packages("pkgbuild") 16 | ``` 17 | 18 | Or install the development version from GitHub: 19 | 20 | ```r 21 | # install.packages("pak") 22 | pak::pak("r-lib/pkgbuild") 23 | ``` 24 | 25 | ## Example 26 | 27 | ``` r 28 | # Check that you have the build tools installed 29 | pkgbuild::check_build_tools(debug = TRUE) 30 | 31 | # Build a package 32 | pkgbuild::build("/path/to/my/package") 33 | 34 | # Run your own code in an environment guaranteed to 35 | # have build tools available 36 | pkgbuild::with_build_tools(my_code) 37 | ``` 38 | 39 | ## Configuration 40 | 41 | ### `DESCRIPTION` entries 42 | 43 | * `Config/build/clean-inst-doc` can be set to `FALSE` to avoid cleaning up 44 | `inst/doc` when building a source package. Set it to `TRUE` to force a 45 | cleanup. See the `clean_doc` argument of `build()` as well. 46 | 47 | * `Config/build/copy-method` can be used to avoid copying large directories 48 | in `R CMD build`. It works by copying (or linking) the files of the 49 | package to a temporary directory, leaving out the (possibly large) files 50 | that are not part of the package. Possible values: 51 | 52 | - `none`: pkgbuild does not copy the package tree. This is the default. 53 | - `copy`: the package files are copied to a temporary directory before 54 | ` R CMD build`. 55 | - `link`: the package files are symbolic linked to a temporary directory 56 | before `R CMD build`. Windows does not have symbolic links, so on Windows 57 | this is equivalent to `copy`. 58 | 59 | You can also use the `pkg.build_copy_method` option or the 60 | `PKG_BUILD_COPY_METHOD` environment variable to set the copy method. 61 | The option is consulted first, then the `DESCRIPTION` entry, then the 62 | environment variable. 63 | 64 | * `Config/build/extra-sources` can be used to define extra source files for 65 | pkgbuild to decide whether a package DLL needs to be recompiled in 66 | `needs_compile()`. The syntax is a comma separated list of file names, 67 | or globs. (See `?utils::glob2rx()`.) E.g. `src/rust/src/*.rs` or `configure*`. 68 | 69 | ### Options 70 | 71 | * `pkg.build_copy_method`: use this option to avoid copying large directories 72 | when building a package. See possible values above, at the 73 | `Config/build/copy-method` `DESCRIPTION` entry. 74 | 75 | * `pkg.build_extra_flags`: set this to `FALSE` to to opt out from adding 76 | debug compiler flags in `compile_dll()`. Takes precedence over the 77 | `PKG_BUILD_EXTRA_FLAGS` environment variable. Possible values: 78 | 79 | - `TRUE`: add extra flags, 80 | - `FALSE`: do not add extra flags, 81 | - `"missing"`: add extra flags if the user does not have a 82 | `$HOME/.R/Makevars` file. 83 | 84 | * `pkg.build_stop_for_warnings`: if it is set to `TRUE`, then pkgbuild will stop 85 | for `R CMD build` errors. It takes precedence over the 86 | `PKG_BUILD_STOP_FOR_WARNINGS` environment variable. 87 | 88 | ### Environment variables 89 | 90 | * `PKG_BUILD_COLOR_DIAGNOSTICS`: set it to `false` to opt out of colored 91 | compiler diagnostics. Set it to `true` to force colored compiler 92 | diagnostics. 93 | 94 | * `PKG_BUILD_COPY_METHOD`: use this environment variable to avoid copying 95 | large directories when building a package. See possible values above, 96 | at the `Config/build/copy-method` `DESCRIPTION` entry. 97 | 98 | * `PKG_BUILD_EXTRA_FLAGS`: set this to `false` to to opt out from adding 99 | debug compiler flags in `compile_dll()`. The `pkg.build_extra_flags` option 100 | takes precedence over this environment variable. Possible values: 101 | 102 | - `"true"`: add extra flags, 103 | - `"false"`: do not add extra flags, 104 | - `"missing"`: add extra flags if the user does not have a 105 | `$HOME/.R/Makevars` file. 106 | 107 | * `PKG_BUILD_STOP_FOR_WARNINGS`: if it is set to `true`, then pkgbuild will stop 108 | for `R CMD build` errors. The `pkg.build_stop_for_warnings` option takes 109 | precedence over this environment variable. 110 | 111 | ## Code of Conduct 112 | 113 | Please note that the pkgbuild project is released with a 114 | [Contributor Code of Conduct](https://pkgbuild.r-lib.org/CODE_OF_CONDUCT.html). 115 | By contributing to this project, you agree to abide by its terms. 116 | -------------------------------------------------------------------------------- /_pkgdown.yml: -------------------------------------------------------------------------------- 1 | url: https://pkgbuild.r-lib.org 2 | template: 3 | package: tidytemplate 4 | bootstrap: 5 5 | includes: 6 | in_header: | 7 | 8 | development: 9 | mode: auto 10 | 11 | -------------------------------------------------------------------------------- /air.toml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/pkgbuild/d2666a845095c05fa0f3f1227007b3f92a0320d9/air.toml -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | 3 | coverage: 4 | status: 5 | project: 6 | default: 7 | target: auto 8 | threshold: 1% 9 | informational: true 10 | patch: 11 | default: 12 | target: auto 13 | threshold: 1% 14 | informational: true 15 | -------------------------------------------------------------------------------- /cran-comments.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/pkgbuild/d2666a845095c05fa0f3f1227007b3f92a0320d9/cran-comments.md -------------------------------------------------------------------------------- /man/build.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/build.R 3 | \name{build} 4 | \alias{build} 5 | \title{Build package} 6 | \usage{ 7 | build( 8 | path = ".", 9 | dest_path = NULL, 10 | binary = FALSE, 11 | vignettes = TRUE, 12 | manual = FALSE, 13 | clean_doc = NULL, 14 | args = NULL, 15 | quiet = FALSE, 16 | needs_compilation = pkg_has_src(path), 17 | compile_attributes = FALSE, 18 | register_routines = FALSE 19 | ) 20 | } 21 | \arguments{ 22 | \item{path}{Path to a package, or within a package.} 23 | 24 | \item{dest_path}{path in which to produce package. If it is an existing 25 | directory, then the output file is placed in \code{dest_path} and named 26 | according to the current R conversions (e.g. \code{.zip} for Windows binary 27 | packages, \code{.tgz} for macOS binary packages, etc). 28 | If it is an existing file, then it will be overwritten. 29 | If \code{dest_path} does not exist, then it is used as a file name. 30 | If \code{NULL}, it defaults to the parent directory of the package.} 31 | 32 | \item{binary}{Produce a binary (\code{--binary}) or source ( 33 | \code{--no-manual --no-resave-data}) version of the package.} 34 | 35 | \item{vignettes, manual}{For source packages: if \code{FALSE}, don't build PDF 36 | vignettes (\code{--no-build-vignettes}) or manual (\code{--no-manual}).} 37 | 38 | \item{clean_doc}{If \code{TRUE}, clean the files in \code{inst/doc} before building 39 | the package. If \code{NULL} and the \code{Config/build/clean-inst-doc} entry is 40 | present in \code{DESCRIPTION}, then that is used. Otherwise, if \code{NULL}, 41 | and interactive, ask to remove the files prior to cleaning. In most 42 | cases cleaning the files is the correct behavior to avoid stale 43 | vignette outputs in the built package.} 44 | 45 | \item{args}{An optional character vector of additional command 46 | line arguments to be passed to \verb{R CMD build} if \code{binary = FALSE}, 47 | or \verb{R CMD install} if \code{binary = TRUE}.} 48 | 49 | \item{quiet}{if \code{TRUE} suppresses output from this function.} 50 | 51 | \item{needs_compilation}{Usually only needed if the packages has 52 | C/C++/Fortran code. By default this is autodetected.} 53 | 54 | \item{compile_attributes}{if \code{TRUE} and the package uses Rcpp, call 55 | \code{\link[Rcpp:compileAttributes]{Rcpp::compileAttributes()}} before building the package. It is ignored 56 | if package does not need compilation.} 57 | 58 | \item{register_routines}{if \code{TRUE} and the package does not use Rcpp, call 59 | register routines with 60 | \code{tools::package_native_routine_registration_skeleton()} before building 61 | the package. It is ignored if package does not need compilation.} 62 | } 63 | \value{ 64 | a string giving the location (including file name) of the built 65 | package 66 | } 67 | \description{ 68 | Building converts a package source directory into a single bundled file. 69 | If \code{binary = FALSE} this creates a \code{tar.gz} package that can 70 | be installed on any platform, provided they have a full development 71 | environment (although packages without source code can typically be 72 | installed out of the box). If \code{binary = TRUE}, the package will have 73 | a platform specific extension (e.g. \code{.zip} for windows), and will 74 | only be installable on the current platform, but no development 75 | environment is needed. 76 | } 77 | \details{ 78 | \subsection{Configuration}{ 79 | \subsection{\code{DESCRIPTION} entries}{ 80 | \itemize{ 81 | \item \code{Config/build/clean-inst-doc} can be set to \code{FALSE} to avoid cleaning up 82 | \code{inst/doc} when building a source package. Set it to \code{TRUE} to force a 83 | cleanup. See the \code{clean_doc} argument. 84 | \item \code{Config/build/copy-method} can be used to avoid copying large 85 | directories in \verb{R CMD build}. It works by copying (or linking) the 86 | files of the package to a temporary directory, leaving out the 87 | (possibly large) files that are not part of the package. Possible 88 | values: 89 | \itemize{ 90 | \item \code{none}: pkgbuild does not copy the package tree. This is the default. 91 | \item \code{copy}: the package files are copied to a temporary directory before 92 | \verb{ R CMD build}. 93 | \item \code{link}: the package files are symbolic linked to a temporary 94 | directory before \verb{R CMD build}. Windows does not have symbolic 95 | links, so on Windows this is equivalent to \code{copy}. 96 | } 97 | 98 | You can also use the \code{pkg.build_copy_method} option or the 99 | \code{PKG_BUILD_COPY_METHOD} environment variable to set the copy method. 100 | The option is consulted first, then the \code{DESCRIPTION} entry, then the 101 | environment variable. 102 | \item \code{Config/build/extra-sources} can be used to define extra source files 103 | for pkgbuild to decide whether a package DLL needs to be recompiled in 104 | \code{needs_compile()}. The syntax is a comma separated list of file names, 105 | or globs. (See \code{\link[utils:glob2rx]{utils::glob2rx()}}.) E.g. \verb{src/rust/src/*.rs} or 106 | \verb{configure*}. 107 | \item \code{Config/build/bootstrap} can be set to \code{TRUE} to run 108 | \verb{Rscript bootstrap.R} in the source directory prior to running subsequent 109 | build steps. 110 | \item \code{Config/build/never-clean} can be set to \code{TRUE} to never add \code{--preclean} 111 | to \verb{R CMD INSTALL}, e.g., when header files have changed. 112 | This helps avoiding rebuilds that can take long for very large C/C++ codebases 113 | and can lead to build failures if object files are out of sync with header files. 114 | Control the dependencies between object files and header files 115 | by adding \verb{include file.d} to \code{Makevars} for each \code{file.c} or \code{file.cpp} source file. 116 | } 117 | } 118 | 119 | \subsection{Options}{ 120 | \itemize{ 121 | \item \code{pkg.build_copy_method}: use this option to avoid copying large 122 | directories when building a package. See possible values above, at the 123 | \code{Config/build/copy-method} \code{DESCRIPTION} entry. 124 | \item \code{pkg.build_stop_for_warnings}: if it is set to \code{TRUE}, then pkgbuild 125 | will stop for \verb{R CMD build} errors. It takes precedence over the 126 | \code{PKG_BUILD_STOP_FOR_WARNINGS} environment variable. 127 | } 128 | } 129 | 130 | \subsection{Environment variables}{ 131 | \itemize{ 132 | \item \code{PKG_BUILD_COLOR_DIAGNOSTICS}: set it to \code{false} to opt out of colored 133 | compiler diagnostics. Set it to \code{true} to force colored compiler 134 | diagnostics. 135 | \item \code{PKG_BUILD_COPY_METHOD}: use this environment variable to avoid copying 136 | large directories when building a package. See possible values above, 137 | at the \code{Config/build/copy-method} \code{DESCRIPTION} entry. 138 | } 139 | 140 | will stop for \verb{R CMD build} errors. The \code{pkg.build_stop_for_warnings} 141 | option takes precedence over this environment variable. 142 | } 143 | 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /man/clean_dll.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/compile-dll.R 3 | \name{clean_dll} 4 | \alias{clean_dll} 5 | \title{Remove compiled objects from /src/ directory} 6 | \usage{ 7 | clean_dll(path = ".") 8 | } 9 | \arguments{ 10 | \item{path}{Path to a package, or within a package.} 11 | } 12 | \description{ 13 | Invisibly returns the names of the deleted files. 14 | } 15 | \seealso{ 16 | \code{\link[=compile_dll]{compile_dll()}} 17 | } 18 | -------------------------------------------------------------------------------- /man/compile_dll.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/compile-dll.R 3 | \name{compile_dll} 4 | \alias{compile_dll} 5 | \title{Compile a .dll/.so from source.} 6 | \usage{ 7 | compile_dll( 8 | path = ".", 9 | force = FALSE, 10 | compile_attributes = pkg_links_to_cpp11(path) || pkg_links_to_rcpp(path), 11 | register_routines = FALSE, 12 | quiet = FALSE, 13 | debug = TRUE 14 | ) 15 | } 16 | \arguments{ 17 | \item{path}{Path to a package, or within a package.} 18 | 19 | \item{force}{If \code{TRUE}, for compilation even if \code{\link[=needs_compile]{needs_compile()}} is 20 | \code{FALSE}.} 21 | 22 | \item{compile_attributes}{if \code{TRUE} and the package uses Rcpp, call 23 | \code{\link[Rcpp:compileAttributes]{Rcpp::compileAttributes()}} before building the package. It is ignored 24 | if package does not need compilation.} 25 | 26 | \item{register_routines}{if \code{TRUE} and the package does not use Rcpp, call 27 | register routines with 28 | \code{tools::package_native_routine_registration_skeleton()} before building 29 | the package. It is ignored if package does not need compilation.} 30 | 31 | \item{quiet}{if \code{TRUE} suppresses output from this function.} 32 | 33 | \item{debug}{If \code{TRUE}, and if no user Makevars is found, then the build 34 | runs without optimisation (\code{-O0}) and with debug symbols (\code{-g}). See 35 | \code{\link[=compiler_flags]{compiler_flags()}} for details. If you have a user Makevars (e.g., 36 | \verb{~/.R/Makevars}) then this argument is ignored.} 37 | } 38 | \description{ 39 | \code{compile_dll} performs a fake R CMD install so code that 40 | works here should work with a regular install (and vice versa). 41 | During compilation, debug flags are set with 42 | \code{\link{compiler_flags}(TRUE)}. 43 | } 44 | \details{ 45 | Invisibly returns the names of the DLL. 46 | \subsection{Configuration}{ 47 | \subsection{Options}{ 48 | \itemize{ 49 | \item \code{pkg.build_extra_flags}: set this to \code{FALSE} to to opt out from adding 50 | debug compiler flags in \code{compile_dll()}. Takes precedence over the 51 | \code{PKG_BUILD_EXTRA_FLAGS} environment variable. Possible values: 52 | \itemize{ 53 | \item \code{TRUE}: add extra flags, 54 | \item \code{FALSE}: do not add extra flags, 55 | \item \code{"missing"}: add extra flags if the user does not have a 56 | \verb{$HOME/.R/Makevars} file. 57 | } 58 | } 59 | } 60 | 61 | \subsection{Environment variables}{ 62 | \itemize{ 63 | \item \code{PKG_BUILD_EXTRA_FLAGS}: set this to \code{false} to to opt out from adding 64 | debug compiler flags in \code{compile_dll()}. The \code{pkg.build_extra_flags} option 65 | takes precedence over this environment variable. Possible values: 66 | \itemize{ 67 | \item \code{"true"}: add extra flags, 68 | \item \code{"false"}: do not add extra flags, 69 | \item \code{"missing"}: add extra flags if the user does not have a 70 | \verb{$HOME/.R/Makevars} file. 71 | } 72 | } 73 | } 74 | 75 | } 76 | } 77 | \note{ 78 | If this is used to compile code that uses Rcpp, you will need to 79 | add the following line to your \code{Makevars} file so that it 80 | knows where to find the Rcpp headers: 81 | 82 | \if{html}{\out{
}}\preformatted{PKG_CPPFLAGS=`$(R_HOME)/bin/Rscript -e 'Rcpp:::CxxFlags()'` 83 | }\if{html}{\out{
}} 84 | } 85 | \seealso{ 86 | \code{\link[=clean_dll]{clean_dll()}} to delete the compiled files. 87 | } 88 | -------------------------------------------------------------------------------- /man/compiler_flags.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/compiler-flags.R 3 | \name{compiler_flags} 4 | \alias{compiler_flags} 5 | \title{Default compiler flags used by devtools.} 6 | \usage{ 7 | compiler_flags(debug = FALSE) 8 | } 9 | \arguments{ 10 | \item{debug}{If \code{TRUE} adds \code{-g -O0} to all flags 11 | (Adding \env{FFLAGS} and \env{FCFLAGS})} 12 | } 13 | \description{ 14 | These default flags enforce good coding practice by ensuring that 15 | \env{CFLAGS} and \env{CXXFLAGS} are set to \code{-Wall -pedantic}. 16 | These tests are run by cran and are generally considered to be good practice. 17 | } 18 | \details{ 19 | By default \code{\link[=compile_dll]{compile_dll()}} is run with \code{compiler_flags(TRUE)}, 20 | and check with \code{compiler_flags(FALSE)}. If you want to avoid the 21 | possible performance penalty from the debug flags, install the package. 22 | } 23 | \examples{ 24 | compiler_flags() 25 | compiler_flags(TRUE) 26 | } 27 | \seealso{ 28 | Other debugging flags: 29 | \code{\link{with_debug}()} 30 | } 31 | \concept{debugging flags} 32 | -------------------------------------------------------------------------------- /man/has_build_tools.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/build-tools.R 3 | \name{has_build_tools} 4 | \alias{has_build_tools} 5 | \alias{check_build_tools} 6 | \alias{with_build_tools} 7 | \alias{local_build_tools} 8 | \title{Are build tools are available?} 9 | \usage{ 10 | has_build_tools(debug = FALSE) 11 | 12 | check_build_tools(debug = FALSE, quiet = FALSE) 13 | 14 | with_build_tools(code, debug = FALSE, required = TRUE) 15 | 16 | local_build_tools( 17 | debug = FALSE, 18 | required = TRUE, 19 | .local_envir = parent.frame() 20 | ) 21 | } 22 | \arguments{ 23 | \item{debug}{If \code{TRUE}, will print out extra information useful for 24 | debugging. If \code{FALSE}, it will use result cached from a previous run.} 25 | 26 | \item{quiet}{if \code{TRUE} suppresses output from this function.} 27 | 28 | \item{code}{Code to rerun in environment where build tools are guaranteed to 29 | exist.} 30 | 31 | \item{required}{If \code{TRUE}, and build tools are not available, 32 | will throw an error. Otherwise will attempt to run \code{code} without 33 | them.} 34 | 35 | \item{.local_envir}{The environment to use for scoping.} 36 | } 37 | \description{ 38 | \code{has_build_tools} returns a logical, \code{check_build_tools} throws 39 | an error. \code{with_build_tools} checks that build tools are available, 40 | then runs \code{code} in an correctly staged environment. 41 | If run interactively from RStudio, and the build tools are not 42 | available these functions will trigger an automated install. 43 | } 44 | \details{ 45 | Errors like \verb{running command '"C:/PROGRA~1/R/R-34~1.2/bin/x64/R" CMD config CC' had status 127} 46 | indicate the code expected Rtools to be on the system PATH. You can 47 | then verify you have rtools installed with \code{has_build_tools()} and 48 | temporarily add Rtools to the PATH \code{with_build_tools({ code })}. 49 | 50 | It is possible to add Rtools to your system PATH manually; you can use 51 | \code{\link[=rtools_path]{rtools_path()}} to show the installed location. However because this 52 | requires manual updating when a new version of Rtools is installed and the 53 | binaries in Rtools may conflict with existing binaries elsewhere on the PATH it 54 | is better practice to use \code{with_build_tools()} as needed. 55 | } 56 | \examples{ 57 | has_build_tools(debug = TRUE) 58 | check_build_tools() 59 | } 60 | \seealso{ 61 | has_rtools 62 | } 63 | -------------------------------------------------------------------------------- /man/has_compiler.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/compiler.R 3 | \name{has_compiler} 4 | \alias{has_compiler} 5 | \alias{check_compiler} 6 | \alias{has_devel} 7 | \title{Is a compiler available?} 8 | \usage{ 9 | has_compiler(debug = FALSE) 10 | 11 | check_compiler(debug = FALSE) 12 | } 13 | \arguments{ 14 | \item{debug}{If \code{TRUE}, will print out extra information useful for 15 | debugging. If \code{FALSE}, it will use result cached from a previous run.} 16 | } 17 | \description{ 18 | These functions check if a small C file can be compiled, linked, loaded 19 | and executed. 20 | 21 | \code{has_compiler()} and \code{has_devel()} return \code{TRUE} or \code{FALSE}. 22 | \code{check_compiler()} and \code{check_devel()} 23 | throw an error if you don't have developer tools installed. 24 | If the \code{"pkgbuild.has_compiler"} option is set to \code{TRUE} or \code{FALSE}, 25 | no check is carried out, and the value of the option is used. 26 | 27 | The implementation is based on a suggestion by Simon Urbanek. 28 | End-users (particularly those on Windows) should generally run 29 | \code{\link[=check_build_tools]{check_build_tools()}} rather than \code{\link[=check_compiler]{check_compiler()}}. 30 | } 31 | \examples{ 32 | has_compiler() 33 | check_compiler() 34 | 35 | with_build_tools(has_compiler()) 36 | } 37 | \seealso{ 38 | \code{\link[=check_build_tools]{check_build_tools()}} 39 | } 40 | -------------------------------------------------------------------------------- /man/has_latex.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/latex.R 3 | \name{has_latex} 4 | \alias{has_latex} 5 | \alias{check_latex} 6 | \title{Is latex installed?} 7 | \usage{ 8 | has_latex() 9 | 10 | check_latex() 11 | } 12 | \description{ 13 | Checks for presence of pdflatex on path. 14 | } 15 | -------------------------------------------------------------------------------- /man/has_rtools.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/rtools-cache.R, R/rtools.R 3 | \name{rtools_path} 4 | \alias{rtools_path} 5 | \alias{has_rtools} 6 | \alias{find_rtools} 7 | \alias{setup_rtools} 8 | \alias{check_rtools} 9 | \title{Is Rtools installed?} 10 | \usage{ 11 | rtools_path() 12 | 13 | has_rtools(debug = FALSE) 14 | 15 | check_rtools(debug = FALSE) 16 | } 17 | \arguments{ 18 | \item{debug}{If \code{TRUE}, will print out extra information useful for 19 | debugging. If \code{FALSE}, it will use result cached from a previous run.} 20 | } 21 | \value{ 22 | Either a visible \code{TRUE} if rtools is found, or an invisible 23 | \code{FALSE} with a diagnostic \code{\link[=message]{message()}}. 24 | As a side-effect the internal package variable \code{rtools_path} is 25 | updated to the paths to rtools binaries. 26 | } 27 | \description{ 28 | To build binary packages on windows, Rtools (found at 29 | \url{https://CRAN.R-project.org/bin/windows/Rtools/}) needs to be on 30 | the path. The default installation process does not add it, so this 31 | script finds it (looking first on the path, then in the registry). 32 | It also checks that the version of rtools matches the version of R. 33 | \code{has_rtools()} determines if Rtools is installed, caching the results. 34 | Afterward, run \code{rtools_path()} to find out where it's installed. 35 | } 36 | \section{Acknowledgements}{ 37 | 38 | This code borrows heavily from RStudio's code for finding Rtools. 39 | Thanks JJ! 40 | } 41 | 42 | \examples{ 43 | has_rtools() 44 | } 45 | \keyword{internal} 46 | -------------------------------------------------------------------------------- /man/needs_compile.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/compile-dll.R 3 | \name{needs_compile} 4 | \alias{needs_compile} 5 | \title{Does the package need recompiling? 6 | (i.e. is there a source or header file newer than the dll)} 7 | \usage{ 8 | needs_compile(path = ".") 9 | } 10 | \arguments{ 11 | \item{path}{Path to a package, or within a package.} 12 | } 13 | \description{ 14 | Does the package need recompiling? 15 | (i.e. is there a source or header file newer than the dll) 16 | } 17 | \keyword{internal} 18 | -------------------------------------------------------------------------------- /man/pkg_has_src.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/has_src.R 3 | \name{pkg_has_src} 4 | \alias{pkg_has_src} 5 | \title{Does a source package have \verb{src/} directory?} 6 | \usage{ 7 | pkg_has_src(path = ".") 8 | } 9 | \arguments{ 10 | \item{path}{Path to package (or directory within package).} 11 | } 12 | \description{ 13 | If it does, you definitely need build tools. 14 | } 15 | -------------------------------------------------------------------------------- /man/pkg_links_to_rcpp.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/c-registration.R 3 | \name{pkg_links_to_rcpp} 4 | \alias{pkg_links_to_rcpp} 5 | \alias{pkg_links_to_cpp11} 6 | \title{Test if a package path is linking to Rcpp or cpp11} 7 | \usage{ 8 | pkg_links_to_rcpp(path) 9 | 10 | pkg_links_to_cpp11(path) 11 | } 12 | \arguments{ 13 | \item{path}{Path to a package, or within a package.} 14 | } 15 | \description{ 16 | Test if a package path is linking to Rcpp or cpp11 17 | } 18 | \keyword{internal} 19 | -------------------------------------------------------------------------------- /man/pkgbuild-package.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pkgbuild-package.R 3 | \docType{package} 4 | \name{pkgbuild-package} 5 | \alias{pkgbuild} 6 | \alias{pkgbuild-package} 7 | \title{pkgbuild: Find Tools Needed to Build R Packages} 8 | \description{ 9 | Provides functions used to build R packages. Locates compilers needed to build R packages on various platforms and ensures the PATH is configured appropriately so R can use them. 10 | } 11 | \seealso{ 12 | Useful links: 13 | \itemize{ 14 | \item \url{https://github.com/r-lib/pkgbuild} 15 | \item \url{https://pkgbuild.r-lib.org} 16 | \item Report bugs at \url{https://github.com/r-lib/pkgbuild/issues} 17 | } 18 | 19 | } 20 | \author{ 21 | \strong{Maintainer}: Gábor Csárdi \email{csardi.gabor@gmail.com} 22 | 23 | Authors: 24 | \itemize{ 25 | \item Hadley Wickham 26 | \item Jim Hester 27 | } 28 | 29 | Other contributors: 30 | \itemize{ 31 | \item Posit Software, PBC [copyright holder, funder] 32 | } 33 | 34 | } 35 | \keyword{internal} 36 | -------------------------------------------------------------------------------- /man/pkgbuild_process.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/build-bg.R 3 | \name{pkgbuild_process} 4 | \alias{pkgbuild_process} 5 | \title{Build package in the background} 6 | \description{ 7 | This R6 class is a counterpart of the \code{\link[=build]{build()}} function, and 8 | represents a background process that builds an R package. 9 | } 10 | \section{Usage}{ 11 | 12 | 13 | \if{html}{\out{
}}\preformatted{bp <- pkgbuild_process$new(path = ".", dest_path = NULL, 14 | binary = FALSE, vignettes = TRUE, manual = FALSE, args = NULL) 15 | bp$get_dest_path() 16 | }\if{html}{\out{
}} 17 | 18 | Other methods are inherited from \link[callr:rcmd_process]{callr::rcmd_process} and 19 | \code{processx::process}. 20 | } 21 | 22 | \section{Arguments}{ 23 | 24 | See the corresponding arguments of \code{\link[=build]{build()}}. 25 | } 26 | 27 | \section{Details}{ 28 | 29 | Most methods are inherited from \link[callr:rcmd_process]{callr::rcmd_process} and 30 | \code{processx::process}. 31 | 32 | \code{bp$get_dest_path()} returns the path to the built package. 33 | } 34 | 35 | \section{Examples}{ 36 | 37 | 38 | \if{html}{\out{
}}\preformatted{## Here we are just waiting, but in a more realistic example, you 39 | ## would probably run some other code instead... 40 | bp <- pkgbuild_process$new("mypackage", dest_path = tempdir()) 41 | bp$is_alive() 42 | bp$get_pid() 43 | bp$wait() 44 | bp$read_all_output_lines() 45 | bp$read_all_error_lines() 46 | bp$get_exit_status() 47 | bp$get_dest_path() 48 | }\if{html}{\out{
}} 49 | } 50 | 51 | -------------------------------------------------------------------------------- /man/rcmd_build_tools.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/rcmd.R 3 | \name{rcmd_build_tools} 4 | \alias{rcmd_build_tools} 5 | \title{Call R CMD 'command' with build tools active} 6 | \usage{ 7 | rcmd_build_tools(..., env = character(), required = TRUE, quiet = FALSE) 8 | } 9 | \arguments{ 10 | \item{...}{Parameters passed on to \code{rcmd_safe}.} 11 | 12 | \item{env}{Additional environment variables to set. The defaults from 13 | \code{callr::rcmd_safe_env()} are always set.} 14 | 15 | \item{required}{If \code{TRUE}, and build tools are not available, 16 | will throw an error. Otherwise will attempt to run \code{code} without 17 | them.} 18 | 19 | \item{quiet}{if \code{TRUE} suppresses output from this function.} 20 | } 21 | \description{ 22 | This is a wrapper around \code{callr::rcmd_safe()} that checks 23 | that you have build tools available, and on Windows, automatically sets 24 | the path to include Rtools. 25 | } 26 | \examples{ 27 | # These env vars are always set 28 | callr::rcmd_safe_env() 29 | 30 | if (has_build_tools()) { 31 | rcmd_build_tools("CONFIG", "CC")$stdout 32 | rcmd_build_tools("CC", "--version")$stdout 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /man/rtools_needed.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/rtools.R 3 | \name{rtools_needed} 4 | \alias{rtools_needed} 5 | \title{Retrieve a text string with the rtools version needed} 6 | \usage{ 7 | rtools_needed(r_version = getRversion()) 8 | } 9 | \description{ 10 | Retrieve a text string with the rtools version needed 11 | } 12 | \keyword{internal} 13 | -------------------------------------------------------------------------------- /man/with_debug.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/with-debug.R 3 | \name{with_debug} 4 | \alias{with_debug} 5 | \title{Temporarily set debugging compilation flags.} 6 | \usage{ 7 | with_debug( 8 | code, 9 | CFLAGS = NULL, 10 | CXXFLAGS = NULL, 11 | FFLAGS = NULL, 12 | FCFLAGS = NULL, 13 | debug = TRUE 14 | ) 15 | } 16 | \arguments{ 17 | \item{code}{to execute.} 18 | 19 | \item{CFLAGS}{flags for compiling C code} 20 | 21 | \item{CXXFLAGS}{flags for compiling C++ code} 22 | 23 | \item{FFLAGS}{flags for compiling Fortran code.} 24 | 25 | \item{FCFLAGS}{flags for Fortran 9x code.} 26 | 27 | \item{debug}{If \code{TRUE} adds \code{-g -O0} to all flags 28 | (Adding \env{FFLAGS} and \env{FCFLAGS})} 29 | } 30 | \description{ 31 | Temporarily set debugging compilation flags. 32 | } 33 | \examples{ 34 | flags <- names(compiler_flags(TRUE)) 35 | with_debug(Sys.getenv(flags)) 36 | \dontrun{ 37 | install("mypkg") 38 | with_debug(install("mypkg")) 39 | } 40 | } 41 | \seealso{ 42 | Other debugging flags: 43 | \code{\link{compiler_flags}()} 44 | } 45 | \concept{debugging flags} 46 | -------------------------------------------------------------------------------- /man/without_compiler.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/with-debug.R 3 | \name{without_compiler} 4 | \alias{without_compiler} 5 | \alias{without_cache} 6 | \alias{without_latex} 7 | \alias{with_latex} 8 | \title{Tools for testing pkgbuild} 9 | \usage{ 10 | without_compiler(code) 11 | 12 | without_cache(code) 13 | 14 | without_latex(code) 15 | 16 | with_latex(code) 17 | } 18 | \arguments{ 19 | \item{code}{Code to execute with broken compilers} 20 | } 21 | \description{ 22 | \code{with_compiler} temporarily disables code compilation by setting 23 | \code{CC}, \code{CXX}, makevars to \code{test}. \code{without_cache} 24 | resets the cache before and after running \code{code}. 25 | } 26 | -------------------------------------------------------------------------------- /pkgbuild.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: No 4 | SaveWorkspace: No 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: knitr 13 | LaTeX: XeLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | 18 | BuildType: Package 19 | PackageUseDevtools: Yes 20 | PackageInstallArgs: --no-multiarch --with-keep.source 21 | PackageCheckArgs: --timings 22 | PackageRoxygenize: rd,collate,namespace 23 | -------------------------------------------------------------------------------- /tests/build-tools.R: -------------------------------------------------------------------------------- 1 | library(pkgbuild) 2 | 3 | check_build_tools(debug = TRUE) 4 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(pkgbuild) 3 | 4 | test_check("pkgbuild") 5 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/archives.md: -------------------------------------------------------------------------------- 1 | # pkg_has_src on non-package files 2 | 3 | Code 4 | pkg_has_src(file.path("fixtures", "xxx.zip")) 5 | Condition 6 | Error in `doTryCatch()`: 7 | ! fixtures/xxx.zip is not a valid package archive file, no DESCRIPTION file 8 | Code 9 | pkg_has_src(file.path("fixtures", "xxx.tar.gz")) 10 | Condition 11 | Error in `doTryCatch()`: 12 | ! fixtures/xxx.tar.gz is not a valid package archive file, no DESCRIPTION file 13 | 14 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/build-process.md: -------------------------------------------------------------------------------- 1 | # build package with src requires compiler 2 | 3 | Code 4 | suppressMessages(local({ 5 | pr <- pkgbuild_process$new("testWithSrc", dest_path = tempdir(), 6 | register_routines = FALSE) 7 | pr$kill() 8 | })) 9 | Condition 10 | Error: 11 | ! Could not find tools necessary to compile a package 12 | Call `pkgbuild::check_build_tools(debug = TRUE)` to diagnose the problem. 13 | 14 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/build.md: -------------------------------------------------------------------------------- 1 | # build package with src requires compiler 2 | 3 | Code 4 | suppressMessages(build("testWithSrc", dest_path = tempdir(), quiet = TRUE)) 5 | Condition 6 | Error: 7 | ! Could not find tools necessary to compile a package 8 | Call `pkgbuild::check_build_tools(debug = TRUE)` to diagnose the problem. 9 | 10 | # package tarball binary build errors 11 | 12 | Code 13 | build(path, dest_path = tempdir(), quiet = TRUE) 14 | Condition 15 | Error: 16 | ! `binary` must be TRUE for package files 17 | 18 | --- 19 | 20 | Code 21 | build(path, dest_path = tempdir(), quiet = TRUE, binary = TRUE, 22 | needs_compilation = FALSE, compile_attributes = TRUE) 23 | Condition 24 | Error: 25 | ! `compile_attributes` must be FALSE for package files 26 | 27 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/compiler-flags.md: -------------------------------------------------------------------------------- 1 | # has_compiler_colored_diagnostics 2 | 3 | Code 4 | has_compiler_colored_diagnostics() 5 | Condition 6 | Error in `cache_exists()`: 7 | ! nope 8 | 9 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/compiler.md: -------------------------------------------------------------------------------- 1 | # has_compiler: succeeds if a compiler exists 2 | 3 | Code 4 | check_compiler() 5 | Condition 6 | Error: 7 | ! Failed to compile C code 8 | 9 | # has_compiler: returns the value of the has_compiler option 10 | 11 | Code 12 | check_compiler() 13 | Condition 14 | Error: 15 | ! Failed to compile C code 16 | 17 | --- 18 | 19 | Code 20 | has_compiler() 21 | Condition 22 | Error in `has_compiler()`: 23 | ! 24 | ! Invalid `pkgbuild.has_compiler` option. 25 | i It must be `TRUE` or `FALSE`, not an integer vector. 26 | 27 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/exclude.md: -------------------------------------------------------------------------------- 1 | # copy_package_tree creates package dir 2 | 3 | Code 4 | sort(dir(tmp, recursive = TRUE, include.dirs = TRUE)) 5 | Output 6 | [1] "testDummy" "testDummy/DESCRIPTION" "testDummy/NAMESPACE" 7 | [4] "testDummy/R" "testDummy/R/a.R" "testDummy/R/b.R" 8 | 9 | --- 10 | 11 | Code 12 | sort(dir(tmp, recursive = TRUE, include.dirs = TRUE)) 13 | Output 14 | [1] "testDummy" "testDummy/DESCRIPTION" "testDummy/NAMESPACE" 15 | [4] "testDummy/R" "testDummy/R/a.R" "testDummy/R/b.R" 16 | 17 | # copy_package_tree errors 18 | 19 | Code 20 | copy_package_tree(test_path("testDummy"), tmp) 21 | Condition 22 | Error in `copy_package_tree()`: 23 | ! Cannot copy package tree to '/pkgbuild-test-' 24 | i Directory '/pkgbuild-test-/testDummy' already exists, and did not want to overwrite. 25 | 26 | # exclusions 27 | 28 | Code 29 | files[, c("path", "exclude", "isdir", "trimmed")] 30 | Output 31 | path exclude isdir trimmed 32 | 1 .RData TRUE FALSE FALSE 33 | 2 .Rbuildignore FALSE FALSE FALSE 34 | 3 .Rhistory TRUE FALSE FALSE 35 | 4 .git TRUE TRUE FALSE 36 | 5 DESCRIPTION FALSE FALSE FALSE 37 | 6 docs TRUE TRUE FALSE 38 | 7 src FALSE TRUE TRUE 39 | 8 src/.DS_Store TRUE FALSE FALSE 40 | 9 src/foo.c.bak TRUE FALSE FALSE 41 | 10 src/foo.c.swp TRUE FALSE FALSE 42 | 11 src/foo.c~ TRUE FALSE FALSE 43 | 12 src/foo.d TRUE FALSE FALSE 44 | 13 src/src.c FALSE FALSE FALSE 45 | 14 src/src.o TRUE FALSE FALSE 46 | 47 | # get_copy_method 48 | 49 | Code 50 | get_copy_method() 51 | Condition 52 | Error in `check_method()`: 53 | ! Invalid `pkg.build_copy_method` value: "foobar". 54 | i It must be one of "none", "copy", and "link". 55 | 56 | --- 57 | 58 | Code 59 | get_copy_method() 60 | Condition 61 | Error in `get_copy_method()`: 62 | ! Invalid `pkg.build_copy_method` value. 63 | i It must be a string, but it is an integer vector. 64 | 65 | # Ignoring .Rbuildignore 66 | 67 | Code 68 | files[, c("path", "exclude", "isdir", "trimmed")] 69 | Output 70 | path exclude isdir trimmed 71 | 1 .Rbuildignore FALSE FALSE FALSE 72 | 2 DESCRIPTION FALSE FALSE FALSE 73 | 3 docs TRUE TRUE FALSE 74 | 4 src FALSE TRUE TRUE 75 | 5 src/src.c FALSE FALSE FALSE 76 | 6 src/src.o TRUE FALSE FALSE 77 | 78 | # copying on windows 79 | 80 | Code 81 | sort(dir(tmp, recursive = TRUE, include.dirs = TRUE)) 82 | Output 83 | [1] "testDummy" "testDummy/DESCRIPTION" "testDummy/NAMESPACE" 84 | [4] "testDummy/R" "testDummy/R/a.R" "testDummy/R/b.R" 85 | 86 | # cp error 87 | 88 | Code 89 | cp("foo", "bar") 90 | Condition 91 | Error in `cp()`: 92 | ! Could not copy package files. 93 | i Failed to copy 'foo' to 'bar'. 94 | 95 | # detect_cp_args 96 | 97 | Code 98 | detect_cp_args() 99 | Output 100 | [1] "-pLR" 101 | 102 | --- 103 | 104 | Code 105 | detect_cp_args() 106 | Output 107 | [1] "-LR" "--preserve=timestamps" 108 | 109 | # cp error on Unix 110 | 111 | Code 112 | cp("foo", "bar") 113 | Condition 114 | Error in `cp()`: 115 | ! Could not copy package files. 116 | i Failed to copy 'foo' to 'bar'. 117 | 118 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/find-package-root.md: -------------------------------------------------------------------------------- 1 | # find_package_root errors 2 | 3 | Code 4 | find_package_root("file1130e91427d3") 5 | Condition 6 | Error in `find_package_root()`: 7 | ! Path does not exist: file1130e91427d3 8 | 9 | --- 10 | 11 | Code 12 | find_package_root("/") 13 | Condition 14 | Error in `find_package_root()`: 15 | ! Could not find R package in `/` or its parent directories. 16 | 17 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/new/build.md: -------------------------------------------------------------------------------- 1 | # warnings can be turned into errors 2 | 3 | Code 4 | if (getRversion() >= "4.1") { 5 | build(file.path(src, "testDummy"), dest_path = dest, quiet = TRUE) 6 | } else { 7 | suppressMessages(build(file.path(src, "testDummy"), dest_path = dest, quiet = TRUE)) 8 | } 9 | Message 10 | ! Stopping as requested for a warning during `R CMD build`. 11 | ! The full output is printed below. 12 | * checking for file '' ... OK 13 | * preparing 'testDummy': 14 | * checking DESCRIPTION meta-information ... OK 15 | * checking for LF line-endings in source and make files and shell scripts 16 | * checking for empty or unneeded directories 17 | NB: this package now depends on R (>= 3.5.0) 18 | WARNING: Added dependency on R >= 3.5.0 because serialized objects in 19 | serialize/load version 3 cannot be read in older versions of R. 20 | File(s) containing such objects: 21 | 'testDummy/inst/testthat-problems.rds' 22 | * building 'testDummy_0.1.tar.gz' 23 | 24 | Condition 25 | Error in `force()`: 26 | ! converted from `R CMD build` warning. 27 | 28 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/old/build.md: -------------------------------------------------------------------------------- 1 | # warnings can be turned into errors 2 | 3 | Code 4 | if (getRversion() >= "4.1") { 5 | build(file.path(src, "testDummy"), dest_path = dest, quiet = TRUE) 6 | } else { 7 | suppressMessages(build(file.path(src, "testDummy"), dest_path = dest, quiet = TRUE)) 8 | } 9 | Condition 10 | Error in `force()`: 11 | ! converted from `R CMD build` warning. 12 | 13 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/style.md: -------------------------------------------------------------------------------- 1 | # style 2 | 3 | Code 4 | cat(style(ok = "OK", "\n", note = "NOTE", "\n", warn = "WARN", "\n", err = "ERROR", 5 | "\n", pale = "Not important", "\n", timing = "[1m]", "\n")) 6 | Output 7 | OK 8 | NOTE 9 | WARN 10 | ERROR 11 | Not important 12 | [1m] 13 | 14 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/utils.md: -------------------------------------------------------------------------------- 1 | # should_stop_for_warnings 2 | 3 | Code 4 | should_stop_for_warnings() 5 | Condition 6 | Error in `get_config_flag_value()`: 7 | ! The `pkg.build_stop_for_warnings` option must be `TRUE` or `FALSE`, if set. 8 | 9 | --- 10 | 11 | Code 12 | should_stop_for_warnings() 13 | Condition 14 | Error in `interpret_envvar_flag()`: 15 | ! The `PKG_BUILD_STOP_FOR_WARNINGS` environment variable must be `true` or `false`, if set. 16 | 17 | # should_add_compiler_flags errors 18 | 19 | Code 20 | should_add_compiler_flags() 21 | Condition 22 | Error in `should_add_compiler_flags()`: 23 | ! Invalid `pkg.build_extra_flags` option. 24 | i It must be `TRUE`, `FALSE` or "missing", not an integer vector. 25 | 26 | --- 27 | 28 | Code 29 | should_add_compiler_flags() 30 | Condition 31 | Error in `should_add_compiler_flags()`: 32 | ! Invalid `pkg_build_extra_flags` option. 33 | i It must be `TRUE`, `FALSE` or "missing", not "foobar". 34 | 35 | --- 36 | 37 | Code 38 | should_add_compiler_flags() 39 | Condition 40 | Error in `should_add_compiler_flags()`: 41 | ! Invalid `PKG_BUILD_EXTRA_FLAGS` environment variable. 42 | i Must be one of `true`, `false` or `missing`. 43 | 44 | -------------------------------------------------------------------------------- /tests/testthat/fixtures/testDummy_0.1.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/pkgbuild/d2666a845095c05fa0f3f1227007b3f92a0320d9/tests/testthat/fixtures/testDummy_0.1.tar.gz -------------------------------------------------------------------------------- /tests/testthat/fixtures/testWithSrc_0.1.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/pkgbuild/d2666a845095c05fa0f3f1227007b3f92a0320d9/tests/testthat/fixtures/testWithSrc_0.1.tar.gz -------------------------------------------------------------------------------- /tests/testthat/fixtures/xxx.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/pkgbuild/d2666a845095c05fa0f3f1227007b3f92a0320d9/tests/testthat/fixtures/xxx.gz -------------------------------------------------------------------------------- /tests/testthat/fixtures/xxx.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/pkgbuild/d2666a845095c05fa0f3f1227007b3f92a0320d9/tests/testthat/fixtures/xxx.tar.gz -------------------------------------------------------------------------------- /tests/testthat/fixtures/xxx.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r-lib/pkgbuild/d2666a845095c05fa0f3f1227007b3f92a0320d9/tests/testthat/fixtures/xxx.zip -------------------------------------------------------------------------------- /tests/testthat/helper.R: -------------------------------------------------------------------------------- 1 | transform_tempdir <- function(x) { 2 | x <- sub(tempdir(), "", x, fixed = TRUE) 3 | x <- sub(normalizePath(tempdir()), "", x, fixed = TRUE) 4 | x <- sub( 5 | normalizePath(tempdir(), winslash = "/"), 6 | "", 7 | x, 8 | fixed = TRUE 9 | ) 10 | x <- sub("\\R\\", "/R/", x, fixed = TRUE) 11 | x <- sub("[\\\\/]file[a-zA-Z0-9]+", "/", x) 12 | x <- sub("[A-Z]:.*Rtmp[a-zA-Z0-9]+[\\\\/]", "/", x) 13 | x <- sub("\\", "/", x, fixed = TRUE) 14 | x 15 | } 16 | -------------------------------------------------------------------------------- /tests/testthat/test-archives.R: -------------------------------------------------------------------------------- 1 | test_that("is_zip_file", { 2 | expect_true(is_zip_file(file.path("fixtures", "xxx.zip"))) 3 | expect_false(is_zip_file(file.path("fixtures", "xxx.gz"))) 4 | expect_false(is_zip_file(file.path("fixtures", "xxx.tar.gz"))) 5 | }) 6 | 7 | test_that("is_gz_file", { 8 | expect_false(is_gz_file(file.path("fixtures", "xxx.zip"))) 9 | expect_true(is_gz_file(file.path("fixtures", "xxx.gz"))) 10 | expect_true(is_gz_file(file.path("fixtures", "xxx.tar.gz"))) 11 | }) 12 | 13 | test_that("is_tar_gz_file", { 14 | expect_false(is_tar_gz_file(file.path("fixtures", "xxx.zip"))) 15 | expect_false(is_tar_gz_file(file.path("fixtures", "xxx.gz"))) 16 | expect_true(is_tar_gz_file(file.path("fixtures", "xxx.tar.gz"))) 17 | }) 18 | 19 | test_that("pkg_has_src", { 20 | expect_false(pkg_has_src(file.path("fixtures", "testDummy_0.1.tar.gz"))) 21 | expect_true(pkg_has_src(file.path("fixtures", "testWithSrc_0.1.tar.gz"))) 22 | }) 23 | 24 | test_that("pkg_has_src on non-package files", { 25 | expect_snapshot(error = TRUE, { 26 | pkg_has_src(file.path("fixtures", "xxx.zip")) 27 | pkg_has_src(file.path("fixtures", "xxx.tar.gz")) 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /tests/testthat/test-build-process.R: -------------------------------------------------------------------------------- 1 | # Package without source code -------------------------------------------- 2 | 3 | test_that("source builds return correct filenames", { 4 | dir.create(tmp <- tempfile()) 5 | on.exit(unlink(tmp, recursive = TRUE), add = TRUE) 6 | 7 | pr <- pkgbuild_process$new("testDummy", dest_path = tmp) 8 | pr$wait(60000) 9 | if (pr$is_alive()) { 10 | pr$kill() 11 | skip("has not finished in one minute") 12 | } 13 | 14 | expect_true(file.exists(pr$get_dest_path())) 15 | expect_true(file.exists(pr$get_built_file())) 16 | expect_true(!is.na(desc::desc(pr$get_built_file())$get("Packaged"))) 17 | }) 18 | 19 | test_that("binary builds return correct filenames", { 20 | # building binaries also installs them to the library, so we need to skip on 21 | # CRAN. 22 | skip_on_cran() 23 | 24 | dir.create(tmp <- tempfile()) 25 | on.exit(unlink(tmp, recursive = TRUE), add = TRUE) 26 | 27 | pr <- pkgbuild_process$new("testDummy", binary = TRUE, dest_path = tmp) 28 | pr$wait(60000) 29 | if (pr$is_alive()) { 30 | pr$kill() 31 | skip("has not finished in one minute") 32 | } 33 | 34 | expect_true(file.exists(pr$get_dest_path())) 35 | expect_true(file.exists(pr$get_built_file())) 36 | expect_true(!is.na(desc::desc(pr$get_built_file())$get("Built"))) 37 | }) 38 | 39 | test_that("can build package without src without compiler", { 40 | # building binaries also installs them to the library, so we need to skip on 41 | # CRAN. 42 | skip_on_cran() 43 | 44 | dir.create(tmp <- tempfile()) 45 | on.exit(unlink(tmp, recursive = TRUE)) 46 | 47 | without_compiler({ 48 | pr <- pkgbuild_process$new("testDummy", binary = TRUE, dest_path = tmp) 49 | pr$wait(60000) 50 | if (pr$is_alive()) { 51 | pr$kill() 52 | skip("has not finished in one minute") 53 | } 54 | 55 | expect_true(file.exists(pr$get_dest_path())) 56 | expect_true(file.exists(pr$get_built_file())) 57 | expect_true(!is.na(desc::desc(pr$get_built_file())$get("Built"))) 58 | }) 59 | }) 60 | 61 | 62 | # Package with src code --------------------------------------------------- 63 | 64 | test_that("source builds return correct filenames", { 65 | dir.create(tmp <- tempfile()) 66 | on.exit(unlink(tmp, recursive = TRUE), add = TRUE) 67 | 68 | pr <- pkgbuild_process$new( 69 | "testWithSrc", 70 | dest_path = tmp, 71 | register_routines = FALSE 72 | ) 73 | pr$wait(60000) 74 | if (pr$is_alive()) { 75 | pr$kill() 76 | skip("has not finished in one minute") 77 | } 78 | 79 | expect_true(file.exists(pr$get_dest_path())) 80 | expect_true(file.exists(pr$get_built_file())) 81 | expect_true(!is.na(desc::desc(pr$get_built_file())$get("Packaged"))) 82 | }) 83 | 84 | test_that("build package with src requires compiler", { 85 | without_compiler({ 86 | expect_snapshot( 87 | error = TRUE, 88 | suppressMessages(local({ 89 | pr <- pkgbuild_process$new( 90 | "testWithSrc", 91 | dest_path = tempdir(), 92 | register_routines = FALSE 93 | ) 94 | pr$kill() 95 | })) 96 | ) 97 | }) 98 | }) 99 | 100 | test_that("can get output, exit status, etc.", { 101 | dir.create(tmp <- tempfile()) 102 | on.exit(unlink(tmp, recursive = TRUE), add = TRUE) 103 | 104 | pr <- pkgbuild_process$new("testDummy", dest_path = tmp) 105 | pr$wait(60000) 106 | if (pr$is_alive()) { 107 | pr$kill() 108 | skip("has not finished in one minute") 109 | } 110 | 111 | out <- pr$read_all_output() 112 | expect_match(out, "* building", fixed = TRUE) 113 | expect_error(err <- pr$read_all_error(), NA) 114 | expect_equal(pr$get_exit_status(), 0) 115 | 116 | path <- pr$get_dest_path() 117 | on.exit(unlink(path)) 118 | }) 119 | 120 | test_that("can kill a build process", { 121 | dir.create(tmp <- tempfile()) 122 | on.exit(unlink(tmp, recursive = TRUE), add = TRUE) 123 | 124 | pr <- pkgbuild_process$new("testDummy", dest_path = tmp) 125 | ret <- pr$kill() 126 | if (!ret) skip("build finished before we could kill it") 127 | 128 | ex_stat <- pr$get_exit_status() 129 | if (.Platform$OS.type == "unix") { 130 | expect_equal(ex_stat, -9) 131 | } else { 132 | expect_true(ex_stat != 0) 133 | } 134 | }) 135 | -------------------------------------------------------------------------------- /tests/testthat/test-build.R: -------------------------------------------------------------------------------- 1 | # Package build setup ---------------------------------------------------- 2 | 3 | test_that("with*_latex context fixtures force has_latex result", { 4 | expect_true(with_latex(has_latex())) 5 | expect_false(without_latex(has_latex())) 6 | 7 | # one of them should be different from default 8 | expect_true({ 9 | with_latex(has_latex()) != has_latex() || 10 | without_latex(has_latex()) != has_latex() 11 | }) 12 | }) 13 | 14 | # expect `manual=TRUE` & empty `args` not to add build --no-manual flag 15 | test_that("source build setup accept args and/or parameterized helpers", { 16 | expect_silent( 17 | res <- with_latex({ 18 | build_setup_source( 19 | file.path(testthat::test_path(), "testDummy"), 20 | file.path(tempdir(), "testDummyBuild"), 21 | vignettes = FALSE, 22 | manual = TRUE, 23 | clean_doc = FALSE, 24 | args = c(), 25 | needs_compilation = FALSE 26 | ) 27 | }) 28 | ) 29 | expect_true(!"--no-manual" %in% res$args) 30 | 31 | # expect `manual=FALSE` to affect build --no-manual flag 32 | expect_silent( 33 | res <- with_latex({ 34 | build_setup_source( 35 | file.path(testthat::test_path(), "testDummy"), 36 | file.path(tempdir(), "testDummyBuild"), 37 | vignettes = FALSE, 38 | manual = FALSE, 39 | clean_doc = FALSE, 40 | args = c(), 41 | needs_compilation = FALSE 42 | ) 43 | }) 44 | ) 45 | expect_true("--no-manual" %in% res$args) 46 | 47 | # expect `args` "--no-manual" to affect build --no-manual flag 48 | expect_silent( 49 | res <- with_latex({ 50 | build_setup_source( 51 | file.path(testthat::test_path(), "testDummy"), 52 | file.path(tempdir(), "testDummyBuild"), 53 | vignettes = FALSE, 54 | manual = TRUE, 55 | clean_doc = FALSE, 56 | args = c("--no-manual"), 57 | needs_compilation = FALSE 58 | ) 59 | }) 60 | ) 61 | expect_true("--no-manual" %in% res$args) 62 | 63 | expect_silent( 64 | res <- build_setup_source( 65 | file.path(testthat::test_path(), "testDummy"), 66 | file.path(tempdir(), "testDummyBuild"), 67 | vignettes = TRUE, 68 | manual = FALSE, 69 | clean_doc = FALSE, 70 | args = c(), 71 | needs_compilation = FALSE 72 | ) 73 | ) 74 | expect_true(!"--no-build-vignettes" %in% res$args) 75 | 76 | # expect `vignettes=FALSE` to affect build --no-build-vignettes flag 77 | expect_silent( 78 | res <- build_setup_source( 79 | file.path(testthat::test_path(), "testDummy"), 80 | file.path(tempdir(), "testDummyBuild"), 81 | vignettes = FALSE, 82 | manual = FALSE, 83 | clean_doc = FALSE, 84 | args = c(), 85 | needs_compilation = FALSE 86 | ) 87 | ) 88 | expect_true("--no-build-vignettes" %in% res$args) 89 | 90 | # expect `arg` `--no-build-vignettes` to produce --no-build-vignettes flag 91 | expect_silent( 92 | res <- build_setup_source( 93 | file.path(testthat::test_path(), "testDummy"), 94 | file.path(tempdir(), "testDummyBuild"), 95 | vignettes = TRUE, 96 | manual = FALSE, 97 | clean_doc = FALSE, 98 | args = c("--no-build-vignettes"), 99 | needs_compilation = FALSE 100 | ) 101 | ) 102 | expect_true("--no-build-vignettes" %in% res$args) 103 | }) 104 | 105 | # Package without source code -------------------------------------------- 106 | 107 | test_that("source builds return correct filenames", { 108 | path <- build("testDummy", dest_path = tempdir(), quiet = TRUE) 109 | on.exit(unlink(path)) 110 | 111 | expect_true(file.exists(path)) 112 | expect_false(is.na(desc::desc(path)$get("Packaged"))) 113 | expect_true(is.na(desc::desc(path)$get("Built"))) 114 | }) 115 | 116 | test_that("binary builds return correct filenames", { 117 | path <- build("testDummy", binary = TRUE, dest_path = tempdir(), quiet = TRUE) 118 | on.exit(unlink(path)) 119 | 120 | expect_true(file.exists(path)) 121 | }) 122 | 123 | test_that("can build package without src without compiler", { 124 | without_compiler({ 125 | path <- build( 126 | "testDummy", 127 | binary = TRUE, 128 | dest_path = tempdir(), 129 | quiet = TRUE 130 | ) 131 | on.exit(unlink(path)) 132 | 133 | expect_true(file.exists(path)) 134 | }) 135 | }) 136 | 137 | 138 | # Package with src code --------------------------------------------------- 139 | 140 | test_that("source builds return correct filenames", { 141 | path <- build( 142 | "testWithSrc", 143 | dest_path = tempdir(), 144 | quiet = TRUE, 145 | register_routines = FALSE 146 | ) 147 | on.exit(unlink(path)) 148 | 149 | expect_true(file.exists(path)) 150 | }) 151 | 152 | test_that("build package with src requires compiler", { 153 | without_compiler({ 154 | expect_snapshot( 155 | error = TRUE, 156 | suppressMessages( 157 | build("testWithSrc", dest_path = tempdir(), quiet = TRUE) 158 | ) 159 | ) 160 | }) 161 | }) 162 | 163 | # Package files ----------------------------------------------------------- 164 | 165 | test_that("package tarball binary build", { 166 | path <- build("testDummy", dest_path = tempdir(), quiet = TRUE) 167 | on.exit(unlink(path), add = TRUE) 168 | 169 | path2 <- build( 170 | path, 171 | dest_path = tempdir(), 172 | quiet = TRUE, 173 | binary = TRUE, 174 | needs_compilation = FALSE, 175 | compile_attributes = FALSE 176 | ) 177 | on.exit(unlink(path2), add = TRUE) 178 | expect_true(file.exists(path2)) 179 | expect_false(is.na(desc::desc(path2)$get("Packaged"))) 180 | expect_false(is.na(desc::desc(path2)$get("Built"))) 181 | }) 182 | 183 | test_that("package tarball binary build errors", { 184 | path <- build("testDummy", dest_path = tempdir(), quiet = TRUE) 185 | on.exit(unlink(path), add = TRUE) 186 | 187 | expect_snapshot( 188 | error = TRUE, 189 | build(path, dest_path = tempdir(), quiet = TRUE) 190 | ) 191 | expect_snapshot( 192 | error = TRUE, 193 | build( 194 | path, 195 | dest_path = tempdir(), 196 | quiet = TRUE, 197 | binary = TRUE, 198 | needs_compilation = FALSE, 199 | compile_attributes = TRUE 200 | ) 201 | ) 202 | }) 203 | 204 | test_that("warnings can be turned into errors", { 205 | src <- withr::local_tempdir() 206 | dest <- withr::local_tempdir() 207 | file.copy(test_path("testDummy"), src, recursive = TRUE, copy.mode = FALSE) 208 | 209 | withr::local_options(pkg.build_stop_for_warnings = TRUE) 210 | expect_silent( 211 | build(file.path(src, "testDummy"), dest_path = dest, quiet = TRUE) 212 | ) 213 | 214 | dir.create( 215 | file.path(src, "testDummy", "inst"), 216 | recursive = TRUE, 217 | showWarnings = FALSE 218 | ) 219 | saveRDS(1:10, file.path(src, "testDummy", "inst", "testthat-problems.rds")) 220 | 221 | # No warning/error on R <= 3.5 222 | if (getRversion() <= "3.6") skip("Needs R 3.5.0") 223 | 224 | # Warning looks different on older R 225 | expect_snapshot( 226 | error = TRUE, 227 | { 228 | if (getRversion() >= "4.1") { 229 | build(file.path(src, "testDummy"), dest_path = dest, quiet = TRUE) 230 | } else { 231 | suppressMessages(build( 232 | file.path(src, "testDummy"), 233 | dest_path = dest, 234 | quiet = TRUE 235 | )) 236 | } 237 | }, 238 | transform = function(x) { 239 | x <- sub("\u2018", "'", x, fixed = TRUE) 240 | x <- sub("\u2019", "'", x, fixed = TRUE) 241 | x <- sub("checking for file '.*'", "checking for file ''", x) 242 | x 243 | }, 244 | variant = if (getRversion() >= "4.1") "new" else "old" 245 | ) 246 | }) 247 | 248 | test_that("Config/build/clean-inst-doc FALSE", { 249 | if (Sys.which("pandoc") == "") skip("No pandoc") 250 | dest <- withr::local_tempdir() 251 | expect_silent( 252 | build( 253 | test_path("testInstDoc"), 254 | dest_path = dest, 255 | quiet = TRUE, 256 | vignettes = TRUE, 257 | clean_doc = NULL 258 | ) 259 | ) 260 | 261 | pkg <- file.path(dest, dir(dest)) 262 | expect_true(length(pkg) == 1) 263 | expect_true(file.exists(pkg)) 264 | pkg_files <- untar(pkg, list = TRUE) 265 | expect_true("testInstDoc/inst/doc/keep.me" %in% pkg_files) 266 | expect_true("testInstDoc/inst/doc/test.html" %in% pkg_files) 267 | }) 268 | 269 | test_that("Config/build/clean-inst-doc TRUE", { 270 | if (Sys.which("pandoc") == "") skip("No pandoc") 271 | src <- withr::local_tempdir() 272 | dest <- withr::local_tempdir() 273 | file.copy(test_path("testInstDoc"), src, recursive = TRUE, copy.mode = FALSE) 274 | 275 | desc::desc_set( 276 | "Config/build/clean-inst-doc" = "TRUE", 277 | file = file.path(src, "testInstDoc") 278 | ) 279 | 280 | expect_silent( 281 | build( 282 | file.path(src, "testInstDoc"), 283 | dest_path = dest, 284 | quiet = TRUE, 285 | vignettes = TRUE, 286 | clean_doc = NULL 287 | ) 288 | ) 289 | pkg <- file.path(dest, dir(dest)) 290 | expect_true(length(pkg) == 1) 291 | expect_true(file.exists(pkg)) 292 | pkg_files <- untar(pkg, list = TRUE) 293 | expect_false("testInstDoc/inst/doc/keep.me" %in% pkg_files) 294 | expect_true("testInstDoc/inst/doc/test.html" %in% pkg_files) 295 | }) 296 | 297 | test_that("bootstrap.R runs on build if present", { 298 | src <- withr::local_tempdir() 299 | dest <- withr::local_tempdir() 300 | file.copy(test_path("testDummy"), src, recursive = TRUE, copy.mode = FALSE) 301 | 302 | writeLines( 303 | c( 304 | 'dir.create("inst")', 305 | 'file.create("inst/file-created-by-bootstrap.txt")' 306 | ), 307 | file.path(src, "testDummy", "bootstrap.R") 308 | ) 309 | 310 | desc::desc_set( 311 | "Config/build/bootstrap" = "TRUE", 312 | file = file.path(src, "testDummy") 313 | ) 314 | 315 | expect_silent( 316 | build( 317 | file.path(src, "testDummy"), 318 | dest_path = dest, 319 | quiet = TRUE 320 | ) 321 | ) 322 | 323 | pkg <- file.path(dest, dir(dest)) 324 | expect_true(length(pkg) == 1) 325 | expect_true(file.exists(pkg)) 326 | pkg_files <- untar(pkg, list = TRUE) 327 | expect_true("testDummy/inst/file-created-by-bootstrap.txt" %in% pkg_files) 328 | }) 329 | 330 | test_that("bootstrap.R does not run if Config/build/bootstrap is not TRUE", { 331 | src <- withr::local_tempdir() 332 | dest <- withr::local_tempdir() 333 | file.copy(test_path("testDummy"), src, recursive = TRUE, copy.mode = FALSE) 334 | 335 | writeLines( 336 | c( 337 | 'dir.create("inst")', 338 | 'file.create("inst/file-created-by-bootstrap.txt")' 339 | ), 340 | file.path(src, "testDummy", "bootstrap.R") 341 | ) 342 | 343 | expect_silent( 344 | build( 345 | file.path(src, "testDummy"), 346 | dest_path = dest, 347 | quiet = TRUE 348 | ) 349 | ) 350 | 351 | pkg <- file.path(dest, dir(dest)) 352 | expect_true(length(pkg) == 1) 353 | expect_true(file.exists(pkg)) 354 | pkg_files <- untar(pkg, list = TRUE) 355 | expect_false("testDummy/inst/file-created-by-bootstrap.txt" %in% pkg_files) 356 | }) 357 | 358 | test_that("bootstrap.R can output stdout, stderr, and warnings when run", { 359 | src <- withr::local_tempdir() 360 | dest <- withr::local_tempdir() 361 | file.copy(test_path("testDummy"), src, recursive = TRUE, copy.mode = FALSE) 362 | 363 | writeLines( 364 | c( 365 | 'message("output on stderr")', 366 | 'cat("output on stdout\\n")', 367 | 'warning("this is a warning")' 368 | ), 369 | file.path(src, "testDummy", "bootstrap.R") 370 | ) 371 | 372 | desc::desc_set( 373 | "Config/build/bootstrap" = "TRUE", 374 | file = file.path(src, "testDummy") 375 | ) 376 | 377 | expect_output( 378 | expect_message( 379 | build( 380 | file.path(src, "testDummy"), 381 | dest_path = dest 382 | ), 383 | "Running bootstrap.R" 384 | ), 385 | "output on stderr.*?output on stdout.*?this is a warning" 386 | ) 387 | }) 388 | -------------------------------------------------------------------------------- /tests/testthat/test-build_tools.R: -------------------------------------------------------------------------------- 1 | test_that("tests always run in environment with dev tools", { 2 | without_cache({ 3 | expect_true(has_build_tools()) 4 | expect_equal(has_rtools(), is_windows()) 5 | }) 6 | }) 7 | 8 | test_that("unless specifically disabled", { 9 | without_compiler({ 10 | expect_false(has_build_tools()) 11 | expect_false(has_rtools()) 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /tests/testthat/test-c-registration.R: -------------------------------------------------------------------------------- 1 | test_that("update_c_registration does nothing if an init.c file already exists", { 2 | skip_if(getRversion() < "3.4") 3 | 4 | init_file <- test_path("testWithSrc", "src", "init.c") 5 | on.exit(unlink(init_file)) 6 | 7 | writeLines( 8 | ' 9 | #include 10 | #include 11 | #include // for NULL 12 | #include 13 | 14 | 15 | /* .Call calls */ 16 | extern SEXP add1(SEXP); 17 | extern SEXP mult2(SEXP); 18 | 19 | static const R_CallMethodDef CallEntries[] = { 20 | {"add1", (DL_FUNC) &add1, 1}, 21 | {"mult2", (DL_FUNC) &mult2, 1}, 22 | {NULL, NULL, 0} 23 | }; 24 | 25 | void R_init_testWithSrc(DllInfo *dll) 26 | { 27 | R_registerRoutines(dll, NULL, CallEntries, NULL, NULL); 28 | R_useDynamicSymbols(dll, FALSE); 29 | }', 30 | init_file 31 | ) 32 | 33 | expect_equal( 34 | update_c_registration(test_path("testWithSrc")), 35 | character() 36 | ) 37 | }) 38 | 39 | test_that("update_c_registration works", { 40 | skip_if(getRversion() < "3.4") 41 | 42 | init_file <- test_path("testWithSrc", "src", "init.c") 43 | on.exit(unlink(init_file)) 44 | expect_false(file.exists(init_file)) 45 | 46 | # Should return with no lines if there are no routines called 47 | expect_equal(update_c_registration(test_path("testWithSrc")), character()) 48 | 49 | # Add a call and try to update again 50 | src_file <- test_path("testWithSrc", "R", "c.R") 51 | 52 | writeLines( 53 | ' 54 | add1 <- function(x) { 55 | .Call("add1", x) 56 | }', 57 | src_file 58 | ) 59 | 60 | on.exit(unlink(src_file), add = TRUE) 61 | 62 | init_lines <- update_c_registration(test_path("testWithSrc")) 63 | 64 | expect_true(any(grepl("generated by pkgbuild", init_lines))) 65 | expect_true(any(grepl('"add1",.*[(]DL_FUNC[)] &add1', init_lines))) 66 | 67 | # update_c_registration should be idempotent if nothing has changed 68 | expect_equal( 69 | update_c_registration(test_path("testWithSrc")), 70 | init_lines 71 | ) 72 | 73 | writeLines( 74 | ' 75 | add1 <- function(x) { 76 | .Call("add1", x) 77 | } 78 | 79 | mult2 <- function(x) { 80 | .Call("mult2", x) 81 | }', 82 | src_file 83 | ) 84 | 85 | # update_c_registration should be idempotent if nothing has changed 86 | update_c_registration(test_path("testWithSrc")) 87 | 88 | init_src <- readLines(init_file) 89 | expect_true(any(grepl("generated by pkgbuild", init_src))) 90 | expect_true(any(grepl('"add1",.*[(]DL_FUNC[)] &add1', init_src))) 91 | expect_true(any(grepl('"mult2",.*[(]DL_FUNC[)] &mult2', init_src))) 92 | }) 93 | 94 | test_that("check_namespace_registration", { 95 | expect_warning(check_namespace_registration(test_path("testWithSrc"))) 96 | }) 97 | -------------------------------------------------------------------------------- /tests/testthat/test-compile_dll.R: -------------------------------------------------------------------------------- 1 | test_that("can compile a DLL and clean up afterwards", { 2 | expect_error( 3 | compile_dll("testWithSrc", quiet = TRUE, register_routines = FALSE), 4 | NA 5 | ) 6 | 7 | clean_dll("testWithSrc") 8 | expect_equal(dir("testWithSrc/src"), "add1.c") 9 | }) 10 | -------------------------------------------------------------------------------- /tests/testthat/test-compiler-flags.R: -------------------------------------------------------------------------------- 1 | test_that("has_compiler_colored_diagnostics", { 2 | local_mocked_bindings(cache_exists = function(...) stop("nope")) 3 | 4 | withr::local_envvar(PKG_BUILD_COLOR_DIAGNOSTICS = "true") 5 | expect_true(has_compiler_colored_diagnostics()) 6 | 7 | withr::local_envvar(PKG_BUILD_COLOR_DIAGNOSTICS = "false") 8 | expect_false(has_compiler_colored_diagnostics()) 9 | 10 | withr::local_envvar(PKG_BUILD_COLOR_DIAGNOSTICS = NA_character_) 11 | expect_snapshot(error = TRUE, has_compiler_colored_diagnostics()) 12 | }) 13 | -------------------------------------------------------------------------------- /tests/testthat/test-compiler.R: -------------------------------------------------------------------------------- 1 | describe("has_compiler", { 2 | withr::local_options(pkgbuild.has_compiler = NULL) 3 | testthat::local_reproducible_output() 4 | it("succeeds if a compiler exists", { 5 | skip_if(is_windows() && !has_rtools()) 6 | without_cache({ 7 | without_compiler({ 8 | expect_false(has_compiler()) 9 | expect_snapshot(error = TRUE, check_compiler()) 10 | }) 11 | cache_reset() 12 | expect_true(has_compiler()) 13 | expect_true(check_compiler()) 14 | }) 15 | }) 16 | 17 | it("returns the value of the has_compiler option", { 18 | skip_if(is_windows() && !has_rtools()) 19 | without_cache({ 20 | without_compiler({ 21 | withr::with_options( 22 | c(pkgbuild.has_compiler = TRUE), 23 | { 24 | expect_true(has_compiler()) 25 | expect_error(check_compiler(), NA) 26 | } 27 | ) 28 | }) 29 | cache_reset() 30 | withr::with_options( 31 | c(pkgbuild.has_compiler = FALSE), 32 | { 33 | expect_false(has_compiler()) 34 | expect_snapshot(error = TRUE, check_compiler()) 35 | } 36 | ) 37 | withr::with_options( 38 | list(pkgbuild.has_compiler = 1:2), 39 | expect_snapshot( 40 | error = TRUE, 41 | has_compiler() 42 | ) 43 | ) 44 | }) 45 | }) 46 | }) 47 | -------------------------------------------------------------------------------- /tests/testthat/test-exclude.R: -------------------------------------------------------------------------------- 1 | test_that("copy_package_tree creates package dir", { 2 | tmp <- withr::local_tempdir("pkgbuild-test-") 3 | 4 | # link 5 | withr::local_options(pkg.build_copy_method = "link") 6 | unlink(tmp, recursive = TRUE) 7 | expect_no_warning( 8 | copy_package_tree(test_path("testDummy"), tmp) 9 | ) 10 | expect_snapshot( 11 | sort(dir(tmp, recursive = TRUE, include.dirs = TRUE)) 12 | ) 13 | 14 | # copy 15 | withr::local_options(pkg.build_copy_method = "copy") 16 | unlink(tmp, recursive = TRUE) 17 | expect_no_warning( 18 | copy_package_tree(test_path("testDummy"), tmp) 19 | ) 20 | expect_snapshot( 21 | sort(dir(tmp, recursive = TRUE, include.dirs = TRUE)) 22 | ) 23 | }) 24 | 25 | test_that("copy_package_tree errors", { 26 | tmp <- withr::local_tempdir("pkgbuild-test-") 27 | mkdirp(file.path(tmp, "testDummy")) 28 | expect_snapshot( 29 | error = TRUE, 30 | copy_package_tree(test_path("testDummy"), tmp), 31 | transform = function(x) { 32 | x <- transform_tempdir(x) 33 | sub("test-[0-9a-f]+", "test-", x) 34 | } 35 | ) 36 | }) 37 | 38 | test_that("exclusions", { 39 | pkgdir <- file.path(withr::local_tempdir("pkgbuild-test-"), "pkg") 40 | mkdirp(pkgdir) 41 | writeLines( 42 | c("Package: pkg", "Version: 1.0.0"), 43 | file.path(pkgdir, "DESCRIPTION") 44 | ) 45 | 46 | # create some structure with files to ignore and keep 47 | writeLines( 48 | c( 49 | "^docs$", 50 | "^src/.*[.]o$" 51 | ), 52 | file.path(pkgdir, ".Rbuildignore") 53 | ) 54 | 55 | mkdirp(file.path(pkgdir, "src")) 56 | file.create(file.path(pkgdir, "src", "src.c")) 57 | file.create(file.path(pkgdir, "src", "src.o")) 58 | mkdirp(file.path(pkgdir, "docs")) 59 | file.create(file.path(pkgdir, "docs", "foo")) 60 | 61 | file.create(file.path(pkgdir, "src", ".DS_Store")) 62 | file.create(file.path(pkgdir, ".Rhistory")) 63 | file.create(file.path(pkgdir, ".RData")) 64 | file.create(file.path(pkgdir, "src", "foo.c~")) 65 | file.create(file.path(pkgdir, "src", "foo.c.bak")) 66 | file.create(file.path(pkgdir, "src", "foo.c.swp")) 67 | file.create(file.path(pkgdir, "src", "foo.d")) 68 | dir.create(file.path(pkgdir, ".git")) 69 | file.create(file.path(pkgdir, ".git", "foo")) 70 | 71 | files <- build_files(pkgdir) 72 | expect_snapshot(files[, c("path", "exclude", "isdir", "trimmed")]) 73 | 74 | dest <- withr::local_tempdir("pkgbuild-test-") 75 | copy_package_tree(pkgdir, dest) 76 | }) 77 | 78 | test_that("get_copy_method", { 79 | local_mocked_bindings(is_windows = function() TRUE) 80 | withr::local_options(pkg.build_copy_method = "link") 81 | expect_equal(get_copy_method(), "copy") 82 | 83 | withr::local_options(pkg.build_copy_method = "foobar") 84 | expect_snapshot(error = TRUE, get_copy_method()) 85 | 86 | withr::local_options(pkg.build_copy_method = 1:10) 87 | expect_snapshot(error = TRUE, get_copy_method()) 88 | 89 | withr::local_options(pkg.build_copy_method = NULL) 90 | local_mocked_bindings(desc_get = function(...) "link", .package = "desc") 91 | expect_equal(get_copy_method(), "copy") 92 | }) 93 | 94 | test_that("Ignoring .Rbuildignore", { 95 | pkgdir <- file.path(withr::local_tempdir("pkgbuild-test-"), "pkg") 96 | mkdirp(pkgdir) 97 | writeLines( 98 | c("Package: pkg", "Version: 1.0.0"), 99 | file.path(pkgdir, "DESCRIPTION") 100 | ) 101 | 102 | # create some structure with files to ignore and keep 103 | writeLines( 104 | c( 105 | "^docs$", 106 | "^src/.*[.]o$", 107 | "^\\.Rbuildignore$" 108 | ), 109 | file.path(pkgdir, ".Rbuildignore") 110 | ) 111 | 112 | mkdirp(file.path(pkgdir, "src")) 113 | file.create(file.path(pkgdir, "src", "src.c")) 114 | file.create(file.path(pkgdir, "src", "src.o")) 115 | mkdirp(file.path(pkgdir, "docs")) 116 | file.create(file.path(pkgdir, "docs", "foo")) 117 | 118 | files <- build_files(pkgdir) 119 | expect_snapshot(files[, c("path", "exclude", "isdir", "trimmed")]) 120 | }) 121 | 122 | test_that("copying on windows", { 123 | # this works on Unix as well 124 | environment(cp)$wind <- TRUE 125 | on.exit(environment(cp)$wind <- NULL, add = TRUE) 126 | tmp <- withr::local_tempdir("pkgbuild-test-") 127 | 128 | # link 129 | withr::local_options(pkg.build_copy_method = "copy") 130 | unlink(tmp, recursive = TRUE) 131 | expect_no_warning( 132 | copy_package_tree(test_path("testDummy"), tmp) 133 | ) 134 | expect_snapshot( 135 | sort(dir(tmp, recursive = TRUE, include.dirs = TRUE)) 136 | ) 137 | }) 138 | 139 | test_that("cp error", { 140 | environment(cp)$wind <- TRUE 141 | on.exit(environment(cp)$wind <- NULL, add = TRUE) 142 | withr::local_dir(withr::local_tempdir()) 143 | expect_snapshot(error = TRUE, cp("foo", "bar")) 144 | }) 145 | 146 | test_that("detect_cp_args", { 147 | local_mocked_bindings( 148 | run = function(...) stop("nope"), 149 | .package = "processx" 150 | ) 151 | expect_snapshot(detect_cp_args()) 152 | 153 | local_mocked_bindings( 154 | run = function(f1, f2) file.create(f2), 155 | .package = "processx" 156 | ) 157 | expect_snapshot(detect_cp_args()) 158 | }) 159 | 160 | test_that("cp error on Unix", { 161 | skip_on_os("windows") 162 | withr::local_dir(withr::local_tempdir()) 163 | expect_snapshot( 164 | error = TRUE, 165 | cp("foo", "bar"), 166 | # do not include the cp output because it is slightly different on 167 | # macOS, Linux, etc. 168 | transform = function(x) utils::head(x, 3) 169 | ) 170 | }) 171 | -------------------------------------------------------------------------------- /tests/testthat/test-find-package-root.R: -------------------------------------------------------------------------------- 1 | test_that("find_package_root", { 2 | tmp <- tempfile() 3 | on.exit(unlink(tmp, recursive = TRUE), add = TRUE) 4 | mkdirp(file.path(tmp, "a", "b", "c", "d")) 5 | lns <- "Package: this" 6 | writeLines(lns, file.path(tmp, "DESCRIPTION")) 7 | 8 | expect_equal( 9 | readLines(file.path(find_package_root(tmp), "DESCRIPTION")), 10 | lns 11 | ) 12 | 13 | expect_equal( 14 | readLines(file.path( 15 | find_package_root(file.path(tmp, "a")), 16 | "DESCRIPTION" 17 | )), 18 | lns 19 | ) 20 | 21 | expect_equal( 22 | readLines(file.path( 23 | find_package_root(file.path(tmp, "a", "b", "c", "d")), 24 | "DESCRIPTION" 25 | )), 26 | lns 27 | ) 28 | 29 | wd <- getwd() 30 | on.exit(setwd(wd), add = TRUE) 31 | setwd(file.path(tmp, "a", "b", "c")) 32 | expect_equal( 33 | readLines(file.path(find_package_root("."), "DESCRIPTION")), 34 | lns 35 | ) 36 | }) 37 | 38 | test_that("find_package_root errors", { 39 | expect_snapshot(error = TRUE, find_package_root("file1130e91427d3")) 40 | 41 | if (!file.exists("/DESCRIPTION")) { 42 | expect_snapshot(error = TRUE, find_package_root("/")) 43 | } 44 | }) 45 | -------------------------------------------------------------------------------- /tests/testthat/test-rtools.R: -------------------------------------------------------------------------------- 1 | test_that("has_rtools finds rtools", { 2 | skip_if_not(is_windows() && !is.null(scan_path_for_rtools())) 3 | 4 | # Rtools path can be looked up by the PATH 5 | without_cache({ 6 | expect_true(has_rtools()) 7 | expect_true(!is.null(scan_path_for_rtools())) 8 | }) 9 | 10 | withr::with_path(Sys.getenv("R_HOME"), action = "replace", { 11 | expect_equal(scan_path_for_rtools(), NULL) 12 | }) 13 | 14 | skip_if_not(!is.null(scan_registry_for_rtools())) 15 | 16 | # Rtools path can be looked up from the registery 17 | expect_true(!is.null(scan_registry_for_rtools())) 18 | without_cache( 19 | withr::with_path(Sys.getenv("R_HOME"), action = "replace", { 20 | has_rtools() 21 | expect_true(all(rtools_path() != "")) 22 | }) 23 | ) 24 | }) 25 | 26 | test_that("rtools_needed works", { 27 | skip_if_not(is_windows()) 28 | 29 | # Test only frozen versions 30 | expect_equal(rtools_needed("3.6.3"), "Rtools 3.5") 31 | expect_equal(rtools_needed("2.9"), "Rtools 3.0") 32 | expect_equal(rtools_needed("0.0.0"), "the appropriate version of Rtools") 33 | }) 34 | -------------------------------------------------------------------------------- /tests/testthat/test-style.R: -------------------------------------------------------------------------------- 1 | test_that("style", { 2 | withr::local_options(cli.num_colors = 256) 3 | expect_snapshot( 4 | cat(style( 5 | ok = "OK", 6 | "\n", 7 | note = "NOTE", 8 | "\n", 9 | warn = "WARN", 10 | "\n", 11 | err = "ERROR", 12 | "\n", 13 | pale = "Not important", 14 | "\n", 15 | timing = "[1m]", 16 | "\n" 17 | )) 18 | ) 19 | }) 20 | -------------------------------------------------------------------------------- /tests/testthat/test-utils.R: -------------------------------------------------------------------------------- 1 | test_that("should_stop_for_warnings", { 2 | withr::local_options(pkg.build_stop_for_warnings = NULL) 3 | withr::local_envvar(PKG_BUILD_STOP_FOR_WARNINGS = NA_character_) 4 | expect_false(should_stop_for_warnings()) 5 | 6 | withr::local_options(pkg.build_stop_for_warnings = FALSE) 7 | withr::local_envvar(PKG_BUILD_STOP_FOR_WARNINGS = "true") 8 | expect_false(should_stop_for_warnings()) 9 | 10 | withr::local_options(pkg.build_stop_for_warnings = TRUE) 11 | withr::local_envvar(PKG_BUILD_STOP_FOR_WARNINGS = "false") 12 | expect_true(should_stop_for_warnings()) 13 | 14 | withr::local_options(pkg.build_stop_for_warnings = NULL) 15 | withr::local_envvar(PKG_BUILD_STOP_FOR_WARNINGS = "true") 16 | expect_true(should_stop_for_warnings()) 17 | 18 | withr::local_options(pkg.build_stop_for_warnings = NULL) 19 | withr::local_envvar(PKG_BUILD_STOP_FOR_WARNINGS = "false") 20 | expect_false(should_stop_for_warnings()) 21 | 22 | withr::local_options(pkg.build_stop_for_warnings = 1:10) 23 | withr::local_envvar(PKG_BUILD_STOP_FOR_WARNINGS = "false") 24 | expect_snapshot(error = TRUE, should_stop_for_warnings()) 25 | 26 | withr::local_options(pkg.build_stop_for_warnings = NULL) 27 | withr::local_envvar(PKG_BUILD_STOP_FOR_WARNINGS = "foobar") 28 | expect_snapshot(error = TRUE, should_stop_for_warnings()) 29 | }) 30 | 31 | test_that("isFALSE", { 32 | pos <- list(FALSE, structure(FALSE, class = "foo")) 33 | neg <- list(1, c(FALSE, TRUE), NA, list(FALSE)) 34 | for (p in pos) expect_true(isFALSE(p), info = p) 35 | for (n in neg) expect_false(isFALSE(n), info = n) 36 | }) 37 | 38 | test_that("should_add_compiler_flags", { 39 | # should not be called if option is set 40 | local_mocked_bindings(makevars_user = function() stop("dont")) 41 | 42 | # options is TRUE 43 | withr::local_options(pkg.build_extra_flags = TRUE) 44 | expect_true(should_add_compiler_flags()) 45 | 46 | # options is FALSE 47 | withr::local_options(pkg.build_extra_flags = FALSE) 48 | expect_false(should_add_compiler_flags()) 49 | 50 | # depends on whether Makevars exists 51 | withr::local_options(pkg.build_extra_flags = "missing") 52 | local_mocked_bindings(makevars_user = function() character()) 53 | expect_true(should_add_compiler_flags()) 54 | local_mocked_bindings(makevars_user = function() "foobar") 55 | withr::local_options(pkg.build_extra_flags = "missing") 56 | expect_false(should_add_compiler_flags()) 57 | 58 | local_mocked_bindings(makevars_user = function() stop("dont")) 59 | withr::local_options(pkg.build_extra_flags = NULL) 60 | 61 | # env var true 62 | withr::local_envvar(PKG_BUILD_EXTRA_FLAGS = "true") 63 | expect_true(should_add_compiler_flags()) 64 | 65 | # env var false 66 | withr::local_envvar(PKG_BUILD_EXTRA_FLAGS = "false") 67 | expect_false(should_add_compiler_flags()) 68 | 69 | # depends on whether Makevars exists 70 | withr::local_envvar(PKG_BUILD_EXTRA_FLAGS = "missing") 71 | local_mocked_bindings(makevars_user = function() character()) 72 | expect_true(should_add_compiler_flags()) 73 | local_mocked_bindings(makevars_user = function() "foobar") 74 | expect_false(should_add_compiler_flags()) 75 | 76 | # no option or env var, then TRUE 77 | local_mocked_bindings(makevars_user = function() stop("dont")) 78 | withr::local_options(pkg.build_extra_flags = NULL) 79 | withr::local_envvar(PKG_BUILD_EXTRA_FLAGS = NA_character_) 80 | expect_true(should_add_compiler_flags()) 81 | }) 82 | 83 | test_that("should_add_compiler_flags errors", { 84 | # invalid option type 85 | withr::local_options(pkg.build_extra_flags = 1:10) 86 | expect_snapshot(error = TRUE, should_add_compiler_flags()) 87 | 88 | # invalid option value 89 | withr::local_options(pkg.build_extra_flags = "foobar") 90 | expect_snapshot(error = TRUE, should_add_compiler_flags()) 91 | 92 | # invalid env var value 93 | withr::local_options(pkg.build_extra_flags = NULL) 94 | withr::local_envvar(PKG_BUILD_EXTRA_FLAGS = "foo") 95 | expect_snapshot(error = TRUE, should_add_compiler_flags()) 96 | }) 97 | -------------------------------------------------------------------------------- /tests/testthat/test-withr.R: -------------------------------------------------------------------------------- 1 | test_that("withr_with_makevars", { 2 | skip_on_cran() 3 | # need to run this without a user Makevars file 4 | file.create(tmpmake <- tempfile()) 5 | on.exit(unlink(tmpmake), add = TRUE) 6 | withr::local_envvar("R_MAKEVARS_USER" = tmpmake, R_MAKEVARS_SITE = tmpmake) 7 | 8 | split_lines <- function(x) strsplit(x, "\n\r?")[[1]] 9 | 10 | orig <- split_lines(callr::rcmd("config", "CFLAGS")$stdout) 11 | new <- withr_with_makevars( 12 | new = c(CFLAGS = "-DFOO"), 13 | split_lines(callr::rcmd("config", "CFLAGS")$stdout) 14 | ) 15 | expect_equal(new, paste(orig, "-DFOO")) 16 | 17 | # with our own custom Makevars file 18 | tmp <- tempfile() 19 | on.exit(unlink(tmp), add = TRUE) 20 | writeLines( 21 | c( 22 | "CFLAGS=-DTHIS", 23 | "CXXFLAGS+=-DTHAT" 24 | ), 25 | tmp 26 | ) 27 | 28 | orig <- split_lines(callr::rcmd("config", "CFLAGS")$stdout) 29 | new <- withr_with_makevars( 30 | new = c(CFLAGS = "-DFOO"), 31 | path = tmp, 32 | split_lines(callr::rcmd("config", "CFLAGS")$stdout) 33 | ) 34 | expect_equal(new, "-DTHIS -DFOO") 35 | 36 | orig <- split_lines(callr::rcmd("config", "CXXFLAGS")$stdout) 37 | new <- withr_with_makevars( 38 | new = c(CXXFLAGS = "-DFOO"), 39 | path = tmp, 40 | split_lines(callr::rcmd("config", "CXXFLAGS")$stdout) 41 | ) 42 | expect_equal(new, paste(orig, "-DTHAT -DFOO")) 43 | }) 44 | -------------------------------------------------------------------------------- /tests/testthat/testDummy/DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: testDummy 2 | Title: Tools to make developing R code easier 3 | License: GPL-2 4 | Description: 5 | Author: Hadley 6 | Maintainer: Hadley 7 | Version: 0.1 8 | -------------------------------------------------------------------------------- /tests/testthat/testDummy/NAMESPACE: -------------------------------------------------------------------------------- 1 | export(a) 2 | -------------------------------------------------------------------------------- /tests/testthat/testDummy/R/a.R: -------------------------------------------------------------------------------- 1 | a <- 1 2 | -------------------------------------------------------------------------------- /tests/testthat/testDummy/R/b.R: -------------------------------------------------------------------------------- 1 | b <- 2 2 | -------------------------------------------------------------------------------- /tests/testthat/testInstDoc/DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: testInstDoc 2 | Title: Tools to make developing R code easier 3 | License: GPL-2 4 | Description: 5 | Author: Hadley 6 | Maintainer: Hadley 7 | Version: 0.1 8 | Config/build/clean-inst-doc: FALSE 9 | VignetteBuilder: 10 | knitr 11 | Suggests: 12 | knitr, 13 | rmarkdown 14 | -------------------------------------------------------------------------------- /tests/testthat/testInstDoc/NAMESPACE: -------------------------------------------------------------------------------- 1 | export(a) 2 | -------------------------------------------------------------------------------- /tests/testthat/testInstDoc/R/a.R: -------------------------------------------------------------------------------- 1 | a <- 1 2 | -------------------------------------------------------------------------------- /tests/testthat/testInstDoc/R/b.R: -------------------------------------------------------------------------------- 1 | b <- 2 2 | -------------------------------------------------------------------------------- /tests/testthat/testInstDoc/inst/doc/keep.me: -------------------------------------------------------------------------------- 1 | Pretty please. 2 | -------------------------------------------------------------------------------- /tests/testthat/testInstDoc/vignettes/test.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "pkgbuild test" 3 | output: rmarkdown::html_vignette 4 | description: > 5 | This is a vignette to be used in the pkgbuild tests. 6 | vignette: > 7 | %\VignetteIndexEntry{pkgbuild test} 8 | %\VignetteEngine{knitr::rmarkdown} 9 | \usepackage[utf8]{inputenc} 10 | --- 11 | 12 | ```{r} 13 | #| label: setup 14 | #| include: FALSE 15 | knitr::opts_chunk$set(collapse = TRUE, comment = "#>") 16 | ``` 17 | 18 | ```{r} 19 | 1:10 20 | ``` 21 | -------------------------------------------------------------------------------- /tests/testthat/testWithSrc/DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: testWithSrc 2 | Title: Tools to make developing R code easier 3 | License: GPL-2 4 | Description: 5 | Author: Hadley 6 | Maintainer: Hadley 7 | Version: 0.1 8 | -------------------------------------------------------------------------------- /tests/testthat/testWithSrc/NAMESPACE: -------------------------------------------------------------------------------- 1 | export(a) 2 | -------------------------------------------------------------------------------- /tests/testthat/testWithSrc/R/a.R: -------------------------------------------------------------------------------- 1 | a <- 1 2 | -------------------------------------------------------------------------------- /tests/testthat/testWithSrc/R/b.R: -------------------------------------------------------------------------------- 1 | b <- 2 2 | -------------------------------------------------------------------------------- /tests/testthat/testWithSrc/src/add1.c: -------------------------------------------------------------------------------- 1 | // In C ---------------------------------------- 2 | #include 3 | #include 4 | 5 | SEXP add1(SEXP a) { 6 | SEXP result = PROTECT(allocVector(REALSXP, 1)); 7 | REAL(result)[0] = asReal(a) + 1; 8 | UNPROTECT(1); 9 | 10 | return result; 11 | } 12 | 13 | SEXP mult2(SEXP a) { 14 | SEXP result = PROTECT(allocVector(REALSXP, 1)); 15 | REAL(result)[0] = asReal(a) * 2; 16 | UNPROTECT(1); 17 | 18 | return result; 19 | } 20 | --------------------------------------------------------------------------------