Skip to content
This repository has been archived by the owner on Nov 8, 2023. It is now read-only.

Commit

Permalink
Update documentation vignettes
Browse files Browse the repository at this point in the history
  • Loading branch information
georgestagg committed Nov 1, 2023
1 parent 6687d6c commit 19de273
Show file tree
Hide file tree
Showing 6 changed files with 222 additions and 31 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@

This package provides functions to help build R packages compiled for WebAssembly (Wasm), manage Wasm binary R package libraries and repositories, and prepare webR compatible filesystem images for static web hosting of data files and R packages.

## Installation
## Requirements

Building binary R packages for Wasm requires cross-compiling packages containing C/C++/Fortran source code using a Wasm development toolchain. As such, the [Emscripten](https://emscripten.org) C/C++ toolchain and a [version of LLVM `flang` configured to output Wasm](https://github.com/lionel-/f18-llvm-project/tree/fix-webr) must be available in the environment for the `rwasm` package to function.

For convenience, webR GitHub repository provides a [Docker container](https://github.com/r-wasm/webr/pkgs/container/webr) containing the full Wasm development environment required for building Wasm R packages.

## Installation
The `rwasm` package is not yet on CRAN. You can install the development version from GitHub via:

```r
Expand Down
99 changes: 69 additions & 30 deletions vignettes/getting-started.Rmd
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
---
title: "Getting Started with rwasm"
title: "Getting started with rwasm"
output: rmarkdown::html_document
vignette: >
%\VignetteIndexEntry{Getting Started with rwasm}
%\VignetteIndexEntry{Getting started with rwasm}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
Expand Down Expand Up @@ -35,12 +35,12 @@ Then start R in the Docker container, mounting the new `output` directory into p
$ docker run -it --rm -v $(PWD)/output:/root -w /output ghcr.io/r-wasm/webr:v0.2.1 R
```

You may now continue by working in this R session. The Docker container will provide the required development environment and tools for building binary R packages for Wasm. R packages and repositories built in this way will be written in the `output` directory.
You may now continue by working in this R session. The Docker container will provide the required development environment and tools for building binary R packages for Wasm. R packages and repositories built in this way will be written in the directory `output`.


### WebR development installation

Building webR from source requires a long time and a lot of disk space, mostly due to the requirement of building LLVM `flang` from source. Ensure that you have both before continuing.
Note that building webR from source takes a long time and a lot of disk space, mostly due to the requirement of building LLVM `flang` from source.

Follow [the webR build instructions](https://github.com/r-wasm/webr#building-webr-from-source) to download and build webR from source. If you are planning to build R packages that depend on system libraries, ensure that you also perform the optional step to additionally build all WebAssembly libraries.

Expand All @@ -49,66 +49,105 @@ Once webR has been built, we need to configure your environment so that the nati
Edit or create the file `~/.webr-vars.mk`, and ensure it contains the following lines, replacing the values with your own installation directories:

```Makefile
WEBR_ROOT=/home/username/some/directory/webr
EMSCRIPTEN_ROOT=/home/username/some/directory/emscripten
WEBR_ROOT=/home/username/webr
EMSCRIPTEN_ROOT=/home/username/emsdk/upstream/emscripten
```

The settings above can also be made available by exporting them as environment variables.
The settings above can also be made available to R by exporting them as environment variables.

## Installing and using the `rwasm` package
## Installing the `rwasm` package

The `rwasm` package builds R binary packages for WebAssembly, organising the output into CRAN-like repositories. The [pak](https://pak.r-lib.org) package can be used to install `rwasm` from GitHub.

```{r eval=FALSE}
install.packages("pak")
pak::pak("georgestagg/rwasm")
pak::pak("r-wasm/webr-repo@pkg")
```

### Adding an R package to the repository
Once installed, load the `rwasm` package. If `rwasm` is able to find the Wasm development environment, it will print the directories it discovered and the version of webR that it is targeting. This might be different to the version of R installed on your native system.

Use `add_pkg()` to build an R package for Wasm and add it to a repository. A new directory `./repo` will be created for the repository if it does not already exist, otherwise the existing repository will be updated to include the new package. By default, hard package dependencies will also be built for Wasm and added to the repository.
```{r eval=FALSE}
library(rwasm)
#> Targeting Wasm packages for R 4.3.0
#> With `WEBR_ROOT` directory: /home/username/webr
#> With `EMSCRIPTEN_ROOT` directory: /home/username/emsdk/upstream/emscripten
```

## Adding an R package to the repository

Use `add_pkg()` to build an R package for Wasm and add it to a repository. A new directory named `repo` will be created for the repository if it does not already exist, otherwise the existing repository will be updated to include the new package. By default, hard package dependencies will also be built for Wasm and added to the repository.

```{r eval=FALSE}
rwasm::add_pkg("cli")
add_pkg("cli")
```

See the `?pkgdepends::pkg_refs` article to see what kinds of packages can be added to the repository in this way.
See the `?pkgdepends::pkg_refs` article to see which kind of package references can be used to add packages to the repository.

### Managing and using the repository
## Managing and using the repository

The output files in `./repo` form a CRAN-like R package repository. These files should be served so that they are available at some URL. That URL can then be passed to `webr::install()` as a repository from which to install Wasm R packages.
The CRAN-like R package repository in the output directory `repo` should be hosted by a web server so that it is available at some URL. Such a URL can then be passed to `webr::install()` as a repository from which to install Wasm R packages.

##### Local testing
### Local testing

A local web server can be used to serve your package repository for testing. The following command starts a HTTP web server powered by the Node.js module `http-server`^[You might need to run `npm install -g http-server` to ensure that the server software is available.]. This particular server software is useful because it easily supports [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) out-of-the-box, required for cross-origin package loading.
The following R command starts a local web server to serve your package repository for testing^[Ensure that the latest version of the `httpuv` package is installed so that the `?httpuv::runStaticServer` function is available.]. When serving your repository locally, be sure to include the `Access-Control-Allow-Origin: *` HTTP header, required for loading R packages from a cross-origin server by the [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) mechanism.

```console
npx http-server --cors='*' -p 9090
```r
httpuv::runStaticServer(
dir = ".",
port = 9090,
browse = FALSE,
headers = list("Access-Control-Allow-Origin" = "*")
)
```

Once the web server is running, start webR session in your browser such as <https://webr.r-wasm.org/latest/>. Install a package from your local repository using your test server URL as the `repos` argument^[You might need to adjust the path portion of the URL, depending on your set up. If it does not work, you could also try `"http://127.0.0.1:9090"` or `"http://127.0.0.1:9090/output/repo"`]:
Once the web server is running start a webR session in your browser, such as the console at <https://webr.r-wasm.org/latest/>. Install a package from your local repository using your test server URL as the `repos` argument^[You might need to adjust the path portion of the URL, depending on your set-up. If it does not work, you could also try `"http://127.0.0.1:9090"` or `"http://127.0.0.1:9090/output/repo"`]:


```{r eval=FALSE}
webr::install("cli", repos = "http://127.0.0.1:9090/repo")
#> Downloading webR package: cli
```

If successful, you should be able to load the `{cli}` package within webR.
### Deployment to static hosting

```{r eval=FALSE}
library(cli)
Once you are happy that your R package repository is working and sufficient, it should be deployed to the web via a static file hosting service of your choice. In this example we will use GitHub Pages.

First, create a new directory for your GitHub repository and copy your webR binary repo into place.

```
$ mkdir -p my-wasm-repo
$ cp -r ./path/to/output/repo my-wasm-repo/repo
$ cd my-wasm-repo
```

##### Deploying
Next, initialise a new GitHub repository to host your binary R packages, and run the commands given by GitHub to initialise and push an initial commit of your Wasm binary packages.

Once you are happy that your R package repository is working and sufficient, it should be deployed to the web via the static file hosting service of your choice. ee the following article for an example of how to use GitHub Pages for this purpose.
```
$ git init
$ git add repo
$ git commit -m "First commit"
$ git branch -M main
$ git remote add origin https://github.com/username/my-wasm-repo.git
$ git push -u origin main
```

Now, in your web browser, refresh your GitHub project and click **Settings**. If you cannot see the "Settings" tab, click the dropdown menu, then click **Settings**.

![](images/settings.png)

* TODO: Deploying an R package repository to GitHub Pages
In the "Code and automation" section of the sidebar, click **Pages**.

### Mounting an Emscripten virtual filesystem image
Under "Build and deployment", under "Source", select **Deploy from a branch**.

TODO
Under "Branch", use the branch dropdown menu and select **main** as the publishing source, then click **Save**.

### Mounting a host R library directory with Node.js
![](images/deploy.png)

TODO
GitHub will then start to prepare your GitHub Pages site to contain your CRAN-like Wasm package repository.

After a little while^[You should be able to see progress of the website build step in the **Actions** section of your GitHub project.], your GitHub Pages website will be ready and webR should be able to install your package from the GitHub Pages repo URL.

```{r eval=FALSE}
webr::install("cli", repos = "http://username.github.io/my-wasm-repo/repo")
#> Downloading webR package: cli
```
Binary file added vignettes/images/deploy.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added vignettes/images/settings.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
86 changes: 86 additions & 0 deletions vignettes/mount-fs-image.Rmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
---
title: "Mounting filesystem images"
output: rmarkdown::html_document
vignette: >
%\VignetteIndexEntry{Mounting filesystem images}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---

## Introduction

The Emscripten WebAssembly environment provides a virtual filesystem (VFS) which supports the concept of *mounting*. With this, an entire file and directory structure can be packaged into a filesystem image to be loaded and mounted at runtime by WebAssembly (Wasm) applications. We can take advantage of this interface to efficiently mount R package libraries, pre-packaged and containing potentially many related R packages, in the VFS accessible to webR.

## Building an R package library

Similarly to the `vignette("getting-started")` article, to build an R package library image we must first build one or more Wasm R packages using `add_pkg()`. As an example, let's build a package with a few hard dependencies. Ensure that you are running R in an environment with access to Wasm development tools^[See the "Setting up the WebAssembly toolchain" section in `vignette("getting-started")` for further details.], then run:

```{r eval=FALSE}
rwasm::add_pkg("dplyr")
```

After the build process has completed, the new `repo` directory contains a CRAN-like package repository with R packages build for Wasm.

Next, run the following to build an Emscripten VFS image:

```{r eval=FALSE}
rwasm::make_vfs_library()
```

By default, this function will create a new directory named `vfs` if it does not exist. The files `vfs/library.data` and `vfs/library.js.metadata` together form an Emscripten filesystem image containing an R package library consisting of all the packages previously added to the CRAN-like repository in `repo` using `add_pkg()`.

## Mounting filesystem images

The filesystem image should now be hosted by a web server so that it is available at some URL. Such a URL can then be passed to `webr::mount()` to be made available somewhere on the virtual filesystem for the Wasm R process.

### Local testing

The following R command starts a local web server to serve your filesystem image for testing^[Ensure that the latest version of the `httpuv` package is installed so that the `?httpuv::runStaticServer` function is available.]. When serving your files locally, be sure to include the `Access-Control-Allow-Origin: *` HTTP header, required for downloading files from a cross-origin server by the [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) mechanism.

```r
httpuv::runStaticServer(
dir = ".",
port = 9090,
browse = FALSE,
headers = list("Access-Control-Allow-Origin" = "*")
)
```

Once the web server is running start a webR session in your browser, such as the console at <https://webr.r-wasm.org/latest/>. Use `webr::mount()` to make the R library image available somewhere on the VFS^[You might need to adjust the path portion of the URL, depending on your set-up. If it does not work, you could also try `"http://127.0.0.1:9090"` or `"http://127.0.0.1:9090/output/vfs"`]:

```{r eval=FALSE}
webr::mount("/my-library", "http://127.0.0.1:9090/vfs/library.data")
```

Once mounted, the contents of the filesystem image are available at `/my-library` in the virtual filesystem.

```{r eval=FALSE}
list.files("/my-library")
#> [1] "R6" "cli" "dplyr" "fansi" "generics" "glue"
#> [7] "lifecycle" "magrittr" "pillar" "pkgconfig" "rlang" "tibble"
#> [13] "tidyselect" "utf8" "vctrs" "withr"
```

This new directory should be added to R's `.libPaths()`, so that R packages may be loaded from the new library.

```{r eval=FALSE}
.libPaths(c(.libPaths(), "/my-library"))
library(dplyr)
#> Attaching package: ‘dplyr’
#>
#> The following objects are masked from ‘package:stats’:
#>
#> filter, lag
#>
#> The following objects are masked from ‘package:base’:
#>
#> intersect, setdiff, setequal, union
```

### Deployment

The filesystem image files should be deployed to the static file hosting service of your choice, so that they are available for download anywhere. See the "Deployment to static hosting" section in `vignette("getting-started")` for an example of how to host static files with GitHub pages, substituting the `repo` directory for the `vfs` directory in the example.

### Packaging other data

Other arbitrary data in the form of files and directories can be packaged for mounting using the same method described above with the `rwasm::file_packager()` function.
65 changes: 65 additions & 0 deletions vignettes/mount-host-dir.Rmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
title: "Mounting host directories in node"
output: rmarkdown::html_document
vignette: >
%\VignetteIndexEntry{Mounting host directories in node}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---

## Introduction

When running under Node.js, the Emscripten WebAssembly environment can make available the contents of a directory on the host filesystem. In addition to providing webR access to external data files, a pre-prepared R package library can be mounted from the host filesystem. This avoids the need to download potentially large R packages or filesystem images over the network.

## Building an R package library

Similarly to the `vignette("getting-started")` article, to build an R package library we must first build one or more Wasm R packages using `add_pkg()`. As an example, let's build a package with a few hard dependencies. Ensure that you are running R in an environment with access to Wasm development tools^[See the "Setting up the WebAssembly toolchain" section in `vignette("getting-started")` for further details.], then run:

```{r eval=FALSE}
rwasm::add_pkg("dplyr")
```

After the build process has completed, the new `repo` directory contains a CRAN-like package repository with R packages build for Wasm.

Next, run the following to build an R package library:

```{r eval=FALSE}
rwasm::make_library()
```

By default, this function will create a new directory named `lib` if it does not already exist. This directory will contain an R package library consisting of all the packages previously added to the CRAN-like repository in `repo` using `add_pkg()`.

## Mounting host directories

In your node application, use the webR JS API to create a new directory on the VFS and mount the host directory containing your R packages:

```js
await webR.init();
await webR.FS.mkdir("/my-library");
await webR.FS.mount('NODEFS', { root: '/path/to/lib' }, "/my-library");
```

Once mounted, the contents of your host R library directory are available at `/my-library` in the virtual filesystem.

```{r eval=FALSE}
list.files("/my-library")
#> [1] "R6" "cli" "dplyr" "fansi" "generics" "glue"
#> [7] "lifecycle" "magrittr" "pillar" "pkgconfig" "rlang" "tibble"
#> [13] "tidyselect" "utf8" "vctrs" "withr"
```

This new directory should be added to R's `.libPaths()`, so that R packages may be loaded from the new library.

```{r eval=FALSE}
.libPaths(c(.libPaths(), "/my-library"))
library(dplyr)
#> Attaching package: ‘dplyr’
#>
#> The following objects are masked from ‘package:stats’:
#>
#> filter, lag
#>
#> The following objects are masked from ‘package:base’:
#>
#> intersect, setdiff, setequal, union
```

0 comments on commit 19de273

Please sign in to comment.