Skip to content

Commit

Permalink
Merge branch 'main' into set-webr-env
Browse files Browse the repository at this point in the history
  • Loading branch information
georgestagg committed Apr 24, 2024
2 parents 6e87278 + 6865be3 commit 373d8c8
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 23 deletions.
42 changes: 27 additions & 15 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
WEBR_ROOT = $(abspath .)
WEBR_ROOT ?= $(abspath .)
WASM = $(WEBR_ROOT)/wasm
HOST = $(WEBR_ROOT)/host
TOOLS = $(WEBR_ROOT)/tools
Expand All @@ -8,53 +8,65 @@ default: webr
# This is created at configure-time
include $(TOOLS)/fortran.mk

# Build webR and install the web app in `./dist`
.PHONY: webr
webr: $(EMFC) $(FORTRAN_WASM_LIB) libs
webr: $(EMFC) $(FORTRAN_WASM_LIB) libs ## Build webR and install the web app in `./dist`
cd R && $(MAKE) && $(MAKE) install
cd src && $(MAKE)

# Supporting libs for webr
.PHONY: libs
libs:
libs: ## Compile supporting libs for webR
cd libs && $(MAKE)

# Build webR in a temporary docker container
.PHONY: docker-webr
docker-webr:
docker-webr: ## Build webR in a temporary Docker container
mkdir -p dist
docker build -t webr-build .
docker run --rm --mount type=bind,source=$(PWD),target=/app webr-build

# Create a permanent docker container for building webR. Call `make`
# from within the container to start the build.
.PHONY: docker-container-%
docker-container-%:
docker-container-%: ## Build webR development Docker container using a supplied name
docker build -t webr-build .
docker run -dit --name $* --mount type=bind,source=$(PWD),target=/app webr-build bash

docs: docs/build
docs/build:
docs: docs/build ## Build documentation and Quarto website
docs/build: ## Build documentation and Quarto website
cd docs && $(MAKE) api && $(MAKE) html

.PHONY: check
check:
check: ## Run unit tests and calculate coverage
cd src && $(MAKE) check

.PHONY: check-pr
check-pr:
check-pr: ## Run additional pull request tests, linter, and calculate coverage
cd src && $(MAKE) lint && $(MAKE) check && $(MAKE) check-packages

.PHONY: clean
clean:
clean: ## Remove Wasm R build
rm -rf $(HOST) $(WASM)/R-*
cd R && $(MAKE) clean

.PHONY: clean-wasm
clean-wasm: clean
clean-wasm: clean ## Remove Wasm R build and supporting libs
rm -rf $(WASM)
cd libs && $(MAKE) clean

.PHONY: distclean
distclean: clean-wasm clean-tools
distclean: clean-wasm clean-tools ## Remove Wasm R build, supporting libs, build tools, and webR distribution
rm -rf dist

