From 549cc3a2459553f53b92f5ced4a9fbab4aa16329 Mon Sep 17 00:00:00 2001 From: Bryce Mecum Date: Tue, 19 May 2026 14:16:33 -0700 Subject: [PATCH 01/22] Try to fix issue --- r/R/arrow-package.R | 5 +++++ r/src/safe-call-into-r-impl.cpp | 11 ++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/r/R/arrow-package.R b/r/R/arrow-package.R index 2706faee5cb1..c5b3008a6ca9 100644 --- a/r/R/arrow-package.R +++ b/r/R/arrow-package.R @@ -150,6 +150,11 @@ s3_finalizer <- new.env(parent = emptyenv()) # needs the C++ library loaded create_binding_cache() + if (identical(R.version$os, "emscripten")) { + # Disable multithreading on WASM/Emscripten (no pthread support) + options(arrow.use_threads = FALSE) + } + if (tolower(Sys.info()[["sysname"]]) == "windows") { # Disable multithreading on Windows # See https://issues.apache.org/jira/browse/ARROW-8379 diff --git a/r/src/safe-call-into-r-impl.cpp b/r/src/safe-call-into-r-impl.cpp index c2fa1e1eac6b..bb3530cb0021 100644 --- a/r/src/safe-call-into-r-impl.cpp +++ b/r/src/safe-call-into-r-impl.cpp @@ -45,7 +45,16 @@ bool SetEnableSignalStopSource(bool enabled) { } // [[arrow::export]] -bool CanRunWithCapturedR() { return MainRThread::GetInstance().Executor() == nullptr; } +bool CanRunWithCapturedR() { +#ifdef __EMSCRIPTEN__ + // Threading is not supported under Emscripten/WASM. Always take the + // synchronous path to avoid attempting pthread_create which will fail + // with "thread constructor failed: Not supported". + return false; +#else + return MainRThread::GetInstance().Executor() == nullptr; +#endif +} // [[arrow::export]] std::string TestSafeCallIntoR(cpp11::function r_fun_that_returns_a_string, From 4162c550976a9578d153c7b380173e6e175a3f76 Mon Sep 17 00:00:00 2001 From: Bryce Mecum Date: Tue, 19 May 2026 14:44:01 -0700 Subject: [PATCH 02/22] test not setting usethreads --- r/R/arrow-package.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/r/R/arrow-package.R b/r/R/arrow-package.R index c5b3008a6ca9..c46b15fb226d 100644 --- a/r/R/arrow-package.R +++ b/r/R/arrow-package.R @@ -152,7 +152,7 @@ s3_finalizer <- new.env(parent = emptyenv()) if (identical(R.version$os, "emscripten")) { # Disable multithreading on WASM/Emscripten (no pthread support) - options(arrow.use_threads = FALSE) + # options(arrow.use_threads = FALSE) } if (tolower(Sys.info()[["sysname"]]) == "windows") { From 67ca35fd4df1078f1a0bdae8ad5a9e32425999d7 Mon Sep 17 00:00:00 2001 From: Bryce Mecum Date: Tue, 19 May 2026 20:52:42 -0700 Subject: [PATCH 03/22] set back --- r/R/arrow-package.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/r/R/arrow-package.R b/r/R/arrow-package.R index c46b15fb226d..721165ab114e 100644 --- a/r/R/arrow-package.R +++ b/r/R/arrow-package.R @@ -151,8 +151,8 @@ s3_finalizer <- new.env(parent = emptyenv()) create_binding_cache() if (identical(R.version$os, "emscripten")) { - # Disable multithreading on WASM/Emscripten (no pthread support) - # options(arrow.use_threads = FALSE) + # Disable multithreading on Wasm/Emscripten + options(arrow.use_threads = FALSE) } if (tolower(Sys.info()[["sysname"]]) == "windows") { From 29e79374d8e090ba7d7b6e583953b36c0a3648b7 Mon Sep 17 00:00:00 2001 From: Bryce Mecum Date: Wed, 20 May 2026 12:39:48 -0700 Subject: [PATCH 04/22] wip, run tests under wasm --- ci/scripts/r_wasm_test.cjs | 141 ++++++++++++++++++++++++++++ ci/scripts/r_wasm_test.sh | 80 ++++++++++++++++ dev/tasks/r/github.linux.r-wasm.yml | 11 ++- 3 files changed, 231 insertions(+), 1 deletion(-) create mode 100644 ci/scripts/r_wasm_test.cjs create mode 100755 ci/scripts/r_wasm_test.sh diff --git a/ci/scripts/r_wasm_test.cjs b/ci/scripts/r_wasm_test.cjs new file mode 100644 index 000000000000..3700268326a7 --- /dev/null +++ b/ci/scripts/r_wasm_test.cjs @@ -0,0 +1,141 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// Smoke-test and run the testthat suite for the arrow R package under webR. +// +// This script is called by r_wasm_test.sh after it sets up the CRAN-like +// repo and installs the webr npm package. +// +// Environment variables: +// ARROW_WASM_REPO_DIR - path to the local CRAN-like repo containing +// the arrow wasm binary package + +const { WebR } = require("webr"); +const http = require("http"); +const fs = require("fs"); +const path = require("path"); + +const repoDir = process.env.ARROW_WASM_REPO_DIR; +if (!repoDir) { + console.error("ERROR: ARROW_WASM_REPO_DIR not set"); + process.exit(1); +} + +async function main() { + // Serve the local repo over HTTP so webR (Emscripten) can access it. + // webR's R runs in an Emscripten sandbox and cannot access the host + // filesystem directly — it fetches packages over HTTP instead. + const server = http.createServer((req, res) => { + const filePath = path.join(repoDir, decodeURIComponent(req.url)); + fs.readFile(filePath, (err, data) => { + if (err) { + res.writeHead(404); + res.end(); + } else { + res.writeHead(200); + res.end(data); + } + }); + }); + server.listen(8080); + console.log("✓ Repo server on :8080"); + + const webR = new WebR({ + RArgs: ["--quiet"], + interactive: false, + }); + + await webR.init(); + console.log("✓ webR initialized"); + + // Install the arrow Wasm package, put localhost:8080 before repo.r-wasm.org + // (which is used for deps) + await webR.installPackages(["arrow"], { + repos: ["http://localhost:8080", "https://repo.r-wasm.org"], + quiet: false, + mount: false, + }); + console.log("✓ arrow installed"); + + // Install test deps. TOOD: This is flakey. We could parse the DESCRIPTION + // file to be more robust. + await webR.installPackages( + ["testthat", "tibble", "dplyr", "withr", "pillar"], + { + repos: ["https://repo.r-wasm.org"], + quiet: false, + mount: false, + }, + ); + console.log("✓ test dependencies installed"); + + // Test the package loads and functions basically + const loadResult = await webR.evalRString(` + library(arrow) + cat("arrow loaded\\n") + cat("R.version$os =", R.version$os, "\\n") + use_threads <- getOption("arrow.use_threads") + cat("arrow.use_threads =", use_threads, "\\n") + stopifnot(identical(use_threads, FALSE)) + tab <- arrow::as_arrow_table(data.frame(x = 1:10, y = letters[1:10])) + stopifnot(nrow(tab) == 10L) + cat("Created Arrow table with", nrow(tab), "rows\\n") + "PASS" + `); + + if (loadResult !== "PASS") { + console.error("Package load test FAILED"); + await webR.close(); + server.close(); + process.exit(1); + } + console.log("✓ Package loads and works correctly"); + + // Run tests + console.log("Running testthat suite under webR..."); + + const testResult = await webR.evalRString(` + library(testthat) + library(arrow) + results <- testthat::test_package("arrow", reporter = "summary", stop_on_failure = FALSE) + df <- as.data.frame(results) + n_pass <- sum(df$passed) + n_skip <- sum(df$skipped) + n_fail <- sum(df$failed) + n_error <- sum(df$error) + cat(sprintf("Results: %d passed, %d skipped, %d failed, %d errors\\n", + n_pass, n_skip, n_fail, n_error)) + if (n_fail > 0 || n_error > 0) "FAIL" else "PASS" + `); + + if (testResult !== "PASS") { + console.error("testthat suite FAILED"); + await webR.close(); + server.close(); + process.exit(1); + } + console.log("✓ testthat suite passed"); + + console.log("✓ All tests passed!"); + await webR.close(); + server.close(); +} + +main().catch((e) => { + console.error("FAILED:", e); + process.exit(1); +}); diff --git a/ci/scripts/r_wasm_test.sh b/ci/scripts/r_wasm_test.sh new file mode 100755 index 000000000000..80191785afcb --- /dev/null +++ b/ci/scripts/r_wasm_test.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# Test the arrow R package built for WebAssembly. +# +# This script is intended to run inside the ghcr.io/r-universe-org/build-wasm +# Docker container after rwasm::build() has produced a .tgz binary. It: +# 1. Sets up a CRAN-like repo structure from the built .tgz +# 2. Installs the npm webr package (Node.js webR runtime) +# 3. Boots webR, installs arrow from the local repo, and verifies: +# - The package can be installed and loaded +# - Multithreading is disabled (arrow.use_threads == FALSE) +# - The testthat test suite runs +# +# Tests that require threading are automatically skipped via +# skip_if_not(CanRunWithCapturedR()) since CanRunWithCapturedR() returns +# FALSE under Emscripten. +# +# Usage: +# r_wasm_test.sh +# +# Example: +# r_wasm_test.sh /work +# +# The arrow .tgz file(s) should already exist in . + +set -euxo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +arrow_r_dir="${1:-.}" + +# Set up a fake CRAN-like repo so we can install the package +tgz_file=$(ls "${arrow_r_dir}"/arrow_*.tgz 2>/dev/null | head -1) +if [ -z "${tgz_file}" ]; then + echo "ERROR: No arrow_*.tgz found in ${arrow_r_dir}" >&2 + exit 1 +fi +echo "Found Wasm binary: ${tgz_file}" + +repo_dir=$(mktemp -d) +# TODO: Not sure if we need this +# Cover multiple R minor versions in case the npm webr package +# uses a different R version than the Docker image's build R. +for r_ver in 4.4 4.5 4.6; do + contrib_dir="${repo_dir}/bin/emscripten/contrib/${r_ver}" + mkdir -p "${contrib_dir}" + cp "${tgz_file}" "${contrib_dir}/" + # type=mac.binary matches .tgz file extension + R -q -e "tools::write_PACKAGES('${contrib_dir}', type = 'mac.binary')" +done + +echo "Repo structure:" +find "${repo_dir}" -type f + +# Install webr in a temporary node project +work_dir=$(mktemp -d) +cd "${work_dir}" +npm init -y > /dev/null 2>&1 +npm install --silent webr 2>/dev/null + +# Run our test script +ARROW_WASM_REPO_DIR="${repo_dir}" node "${SCRIPT_DIR}/r_wasm_test.cjs" + +# Cleanup temp dirs +rm -rf "${work_dir}" "${repo_dir}" diff --git a/dev/tasks/r/github.linux.r-wasm.yml b/dev/tasks/r/github.linux.r-wasm.yml index ee38740bb47d..542be91d9e7b 100644 --- a/dev/tasks/r/github.linux.r-wasm.yml +++ b/dev/tasks/r/github.linux.r-wasm.yml @@ -21,7 +21,7 @@ jobs: r-universe-wasm: - name: "R-universe Wasm build" + name: "R-universe Wasm build and test" runs-on: ubuntu-latest timeout-minutes: 60 @@ -56,6 +56,15 @@ jobs: 2>&1 | tee build-wasm.log ' + - name: Smoke-test arrow in webR + shell: bash + run: | + docker run --rm \ + -v "${PWD}/arrow:/arrow" \ + -w /tmp \ + ghcr.io/r-universe-org/build-wasm:latest \ + bash /arrow/ci/scripts/r_wasm_test.sh /arrow/r + - name: List generated artifacts if: always() shell: bash From e821b9181cc450e36fb00a6951742a400776cde4 Mon Sep 17 00:00:00 2001 From: Bryce Mecum Date: Wed, 20 May 2026 16:28:12 -0700 Subject: [PATCH 05/22] set NODE_PATH --- ci/scripts/r_wasm_test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/scripts/r_wasm_test.sh b/ci/scripts/r_wasm_test.sh index 80191785afcb..f09324cc31ba 100755 --- a/ci/scripts/r_wasm_test.sh +++ b/ci/scripts/r_wasm_test.sh @@ -74,7 +74,7 @@ npm init -y > /dev/null 2>&1 npm install --silent webr 2>/dev/null # Run our test script -ARROW_WASM_REPO_DIR="${repo_dir}" node "${SCRIPT_DIR}/r_wasm_test.cjs" +ARROW_WASM_REPO_DIR="${repo_dir}" NODE_PATH="${work_dir}/node_modules" node "${SCRIPT_DIR}/r_wasm_test.cjs" # Cleanup temp dirs rm -rf "${work_dir}" "${repo_dir}" From c43659ef1790a4009190bcd5d61ff3445bede752 Mon Sep 17 00:00:00 2001 From: Bryce Mecum Date: Wed, 20 May 2026 20:33:17 -0700 Subject: [PATCH 06/22] try this for tests --- ci/scripts/r_wasm_test.cjs | 69 ++++++++++++++++++++++++++++++++++---- ci/scripts/r_wasm_test.sh | 2 +- 2 files changed, 63 insertions(+), 8 deletions(-) diff --git a/ci/scripts/r_wasm_test.cjs b/ci/scripts/r_wasm_test.cjs index 3700268326a7..34226e173578 100644 --- a/ci/scripts/r_wasm_test.cjs +++ b/ci/scripts/r_wasm_test.cjs @@ -21,8 +21,9 @@ // repo and installs the webr npm package. // // Environment variables: -// ARROW_WASM_REPO_DIR - path to the local CRAN-like repo containing -// the arrow wasm binary package +// ARROW_WASM_REPO_DIR - path to the local CRAN-like repo containing +// the arrow wasm binary package +// ARROW_R_TESTS_DIR - path to the arrow R package tests/testthat directory const { WebR } = require("webr"); const http = require("http"); @@ -35,6 +36,26 @@ if (!repoDir) { process.exit(1); } +const testsDir = process.env.ARROW_R_TESTS_DIR; +if (!testsDir) { + console.error("ERROR: ARROW_R_TESTS_DIR not set"); + process.exit(1); +} + +// Recursively list all files under a directory +function listFilesRecursive(dir) { + const results = []; + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const full = path.join(dir, entry.name); + if (entry.isDirectory()) { + results.push(...listFilesRecursive(full)); + } else { + results.push(full); + } + } + return results; +} + async function main() { // Serve the local repo over HTTP so webR (Emscripten) can access it. // webR's R runs in an Emscripten sandbox and cannot access the host @@ -62,6 +83,25 @@ async function main() { await webR.init(); console.log("✓ webR initialized"); + // Upload test files from the host into webR's virtual filesystem. + // rwasm builds don't include tests (no --install-tests flag), so we + // load them separately and use test_dir() instead of test_package(). + const vfsTestDir = "/tmp/arrow-tests"; + await webR.FS.mkdir(vfsTestDir); + const testFiles = listFilesRecursive(testsDir); + for (const filePath of testFiles) { + const rel = path.relative(testsDir, filePath); + const vfsPath = path.posix.join(vfsTestDir, rel.split(path.sep).join("/")); + // Ensure parent directories exist + const vfsDir = path.posix.dirname(vfsPath); + if (vfsDir !== vfsTestDir) { + await webR.evalRVoid(`dir.create("${vfsDir}", recursive = TRUE, showWarnings = FALSE)`); + } + const contents = fs.readFileSync(filePath); + await webR.FS.writeFile(vfsPath, contents); + } + console.log(`✓ Uploaded ${testFiles.length} test files to webR VFS`); + // Install the arrow Wasm package, put localhost:8080 before repo.r-wasm.org // (which is used for deps) await webR.installPackages(["arrow"], { @@ -71,10 +111,25 @@ async function main() { }); console.log("✓ arrow installed"); - // Install test deps. TOOD: This is flakey. We could parse the DESCRIPTION - // file to be more robust. + // Install test deps from DESCRIPTION Suggests + packages used in helpers. + // All of these are available on repo.r-wasm.org for wasm. await webR.installPackages( - ["testthat", "tibble", "dplyr", "withr", "pillar"], + [ + "testthat", + "tibble", + "dplyr", + "withr", + "pillar", + "lubridate", + "purrr", + "stringr", + "stringi", + "bit64", + "hms", + "rlang", + "vctrs", + "tzdb", + ], { repos: ["https://repo.r-wasm.org"], quiet: false, @@ -105,13 +160,13 @@ async function main() { } console.log("✓ Package loads and works correctly"); - // Run tests + // Run tests using test_dir() since the wasm binary doesn't include tests console.log("Running testthat suite under webR..."); const testResult = await webR.evalRString(` library(testthat) library(arrow) - results <- testthat::test_package("arrow", reporter = "summary", stop_on_failure = FALSE) + results <- testthat::test_dir("${vfsTestDir}", reporter = "summary", stop_on_failure = FALSE, package = "arrow") df <- as.data.frame(results) n_pass <- sum(df$passed) n_skip <- sum(df$skipped) diff --git a/ci/scripts/r_wasm_test.sh b/ci/scripts/r_wasm_test.sh index f09324cc31ba..d50a15d4e52b 100755 --- a/ci/scripts/r_wasm_test.sh +++ b/ci/scripts/r_wasm_test.sh @@ -74,7 +74,7 @@ npm init -y > /dev/null 2>&1 npm install --silent webr 2>/dev/null # Run our test script -ARROW_WASM_REPO_DIR="${repo_dir}" NODE_PATH="${work_dir}/node_modules" node "${SCRIPT_DIR}/r_wasm_test.cjs" +ARROW_WASM_REPO_DIR="${repo_dir}" ARROW_R_TESTS_DIR="${arrow_r_dir}/tests/testthat" NODE_PATH="${work_dir}/node_modules" node "${SCRIPT_DIR}/r_wasm_test.cjs" # Cleanup temp dirs rm -rf "${work_dir}" "${repo_dir}" From a5787ad5ac33c8a7f53f65a5b194783afd5a956a Mon Sep 17 00:00:00 2001 From: Bryce Mecum Date: Wed, 20 May 2026 20:38:44 -0700 Subject: [PATCH 07/22] tidy up a bit --- ci/scripts/r_wasm_test.cjs | 134 ++++++++++++++----------------------- 1 file changed, 49 insertions(+), 85 deletions(-) diff --git a/ci/scripts/r_wasm_test.cjs b/ci/scripts/r_wasm_test.cjs index 34226e173578..054119039861 100644 --- a/ci/scripts/r_wasm_test.cjs +++ b/ci/scripts/r_wasm_test.cjs @@ -15,15 +15,10 @@ // specific language governing permissions and limitations // under the License. -// Smoke-test and run the testthat suite for the arrow R package under webR. -// -// This script is called by r_wasm_test.sh after it sets up the CRAN-like -// repo and installs the webr npm package. -// -// Environment variables: -// ARROW_WASM_REPO_DIR - path to the local CRAN-like repo containing -// the arrow wasm binary package -// ARROW_R_TESTS_DIR - path to the arrow R package tests/testthat directory +// Smoke-test the arrow R package under webR, then run the testthat suite. +// Called by r_wasm_test.sh. Requires env vars: +// ARROW_WASM_REPO_DIR - local CRAN-like repo with the arrow .tgz +// ARROW_R_TESTS_DIR - path to tests/testthat in the source tree const { WebR } = require("webr"); const http = require("http"); @@ -42,7 +37,6 @@ if (!testsDir) { process.exit(1); } -// Recursively list all files under a directory function listFilesRecursive(dir) { const results = []; for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { @@ -57,9 +51,7 @@ function listFilesRecursive(dir) { } async function main() { - // Serve the local repo over HTTP so webR (Emscripten) can access it. - // webR's R runs in an Emscripten sandbox and cannot access the host - // filesystem directly — it fetches packages over HTTP instead. + // Serve the repo over HTTP (webR can't access the host filesystem directly) const server = http.createServer((req, res) => { const filePath = path.join(repoDir, decodeURIComponent(req.url)); fs.readFile(filePath, (err, data) => { @@ -75,35 +67,28 @@ async function main() { server.listen(8080); console.log("✓ Repo server on :8080"); - const webR = new WebR({ - RArgs: ["--quiet"], - interactive: false, - }); - + const webR = new WebR({ RArgs: ["--quiet"], interactive: false }); await webR.init(); console.log("✓ webR initialized"); - // Upload test files from the host into webR's virtual filesystem. - // rwasm builds don't include tests (no --install-tests flag), so we - // load them separately and use test_dir() instead of test_package(). + // Upload test files to webR VFS (rwasm doesn't include tests in binaries) const vfsTestDir = "/tmp/arrow-tests"; await webR.FS.mkdir(vfsTestDir); const testFiles = listFilesRecursive(testsDir); - for (const filePath of testFiles) { - const rel = path.relative(testsDir, filePath); + const createdDirs = new Set([vfsTestDir]); + for (const file of testFiles) { + const rel = path.relative(testsDir, file); const vfsPath = path.posix.join(vfsTestDir, rel.split(path.sep).join("/")); - // Ensure parent directories exist const vfsDir = path.posix.dirname(vfsPath); - if (vfsDir !== vfsTestDir) { - await webR.evalRVoid(`dir.create("${vfsDir}", recursive = TRUE, showWarnings = FALSE)`); + if (!createdDirs.has(vfsDir)) { + await webR.evalRVoid(`dir.create("${vfsDir}", recursive=TRUE, showWarnings=FALSE)`); + createdDirs.add(vfsDir); } - const contents = fs.readFileSync(filePath); - await webR.FS.writeFile(vfsPath, contents); + await webR.FS.writeFile(vfsPath, fs.readFileSync(file)); } - console.log(`✓ Uploaded ${testFiles.length} test files to webR VFS`); + console.log(`✓ Uploaded ${testFiles.length} test files to VFS`); - // Install the arrow Wasm package, put localhost:8080 before repo.r-wasm.org - // (which is used for deps) + // Install arrow from local repo, deps from r-wasm.org await webR.installPackages(["arrow"], { repos: ["http://localhost:8080", "https://repo.r-wasm.org"], quiet: false, @@ -111,81 +96,60 @@ async function main() { }); console.log("✓ arrow installed"); - // Install test deps from DESCRIPTION Suggests + packages used in helpers. - // All of these are available on repo.r-wasm.org for wasm. - await webR.installPackages( - [ - "testthat", - "tibble", - "dplyr", - "withr", - "pillar", - "lubridate", - "purrr", - "stringr", - "stringi", - "bit64", - "hms", - "rlang", - "vctrs", - "tzdb", - ], - { - repos: ["https://repo.r-wasm.org"], - quiet: false, - mount: false, - }, - ); + // Install test deps parsed from DESCRIPTION + const depsList = await webR.evalRString(` + desc <- read.dcf(system.file("DESCRIPTION", package = "arrow"), + fields = c("Imports", "Suggests")) + pkgs <- unlist(strsplit(paste(na.omit(desc[1,]), collapse = ","), ",\\\\s*")) + pkgs <- trimws(sub("\\\\s*\\\\(.*\\\\)", "", pkgs)) + pkgs <- pkgs[pkgs != "" & pkgs != "R"] + pkgs <- pkgs[!pkgs %in% loadedNamespaces()] + paste(pkgs, collapse = "\\n") + `); + const testDeps = depsList.split("\n").filter(Boolean); + console.log(`Installing ${testDeps.length} dependencies from DESCRIPTION...`); + await webR.installPackages(testDeps, { + repos: ["https://repo.r-wasm.org"], + quiet: false, + mount: false, + }); console.log("✓ test dependencies installed"); - // Test the package loads and functions basically + // Smoke test: package loads, threading disabled, basic operations work const loadResult = await webR.evalRString(` library(arrow) - cat("arrow loaded\\n") cat("R.version$os =", R.version$os, "\\n") - use_threads <- getOption("arrow.use_threads") - cat("arrow.use_threads =", use_threads, "\\n") - stopifnot(identical(use_threads, FALSE)) + stopifnot(identical(getOption("arrow.use_threads"), FALSE)) tab <- arrow::as_arrow_table(data.frame(x = 1:10, y = letters[1:10])) stopifnot(nrow(tab) == 10L) - cat("Created Arrow table with", nrow(tab), "rows\\n") + cat("Created table with", nrow(tab), "rows\\n") "PASS" `); - if (loadResult !== "PASS") { - console.error("Package load test FAILED"); - await webR.close(); - server.close(); - process.exit(1); + throw new Error("Smoke test failed"); } - console.log("✓ Package loads and works correctly"); - - // Run tests using test_dir() since the wasm binary doesn't include tests - console.log("Running testthat suite under webR..."); + console.log("✓ Smoke test passed"); + // Run testthat suite + console.log("Running testthat suite..."); const testResult = await webR.evalRString(` library(testthat) - library(arrow) - results <- testthat::test_dir("${vfsTestDir}", reporter = "summary", stop_on_failure = FALSE, package = "arrow") + results <- testthat::test_dir( + "${vfsTestDir}", + reporter = "summary", + stop_on_failure = FALSE, + package = "arrow" + ) df <- as.data.frame(results) - n_pass <- sum(df$passed) - n_skip <- sum(df$skipped) - n_fail <- sum(df$failed) - n_error <- sum(df$error) cat(sprintf("Results: %d passed, %d skipped, %d failed, %d errors\\n", - n_pass, n_skip, n_fail, n_error)) - if (n_fail > 0 || n_error > 0) "FAIL" else "PASS" + sum(df$passed), sum(df$skipped), sum(df$failed), sum(df$error))) + if (sum(df$failed) > 0 || sum(df$error) > 0) "FAIL" else "PASS" `); - if (testResult !== "PASS") { - console.error("testthat suite FAILED"); - await webR.close(); - server.close(); - process.exit(1); + throw new Error("testthat suite failed"); } console.log("✓ testthat suite passed"); - console.log("✓ All tests passed!"); await webR.close(); server.close(); } From 2ef885c1e6ab6cb408bfc65f960e81f8902742bb Mon Sep 17 00:00:00 2001 From: Bryce Mecum Date: Wed, 20 May 2026 21:01:06 -0700 Subject: [PATCH 08/22] configure tzdb --- r/R/arrow-package.R | 2 ++ 1 file changed, 2 insertions(+) diff --git a/r/R/arrow-package.R b/r/R/arrow-package.R index 721165ab114e..8b2c57c9184d 100644 --- a/r/R/arrow-package.R +++ b/r/R/arrow-package.R @@ -153,6 +153,8 @@ s3_finalizer <- new.env(parent = emptyenv()) if (identical(R.version$os, "emscripten")) { # Disable multithreading on Wasm/Emscripten options(arrow.use_threads = FALSE) + # No system tzdata on Emscripten; use the tzdb R package + configure_tzdb() } if (tolower(Sys.info()[["sysname"]]) == "windows") { From b0d79f1dfc99fd1f3addd2412c529614ddebef5a Mon Sep 17 00:00:00 2001 From: Bryce Mecum Date: Wed, 20 May 2026 21:05:28 -0700 Subject: [PATCH 09/22] add skip --- r/tests/testthat/helper-skip.R | 4 ++++ r/tests/testthat/test-dplyr-filter.R | 2 ++ 2 files changed, 6 insertions(+) diff --git a/r/tests/testthat/helper-skip.R b/r/tests/testthat/helper-skip.R index 133b03798813..283d5578d7dd 100644 --- a/r/tests/testthat/helper-skip.R +++ b/r/tests/testthat/helper-skip.R @@ -101,6 +101,10 @@ skip_on_linux_devel <- function() { } } +skip_on_emscripten <- function() { + skip_if(identical(R.version$os, "emscripten"), "Not supported on Emscripten") +} + skip_on_r_older_than <- function(r_version) { if (force_tests()) { return() diff --git a/r/tests/testthat/test-dplyr-filter.R b/r/tests/testthat/test-dplyr-filter.R index ad69b26be798..bc912c343c0b 100644 --- a/r/tests/testthat/test-dplyr-filter.R +++ b/r/tests/testthat/test-dplyr-filter.R @@ -415,6 +415,8 @@ test_that("filter() with namespaced functions", { }) test_that("filter() with across()", { + skip_on_emscripten() # TODO(xxx): need to figure out what warnings this throws + compare_dplyr_binding( .input |> filter(if_any(ends_with("l"), ~ is.na(.))) |> From b9c6df0bd4821a83126d8681d184fd83d32dade2 Mon Sep 17 00:00:00 2001 From: Bryce Mecum Date: Thu, 21 May 2026 08:00:35 -0700 Subject: [PATCH 10/22] add debug info for ci --- r/R/arrow-package.R | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/r/R/arrow-package.R b/r/R/arrow-package.R index 8b2c57c9184d..20514f704566 100644 --- a/r/R/arrow-package.R +++ b/r/R/arrow-package.R @@ -190,19 +190,33 @@ configure_tzdb <- function() { tryCatch( { tzdb::tzdb_initialize() - set_timezone_database(tzdb::tzdb_path("text")) + tz_path <- tzdb::tzdb_path("text") + packageStartupMessage("[configure_tzdb] tzdb path: ", tz_path) + packageStartupMessage("[configure_tzdb] path exists: ", dir.exists(tz_path)) + if (dir.exists(tz_path)) { + tz_files <- list.files(tz_path, recursive = TRUE) + packageStartupMessage( + "[configure_tzdb] tzdb contents (", length(tz_files), " files): ", + paste(head(tz_files, 10), collapse = ", "), + if (length(tz_files) > 10) "..." + ) + } + set_timezone_database(tz_path) + packageStartupMessage("[configure_tzdb] successfully configured timezone database") }, error = function(e) { packageStartupMessage( - "The tzdb package was available but failed to initialize: ", - e, + "[configure_tzdb] tzdb package available but failed to initialize: ", + conditionMessage(e) + ) + packageStartupMessage( "Timezones will not be available to Arrow compute functions." ) } ) } else { packageStartupMessage( - "The tzdb package is not installed. ", + "[configure_tzdb] tzdb package is NOT installed. ", "Timezones will not be available to Arrow compute functions. ", "If you get errors when using Arrow on datetimes, try running ", "`install.packages('tzdb')` and trying again." From 88ab8418c84c5355a47662dd6de5018c4447e0ce Mon Sep 17 00:00:00 2001 From: Bryce Mecum Date: Wed, 1 Jul 2026 17:41:35 -0700 Subject: [PATCH 11/22] add more skips --- r/tests/testthat/test-dataset-dplyr.R | 2 ++ r/tests/testthat/test-dplyr-arrange.R | 1 + r/tests/testthat/test-dplyr-query.R | 1 + 3 files changed, 4 insertions(+) diff --git a/r/tests/testthat/test-dataset-dplyr.R b/r/tests/testthat/test-dataset-dplyr.R index 09ca0ef155b7..ff6fe435747a 100644 --- a/r/tests/testthat/test-dataset-dplyr.R +++ b/r/tests/testthat/test-dataset-dplyr.R @@ -70,6 +70,7 @@ test_that("filter() with %in%", { }) test_that("filter() on timestamp columns", { + skip_on_emscripten() skip_if_not_available("re2") ds <- open_dataset(dataset_dir, partitioning = schema(part = uint8())) @@ -119,6 +120,7 @@ test_that("filter() on date32 columns", { ) skip_if_not_available("re2") + skip_on_emscripten() # Also with timestamp scalar expect_equal( diff --git a/r/tests/testthat/test-dplyr-arrange.R b/r/tests/testthat/test-dplyr-arrange.R index d502a7f040a1..3d0dd5066654 100644 --- a/r/tests/testthat/test-dplyr-arrange.R +++ b/r/tests/testthat/test-dplyr-arrange.R @@ -238,6 +238,7 @@ test_that("Can use across() within arrange()", { collect(), example_data ) + skip_on_emscripten() compare_dplyr_binding( .input |> arrange(across(starts_with("d"), desc)) |> diff --git a/r/tests/testthat/test-dplyr-query.R b/r/tests/testthat/test-dplyr-query.R index 53864a166337..90a502e5428e 100644 --- a/r/tests/testthat/test-dplyr-query.R +++ b/r/tests/testthat/test-dplyr-query.R @@ -646,6 +646,7 @@ test_that("Scalars in expressions match the type of the field, if possible", { # it merges. # https://github.com/r-hub/rhub-linux-builders/pull/65 skip_if(identical(Sys.timezone(), "/UTC")) + skip_on_emscripten() expect_output( tab |> From d29b200692c7e2e5cb38df6abfbd12ed4b620b81 Mon Sep 17 00:00:00 2001 From: Bryce Mecum Date: Wed, 1 Jul 2026 17:42:40 -0700 Subject: [PATCH 12/22] Format arrow-package.R with Air --- r/R/arrow-package.R | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/r/R/arrow-package.R b/r/R/arrow-package.R index 20514f704566..d8e443d10046 100644 --- a/r/R/arrow-package.R +++ b/r/R/arrow-package.R @@ -196,7 +196,9 @@ configure_tzdb <- function() { if (dir.exists(tz_path)) { tz_files <- list.files(tz_path, recursive = TRUE) packageStartupMessage( - "[configure_tzdb] tzdb contents (", length(tz_files), " files): ", + "[configure_tzdb] tzdb contents (", + length(tz_files), + " files): ", paste(head(tz_files, 10), collapse = ", "), if (length(tz_files) > 10) "..." ) From e53e9475772e5f1567900811bb0df5cd274858cd Mon Sep 17 00:00:00 2001 From: Bryce Mecum Date: Wed, 1 Jul 2026 19:07:04 -0700 Subject: [PATCH 13/22] Fix gmake invocation failure --- r/inst/build_arrow_static.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/r/inst/build_arrow_static.sh b/r/inst/build_arrow_static.sh index 349531b75fd9..0df5240888ab 100755 --- a/r/inst/build_arrow_static.sh +++ b/r/inst/build_arrow_static.sh @@ -114,7 +114,7 @@ ${CMAKE_WRAPPER} ${CMAKE} -DARROW_BOOST_USE_SHARED=OFF \ -G "${CMAKE_GENERATOR:-Unix Makefiles}" \ ${SOURCE_DIR} -${CMAKE} --build . --target install -- -j $N_JOBS +${CMAKE} --build . --target install --parallel $N_JOBS if command -v sccache &> /dev/null; then echo "=== sccache stats after the build ===" From 89d08692ac45cb1de626a96e9b90429755ee0389 Mon Sep 17 00:00:00 2001 From: Bryce Mecum Date: Wed, 1 Jul 2026 19:29:17 -0700 Subject: [PATCH 14/22] Update build_arrow_static.sh --- r/inst/build_arrow_static.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/r/inst/build_arrow_static.sh b/r/inst/build_arrow_static.sh index 0df5240888ab..26b11453eda2 100755 --- a/r/inst/build_arrow_static.sh +++ b/r/inst/build_arrow_static.sh @@ -38,9 +38,9 @@ DEST_DIR="$(mkdir -p "${DEST_DIR}" && cd "${DEST_DIR}" && pwd)" if [ "$N_JOBS" = "" ]; then if [ "`uname -s`" = "Darwin" ]; then - N_JOBS="$(sysctl -n hw.logicalcpu)" + N_JOBS="$(sysctl -n hw.logicalcpu 2>/dev/null || echo 2)" else - N_JOBS="$(nproc)" + N_JOBS="$(nproc 2>/dev/null || echo 2)" fi fi @@ -114,7 +114,7 @@ ${CMAKE_WRAPPER} ${CMAKE} -DARROW_BOOST_USE_SHARED=OFF \ -G "${CMAKE_GENERATOR:-Unix Makefiles}" \ ${SOURCE_DIR} -${CMAKE} --build . --target install --parallel $N_JOBS +MAKEFLAGS="-j$N_JOBS" ${CMAKE} --build . --target install if command -v sccache &> /dev/null; then echo "=== sccache stats after the build ===" From 0eb079b459bb52bc5aa8fad3fb56daf69245c97e Mon Sep 17 00:00:00 2001 From: Bryce Mecum Date: Wed, 1 Jul 2026 19:32:43 -0700 Subject: [PATCH 15/22] Make r_wasm_test.cjs slightly safer --- ci/scripts/r_wasm_test.cjs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ci/scripts/r_wasm_test.cjs b/ci/scripts/r_wasm_test.cjs index 054119039861..ccc6cb393bad 100644 --- a/ci/scripts/r_wasm_test.cjs +++ b/ci/scripts/r_wasm_test.cjs @@ -54,6 +54,11 @@ async function main() { // Serve the repo over HTTP (webR can't access the host filesystem directly) const server = http.createServer((req, res) => { const filePath = path.join(repoDir, decodeURIComponent(req.url)); + if (!filePath.startsWith(path.resolve(repoDir))) { + res.writeHead(403); + res.end(); + return; + } fs.readFile(filePath, (err, data) => { if (err) { res.writeHead(404); From 03e9002c8655d2c3b646042613b66da84f3d4fc2 Mon Sep 17 00:00:00 2001 From: Bryce Mecum Date: Wed, 1 Jul 2026 19:34:48 -0700 Subject: [PATCH 16/22] respect force_tests in skip_on_emscripten --- r/tests/testthat/helper-skip.R | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/r/tests/testthat/helper-skip.R b/r/tests/testthat/helper-skip.R index 283d5578d7dd..ed2bfae0dbb7 100644 --- a/r/tests/testthat/helper-skip.R +++ b/r/tests/testthat/helper-skip.R @@ -102,6 +102,10 @@ skip_on_linux_devel <- function() { } skip_on_emscripten <- function() { + if (force_tests()) { + return() + } + skip_if(identical(R.version$os, "emscripten"), "Not supported on Emscripten") } From f309ad9869a95dde72856e9a6d738340b9ecab48 Mon Sep 17 00:00:00 2001 From: Bryce Mecum Date: Wed, 1 Jul 2026 19:36:35 -0700 Subject: [PATCH 17/22] Remove TODO --- r/tests/testthat/test-dplyr-filter.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/r/tests/testthat/test-dplyr-filter.R b/r/tests/testthat/test-dplyr-filter.R index bc912c343c0b..7b0ca374bd03 100644 --- a/r/tests/testthat/test-dplyr-filter.R +++ b/r/tests/testthat/test-dplyr-filter.R @@ -415,7 +415,7 @@ test_that("filter() with namespaced functions", { }) test_that("filter() with across()", { - skip_on_emscripten() # TODO(xxx): need to figure out what warnings this throws + skip_on_emscripten() compare_dplyr_binding( .input |> From 3866da32e850a3a6e004628d8b6b821b6018e156 Mon Sep 17 00:00:00 2001 From: Bryce Mecum Date: Wed, 1 Jul 2026 19:43:16 -0700 Subject: [PATCH 18/22] revert our changes to build_arrow_static.sh --- r/inst/build_arrow_static.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/r/inst/build_arrow_static.sh b/r/inst/build_arrow_static.sh index 26b11453eda2..349531b75fd9 100755 --- a/r/inst/build_arrow_static.sh +++ b/r/inst/build_arrow_static.sh @@ -38,9 +38,9 @@ DEST_DIR="$(mkdir -p "${DEST_DIR}" && cd "${DEST_DIR}" && pwd)" if [ "$N_JOBS" = "" ]; then if [ "`uname -s`" = "Darwin" ]; then - N_JOBS="$(sysctl -n hw.logicalcpu 2>/dev/null || echo 2)" + N_JOBS="$(sysctl -n hw.logicalcpu)" else - N_JOBS="$(nproc 2>/dev/null || echo 2)" + N_JOBS="$(nproc)" fi fi @@ -114,7 +114,7 @@ ${CMAKE_WRAPPER} ${CMAKE} -DARROW_BOOST_USE_SHARED=OFF \ -G "${CMAKE_GENERATOR:-Unix Makefiles}" \ ${SOURCE_DIR} -MAKEFLAGS="-j$N_JOBS" ${CMAKE} --build . --target install +${CMAKE} --build . --target install -- -j $N_JOBS if command -v sccache &> /dev/null; then echo "=== sccache stats after the build ===" From 172c0d4a27bb68a67de4b7ebd8eb378194ce7b1a Mon Sep 17 00:00:00 2001 From: Bryce Mecum Date: Wed, 1 Jul 2026 19:51:50 -0700 Subject: [PATCH 19/22] revert debug messages in arrow-package.R --- r/R/arrow-package.R | 25 ++++--------------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/r/R/arrow-package.R b/r/R/arrow-package.R index d8e443d10046..fcb8cc284036 100644 --- a/r/R/arrow-package.R +++ b/r/R/arrow-package.R @@ -190,35 +190,18 @@ configure_tzdb <- function() { tryCatch( { tzdb::tzdb_initialize() - tz_path <- tzdb::tzdb_path("text") - packageStartupMessage("[configure_tzdb] tzdb path: ", tz_path) - packageStartupMessage("[configure_tzdb] path exists: ", dir.exists(tz_path)) - if (dir.exists(tz_path)) { - tz_files <- list.files(tz_path, recursive = TRUE) - packageStartupMessage( - "[configure_tzdb] tzdb contents (", - length(tz_files), - " files): ", - paste(head(tz_files, 10), collapse = ", "), - if (length(tz_files) > 10) "..." - ) - } - set_timezone_database(tz_path) - packageStartupMessage("[configure_tzdb] successfully configured timezone database") + set_timezone_database(tzdb::tzdb_path("text")) }, error = function(e) { packageStartupMessage( - "[configure_tzdb] tzdb package available but failed to initialize: ", - conditionMessage(e) - ) - packageStartupMessage( - "Timezones will not be available to Arrow compute functions." + "The tzdb package was available but failed to initialize: ", + e, ) } ) } else { packageStartupMessage( - "[configure_tzdb] tzdb package is NOT installed. ", + "The tzdb package is not installed. ", "Timezones will not be available to Arrow compute functions. ", "If you get errors when using Arrow on datetimes, try running ", "`install.packages('tzdb')` and trying again." From 4bf56698176f77181f98661d225d62108b053b21 Mon Sep 17 00:00:00 2001 From: Bryce Mecum Date: Wed, 1 Jul 2026 20:01:04 -0700 Subject: [PATCH 20/22] try to fix tz issue under wasm --- cpp/src/arrow/vendored/datetime/visibility.h | 2 +- r/tests/testthat/test-dataset-dplyr.R | 4 ++-- r/tests/testthat/test-dplyr-arrange.R | 2 +- r/tests/testthat/test-dplyr-filter.R | 8 ++++++-- r/tests/testthat/test-dplyr-query.R | 2 +- 5 files changed, 11 insertions(+), 7 deletions(-) diff --git a/cpp/src/arrow/vendored/datetime/visibility.h b/cpp/src/arrow/vendored/datetime/visibility.h index 780c00d70bd9..1942d87ffc24 100644 --- a/cpp/src/arrow/vendored/datetime/visibility.h +++ b/cpp/src/arrow/vendored/datetime/visibility.h @@ -17,7 +17,7 @@ #pragma once -#ifndef _WIN32 +#if !defined(_WIN32) && !defined(__EMSCRIPTEN__) # define USE_OS_TZDB 1 #endif diff --git a/r/tests/testthat/test-dataset-dplyr.R b/r/tests/testthat/test-dataset-dplyr.R index ff6fe435747a..a755ecebbd66 100644 --- a/r/tests/testthat/test-dataset-dplyr.R +++ b/r/tests/testthat/test-dataset-dplyr.R @@ -70,7 +70,7 @@ test_that("filter() with %in%", { }) test_that("filter() on timestamp columns", { - skip_on_emscripten() + # skip_on_emscripten() # TODO: remove if timezone fix (visibility.h) works skip_if_not_available("re2") ds <- open_dataset(dataset_dir, partitioning = schema(part = uint8())) @@ -120,7 +120,7 @@ test_that("filter() on date32 columns", { ) skip_if_not_available("re2") - skip_on_emscripten() + # skip_on_emscripten() # TODO: remove if timezone fix (visibility.h) works # Also with timestamp scalar expect_equal( diff --git a/r/tests/testthat/test-dplyr-arrange.R b/r/tests/testthat/test-dplyr-arrange.R index 3d0dd5066654..2412bbd2e1e8 100644 --- a/r/tests/testthat/test-dplyr-arrange.R +++ b/r/tests/testthat/test-dplyr-arrange.R @@ -238,7 +238,7 @@ test_that("Can use across() within arrange()", { collect(), example_data ) - skip_on_emscripten() + # skip_on_emscripten() # TODO: Unrelated to timezone fix; passing a bare function (desc) to across() .fns fails on Emscripten compare_dplyr_binding( .input |> arrange(across(starts_with("d"), desc)) |> diff --git a/r/tests/testthat/test-dplyr-filter.R b/r/tests/testthat/test-dplyr-filter.R index 7b0ca374bd03..fe7c84c80a8d 100644 --- a/r/tests/testthat/test-dplyr-filter.R +++ b/r/tests/testthat/test-dplyr-filter.R @@ -25,7 +25,11 @@ tbl <- example_data tbl$verses <- verses[[1]] # c(" a ", " b ", " c ", ...) increasing padding # nchar = 3 5 7 9 11 13 15 17 19 21 -tbl$padded_strings <- stringr::str_pad(letters[1:10], width = 2 * (1:10) + 1, side = "both") +tbl$padded_strings <- stringr::str_pad( + letters[1:10], + width = 2 * (1:10) + 1, + side = "both" +) tbl$some_negative <- tbl$int * (-1)^(1:nrow(tbl)) # nolint test_that("filter() on is.na()", { @@ -415,7 +419,7 @@ test_that("filter() with namespaced functions", { }) test_that("filter() with across()", { - skip_on_emscripten() + # skip_on_emscripten() # TODO: Remove if timezone fix works compare_dplyr_binding( .input |> diff --git a/r/tests/testthat/test-dplyr-query.R b/r/tests/testthat/test-dplyr-query.R index 90a502e5428e..257e9dabe317 100644 --- a/r/tests/testthat/test-dplyr-query.R +++ b/r/tests/testthat/test-dplyr-query.R @@ -646,7 +646,7 @@ test_that("Scalars in expressions match the type of the field, if possible", { # it merges. # https://github.com/r-hub/rhub-linux-builders/pull/65 skip_if(identical(Sys.timezone(), "/UTC")) - skip_on_emscripten() + # skip_on_emscripten() # TODO: remove if timezone fix (visibility.h) works expect_output( tab |> From 093a443c2cd29697f30198b1c6b45ed48625df12 Mon Sep 17 00:00:00 2001 From: Bryce Mecum Date: Wed, 1 Jul 2026 20:36:06 -0700 Subject: [PATCH 21/22] Fix cmake issue from nixlibs --- r/tools/nixlibs.R | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/r/tools/nixlibs.R b/r/tools/nixlibs.R index ba705e03ad7e..2a8a7821f1c7 100644 --- a/r/tools/nixlibs.R +++ b/r/tools/nixlibs.R @@ -527,6 +527,10 @@ build_libarrow <- function(src_dir, dst_dir) { # CRAN policy says not to use more than 2 cores during checks # If you have more and want to use more, set MAKEFLAGS or NOT_CRAN ncores <- parallel::detectCores() + # detectCores() returns NA in some environments under emscripten, handle it + if (is.na(ncores)) { + ncores <- 1L + } if (!not_cran) { ncores <- min(ncores, 2) } From 9cc126437bdee30b0bba036ce7d31bb09e449801 Mon Sep 17 00:00:00 2001 From: Bryce Mecum Date: Wed, 1 Jul 2026 23:38:13 -0700 Subject: [PATCH 22/22] Syntax fix --- build-wasm.log | 5 +++ cpp/src/arrow/vendored/datetime/visibility.h | 8 ++++- r/R/arrow-package.R | 2 +- r/run-wasm-build-and-test.sh | 33 ++++++++++++++++++++ 4 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 build-wasm.log create mode 100644 r/run-wasm-build-and-test.sh diff --git a/build-wasm.log b/build-wasm.log new file mode 100644 index 000000000000..a409358a6be3 --- /dev/null +++ b/build-wasm.log @@ -0,0 +1,5 @@ +> if (!requireNamespace("pak", quietly = TRUE)) install.packages("pak", repos = "https://cloud.r-project.org"); if (!requireNamespace("rwasm", quietly = TRUE)) pak::pak("r-wasm/rwasm"); rwasm::build(".") +Error in if (startsWith(url, "file:")) "internal" else "libcurl" : + missing value where TRUE/FALSE needed +Calls: -> make_remote_tarball -> download.file +Execution halted diff --git a/cpp/src/arrow/vendored/datetime/visibility.h b/cpp/src/arrow/vendored/datetime/visibility.h index 1942d87ffc24..492d189a0397 100644 --- a/cpp/src/arrow/vendored/datetime/visibility.h +++ b/cpp/src/arrow/vendored/datetime/visibility.h @@ -17,10 +17,16 @@ #pragma once -#if !defined(_WIN32) && !defined(__EMSCRIPTEN__) +#ifndef _WIN32 # define USE_OS_TZDB 1 #endif +#ifdef __EMSCRIPTEN__ +// Emscripten has no OS timezone database and no curl, so disable both. +# undef USE_OS_TZDB +# define HAS_REMOTE_API 0 +#endif + #if defined(ARROW_STATIC) // intentially empty #elif defined(ARROW_EXPORTING) diff --git a/r/R/arrow-package.R b/r/R/arrow-package.R index fcb8cc284036..179bc1945c30 100644 --- a/r/R/arrow-package.R +++ b/r/R/arrow-package.R @@ -195,7 +195,7 @@ configure_tzdb <- function() { error = function(e) { packageStartupMessage( "The tzdb package was available but failed to initialize: ", - e, + e ) } ) diff --git a/r/run-wasm-build-and-test.sh b/r/run-wasm-build-and-test.sh new file mode 100644 index 000000000000..d89530b310c8 --- /dev/null +++ b/r/run-wasm-build-and-test.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# Build and test the Arrow R package for WebAssembly locally, +# replicating the test-r-wasm crossbow job exactly. +# +# Usage: bash r/run-wasm-build-and-test.sh +# Run from the repo root. + +set -euxo pipefail + +cd "$(git rev-parse --show-toplevel)/r" + +# Step 1: sync C++ source and build R source tarball (done on host, not in Docker) +make sync-cpp +R CMD build --no-build-vignettes . + +# Step 2: build Wasm binary inside the R-universe container +docker run --rm \ + -v "${PWD}:/work" \ + -w /work \ + ghcr.io/r-universe-org/build-wasm:latest \ + bash -lc ' + set -euxo pipefail + R -q -e "if (!requireNamespace(\"pak\", quietly = TRUE)) install.packages(\"pak\", repos = \"https://cloud.r-project.org\"); if (!requireNamespace(\"rwasm\", quietly = TRUE)) pak::pak(\"r-wasm/rwasm\"); rwasm::build(\".\")" \ + 2>&1 | tee build-wasm.log + ' + +# Step 3: run the test suite inside the R-universe container +ARROW_ROOT="$(git rev-parse --show-toplevel)" +docker run --rm \ + -v "${ARROW_ROOT}:/arrow" \ + -w /tmp \ + ghcr.io/r-universe-org/build-wasm:latest \ + bash /arrow/ci/scripts/r_wasm_test.sh /arrow/r