From 8a1823b41e8e03d4ca0ad9044241fa3a33cf22ff Mon Sep 17 00:00:00 2001 From: Mahyar McDonald Date: Fri, 31 Oct 2025 12:04:24 -0700 Subject: [PATCH] aptfile support --- README.md | 45 +++++++++++++++++++++++++++++++- action.yml | 4 +-- lib.sh | 25 ++++++++++++++++++ post_cache_action.sh | 17 +++++++++++- pre_cache_action.sh | 61 ++++++++++++++++++++++++++++++++++++-------- 5 files changed, 138 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index c0d1c3b..dc65005 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ There are three kinds of version labels you can use. ### Inputs -- `packages` - Space delimited list of packages to install. +- `packages` - Space delimited list of packages to install. If not provided, packages will be read from `Aptfile` at the repository root if it exists. Packages from both the input and `Aptfile` will be merged if both are provided. - `version` - Version of cache to load. Each version will have its own cache. Note, all characters except spaces are allowed. - `execute_install_scripts` - Execute Debian package pre and post install script upon restore. See [Caveats / Non-file Dependencies](#non-file-dependencies) for more information. - `empty_packages_behavior` - Desired behavior when the given `packages` is empty. `'error'` (default), `'warn'` or `'ignore'`. @@ -121,6 +121,49 @@ install_from_multiple_repos: version: 1.0 ``` +### Using Aptfile + +You can also use an `Aptfile` at your repository root to specify packages. The action will automatically read and cache packages from the `Aptfile` if it exists. Comments (lines starting with `#`) and inline comments are supported. + +**Example Aptfile:** +``` +# Core development tools +cmake +autoconf +git +gh + +# Build dependencies +build-essential +libssl-dev +python3-dev +``` + +**Example workflow using Aptfile:** +```yaml +name: Build with Aptfile +on: push +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: awalsh128/cache-apt-pkgs-action@latest + with: + version: v1 + # packages input can be omitted if using Aptfile only + - name: Build + run: make +``` + +You can also combine packages from both the input and `Aptfile`: +```yaml +- uses: awalsh128/cache-apt-pkgs-action@latest + with: + version: v1 + packages: protobuf-compiler sd # Additional packages beyond Aptfile +``` + ## Caveats ### Non-file Dependencies diff --git a/action.yml b/action.yml index 4b30ff6..3deb135 100644 --- a/action.yml +++ b/action.yml @@ -7,8 +7,8 @@ branding: inputs: packages: - description: 'Space delimited list of packages to install. Version can be specified optionally using APT command syntax of = (e.g. xdot=1.2-2).' - required: true + description: 'Space delimited list of packages to install. Version can be specified optionally using APT command syntax of = (e.g. xdot=1.2-2). If not provided, packages will be read from Aptfile at repository root if it exists.' + required: false default: '' version: description: 'Version of cache to load. Each version will have its own cache. Note, all characters except spaces are allowed.' diff --git a/lib.sh b/lib.sh index 755d939..92ca9a6 100755 --- a/lib.sh +++ b/lib.sh @@ -157,6 +157,31 @@ function validate_bool { fi } +############################################################################### +# Parses an Aptfile and extracts package names. +# Arguments: +# File path to the Aptfile. +# Returns: +# Space delimited list of package names (comments and empty lines removed). +############################################################################### +function parse_aptfile { + local aptfile_path="${1}" + if test ! -f "${aptfile_path}"; then + echo "" + return + fi + + # Remove lines starting with #, remove inline comments (everything after #), + # trim whitespace, remove empty lines, and join with spaces + grep -v '^[[:space:]]*#' "${aptfile_path}" \ + | sed 's/#.*$//' \ + | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' \ + | grep -v '^$' \ + | tr '\n' ' ' \ + | sed 's/[[:space:]]\+/ /g' \ + | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' +} + ############################################################################### # Writes the manifest to a specified file. # Arguments: diff --git a/post_cache_action.sh b/post_cache_action.sh index a6a5689..c40bb35 100755 --- a/post_cache_action.sh +++ b/post_cache_action.sh @@ -29,7 +29,22 @@ test "${debug}" = "true" && set -x add_repository="${6}" # List of the packages to use. -packages="${@:7}" +# Try to read from saved file first (includes Aptfile packages), fallback to input +packages_filepath="${cache_dir}/packages.txt" +if test -f "${packages_filepath}"; then + packages="$(cat "${packages_filepath}")" + # Check if packages.txt is empty or contains only whitespace + if test -z "${packages}"; then + log "packages.txt exists but is empty, falling back to input packages" + packages="${@:7}" + else + log "Using packages from cache directory (includes Aptfile if present)" + fi +else + # Fallback to input packages (for backwards compatibility) + packages="${@:7}" + log "Using packages from input (Aptfile not processed)" +fi if test "${cache_hit}" = "true"; then ${script_dir}/restore_pkgs.sh "${cache_dir}" "${cache_restore_root}" "${execute_install_scripts}" "${debug}" diff --git a/pre_cache_action.sh b/pre_cache_action.sh index 5cb64cb..880c34f 100755 --- a/pre_cache_action.sh +++ b/pre_cache_action.sh @@ -30,39 +30,71 @@ add_repository="${5}" # List of the packages to use. input_packages="${@:6}" -# Trim commas, excess spaces, and sort. -log "Normalizing package list..." -packages="$(get_normalized_package_list "${input_packages}")" -log "done" +# Check for Aptfile at repository root and merge with input packages +aptfile_path="${GITHUB_WORKSPACE:-.}/Aptfile" +aptfile_packages="" +if test -n "${GITHUB_WORKSPACE}" && test -f "${aptfile_path}"; then + log "Found Aptfile at ${aptfile_path}, parsing packages..." + aptfile_packages="$(parse_aptfile "${aptfile_path}")" + if test -n "${aptfile_packages}"; then + log "Parsed $(echo "${aptfile_packages}" | wc -w) package(s) from Aptfile" + else + log "Aptfile is empty or contains only comments" + fi +elif test -z "${GITHUB_WORKSPACE}"; then + log "GITHUB_WORKSPACE not set, skipping Aptfile check" +else + log "No Aptfile found at ${aptfile_path}" +fi + +# Merge input packages with Aptfile packages +if test -n "${input_packages}" && test -n "${aptfile_packages}"; then + combined_packages="${input_packages} ${aptfile_packages}" + log "Merging packages from input and Aptfile..." +elif test -n "${aptfile_packages}"; then + combined_packages="${aptfile_packages}" + log "Using packages from Aptfile only..." +elif test -n "${input_packages}"; then + combined_packages="${input_packages}" + log "Using packages from input only..." +else + combined_packages="" +fi # Create cache directory so artifacts can be saved. mkdir -p ${cache_dir} -log "Validating action arguments (version='${version}', packages='${packages}')..."; +log "Validating action arguments (version='${version}', packages='${combined_packages}')..."; if grep -q " " <<< "${version}"; then log "aborted" log "Version value '${version}' cannot contain spaces." >&2 exit 2 fi -# Is length of string zero? -if test -z "${packages}"; then +# Check if packages are empty before calling get_normalized_package_list +# (which would error if called with empty input) +if test -z "${combined_packages}"; then case "$EMPTY_PACKAGES_BEHAVIOR" in ignore) exit 0 ;; warn) - echo "::warning::Packages argument is empty." + echo "::warning::Packages argument is empty. Please provide packages via the 'packages' input or create an Aptfile at the repository root." exit 0 ;; *) log "aborted" - log "Packages argument is empty." >&2 + log "Packages argument cannot be empty. Please provide packages via the 'packages' input or create an Aptfile at the repository root." >&2 exit 3 ;; esac fi +# Trim commas, excess spaces, and sort. +log "Normalizing package list..." +packages="$(get_normalized_package_list "${combined_packages}")" +log "done" + validate_bool "${execute_install_scripts}" execute_install_scripts 4 # Basic validation for repository parameter @@ -119,5 +151,14 @@ log "- Value hashed as '${key}'." log "done" key_filepath="${cache_dir}/cache_key.md5" -echo ${key} > ${key_filepath} +echo "${key}" > "${key_filepath}" log "Hash value written to ${key_filepath}" + +# Save normalized packages to file so post_cache_action.sh can use them +packages_filepath="${cache_dir}/packages.txt" +echo "${packages}" > "${packages_filepath}" +if test ! -f "${packages_filepath}"; then + log "Failed to write packages.txt" >&2 + exit 4 +fi +log "Normalized packages saved to ${packages_filepath}"