.PHONY: help
help: ## Show help messages for make targets
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(firstword $(MAKEFILE_LIST)) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[32m%-18s\033[0m %s\n", $$1, $$2}'

.PHONY: debug
debug: ## Print all variables for debugging
@printf "\033[32m%-18s\033[0m %s\n" "WEBR_ROOT" "$(WEBR_ROOT)"
@printf "\033[32m%-18s\033[0m %s\n" "WASM" "$(WASM)"
@printf "\033[32m%-18s\033[0m %s\n" "HOST" "$(HOST)"
@printf "\033[32m%-18s\033[0m %s\n" "TOOLS" "$(TOOLS)"
@printf "\033[32m%-18s\033[0m %s\n" "EMFC" "$(EMFC)"
@printf "\033[32m%-18s\033[0m %s\n" "FORTRAN_WASM_LIB" "$(FORTRAN_WASM_LIB)"
@printf "\033[32m%-18s\033[0m %s\n" "PWD" "$(PWD)"
@printf "\033[32m%-18s\033[0m %s\n" "MAKE" "$(MAKE)"
6 changes: 6 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,14 @@

* A `webR.version` property has been added, containing the current version and build information (#409).

* An `RObject.class()` method has been added, returning an `RCharacter` object with the names of the classes from which the given R object inherits. This has been implemented using R's `class()` function, and so the implicit class is similarly returned when the R object has no `class` attribute.

* WebR now sets the environment variable `WEBR` equal to `"1"` and `WEBR_VERSION` equal to the webR version string in the WebAssembly environment (#414).

## Bug Fixes

* Fix installing packages via shim with `character.only = TRUE` (#413).

# webR 0.3.2

## New features
Expand Down
8 changes: 6 additions & 2 deletions packages/webr/R/library.R
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ library_shim <- function(pkg, ..., show_menu = getOption("webr.show_menu")) {
return(invisible(NULL))
}
}
base::library(package, character.only = TRUE, ...)
args <- list(package, character.only = TRUE, ...)
args <- args[!duplicated(names(args))]
do.call(base::library, args)
}

#' @rdname library_shim
Expand All @@ -46,5 +48,7 @@ require_shim <- function(pkg, ..., show_menu = getOption("webr.show_menu")) {
return(invisible(NULL))
}
}
base::require(package, character.only = TRUE, ...)
args <- list(package, character.only = TRUE, ...)
args <- args[!duplicated(names(args))]
do.call(base::require, args)
}
34 changes: 34 additions & 0 deletions src/tests/webR/robj.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,40 @@ test('Get RObject type as a string', async () => {
expect(await result.toString()).toEqual('[object RObject:null]');
});

describe('Working with R object classes', () => {
test('Get R object intrinsic class', async () => {
const numeric = await new webR.RDouble([1, 2, 3]);
const character = await new webR.RCharacter(['a', 'b', 'c']);

const numericClass = await numeric.class();
const characterClass = await character.class();
expect(await numericClass.toArray()).toContain("numeric");
expect(await characterClass.toArray()).toContain("character");
});

test('Get R object class vector', async () => {
const jsObj = { a: [1, 2, 3], b: [3, 4, 5], c: ['x', 'y', 'z'] };
const rObj = await new webR.RDataFrame(jsObj);
const classes = await rObj.class();
expect(await classes.toArray()).toEqual(["data.frame"]);
});

test('Get R object class vector from attributes', async () => {
const rObj = await webR.evalR('x <- 123; class(x) <- c("abc", "def"); x');
const classes = await rObj.class();
expect(await classes.toArray()).toEqual(["abc", "def"]);
});

test('Get S4 R object class vector', async () => {
const rClass = await webR.evalR('getClass("MethodDefinition")');
const classes = await rClass.class();
const attrs = await rClass.attrs();
const attrPackage = await attrs.get('package');
expect(await classes.toArray()).toEqual(["classRepresentation"]);
expect(await attrPackage.toString()).toEqual("methods");
});
});

describe('Working with R lists and vectors', () => {
test('Get R object attributes', async () => {
const vector = await webR.evalR('c(a=1, b=2, c=3)');
Expand Down
19 changes: 13 additions & 6 deletions src/tests/webR/webr-worker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ beforeAll(async () => {

describe('Download and install binary webR packages', () => {
test('Install packages via evalR', async () => {
// eslint-disable-next-line @typescript-eslint/no-empty-function
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => { });
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => null);

// Downloading and extracting a .tgz package
await webR.evalR(
Expand All @@ -36,8 +35,7 @@ describe('Download and install binary webR packages', () => {
});

test('Install packages quietly', async () => {
// eslint-disable-next-line @typescript-eslint/no-empty-function
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => { });
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => null);
await webR.evalR(
'webr::install("Matrix", repos="https://repo.r-wasm.org/", mount = FALSE, quiet = TRUE)'
);
Expand All @@ -46,13 +44,22 @@ describe('Download and install binary webR packages', () => {
});

test('Install packages via API', async () => {
// eslint-disable-next-line @typescript-eslint/no-empty-function
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => { });
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => null);
await webR.installPackages(['MASS'], { mount: false });
const pkg = (await webR.evalR('"MASS" %in% library(MASS)')) as RLogical;
expect(await pkg.toBoolean()).toEqual(true);
warnSpy.mockRestore();
});

test('Install packages via package shim', async () => {
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => null);
const pkg = (await webR.evalR(`
webr::shim_install()
"rlang" %in% library("rlang", character.only = TRUE, show_menu = FALSE, quiet = TRUE)
`)) as RLogical;
expect(await pkg.toBoolean()).toEqual(true);
warnSpy.mockRestore();
});
});

describe('Test webR virtual filesystem', () => {
Expand Down
11 changes: 11 additions & 0 deletions src/webR/robj-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,17 @@ export class RObject extends RObjectBase {
return RPairlist.wrap(Module._ATTRIB(this.ptr));
}

class(): RCharacter {
const prot = { n: 0 };
const classCall = new RCall([new RSymbol('class'), this]);
protectInc(classCall, prot);
try {
return classCall.eval() as RCharacter;
} finally {
unprotect(prot.n);
}
}

setNames(values: (string | null)[] | null): this {
let namesObj: RObject;

Expand Down

0 comments on commit 373d8c8

Please sign in to comment.