From 89399dc6b49e500523b00364cd9e0102634f61b5 Mon Sep 17 00:00:00 2001 From: "Michal S." Date: Thu, 30 Jul 2026 22:39:06 +0100 Subject: [PATCH] Adopt BSD make ports framework --- Makefile | 7 ++ README.md | 27 ++++-- infrastructure/README.md | 8 +- ports/Makefile | 9 ++ ports/Mk/Scripts/check-plist.sh | 56 ++++++++++++ ports/Mk/Scripts/checksum.sh | 34 +++++++ ports/Mk/Scripts/fetch.sh | 24 +++++ ports/Mk/Scripts/install-depends.sh | 37 ++++++++ ports/Mk/Scripts/package.sh | 68 ++++++++++++++ ports/Mk/mouse.port.mk | 134 ++++++++++++++++++++++++++++ ports/README.md | 11 ++- tests/check-layout.sh | 41 ++++----- tests/framework.mk | 13 +++ 13 files changed, 433 insertions(+), 36 deletions(-) create mode 100644 ports/Makefile create mode 100755 ports/Mk/Scripts/check-plist.sh create mode 100755 ports/Mk/Scripts/checksum.sh create mode 100755 ports/Mk/Scripts/fetch.sh create mode 100755 ports/Mk/Scripts/install-depends.sh create mode 100755 ports/Mk/Scripts/package.sh create mode 100644 ports/Mk/mouse.port.mk create mode 100644 tests/framework.mk diff --git a/Makefile b/Makefile index 35fe6ca..e96bcf7 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,11 @@ +BMAKE ?= bmake + .PHONY: check check: ./tests/check-layout.sh + @if command -v "$(BMAKE)" >/dev/null 2>&1; then \ + "$(BMAKE)" -f tests/framework.mk check; \ + else \ + printf '%s\n' "bmake parse check skipped: bmake is unavailable"; \ + fi diff --git a/README.md b/README.md index 6805a55..20baa5c 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ # MOUSE ports This repository is the independently versioned source-port collection for -MOUSE. It contains third-party recipes and patches only; the `mouse-port` -implementation, ports contract, base-system manifest, and release integration -belong to `mouse-src`. +MOUSE. Each port is a BSD make project, while shared phase and quality-control +machinery lives under `ports/Mk`. -Each port lives below `ports/NAME` and follows the recipe contract shipped by -the compatible MOUSE base release. A MOUSE release pins one tested commit from -this repository, while installed systems may move to later compatible ports -revisions without replacing the base system. +Each port lives below `ports/category/name` and contains a `Makefile`, +`distinfo`, and `pkg-plist`, plus optional patches and service definitions +under `files`. A MOUSE release pins one tested commit from this repository, +while installed systems may move to later compatible ports revisions without +replacing the base system. The adversarial and demonstration recipes used by the base-system acceptance suite are deliberately not part of this collection. They live in @@ -19,3 +19,16 @@ Run the collection checks with: ```sh make check ``` + +On MOUSE, a port's public interface is: + +```sh +make fetch +make checksum +make build +make stage +make package +make install +make deinstall +make clean +``` diff --git a/infrastructure/README.md b/infrastructure/README.md index 8097859..fdeab37 100644 --- a/infrastructure/README.md +++ b/infrastructure/README.md @@ -1,4 +1,8 @@ # Infrastructure -Collection-wide generation, lint, and publication tooling belongs here. Base -image assembly and the `mouse-port` implementation remain in `mouse-src`. +Collection-wide generation, lint, and publication tooling belongs here. The +live framework is `ports/Mk/mouse.port.mk`, with narrow shell helpers in +`ports/Mk/Scripts`. + +Base-manifest enforcement and transactional package registration remain a +private base-system responsibility in `mouse-src`. diff --git a/ports/Makefile b/ports/Makefile new file mode 100644 index 0000000..24e6418 --- /dev/null +++ b/ports/Makefile @@ -0,0 +1,9 @@ +.PHONY: identity list + +identity: + @. /etc/mouse-release; \ + revision=$$(cat /usr/share/mouse/ports.revision); \ + printf '%s\n' "$$PRETTY_NAME" "ports $$revision" + +list: + @/usr/libexec/mouse-package list diff --git a/ports/Mk/Scripts/check-plist.sh b/ports/Mk/Scripts/check-plist.sh new file mode 100755 index 0000000..fde6f15 --- /dev/null +++ b/ports/Mk/Scripts/check-plist.sh @@ -0,0 +1,56 @@ +#!/bin/sh +set -eu + +if [ "$#" -ne 3 ]; then + printf '%s\n' "usage: check-plist.sh STAGEDIR PREFIX PLIST" >&2 + exit 2 +fi + +stage=$1 +prefix=$2 +plist=$3 +[ -f "$plist" ] || { + printf '%s\n' "missing packing list: $plist" >&2 + exit 1 +} + +normalize() { + case "$1" in + /*) printf '%s\n' "$1" ;; + *) printf '%s/%s\n' "$prefix" "$1" ;; + esac +} + +while IFS= read -r entry || [ -n "$entry" ]; do + case "$entry" in + ""|\#*) continue ;; + esac + path=$(normalize "$entry") + if [ ! -f "$stage$path" ] && [ ! -L "$stage$path" ]; then + printf '%s\n' "packing-list path was not staged: $path" >&2 + exit 1 + fi +done <"$plist" + +( + cd "$stage" + find . -mindepth 1 \( -type f -o -type l \) -print +) | +while IFS= read -r relative; do + path=${relative#.} + found=0 + while IFS= read -r entry || [ -n "$entry" ]; do + case "$entry" in + ""|\#*) continue ;; + esac + normalized=$(normalize "$entry") + if [ "$normalized" = "$path" ]; then + found=1 + break + fi + done <"$plist" + [ "$found" -eq 1 ] || { + printf '%s\n' "staged path is absent from packing list: $path" >&2 + exit 1 + } +done diff --git a/ports/Mk/Scripts/checksum.sh b/ports/Mk/Scripts/checksum.sh new file mode 100755 index 0000000..c835c7e --- /dev/null +++ b/ports/Mk/Scripts/checksum.sh @@ -0,0 +1,34 @@ +#!/bin/sh +set -eu + +if [ "$#" -lt 4 ]; then + printf '%s\n' \ + "usage: checksum.sh DISTDIR DISTINFO SHA256_COMMAND DISTFILE..." >&2 + exit 2 +fi + +distdir=$1 +distinfo=$2 +sha256_command=$3 +shift 3 +[ -f "$distinfo" ] || { + printf '%s\n' "missing distinfo: $distinfo" >&2 + exit 1 +} + +for distfile in "$@"; do + expected=$(sed -n "s/^SHA256 ($distfile) = //p" "$distinfo") + case "$expected" in + ????????????????????????????????????????????????????????????????) ;; + *) printf '%s\n' "invalid or missing checksum for $distfile" >&2; exit 1 ;; + esac + case "$expected" in + *[!0-9a-f]*) printf '%s\n' "invalid checksum for $distfile" >&2; exit 1 ;; + esac + actual=$("$sha256_command" "$distdir/$distfile") + [ "$actual" = "$expected" ] || { + printf '%s\n' \ + "$distfile checksum mismatch: expected $expected, got $actual" >&2 + exit 1 + } +done diff --git a/ports/Mk/Scripts/fetch.sh b/ports/Mk/Scripts/fetch.sh new file mode 100755 index 0000000..b0e2ab1 --- /dev/null +++ b/ports/Mk/Scripts/fetch.sh @@ -0,0 +1,24 @@ +#!/bin/sh +set -eu + +if [ "$#" -lt 4 ]; then + printf '%s\n' \ + "usage: fetch.sh DISTDIR MASTER_SITES FETCH_COMMAND DISTFILE..." >&2 + exit 2 +fi + +distdir=$1 +master_sites=$2 +fetch_command=$3 +shift 3 +mkdir -p "$distdir" + +for distfile in "$@"; do + case "$distfile" in + ""|*/*) printf '%s\n' "unsafe distfile name: $distfile" >&2; exit 1 ;; + esac + if [ ! -f "$distdir/$distfile" ]; then + "$fetch_command" "$master_sites$distfile" \ + "$distdir/$distfile" + fi +done diff --git a/ports/Mk/Scripts/install-depends.sh b/ports/Mk/Scripts/install-depends.sh new file mode 100755 index 0000000..68c3f95 --- /dev/null +++ b/ports/Mk/Scripts/install-depends.sh @@ -0,0 +1,37 @@ +#!/bin/sh +set -eu + +if [ "$#" -lt 2 ]; then + printf '%s\n' \ + "usage: install-depends.sh PORTSDIR PACKAGE_COMMAND DEPENDENCY..." >&2 + exit 2 +fi + +portsdir=$1 +package_command=$2 +shift 2 + +for dependency in "$@"; do + case "$dependency" in + *:*/*) ;; + *) printf '%s\n' "invalid dependency: $dependency" >&2; exit 1 ;; + esac + name=${dependency%%:*} + origin=${dependency#*:} + category=${origin%%/*} + port=${origin#*/} + case "$origin" in + */*/*) printf '%s\n' "invalid dependency origin: $origin" >&2; exit 1 ;; + esac + for component in "$name" "$category" "$port"; do + case "$component" in + ""|[!a-z]*|*[!a-z0-9-]*) + printf '%s\n' "invalid dependency: $dependency" >&2 + exit 1 + ;; + esac + done + if ! "$package_command" status "$name" >/dev/null 2>&1; then + make -C "$portsdir/$origin" install + fi +done diff --git a/ports/Mk/Scripts/package.sh b/ports/Mk/Scripts/package.sh new file mode 100755 index 0000000..9a5ef35 --- /dev/null +++ b/ports/Mk/Scripts/package.sh @@ -0,0 +1,68 @@ +#!/bin/sh +set -eu + +if [ "$#" -lt 9 ]; then + printf '%s\n' \ + "usage: package.sh PKGDIR STAGEDIR NAME VERSION ORIGIN PLIST DISTINFO MAKEFILE PORTS_REVISION [DEPENDENCY...]" >&2 + exit 2 +fi + +package=$1 +stage=$2 +name=$3 +version=$4 +origin=$5 +plist=$6 +distinfo=$7 +makefile=$8 +ports_revision=$9 +shift 9 + +case "$name" in + ""|[!a-z]*|*[!a-z0-9-]*) printf '%s\n' "invalid package name: $name" >&2; exit 1 ;; +esac +case "$version" in + ""|*[!A-Za-z0-9._+-]*) printf '%s\n' "invalid package version: $version" >&2; exit 1 ;; +esac + +rm -rf "$package" +mkdir -p "$package/root" +cp -R "$stage/." "$package/root/" +{ + printf 'name=%s\n' "$name" + printf 'version=%s\n' "$version" + printf 'origin=%s\n' "$origin" + printf 'dependencies=' + separator= + for dependency in "$@"; do + case "$dependency" in + *:*/*) ;; + *) printf '%s\n' "invalid dependency: $dependency" >&2; exit 1 ;; + esac + dependency_name=${dependency%%:*} + dependency_origin=${dependency#*:} + category=${dependency_origin%%/*} + dependency_port=${dependency_origin#*/} + case "$dependency_origin" in + */*/*) + printf '%s\n' "invalid dependency origin: $dependency_origin" >&2 + exit 1 + ;; + esac + for component in "$dependency_name" "$category" "$dependency_port"; do + case "$component" in + ""|[!a-z]*|*[!a-z0-9-]*) + printf '%s\n' "invalid dependency: $dependency" >&2 + exit 1 + ;; + esac + done + printf '%s%s' "$separator" "$dependency_name" + separator=" " + done + printf '\n' +} >"$package/+MANIFEST" +cp "$plist" "$package/+PLIST" +cp "$distinfo" "$package/+DISTINFO" +cp "$makefile" "$package/+BUILD" +cp "$ports_revision" "$package/+PORTS_REVISION" diff --git a/ports/Mk/mouse.port.mk b/ports/Mk/mouse.port.mk new file mode 100644 index 0000000..aa76db5 --- /dev/null +++ b/ports/Mk/mouse.port.mk @@ -0,0 +1,134 @@ +# Common MOUSE ports framework. Individual ports define metadata and any +# port-specific do-build or do-install targets before including this file. + +.if !defined(PORTNAME) || empty(PORTNAME) +.error PORTNAME is required +.endif +.if !defined(PORTVERSION) || empty(PORTVERSION) +.error PORTVERSION is required +.endif +.if !defined(CATEGORIES) || empty(CATEGORIES) +.error CATEGORIES is required +.endif +.if !defined(MASTER_SITES) || empty(MASTER_SITES) +.error MASTER_SITES is required +.endif +.if !defined(DISTFILES) || empty(DISTFILES) +.error DISTFILES is required +.endif + +.if !defined(PORTSDIR) +PORTSDIR:= ${.PARSEDIR:H:tA} +.endif +PORTDIR?= ${.CURDIR} +PORTORIGIN?= ${CATEGORIES}/${PORTNAME} +PREFIX?= /usr/local +DISTDIR?= /var/cache/mouse/ports/distfiles +WRKDIR?= /var/tmp/mouse-ports/${PORTNAME}-${PORTVERSION} +WRKSRC?= ${WRKDIR}/source +STAGEDIR?= ${WRKDIR}/stage +PACKAGES?= /var/cache/mouse/packages +PKGNAME?= ${PORTNAME}-${PORTVERSION} +PKGDIR?= ${PACKAGES}/${PKGNAME} +DISTINFO_FILE?= ${PORTDIR}/distinfo +PLIST?= ${PORTDIR}/pkg-plist +PORTS_REVISION_FILE?=/usr/share/mouse/ports.revision +RUN_DEPENDS?= +NO_EXTRACT?= no +EXTRACT_CMD?= /usr/bin/tar + +MOUSE_PACKAGE?= /usr/libexec/mouse-package +MOUSE_FETCH?= /usr/libexec/mouse-fetch +MOUSE_SHA256?= /usr/bin/mouse-sha256 +MOUSE_SH?= /bin/sh + +FETCH_SCRIPT= ${PORTSDIR}/Mk/Scripts/fetch.sh +CHECKSUM_SCRIPT=${PORTSDIR}/Mk/Scripts/checksum.sh +PLIST_SCRIPT= ${PORTSDIR}/Mk/Scripts/check-plist.sh +DEPENDS_SCRIPT= ${PORTSDIR}/Mk/Scripts/install-depends.sh +PACKAGE_SCRIPT= ${PORTSDIR}/Mk/Scripts/package.sh + +.PHONY: all fetch checksum extract configure build stage stage-qa package \ + install deinstall status clean do-extract do-configure do-build do-install +.MAIN: all + +all: build + +fetch: + ${MOUSE_SH} ${FETCH_SCRIPT} "${DISTDIR}" "${MASTER_SITES}" \ + "${MOUSE_FETCH}" ${DISTFILES} + +checksum: fetch + ${MOUSE_SH} ${CHECKSUM_SCRIPT} "${DISTDIR}" "${DISTINFO_FILE}" \ + "${MOUSE_SHA256}" ${DISTFILES} + +extract: checksum + rm -rf "${WRKDIR}" + mkdir -p "${WRKSRC}" +.if ${NO_EXTRACT:tl} == "yes" + : +.else + @test -x "${EXTRACT_CMD}" || { \ + echo "mouse.port.mk: archive extraction requires ${EXTRACT_CMD}" >&2; \ + false; \ + } + cd "${WRKSRC}" && "${EXTRACT_CMD}" -xf "${DISTDIR}/${DISTFILES}" +.endif + ${MAKE} do-extract + +configure: extract + ${MAKE} do-configure + +build: configure + ${MAKE} do-build + +stage: build + rm -rf "${STAGEDIR}" + mkdir -p "${STAGEDIR}" + ${MAKE} do-install + ${MAKE} stage-qa + +stage-qa: + ${MOUSE_SH} ${PLIST_SCRIPT} "${STAGEDIR}" "${PREFIX}" "${PLIST}" + +package: stage + ${MOUSE_SH} ${PACKAGE_SCRIPT} "${PKGDIR}" "${STAGEDIR}" \ + "${PORTNAME}" "${PORTVERSION}" "${PORTORIGIN}" "${PLIST}" \ + "${DISTINFO_FILE}" "${PORTDIR}/Makefile" \ + "${PORTS_REVISION_FILE}" ${RUN_DEPENDS} + +install: + ${MOUSE_SH} ${DEPENDS_SCRIPT} "${PORTSDIR}" "${MOUSE_PACKAGE}" \ + ${RUN_DEPENDS} + ${MAKE} package + ${MOUSE_PACKAGE} add "${PKGDIR}" + +deinstall: + ${MOUSE_PACKAGE} delete "${PORTNAME}" + +status: + ${MOUSE_PACKAGE} status "${PORTNAME}" + +clean: + rm -rf "${WRKDIR}" "${PKGDIR}" + +.if !target(do-extract) +do-extract: + : +.endif + +.if !target(do-configure) +do-configure: + : +.endif + +.if !target(do-build) +do-build: + : +.endif + +.if !target(do-install) +do-install: + @echo "mouse.port.mk: ${PORTNAME} must define do-install" >&2 + @false +.endif diff --git a/ports/README.md b/ports/README.md index 0fe7127..2d6a8f9 100644 --- a/ports/README.md +++ b/ports/README.md @@ -1,7 +1,14 @@ # Ports collection -Each immediate child directory is one port named `[a-z][a-z0-9-]*` and contains -one `port.conf` plus its trusted `build.sh` and any port-owned patches or files. +Ports use `category/name` directories. Every ordinary port contains a +`Makefile`, `distinfo`, and `pkg-plist`; optional patches and service files live +under `files`. + +Every port ends its Makefile with: + +```make +.include "${.PARSEDIR}/../../Mk/mouse.port.mk" +``` The collection is intentionally empty until the first real third-party port is accepted. Test-only recipes do not belong here. diff --git a/tests/check-layout.sh b/tests/check-layout.sh index d18581d..390e8aa 100755 --- a/tests/check-layout.sh +++ b/tests/check-layout.sh @@ -2,32 +2,23 @@ set -eu root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) -found=0 -for directory in "$root"/ports/*; do - [ -d "$directory" ] || continue - [ "${directory##*/}" != README.md ] || continue - found=1 - name=${directory##*/} - case "$name" in - ""|[!a-z]*|*[!a-z0-9-]*) - printf '%s\n' "invalid port directory name: $name" >&2 +[ -f "$root/ports/Mk/mouse.port.mk" ] +[ -x "$root/ports/Mk/Scripts/check-plist.sh" ] +[ -x "$root/ports/Mk/Scripts/checksum.sh" ] +[ -x "$root/ports/Mk/Scripts/fetch.sh" ] +[ -x "$root/ports/Mk/Scripts/install-depends.sh" ] +[ -x "$root/ports/Mk/Scripts/package.sh" ] + +find "$root/ports" -mindepth 2 -maxdepth 2 -type d \ + ! -path "$root/ports/Mk/*" -print | +while IFS= read -r port; do + for file in Makefile distinfo pkg-plist; do + [ -f "$port/$file" ] || { + printf '%s\n' "missing $file: $port" >&2 exit 1 - ;; - esac - [ -f "$directory/port.conf" ] || { - printf '%s\n' "missing recipe: ports/$name/port.conf" >&2 - exit 1 - } - [ -x "$directory/build.sh" ] || { - printf '%s\n' "missing executable build script: ports/$name/build.sh" >&2 - exit 1 - } - sh -n "$directory/build.sh" + } + done done -if [ "$found" -eq 0 ]; then - printf '%s\n' "ports collection is empty" -else - printf '%s\n' "ports layout check passed" -fi +printf '%s\n' "ports layout checks passed" diff --git a/tests/framework.mk b/tests/framework.mk new file mode 100644 index 0000000..cb90519 --- /dev/null +++ b/tests/framework.mk @@ -0,0 +1,13 @@ +PORTNAME= framework-test +PORTVERSION= 1.0.0 +CATEGORIES= tests +MASTER_SITES= http://example.invalid/ +DISTFILES= framework-test.c +NO_EXTRACT= yes + +.include "${.PARSEDIR}/../ports/Mk/mouse.port.mk" + +check: + @test "${PORTNAME}" = framework-test + @test "${PORTSDIR}" = "${.CURDIR}/ports" + @test "${PORTORIGIN}" = tests/framework-test