Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions .github/workflows/armv7-termux.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: ARMv7 Termux binary

on:
workflow_dispatch:
pull_request:
paths:
- '.github/workflows/armv7-termux.yml'
- 'build-armv7-termux'
- 'build/termux-armv7/**'
- 'code/**'
push:
branches:
- trunk
paths:
- '.github/workflows/armv7-termux.yml'
- 'build-armv7-termux'
- 'build/termux-armv7/**'
- 'code/**'

permissions:
contents: read

concurrency:
group: armv7-termux-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
build:
runs-on: ubuntu-24.04-arm
timeout-minutes: 360

steps:
- name: Check out the exact source
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}

- name: Build and execute the ARMv7 Termux package
env:
IR_TERMUX_ARTIFACT_DIR: ${{ runner.temp }}/ir-armv7-artifacts
IR_TERMUX_FREE_DISK: 1
IR_TERMUX_KEEP_WORK: 1
run: ./build-armv7-termux

- name: Show the acceptance record
run: cat "${{ runner.temp }}/ir-armv7-artifacts/build-record.txt"

- name: Upload the installable package and provenance
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
with:
name: ir-termux-armv7-${{ github.event.pull_request.head.sha || github.sha }}
path: ${{ runner.temp }}/ir-armv7-artifacts/
if-no-files-found: error
compression-level: 0
retention-days: 30
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,18 @@ Code that used a single `=` as assignment must use an arrow in this R. The comma
## You do not have to give up ordinary R

The tested Linux instructions install this build in its own folder and use a separate package library. They do not remove ordinary R, alter your projects, rewrite your scripts, or upload your work. Close this R and launch ordinary R as before whenever you want to switch back.


## ARMv7 Android / Termux

The `ARMv7 Termux binary` workflow builds an installable 32-bit `arm` package.
Download its `ir_*.deb` artifact and install it in Termux:

```sh
apt install ./ir_*.deb
ir
```

The package installs `ir` and `irscript` with a private runtime. It does not
replace the ordinary `R` or `Rscript` commands. The exact reproducible entry
point is [`build-armv7-termux`](build-armv7-termux).
177 changes: 177 additions & 0 deletions build-armv7-termux
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
#!/usr/bin/env bash

set -euo pipefail

readonly TUR_REVISION=4c751a5f7a05d62acd0f1d64aa6dd7fa17a79539
readonly TUR_REPOSITORY=https://github.com/termux-user-repository/tur.git
readonly TERMUX_PACKAGES_REVISION=13391c04904a245c9bdbbbc2c02a3756cae47113
readonly TERMUX_PACKAGES_REPOSITORY=https://github.com/termux/termux-packages.git
readonly container_repository_root=/data/data/com.termux/files/home/termux-packages

die() {
printf 'build-armv7-termux: %s\n' "$*" >&2
exit 1
}

require_command() {
command -v "$1" >/dev/null 2>&1 || die "required command not found: $1"
}

readonly repository_root="$(CDPATH= cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
readonly source_root="$repository_root/code"
readonly recipe_root="$repository_root/build/termux-armv7/package"

test -f "$source_root/VERSION" || die "run this file from a complete ir checkout containing code/VERSION"
test -f "$recipe_root/build.sh" || die "Termux package recipe is missing"

case "$(uname -m)" in
aarch64|arm64)
;;
*)
die "the native ARMv7 build requires an ARM64 Linux host; use the ARMv7 Termux GitHub workflow"
;;
esac

require_command docker
require_command dpkg-deb
require_command git
require_command sed
require_command sha256sum

source_revision="$(git -C "$repository_root" rev-parse HEAD)"
case "$source_revision" in
*[!0-9a-f]*|'') die "could not determine an immutable ir source revision" ;;
esac

upstream_version="$(sed -n '1s/ .*//p' "$source_root/VERSION")"
case "$upstream_version" in
[0-9]*.[0-9]*.[0-9]*) ;;
*) die "unexpected code/VERSION value: $upstream_version" ;;
esac
readonly source_revision upstream_version
readonly package_version="${upstream_version}~devel+git${source_revision:0:12}"

work_root="${IR_TERMUX_WORK_DIR:-}"
remove_work_root=false
if test -z "$work_root"; then
work_root="$(mktemp -d "${TMPDIR:-/tmp}/ir-termux-armv7.XXXXXX")"
remove_work_root=true
else
mkdir -p "$work_root"
work_root="$(CDPATH= cd -- "$work_root" && pwd)"
fi
readonly work_root

