├── Dockerfile ├── .pre-commit-config.yaml ├── LICENSE ├── README.md ├── action.yaml └── entrypoint.sh /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM archlinux:base-devel 2 | 3 | COPY entrypoint.sh /entrypoint.sh 4 | 5 | ENTRYPOINT ["/entrypoint.sh"] 6 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v4.0.1 4 | hooks: 5 | - id: check-merge-conflict 6 | - id: check-yaml 7 | - id: end-of-file-fixer 8 | - id: trailing-whitespace 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Eric Langlois 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pkgbuild-action 2 | GitHub action to build and check a PKGBUILD package 3 | 4 | ## Features 5 | * Checks that .SRCINFO matches PKGBUILD if .SRCINFO exists 6 | * Builds package(s) with makepkg (configurable arguments) 7 | * Runs on a bare minimum Arch Linux install to help detect missing dependencies 8 | * Outputs built package archives 9 | * Checks PKGBUILD and package archives with [namcap](https://wiki.archlinux.org/index.php/namcap) 10 | 11 | ## Interface 12 | Inputs: 13 | * `pkgdir`: Relative path to directory containing the PKGBUILD file 14 | (repo root by default). 15 | * `aurDeps`: Support AUR dependencies if nonempty. 16 | * `namcapDisable`: Disable namcap checks if nonempty. 17 | * `namcapRules`: A comma-separated list of rules for namcap to run. 18 | * `namcapExcludeRules`: A comma-separated list of rules for namcap not to run. 19 | * `makepkgArgs`: Additional arguments to pass to `makepkg`. 20 | 21 | Outputs: 22 | * `pkgfileN`: Filename of Nth built package archive (ordered as `makepkg --packagelist`). 23 | Empty if not built. N = 0, 1, ... 24 | 25 | ## Example Usage 26 | ```yaml 27 | name: PKGBUILD CI 28 | 29 | on: [push, pull_request] 30 | 31 | jobs: 32 | pkgbuild: 33 | runs-on: ubuntu-latest 34 | steps: 35 | - name: Checkout 36 | uses: actions/checkout@v2 37 | - name: Makepkg Build and Check 38 | id: makepkg 39 | uses: edlanglois/pkgbuild-action@v1 40 | - name: Print Package Files 41 | run: | 42 | echo "Successfully created the following package archive" 43 | echo "Package: ${{ steps.makepkg.outputs.pkgfile0 }}" 44 | # Uncomment to upload the package as an artifact 45 | # - name: Upload Package Archive 46 | # uses: actions/upload-artifact@v2 47 | # with: 48 | # path: ${{ steps.makepkg.outputs.pkgfile0 }} 49 | ``` 50 | -------------------------------------------------------------------------------- /action.yaml: -------------------------------------------------------------------------------- 1 | name: Makepkg Build and Check 2 | author: Eric Langlois 3 | description: Build and check a PKGBUILD package 4 | branding: 5 | color: blue 6 | icon: chevron-up 7 | inputs: 8 | pkgdir: 9 | description: "Relative path to directory containing the PKGBUILD file." 10 | required: false 11 | default: "." 12 | pacmanConf: 13 | description: "Relative path to alternative configuration file for pacman." 14 | required: false 15 | default: "/etc/pacman.conf" 16 | aurDeps: 17 | description: "Support AUR dependencies if nonempty." 18 | required: false 19 | default: "" 20 | namcapDisable: 21 | description: "Disable namcap checks if nonempty." 22 | required: false 23 | default: "" 24 | namcapRules: 25 | description: "A comma-separated list of rules for namcap to run." 26 | required: false 27 | default: "" 28 | namcapExcludeRules: 29 | description: "A comma-separated list of rules for namcap not to run." 30 | required: false 31 | default: "" 32 | makepkgArgs: 33 | description: "Additional arguments to pass to makepkg." 34 | required: false 35 | default: "" 36 | makepkgConf: 37 | description: "Relative path to alternative configuration file for makepkg." 38 | required: false 39 | default: "/etc/makepkg.conf" 40 | multilib: 41 | description: "Install 'multilib-devel' to build lib32 packages." 42 | required: false 43 | default: false 44 | repoReleaseTag: 45 | description: "Tag to use as repository name." 46 | required: false 47 | default: "" 48 | outputs: 49 | pkgfile0: 50 | description: "Filename of the first generated package archive. Usually only one." 51 | pkgfile1: 52 | description: "Filename of the 2nd generated package archive." 53 | pkgfile2: 54 | description: "Filename of the 3rd generated package archive." 55 | pkgfile3: 56 | description: "Filename of the 4th generated package archive. etc." 57 | oldfile0: 58 | description: "Filename of the first removed package archive. Usually only one." 59 | oldfile1: 60 | description: "Filename of the 2nd removed package archive." 61 | oldfile2: 62 | description: "Filename of the 3rd removed package archive." 63 | oldfile3: 64 | description: "Filename of the 4th removed package archive. etc." 65 | repofile0: 66 | description: ".db" 67 | repofile1: 68 | description: ".db.tar.gz" 69 | repofile2: 70 | description: ".files" 71 | repofile3: 72 | description: ".files.tar.gz" 73 | runs: 74 | using: 'docker' 75 | image: 'Dockerfile' 76 | -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | FILE="$(basename "$0")" 5 | 6 | # Enable the multilib repository 7 | cat << EOM >> /etc/pacman.conf 8 | [multilib] 9 | Include = /etc/pacman.d/mirrorlist 10 | EOM 11 | 12 | # Add alerque repository for paru 13 | cat << EOM >> /etc/pacman.conf 14 | [alerque] 15 | SigLevel = Optional TrustAll 16 | Server = https://arch.alerque.com/\$arch 17 | EOM 18 | pacman-key --recv-keys 63CC496475267693 19 | 20 | if [ -n "${INPUT_PACMANCONF:-}" ]; then 21 | echo "Using ${INPUT_PACMANCONF:-} as pacman.conf" 22 | cp "${INPUT_PACMANCONF:-}" /etc/pacman.conf 23 | fi 24 | 25 | if [ -n "${INPUT_MAKEPKGCONF:-}" ]; then 26 | echo "Using ${INPUT_MAKEPKGCONF:-} as makepkg.conf" 27 | cp "${INPUT_MAKEPKGCONF:-}" /etc/makepkg.conf 28 | fi 29 | 30 | pacman -Syu --noconfirm --needed base base-devel 31 | pacman -Syu --noconfirm --needed ccache 32 | #pacman -Syu --noconfirm --needed ccache-ext 33 | 34 | if [ "${INPUT_MULTILIB:-false}" == true ]; then 35 | pacman -Syu --noconfirm --needed multilib-devel 36 | fi 37 | 38 | # Makepkg does not allow running as root 39 | # Create a new user `builder` 40 | # `builder` needs to have a home directory because some PKGBUILDs will try to 41 | # write to it (e.g. for cache) 42 | useradd builder -m 43 | # When installing dependencies, makepkg will use sudo 44 | # Give user `builder` passwordless sudo access 45 | echo "builder ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers 46 | 47 | # Give all users (particularly builder) full access to these files 48 | chmod -R a+rw . 49 | 50 | BASEDIR="$PWD" 51 | cd "${INPUT_PKGDIR:-.}" 52 | 53 | function download_database () { 54 | # Download the repository files if a repository tag has been specified 55 | # This is put here to fail early in case they weren't downloaded 56 | REPOFILES=("${INPUT_REPORELEASETAG:-}".{db{,.tar.gz},files{,.tar.gz}}) 57 | for REPOFILE in "${REPOFILES[@]}"; do 58 | sudo -u builder curl \ 59 | --retry 5 --retry-delay 30 --retry-all-errors \ 60 | --location --fail \ 61 | -o "$REPOFILE" "$GITHUB_SERVER_URL"/"$GITHUB_REPOSITORY"/releases/download/"${INPUT_REPORELEASETAG:-}"/"$REPOFILE" 62 | done 63 | # Delete the `.db` and `repo_name.files` symlinks 64 | rm "${INPUT_REPORELEASETAG:-}".{db,files} || true 65 | } 66 | 67 | if [ -n "${INPUT_REPORELEASETAG:-}" ]; then 68 | # Download database files to test for availability 69 | download_database 70 | # Delete them because they will be downloaded again 71 | rm "${INPUT_REPORELEASETAG:-}".{db,files}.tar.gz 72 | fi 73 | 74 | # Assume that if .SRCINFO is missing then it is generated elsewhere. 75 | # AUR checks that .SRCINFO exists so a missing file can't go unnoticed. 76 | if [ -f .SRCINFO ] && ! sudo -u builder makepkg --printsrcinfo | diff - .SRCINFO; then 77 | echo "::error file=$FILE,line=$LINENO::Mismatched .SRCINFO. Update with: makepkg --printsrcinfo > .SRCINFO" 78 | exit 1 79 | fi 80 | 81 | # Optionally install dependencies from AUR 82 | if [ -n "${INPUT_AURDEPS:-}" ]; then 83 | # First install paru 84 | pacman -Syu --noconfirm paru 85 | 86 | # Extract dependencies from .SRCINFO (depends or depends_x86_64) and install 87 | mapfile -t PKGDEPS < \ 88 | <(sed -n -e 's/^[[:space:]]*\(make\)\?depends\(_x86_64\)\? = \([[:alnum:][:punct:]]*\)[[:space:]]*$/\3/p' .SRCINFO) 89 | sudo -H -u builder paru --sync --noconfirm "${PKGDEPS[@]}" 90 | fi 91 | 92 | # Make the builder user the owner of these files 93 | # Without this, (e.g. only having every user have read/write access to the files), 94 | # makepkg will try to change the permissions of the files itself which will fail since it does not own the files/have permission 95 | # we can't do this earlier as it will change files that are for github actions, which results in warnings in github actions logs. 96 | chown -R builder . 97 | 98 | # Build packages 99 | # INPUT_MAKEPKGARGS is intentionally unquoted to allow arg splitting 100 | # shellcheck disable=SC2086 101 | sudo -H -u builder CCACHE_DIR="$BASEDIR/.ccache" makepkg --syncdeps --noconfirm ${INPUT_MAKEPKGARGS:-} 102 | 103 | # Get array of packages to be built 104 | # shellcheck disable=SC2086 105 | mapfile -t PKGFILES < <( sudo -u builder makepkg --packagelist ${INPUT_MAKEPKGARGS:-}) 106 | echo "Package(s): ${PKGFILES[*]}" 107 | 108 | if [ -n "${INPUT_REPORELEASETAG:-}" ]; then 109 | # Download database files again in case another action updated them in the meantime 110 | download_database 111 | # Create package file list for the old database 112 | zcat "${INPUT_REPORELEASETAG:-}".db.tar.gz | strings | grep '.pkg.tar.' | sort > old_db.packages 113 | fi 114 | 115 | # Report built package archives 116 | i=0 117 | for PKGFILE in "${PKGFILES[@]}"; do 118 | # Replace colon (:) in files name because releases don't like it 119 | # It seems to not mess with pacman so it doesn't need to be guarded 120 | srcdir="$(dirname "$PKGFILE")" 121 | srcfile="$(basename "$PKGFILE")" 122 | if [[ "$srcfile" == *:* ]]; then 123 | dest="$srcdir/${srcfile//:/.}" 124 | mv "$PKGFILE" "$dest" 125 | PKGFILE="$dest" 126 | fi 127 | # makepkg reports absolute paths, must be relative for use by other actions 128 | RELPKGFILE="$(realpath --relative-base="$BASEDIR" "$PKGFILE")" 129 | # Caller arguments to makepkg may mean the pacakge is not built 130 | if [ -f "$PKGFILE" ]; then 131 | echo "pkgfile$i=$RELPKGFILE" >> $GITHUB_OUTPUT 132 | # Optionally add the packages to a makeshift repository in GitHub releases 133 | if [ -n "${INPUT_REPORELEASETAG:-}" ]; then 134 | sudo -u builder repo-add "${INPUT_REPORELEASETAG:-}".db.tar.gz "$(basename "$PKGFILE")" 135 | else 136 | echo "Skipping repository update for $RELPKGFILE" 137 | fi 138 | else 139 | echo "Archive $RELPKGFILE not built" 140 | fi 141 | (( ++i )) 142 | done 143 | 144 | if [ -n "${INPUT_REPORELEASETAG:-}" ]; then 145 | # Delete the `.db` and `repo_name.files` symlinks 146 | rm "${INPUT_REPORELEASETAG:-}".{db,files} 147 | # Copy repo archives to their suffix-less symlinks because symlinks are not uploaded to GitHub releases 148 | cp "${INPUT_REPORELEASETAG:-}".db{.tar.gz,} 149 | cp "${INPUT_REPORELEASETAG:-}".files{.tar.gz,} 150 | REPOFILES=("${INPUT_REPORELEASETAG:-}".{db{,.tar.gz},files{,.tar.gz}}) 151 | j=0 152 | for REPOFILE in "${REPOFILES[@]}"; do 153 | RELREPOFILE="$(realpath --relative-base="$BASEDIR" "$(realpath -s "$REPOFILE")")" 154 | echo "repofile$j=$RELREPOFILE" >> $GITHUB_OUTPUT 155 | (( ++j )) 156 | done 157 | # List package files removed from the database 158 | zcat "${INPUT_REPORELEASETAG:-}".db.tar.gz | strings | grep '.pkg.tar.' | sort > new_db.packages 159 | k=0 160 | for OLDFILE in $(diff {old,new}_db.packages | grep -E "^<" | cut -c3-);do 161 | echo "oldfile$k=$OLDFILE" >> $GITHUB_OUTPUT 162 | (( ++k )) 163 | done 164 | fi 165 | 166 | function prepend () { 167 | # Prepend the argument to each input line 168 | while read -r line; do 169 | echo "$1$line" 170 | done 171 | } 172 | 173 | function namcap_check() { 174 | # Run namcap checks 175 | # Installing namcap after building so that makepkg happens on a minimal 176 | # install where any missing dependencies can be caught. 177 | pacman -S --noconfirm --needed namcap 178 | 179 | NAMCAP_ARGS=() 180 | if [ -n "${INPUT_NAMCAPRULES:-}" ]; then 181 | NAMCAP_ARGS+=( "-r" "${INPUT_NAMCAPRULES}" ) 182 | fi 183 | if [ -n "${INPUT_NAMCAPEXCLUDERULES:-}" ]; then 184 | NAMCAP_ARGS+=( "-e" "${INPUT_NAMCAPEXCLUDERULES}" ) 185 | fi 186 | 187 | # For reasons that I don't understand, sudo is not resetting '$PATH' 188 | # As a result, namcap finds program paths in /usr/sbin instead of /usr/bin 189 | # which makes namcap fail to identify the packages that provide the 190 | # program and so it emits spurious warnings. 191 | # More details: https://bugs.archlinux.org/task/66430 192 | # 193 | # Work around this issue by putting bin ahead of sbin in $PATH 194 | export PATH="/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin" 195 | 196 | namcap "${NAMCAP_ARGS[@]}" PKGBUILD \ 197 | | prepend "::warning file=$FILE,line=$LINENO::" 198 | for PKGFILE in "${PKGFILES[@]}"; do 199 | if [ -f "$PKGFILE" ]; then 200 | RELPKGFILE="$(realpath --relative-base="$BASEDIR" "$PKGFILE")" 201 | namcap "${NAMCAP_ARGS[@]}" "$PKGFILE" \ 202 | | prepend "::warning file=$FILE,line=$LINENO::$RELPKGFILE:" 203 | fi 204 | done 205 | } 206 | 207 | if [ -z "${INPUT_NAMCAPDISABLE:-}" ]; then 208 | namcap_check 209 | fi 210 | --------------------------------------------------------------------------------