diff --git a/.github/workflows/code_quality_check.yaml b/.github/workflows/code_quality_check.yaml index 90d1660..140c183 100644 --- a/.github/workflows/code_quality_check.yaml +++ b/.github/workflows/code_quality_check.yaml @@ -26,6 +26,9 @@ jobs: contents: read steps: + - name: "Install shellcheck" + run: sudo DEBIAN_FRONTEND=noninteractive apt-get update && sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends shellcheck + - name: "Setup BEAM" uses: erlef/setup-beam@e6d7c94229049569db56a7ad5a540c051a010af9 id: "beam" @@ -53,3 +56,7 @@ jobs: # dialyzer - name: "Check with dialyzer" run: rebar3 dialyzer + + # shellcheck + - name: "Check generate_fun_data.sh with shellcheck" + run: shellcheck -o require-variable-braces,quote-safe-variables,check-set-e-suppressed,add-default-case,avoid-nullary-conditions generate_fun_data.sh diff --git a/generate_fun_data.sh b/generate_fun_data.sh index 551a9e1..6ab513d 100755 --- a/generate_fun_data.sh +++ b/generate_fun_data.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh # generate_fun_data.sh # # Regenerates supported_functions.data with version information @@ -9,7 +9,7 @@ # SPDX-FileCopyrightText: 2026 Winford (UncleGrumpy) # SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later -set -euo pipefail +set -e if [ "$#" -eq 0 ]; then echo "Usage: $0 " @@ -17,29 +17,35 @@ if [ "$#" -eq 0 ]; then fi ATOMVM_DIR="$1" -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -SPECTROMETER="$SCRIPT_DIR/_build/default/bin/spectrometer" -TMP_CACHE_DIR="${TMPDIR:-/tmp}/spectrometer_version_cache.$$" +# POSIX-compatible way to get script directory +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +SPECTROMETER="${SCRIPT_DIR}/_build/default/bin/spectrometer" +TMP_BASE_DIR="$(mktemp -d)" +TMP_CACHE_DIR="${TMP_BASE_DIR}/spectrometer_version_cache" +mkdir -p "${TMP_CACHE_DIR}" + +# Ensure cleanup on any exit (including early termination from set -e) +trap 'rm -rf "${TMP_BASE_DIR}"' EXIT -if [ ! -d "$ATOMVM_DIR/.git" ]; then - echo "Error: $ATOMVM_DIR does not appear to be a valid AtomVM git repository" +if [ ! -d "${ATOMVM_DIR}/.git" ]; then + echo "Error: ${ATOMVM_DIR} does not appear to be a valid AtomVM git repository" exit 1 fi echo "=== AtomVM Version Data Regeneration ===" -echo "Spectrometer: $SPECTROMETER" -echo "AtomVM directory: $ATOMVM_DIR" -echo "Cache directory: $TMP_CACHE_DIR" +echo "Spectrometer: ${SPECTROMETER}" +echo "AtomVM directory: ${ATOMVM_DIR}" +echo "Cache directory: ${TMP_CACHE_DIR}" echo "" # Step 1: (re)build the escript echo "=== Building atomvm_spectrometer escript ===" -cd "$SCRIPT_DIR" +cd "${SCRIPT_DIR}" rebar3 escriptize echo "" # Step 2: cd into AtomVM local checkout directory -cd "$ATOMVM_DIR" +cd "${ATOMVM_DIR}" # Step 3: Sync latest changes echo "=== Syncing AtomVM repository ===" @@ -48,19 +54,17 @@ git pull || echo "Warning: git pull failed, continuing anyway" git fetch --tags || echo "Warning: git fetch --tags failed, continuing anyway" echo "" -# Create cache directory and output file path -rm -rf "${TMP_CACHE_DIR}" -mkdir -p "${TMP_CACHE_DIR}" +# Set output file path (cache directory created via mkdir -p) OUTPUT_FILE="${TMP_CACHE_DIR}/supported_functions.data" # Step 4: Scan main branch (stored as {unreleased, <<"main">>}) -echo "=== Scanning branch: main (stored as {unreleased, \"main\"}) ===" +echo '=== Scanning branch: main (stored as {unreleased, <<"main">>}) ===' if git checkout "main" --quiet 2>/dev/null; then - "$SPECTROMETER" update \ - --atomvm-dir "$ATOMVM_DIR" \ + "${SPECTROMETER}" update \ + --atomvm-dir "${ATOMVM_DIR}" \ --branch "main" \ - --cache "$TMP_CACHE_DIR" \ - --output "$OUTPUT_FILE" \ + --cache "${TMP_CACHE_DIR}" \ + --output "${OUTPUT_FILE}" \ --force \ --no-tests echo "" @@ -68,76 +72,239 @@ else echo "Warning: Could not checkout main" fi -# Step 5: Scan release-0.7 branch (stored as {unreleased, <<"0.7.x">>}) -echo '=== Scanning branch: release-0.7 (stored as {unreleased, <<"0.7.x">>}) ===' +# Step 5: Scan latest release branch (stored as {unreleased, <<"X.Y.x">>}) +echo '=== Discovering latest release branch ===' git fetch origin --quiet 2>/dev/null -if git show-ref --verify --quiet refs/remotes/origin/release-0.7; then - if git checkout "release-0.7" --quiet 2>/dev/null; then - "$SPECTROMETER" update \ - --atomvm-dir "$ATOMVM_DIR" \ - --branch "release-0.7" \ - --cache "$TMP_CACHE_DIR" \ - --output "$OUTPUT_FILE" \ - --force \ - --no-tests - echo "" + +# Find the most recent release-X.X branch from remote +LATEST_RELEASE_BRANCH="" +LATEST_RELEASE_VER=0 + +for BRANCH in $(git branch -r | grep -E 'origin/release-[0-9]+\.[0-9]+$' | sed 's|origin/||'); do + # Extract version number: release-0.7 -> 0.7 -> convert to comparable integer + VER=$(echo "${BRANCH}" | sed 's/^release-//') + MAJOR=$(echo "${VER}" | cut -d. -f1) + MINOR=$(echo "${VER}" | cut -d. -f2) + VER_INT=$((MAJOR * 100 + MINOR)) + + if [ "${VER_INT}" -gt "${LATEST_RELEASE_VER}" ]; then + LATEST_RELEASE_VER="${VER_INT}" + LATEST_RELEASE_BRANCH="${BRANCH}" + fi +done + +if [ -n "${LATEST_RELEASE_BRANCH}" ]; then + VER_SUFFIX=$(echo "${LATEST_RELEASE_BRANCH}" | sed 's/^release-//') + echo "=== Scanning branch: ${LATEST_RELEASE_BRANCH} (stored as {unreleased, <<${VER_SUFFIX}.x>>}) ===" + if git show-ref --verify --quiet "refs/remotes/origin/${LATEST_RELEASE_BRANCH}"; then + if git checkout "${LATEST_RELEASE_BRANCH}" --quiet 2>/dev/null; then + "${SPECTROMETER}" update \ + --atomvm-dir "${ATOMVM_DIR}" \ + --branch "${LATEST_RELEASE_BRANCH}" \ + --cache "${TMP_CACHE_DIR}" \ + --output "${OUTPUT_FILE}" \ + --force \ + --no-tests + echo "" + else + echo "Warning: Failed to checkout ${LATEST_RELEASE_BRANCH}, skipping..." + fi else - echo "Warning: Failed to checkout release-0.7, skipping..." + echo "Warning: Could not verify ${LATEST_RELEASE_BRANCH}" + fi +else + echo "Warning: No release-X.X branch found" +fi + +# Step 6: Generate tag list from git +# Fetch latest tags and filter according to precedence rules +echo "=== Fetching and filtering git tags ===" +git fetch --tags --quiet 2>/dev/null || echo "Warning: git fetch --tags failed" + +# Build list of tags to process: +# - Release tags (vX.Y.Z) are included +# - Pre-release tags are only included if no matching release tag exists +# - For unreleased versions, only the newest pre-release is kept per precedence +TMP_TAG_FILE="${TMP_CACHE_DIR}/tags_to_scan.txt" +: > "${TMP_TAG_FILE}" + +# Store all tags, categorized +TMP_RELEASE_TAGS="${TMP_CACHE_DIR}/release_tags.txt" +TMP_PRERELEASE_TAGS="${TMP_CACHE_DIR}/prerelease_tags.txt" +: > "${TMP_RELEASE_TAGS}" +: > "${TMP_PRERELEASE_TAGS}" + +# Extract tags and categorize them +# Pre-release tags contain -alpha, -beta, or -rc after the version +# Use git's native sorting for portability (git >= 2.0 supports --sort=v:refname) +# Fall back to no sort on older git versions (lexical order is acceptable) +if git tag -l --sort=v:refname "v*.*.*" 2>/dev/null | head -1 >/dev/null 2>&1; then + TAGS=$(git tag -l --sort=v:refname "v*.*.*" 2>/dev/null) +else + TAGS=$(git tag -l "v*.*.*" 2>/dev/null) +fi +for TAG in ${TAGS}; do + case "${TAG}" in + *[-_]alpha.*|*[-_]beta.*|*[-_]rc.*) + # Pre-release tag (v1.2.3-alpha.1, v1.2.3-rc.2, etc.) + echo "${TAG}" >> "${TMP_PRERELEASE_TAGS}" + ;; + v*.*.*) + # Release tag (v1.2.3) + echo "${TAG}" >> "${TMP_RELEASE_TAGS}" + ;; + *) + echo "Fatal error, no release tags found" + exit 1 + ;; + esac +done + +# Process tags: for each version, prefer release over pre-release +# Build a list of versions we've seen +TMP_VERSIONS="${TMP_CACHE_DIR}/versions.txt" +: > "${TMP_VERSIONS}" + +# First pass: collect all base versions from release tags +while IFS= read -r REL_TAG; do + # Extract base version: v1.2.3 -> 1.2.3 + BASE_VER=$(echo "${REL_TAG}" | sed 's/^v//') + echo "${BASE_VER}" >> "${TMP_VERSIONS}" + echo "${REL_TAG}" >> "${TMP_TAG_FILE}" +done < "${TMP_RELEASE_TAGS}" + +# Second pass: process pre-release tags for unreleased versions +# Group by version base, keep only the newest per group based on precedence +# Track processed version bases to avoid duplicates +PROCESSED_BASES="${TMP_CACHE_DIR}/processed_bases.txt" +: > "${PROCESSED_BASES}" + +# Read prerelease tags into a variable to avoid shellcheck SC2094 warning +PRE_TAGS_LIST=$(cat "${TMP_PRERELEASE_TAGS}") + +for PRE_TAG in ${PRE_TAGS_LIST}; do + # Extract version base from pre-release: v1.2.3-alpha.1 -> v1.2.3 + VERSION_BASE=$(echo "${PRE_TAG}" | sed -E 's/^(v[0-9]+\.[0-9]+\.[0-9]+).*/\1/') + + # Skip if a release tag exists for this version + if grep -qx "${VERSION_BASE#v}" "${TMP_VERSIONS}" 2>/dev/null; then + continue + fi + + # Skip if we've already processed this version base + if grep -qx "${VERSION_BASE}" "${PROCESSED_BASES}" 2>/dev/null; then + continue + fi + + # Mark this version base as processed + echo "${VERSION_BASE}" >> "${PROCESSED_BASES}" + + # Get all pre-release tags for this version base + PRE_TAGS_FOR_VER=$(echo "${PRE_TAGS_LIST}" | grep "^${VERSION_BASE}-" 2>/dev/null || true) + + if [ -z "${PRE_TAGS_FOR_VER}" ]; then + continue + fi + + # Find the best (newest) pre-release per version base based on precedence + # Precedence: rc > beta > alpha + # Within each type, pick the highest number + BEST_RC="" + BEST_BETA="" + BEST_ALPHA="" + RC_NUM=0 + BETA_NUM=0 + ALPHA_NUM=0 + + for PT in ${PRE_TAGS_FOR_VER}; do + PR_TYPE=$(echo "${PT}" | sed -E 's/.*-(alpha|beta|rc)\.[0-9]+/\1/') + PR_NUM=$(echo "${PT}" | sed -E 's/.*-(alpha|beta|rc)\.([0-9]+)/\2/') + + case "${PR_TYPE}" in + rc) + if [ "${PR_NUM}" -gt "${RC_NUM}" ]; then + RC_NUM="${PR_NUM}" + BEST_RC="${PT}" + fi + ;; + beta) + if [ "${PR_NUM}" -gt "${BETA_NUM}" ]; then + BETA_NUM="${PR_NUM}" + BEST_BETA="${PT}" + fi + ;; + alpha) + if [ "${PR_NUM}" -gt "${ALPHA_NUM}" ]; then + ALPHA_NUM="${PR_NUM}" + BEST_ALPHA="${PT}" + fi + ;; + *) + echo "Ignoring tag: ${PR_TYPE}" + ;; + esac + done + + # Select best pre-release: rc > beta > alpha + if [ -n "${BEST_RC}" ]; then + echo "${BEST_RC}" >> "${TMP_TAG_FILE}" + elif [ -n "${BEST_BETA}" ]; then + echo "${BEST_BETA}" >> "${TMP_TAG_FILE}" + elif [ -n "${BEST_ALPHA}" ]; then + echo "${BEST_ALPHA}" >> "${TMP_TAG_FILE}" fi +done + +# Sort tags by version (newest first for processing) +# Use git's sort if available, otherwise fall back to lexical sort +if git tag -l --sort=v:refname "v*.*.*" 2>/dev/null | head -1 >/dev/null 2>&1; then + sort -rV "${TMP_TAG_FILE}" > "${TMP_TAG_FILE}.sorted" 2>/dev/null || sort -r "${TMP_TAG_FILE}" > "${TMP_TAG_FILE}.sorted" else - echo "Warning: Could not checkout release-0.7" + # Lexical sort is acceptable for final ordering + sort -r "${TMP_TAG_FILE}" > "${TMP_TAG_FILE}.sorted" fi +mv "${TMP_TAG_FILE}.sorted" "${TMP_TAG_FILE}" -# Step 6: Define tags to scan (in reverse-chronological order) -TAGS=( - "v0.7.0-alpha.1" - "v0.6.6" - "v0.6.5" - "v0.6.4" - "v0.6.3" - "v0.6.2" - "v0.6.1" - "v0.6.0" - "v0.5.0" -) +echo "Tags to scan:" +cat "${TMP_TAG_FILE}" +echo "" # Step 7: Scan each tag (release is derived automatically from --tag) -for TAG in "${TAGS[@]}"; do - echo "=== Scanning tag: $TAG ===" - git checkout "$TAG" --quiet 2>/dev/null || { echo "Warning: Could not checkout $TAG"; continue; } - "$SPECTROMETER" update \ - --atomvm-dir "$ATOMVM_DIR" \ - --tag "$TAG" \ - --cache "$TMP_CACHE_DIR" \ - --output "$OUTPUT_FILE" \ +while IFS= read -r TAG; do + [ -z "${TAG}" ] && continue + echo "=== Scanning tag: ${TAG} ===" + git checkout "${TAG}" --quiet 2>/dev/null || { echo "Warning: Could not checkout ${TAG}"; continue; } + if ! "${SPECTROMETER}" update \ + --atomvm-dir "${ATOMVM_DIR}" \ + --tag "${TAG}" \ + --cache "${TMP_CACHE_DIR}" \ + --output "${OUTPUT_FILE}" \ --force \ - --no-tests + --no-tests; then + echo "Warning: Failed to scan tag ${TAG}, continuing..." + fi echo "" -done +done < "${TMP_TAG_FILE}" # Step 8: Copy result to project priv/ -DEST_FILE="$SCRIPT_DIR/priv/supported_functions.data" -if [ ! -f "$OUTPUT_FILE" ]; then - echo "Error: Generated file not found at $OUTPUT_FILE" +DEST_FILE="${SCRIPT_DIR}/priv/supported_functions.data" +if [ ! -f "${OUTPUT_FILE}" ]; then + echo "Error: Generated file not found at ${OUTPUT_FILE}" exit 1 fi -echo "=== Copying result to $SCRIPT_DIR/priv/supported_functions.data ===" -mkdir -p "$SCRIPT_DIR/priv" -# Check if files are the same (same inode) -if [ "$OUTPUT_FILE" -ef "$DEST_FILE" ]; then - echo "Files are already the same, no copy needed" +echo "=== Copying result to ${SCRIPT_DIR}/priv/supported_functions.data ===" +mkdir -p "${SCRIPT_DIR}/priv" +# POSIX-compatible check if files are the same (compare content) +if [ -f "${DEST_FILE}" ] && cmp -s "${OUTPUT_FILE}" "${DEST_FILE}"; then + echo "Files are identical, no copy needed" else - if [ -f "$DEST_FILE" ]; then + if [ -f "${DEST_FILE}" ]; then BACKUP_TS="$(date +%Y%m%d%H%M)" - echo "Backing up existing $DEST_FILE to ${DEST_FILE}.${BACKUP_TS}.bak" + echo "Backing up existing ${DEST_FILE} to ${DEST_FILE}.${BACKUP_TS}.bak" mv "${DEST_FILE}" "${DEST_FILE}.${BACKUP_TS}.bak" fi - cp "$OUTPUT_FILE" "$DEST_FILE" -fi - -if [ -d "$TMP_CACHE_DIR" ]; then - rm -rf "$TMP_CACHE_DIR" + cp "${OUTPUT_FILE}" "${DEST_FILE}" fi echo "" -echo "Done! Version data written to $DEST_FILE" +echo "Done! Version data written to ${DEST_FILE}" diff --git a/priv/supported_functions.data b/priv/supported_functions.data index 26aa4ab..a9e16c0 100644 --- a/priv/supported_functions.data +++ b/priv/supported_functions.data @@ -2375,11 +2375,28 @@ ]}, {uart, [ {close, 1, all, <<118, 48, 46, 54, 46, 48>>}, + {deinit, 1, [rp2], {unreleased, <<109, 97, 105, 110>>}}, + {getc, 1, [rp2], {unreleased, <<109, 97, 105, 110>>}}, + {init, 2, [rp2], {unreleased, <<109, 97, 105, 110>>}}, + {is_readable, 1, [rp2], {unreleased, <<109, 97, 105, 110>>}}, + {is_readable_within_us, 2, [rp2], {unreleased, <<109, 97, 105, 110>>}}, + {is_writable, 1, [rp2], {unreleased, <<109, 97, 105, 110>>}}, {open, 1, all, <<118, 48, 46, 54, 46, 48>>}, {open, 2, all, <<118, 48, 46, 53, 46, 48>>}, + {putc, 2, [rp2], {unreleased, <<109, 97, 105, 110>>}}, + {putc_raw, 2, [rp2], {unreleased, <<109, 97, 105, 110>>}}, + {puts, 2, [rp2], {unreleased, <<109, 97, 105, 110>>}}, {read, 1, all, <<118, 48, 46, 53, 46, 48>>}, {read, 2, all, <<118, 48, 46, 54, 46, 54>>}, - {write, 2, all, <<118, 48, 46, 53, 46, 48>>} + {read_blocking, 2, [rp2], {unreleased, <<109, 97, 105, 110>>}}, + {set_baudrate, 2, [rp2], {unreleased, <<109, 97, 105, 110>>}}, + {set_break, 2, [rp2], {unreleased, <<109, 97, 105, 110>>}}, + {set_fifo_enabled, 2, [rp2], {unreleased, <<109, 97, 105, 110>>}}, + {set_format, 4, [rp2], {unreleased, <<109, 97, 105, 110>>}}, + {set_hw_flow, 3, [rp2], {unreleased, <<109, 97, 105, 110>>}}, + {tx_wait_blocking, 1, [rp2], {unreleased, <<109, 97, 105, 110>>}}, + {write, 2, all, <<118, 48, 46, 53, 46, 48>>}, + {write_blocking, 2, [rp2], {unreleased, <<109, 97, 105, 110>>}} ]}, {unicode, [ {characters_to_binary, 1, all, <<118, 48, 46, 54, 46, 48>>},