cleanup() {
if $remove_work_root && test "${IR_TERMUX_KEEP_WORK:-0}" != 1; then
rm -rf -- "$work_root"
else
printf 'kept build directory: %s\n' "$work_root"
fi
}
trap cleanup EXIT

readonly termux_packages="$work_root/termux-packages"
test ! -e "$termux_packages" || die "build directory is not empty: $termux_packages"

git clone --filter=blob:none --no-checkout --depth=1 \
"$TUR_REPOSITORY" "$termux_packages"
git -C "$termux_packages" fetch --depth=1 origin "$TUR_REVISION"
git -C "$termux_packages" checkout --detach "$TUR_REVISION"
test "$(git -C "$termux_packages" rev-parse HEAD)" = "$TUR_REVISION" || die "TUR revision mismatch"

git clone --filter=blob:none --no-checkout --depth=1 \
"$TERMUX_PACKAGES_REPOSITORY" "$termux_packages/termux-packages"
git -C "$termux_packages/termux-packages" fetch --depth=1 origin \
"$TERMUX_PACKAGES_REVISION"
git -C "$termux_packages/termux-packages" checkout --detach \
"$TERMUX_PACKAGES_REVISION"
test "$(git -C "$termux_packages/termux-packages" rev-parse HEAD)" = \
"$TERMUX_PACKAGES_REVISION" || die "termux-packages revision mismatch"

# TUR's setup normally advances an existing termux-packages checkout. Keep
# this build on the reviewed revision above.
sed -i \
-e "s/git reset --hard origin\/master/git reset --hard $TERMUX_PACKAGES_REVISION/" \
-e '/git pull --rebase/d' \
"$termux_packages/setup-environment.sh"
grep -q "git reset --hard $TERMUX_PACKAGES_REVISION" \
"$termux_packages/setup-environment.sh" || die "could not pin termux-packages setup"

mkdir -p "$termux_packages/sources/ir"
cp -a "$source_root/." "$termux_packages/sources/ir/"
cp -a "$recipe_root" "$termux_packages/tur-on-device/ir"
sed -i "s/@IR_PACKAGE_VERSION@/$package_version/g" \
"$termux_packages/tur-on-device/ir/build.sh"
if grep -q '@IR_PACKAGE_VERSION@' "$termux_packages/tur-on-device/ir/build.sh"; then
die "package-version substitution failed"
fi

cd "$termux_packages"
./setup-environment.sh

export ARCH=arm
./common-files/build-termux-docker.sh true
if test "${IR_TERMUX_FREE_DISK:-0}" = 1; then
./scripts/free-space.sh
fi
./common-files/build-termux-docker.sh login ./scripts/setup-termux.sh

if command -v sudo >/dev/null 2>&1; then
sudo chown -R 1000:1000 "$termux_packages"
elif test "$(id -u)" = 0; then
chown -R 1000:1000 "$termux_packages"
fi

./common-files/build-termux-docker.sh login ./build-package.sh -I ir

mapfile -t packages < <(find tur/output tur-on-device/output output \
-maxdepth 1 -type f -name 'ir_*.deb' -print 2>/dev/null | sort)
test "${#packages[@]}" = 1 || die "expected exactly one ir .deb; found ${#packages[@]}"
readonly package_path="${packages[0]}"
readonly package_in_container="$container_repository_root/$package_path"

./common-files/build-termux-docker.sh login -lc "
set -euo pipefail
test \"\$(dpkg --print-architecture)\" = arm
test \"\$(dpkg-deb --field '$package_in_container' Architecture)\" = arm
dpkg -i '$package_in_container'
test -x \"\$PREFIX/bin/ir\"
test -x \"\$PREFIX/bin/irscript\"
test ! -e \"\$PREFIX/bin/R\"
test ! -e \"\$PREFIX/bin/Rscript\"
runtime=\"\$PREFIX/lib/ir/R/bin/exec/R\"
test -x \"\$runtime\"
readelf -h \"\$runtime\" | grep -Eq 'Class:[[:space:]]+ELF32'
readelf -h \"\$runtime\" | grep -Eq 'Machine:[[:space:]]+ARM'
ir --vanilla --slave -e 'x ← 8 ÷ 2; stopifnot((x = 4)); cat(\"ir-armv7-ok\\n\")'
dpkg-query -W -f='\${Package}=\${Version}\\n' | sort > \
'$container_repository_root/build-environment-packages.txt'
"

artifact_root="${IR_TERMUX_ARTIFACT_DIR:-$repository_root/artifacts/termux-armv7}"
mkdir -p "$artifact_root"
artifact_root="$(CDPATH= cd -- "$artifact_root" && pwd)"
cp "$package_path" "$artifact_root/"
cp build-environment-packages.txt "$artifact_root/"
readonly artifact_path="$artifact_root/$(basename -- "$package_path")"

