diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..74d64a5 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +[*] +end_of_line = lf +charset = utf-8 + +[*.{nix,json,sh}] +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true diff --git a/.forgejo/workflows/check.yml b/.forgejo/workflows/check.yml new file mode 100644 index 0000000..9e2a6ce --- /dev/null +++ b/.forgejo/workflows/check.yml @@ -0,0 +1,41 @@ +on: + push: +jobs: + check: + runs-on: nixos + strategy: + matrix: + check: + - treefmt + steps: + - uses: "https://git.salame.cl/actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683" # v4 + - name: Run checks + run: | + nix --version + # shellcheck disable=SC2016 + nix build --print-build-logs '.#checks.x86_64-linux.${{ matrix.check }}' + check-renovaterc: + runs-on: nixos + steps: + - uses: "https://git.salame.cl/actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683" # v4 + - name: Validate renovaterc.json + run: | + nix --version + nix shell nixpkgs#renovate --command renovate-config-validator + report-size: + runs-on: nixos + needs: check + steps: + - uses: "https://git.salame.cl/actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683" # v4 + - run: nix --version + - name: Create Size Report + uses: ./action.yml + with: + # Create a comment on the associated PR + comment-on-pr: ${{ github.ref_name != 'main' }} + # Generate artifacts on main (to speed up comparisons) + generate-artifact: ${{ github.ref_name == 'main' }} + # Generate comparisons to main + do-comparison: 'true' + # This job's name (so we can find the artifacts) + job-name: report-size diff --git a/README.md b/README.md index 7e50c4a..d3bceb9 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ For more details see the [action.yaml](./action.yml) file. - Reduce the `NAR Size` by reducing the size of the build outputs, e.g. don't copy unnecessary data to the $out dir, optimize binaries for size, etc. - Reduce the `Size` by reducing the dependencies (e.g. `buildInputs`). - Don't worry too much about size, some dependencies are deduplicated, e.g. `glibc` adds ~40MiB to the `Size`, but is generally shared by ~every binary on the system, so, chances are, you are already including it from somewhere else and statically linking with e.g. `musl` is not gonna improve things. + # NixOS Configurations | Name | Size | Size Change | NAR Size | NAR Size Change | diff --git a/comment_on_pr.sh b/comment_on_pr.sh index e3feb0a..e7a7487 100755 --- a/comment_on_pr.sh +++ b/comment_on_pr.sh @@ -11,34 +11,34 @@ util_path="${GITHUB_ACTION_PATH:-.}/utils.sh" # # JSON_FILE can be piped from stdin json_to_md_rows() { - jq --raw-output \ - ".$1[]"' | "| `\(.name)` | \(.size) | \(.narSize) |"' "$2" | - numfmt --suffix=B --to=iec-i --field=4,6 + jq --raw-output \ + ".$1[]"' | "| `\(.name)` | \(.size) | \(.narSize) |"' "$2" | + numfmt --suffix=B --to=iec-i --field=4,6 } # USAGE: json_to_md_rows [JSON_FILE] # # JSON_FILE can be piped from stdin json_to_md_rows_and_change() { - jq --raw-output \ - ".$1[]"' | "| `\(.name)` | \(.size) | \(.sizeChange) | \(.narSize) | \(.narSizeChange) |"' | - numfmt --suffix=B --to=iec-i --field=4,6,8,10 + jq --raw-output \ + ".$1[]"' | "| `\(.name)` | \(.size) | \(.sizeChange) | \(.narSize) | \(.narSizeChange) |"' | + numfmt --suffix=B --to=iec-i --field=4,6,8,10 } # USAGE: has_elements has_elements() { - if [ "${2+set}" = 'set' ]; then - [ "$(jq ".$1 != []" "$2")" = 'true' ] - else - [ "$(jq ".$1 != []")" = 'true' ] - fi + if [ "${2+set}" = 'set' ]; then + [ "$(jq ".$1 != []" "$2")" = 'true' ] + else + [ "$(jq ".$1 != []")" = 'true' ] + fi } # USAGE: markdown_from_report [BASE_REPORT] # # If BASE_REPORT is provided, a comparison will be made markdown_from_report() { - cat <<-"EOF" + cat <<-"EOF" # Flake output sizes @@ -49,12 +49,12 @@ markdown_from_report() { - `Size`: the closure size (size on disk/NAR size + all transitive dependencies). - `NAR Size`: the size of the build output (package without the dependencies). EOF - if [ "${2+set}" = "set" ]; then - cat <<-"EOF" + if [ "${2+set}" = "set" ]; then + cat <<-"EOF" - `[NAR] Size Change`: the amount changed compared to the main branch. EOF - fi - cat <<-"EOF" + fi + cat <<-"EOF" **Tips on reading this data:** @@ -66,62 +66,62 @@ markdown_from_report() { - Don't worry too much about size, some dependencies are deduplicated, e.g. `glibc` adds ~40MiB to the `Size`, but is generally shared by ~every binary on the system, so, chances are, you are already including it from somewhere else and statically linking with e.g. `musl` is not gonna improve things. EOF - if [ "${2+set}" = "set" ]; then - compare=$(jq --slurp --from-file "${GITHUB_ACTION_PATH:-.}/compare.jq" "$1" "$2") - if echo "$compare" | has_elements 'nixosConfigurations'; then - cat <<-"EOF" + if [ "${2+set}" = "set" ]; then + compare=$(jq --slurp --from-file "${GITHUB_ACTION_PATH:-.}/compare.jq" "$1" "$2") + if echo "$compare" | has_elements 'nixosConfigurations'; then + cat <<-"EOF" # NixOS Configurations | Name | Size | Size Change | NAR Size | NAR Size Change | |------|-----:|------------:|---------:|----------------:| EOF - echo "$compare" | json_to_md_rows_and_change "nixosConfigurations" - echo - fi - if echo "$compare" | has_elements 'packages'; then - cat <<-"EOF" + echo "$compare" | json_to_md_rows_and_change "nixosConfigurations" + echo + fi + if echo "$compare" | has_elements 'packages'; then + cat <<-"EOF" # Packages | Name | Size | Size Change | NAR Size | NAR Size Change | |------|-----:|------------:|---------:|----------------:| EOF - echo "$compare" | json_to_md_rows_and_change "packages" - echo - fi - else - if has_elements 'nixosConfigurations' "$1"; then - cat <<-"EOF" + echo "$compare" | json_to_md_rows_and_change "packages" + echo + fi + else + if has_elements 'nixosConfigurations' "$1"; then + cat <<-"EOF" # NixOS Configurations | Name | Size | NAR Size | |------|-----:|---------:| EOF - json_to_md_rows "nixosConfigurations" "$1" - echo - fi - if has_elements 'packages' "$1"; then - cat <<-"EOF" + json_to_md_rows "nixosConfigurations" "$1" + echo + fi + if has_elements 'packages' "$1"; then + cat <<-"EOF" # Packages | Name | Size | NAR Size | |------|-----:|---------:| EOF - json_to_md_rows "packages" "$1" - echo - fi - fi + json_to_md_rows "packages" "$1" + echo + fi + fi } # Test outside CI if [ "${CI:-false}" != 'true' ]; then - markdown_from_report "$@" - exit 0 + markdown_from_report "$@" + exit 0 fi # Protect against running before a PR is made or if it is triggered on the main branch if [ -z "$PR_ID" ]; then - warn "No PR created for this commit" - exit 0 + warn "No PR created for this commit" + exit 0 fi log 'Generating comment body' @@ -137,19 +137,19 @@ log "$data" endgroup if [ -z "$COMMENT_ID" ]; then - log 'Posting new comment' - curl -o - -X 'POST' \ - "$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/issues/$PR_ID/comments" \ - -H 'accept: application/json' \ - -H "Authorization: token $GITHUB_TOKEN" \ - -H 'Content-Type: application/json' \ - -d "$data" + log 'Posting new comment' + curl -o - -X 'POST' \ + "$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/issues/$PR_ID/comments" \ + -H 'accept: application/json' \ + -H "Authorization: token $GITHUB_TOKEN" \ + -H 'Content-Type: application/json' \ + -d "$data" else - log "Editing comment $COMMENT_ID" - curl -o - -X 'PATCH' \ - "$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/issues/$PR_ID/comments/$COMMENT_ID" \ - -H 'accept: application/json' \ - -H "Authorization: token $GITHUB_TOKEN" \ - -H 'Content-Type: application/json' \ - -d "$data" + log "Editing comment $COMMENT_ID" + curl -o - -X 'PATCH' \ + "$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/issues/$PR_ID/comments/$COMMENT_ID" \ + -H 'accept: application/json' \ + -H "Authorization: token $GITHUB_TOKEN" \ + -H 'Content-Type: application/json' \ + -d "$data" fi diff --git a/create-report.sh b/create-report.sh index 1a8a15d..bd478aa 100755 --- a/create-report.sh +++ b/create-report.sh @@ -13,7 +13,7 @@ endgroup group 'Show Packages' packages=$( - jq --raw-output '.packages."x86_64-linux" | select(. != null) | keys[]' <<-EOF + jq --raw-output '.packages."x86_64-linux" | select(. != null) | keys[]' <<-EOF $flake_info EOF ) @@ -22,7 +22,7 @@ endgroup group 'Show NixOS Configurations' configurations=$( - jq --raw-output '.nixosConfigurations | select(. != null) | keys[]' <<-EOF + jq --raw-output '.nixosConfigurations | select(. != null) | keys[]' <<-EOF $flake_info EOF ) @@ -30,34 +30,34 @@ log "$configurations" endgroup pkgs_json() { - group 'Building packages' - trap endgroup RETURN - for package in $packages; do - log "Building $package" - path=$(nix build --print-out-paths ".#$package") - log "Calculating size of $package" - nix path-info --closure-size --json "$path" | - jq --compact-output --arg pkg "$package" '.[] | {"name": $pkg, "size": .closureSize, "narSize": .narSize}' - done - endgroup + group 'Building packages' + trap endgroup RETURN + for package in $packages; do + log "Building $package" + path=$(nix build --print-out-paths ".#$package") + log "Calculating size of $package" + nix path-info --closure-size --json "$path" | + jq --compact-output --arg pkg "$package" '.[] | {"name": $pkg, "size": .closureSize, "narSize": .narSize}' + done + endgroup } configs_json() { - group 'Building NixOS configurations' - trap endgroup RETURN - for config in $configurations; do - log "Building $config" - path=$(nix build --print-out-paths ".#nixosConfigurations.$config.config.system.build.toplevel") - log "Calculating size of $config" - nix path-info --closure-size --json "$path" | - jq --compact-output --arg pkg "$config" '.[] | {"name": $pkg, "size": .closureSize, "narSize": .narSize}' - done + group 'Building NixOS configurations' + trap endgroup RETURN + for config in $configurations; do + log "Building $config" + path=$(nix build --print-out-paths ".#nixosConfigurations.$config.config.system.build.toplevel") + log "Calculating size of $config" + nix path-info --closure-size --json "$path" | + jq --compact-output --arg pkg "$config" '.[] | {"name": $pkg, "size": .closureSize, "narSize": .narSize}' + done } pkgs=$(pkgs_json | jq --slurp '.') configs=$(configs_json | jq --slurp '.') echo "{}" | jq \ - --argjson pkgs "$pkgs" \ - --argjson configs "$configs" \ - '{"packages": $pkgs, "nixosConfigurations": $configs}' + --argjson pkgs "$pkgs" \ + --argjson configs "$configs" \ + '{"packages": $pkgs, "nixosConfigurations": $configs}' diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..7e05325 --- /dev/null +++ b/flake.lock @@ -0,0 +1,98 @@ +{ + "nodes": { + "flake-parts": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1741352980, + "narHash": "sha256-+u2UunDA4Cl5Fci3m7S643HzKmIDAe+fiXrLqYsR2fs=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "f4330d22f1c5d2ba72d3d22df5597d123fdb60a9", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1741513245, + "narHash": "sha256-7rTAMNTY1xoBwz0h7ZMtEcd8LELk9R5TzBPoHuhNSCk=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e3e32b642a31e6714ec1b712de8c91a3352ce7e1", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-lib": { + "locked": { + "lastModified": 1740877520, + "narHash": "sha256-oiwv/ZK/2FhGxrCkQkB83i7GnWXPPLzoqFHpDD3uYpk=", + "owner": "nix-community", + "repo": "nixpkgs.lib", + "rev": "147dee35aab2193b174e4c0868bd80ead5ce755c", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixpkgs.lib", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-parts": "flake-parts", + "nixpkgs": "nixpkgs", + "systems": "systems", + "treefmt-nix": "treefmt-nix" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "treefmt-nix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1739829690, + "narHash": "sha256-mL1szCeIsjh6Khn3nH2cYtwO5YXG6gBiTw1A30iGeDU=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "3d0579f5cc93436052d94b73925b48973a104204", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..46e3e7b --- /dev/null +++ b/flake.nix @@ -0,0 +1,43 @@ +{ + description = "Description for the project"; + + inputs = { + flake-parts.url = "github:hercules-ci/flake-parts"; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + treefmt-nix = { + url = "github:numtide/treefmt-nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + systems.url = "github:nix-systems/default"; + }; + + outputs = + inputs@{ flake-parts, ... }: + flake-parts.lib.mkFlake { inherit inputs; } { + imports = [ inputs.treefmt-nix.flakeModule ]; + systems = import inputs.systems; + + perSystem = + { pkgs, ... }: + { + # Export pkgs.hello so we can test this action + packages = { inherit (pkgs) hello; }; + + # Setup formatters + treefmt = { + # Ignore images + settings.global.excludes = [ "*.png" ]; + projectRootFile = "flake.nix"; + programs = { + mdformat.enable = true; + nixfmt.enable = true; + shfmt.enable = true; + shellcheck.enable = true; + statix.enable = true; + typos.enable = true; + yamlfmt.enable = true; + }; + }; + }; + }; +} diff --git a/retrieve-old-report.sh b/retrieve-old-report.sh index 0d748b1..e7c8de6 100755 --- a/retrieve-old-report.sh +++ b/retrieve-old-report.sh @@ -3,59 +3,59 @@ . "${GITHUB_ACTION_PATH}/utils.sh" repo_info() { - curl -X GET \ - -H "Authorization: token $GITHUB_TOKEN" \ - -H 'accept: application/json' \ - "$GITHUB_API_URL/repos/$GITHUB_REPOSITORY" + curl -X GET \ + -H "Authorization: token $GITHUB_TOKEN" \ + -H 'accept: application/json' \ + "$GITHUB_API_URL/repos/$GITHUB_REPOSITORY" } in_private_repo() { - test "$(repo_info | jq --raw-output '.private')" = 'true' + test "$(repo_info | jq --raw-output '.private')" = 'true' } default_branch() { - repo_info | jq --raw-output '.default_branch' + repo_info | jq --raw-output '.default_branch' } # USAGE: base_report_url base_report_url() { - curl -X 'GET' \ - "$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/actions/tasks" \ - -H "Authorization: token $GITHUB_TOKEN" \ - -H 'accept: application/json' | - jq --raw-output \ - --arg name "$JOB_NAME" \ - --arg head_branch "$1" \ - '[.workflow_runs[] | select(.name == $name and .head_branch == $head_branch)] | first | .url' + curl -X 'GET' \ + "$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/actions/tasks" \ + -H "Authorization: token $GITHUB_TOKEN" \ + -H 'accept: application/json' | + jq --raw-output \ + --arg name "$JOB_NAME" \ + --arg head_branch "$1" \ + '[.workflow_runs[] | select(.name == $name and .head_branch == $head_branch)] | first | .url' } # USAGE: has_report has_report() { - http_code=$(curl -X 'GET' -o /dev/null --silent -Iw '%{http_code}' \ - "$1" -H "Authorization: token $GITHUB_TOKEN") - log "Got code $http_code for $1" - test "$http_code" = '200' + http_code=$(curl -X 'GET' -o /dev/null --silent -Iw '%{http_code}' \ + "$1" -H "Authorization: token $GITHUB_TOKEN") + log "Got code $http_code for $1" + test "$http_code" = '200' } # If a base branch is not provided, use the default branch base_branch=${BASE_BRANCH:-$(default_branch)} if [ "$(in_private_repo)" != 'true' ] && [ "$JOB_NAME" ]; then - url=$(base_report_url "$base_branch") + url=$(base_report_url "$base_branch") - log "Found previous run at: $url" - report_url="$url/artifacts/$ARTIFACT_NAME" + log "Found previous run at: $url" + report_url="$url/artifacts/$ARTIFACT_NAME" - if has_report "$report_url"; then - log 'Found previous report, downloading...' - curl -X 'GET' \ - "$report_url" \ - -H "Authorization: token $GITHUB_TOKEN" | - gunzip >old-report.json - log "Reporting on sizes and comparing to sizes in $base_branch" - exit 0 - fi - error "Failed to find previous report, expected at: $report_url" + if has_report "$report_url"; then + log 'Found previous report, downloading...' + curl -X 'GET' \ + "$report_url" \ + -H "Authorization: token $GITHUB_TOKEN" | + gunzip >old-report.json + log "Reporting on sizes and comparing to sizes in $base_branch" + exit 0 + fi + error "Failed to find previous report, expected at: $report_url" fi warn "Couldn't retrieve old report:" @@ -69,9 +69,9 @@ error "Falling back to slow method (checkout $base_branch and generate the repor old=$(mktemp -d) group "Download files from $base_branch" curl -X 'GET' \ - "$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/archive/$base_branch.tar.gz" \ - -H "Authorization: token $GITHUB_TOKEN" | - tar -zvx --strip-components=1 -C "$old" + "$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/archive/$base_branch.tar.gz" \ + -H "Authorization: token $GITHUB_TOKEN" | + tar -zvx --strip-components=1 -C "$old" endgroup (cd "$old" && "$GITHUB_ACTION_PATH/create-report.sh") >old-report.json diff --git a/utils.sh b/utils.sh index 9cba1aa..e61c2a3 100755 --- a/utils.sh +++ b/utils.sh @@ -1,19 +1,21 @@ +#!/bin/sh + log() { - echo "$@" >&2 + echo "$@" >&2 } warn() { - log "\e[0;33m[ERROR]:" "$@" "\e[0m" + log "\e[0;33m[ERROR]:" "$@" "\e[0m" } error() { - log "\e[0;31m[WARN]:" "$@" "\e[0m" + log "\e[0;31m[WARN]:" "$@" "\e[0m" } group() { - log "::group::$1" + log "::group::$1" } endgroup() { - log '::endgroup::' + log '::endgroup::' }