diff --git a/.github/workflows/pr-labels.yml b/.github/workflows/pr-labels.yml index fa21115e9..67daf3ba2 100644 --- a/.github/workflows/pr-labels.yml +++ b/.github/workflows/pr-labels.yml @@ -5,7 +5,7 @@ on: jobs: check-labels: name: Check that PRs against the stable branch are labelled correctly - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - name: Check labels run: | diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 3528f2756..c2612f004 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -4,7 +4,7 @@ on: jobs: lint: name: Check that it conforms to the style guide - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - name: Checkout the Git repository uses: actions/checkout@v2 @@ -20,7 +20,7 @@ jobs: run: make lint pr: name: Check that it builds without error - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 needs: lint steps: - name: Checkout the Git repository diff --git a/.github/workflows/stable.yml b/.github/workflows/stable.yml index c111f7b4d..b4885bd77 100644 --- a/.github/workflows/stable.yml +++ b/.github/workflows/stable.yml @@ -6,7 +6,7 @@ on: jobs: stable: name: Build and publish the stable channel - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - name: Checkout the Git repository uses: actions/checkout@v2 diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 0e2aaaabb..13f5060e4 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -6,7 +6,7 @@ on: jobs: testing: name: Build and publish the testing channel - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - name: Checkout the Git repository uses: actions/checkout@v2 diff --git a/Makefile b/Makefile index 454de0ef2..90ce44d60 100644 --- a/Makefile +++ b/Makefile @@ -59,7 +59,7 @@ repo-local: ./scripts/repo_build.py --local $(FLAGS) repo-new: - ./scripts/repo_build.py --no-fetch $(FLAGS) + ./scripts/repo_build.py --diff $(FLAGS) repo-check: ./scripts/repo-check build/repo diff --git a/README.md b/README.md index 267ac6a8f..8b732dabe 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ To automatically install Opkg, Entware and Toltec, run the bootstrap script in a ```sh $ wget http://toltec-dev.org/bootstrap -$ echo "46f556b06f5624b48e974ae040b6213828eff6aa2cc78618a4d8961a27cdc8b3 bootstrap" | sha256sum -c && bash bootstrap +$ echo "5b494f5b98c4cb5f1d9836f966075026abf48a0cd320a99c026c4b18d76c8a0b bootstrap" | sha256sum -c && bash bootstrap ``` > **Warning:** diff --git a/docs/package.md b/docs/package.md index 7e2533080..fc81dbd4f 100644 --- a/docs/package.md +++ b/docs/package.md @@ -186,13 +186,44 @@ Note that this may be different from the license of the recipe itself, which is Type - Array of strings + Array of dependency specifications (strings) + + + +The list of Toltec or Entware packages that are needed to build, configure and use this package. +Dependency specifications have the following format: `package-name[(<<|<=|=|=>|>>)version]`. +For example, `xochitl`, `oxide=1.2`, and `draft<<2.0` are valid dependency specifications. + +*At build time,* all the dependencies of a recipe are offline-installed (i.e., no [install script](#install-section) is run) in the build container’s `$SYSROOT` before its build script is executed (see [below](#build-section)). +For [split packages](#split-packages), only recipe-level dependencies are honoured at this stage. +Circular dependencies are disallowed. + +*At install time,* it is guaranteed that all needed packages are unpacked and configured before this package is configured (i.e., before its `configure()` script is run). + +A version constraint can be added after each dependency declaration. +Repeat the dependency twice to specify the two ends of a version range. +Version constraints are only checked at install time. + +#### `makedepends` + + + + + + + +
Required?No, defaults to () +
TypeArray of dependency specifications (strings)
-A list of package names that must be installed on the device before this package can be configured and used. +The list of Debian, Toltec or Entware packages that are needed only to build this package. +Dependency specifications have the following format: `[host:|build:]package-name`. +For example, `build:autotools` and `libvncserver>=0.9.13` are valid dependency specifications. -See . +*Host-type dependencies* (prefixed with `host:`) are packages from Toltec or Entware that will be installed in the container’s `$SYSROOT` before the recipe’s build script is executed. + +*Build-type dependencies* (prefixed with `build:`) are packages from Debian that will be installed in the container’s root system before the recipe’s build script is executed. #### `conflicts` @@ -207,9 +238,8 @@ See -A list of package names that must **NOT** be unpacked at the same time as this package. - -See . +A list of package names that cannot be installed at the same time as this package. +Note that providing the same functionality as another package is not a sufficient reason for declaring a conflict, unless that package cannot be used in the presence of the other package. #### `image` diff --git a/package/appmarkable/package b/package/appmarkable/package index a6297509a..e100f07eb 100644 --- a/package/appmarkable/package +++ b/package/appmarkable/package @@ -5,15 +5,15 @@ pkgnames=(appmarkable) pkgdesc="Front-end for apps that do not have a graphical user interface" url="https://github.com/LinusCDE/appmarkable" -pkgver=0.0.0-8 -timestamp=2020-09-07T00:16Z +pkgver=0.0.0-9 +timestamp=2021-03-10T18:36Z section="devel" maintainer="Linus K. " license=MIT -image=rust:v1.1 -source=(https://github.com/LinusCDE/appmarkable/archive/b4226e896f441af9895ed5a4ff183af7f93b11ae.zip) -sha256sums=(0422cee28668d4e7ff554c26884e45ec63feff840eec92e6b8b2e8a7905a9d3b) +image=rust:v1.4 +source=(https://github.com/LinusCDE/appmarkable/archive/c44ee87ea2b1f1e41c9592476c076150c9a1acf4.zip) +sha256sums=(76e151aeae0f18b206dd3c6258bf74bcb5256ee2f803e1ed2073278831158f60) build() { rm -r .cargo/ diff --git a/package/calculator/package b/package/calculator/package index 4d097b7c1..d1c8fc923 100644 --- a/package/calculator/package +++ b/package/calculator/package @@ -10,6 +10,7 @@ timestamp=2020-08-20T12:28Z section="math" maintainer="Mattéo Delabre " license=GPL-3.0-or-later +makedepends=(build:imagemagick build:librsvg2-bin) image=qt:v1.1 source=( @@ -24,14 +25,6 @@ sha256sums=( ) build() { - # Get needed build packages - export DEBIAN_FRONTEND=noninteractive - apt-get update -y - apt-get install -y \ - --no-install-recommends \ - -o Dpkg::Options::="--force-confdef" \ - imagemagick librsvg2-bin - qmake Calculator.pro make diff --git a/package/ddvk-hacks/package b/package/ddvk-hacks/package new file mode 100644 index 000000000..4b18716c5 --- /dev/null +++ b/package/ddvk-hacks/package @@ -0,0 +1,180 @@ +#!/usr/bin/env bash +# Copyright (c) 2021 The Toltec Contributors +# SPDX-License-Identifier: MIT + +pkgnames=(ddvk-hacks) +pkgdesc="Enhance Xochitl with additional features" +url=https://github.com/ddvk/remarkable-hacks +pkgver=17.04-1 +timestamp=2020-12-25T14:32Z +section="readers" +maintainer="Mattéo Delabre " +license=MIT +flags=(nostrip) + +source=(https://github.com/ddvk/remarkable-hacks/archive/4b75aeaffa2794ff7d4c106c75e47278d5855401.zip) +sha256sums=(41b2e2c3c740c109bc33bb3a2d6db9d52bfe8a282a10029d9da07fa70e0808d3) + +_patches_dir="/opt/share/ddvk-hacks" +_xochitl_path="/usr/bin/xochitl" +_work_dir="/home/root/.local/share/ddvk-hacks" +_backup_path="$_work_dir/xochitl.backup" +_old_backup_path="$_backup_path.old" +_patched_path="$_work_dir/xochitl.patched" + +package() { + install -D -m 644 -t "$pkgdir$_patches_dir" "$srcdir"/patches/2113/patch_09 + install -D -m 644 -t "$pkgdir$_patches_dir" "$srcdir"/patches/22048/patch_10.10 + install -D -m 644 -t "$pkgdir$_patches_dir" "$srcdir"/patches/22182/patch_11.01 + install -D -m 644 -t "$pkgdir$_patches_dir" "$srcdir"/patches/23016/patch_12.11 + install -D -m 644 -t "$pkgdir$_patches_dir" "$srcdir"/patches/23023/patch_13.07 + install -D -m 644 -t "$pkgdir$_patches_dir" "$srcdir"/patches/23127/patch_14.01 + install -D -m 644 -t "$pkgdir$_patches_dir" "$srcdir"/patches/24027_rm1/patch_15.1.02 + install -D -m 644 -t "$pkgdir$_patches_dir" "$srcdir"/patches/24027_rm2/patch_15.2.01 + install -D -m 644 -t "$pkgdir$_patches_dir" "$srcdir"/patches/24130_rm1/patch_16.1.06 + install -D -m 644 -t "$pkgdir$_patches_dir" "$srcdir"/patches/24130_rm2/patch_16.2.03 + install -D -m 644 -t "$pkgdir$_patches_dir" "$srcdir"/patches/25027_rm1/patch_17.1.04 + install -D -m 644 -t "$pkgdir$_patches_dir" "$srcdir"/patches/25027_rm2/patch_17.2.04 +} + +configure() { + local build_date + build_date="$(cat /etc/version)" + local patch_version + local device + local original_hash + local xochitl_version + + case "$build_date" in + "20201127104549") + patch_version="17.2.04" + device="reMarkable 2" + original_hash="0ed1af968a31e816513d15321bd02b9625ccb073" + xochitl_version="2.5.0.27" + ;; + "20201127104105") + patch_version="17.1.04" + device="reMarkable 1" + original_hash="4296b9c6d7a66aadd12e1cf61a13b7b19504673d" + xochitl_version="2.5.0.27" + ;; + "20201028164335") + patch_version="16.1.06" + device="reMarkable 1" + original_hash="336529ce6e7ef9d6fadd30872708556ca8711f0b" + xochitl_version="2.4.1.30" + ;; + "20201028163830") + patch_version="16.2.03" + device="reMarkable 2" + original_hash="c88d155b7ca8c770240b2c00048968f8445f8115" + xochitl_version="2.4.1.30" + ;; + "20201016123042") + patch_version="15.2.01" + device="reMarkable 2" + original_hash="797f58ed93d2e22e7d77fcd9de6c6eb5d49a3a7f" + xochitl_version="2.4.0.27" + ;; + "20201016123325") + patch_version="15.1.02" + device="reMarkable 1" + original_hash="891e06535c0ae742eeaa3b9a20e9ff03d0f659d3" + xochitl_version="2.4.0.27" + ;; + "20200914085553" | "20200914090635") + patch_version="14.01" + device="reMarkable 2" + original_hash="596b02f401fb0ceb6a73df470fbab418b305cdbc" + xochitl_version="2.3.1.27" + ;; + "20200904144143") + patch_version="13.07" + device="reMarkable 2" + original_hash="7eb1ed8b75b1b282fd4ecf30ef19118d3a41fcc7" + xochitl_version="2.3.0.23" + ;; + "20200709160645") + patch_version="12.11" + device="reMarkable 1" + original_hash="005b05ef64f079aaf377d373cb7e2889a2aa774a" + xochitl_version="2.3.0.16" + ;; + "20200805214933") + patch_version="11.01" + device="reMarkable 2" + original_hash="c7d965972a5a6d2bf8503b1b09b52a89c422505b" + xochitl_version="2.2.1.82" + ;; + "20200528081414") + patch_version="10.10" + device="reMarkable 1" + original_hash="7e92c177df685972a699db6c4a7a918296447f74" + xochitl_version="2.2.0.48" + ;; + "20200320131825") + patch_version="09" + device="reMarkable 1" + original_hash="c8661fbd74a049134509dc22da415bb651d7feac" + xochitl_version="2.1.1.3" + ;; + *) + echo + echo "Error: The version the device is running is not supported, yet." + echo "Build date: $build_date" + echo + exit 1 + ;; + esac + + echo + echo "Device: $device" + echo "Xochitl version: $xochitl_version" + echo "Patch version: $patch_version" + echo + + if ! sha1sum -c <(echo "$original_hash $_xochitl_path") > /dev/null 2>&1; then + echo "Error: Invalid Xochitl checksum" + echo "Maybe ddvk-hacks are already installed?" + echo + exit 1 + fi + + if [[ -f "$_backup_path" ]]; then + mv "$_backup_path" "$_old_backup_path" + fi + + mkdir -p "$(dirname "$_backup_path")" + cp "$_xochitl_path" "$_backup_path" + + echo "Patching Xochitl" + bspatch "$_backup_path" "$_patched_path" "$_patches_dir"/patch_"$patch_version" + cp "$_patched_path" "$_xochitl_path" + rm -rf /home/root/.cache/remarkable/xochitl/qmlcache/* + + echo "Please restart Xochitl to use the patches" + echo +} + +_restore() { + echo + echo "Restoring the original Xochitl binary" + + if ! diff "$_xochitl_path" "$_patched_path" > /dev/null 2>&1; then + echo "Warning: Xochitl binary has changed!" + echo "Not restoring the backup" + else + cp "$_backup_path" "$_xochitl_path" + rm -rf /home/root/.cache/remarkable/xochitl/qmlcache/* + fi + + echo +} + +preremove() { + _restore +} + +preupgrade() { + _restore +} diff --git a/package/draft/package b/package/draft/package index bcc4ed468..edd4c7bf5 100644 --- a/package/draft/package +++ b/package/draft/package @@ -12,7 +12,7 @@ maintainer="Mattéo Delabre " license=Apache-2.0 depends=(xochitl) -image=qt:v1.1 +image=qt:v1.3.2 source=( https://github.com/dixonary/draft-reMarkable/archive/5bd660a2fd07eba166c6110d2b48cfc58ee67e58.zip draft.service diff --git a/package/evtest/package b/package/evtest/package index 896abbf7f..4b79fdead 100644 --- a/package/evtest/package +++ b/package/evtest/package @@ -10,17 +10,14 @@ timestamp=2020-12-30T02:52Z section="utils" maintainer="Linus K. " license=GPL-2.0-only +makedepends=(build:automake) image=base:v1.2.1 -source=(https://gitlab.freedesktop.org/libevdev/evtest/-/archive/evtest-1.34/evtest-evtest-1.34.zip) +source=("https://gitlab.freedesktop.org/libevdev/evtest/-/archive/evtest-${pkgver%-*}/evtest-evtest-${pkgver%-*}.zip") sha256sums=(62f7e34c5bab91b5015de5b056d79051c677c5bd5702facb2885f8e4ba0df84c) build() { - apt-get update - apt-get install -y autoconf - export CC=arm-linux-gnueabihf-gcc - ./autogen.sh --host armv7 ./configure --host armv7 make diff --git a/package/libdlib/package b/package/libdlib/package new file mode 100644 index 000000000..18a215be7 --- /dev/null +++ b/package/libdlib/package @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +# Copyright (c) 2021 The Toltec Contributors +# SPDX-License-Identifier: MIT + +pkgnames=(libdlib libdlib-dev) +pkgdesc="Toolkit for making machine learning and data analysis applications in C++" +url=http://dlib.net +pkgver=19.21-1 +timestamp=2020-08-08T19:41:07Z +section="devel" +maintainer="Mattéo Delabre " +license=BSL-1.0 + +image=base:v1.3.1 +source=("https://github.com/davisking/dlib/archive/v${pkgver%-*}.tar.gz") +sha256sums=(116f52e58be04b47dab52057eaad4b5c4d5c3032d927fe23d55b0741fc4107a0) + +build() { + cmake -B build \ + -DCMAKE_TOOLCHAIN_FILE="/usr/share/cmake/$CHOST.cmake" \ + -DCMAKE_INSTALL_PREFIX="/opt" \ + -DBUILD_SHARED_LIBS=ON + cmake --build build + cmake --build build --target install -- DESTDIR=../install +} + +libdlib() { + package() { + install -d "$pkgdir"/opt/lib "$pkgdir"/usr/lib + cp --no-dereference "$srcdir"/install/opt/lib/lib*.so* "$pkgdir"/opt/lib + + for file in "$pkgdir"/opt/lib/lib*.so*; do + base="${file#"$pkgdir"}" + ln -s "$base" "${file/\/opt/\/usr}" + done + } +} + +libdlib-dev() { + pkgdesc="$pkgdesc - development files" + + package() { + install -d "$pkgdir"/opt/lib + cp -r "$srcdir"/install/opt/lib/pkgconfig "$pkgdir"/opt/lib + cp -r "$srcdir"/install/opt/lib/cmake "$pkgdir"/opt/lib + cp -r "$srcdir"/install/opt/include "$pkgdir"/opt + } +} diff --git a/package/libvncserver/package b/package/libvncserver/package new file mode 100644 index 000000000..5e831bd29 --- /dev/null +++ b/package/libvncserver/package @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# Copyright (c) 2021 The Toltec Contributors +# SPDX-License-Identifier: MIT + +pkgnames=(libvncserver libvncclient libvncserver-dev) +pkgdesc="C libraries for implementing VNC servers or clients" +url=https://libvnc.github.io +pkgver=0.9.13-1 +timestamp=2020-06-13T19:19:11Z +section="devel" +maintainer="Mattéo Delabre " +license=GPL-2.0-or-later + +image=base:v1.3.1 +source=("https://github.com/LibVNC/libvncserver/archive/LibVNCServer-${pkgver%-*}.zip") +sha256sums=(d209d70998a9b98f9120eeb82df7a17767796c477eaa8297e0a55856a977c54f) + +build() { + cmake -B build \ + -DCMAKE_TOOLCHAIN_FILE="/usr/share/cmake/$CHOST.cmake" \ + -DCMAKE_INSTALL_PREFIX="/opt" + cmake --build build + cmake --build build --target install -- DESTDIR=../install +} + +libvncserver() { + pkgdesc="$pkgdesc - server library" + + package() { + install -d "$pkgdir"/opt/lib "$pkgdir"/usr/lib + cp --no-dereference "$srcdir"/install/opt/lib/libvncserver*.so* "$pkgdir"/opt/lib + + for file in "$pkgdir"/opt/lib/lib*.so*; do + base="${file#"$pkgdir"}" + ln -s "$base" "${file/\/opt/\/usr}" + done + } +} + +libvncclient() { + pkgdesc="$pkgdesc - client library" + + package() { + install -d "$pkgdir"/opt/lib "$pkgdir"/usr/lib + cp --no-dereference "$srcdir"/install/opt/lib/libvncclient*.so* "$pkgdir"/opt/lib + + for file in "$pkgdir"/opt/lib/lib*.so*; do + base="${file#"$pkgdir"}" + ln -s "$base" "${file/\/opt/\/usr}" + done + } +} + +libvncserver-dev() { + pkgdesc="$pkgdesc - development files" + + package() { + install -d "$pkgdir"/opt/lib + cp -r "$srcdir"/install/opt/lib/pkgconfig "$pkgdir"/opt/lib + cp -r "$srcdir"/install/opt/include "$pkgdir"/opt + } +} diff --git a/package/oxide/package b/package/oxide/package index de93fe2fc..b224e68ce 100644 --- a/package/oxide/package +++ b/package/oxide/package @@ -3,19 +3,19 @@ # SPDX-License-Identifier: MIT pkgnames=(erode fret oxide rot tarnish decay) -pkgver=2.1~beta-5 +pkgver=2.1.2~1 timestamp=2021-01-07T03:28Z maintainer="Eeems " license=MIT -source=(https://github.com/Eeems/oxide/releases/download/v2.1/oxide.zip) -sha256sums=(f3f50688db7a11fab74caeb607f23f26f8315ff1d3ec28566f408d4301af26be) +source=(https://github.com/Eeems/oxide/releases/download/v2.1.2/oxide.zip) +sha256sums=(c9e8214dd60346b0865bc41e8180064cbb7e108ed676b6726abecff0b4adb710) erode() { pkgdesc="Task manager" url=https://github.com/Eeems/oxide/tree/master/applications/process-manager section="admin" - depends=("tarnish (= $pkgver)") + depends=("tarnish=$pkgver") package() { install -D -m 755 -t "$pkgdir"/opt/bin "$srcdir"/opt/bin/erode @@ -27,7 +27,7 @@ fret() { pkgdesc="Take screenshots" url=https://github.com/Eeems/oxide/tree/master/applications/screenshot-tool section="utils" - depends=("tarnish (= $pkgver)") + depends=("tarnish=$pkgver") package() { install -D -m 755 -t "$pkgdir"/opt/bin "$srcdir"/opt/bin/fret @@ -39,7 +39,7 @@ oxide() { pkgdesc="Launcher application" url=https://github.com/Eeems/oxide/tree/master/applications/launcher section="launchers" - depends=("erode (= $pkgver)" "fret (= $pkgver)" "tarnish (= $pkgver)" "rot (= $pkgver)" "decay (= $pkgver)") + depends=("erode=$pkgver" "fret=$pkgver" "tarnish=$pkgver" "rot=$pkgver" "decay=$pkgver") package() { install -D -m 755 -t "$pkgdir"/opt/bin "$srcdir"/opt/bin/oxide @@ -60,7 +60,7 @@ rot() { pkgdesc="Manage Oxide settings through the command line" url=https://github.com/Eeems/oxide/tree/master/applications/settings-manager section="admin" - depends=("tarnish (= $pkgver)") + depends=("tarnish=$pkgver") package() { install -D -m 755 -t "$pkgdir"/opt/bin "$srcdir"/opt/bin/rot @@ -100,7 +100,7 @@ decay() { pkgdesc="Lockscreen application" url=https://github.com/Eeems/oxide/tree/master/applications/lockscreen section="utils" - depends=("tarnish (= $pkgver)") + depends=("tarnish=$pkgver") package() { install -D -m 755 -t "$pkgdir"/opt/bin "$srcdir"/opt/bin/decay diff --git a/package/plato/package b/package/plato/package index 2e555ce8a..6e4149029 100644 --- a/package/plato/package +++ b/package/plato/package @@ -10,21 +10,13 @@ timestamp=2021-01-13T23:43Z section="readers" maintainer="Linus K. " license=AGPL-3.0-or-later +makedepends=(build:jq build:patchelf build:unzip build:wget) image=rust:v1.2.1 -source=(https://github.com/LinusCDE/plato/archive/0.9.13-rm-release-7.zip) +source=("https://github.com/LinusCDE/plato/archive/${pkgver%-*}-rm-release-7.zip") sha256sums=(17718d74066e5cbec043c98b52958a311eb793674751d6b7789b5baff2227f96) build() { - # Get needed build packages - export DEBIAN_FRONTEND=noninteractive - apt-get update -y - apt-get install -y \ - jq \ - patchelf \ - unzip \ - wget - # Fall back to system-wide config rm .cargo/config diff --git a/package/recrossable/package b/package/recrossable/package index 4787351b6..fb1ffbfda 100644 --- a/package/recrossable/package +++ b/package/recrossable/package @@ -5,34 +5,37 @@ pkgnames=(recrossable) pkgdesc="Solve crossword puzzles" url=https://github.com/sandsmark/recrossable -pkgver=0.0.0-3 -timestamp=2020-10-05T08:43Z +pkgver=0.0.0-5 +timestamp=2021-01-15T12:58:22Z section="games" maintainer="Mattéo Delabre " license=GPL-2.0-or-later +depends=(libdlib) +makedepends=(build:imagemagick build:librsvg2-bin host:libdlib-dev) -image=qt:v1.2 +image=qt:v1.4 source=( - https://github.com/sandsmark/recrossable/archive/5faa37c6e8a13e3ddeb0fe6d9d5f15717f83b662.zip + https://github.com/sandsmark/recrossable/archive/234d5744c0b20087a588d0ecead0a9f58c1f323d.zip recrossable.draft recrossable.svg ) sha256sums=( - 51e9f630204262fd2b839dafadb4e288797609467e9f07c29f7581711cb2eaec + 24b7512d295df504583ccdde7307e8f59fc561da638b992902b4ebccc412fa92 SKIP SKIP ) build() { - # Get needed build packages - export DEBIAN_FRONTEND=noninteractive - apt-get update -y - apt-get install -y \ - --no-install-recommends \ - -o Dpkg::Options::="--force-confdef" \ - imagemagick librsvg2-bin - - sed -i 's/linux-oe-g++/linux-arm-gnueabihf-g++/' recrossable.pro + cat << QMAKE >> recrossable.pro +linux-arm-remarkable-g++ { + LIBS += -lqsgepaper + DEFINES += REMARKABLE_DEVICE + + CONFIG += link_pkgconfig + PKGCONFIG += dlib-1 +} +QMAKE + qmake make diff --git a/package/rm2fb/package b/package/rm2fb/package index 702f7e9b7..51f6573a1 100644 --- a/package/rm2fb/package +++ b/package/rm2fb/package @@ -1,26 +1,26 @@ #!/usr/bin/env bash -# Copyright (c) 2020 The Toltec Contributors +# Copyright (c) 2021 The Toltec Contributors # SPDX-License-Identifier: MIT pkgnames=(rm2fb) -timestamp=2020-10-10T01:41+00:00 +timestamp=2021-02-21T01:41+00:00 maintainer="raisjn " license=MIT pkgdesc="Interface to the reMarkable 2 framebuffer" url="https://github.com/ddvk/remarkable2-framebuffer" -pkgver=1.0.0-7 +pkgver=1.0.1-1 section="devel" image=qt:v1.1 source=( - https://github.com/ddvk/remarkable2-framebuffer/archive/cd2766864c7985fa50cdf85f4ae0a411d7ca4e56.zip + https://github.com/ddvk/remarkable2-framebuffer/archive/aa70ce37c71f84b91434f5c469fa71f4b1bb328c.zip rm2fb.conf rm2fb.service rm2fb-server rm2fb-client ) sha256sums=( - c274c458270eec28950834bd28ff069a0af65e40fcc288a8ff28235001e8914f + 76f1c0b72260a9743f3e008100ffddf1f89cd44c6f0376d906e98ae66eaea716 SKIP SKIP SKIP @@ -34,8 +34,10 @@ build() { package() { mkdir -p "$pkgdir"/opt/lib "$pkgdir"/opt/etc "$pkgdir"/opt/bin - install -D -m 755 "$srcdir"/src/client/librm2fb_client.so.${pkgver%-*} "$pkgdir"/opt/lib/ - install -D -m 755 "$srcdir"/src/server/librm2fb_server.so.${pkgver%-*} "$pkgdir"/opt/lib/ + install -D -m 755 "$srcdir/src/client/librm2fb_client.so.${pkgver%-*}" "$pkgdir"/opt/lib/ + ln -s "librm2fb_client.so.${pkgver%-*}" "$pkgdir/opt/lib/librm2fb_client.so.${pkgver%.*.*-*}" + install -D -m 755 "$srcdir/src/server/librm2fb_server.so.${pkgver%-*}" "$pkgdir"/opt/lib/ + ln -s "librm2fb_server.so.${pkgver%-*}" "$pkgdir/opt/lib/librm2fb_server.so.${pkgver%.*.*-*}" install -D -m 755 "$srcdir"/rm2fb-server "$pkgdir"/opt/bin/ install -D -m 755 "$srcdir"/rm2fb-client "$pkgdir"/opt/bin/ install -D -m 644 "$srcdir"/rm2fb.conf "$pkgdir"/etc/systemd/system.conf.d/01-rm2fb.conf diff --git a/package/rm2fb/rm2fb-client b/package/rm2fb/rm2fb-client index 4cbd93b4e..8dbb3063a 100644 --- a/package/rm2fb/rm2fb-client +++ b/package/rm2fb/rm2fb-client @@ -1,3 +1,2 @@ #!/usr/bin/env bash -# shellcheck disable=SC2048 -LD_PRELOAD=/opt/lib/librm2fb_client.so.1.0.0 $* +LD_PRELOAD=/opt/lib/librm2fb_client.so.1 exec "$@" diff --git a/package/rm2fb/rm2fb-server b/package/rm2fb/rm2fb-server index bc8c13500..bdc224958 100644 --- a/package/rm2fb/rm2fb-server +++ b/package/rm2fb/rm2fb-server @@ -1,4 +1,2 @@ #!/usr/bin/env bash -# we actually do want to execute this command -# shellcheck disable=SC2091 -LD_PRELOAD=/opt/lib/librm2fb_server.so.1.0.0 $(command -v remarkable-shutdown) +LD_PRELOAD=/opt/lib/librm2fb_server.so.1 exec "$(command -v remarkable-shutdown)" diff --git a/package/rm2fb/rm2fb.conf b/package/rm2fb/rm2fb.conf index ece51aa2f..9c920e20d 100644 --- a/package/rm2fb/rm2fb.conf +++ b/package/rm2fb/rm2fb.conf @@ -1,2 +1,2 @@ [Manager] -DefaultEnvironment=LD_PRELOAD=/opt/lib/librm2fb_client.so.1.0.0 +DefaultEnvironment=LD_PRELOAD=/opt/lib/librm2fb_client.so.1 diff --git a/package/rm2fb/rm2fb.service b/package/rm2fb/rm2fb.service index 1e7f1ec0e..f5525b962 100644 --- a/package/rm2fb/rm2fb.service +++ b/package/rm2fb/rm2fb.service @@ -1,4 +1,4 @@ -# Copyright (c) 2020 The Toltec Contributors +# Copyright (c) 2021 The Toltec Contributors # SPDX-License-Identifier: MIT [Unit] @@ -14,7 +14,7 @@ ExecStart=/usr/bin/remarkable-shutdown Restart=on-failure RestartSec=5 Environment="HOME=/home/root" -Environment="LD_PRELOAD=/opt/lib/librm2fb_server.so.1.0.0" +Environment="LD_PRELOAD=/opt/lib/librm2fb_server.so.1" [Install] WantedBy=multi-user.target xochitl.service diff --git a/package/rmkit/2021_03_05_genie.diff b/package/rmkit/2021_03_05_genie.diff new file mode 100644 index 000000000..9b329fb43 --- /dev/null +++ b/package/rmkit/2021_03_05_genie.diff @@ -0,0 +1,29 @@ +diff --git a/src/genie/gesture_parser.cpy b/src/genie/gesture_parser.cpy +index fa82b67..c94fd59 100644 +--- a/src/genie/gesture_parser.cpy ++++ b/src/genie/gesture_parser.cpy +@@ -16,17 +16,18 @@ namespace genie: + ; + + void run_command(string command): ++ debug "RUNNING COMMAND", command ++ string cmd = command + "&" ++ c_str := cmd.c_str() ++ _ := system(c_str) ++ + ui::TaskQueue::add_task([=]() { + usleep(1e3 * 50) +- +- debug "RUNNING COMMAND", command +- string cmd = command +- c_str := cmd.c_str() +- _ := system(c_str) +- + ui::MainLoop::reset_gestures() + }) + ++ ++ + input::SwipeGesture* build_swipe_gesture(GestureConfigData gcd): + fb := framebuffer::get() + fw, fh := fb->get_display_size() diff --git a/package/rmkit/changelog b/package/rmkit/changelog new file mode 100644 index 000000000..172f5ec0a --- /dev/null +++ b/package/rmkit/changelog @@ -0,0 +1,221 @@ +2021-02-25 raisjn + + lamp: + + * add new binary lamp for injecting pen strokes + + iago: + + * add new binary iago for drawing shapes + + +2021-02-17 raisjn + + remux: + + * better multi-touch gesture support + * launch more consistently after closing or stylus touches + * better power management and ability to toggle (manage_power=no) + * update to use less power + + genie: + + * better gesture support + + mines: + + * speed up game over screen + + harmony: + + * add 4 shades of gray + * better support for rM1 buffer + * faster drawing on first draw + +2021-02-04 raisjn + + bufshot -> 0.1.0-1 + + * add new binary bufshot for taking screenshots + +2021-01-20 raisjn + + rmkit (requires bumping all apps): + + * fix font artifacts when using large font + * warn when running on rM2 without rm2fb + + remux: + + * use ?MB for apps we don't know mem usage of + * idle fixes for rM2 + * properly draw 8bit grayscale suspend screens + * fix touch flood to use ABS_DISTANCE=2 instead of 0 + +2021-01-16 raisjn + + rmkit: + + * fix touch event handling while dragging + + harmony: + + * faster drawing on pen down on rM2 + + remux: + + * add "quick back" feature + * add config file to configure gestures + * make killing xochitl harder + * remove three finger launch + + nao: + + * use built in wget when available + +2021-01-14 raisjn + + + simple -> 0.1.1: + + * add underline to button text + * add widget ID implementation + + remux -> 0.1.6: + + * add "back" feature + * add /run/remux.api fifo which accepts "show", "hide" and "back" as input + +2021-01-10 raisjn + + + genie -> 1.1-1: + + * add gesture invalidator when pen is writing + * fix environment in service file + +2021-01-07 raisjn + + + genie -> 1.0 + + * genie is a gesture based launcher that recognizes gestures and runs commands + +2021-01-5 raisjn + + + remux -> 0.1.5-1 + + * launch remux on 3 finger tap (much more useful on rM2) + +2020-12-31 raisjn + + + remux -> 0.1.4 + + * use remux with fix for suspend/shutdown threshold == 0 + * use suspended.png if sleeping.png isn't available + + rmkit + + * update build to make src/build before building stb.o + +2020-12-26 raisjn + + rmkit: + + * update remux with fix for broken wifi on suspend + + package: + + * add icons for harmony, mines and nao + +2020-12-25 raisjn + + remux: + + * correctly suspend on remux for rM2 + * add fix to remux so that koreader doesn't restart when focused + +2020-12-21 raisjn + + harmony: + + * create saved_images dir in package configure script + + +2020-12-19 raisjn + + remux: + + * update remux with shutdown fix for rM1 + * update to remux for better cmdline parsing + * update remux so LAST_ACTION defaults to now + +2020-12-14 raisjn + + rmkit: + + * update rmkit to latest version which supports rm2, release as 0.1.0 + + remux: + + * continued rm2 support in remux + * fix suspend screen in remux on rm2 + +2020-11-08 raisjn + + rmkit: + + * update to latest version (11.07.2020) + +2020-10-15 raisjn + + remux: + + * add memory usage analyzer to remux + +2020-10-08 raisjn + + remux: + + * make remux properly dismissable on first launch + + +2020-09-30 raisjn + + remux: + + * read xochitl command from systemd service file + * use rgb565 to draw better quality suspend screen + * better KOReader integration + +2020-09-27 raisjn + + remux: + + * use process groups for managing programs + + nao: + + * add exit button to main screen + +2020-09-21 raisjn + + remux: + + * better plato interaction + * fixes for power management + * rename remux from remux.exe + + nao: + + * add sync, check and upgrade to main menu + +2020-09-14 raisjn + + * split rmkit into harmony, mines, nao and simple packages + +2020-09-02 raisjn + + * add rmkit apps: harmony, remux, mines, ... diff --git a/package/rmkit/package b/package/rmkit/package index 5e3637752..837cfb789 100644 --- a/package/rmkit/package +++ b/package/rmkit/package @@ -2,32 +2,46 @@ # Copyright (c) 2020 The Toltec Contributors # SPDX-License-Identifier: MIT -pkgnames=(genie harmony mines nao remux simple) -timestamp=2021-01-12T14:03-08:00 +pkgnames=(bufshot genie harmony iago lamp mines nao remux simple) +timestamp=2021-02-24T13:20-08:00 maintainer="raisjn " license=MIT image=python:v1.1 source=( - https://github.com/rmkit-dev/rmkit/archive/3d8ba83684f76b3e4146af9f295de4615a416cb8.zip + https://github.com/rmkit-dev/rmkit/archive/681354803a158eca9c267b1f1976affda391bdff.zip remux.service genie.service + 2021_03_05_genie.diff ) sha256sums=( - 3f37bf01547395404d27df788c959895f194711e80ef26839f521d1dedc4faed + 6e2b1fb7fa52f6412268b1d063e9dd117d1913411fe26627c34cf3d02a836de5 + SKIP SKIP SKIP ) build() { pip3 install okp + patch -p1 < 2021_03_05_genie.diff make } +bufshot() { + pkgdesc="program for saving the framebuffer as a png" + url="https://github.com/rmkit-dev/rmkit/tree/master/src/bufshot" + pkgver=0.1.0-2 + section="utils" + + package() { + install -D -m 755 "$srcdir"/src/build/bufshot "$pkgdir"/opt/bin/bufshot + } +} + genie() { pkgdesc="Gesture engine that connects commands to gestures" url="https://rmkit.dev/apps/genie" - pkgver=0.1.2-3 + pkgver=0.1.4-2 section="utils" package() { @@ -54,7 +68,7 @@ genie() { harmony() { pkgdesc="Procedural sketching app" url="https://rmkit.dev/apps/harmony" - pkgver=0.1.1-2 + pkgver=0.1.2-1 section="drawing" package() { @@ -68,10 +82,33 @@ harmony() { } } +iago() { + pkgdesc="overlay for drawing shapes via stroke injection" + url="https://rmkit.dev/apps/iago" + pkgver=0.1.0-1 + section="utils" + depends=("lamp") + + package() { + install -D -m 755 "$srcdir"/src/build/iago "$pkgdir"/opt/bin/iago + } +} + +lamp() { + pkgdesc="config based stroke injection utility" + url="https://rmkit.dev/apps/lamp" + pkgver=0.1.0-1 + section="utils" + + package() { + install -D -m 755 "$srcdir"/src/build/lamp "$pkgdir"/opt/bin/lamp + } +} + mines() { pkgdesc="Mine detection game" url="https://rmkit.dev/apps/minesweeper" - pkgver=0.1.1-2 + pkgver=0.1.2-1 section="games" package() { @@ -84,7 +121,7 @@ mines() { nao() { pkgdesc="Nao Package Manager: opkg UI built with SAS" url="https://rmkit.dev/apps/nao" - pkgver=0.1.1-1 + pkgver=0.1.2-1 section="admin" depends=(simple) @@ -96,9 +133,9 @@ nao() { } remux() { - pkgdesc="App launcher that supports multi-tasking applications" + pkgdesc="Launcher that supports multi-tasking applications" url="https://rmkit.dev/apps/remux" - pkgver=0.1.7-3 + pkgver=0.1.8-1 section="launchers" package() { @@ -130,7 +167,7 @@ remux() { simple() { pkgdesc="Simple app script for writing scripted applications" url="https://rmkit.dev/apps/sas" - pkgver=0.1.2-2 + pkgver=0.1.3-1 section="devel" package() { diff --git a/package/rmservewacominput/package b/package/rmservewacominput/package index 06406a55b..6a392feb5 100644 --- a/package/rmservewacominput/package +++ b/package/rmservewacominput/package @@ -5,16 +5,24 @@ pkgnames=(rmservewacominput) pkgdesc="Serve pen input on port 33333" url=https://github.com/LinusCDE/rmWacomToMouse -pkgver=0.2.5-9 -timestamp=2020-09-06T23:23Z +pkgver=0.3.0-1 +timestamp=2021-03-10T18:36Z section="utils" maintainer="Linus K. " license=MIT depends=(appmarkable) -image=base:v1.1 -source=(https://github.com/LinusCDE/rmWacomToMouse/archive/1a45d3446c8bc11de04800a702576056016875ed.zip) -sha256sums=(4d51ff9d6b8f57fe4970b4a0adebafe3513ddeb521a2707b81a42d32f73dadb1) +image=base:v1.4 +source=( + https://github.com/LinusCDE/rmWacomToMouse/archive/fd1c5454b65f456f6e890b99109e50a8f576dad1.zip + rmservewacominput.draft + rmservewacominput-gui +) +sha256sums=( + c2c15b519a8352a5b52e1b15154e4024acc423552c6cf7559c23fcb8beffcd56 + SKIP + SKIP +) build() { cd c_implementation @@ -23,6 +31,7 @@ build() { package() { install -D -m 755 "$srcdir"/c_implementation/rmServeWacomInput "$pkgdir"/opt/bin/rmservewacominput - install -D -m 644 "$srcdir"/oxide "$pkgdir"/opt/etc/draft/rmservewacominput + install -D -m 755 "$srcdir"/rmservewacominput-gui "$pkgdir"/opt/bin/rmservewacominput-gui + install -D -m 644 "$srcdir"/rmservewacominput.draft "$pkgdir"/opt/etc/draft/rmservewacominput.draft install -D -m 644 "$srcdir"/icon.png "$pkgdir"/opt/etc/draft/icons/rmservewacominput.png } diff --git a/package/rmservewacominput/rmservewacominput-gui b/package/rmservewacominput/rmservewacominput-gui new file mode 100755 index 000000000..f63e49daa --- /dev/null +++ b/package/rmservewacominput/rmservewacominput-gui @@ -0,0 +1,6 @@ +#!/bin/bash + +# Copyright (c) 2021 The Toltec Contributors +# SPDX-License-Identifier: MIT + +exec /opt/bin/appmarkable /opt/bin/rmservewacominput -n rmServeWacomInput -i /opt/etc/draft/icons/rmservewacominput.png diff --git a/package/rmservewacominput/rmservewacominput.draft b/package/rmservewacominput/rmservewacominput.draft new file mode 100644 index 000000000..f56df3c93 --- /dev/null +++ b/package/rmservewacominput/rmservewacominput.draft @@ -0,0 +1,8 @@ +# Copyright (c) 2021 The Toltec Contributors +# SPDX-License-Identifier: MIT + +name=rmServeWacomInput +desc=Open a server on port 33333 and serve pen input +call=/opt/bin/rmservewacominput-gui +term=: +imgFile=rmservewacominput diff --git a/package/toltec-bootstrap/entware-reenable b/package/toltec-bootstrap/entware-reenable index 53eaf91d8..1108a67b6 100644 --- a/package/toltec-bootstrap/entware-reenable +++ b/package/toltec-bootstrap/entware-reenable @@ -1,6 +1,9 @@ #!/usr/bin/env bash # Path for the systemd unit that mounts Entware to /opt -systemd_mount_path=/lib/systemd/system/opt.mount +systemd_opt_mount_path=/lib/systemd/system/opt.mount + +# Path for the systemd unit that mounts /opt/share/zoneinfo to /usr/share/zoneinfo +systemd_timezone_mount_path=/lib/systemd/system/usr-share-zoneinfo.mount # Path where the actual Entware distribution resides (will be mounted to /opt) entware_path=/home/root/.entware @@ -10,7 +13,7 @@ entware-mount() { mkdir -p "$entware_path" # Create systemd mount unit to mount over /opt on reboot - cat > "$systemd_mount_path" << UNIT + cat > "$systemd_opt_mount_path" << UNIT [Unit] Description=Bind mount Entware over /opt DefaultDependencies=no @@ -28,7 +31,33 @@ WantedBy=local-fs.target UNIT systemctl daemon-reload - systemctl enable --now opt.mount 2> /dev/null + systemctl enable --now "$(basename "$systemd_opt_mount_path")" 2> /dev/null +} + +# Mount /opt/share/zoneinfo to /usr/share/zoneinfo +timezone-mount() { + mkdir -p "$entware_path"/share/zoneinfo + + # Create systemd mount unit to mount over /opt on reboot + cat > "$systemd_timezone_mount_path" << UNIT +[Unit] +Description=Bind mount /opt/share/zoneinfo over /usr/share/zoneinfo +DefaultDependencies=no +Conflicts=umount.target +Before=local-fs.target umount.target + +[Mount] +What=/home/root/.entware/share/zoneinfo +Where=/usr/share/zoneinfo +Type=none +Options=bind + +[Install] +WantedBy=local-fs.target +UNIT + + systemctl daemon-reload + systemctl enable --now "$(basename "$systemd_timezone_mount_path")" 2> /dev/null } reinstall-root() { @@ -48,6 +77,7 @@ reinstall-root() { main() { entware-mount + timezone-mount reinstall-root } diff --git a/package/toltec-bootstrap/package b/package/toltec-bootstrap/package index 4d7814d06..8cfe2ff6b 100644 --- a/package/toltec-bootstrap/package +++ b/package/toltec-bootstrap/package @@ -5,9 +5,9 @@ pkgnames=(toltec-bootstrap) pkgdesc="Toltec installation script" url=https://toltec-dev.org/ -pkgver=0.0.1-3 +pkgver=0.0.2-1 timestamp=2021-01-15T20:15Z -section=utils +section="utils" maintainer="Eeems " license=MIT diff --git a/package/vnsee/package b/package/vnsee/package index c8aeb13a4..9ba6bbfbe 100644 --- a/package/vnsee/package +++ b/package/vnsee/package @@ -1,42 +1,27 @@ #!/usr/bin/env bash -# Copyright (c) 2020 The Toltec Contributors +# Copyright (c) 2021 The Toltec Contributors # SPDX-License-Identifier: MIT pkgnames=(vnsee) pkgdesc="VNC client allowing you to use the device as a second screen" url=https://github.com/matteodelabre/vnsee -pkgver=0.3.1-1 +pkgver=0.3.1-2 timestamp=2021-01-25T12:54:25Z section="screensharing" maintainer="Mattéo Delabre " license=GPL-3.0-only +depends=(libvncclient) +makedepends=(host:libvncserver-dev) -image=base:v1.3 -_vnclib=LibVNCServer-0.9.13 -source=( - "https://github.com/matteodelabre/vnsee/archive/v${pkgver%-*}.zip" - "https://github.com/LibVNC/libvncserver/archive/$_vnclib.zip" -) -noextract=("$_vnclib.zip") -sha256sums=( - f7aa113bd72d6128800501bf53840d127fe6711119f3c8ae60419a35ed2f8583 - d209d70998a9b98f9120eeb82df7a17767796c477eaa8297e0a55856a977c54f -) - -prepare() { - bsdtar -x \ - --strip-components 1 \ - --directory "$srcdir/libvncserver" \ - --file "$srcdir/$_vnclib.zip" -} +image=base:v1.3.2 +source=("https://github.com/matteodelabre/vnsee/archive/3f07c04e1991c1c5ff3b5a4758aa42301bf86fae.zip") +sha256sums=(57cef02f066ba26f0258e476339586f5c7a0f67ad31bf35cb479a362f1eba525) build() { - mkdir build - cd build - cmake .. \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_TOOLCHAIN_FILE="/usr/share/cmake/$CHOST.cmake" - make + cmake -B build \ + -DCMAKE_TOOLCHAIN_FILE="/usr/share/cmake/$CHOST.cmake" \ + -DCMAKE_INSTALL_PREFIX="/opt" + cmake --build build } package() { diff --git a/package/wikipedia/package b/package/wikipedia/package new file mode 100644 index 000000000..d2cf5eb75 --- /dev/null +++ b/package/wikipedia/package @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +# Copyright (c) 2021 The Toltec Contributors +# SPDX-License-Identifier: MIT + +pkgnames=(wikipedia) +pkgdesc="The free encyclopedia" +url=https://github.com/dps/remarkable-wikipedia +pkgver=0.1.0-2 +timestamp=2021-03-11T04:50Z +section="readers" +maintainer="David Singleton " +license=MIT + +image=qt:v1.2.2 +source=( + https://github.com/dps/remarkable-wikipedia/archive/eb00876ef49e7deedc127bc6c1486e3ed13aedcc.zip + wikipedia.draft + wikipedia.png +) +sha256sums=( + 9139cf41c8950126780273977b3d70f8126da8a87066db6c21845d8409ad34b2 + SKIP + SKIP +) + +build() { + sed -i 's/linux-oe-g++/linux-arm-gnueabihf-g++/' qtwikipedia.pro + qmake qtwikipedia.pro + make +} + +package() { + install -D -m 755 "$srcdir"/qtwikipedia "$pkgdir"/opt/bin/qtwikipedia + install -D -m 644 -t "$pkgdir"/opt/etc/draft "$srcdir"/wikipedia.draft + install -D -m 644 -t "$pkgdir"/opt/etc/draft/icons "$srcdir"/wikipedia.png +} diff --git a/package/wikipedia/wikipedia.draft b/package/wikipedia/wikipedia.draft new file mode 100644 index 000000000..c49ffcfb9 --- /dev/null +++ b/package/wikipedia/wikipedia.draft @@ -0,0 +1,8 @@ +# Copyright (c) 2021 The Toltec Contributors +# SPDX-License-Identifier: MIT + +name=wikipedia +desc=The free encyclopedia +call=/opt/bin/qtwikipedia +term=: +imgFile=wikipedia \ No newline at end of file diff --git a/package/wikipedia/wikipedia.png b/package/wikipedia/wikipedia.png new file mode 100644 index 000000000..07746fb2e Binary files /dev/null and b/package/wikipedia/wikipedia.png differ diff --git a/package/wireguard/package b/package/wireguard/package index 3a5dc9eb5..c676c8b31 100755 --- a/package/wireguard/package +++ b/package/wireguard/package @@ -5,13 +5,13 @@ pkgnames=(wireguard) pkgdesc="Fast, modern, secure VPN tunnel" url=https://www.wireguard.com -_wireguardver=1.0.20201221 -pkgver=$_wireguardver-1 -timestamp=2020-12-21T11:54Z +pkgver=1.0.20210219-1 +_wireguardtoolsver=1.0.20210223 +timestamp=2021-02-19T14:08Z section=utils maintainer="Jonah Weissman " -license=GPL-2.0 -depends=(wireguard-tools) +license=GPL-2.0-only +makedepends=(build:bc build:lzop build:git) flags=(nostrip) _kernelrepo=https://github.com/remarkable/linux @@ -23,49 +23,64 @@ _defconfigs=( arch/arm/configs/zero-gravitas_defconfig arch/arm/configs/zero-sugar_defconfig ) -_moddirs=( - 4.9.84-zero-gravitas - 4.14.78 + +image=base:v1.3.2 +source=( + "https://git.zx2c4.com/wireguard-linux-compat/snapshot/wireguard-linux-compat-${pkgver%-*}.tar.xz" + "https://git.zx2c4.com/wireguard-tools/snapshot/wireguard-tools-${_wireguardtoolsver}.tar.xz" +) +noextract=("wireguard-tools-${_wireguardtoolsver}.tar.xz") +sha256sums=( + 99d35296b8d847a0d4db97a4dda96b464311a6354e75fe0bef6e7c4578690f00 + 1f72da217044622d79e0bab57779e136a3df795e3761a3fc1dc0941a9055877c ) -image=base:v1.2.1 -source=(https://git.zx2c4.com/wireguard-linux-compat/snapshot/wireguard-linux-compat-"$_wireguardver".zip) -sha256sums=(c74cedb10c9b830d937b5575e4b7d2625a8534d2c06a80368e5530db23e69d3b) +prepare() { + bsdtar -x \ + --directory "$srcdir" \ + --file "$srcdir/wireguard-tools-${_wireguardtoolsver}.tar.xz" + mv "$srcdir/wireguard-tools-${_wireguardtoolsver}" "$srcdir/wireguard-tools" + # the symlink at src/wg-quick/wg needs something to point to + touch "$srcdir/wireguard-tools/src/wg" +} build() { - apt-get update - apt-get install -y bc lzop git + make -C wireguard-tools/src PLATFORM=linux "CC=${CROSS_COMPILE}cc" - # we will save the generated files in .pc, - # so that they will not be deleted by `make mrproper` - mkdir .pc - git init + mkdir pkg + git init linux for i in $(seq 0 1); do - git fetch --depth=1 "$_kernelrepo" "${_kernelrevs[$i]}" - git checkout -f "${_kernelrevs[$i]}" - touch .scmversion - cp "${_defconfigs[$i]}" .config - echo "CONFIG_NET_FOU=m" >> .config - make olddefconfig - make modules - make modules_prepare + ( + cd linux + git fetch --depth=1 "$_kernelrepo" "${_kernelrevs[$i]}" + git checkout -f "${_kernelrevs[$i]}" + make mrproper + touch .scmversion + cp "${_defconfigs[$i]}" .config + echo "CONFIG_NET_FOU=m" >> .config + make olddefconfig + make net/ipv4/udp_tunnel.ko + make net/ipv6/ip6_udp_tunnel.ko + make modules_prepare + ) - # make wireguard.ko - KERNELDIR=$(pwd) make -C src/ - export MOD_INSTALL_PATH=".pc/${_moddirs[$i]}" - install -D -m 644 net/ipv4/udp_tunnel.ko \ + make -C src/ "KERNELDIR=$(realpath linux)" + KERNELRELEASE=$(cat linux/include/config/kernel.release) + export MOD_INSTALL_PATH="pkg/$KERNELRELEASE" + install -D -m 644 linux/net/ipv4/udp_tunnel.ko \ "$MOD_INSTALL_PATH/kernel/net/ipv4/udp_tunnel.ko" - install -D -m 644 net/ipv6/ip6_udp_tunnel.ko \ + install -D -m 644 linux/net/ipv6/ip6_udp_tunnel.ko \ "$MOD_INSTALL_PATH/kernel/net/ipv6/ip6_udp_tunnel.ko" install -D -m 644 src/wireguard.ko \ "$MOD_INSTALL_PATH/extra/wireguard.ko" - make mrproper done } package() { + make -C "$srcdir/wireguard-tools/src" DESTDIR="$pkgdir" WITH_WGQUICK=yes \ + WITH_SYSTEMDUNITS=yes WITH_BASHCOMPLETION=no install mkdir -p "$pkgdir/lib/modules" - cp -r "$srcdir/.pc"/* "$pkgdir/lib/modules" + cp -r "$srcdir/pkg"/* "$pkgdir/lib/modules" } configure() { diff --git a/package/zoneinfo-utils/package b/package/zoneinfo-utils/package new file mode 100644 index 000000000..0135e119d --- /dev/null +++ b/package/zoneinfo-utils/package @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +# Copyright (c) 2020 The Toltec Contributors +# SPDX-License-Identifier: MIT + +# Inspired by https://github.com/archlinux/svntogit-packages/blob/packages/tzdata/trunk/PKGBUILD + +pkgnames=(zoneinfo-utils) +pkgdesc="Utilities for interacting with zoneinfo files" +url=https://www.iana.org/time-zones +_tzver=2020f +pkgver="$_tzver"-1 +timestamp=2020-05-04T06:16Z +section=utils +maintainer="Eeems " +license="custom: public domain" +depends=(zoneinfo-core) +makedepends=(build:gawk) +image=base:v1.3.2 + +source=( + "https://www.iana.org/time-zones/repository/releases/tzcode${_tzver}.tar.gz" + "https://www.iana.org/time-zones/repository/releases/tzdata${_tzver}.tar.gz" +) +sha256sums=( + cfeeea2a7745164f64bd9f6d76e47916f4ac820c4434493674adbbd4324329c5 + 121131918c3ae6dc5d40f0eb87563a2be920b71a76e2392c09519a5e4a666881 +) + +prepare() { + sed -i "s:sbin:bin:" Makefile +} + +build() { + mkdir .x86 + pushd .x86 > /dev/null + + shopt -s extglob + cp -r ../* . + make VERSION="$_tzver" + + popd > /dev/null + + make VERSION="$_tzver" CC=arm-linux-gnueabihf-cc +} + +package() { + pushd "${srcdir}" > /dev/null + local zic=.x86/zic + # install tzcode stuff + make DESTDIR="${pkgdir}" zic="$zic" install + # install license + install -Dm644 LICENSE "${pkgdir}"/opt/usr/share/licenses/tzdata/LICENSE + + popd > /dev/null + + mv "${pkgdir}"/usr/{lib,share/man} "${pkgdir}"/opt/usr + mv "${pkgdir}"/usr/{s,}bin "${pkgdir}"/opt + + install -D -m 644 -t "$pkgdir"/opt/share/zoneinfo "$srcdir"/iso3166.tab + install -D -m 644 -t "$pkgdir"/opt/share/zoneinfo "$srcdir"/zone1970.tab + + # cleanup + rm -rf "${pkgdir:?}"/{etc,usr} +} diff --git a/package/zshelf/package b/package/zshelf/package index a2359e095..4c4d57ed7 100644 --- a/package/zshelf/package +++ b/package/zshelf/package @@ -23,7 +23,7 @@ sha256sums=( SKIP ) -image=qt:v1.1 +image=qt:v1.3.2 build() { qmake zshelf.pro make diff --git a/scripts/bootstrap/bootstrap b/scripts/bootstrap/bootstrap index 81562a0da..30267186d 100755 --- a/scripts/bootstrap/bootstrap +++ b/scripts/bootstrap/bootstrap @@ -21,8 +21,11 @@ set -eE [[ -z $wget_path ]] && wget_path=/home/root/.local/bin/wget # Path for the systemd unit that mounts Entware to /opt -[[ -z $old_systemd_mount_path ]] && old_systemd_mount_path=/etc/systemd/system/opt.mount -[[ -z $systemd_mount_path ]] && systemd_mount_path=/lib/systemd/system/opt.mount +[[ -z $old_systemd_opt_mount_path ]] && old_systemd_opt_mount_path=/etc/systemd/system/opt.mount +[[ -z $systemd_opt_mount_path ]] && systemd_opt_mount_path=/lib/systemd/system/opt.mount + +# Path for the systemd unit that mounts /opt/share/zoneinfo to /usr/share/zoneinfo +[[ -z $systemd_timezone_mount_path ]] && systemd_timezone_mount_path=/lib/systemd/system/usr-share-zoneinfo.mount # Path where the actual Entware distribution resides (will be mounted to /opt) [[ -z $entware_path ]] && entware_path=/home/root/.entware @@ -65,9 +68,15 @@ error-cleanup() { rm -rf "$entware_path" fi - if [[ -f $systemd_mount_path ]]; then - systemctl disable --now opt.mount 2> /dev/null - rm "$systemd_mount_path" + if [[ -f $systemd_opt_mount_path ]]; then + systemctl disable --now "$(basename "$systemd_opt_mount_path")" 2> /dev/null + rm "$systemd_opt_mount_path" + systemctl daemon-reload + fi + + if [[ -f $systemd_timezone_mount_path ]]; then + systemctl disable --now "$(basename "$systemd_timezone_mount_path")" 2> /dev/null + rm "$systemd_timezone_mount_path" systemctl daemon-reload fi @@ -119,20 +128,20 @@ entware-mount() { mkdir -p /opt mkdir -p "$entware_path" - if [[ -f $old_systemd_mount_path ]] && [[ ! -f $systemd_mount_path ]]; then + if [[ -f $old_systemd_opt_mount_path ]] && [[ ! -f $systemd_opt_mount_path ]]; then # The user was probably using https://github.com/Evidlo/remarkable_entware # before switching to toltec. Removing old .mount file to prevent duplicate files log "Updating opt.mount location" - if systemctl -q is-enabled opt.mount; then + if systemctl -q is-enabled "$(basename "$old_systemd_opt_mount_path")"; then # Remove path to old file if still symlinked - systemctl disable opt.mount + systemctl disable "$(basename "$old_systemd_opt_mount_path")" fi - rm $old_systemd_mount_path + rm "$old_systemd_opt_mount_path" fi # Create systemd mount unit to mount over /opt on reboot - cat > "$systemd_mount_path" << UNIT + cat > "$systemd_opt_mount_path" << UNIT [Unit] Description=Bind mount Entware over /opt DefaultDependencies=no @@ -150,7 +159,7 @@ WantedBy=local-fs.target UNIT systemctl daemon-reload - systemctl enable --now opt.mount 2> /dev/null + systemctl enable --now "$(basename "$systemd_opt_mount_path")" 2> /dev/null } # Install Entware to /opt @@ -223,6 +232,32 @@ entware-install() { fi } +# Mount /opt/share/zoneinfo to /usr/share/zoneinfo +timezone-mount() { + mkdir -p "$entware_path"/share/zoneinfo + + # Create systemd mount unit to mount over /opt on reboot + cat > "$systemd_timezone_mount_path" << UNIT +[Unit] +Description=Bind mount /opt/share/zoneinfo over /usr/share/zoneinfo +DefaultDependencies=no +Conflicts=umount.target +Before=local-fs.target umount.target + +[Mount] +What=/home/root/.entware/share/zoneinfo +Where=/usr/share/zoneinfo +Type=none +Options=bind + +[Install] +WantedBy=local-fs.target +UNIT + + systemctl daemon-reload + systemctl enable --now "$(basename "$systemd_timezone_mount_path")" 2> /dev/null +} + # Add Toltec configuration to an existing Entware install # # Arguments: List of additional packages to install, one package per argument @@ -335,11 +370,14 @@ main() { else log "Re-enabling existing Entware install" entware-mount + timezone-mount fi else log "Creating $entware_path and mounting to /opt" entware-mount entware-install + log "Mounting timezone info over /usr/share/zoneinfo" + timezone-mount fi toltec-install "$@" diff --git a/scripts/package_build.py b/scripts/package_build.py index 2479262c9..10955f3ff 100755 --- a/scripts/package_build.py +++ b/scripts/package_build.py @@ -6,7 +6,9 @@ import argparse import logging import sys +from toltec import paths from toltec.builder import Builder +from toltec.repo import Repo from toltec.util import argparse_add_verbose, LOGGING_FORMAT parser = argparse.ArgumentParser(description=__doc__) @@ -28,9 +30,18 @@ args = parser.parse_args() logging.basicConfig(format=LOGGING_FORMAT, level=args.verbose) -builder = Builder() -if not builder.make( - args.recipe_name, args.packages_names if args.packages_names else None -): +repo = Repo(paths.RECIPE_DIR, paths.REPO_DIR) +builder = Builder(paths.WORK_DIR, paths.REPO_DIR) + +recipe = repo.recipes[args.recipe_name] +packages = ( + [recipe.packages[name] for name in args.packages_names] + if args.packages_names + else None +) + +if not builder.make(recipe, packages): sys.exit(1) + +repo.make_index() diff --git a/scripts/repo_build.py b/scripts/repo_build.py index 24a34ec9b..2696d6d67 100755 --- a/scripts/repo_build.py +++ b/scripts/repo_build.py @@ -5,6 +5,8 @@ import argparse import logging +import os +from toltec import paths from toltec.builder import Builder from toltec.repo import Repo from toltec.util import argparse_add_verbose, LOGGING_FORMAT @@ -12,10 +14,10 @@ parser = argparse.ArgumentParser(description=__doc__) parser.add_argument( - "-n", - "--no-fetch", + "-d", + "--diff", action="store_true", - help="do not fetch missing packages from the remote repository", + help="only keep new packages that do not exist on the remote repository", ) argparse_add_verbose(parser) @@ -44,13 +46,26 @@ remote = args.remote_repo if not args.local else None logging.basicConfig(format=LOGGING_FORMAT, level=args.verbose) -repo = Repo() -builder = Builder() -missing = repo.fetch_packages(remote, fetch_missing=not args.no_fetch) +repo = Repo(paths.RECIPE_DIR, paths.REPO_DIR) +builder = Builder(paths.WORK_DIR, paths.REPO_DIR) +results = repo.fetch_packages(remote) +repo.make_index() + +fetched = results.fetched +missing = results.missing +ordered_missing = repo.order_dependencies(list(missing.keys())) + +for recipe in ordered_missing: + if missing[recipe]: + builder.make(recipe, missing[recipe]) + repo.make_index() -for recipe_name, packages in missing.items(): - if packages: - builder.make(recipe_name, packages) +if args.diff: + for packages in fetched.values(): + for package in packages: + filename = package.filename() + local_path = os.path.join(repo.repo_dir, filename) + os.remove(local_path) repo.make_index() repo.make_listing() diff --git a/scripts/toltec/builder.py b/scripts/toltec/builder.py index df2087029..7e6a9100e 100644 --- a/scripts/toltec/builder.py +++ b/scripts/toltec/builder.py @@ -3,7 +3,16 @@ """Build recipes and create packages.""" import shutil -from typing import Any, Deque, Iterable, MutableMapping, Optional, Tuple +from typing import ( + Any, + Deque, + Dict, + Iterable, + List, + MutableMapping, + Optional, + Tuple, +) from collections import deque import re import os @@ -13,6 +22,7 @@ import requests from . import bash, util, ipk, paths from .recipe import Recipe, Package +from .version import DependencyKind logger = logging.getLogger(__name__) @@ -27,15 +37,16 @@ class BuildContextAdapter(logging.LoggerAdapter): def process( self, msg: str, kwargs: MutableMapping[str, Any] ) -> Tuple[str, MutableMapping[str, Any]]: + prefix = "" + if "recipe" in self.extra: - if "package" in self.extra: - return ( - "%s (%s): %s" - % (self.extra["package"], self.extra["recipe"], msg), - kwargs, - ) + prefix += self.extra["recipe"] + + if "package" in self.extra: + prefix += f" ({self.extra['package']})" - return "%s: %s" % (self.extra["recipe"], msg), kwargs + if prefix: + return f"{prefix}: {msg}", kwargs return msg, kwargs @@ -50,12 +61,20 @@ class Builder: # pylint: disable=too-few-public-methods IMAGE_PREFIX = "ghcr.io/toltec-dev/" # Toltec Docker image used for generic tasks - DEFAULT_IMAGE = "base:v1.2.2" + DEFAULT_IMAGE = "toolchain:v1.3.1" - def __init__(self) -> None: - """Create a builder helper.""" - os.makedirs(paths.WORK_DIR, exist_ok=True) - os.makedirs(paths.REPO_DIR, exist_ok=True) + def __init__(self, work_dir: str, repo_dir: str) -> None: + """ + Create a builder helper. + + :param work_dir: directory where packages are built + :param repo_dir: directory where built packages are stored + """ + self.work_dir = work_dir + os.makedirs(work_dir, exist_ok=True) + + self.repo_dir = repo_dir + os.makedirs(repo_dir, exist_ok=True) self.install_lib = "" install_lib_path = os.path.join(paths.SCRIPTS_DIR, "install-lib") @@ -65,6 +84,9 @@ def __init__(self) -> None: if not line.strip().startswith("#"): self.install_lib += line + self.context: Dict[str, str] = {} + self.adapter = BuildContextAdapter(logger, self.context) + try: self.docker = docker.from_env() except docker.errors.DockerException as err: @@ -75,46 +97,26 @@ def __init__(self) -> None: ) from err def make( - self, recipe_name: str, packages_names: Optional[Iterable[str]] = None + self, recipe: Recipe, packages: Optional[Iterable[Package]] = None ) -> bool: """ Build a recipe and create its associated packages. - :param recipe_name: name of the recipe to make - :param packages_names: list of packages names of the recipe to make + :param recipe: recipe to make + :param packages: list of packages of the recipe to make (default: all of them) :returns: true if all packages were built correctly """ - recipe_dir = os.path.join(paths.RECIPE_DIR, recipe_name) - build_dir = os.path.join(paths.WORK_DIR, recipe_name) - recipe = Recipe.from_file(recipe_dir) - - context = {"recipe": recipe.name} - adapter = BuildContextAdapter(logger, context) - - try: - os.mkdir(build_dir) - except FileExistsError: - build_dir_rel = os.path.relpath(build_dir) - ans = util.query_user( - f"The build directory '{build_dir_rel}' for recipe \ -'{recipe.name}' already exists.\nWould you like to [c]ancel, [r]emove that \ -directory, or [k]eep it (not recommended)?", - default="c", - options=["c", "r", "k"], - aliases={ - "cancel": "c", - "remove": "r", - "keep": "k", - }, - ) - - if ans == "c": - return False - - if ans == "r": - shutil.rmtree(build_dir) - os.mkdir(build_dir) + self.context["recipe"] = recipe.name + build_dir = os.path.join(self.work_dir, recipe.name) + + if not util.check_directory( + build_dir, + f"The build directory '{os.path.relpath(build_dir)}' for recipe \ +'{recipe.name}' already exists.\nWould you like to [c]ancel, [r]emove \ +that directory, or [k]eep it (not recommended)?", + ): + return False src_dir = os.path.join(build_dir, "src") os.makedirs(src_dir, exist_ok=True) @@ -122,43 +124,32 @@ def make( base_pkg_dir = os.path.join(build_dir, "pkg") os.makedirs(base_pkg_dir, exist_ok=True) - self._fetch_source(adapter, recipe, recipe_dir, src_dir) - self._prepare(adapter, recipe, src_dir) - self._build(adapter, recipe, src_dir) - self._strip(adapter, recipe, src_dir) + self._fetch_source(recipe, src_dir) + self._prepare(recipe, src_dir) + self._build(recipe, src_dir) + self._strip(recipe, src_dir) - for package_name in ( - packages_names - if packages_names is not None - else recipe.packages.keys() + for package in ( + packages if packages is not None else recipe.packages.values() ): - if package_name not in recipe.packages: - raise BuildError( - f"Package '{package_name}' does not exist in \ -recipe '{recipe.name}'" - ) - - assert package_name is not None - package = recipe.packages[package_name] - context["package"] = package_name + self.context["package"] = package.name - pkg_dir = os.path.join(base_pkg_dir, package_name) + pkg_dir = os.path.join(base_pkg_dir, package.name) os.makedirs(pkg_dir, exist_ok=True) - self._package(adapter, package, src_dir, pkg_dir) - self._archive(adapter, package, pkg_dir) + self._package(package, src_dir, pkg_dir) + self._archive(package, pkg_dir) + del self.context["package"] return True def _fetch_source( self, - adapter: BuildContextAdapter, recipe: Recipe, - recipe_dir: str, src_dir: str, ) -> None: """Fetch and extract all source files required to build a recipe.""" - adapter.info("Fetching source files") + self.adapter.info("Fetching source files") for source in recipe.sources: filename = os.path.basename(source.url) @@ -166,7 +157,7 @@ def _fetch_source( if self.URL_REGEX.match(source.url) is None: # Get source file from the recipe’s directory - shutil.copy2(os.path.join(recipe_dir, source.url), local_path) + shutil.copy2(os.path.join(recipe.path, source.url), local_path) else: # Fetch source file from the network req = requests.get(source.url) @@ -193,22 +184,20 @@ def _fetch_source( # Automatically extract source archives if not source.noextract: if not util.auto_extract(local_path, src_dir): - adapter.debug( + self.adapter.debug( "Not extracting %s (unsupported archive type)", local_path, ) - def _prepare( - self, adapter: BuildContextAdapter, recipe: Recipe, src_dir: str - ) -> None: + def _prepare(self, recipe: Recipe, src_dir: str) -> None: """Prepare source files before building.""" script = recipe.functions["prepare"] if not script: - adapter.info("Skipping prepare (nothing to do)") + self.adapter.debug("Skipping prepare (nothing to do)") return - adapter.info("Preparing source files") + self.adapter.info("Preparing source files") logs = bash.run_script( script=script, variables={ @@ -218,19 +207,17 @@ def _prepare( }, ) - self._print_logs(logs, adapter, "prepare()") + self._print_logs(logs, "prepare()") - def _build( - self, adapter: BuildContextAdapter, recipe: Recipe, src_dir: str - ) -> None: + def _build(self, recipe: Recipe, src_dir: str) -> None: """Build artifacts for a recipe.""" script = recipe.functions["build"] if not script: - adapter.info("Skipping build (nothing to do)") + self.adapter.debug("Skipping build (nothing to do)") return - adapter.info("Building artifacts") + self.adapter.info("Building artifacts") # Set fixed atime and mtime for all the source files epoch = int(recipe.timestamp.timestamp()) @@ -239,7 +226,41 @@ def _build( os.utime(filename, (epoch, epoch)) mount_src = "/src" + repo_src = "/repo" uid = os.getuid() + pre_script: List[str] = [] + + # Install required dependencies + build_deps = [] + host_deps = [] + + for dep in recipe.makedepends: + if dep.kind == DependencyKind.Build: + build_deps.append(dep.package) + elif dep.kind == DependencyKind.Host: + host_deps.append(dep.package) + + if build_deps: + pre_script.extend( + ( + "export DEBIAN_FRONTEND=noninteractive", + "apt-get update -qq", + "apt-get install -qq --no-install-recommends" + ' -o Dpkg::Options::="--force-confdef"' + ' -o Dpkg::Options::="--force-confold"' + " -- " + " ".join(build_deps), + ) + ) + + if host_deps: + pre_script.extend( + ( + "opkg update --verbosity=0 --offline-root $SYSROOT", + "opkg install --verbosity=0 --no-install-recommends" + " --offline-root $SYSROOT" + " -- " + " ".join(host_deps), + ) + ) logs = bash.run_script_in_container( self.docker, @@ -249,7 +270,12 @@ def _build( type="bind", source=os.path.abspath(src_dir), target=mount_src, - ) + ), + docker.types.Mount( + type="bind", + source=os.path.abspath(self.repo_dir), + target=repo_src, + ), ], variables={ **recipe.variables, @@ -258,6 +284,7 @@ def _build( }, script="\n".join( ( + *pre_script, f'cd "{mount_src}"', script, f'chown -R {uid}:{uid} "{mount_src}"', @@ -265,17 +292,15 @@ def _build( ), ) - self._print_logs(logs, adapter, "build()") + self._print_logs(logs, "build()") - def _strip( - self, adapter: BuildContextAdapter, recipe: Recipe, src_dir: str - ) -> None: + def _strip(self, recipe: Recipe, src_dir: str) -> None: """Strip all debugging symbols from binaries.""" if "nostrip" in recipe.flags: - adapter.info("Not stripping binaries (nostrip flag set)") + self.adapter.debug("Not stripping binaries (nostrip flag set)") return - adapter.info("Stripping binaries") + self.adapter.info("Stripping binaries") mount_src = "/src" logs = bash.run_script_in_container( @@ -301,17 +326,11 @@ def _strip( ), ) - self._print_logs(logs, adapter) + self._print_logs(logs) - def _package( - self, - adapter: BuildContextAdapter, - package: Package, - src_dir: str, - pkg_dir: str, - ) -> None: + def _package(self, package: Package, src_dir: str, pkg_dir: str) -> None: """Make a package from a recipe’s build artifacts.""" - adapter.info("Packaging build artifacts") + self.adapter.info("Packaging build artifacts") logs = bash.run_script( script=package.functions["package"], variables={ @@ -322,23 +341,20 @@ def _package( }, ) - self._print_logs(logs, adapter, "package()") - - adapter.debug("Resulting tree:") + self._print_logs(logs, "package()") + self.adapter.debug("Resulting tree:") for filename in util.list_tree(pkg_dir): - adapter.debug( + self.adapter.debug( " - %s", os.path.normpath( os.path.join("/", os.path.relpath(filename, pkg_dir)) ), ) - def _archive( - self, adapter: BuildContextAdapter, package: Package, pkg_dir: str - ) -> None: + def _archive(self, package: Package, pkg_dir: str) -> None: """Create an archive for a package.""" - adapter.info("Creating archive") + self.adapter.info("Creating archive") ar_path = os.path.join(paths.REPO_DIR, package.filename()) # Inject Oxide-specific hook for reloading apps @@ -425,13 +441,13 @@ def _archive( scripts[step + "rm"] = script - adapter.debug("Install scripts:") + self.adapter.debug("Install scripts:") if scripts: for script in sorted(scripts): - adapter.debug(" - %s", script) + self.adapter.debug(" - %s", script) else: - adapter.debug("(none)") + self.adapter.debug("(none)") epoch = int(package.parent.timestamp.timestamp()) @@ -447,10 +463,9 @@ def _archive( # Set fixed atime and mtime for the resulting archive os.utime(ar_path, (epoch, epoch)) - @staticmethod def _print_logs( + self, logs: bash.LogGenerator, - adapter: BuildContextAdapter, function_name: str = None, max_lines_on_fail: int = 50, ) -> None: @@ -459,7 +474,6 @@ def _print_logs( if a ScriptError is caught. :param logs: generator of log lines - :param adapter: logging output :param function_name: calling function name :param max_lines_on_fail: number of context lines to print in non-debug mode @@ -467,22 +481,22 @@ def _print_logs( log_buffer: Deque[str] = deque() try: for line in logs: - if adapter.getEffectiveLevel() <= logging.DEBUG: - adapter.debug(line) + if self.adapter.getEffectiveLevel() <= logging.DEBUG: + self.adapter.debug(line) else: if len(log_buffer) == max_lines_on_fail: log_buffer.popleft() log_buffer.append(line) except bash.ScriptError as err: if len(log_buffer) > 0: - adapter.info( + self.adapter.info( f"Only showing up to {max_lines_on_fail} lines of context. " + "Use --verbose for the full output." ) for line in log_buffer: - adapter.error(line) + self.adapter.error(line) if function_name: - adapter.error(f"{function_name} failed") + self.adapter.error(f"{function_name} failed") raise err diff --git a/scripts/toltec/graphlib.py b/scripts/toltec/graphlib.py new file mode 100644 index 000000000..2a303928c --- /dev/null +++ b/scripts/toltec/graphlib.py @@ -0,0 +1,271 @@ +# Copied from Python 3.9’s graphlib.py module to enable using the +# topological sorting algorithm on earlier Python versions +# SPDX-License-Identifier: PSF-2.0 +"""Functionality to operate with graph-like structures""" + +from typing import ( + Generic, + List, + Iterable, + MutableMapping, + Mapping, + Optional, + Tuple, + TypeVar, +) + +__all__ = ["TopologicalSorter", "CycleError"] + +_T = TypeVar("_T") +_NODE_OUT = -1 +_NODE_DONE = -2 + + +class _NodeInfo(Generic[_T]): # pylint: disable=too-few-public-methods + __slots__ = "node", "npredecessors", "successors" + + def __init__(self, node: _T): + # The node this class is augmenting. + self.node = node + + # Number of predecessors, generally >= 0. When this value falls to + # 0, and is returned by get_ready(), this is set to _NODE_OUT and + # when the node is marked done by a call to done(), set to + # _NODE_DONE. + self.npredecessors = 0 + + # List of successor nodes. The list can contain duplicated elements + # as long as they're all reflected in the successor's npredecessors + # attribute). + self.successors: List[_T] = [] + + +class CycleError(ValueError): + """Subclass of ValueError raised by TopologicalSorter.prepare if cycles + exist in the working graph. + + If multiple cycles exist, only one undefined choice among them will be + reported and included in the exception. The detected cycle can be + accessed via the second element in the *args* attribute of the + exception instance and consists in a list of nodes, such that each node + is, in the graph, an immediate predecessor of the next node in the + list. In the reported list, the first and the last node will be the + same, to make it clear that it is cyclic. + """ + + +class TopologicalSorter(Generic[_T]): + """Provides functionality to topologically sort a graph of hashable + nodes""" + + def __init__(self, graph: Optional[Mapping[_T, Iterable[_T]]] = None): + self._node2info: MutableMapping[_T, _NodeInfo[_T]] = {} + self._ready_nodes: Optional[List[_T]] = None + self._npassedout = 0 + self._nfinished = 0 + + if graph is not None: + for node, predecessors in graph.items(): + self.add(node, *predecessors) + + def _get_nodeinfo(self, node: _T) -> _NodeInfo[_T]: + if (result := self._node2info.get(node)) is None: + self._node2info[node] = result = _NodeInfo(node) + return result + + def add(self, node: _T, *predecessors: _T) -> None: + """Add a new node and its predecessors to the graph. + + Both the *node* and all elements in *predecessors* must be + hashable. + + If called multiple times with the same node argument, the set of + dependencies will be the union of all dependencies passed in. + + It is possible to add a node with no dependencies (*predecessors* + is not provided) as well as provide a dependency twice. If a node + that has not been provided before is included among *predecessors* + it will be automatically added to the graph with no predecessors of + its own. + + Raises ValueError if called after "prepare". + """ + if self._ready_nodes is not None: + raise ValueError("Nodes cannot be added after a call to prepare()") + + # Create the node -> predecessor edges + nodeinfo = self._get_nodeinfo(node) + nodeinfo.npredecessors += len(predecessors) + + # Create the predecessor -> node edges + for pred in predecessors: + pred_info = self._get_nodeinfo(pred) + pred_info.successors.append(node) + + def prepare(self) -> None: + """Mark the graph as finished and check for cycles in the graph. + + If any cycle is detected, "CycleError" will be raised, but + "get_ready" can still be used to obtain as many nodes as possible + until cycles block more progress. After a call to this function, + the graph cannot be modified and therefore no more nodes can be + added using "add". + """ + if self._ready_nodes is not None: + raise ValueError("cannot prepare() more than once") + + self._ready_nodes = [ + i.node for i in self._node2info.values() if i.npredecessors == 0 + ] + # ready_nodes is set before we look for cycles on purpose: + # if the user wants to catch the CycleError, that's fine, + # they can continue using the instance to grab as many + # nodes as possible before cycles block more progress + cycle = self._find_cycle() + if cycle: + raise CycleError("nodes are in a cycle", cycle) + + def get_ready(self) -> Tuple[_T, ...]: + """Return a tuple of all the nodes that are ready. + + Initially it returns all nodes with no predecessors; once those are + marked as processed by calling "done", further calls will return + all new nodes that have all their predecessors already processed. + Once no more progress can be made, empty tuples are returned. + + Raises ValueError if called without calling "prepare" previously. + """ + if self._ready_nodes is None: + raise ValueError("prepare() must be called first") + + # Get the nodes that are ready and mark them + result = tuple(self._ready_nodes) + n2i = self._node2info + for node in result: + n2i[node].npredecessors = _NODE_OUT + + # Clean the list of nodes that are ready and update + # the counter of nodes that we have returned. + self._ready_nodes.clear() + self._npassedout += len(result) + + return result + + def is_active(self) -> bool: + """Return ``True`` if more progress can be made and ``False`` + otherwise. + + Progress can be made if cycles do not block the resolution and + either there are still nodes ready that haven't yet been returned + by "get_ready" or the number of nodes marked "done" is less than + the number that have been returned by "get_ready". + + Raises ValueError if called without calling "prepare" previously. + """ + if self._ready_nodes is None: + raise ValueError("prepare() must be called first") + return self._nfinished < self._npassedout or bool(self._ready_nodes) + + def __bool__(self) -> bool: + return self.is_active() + + def done(self, *nodes: _T) -> None: + """Marks a set of nodes returned by "get_ready" as processed. + + This method unblocks any successor of each node in *nodes* for + being returned in the future by a call to "get_ready". + + Raises :exec:`ValueError` if any node in *nodes* has already been + marked as processed by a previous call to this method, if a node + was not added to the graph by using "add" or if called without + calling "prepare" previously or if node has not yet been returned + by "get_ready". + """ + + if self._ready_nodes is None: + raise ValueError("prepare() must be called first") + + n2i = self._node2info + + for node in nodes: + # Check if we know about this node (it was added previously + # using add() + if (nodeinfo := n2i.get(node)) is None: + raise ValueError(f"node {node!r} was not added using add()") + + # If the node has not being returned (marked as ready) + # previously, inform the user. + stat = nodeinfo.npredecessors + if stat != _NODE_OUT: + if stat >= 0: + raise ValueError( + f"node {node!r} was not passed out (still not ready)" + ) + if stat == _NODE_DONE: + raise ValueError(f"node {node!r} was already marked done") + assert False, f"node {node!r}: unknown status {stat}" + + # Mark the node as processed + nodeinfo.npredecessors = _NODE_DONE + + # Go to all the successors and reduce the number of + # predecessors, collecting all the ones that are ready to be + # returned in the next get_ready() call. + for successor in nodeinfo.successors: + successor_info = n2i[successor] + successor_info.npredecessors -= 1 + if successor_info.npredecessors == 0: + self._ready_nodes.append(successor) + self._nfinished += 1 + + def _find_cycle(self) -> Optional[List[_T]]: + n2i = self._node2info + stack: List[_T] = [] + itstack = [] + seen = set() + node2stacki: MutableMapping[_T, int] = {} + + for node in n2i: + if node in seen: + continue + + while True: + if node in seen: + # If we have seen already the node and is in the + # current stack we have found a cycle. + if node in node2stacki: + return stack[node2stacki[node] :] + [node] + # else go on to get next successor + else: + seen.add(node) + itstack.append(iter(n2i[node].successors).__next__) + node2stacki[node] = len(stack) + stack.append(node) + + # Backtrack to the topmost stack entry with + # at least another successor. + while stack: + try: + node = itstack[-1]() + break + except StopIteration: + del node2stacki[stack.pop()] + itstack.pop() + else: + break + return None + + def static_order(self) -> Iterable[_T]: + """Returns an iterable of nodes in a topological order. + + The particular order that is returned may depend on the specific + order in which the items were inserted in the graph. + + Using this method does not require to call "prepare" or "done". If + any cycle is detected, :exc:`CycleError` will be raised. + """ + self.prepare() + while self.is_active(): + node_group = self.get_ready() + yield from node_group + self.done(*node_group) diff --git a/scripts/toltec/recipe.py b/scripts/toltec/recipe.py index 888ecdca9..b8ab5a195 100644 --- a/scripts/toltec/recipe.py +++ b/scripts/toltec/recipe.py @@ -14,7 +14,8 @@ import os import textwrap import dateutil.parser -from . import bash, version +from .version import Version, Dependency, DependencyKind +from . import bash class RecipeError(Exception): @@ -33,15 +34,29 @@ class Source: class Recipe: # pylint:disable=too-many-instance-attributes,disable=too-few-public-methods """Load recipes.""" - def __init__(self, name: str, definition: str): + @staticmethod + def from_file(path: str) -> "Recipe": + """ + Load a recipe from its directory. + + :param path: path to the directory containing the recipe definition + :returns: loaded recipe + """ + name = os.path.basename(path) + with open(os.path.join(path, "package"), "r") as recipe: + return Recipe(name, path, recipe.read()) + + def __init__(self, name: str, path: str, definition: str): """ Load a recipe from a Bash source. :param name: name of the recipe + :param path: path to the directory containing the recipe definition :param definition: source string of the recipe :raises RecipeError: if the recipe contains an error """ self.name = name + self.path = path variables, functions = bash.get_declarations(definition) # Original declarations of standard fields and functions @@ -64,8 +79,7 @@ def _load_fields(self, variables: bash.Variables) -> None: self.timestamp = dateutil.parser.isoparse(timestamp_str) except ValueError as err: raise RecipeError( - "Field 'timestamp' does not contain a \ -valid ISO-8601 date" + "Field 'timestamp' does not contain a valid ISO-8601 date" ) from err self.maintainer = _pop_field_string(variables, "maintainer") @@ -88,11 +102,20 @@ def _load_fields(self, variables: bash.Variables) -> None: if len(sources) != len(sha256sums): raise RecipeError( - f"Expected the same number of sources \ -and checksums, got {len(sources)} source(s) and \ -{len(sha256sums)} checksum(s)" + f"Expected the same number of sources and checksums, got \ +{len(sources)} source(s) and {len(sha256sums)} checksum(s)" ) + depends_raw = _pop_field_indexed(variables, "depends", []) + variables["depends"] = depends_raw + + makedepends_raw = _pop_field_indexed(variables, "makedepends", []) + self.variables["makedepends"] = makedepends_raw + + self.makedepends = [ + Dependency.parse(dep or "") for dep in depends_raw + makedepends_raw + ] + self.sources = [] for source, checksum in zip(sources, sha256sums): @@ -108,14 +131,14 @@ def _load_functions(self, functions: bash.Functions) -> None: """Parse and check standard functions.""" if self.image and "build" not in functions: raise RecipeError( - "Missing build() function for a recipe \ -which declares a build image" + "Missing build() function for a recipe which declares a \ +build image" ) if not self.image and "build" in functions: raise RecipeError( - "Missing image declaration for a recipe \ -which has a build() step" + "Missing image declaration for a recipe which has a \ +build() step" ) self.functions["prepare"] = functions.pop("prepare", "") @@ -141,8 +164,8 @@ def _load_packages( for pkg_name in pkgnames: if pkg_name not in functions: raise RecipeError( - "Missing required function \ -{pkg_name}() for corresponding package" + "Missing required function {pkg_name}() for \ +corresponding package" ) pkg_def = functions.pop(pkg_name) @@ -161,13 +184,6 @@ def _load_packages( for pkg_name, (pkg_vars, pkg_funcs) in pkg_decls.items(): self.packages[pkg_name] = Package(self, pkg_vars, pkg_funcs) - @staticmethod - def from_file(path: str) -> "Recipe": - """Load a recipe from a file.""" - name = os.path.basename(path) - with open(os.path.join(path, "package"), "r") as recipe: - return Recipe(name, recipe.read()) - class Package: # pylint:disable=too-many-instance-attributes """Load packages.""" @@ -203,7 +219,7 @@ def _load_fields(self, variables: bash.Variables) -> None: pkgver_str = _pop_field_string(variables, "pkgver") self.variables["pkgver"] = pkgver_str - self.version = version.Version.parse(pkgver_str) + self.version = Version.parse(pkgver_str) self.arch = _pop_field_string(variables, "arch", "armv7-3.2") self.variables["arch"] = self.arch @@ -220,18 +236,39 @@ def _load_fields(self, variables: bash.Variables) -> None: self.license = _pop_field_string(variables, "license") self.variables["license"] = self.license - self.depends = _pop_field_indexed(variables, "depends", []) - self.variables["depends"] = self.depends + depends_raw = _pop_field_indexed(variables, "depends", []) + self.variables["depends"] = depends_raw + self.depends = [] + + for dep_raw in depends_raw: + dep = Dependency.parse(dep_raw or "") + + if dep.kind != DependencyKind.Host: + raise RecipeError( + "Only host packages are supported in the 'depends' field" + ) + + self.depends.append(dep) + + conflicts_raw = _pop_field_indexed(variables, "conflicts", []) + self.variables["conflicts"] = conflicts_raw + self.conflicts = [] + + for conflict_raw in conflicts_raw: + conflict = Dependency.parse(conflict_raw or "") + + if dep.kind != DependencyKind.Host: + raise RecipeError( + "Only host packages are supported in the 'conflicts' field" + ) - self.conflicts = _pop_field_indexed(variables, "conflicts", []) - self.variables["conflicts"] = self.conflicts + self.conflicts.append(conflict) def _load_functions(self, functions: bash.Functions) -> None: """Parse and check standard functions.""" if "package" not in functions: raise RecipeError( - f"Missing required function package() \ -for package {self.name}" + f"Missing required function package() for package {self.name}" ) self.functions["package"] = functions.pop("package") @@ -291,14 +328,14 @@ def control_fields(self) -> str: if self.depends: control += ( "Depends: " - + ", ".join(item for item in self.depends if item) + + ", ".join(dep.to_debian() for dep in self.depends if dep) + "\n" ) if self.conflicts: control += ( "Conflicts: " - + ", ".join(item for item in self.conflicts if item) + + ", ".join(dep.to_debian() for dep in self.conflicts if dep) + "\n" ) diff --git a/scripts/toltec/repo.py b/scripts/toltec/repo.py index ecc0a9418..2e585be4b 100644 --- a/scripts/toltec/repo.py +++ b/scripts/toltec/repo.py @@ -4,100 +4,145 @@ Build the package repository. """ +from collections import namedtuple from datetime import datetime import gzip import itertools import logging import os -from typing import Dict, List, Optional +from typing import Dict, Iterable, List, Optional import requests -from .recipe import Recipe +from .graphlib import TopologicalSorter +from .recipe import Package, Recipe from .util import file_sha256, HTTP_DATE_FORMAT -from . import paths, templating +from .version import DependencyKind +from . import templating logger = logging.getLogger(__name__) +GroupedPackages = Dict[Recipe, List[Package]] +FetchedMissing = namedtuple("FetchedMissing", ["fetched", "missing"]) class Repo: """Repository of Toltec packages.""" - def __init__(self) -> None: - """Initialize the package repository.""" + def __init__(self, recipe_dir: str, repo_dir: str) -> None: + """ + Initialize the package repository. + + :param recipe_dir: directory where recipe definitions are stored + :param repo_dir: directory where built packages are stored + """ + self.recipe_dir = recipe_dir + self.repo_dir = repo_dir self.recipes = {} - for name in os.listdir(paths.RECIPE_DIR): + for name in os.listdir(self.recipe_dir): if name[0] != ".": self.recipes[name] = Recipe.from_file( - os.path.join(paths.RECIPE_DIR, name) + os.path.join(self.recipe_dir, name) ) - def fetch_packages( - self, remote: Optional[str], fetch_missing: bool - ) -> Dict[str, List[str]]: + def fetch_packages(self, remote: Optional[str]) -> FetchedMissing: """ - Fetch missing packages. + Fetch locally missing packages from a remote server and report which + packages are missing from the remote and need to be built locally. + + If `remote` is None, no packages are fetched from the network and all + the packages that are not in the local repo will be considered missing. :param remote: remote server from which to check for existing packages - :param fetch_missing: pass true to fetch missing packages from remote - :returns: missing packages grouped by parent recipe + :returns: tuple containing fetched and missing packages grouped by + their parent recipe """ logger.info("Scanning for missing packages") - missing: Dict[str, List[str]] = {} + fetched: GroupedPackages = {} + missing: GroupedPackages = {} for recipe in self.recipes.values(): - missing[recipe.name] = [] + fetched[recipe] = [] + missing[recipe] = [] for package in recipe.packages.values(): filename = package.filename() - local_path = os.path.join(paths.REPO_DIR, filename) + local_path = os.path.join(self.repo_dir, filename) if os.path.isfile(local_path): continue if remote is not None: remote_path = os.path.join(remote, filename) + req = requests.get(remote_path) - if fetch_missing: - req = requests.get(remote_path) + if req.status_code == 200: + with open(local_path, "wb") as local: + for chunk in req.iter_content(chunk_size=1024): + local.write(chunk) - if req.status_code == 200: - with open(local_path, "wb") as local: - for chunk in req.iter_content(chunk_size=1024): - local.write(chunk) + last_modified = int( + datetime.strptime( + req.headers["Last-Modified"], + HTTP_DATE_FORMAT, + ).timestamp() + ) - last_modified = int( - datetime.strptime( - req.headers["Last-Modified"], - HTTP_DATE_FORMAT, - ).timestamp() - ) - - os.utime(local_path, (last_modified, last_modified)) - continue - else: - req = requests.head(remote_path) - if req.status_code == 200: - continue + os.utime(local_path, (last_modified, last_modified)) + fetched[recipe].append(package) + continue logger.info( "Package %s (%s) is missing", package.pkgid(), recipe.name ) - missing[recipe.name].append(package.name) + missing[recipe].append(package) + + return FetchedMissing(fetched=fetched, missing=missing) + + @staticmethod + def order_dependencies(recipes: List[Recipe]) -> Iterable[Recipe]: + """ + Order a list of recipes so that all recipes that a recipe needs + come before that recipe in the list. + + :param recipes: list of recipes to order + :returns: ordered list of recipes + :raises graphlib.CycleError: if a circular dependency exists + """ + # See + toposort: TopologicalSorter[ # pylint:disable=unsubscriptable-object + Recipe + ] = TopologicalSorter() + parent_recipes = {} + + for recipe in recipes: + for package in recipe.packages.values(): + parent_recipes[package.name] = recipe + + for recipe in recipes: + deps = [] + + for dep in recipe.makedepends: + if ( + dep.kind == DependencyKind.Host + and dep.package in parent_recipes + ): + deps.append(parent_recipes[dep.package]) + + toposort.add(recipe, *deps) - return missing + return toposort.static_order() def make_index(self) -> None: """Generate index files for all the packages in the repo.""" logger.info("Generating package index") - index_path = os.path.join(paths.REPO_DIR, "Packages") - index_gzip_path = os.path.join(paths.REPO_DIR, "Packages.gz") + index_path = os.path.join(self.repo_dir, "Packages") + index_gzip_path = os.path.join(self.repo_dir, "Packages.gz") with open(index_path, "w") as index_file: with gzip.open(index_gzip_path, "wt") as index_gzip_file: for recipe in self.recipes.values(): for package in recipe.packages.values(): filename = package.filename() - local_path = os.path.join(paths.REPO_DIR, filename) + local_path = os.path.join(self.repo_dir, filename) if not os.path.isfile(local_path): continue @@ -129,7 +174,7 @@ def make_listing(self) -> None: ) ) - listing_path = os.path.join(paths.REPO_DIR, "index.html") + listing_path = os.path.join(self.repo_dir, "index.html") template = templating.env.get_template("listing.html") with open(listing_path, "w") as listing_file: diff --git a/scripts/toltec/templates/listing.html b/scripts/toltec/templates/listing.html index 4f9673318..eca4eb499 100644 --- a/scripts/toltec/templates/listing.html +++ b/scripts/toltec/templates/listing.html @@ -7,7 +7,7 @@