diff --git a/.gitignore b/.gitignore index e50a283..1f36a17 100644 --- a/.gitignore +++ b/.gitignore @@ -94,3 +94,7 @@ # Benchmarking input and output /bench/data /bench/work + +# AmigaOS packaging output +dist/ +bld-*/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 44b696b..361e39a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -126,9 +126,45 @@ if (AMIGA) endif() elseif ($ENV{CROSS_PFX} STREQUAL "ppc-amigaos") set(AMIGA_PPC ON) - message(STATUS "Building for PowerPC AmigaOS") + if (NOT DEFINED PPC_CRT) + set(PPC_CRT "newlib") + endif() + set(PPC_CRT "${PPC_CRT}" CACHE STRING "ppc-amigaos C runtime: newlib, clib2 or clib4") + set_property(CACHE PPC_CRT PROPERTY STRINGS newlib clib2 clib4) + if (NOT PPC_CRT STREQUAL "newlib" AND + NOT PPC_CRT STREQUAL "clib2" AND + NOT PPC_CRT STREQUAL "clib4") + message(FATAL_ERROR "PPC_CRT must be newlib, clib2 or clib4 (got: ${PPC_CRT})") + endif() + if (NOT PPC_CRT STREQUAL "newlib") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mcrt=${PPC_CRT}") + endif() + message(STATUS "Building for PowerPC AmigaOS using CRT: ${PPC_CRT}") elseif($ENV{CROSS_PFX} STREQUAL "ppc-morphos") set(MORPHOS ON) + if (NOT DEFINED MORPHOS_CLIB) + set(MORPHOS_CLIB "default") + endif() + set(MORPHOS_CLIB "${MORPHOS_CLIB}" CACHE STRING "MorphOS C library: default or libnix") + set_property(CACHE MORPHOS_CLIB PROPERTY STRINGS default libnix) + if (NOT MORPHOS_CLIB STREQUAL "default" AND NOT MORPHOS_CLIB STREQUAL "libnix") + message(FATAL_ERROR "MORPHOS_CLIB must be default or libnix (got: ${MORPHOS_CLIB})") + endif() + option(MORPHOS_BASEREL32 "MorphOS: build with -mbaserel32" OFF) + if (MORPHOS_CLIB STREQUAL "libnix") + # Same situation as the -noixemul path below: libnix's headers + # disagree with the ixemul ones about va_list, so the varargs + # helpers in debug.c trip -Wincompatible-pointer-types. + set(DISABLE_WERROR ON) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-incompatible-pointer-types") + endif() + if (MORPHOS_CLIB STREQUAL "libnix") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mclib=libnix") + endif() + if (MORPHOS_BASEREL32) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mbaserel32") + endif() + message(STATUS "Building for PowerPC MorphOS using clib: ${MORPHOS_CLIB}, baserel32: ${MORPHOS_BASEREL32}") if (NOIXEMUL) message(STATUS "Building for PowerPC MorphOS without ixemul") set(DISABLE_WERROR ON) diff --git a/README.md b/README.md index 098c8a6..5d33b21 100644 --- a/README.md +++ b/README.md @@ -346,6 +346,13 @@ make libjson-c.a will get created in the build directory. +Select the C runtime with `-DPPC_CRT=`: `newlib` (default), `clib2` or `clib4`. +Anything other than `newlib` is passed to the compiler as `-mcrt=`. + +``` +cmake -DPPC_CRT=clib4 .. +``` + ### To build for PowerPC MorphOS: ``` @@ -363,6 +370,72 @@ If you are making an application that absolutely requires ixemul, then remove th libjson-c.a will get created in the build directory. +Two further options apply to MorphOS: + +* `-DMORPHOS_CLIB=` selects `default` (ixemul) or `libnix` (passed as + `-mclib=libnix`). That is the same multilib `-noixemul` selects, and is the + self-contained ABI -- an ixemul build needs `ixemul.library` on the target. +* `-DMORPHOS_BASEREL32=ON` adds `-mbaserel32`. + +``` +cmake -DMORPHOS_CLIB=libnix .. +cmake -DMORPHOS_BASEREL32=ON .. +``` + +### Building and packaging every variant + +Two scripts at the repository root build the whole matrix across all three +systems, so you do not have to drive the container by hand. + +* `build-amiga-sdk.sh [OS ...]` — builds every variant and installs + `libjson-c.a` plus the `json-c/` headers into an SDK tree. The location comes + from `$AMIGA_SDK` (default `/opt/amiga`); either a `/` or an + AmigaSDK-gcc-style `amigaos3/sdk//` layout is accepted, and an OS + whose subdirectory is absent is skipped rather than failing. +* `package-amiga.sh [OS ...]` — builds the same matrix into a staging tree and + produces `dist/json-c.lha` for distribution. + +Both default to `AmigaOS3 AmigaOS4 MorphOS`, and both take the variant list +from the same table in `build-amiga-sdk.sh`, so they cannot disagree. 22 +libraries are built in total: 15 for AmigaOS 3 (newlib, libnix and clib2, each +across the five multilib slots), 3 for AmigaOS 4 (newlib, clib2, clib4) and 4 +for MorphOS (ixemul and native, each with and without `-mbaserel32`). + +Which of them actually *link* is a separate question from whether they build, +and was checked by compiling a program against each one: + +| target | links | +|--------|-------| +| AmigaOS 3, libnix | all 5 slots | +| AmigaOS 3, clib2 | `lib/libm020/` and `lib/libb32/libm020/` only | +| AmigaOS 3, newlib | none -- the m68k newlib tree has no `gettimeofday()` | +| AmigaOS 4 | all 3 (newlib, clib2, clib4) | +| MorphOS | all 4 slots | + +The clib2 failures are inside clib2 itself: its 68000 multilib provides no +`strtoll`/`strtoull`, and 16-bit `-fbaserel` overflows with *truncated to fit: +DREL16* once json-c's data is added -- `-fbaserel32` has no 64 KB limit. Link +with `-ljson-c -lm` after your own objects, and add `-lunix` for clib2. + +The archive is laid out with the readme at the root and one directory per +system, each following the compiler's own layout so it can be copied straight +over an SDK: + +``` +json-c.readme +json-c/AmigaOS3/{include,lib,...}/ plus libnix/ and clib2/ subtrees +json-c/AmigaOS4/{include,lib,lib/clib2,lib/clib4}/ +json-c/MorphOS/{include,lib,lib/libb32,lib/libnix}/ +``` + +Note that m68k and PPC lay their C runtimes out differently, and the variant +table follows each. On m68k a runtime is a separate tree (`libnix/`, `clib2/`), +each with its own `include/` and `lib/`. On PPC a runtime is a multilib *slot* +beneath a single shared `lib/`: `-mcrt=clib4` searches `/lib/clib4/` +and takes headers from `/include/`. This was read off +`-print-search-dirs` rather than assumed, because installing to the wrong one +produces a library the compiler silently never finds. + Linking to `libjson-c` ---------------------- diff --git a/build-amiga-sdk.sh b/build-amiga-sdk.sh new file mode 100755 index 0000000..835edda --- /dev/null +++ b/build-amiga-sdk.sh @@ -0,0 +1,111 @@ +#!/usr/bin/env bash +# Cross-build json-c for Amiga systems across every multilib slot and install +# the archives plus headers into an SDK tree. +# +# ./build-amiga-sdk.sh [OS ...] # default: AmigaOS3 AmigaOS4 MorphOS +# +# SDK location comes from $AMIGA_SDK (default /opt/amiga). +set -uo pipefail + +SDK=${AMIGA_SDK:-/opt/amiga} +cd "$(dirname "$0")" +ROOT=$PWD +OSES=("$@"); [[ ${#OSES[@]} -eq 0 ]] && OSES=(AmigaOS3 AmigaOS4 MorphOS) + +# os | image tag | sdk subdir +os_image() { case "$1" in + AmigaOS3) echo "sacredbanana/amiga-compiler:m68k-amigaos m68k-amigaos" ;; + AmigaOS4) echo "sacredbanana/amiga-compiler:ppc-amigaos ppc-amigaos" ;; + MorphOS) echo "sacredbanana/amiga-compiler:ppc-morphos ppc-morphos" ;; + *) return 1 ;; +esac; } + +# Where an AmigaSDK-gcc-style checkout keeps each system's target tree. +os_sdkdir() { case "$1" in + AmigaOS3) echo "amigaos3/sdk" ;; + AmigaOS4) echo "amigaos4/sdk" ;; + MorphOS) echo "morphos/sdk" ;; + *) return 1 ;; +esac; } + +# Per-OS variant table: "tree|slot|cmake args" +variants_AmigaOS3=( + "libnix|.|-DM68K_CRT=nix20 -DM68K_CPU=68000 -DM68K_BASEREL=off" + "libnix|libm020|-DM68K_CRT=nix20 -DM68K_CPU=68020 -DM68K_BASEREL=off" + "libnix|libb|-DM68K_CRT=nix20 -DM68K_CPU=68000 -DM68K_BASEREL=baserel" + "libnix|libb/libm020|-DM68K_CRT=nix20 -DM68K_CPU=68020 -DM68K_BASEREL=baserel" + "libnix|libb32/libm020|-DM68K_CRT=nix20 -DM68K_CPU=68020 -DM68K_BASEREL=baserel32" + ".|.|-DM68K_CRT=newlib -DM68K_CPU=68000 -DM68K_BASEREL=off" + ".|libm020|-DM68K_CRT=newlib -DM68K_CPU=68020 -DM68K_BASEREL=off" + ".|libb|-DM68K_CRT=newlib -DM68K_CPU=68000 -DM68K_BASEREL=baserel" + ".|libb/libm020|-DM68K_CRT=newlib -DM68K_CPU=68020 -DM68K_BASEREL=baserel" + ".|libb32/libm020|-DM68K_CRT=newlib -DM68K_CPU=68020 -DM68K_BASEREL=baserel32" + "clib2|.|-DM68K_CRT=clib2 -DM68K_CPU=68000 -DM68K_BASEREL=off" + "clib2|libm020|-DM68K_CRT=clib2 -DM68K_CPU=68020 -DM68K_BASEREL=off" + "clib2|libb|-DM68K_CRT=clib2 -DM68K_CPU=68000 -DM68K_BASEREL=baserel" + "clib2|libb/libm020|-DM68K_CRT=clib2 -DM68K_CPU=68020 -DM68K_BASEREL=baserel" + "clib2|libb32/libm020|-DM68K_CRT=clib2 -DM68K_CPU=68020 -DM68K_BASEREL=baserel32" +) +# Unlike m68k, where each C runtime is its own tree (libnix/, clib2/), the PPC +# toolchains make the runtime a multilib SLOT under lib/, with headers coming +# from the shared /include/. Verified with -print-search-dirs: +# ppc-amigaos -mcrt=clib2 -> /lib/clib2/ +# ppc-amigaos -mcrt=clib4 -> /lib/clib4/ +# ppc-morphos -mclib=libnix-> /lib/libnix/ +# ppc-morphos -mbaserel32 -> /lib/libb32/ +variants_AmigaOS4=( + ".|.|-DPPC_CRT=newlib" + ".|clib2|-DPPC_CRT=clib2" + ".|clib4|-DPPC_CRT=clib4" +) +# MORPHOS_CLIB=libnix (-mclib=libnix) and NOIXEMUL (-noixemul) select the same +# multilib; it is the self-contained ABI, needing no ixemul.library on target. +variants_MorphOS=( + ".|.|" + ".|libb32|-DMORPHOS_BASEREL32=ON" + ".|libnix|-DMORPHOS_CLIB=libnix" + ".|libb32/libnix|-DMORPHOS_CLIB=libnix -DMORPHOS_BASEREL32=ON" +) + +# $1 out-dir-for-libs $2 out-dir-for-headers $3.. OS list +ok=0; fail=0; failed=() +for os in "${OSES[@]}"; do + read -r image sdksub <<<"$(os_image "$os")" || { echo "skip: unknown OS $os"; continue; } + # /opt/amiga keeps the target tree directly under /, while an + # AmigaSDK-gcc checkout nests it under /sdk/. Accept either. + osdir=$(os_sdkdir "$os") + if [[ -d $SDK/$sdksub ]]; then SDKROOT=$SDK/$sdksub + elif [[ -d $SDK/$osdir/$sdksub ]]; then SDKROOT=$SDK/$osdir/$sdksub + else echo "skip $os: neither $SDK/$sdksub nor $SDK/$osdir/$sdksub"; continue; fi + + eval "vars=(\"\${variants_$os[@]}\")" + for v in "${vars[@]}"; do + IFS='|' read -r tree slot cargs <<<"$v" + bdir="bld-$os-$(echo "$tree$slot$cargs" | tr -c 'A-Za-z0-9' '-' | cut -c1-40)" + printf ' %-9s %-7s %-16s ' "$os" "$tree" "$slot" + if ! docker run --rm -v "$ROOT":/work -e USER="$(id -u)" -e GROUP="$(id -g)" "$image" sh -c " + set -e + rm -rf /work/$bdir && mkdir -p /work/$bdir && cd /work/$bdir + cmake $cargs -DCMAKE_BUILD_TYPE=Release -DBUILD_APPS=NO \ + -DCMAKE_INSTALL_PREFIX=/work/$bdir/inst .. >cmake.log 2>&1 + make -j\$(nproc) json-c >make.log 2>&1 + make install >install.log 2>&1 + " 2>/dev/null; then + echo "BUILD FAILED (logs in $bdir)"; fail=$((fail+1)); failed+=("$os/$tree/$slot"); continue + fi + lib=$bdir/libjson-c.a + [[ -f $lib ]] || lib=$bdir/inst/lib/libjson-c.a + [[ -f $lib ]] || { echo "no libjson-c.a"; fail=$((fail+1)); failed+=("$os/$tree/$slot"); continue; } + + base=$SDKROOT; [[ $tree != "." ]] && base=$SDKROOT/$tree + dest=$base/lib; [[ $slot != "." ]] && dest=$base/lib/$slot + mkdir -p "$dest" "$base/include" + cp "$lib" "$dest/libjson-c.a" + cp -a "$bdir/inst/include/json-c" "$base/include/" + echo "ok"; ok=$((ok+1)); rm -rf "$bdir" + done +done +echo +echo "built+installed: $ok failed: $fail" +((fail)) && { printf 'failures: %s\n' "${failed[*]}"; exit 1; } +exit 0 diff --git a/json-c.readme b/json-c.readme new file mode 100644 index 0000000..67b447e --- /dev/null +++ b/json-c.readme @@ -0,0 +1,110 @@ +Short: JSON parser and generator library (static libs + headers) +Author: Michael Clark, Eric Haszlakiewicz et al. +Uploader: Cameron Armstrong +Type: dev/lib +Version: 0.19.99 +Architecture: m68k-amigaos >= 2.0; ppc-amigaos; ppc-morphos +Distribution: Aminet +Requires: an Amiga gcc cross compiler + +json-c implements a reference-counted object model for JSON, a parser that +builds it from text, and a serialiser that writes it back out. This archive +contains static libraries and public headers for Amiga development. + + https://github.com/json-c/json-c + + +Contents + + json-c.readme this file + json-c/AmigaOS3/... m68k AmigaOS libraries and headers + json-c/AmigaOS4/... ppc-amigaos libraries and headers + json-c/MorphOS/... ppc-morphos libraries and headers + + Under each OS directory the trees follow the compiler's own layout, so + they can be copied straight over an existing SDK, e.g. + + cp -a json-c/AmigaOS3/* /opt/amiga/m68k-amigaos/ + + AmigaOS3 ships one set per C runtime -- newlib at the top level, plus + libnix/ and clib2/ -- and within each, one library per multilib: + + lib/ 68000, absolute addressing + lib/libm020/ 68020, absolute addressing + lib/libb/ 68000, -fbaserel + lib/libb/libm020/ 68020, -fbaserel + lib/libb32/libm020/ 68020, -fbaserel32 + + On PPC a C runtime is not a separate tree but a multilib slot beneath one + shared lib/, with headers in the single shared include/: + + AmigaOS4 lib/ newlib + lib/clib2/ clib2 + lib/clib4/ clib4 + + MorphOS lib/ ixemul + lib/libb32/ ixemul, -mbaserel32 + lib/libnix/ native C library (-noixemul) + lib/libb32/libnix/ native C library, -mbaserel32 + + -mclib=libnix and -noixemul select the same MorphOS multilib. That ABI is + the self-contained one -- an ixemul build needs ixemul.library on the + target -- so prefer lib/libnix/ for new code. + + +Usage + + #include + + struct json_object *o = json_tokener_parse("{\"a\":1}"); + printf("%s\n", json_object_to_json_string(o)); + json_object_put(o); + + Link with -ljson-c -lm, after your own objects on the command line. For + clib2 add -lunix as well, since clib2 keeps its POSIX layer there. + + +Which variants link + + Verified by compiling the snippet above against each shipped library. + + AmigaOS3 libnix all 5 slots link. + + clib2 lib/libm020/ and lib/libb32/libm020/ link. The other + three fail inside clib2 itself: its 68000 multilib has + no strtoll()/strtoull(), and 16-bit -fbaserel overflows + with "truncated to fit: DREL16" once json-c's data is + added. -fbaserel32 has no such limit. + + newlib builds, but does not link: the m68k newlib tree has no + gettimeofday(). The libraries are shipped anyway, since + that is a gap in the SDK rather than in json-c and a + newlib with gettimeofday() implemented would link. + + AmigaOS4 all three (newlib, clib2, clib4) link. + + MorphOS all four slots link. + + +A note on base-relative addressing (AmigaOS3) + + A base-relative library addresses its globals through a4, which the + program's startup code sets up once. Only link the libb/ or libb32/ + variants into a program that is itself base-relative AND that marks every + function the OS calls back into (BOOPSI/MUI dispatchers, struct Hook + entries, interrupt servers) with __saveds. The m68k SDI_hook.h macros set + up register arguments but do not add __saveds, so this is easy to get + wrong, and the failure mode is intermittent memory corruption rather than + a clean error -- libnix keeps malloc's heap state in a4-relative storage. + + Conversely a non-base-relative library built at -O1 or above may use a4 as + a scratch register, which is harmless in a non-base-relative program but + corrupts the base pointer of one that is. + + Match the variant to the program. If in doubt, use the plain lib/ or + lib/libm020/ builds. + + +Licence + + MIT; see COPYING in the source distribution. diff --git a/package-amiga.sh b/package-amiga.sh new file mode 100755 index 0000000..075991f --- /dev/null +++ b/package-amiga.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# Build every json-c variant for the requested Amiga systems and package them +# into dist/json-c.lha. +# +# ./package-amiga.sh [OS ...] # default: all three +# +# Archive layout: +# json-c.readme at the archive root +# json-c/AmigaOS3/... per-OS libraries and headers +# json-c/AmigaOS4/... +# json-c/MorphOS/... +set -uo pipefail + +cd "$(dirname "$0")" +ROOT=$PWD +DIST=$ROOT/dist +STAGE=$DIST/stage +OSES=("$@"); [[ ${#OSES[@]} -eq 0 ]] && OSES=(AmigaOS3 AmigaOS4 MorphOS) + +command -v lha >/dev/null || { echo "error: lha not found on PATH" >&2; exit 1; } + +# Build straight into the staging tree by pointing AMIGA_SDK at it: the build +# script's SDK layout ($SDK/) is the same shape we want per OS, so each +# OS gets staged under its own directory. +rm -rf "$DIST"; mkdir -p "$STAGE/json-c" +cp "$ROOT/json-c.readme" "$STAGE/json-c.readme" + +built=() +for os in "${OSES[@]}"; do + case "$os" in + AmigaOS3) sub=m68k-amigaos ;; + AmigaOS4) sub=ppc-amigaos ;; + MorphOS) sub=ppc-morphos ;; + *) echo "skip: unknown OS $os"; continue ;; + esac + tmp=$DIST/sdk-$os + mkdir -p "$tmp/$sub" + echo "=== $os ===" + # Stage whatever landed even if some variants failed -- a single unsupported + # multilib should not drop the whole OS from the archive. + AMIGA_SDK="$tmp" ./build-amiga-sdk.sh "$os" || echo " ($os: some variants failed, see above)" + if [[ -n $(find "$tmp/$sub" -name 'libjson-c.a' -print -quit 2>/dev/null) ]]; then + mkdir -p "$STAGE/json-c/$os" + cp -a "$tmp/$sub/." "$STAGE/json-c/$os/" + built+=("$os") + else + echo " $os: nothing built, omitted from the archive" + fi + rm -rf "$tmp" +done + +[[ ${#built[@]} -eq 0 ]] && { echo "nothing built"; exit 1; } + +# NOTE: build the archive from a clean staging tree with `lha -aq2`. Do NOT try +# to refresh a single entry (e.g. the readme) in an existing archive with +# `lha u` -- it APPENDS a second entry with the same name rather than replacing +# it, and extraction then yields the stale copy. +( cd "$STAGE" && lha -aq2 "$DIST/json-c.lha" * >/dev/null ) +rm -rf "$STAGE" +echo +echo "packaged: ${built[*]}" +ls -la "$DIST"