diff --git a/.github/workflows/01-make-dist.yml b/.github/workflows/01-make-dist.yml new file mode 100644 index 0000000..f54c7b0 --- /dev/null +++ b/.github/workflows/01-make-dist.yml @@ -0,0 +1,259 @@ +# Adapted from NUT 01-make-dist.yml with inspiration taken from +# https://javahelps.com/manage-github-artifact-storage-quota +# regarding uploads of artifacts and clearing the way for them. +# See also: +# https://github.com/actions/upload-artifact +# https://docs.github.com/en/actions/reference/workflows-and-actions/variables +# https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax +#name: "GHA-01: Make dist and docs tarballs, see workflow page for links" +name: "GHA-01: Tarballs" + +on: + push: + branches: [ "master" ] + tags: + - v* + pull_request_target: + # The branches below must be a subset of the branches above + # Note that for PRs this runs the copy of workflow in the + # target branch (and only then gets some of the permissions + # listed below), and identification/naming of PR source vs. + # a pushed branch (e.g. master updated by a PR merge) is + # tricky. + branches: [ "master" ] + schedule: + - cron: '45 12 * * 0' + workflow_dispatch: + # Allow manually running the action, e.g. if disabled after some quietness in the source + +permissions: + checks: write + contents: write + issues: write + pull-requests: write + +jobs: + make-dist-tarballs: + name: "Make Dist and Docs Tarballs, see workflow page for links" + # FIXME: Prepare/maintain a container image with pre-installed + # WMNut build/tooling prereqs (save about 3 minutes per run!) + # Maybe https://aschmelyun.com/blog/using-docker-run-inside-of-github-actions/ + # => https://github.com/addnab/docker-run-action can help + runs-on: ubuntu-latest + + strategy: + fail-fast: false + + steps: + - name: Checkout repository + uses: actions/checkout@v5 + with: + fetch-depth: 0 + fetch-tags: true + # NOTE: pull_request_target protects us by using the workflow definition + # from target branch (so trusted operations like artifact management can + # be used), but it also uses the code from that branch too, so fix it back. + # Maybe use "github.event.pull_request.merge_commit_sha" instead?.. though + # it may be null if the PR is not mergeable or is still checked for that, + # or may return the commit for PREVIOUS pushed iteration's merge to target. + # See more ideas in https://github.com/actions/checkout/issues/518 : + ref: "${{ github.event.pull_request.number > 0 && github.event.pull_request.head.sha || github.ref_name }}" + persist-credentials: false + + # https://github.com/marketplace/actions/substitute-string + # See also examples in https://github.com/dhimmel/dump-actions-context/ + # Note it warns about "unexpected input(s)" with replacement tokens below, + # as they are by design not predefined, as far as actions API is concened. + # They still work for substitutions though. + - uses: bluwy/substitute-string-action@v3 + id: subst-github-ref-name + with: + _input-text: "${{ github.event.pull_request.number > 0 && format('PR-{0}', github.event.pull_request.number) || github.head_ref || github.ref_name }}" + " ": _ + "/": _ + + - name: Debug PR/branch identification + run: | + echo "steps.subst-github-ref-name.outputs.result='${{ steps.subst-github-ref-name.outputs.result }}'" || true + echo "github.event.pull_request.number='${{ github.event.pull_request.number }}'" || true + echo "format('PR-{0}', github.event.pull_request.number)='${{ format('PR-{0}', github.event.pull_request.number) }}'" || true + echo "github.head_ref='${{ github.head_ref }}'" || true + echo "github.ref='${{ github.ref }}'" || true + echo "github.ref_name='${{ github.ref_name }}'" || true + + # Make build identification more useful (so we use no fallbacks in script) + - name: Try to get more Git metadata + run: | + git describe || { + git remote -v || true + git branch -a || true + for R in `git remote` ; do git fetch $R master ; done || true + git fetch --tags + pwd ; ls -la + echo "=== Known commits in history:" + git log --oneline | wc -l + echo "=== Recent commits in history:" + git log -2 || true + echo "=== Known tags:" + git tag || true + echo "=== Try to ensure 'git describe' works:" + git describe || { + git fetch --all && for R in `git remote` ; do for T in `git tag` ; do git fetch $R $T ; done ; done + git describe || { + TEST_REF="`git symbolic-ref --short HEAD 2>/dev/null || cat .git/HEAD`" && [ -n "${TEST_REF}" ] && git checkout master && git pull --all && git checkout "${TEST_REF}" + git describe || true + } + } + } + + # Using hints from https://askubuntu.com/questions/272248/processing-triggers-for-man-db + # and our own docs/config-prereqs.txt + # NOTE: Currently installing the MAX prerequisite footprint, + # which for building just the docs may be a bit of an overkill. + - name: WMNut CI Prerequisite packages (Ubuntu, GCC) + run: | + echo "set man-db/auto-update false" | sudo debconf-communicate + sudo dpkg-reconfigure man-db + sudo apt update + sudo apt install \ + gcc g++ clang \ + libxpm-dev libxext-dev libupsclient-dev libc6-dev-amd64-cross libgcc-s1-amd64-cross ccache \ + || exit + date > .timestamp-init + + - name: Prepare ccache + # Based on https://docs.github.com/en/actions/reference/workflows-and-actions/dependency-caching#example-using-the-cache-action example + id: cache-ccache + uses: actions/cache@v4 + env: + compiler: 'CC=gcc CXX=g++' + cache-name: cache-ccache-${{ env.compiler }} + with: + path: | + ~/.ccache + ~/.cache/ccache + ~/.config/ccache/ccache.conf + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/.timestamp-init') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + + - name: CCache stats before build + run: | + ccache -sv || ccache -s || echo "FAILED to read ccache info, oh well" + rm -f .timestamp-init + + #- name: Debug gitlog2version processing + # run: bash -x ./tools/gitlog2version.sh || true + + - name: WMNut CI Build Configuration + env: + compiler: 'CC=gcc CXX=g++' + run: | + PATH="/usr/lib/ccache:$PATH" ; export PATH + CCACHE_COMPRESS=true; export CCACHE_COMPRESS + ccache --version || true + ( ${{env.compiler}} ; echo "=== CC: $CC => `command -v $CC` =>" ; $CC --version ; echo "=== CXX: $CXX => `command -v $CXX` =>" ; $CXX --version ) || true + ./autogen.sh && \ + ./configure ${{env.compiler}} --enable-debug --enable-Werror + + # NOTE: In this scenario we do not build actually WMNut in the main + # checkout directory, at least not explicitly (recipe may generate + # some files like man pages to fulfill the "dist" requirements; + # for now this may generate some libs to figure out their IDs). + # We do `make docs` to provide them as a separate tarball just + # in case, later. + # DO NOT `make dist-files` here as it includes `dist-sig` and + # needs a GPG keychain with maintainers' secrets deployed locally. + - name: WMNut CI Build to create "dist" tarball and related files + env: + compiler: 'CC=gcc CXX=g++' + run: | + PATH="/usr/lib/ccache:$PATH" ; export PATH + CCACHE_COMPRESS=true; export CCACHE_COMPRESS + ccache --version || true + ( ${{env.compiler}} ; echo "=== CC: $CC => `command -v $CC` =>" ; $CC --version ; echo "=== CXX: $CXX => `command -v $CXX` =>" ; $CXX --version ) || true + make -s -j 8 dist dist-hash + + - name: WMNut CI Build to verify "dist" tarball build + env: + compiler: 'CC=gcc CXX=g++' + run: | + PATH="/usr/lib/ccache:$PATH" ; export PATH + CCACHE_COMPRESS=true; export CCACHE_COMPRESS + ccache --version || true + ( ${{env.compiler}} ; echo "=== CC: $CC => `command -v $CC` =>" ; $CC --version ; echo "=== CXX: $CXX => `command -v $CXX` =>" ; $CXX --version ) || true + make -s -j 8 distcheck + + - name: WMNut CI Build to verify "dist" tarball build self-reproducibility + env: + compiler: 'CC=gcc CXX=g++' + run: | + PATH="/usr/lib/ccache:$PATH" ; export PATH + CCACHE_COMPRESS=true; export CCACHE_COMPRESS + ccache --version || true + ( ${{env.compiler}} ; echo "=== CC: $CC => `command -v $CC` =>" ; $CC --version ; echo "=== CXX: $CXX => `command -v $CXX` =>" ; $CXX --version ) || true + make -s -j 8 distcheck-completeness + + - name: CCache stats after distcheck + run: ccache -sv || ccache -s || echo "FAILED to read ccache info, oh well" + + # Inspired by https://javahelps.com/manage-github-artifact-storage-quota + # Note that the code below wipes everything matched by the filter! + # We may want another script block (after this cleanup of obsolete data) + # to iterate clearing the way build by build until there's X MB available + # (at least 300KB as of Nov 2025). + - if: env.GITHUB_REF_TYPE != 'tag' && steps.subst-github-ref-name.outputs.result != 'master' + name: Delete Old Artifacts for this feature branch/PR + uses: actions/github-script@v6 + id: delete_old_artifact_for_pr + continue-on-error: true + with: + script: | + const res = await github.rest.actions.listArtifactsForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + }) + + res.data.artifacts + .filter(({ name }) => name === 'WMNUT-tarballs-${{ steps.subst-github-ref-name.outputs.result }}') + .forEach(({ id }) => { + github.rest.actions.deleteArtifact({ + owner: context.repo.owner, + repo: context.repo.repo, + artifact_id: id, + }) + }) + + - name: Upload tarball and its checksum artifacts + uses: actions/upload-artifact@v4 + id: upload_artifact + with: + name: WMNUT-tarballs-${{ steps.subst-github-ref-name.outputs.result }} + path: | + wmnut-*.tar* + compression-level: 0 + overwrite: true + + # NOTE: Despite the docs examples, due to GH API changes in Mar 2025 + # this action can no longer update an existing check, only create a + # new one. Also calls authenticated with "github-actions" GH App may + # not set "details_url" freely... + # https://docs.github.com/en/rest/checks/runs?apiVersion=2022-11-28 + - name: "GHA-01: Create new GH Check report - shell/cURL" + if: always() + continue-on-error: true + env: + artifact_name: "WMNUT-tarballs-${{ steps.subst-github-ref-name.outputs.result }}.zip" + artifact_url: ${{ steps.upload_artifact.outputs.artifact-url }} + ref: ${{ github.event.pull_request.head.sha || github.sha }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + curl -L \ + -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer ${{ env.GITHUB_TOKEN }}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/${{ github.repository }}/check-runs \ + -d '{"head_sha": "${{ env.ref }}", "name": "URL for ${{ env.artifact_name }}", "details_url": "${{ env.artifact_url }}", "status": "completed", "conclusion": "${{ job.status }}", "output": {"title": "${{ env.artifact_url }}", "summary": "Dist and Docs [${{ env.artifact_name }}](${{ env.artifact_url }}) are available for commit ${{ env.ref }}"} }' diff --git a/.github/workflows/codeql.yml b/.github/workflows/05-codeql.yml similarity index 78% rename from .github/workflows/codeql.yml rename to .github/workflows/05-codeql.yml index 01ae10f..e8bfb1d 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/05-codeql.yml @@ -1,7 +1,7 @@ # The contents below are based on sample configuration from CodeQL # and on the variant of that file used in the main NUT repository. # -name: "CodeQL" +name: "GHA-05: CodeQL" on: push: @@ -34,13 +34,16 @@ jobs: # Build with OS-provided NUT package (or build v2.8.0 if pkg is too old), or NUT trunk? os: [ 'ubuntu-latest' ] # TOTHINK: windows-latest, macos-latest? - build-mode: [ 'none' ] + build-mode: [ 'manual' ] # https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages - # Abusing "none" here to try building with ccache (and + # Abusing "manual" here to try building with ccache (and # have codeql not intercept that build but parse C/C++ # files on its own), and "manual" to custom-build without; # the "autobuild" mode is handled by codeql itself but # would probably ignore our CC/CXX setting + # NOTE: We do not add ccache to PATH when actually compiling NUT code + # (we only speed up "configure" stages), so compilation always happens + # and is parsed by current CodeQL detectors of the day as they evolve! compiler: [ 'CC=gcc CXX=g++', 'CC=clang CXX=clang++' ] steps: @@ -49,13 +52,13 @@ jobs: # Using hints from https://askubuntu.com/questions/272248/processing-triggers-for-man-db - if: matrix.language == 'cpp' && matrix.os == 'ubuntu-latest' - name: Initialize dependencies (Ubuntu) + name: NUT CI Prerequisite packages (Ubuntu) run: | echo "set man-db/auto-update false" | sudo debconf-communicate sudo dpkg-reconfigure man-db - sudo apt-get update + sudo apt update case x"${{matrix.compiler}}" in x*clang*) sudo apt install clang ;; x*) sudo apt install gcc g++ ;; esac - sudo apt-get install libxpm-dev libxext-dev libupsclient-dev libc6-dev-amd64-cross libgcc-s1-amd64-cross ccache + sudo apt install libxpm-dev libxext-dev libupsclient-dev libc6-dev-amd64-cross libgcc-s1-amd64-cross ccache date > .timestamp-init - name: Prepare ccache @@ -122,9 +125,15 @@ jobs: uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} - queries: +security-and-quality build-mode: ${{ matrix.build-mode }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the (whole) list here with "+" to use these queries and those in the config file. + # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + queries: +security-extended,security-and-quality + # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). + # If this step fails, then you should remove it and run the build manually (see below) # https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages - if: matrix.build-mode == 'autobuild' name: Autobuild @@ -134,21 +143,29 @@ jobs: CODEQL_EXTRACTOR_CPP_AUTOINSTALL_DEPENDENCIES: false - if: matrix.build-mode != 'autobuild' && matrix.language == 'cpp' - name: WMNut CI Build + name: WMNut CI Build Configuration run: | case x"${{matrix.build-mode}}" in - xnone) + xmanual) PATH="/usr/lib/ccache:$PATH" ; export PATH CCACHE_COMPRESS=true; export CCACHE_COMPRESS ccache --version || true ;; - xmanual|*) - echo "NOTE: NOT USING CCACHE for the CI-tested code base" >&2 + xnone|*) + echo "NOTE: NOT USING CCACHE for the CI-tested code base configuration" >&2 ;; esac ( ${{matrix.compiler}} ; echo "=== CC: $CC => `command -v $CC` =>" ; $CC --version ; echo "=== CXX: $CXX => `command -v $CXX` =>" ; $CXX --version ) || true - ./autogen.sh + ./autogen.sh && \ ./configure ${{matrix.compiler}} --enable-debug --enable-Werror + + # NOTE: We do not add ccache to PATH here, so compilation always happens + # and is parsed by current CodeQL detectors of the day as they evolve: + - if: matrix.build-mode != 'autobuild' && matrix.language == 'cpp' + name: WMNut CI Build Compilation + run: | + echo "NOTE: NOT USING CCACHE for the CI-tested code base compilation" >&2 + ( ${{matrix.compiler}} ; echo "=== CC: $CC => `command -v $CC` =>" ; $CC --version ; echo "=== CXX: $CXX => `command -v $CXX` =>" ; $CXX --version ) || true make -s -j 8 || exit - if: matrix.build-mode != 'autobuild' && matrix.language == 'cpp' diff --git a/Makefile.am b/Makefile.am index ceeb938..8b45ddb 100644 --- a/Makefile.am +++ b/Makefile.am @@ -29,4 +29,63 @@ dist-hash: wmnut-@PACKAGE_VERSION@.tar.gz md5sum wmnut-@PACKAGE_VERSION@.tar.gz > wmnut-@PACKAGE_VERSION@.tar.gz.md5 sha256sum wmnut-@PACKAGE_VERSION@.tar.gz > wmnut-@PACKAGE_VERSION@.tar.gz.sha256 +# Partially snatched from automake generated code +distcheck-completeness: dist + @chmod -R +w $(distdir) $(distdir)-orig-* $(distdir)-derived-* || true + @rm -rf $(distdir) $(distdir)-orig-* $(distdir)-derived-* || true + @case '$(DIST_ARCHIVES)' in \ + *.tar.gz*) \ + eval GZIP= gzip $(GZIP_ENV) -dc $(distdir).tar.gz | $(am__untar) ;;\ + *.tar.bz2*) \ + bzip2 -dc $(distdir).tar.bz2 | $(am__untar) ;;\ + *.tar.lz*) \ + lzip -dc $(distdir).tar.lz | $(am__untar) ;;\ + *.tar.xz*) \ + xz -dc $(distdir).tar.xz | $(am__untar) ;;\ + *.tar.Z*) \ + uncompress -c $(distdir).tar.Z | $(am__untar) ;;\ + *.shar.gz*) \ + eval GZIP= gzip $(GZIP_ENV) -dc $(distdir).shar.gz | unshar ;;\ + *.zip*) \ + unzip $(distdir).zip ;;\ + *.tar.zst*) \ + zstd -dc $(distdir).tar.zst | $(am__untar) ;;\ + esac + +@RES=0 ; \ + { cp -rf $(distdir) $(distdir)-orig-$$$$ \ + && am__cwd="`pwd`" \ + && $(am__cd) $(distdir) \ + && ./configure \ + $(AM_DISTCHECK_CONFIGURE_FLAGS) \ + $(DISTCHECK_CONFIGURE_FLAGS) \ + --prefix="`pwd`/.inst" \ + && $(MAKE) $(AM_MAKEFLAGS) dist \ + && case '$(DIST_ARCHIVES)' in \ + *.tar.gz*) \ + eval GZIP= gzip $(GZIP_ENV) -dc $(distdir).tar.gz | $(am__untar) ;;\ + *.tar.bz2*) \ + bzip2 -dc $(distdir).tar.bz2 | $(am__untar) ;;\ + *.tar.lz*) \ + lzip -dc $(distdir).tar.lz | $(am__untar) ;;\ + *.tar.xz*) \ + xz -dc $(distdir).tar.xz | $(am__untar) ;;\ + *.tar.Z*) \ + uncompress -c $(distdir).tar.Z | $(am__untar) ;;\ + *.shar.gz*) \ + eval GZIP= gzip $(GZIP_ENV) -dc $(distdir).shar.gz | unshar ;;\ + *.zip*) \ + unzip $(distdir).zip ;;\ + *.tar.zst*) \ + zstd -dc $(distdir).tar.zst | $(am__untar) ;;\ + esac \ + && mv $(distdir) ../$(distdir)-derived-$$$$ \ + && $(MAKE) $(AM_MAKEFLAGS) maintainer-clean \ + && cd "$$am__cwd" \ + && echo " $@ diff $(distdir)-orig-$$$$ $(distdir)-derived-$$$$" \ + && diff $(distdir)-orig-$$$$ $(distdir)-derived-$$$$ ; \ + } || RES=$$? ; \ + rm -rf $(distdir)-orig-$$$$ $(distdir)-derived-$$$$ $(distdir) ; \ + exit $$RES + @echo "SUCCESS: $(distdir) archives are self-reproducing" + MAINTAINERCLEANFILES = wmnut-*.tar* diff --git a/autogen.sh b/autogen.sh index 8f7a487..9a909a7 100755 --- a/autogen.sh +++ b/autogen.sh @@ -15,4 +15,13 @@ if (command -v pkg-config) >/dev/null 2>/dev/null ; then esac fi -autoreconf -i +RES=0 +autoreconf -i || RES=$? + +if [ x"$RES" = x0 ] ; then + echo "$0: SUCCESS" +else + echo "$0: FAILED ($RES)" +fi >&2 + +exit $RES