Skip to content

Commit

Permalink
feat(r-dependent-packages): new feature r-dependent-packages (#226)
Browse files Browse the repository at this point in the history
  • Loading branch information
eitsupi authored May 12, 2024
1 parent b24eea2 commit 2057c93
Show file tree
Hide file tree
Showing 16 changed files with 413 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ jobs:
pandoc: ./**/pandoc/**
quarto-cli: ./**/quarto-cli/**
r-apt: ./**/r-apt/**
r-dependent-packages: ./**/r-dependent-packages/**
r-history: ./**/r-history/**
r-packages: ./**/r-packages/**
r-rig: ./**/r-rig/**
Expand Down Expand Up @@ -90,6 +91,7 @@ jobs:
- pandoc
- quarto-cli
- r-apt
- r-dependent-packages
- r-history
- r-packages
- r-rig
Expand Down Expand Up @@ -121,6 +123,7 @@ jobs:
- pandoc
- quarto-cli
- r-apt
- r-dependent-packages
- r-history
- r-packages
- r-rig
Expand Down
83 changes: 83 additions & 0 deletions src/r-dependent-packages/NOTES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<!-- markdownlint-disable MD041 -->

## System Requirements

Please use this with an R-installed image (e.g. [`ghcr.io/rocker-org/devcontainer/r-ver`](https://rocker-project.org/images/devcontainer/images.html))
or this should be installed after installing Features that installs R
(e.g. [`ghcr.io/rocker-org/devcontainer-features/r-apt`](https://github.com/rocker-org/devcontainer-features/tree/main/src/r-apt),
[`ghcr.io/rocker-org/devcontainer-features/r-rig`](https://github.com/rocker-org/devcontainer-features/tree/main/src/r-rig)).

```json
"features": {
"ghcr.io/rocker-org/devcontainer-features/r-rig:1": {},
"ghcr.io/rocker-org/devcontainer-features/r-dependent-packages:latest": {}
}
```

## DESCRIPTION file format

This feature installs packages listed in the manifest file named `DESCRIPTION`.
If you are new to the `DESCRIPTION` file, please refer to the
[`usethis::use_description()`](https://usethis.r-lib.org/reference/use_description.html) function's reference.

Here is an minimal example of the `DESCRIPTION` file.
At least, the `Package` and `Version` fields are required.
The `Imports` field is a list of packages that will be installed.

```dcf
Package: foo
Version: 0.0.0.9000
Imports:
cli,
rlang
```

### Additional fields

When developing an R package, we may want to specify dependencies that are only needed during development
in the `DESCRIPTION` file. In such cases, we can use any field name starting with `Config`,
and generally specify multiple fields prefixed with `Config/Needs` as follows:

```dcf
Package: foo
Version: 0.0.0.9000
Suggests:
cli
Config/Needs/website:
curl
Config/Needs/dev:
crayon
```

If we want use such fields to install dependencies, we can specify the `dependencyTypes` field of
this Feature like this:

```json
"ghcr.io/rocker-org/devcontainer-features/r-dependent-packages:latest": {
"dependencyTypes": "all,Config/Needs/website,Config/Needs/dev"
}
```

## Environment variables

Enviroment variables listed in [the `containerEnv` field](https://containers.dev/implementors/json_reference/#general-properties)
are used in the package installation process.
See [the reference of the `pak` package](https://pak.r-lib.org/reference/pak-config.html) for options for `pak`.

```json
"containerEnv": {
"NOT_CRAN": "true",
"PKG_CRAN_MIRROR": "https://cloud.r-project.org/"
}
```

## Cache directory and cache volume

The package cache directory in the container is set to `/pak/cache`.

This directory is stored in a volume named `devcontainer-pak-cache`
and is shared among multiple containers.

## References

- [pak](https://pak.r-lib.org/)
81 changes: 81 additions & 0 deletions src/r-dependent-packages/devcontainer-feature.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
{
"name": "R packages from the DESCRIPTION file (via pak)",
"id": "r-dependent-packages",
"version": "0.1.0",
"description": "This Feature sets scripts to install dependent R packages from the DESCRIPTION file in the repository.",
"documentationURL": "https://github.com/rocker-org/devcontainer-features/tree/main/src/r-dependent-packages",
"options": {
"when": {
"type": "string",
"default": "postCreate",
"enum": [
"onCreate",
"updateContent",
"postCreate"
],
"description": "When to install the dependent R packages? Each option corresponds to the lifecycle scripts."
},
"pakVersion": {
"type": "string",
"enum": [
"auto",
"devel",
"stable"
],
"default": "auto",
"description": "Version of pak to install. By default, the stable version is installed if needed."
},
"manifestRoot": {
"type": "string",
"default": ".",
"description": "The root path of the DESCRIPTION file recording the dependent R packages. Passed to the `root` argument of the `pak::local_install_deps()` function.",
"proposals": [
"."
]
},
"additionalRepositories": {
"type": "string",
"default": "",
"description": "String passed to the `pak::repo_add()` function.",
"proposals": [
"",
"rhub = 'https://r-hub.r-universe.dev', jeroen = 'https://jeroen.r-universe.dev'"
]
},
"dependencyTypes": {
"type": "string",
"default": "all",
"description": "Comma separated list of dependency types to install. Passed to the `dependencies` argument of the `pak::local_install_deps()` function.",
"proposals": [
"all",
"hard",
"all,Config/Needs/website"
]
}
},
"containerEnv": {
"PKG_PACKAGE_CACHE_DIR": "/pak/cache"
},
"mounts": [
{
"source": "devcontainer-pak-cache",
"target": "/pak/cache",
"type": "volume"
}
],
"onCreateCommand": {
"r-dependent-packages": "/usr/local/share/rocker-devcontainer-features/r-dependent-packages/scripts/oncreate.sh"
},
"updateContentCommand": {
"r-dependent-packages": "/usr/local/share/rocker-devcontainer-features/r-dependent-packages/scripts/updatecontent.sh"
},
"postCreateCommand": {
"r-dependent-packages": "/usr/local/share/rocker-devcontainer-features/r-dependent-packages/scripts/postcreate.sh"
},
"installsAfter": [
"ghcr.io/devcontainers/features/common-utils",
"ghcr.io/rocker-org/devcontainer-features/r-apt",
"ghcr.io/rocker-org/devcontainer-features/r-packages",
"ghcr.io/rocker-org/devcontainer-features/r-rig"
]
}
3 changes: 3 additions & 0 deletions src/r-dependent-packages/empty_script.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/usr/bin/env bash

echo "This script is empty. Done nothing..."
87 changes: 87 additions & 0 deletions src/r-dependent-packages/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#!/usr/bin/env bash

WHEN=${WHEN:-"postCreate"}
PAK_VERSION=${PAKVERSION:-"auto"}
ROOT=${MANIFESTROOT:-"."}
REPOS=${ADDITIONALREPOSITORIES:-""}
DEPS=${DEPENDENCYTYPES:-"all"}

PKG_PACKAGE_CACHE_DIR=${PKG_PACKAGE_CACHE_DIR:-"/pak/cache"}

USERNAME=${USERNAME:-${_REMOTE_USER}}

LIFECYCLE_SCRIPTS_DIR="/usr/local/share/rocker-devcontainer-features/r-dependent-packages/scripts"

set -e

if [ "$(id -u)" -ne 0 ]; then
echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
exit 1
fi

create_cache_dir() {
if [ -d "$1" ]; then
echo "Cache directory $1 already exists. Skip creation..."
else
echo "Create cache directory $1..."
mkdir -p "$1"
fi

if [ -z "$2" ]; then
echo "No username provided. Skip chown..."
else
echo "Change owner of $1 to $2..."
chown -R "$2:$2" "$1"
fi
}

check_r() {
if [ ! -x "$(command -v R)" ]; then
echo "(!) Cannot run R. Please install R before installing this Feature."
echo " Skip installation..."
exit 0
fi
}

install_pak() {
local version=$1

if [ "${version}" = "auto" ]; then
if su "${USERNAME}" -c "R -s -e 'packageVersion(\"pak\")'" >/dev/null 2>&1; then
echo "pak is already installed. Skip pak installation..."
return
else
version="stable"
fi
fi

echo "Installing pak ${version}..."
# shellcheck disable=SC2016
su "${USERNAME}" -c 'R -q -e "install.packages(\"pak\", repos = sprintf(\"https://r-lib.github.io/p/pak/'"${version}"'/%s/%s/%s\", .Platform\$pkgType, R.Version()\$os, R.Version()\$arch))"'
}

export DEBIAN_FRONTEND=noninteractive

create_cache_dir "${PKG_PACKAGE_CACHE_DIR}" "${USERNAME}"

# Set Lifecycle scripts
mkdir -p "${LIFECYCLE_SCRIPTS_DIR}"

POSSIBLE_LIFECYCLE=("onCreate" "updateContent" "postCreate")
for lifecycle in "${POSSIBLE_LIFECYCLE[@]}"; do
cp empty_script.sh "${LIFECYCLE_SCRIPTS_DIR}/${lifecycle,,}.sh"
done

# Enxure pak installed
check_r
install_pak "${PAK_VERSION}"

# Replace the target lifecycle script
echo "Set the lifecycle script for '${WHEN}'..."
sed \
-e "s|@ROOT@|${ROOT}|" \
-e "s|@REPOS@|${REPOS//"'"/'"'}|" \
-e "s|@DEPS@|${DEPS}|" \
lifecycle_script.sh >"${LIFECYCLE_SCRIPTS_DIR}/${WHEN,,}.sh"

echo "Done!"
27 changes: 27 additions & 0 deletions src/r-dependent-packages/lifecycle_script.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/usr/bin/env bash

PKG_PACKAGE_CACHE_DIR=${PKG_PACKAGE_CACHE_DIR:-"/pak/cache"}

set -e

fix_permissions() {
local dir
dir="${1}"

if [ ! -w "${dir}" ]; then
echo "Fixing permissions of '${dir}'..."
sudo chown -R "$(id -u):$(id -g)" "${dir}"
echo "Done!"
else
echo "Permissions of '${dir}' are OK!"
fi
}

fix_permissions "${PKG_PACKAGE_CACHE_DIR}"

echo "Install dependent R packages..."

R -q -e \
'pak::repo_add(@REPOS@); pak::local_install_deps("@ROOT@", dependencies = trimws(unlist(strsplit("@DEPS@", ","))))'

echo "Done!"
13 changes: 13 additions & 0 deletions test/r-dependent-packages/r-ver-default.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/usr/bin/env bash

set -e

# Optional: Import test library bundled with the devcontainer CLI
source dev-container-features-test-lib

# Feature-specific tests
check "R cli package" bash -c "R -q -e 'names(installed.packages()[, 3])' | grep cli"
check "R rlang package" bash -c "R -q -e 'names(installed.packages()[, 3])' | grep rlang"

# Report result
reportResults
5 changes: 5 additions & 0 deletions test/r-dependent-packages/r-ver-default/DESCRIPTION
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Package: foo
Version: 0.0.0.9000
Imports:
cli,
rlang
13 changes: 13 additions & 0 deletions test/r-dependent-packages/r-ver-postcreate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/usr/bin/env bash

set -e

# Optional: Import test library bundled with the devcontainer CLI
source dev-container-features-test-lib

# Feature-specific tests
check "R cli package" bash -c "R -q -e 'names(installed.packages()[, 3])' | grep cli"
check "R rlang package" bash -c "R -q -e 'names(installed.packages()[, 3])' | grep rlang"

# Report result
reportResults
5 changes: 5 additions & 0 deletions test/r-dependent-packages/r-ver-postcreate/DESCRIPTION
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Package: foo
Version: 0.0.0.9000
Imports:
cli,
rlang
13 changes: 13 additions & 0 deletions test/r-dependent-packages/r-ver-updatecontent.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/usr/bin/env bash

set -e

# Optional: Import test library bundled with the devcontainer CLI
source dev-container-features-test-lib

# Feature-specific tests
check "R cli package" bash -c "R -q -e 'names(installed.packages()[, 3])' | grep cli"
check "R rlang package" bash -c "R -q -e 'names(installed.packages()[, 3])' | grep rlang"

# Report result
reportResults
5 changes: 5 additions & 0 deletions test/r-dependent-packages/r-ver-updatecontent/DESCRIPTION
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Package: foo
Version: 0.0.0.9000
Imports:
cli,
rlang
14 changes: 14 additions & 0 deletions test/r-dependent-packages/realworld.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env bash

set -e

# Optional: Import test library bundled with the devcontainer CLI
source dev-container-features-test-lib

# Feature-specific tests
check "R cli package" bash -c "R -q -e 'names(installed.packages()[, 3])' | grep cli"
check "R rlang package" bash -c "R -q -e 'names(installed.packages()[, 3])' | grep curl"
check "R rlang package" bash -c "R -q -e 'names(installed.packages()[, 3])' | grep crayon"

# Report result
reportResults
8 changes: 8 additions & 0 deletions test/r-dependent-packages/realworld/DESCRIPTION
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Package: foo
Version: 0.0.0.9000
Suggests:
cli
Config/Needs/website:
curl
Config/Needs/dev:
github::r-lib/crayon
Loading

0 comments on commit 2057c93

Please sign in to comment.