(
cd "$artifact_root"
sha256sum "$(basename -- "$artifact_path")" > SHA256SUMS
)

{
printf 'ir-source=%s\n' "$source_revision"
printf 'ir-version=%s\n' "$upstream_version"
printf 'package-version=%s\n' "$package_version"
printf 'tur-source=%s\n' "$TUR_REVISION"
printf 'termux-packages-source=%s\n' "$TERMUX_PACKAGES_REVISION"
printf 'architecture=%s\n' "$(dpkg-deb --field "$artifact_path" Architecture)"
printf 'package=%s\n' "$(basename -- "$artifact_path")"
printf 'sha256=%s\n' "$(sha256sum "$artifact_path" | cut -d ' ' -f 1)"
printf 'container-acceptance=%s\n' ir-armv7-ok
} > "$artifact_root/build-record.txt"

printf 'ARMv7 Termux package: %s\n' "$artifact_path"
printf 'Checksum: %s\n' "$artifact_root/SHA256SUMS"
25 changes: 25 additions & 0 deletions build/termux-armv7/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# ARMv7 Termux build

Run `./build-armv7-termux` at the repository root. It produces an `arm`
Termux `.deb`, `SHA256SUMS`, and `build-record.txt` under
`artifacts/termux-armv7/`.

This is a native 32-bit Android build inside TUR's ARM environment. R runs
newly built R code during its own build, so an ordinary NDK cross-build is not
an equivalent test.

The package installs `ir` and `irscript`. Its runtime lives under
`$PREFIX/lib/ir/R`; it does not install `R` or `Rscript` and therefore does not
replace an ordinary R installation.

The TUR and `termux-packages` source revisions are pinned in
`build-armv7-termux`. The two Android patches are derived from TUR's
`tur-on-device/r-base` recipe at that same TUR revision. The build also fixes
its locale to `C.UTF-8`, because ir's source contains Unicode grammar symbols.

Acceptance requires all of the following inside the ARM Termux environment:

- package architecture is `arm`;
- the runtime is an ELF32 ARM executable;
- `ir` and `irscript` exist while `R` and `Rscript` do not;
- `x ← 8 ÷ 2; stopifnot((x = 4))` succeeds.
90 changes: 90 additions & 0 deletions build/termux-armv7/package/0001-wctrans-impl.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
--- a/src/main/Makefile.in
+++ b/src/main/Makefile.in
@@ -36,6 +36,7 @@ SOURCES_C = \
times.c \
unique.c util.c \
version.c \
+ wctrans-impl.c \
g_alab_her.c g_cntrlify.c g_fontdb.c g_her_glyph.c

SOURCES_F = xxxpr.f
--- /dev/null
+++ b/src/main/wctrans-impl.c
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <wctype.h>
+#include <string.h>
+#include <errno.h>
+#include <locale.h>
+
+static const wctrans_t wctrans_tolower = (wctrans_t)(1);
+static const wctrans_t wctrans_toupper = (wctrans_t)(2);
+
+wctrans_t wctrans(const char* name) {
+ if (strcmp(name, "tolower") == 0) return wctrans_tolower;
+ if (strcmp(name, "toupper") == 0) return wctrans_toupper;
+ return NULL;
+}
+
+wint_t towctrans(wint_t c, wctrans_t t) {
+ if (t == wctrans_tolower) return towlower(c);
+ if (t == wctrans_toupper) return towupper(c);
+ errno = EINVAL;
+ return 0;
+}
--- a/src/main/character.c
+++ b/src/main/character.c
@@ -89,6 +89,8 @@
#include <R_ext/Itermacros.h>
#include <rlocale.h> // overrides iswxxxx on some platforms.

+#include "wctrans.h"
+
/* We use a shared buffer here to avoid reallocing small buffers, and
keep a standard-size (MAXELTSIZE = 8192) buffer allocated shared
between the various functions.
--- /dev/null
+++ b/src/main/wctrans.h
@@ -0,0 +1,4 @@
+#include <wctype.h>
+
+wint_t towctrans(wint_t __wc, wctrans_t __transform);
+wctrans_t wctrans(const char* __name);
--- a/src/main/grep.c
+++ b/src/main/grep.c
@@ -68,6 +68,8 @@
#include <wchar.h>
#include <wctype.h> /* for wctrans_t */

+#include "wctrans.h"
+
/* As from TRE 0.8.0, tre.h replaces regex.h */
#include <tre/tre.h>

Loading