Merge pull request #4 from bnema/ci/release-aur
ci: automated releases to GitHub and the AUR
16 files · +537 -9
A .github/workflows/ci.yml +41 -0
| @@ -0,0 +1,41 @@ | |||
| 1 | + | name: CI | |
| 2 | + | on: | |
| 3 | + | push: | |
| 4 | + | branches: [main] | |
| 5 | + | pull_request: | |
| 6 | + | # The release workflow runs these checks before it publishes. | |
| 7 | + | workflow_call: | |
| 8 | + | concurrency: | |
| 9 | + | group: ci-${{ github.ref }} | |
| 10 | + | cancel-in-progress: true | |
| 11 | + | jobs: | |
| 12 | + | # Arch matches the development system: libwayland and Go versions change | |
| 13 | + | # protocol behavior. | |
| 14 | + | check: | |
| 15 | + | runs-on: ubuntu-latest | |
| 16 | + | container: | |
| 17 | + | image: archlinux:latest | |
| 18 | + | # An init process reaps killed children, as on a real session. | |
| 19 | + | options: --init | |
| 20 | + | env: | |
| 21 | + | XDG_RUNTIME_DIR: /tmp/xdg-runtime | |
| 22 | + | steps: | |
| 23 | + | # wayland: libwayland-server for the in-process test compositor | |
| 24 | + | # (NEFERCAP_REQUIRE_WAYLAND=1 fails without it); libxkbcommon is loaded | |
| 25 | + | # at runtime through purego; ffmpeg (libx264) runs the encoder tests; | |
| 26 | + | # gcc is for the race detector. | |
| 27 | + | - run: >- | |
| 28 | + | pacman -Syu --noconfirm --needed | |
| 29 | + | git go make gcc wayland libxkbcommon ffmpeg | |
| 30 | + | - uses: actions/checkout@v4 | |
| 31 | + | - run: | | |
| 32 | + | git config --global --add safe.directory "$GITHUB_WORKSPACE" | |
| 33 | + | install -d -m 700 "$XDG_RUNTIME_DIR" | |
| 34 | + | - run: | | |
| 35 | + | go install github.com/bnema/hexcheck/cmd/hexcheck@v0.2.0 | |
| 36 | + | go install github.com/vektra/mockery/v3@v3.8.0 | |
| 37 | + | go install honnef.co/go/tools/cmd/staticcheck@latest | |
| 38 | + | - run: make check | |
| 39 | + | - run: make race | |
| 40 | + | - run: make mocks-check | |
| 41 | + | - run: ~/go/bin/staticcheck ./... | |
| @@ -0,0 +1,41 @@ | |||||
| 1 | + | name: CI | |||
| 2 | + | on: | |||
| 3 | + | push: | |||
| 4 | + | branches: [main] | |||
| 5 | + | pull_request: | |||
| 6 | + | # The release workflow runs these checks before it publishes. | |||
| 7 | + | workflow_call: | |||
| 8 | + | concurrency: | |||
| 9 | + | group: ci-${{ github.ref }} | |||
| 10 | + | cancel-in-progress: true | |||
| 11 | + | jobs: | |||
| 12 | + | # Arch matches the development system: libwayland and Go versions change | |||
| 13 | + | # protocol behavior. | |||
| 14 | + | check: | |||
| 15 | + | runs-on: ubuntu-latest | |||
| 16 | + | container: | |||
| 17 | + | image: archlinux:latest | |||
| 18 | + | # An init process reaps killed children, as on a real session. | |||
| 19 | + | options: --init | |||
| 20 | + | env: | |||
| 21 | + | XDG_RUNTIME_DIR: /tmp/xdg-runtime | |||
| 22 | + | steps: | |||
| 23 | + | # wayland: libwayland-server for the in-process test compositor | |||
| 24 | + | # (NEFERCAP_REQUIRE_WAYLAND=1 fails without it); libxkbcommon is loaded | |||
| 25 | + | # at runtime through purego; ffmpeg (libx264) runs the encoder tests; | |||
| 26 | + | # gcc is for the race detector. | |||
| 27 | + | - run: >- | |||
| 28 | + | pacman -Syu --noconfirm --needed | |||
| 29 | + | git go make gcc wayland libxkbcommon ffmpeg | |||
| 30 | + | - uses: actions/checkout@v4 | |||
| 31 | + | - run: | | |||
| 32 | + | git config --global --add safe.directory "$GITHUB_WORKSPACE" | |||
| 33 | + | install -d -m 700 "$XDG_RUNTIME_DIR" | |||
| 34 | + | - run: | | |||
| 35 | + | go install github.com/bnema/hexcheck/cmd/hexcheck@v0.2.0 | |||
| 36 | + | go install github.com/vektra/mockery/v3@v3.8.0 | |||
| 37 | + | go install honnef.co/go/tools/cmd/staticcheck@latest | |||
| 38 | + | - run: make check | |||
| 39 | + | - run: make race | |||
| 40 | + | - run: make mocks-check | |||
| 41 | + | - run: ~/go/bin/staticcheck ./... | |||
A .github/workflows/release.yml +93 -0
| @@ -0,0 +1,93 @@ | |||
| 1 | + | name: Release | |
| 2 | + | on: | |
| 3 | + | push: | |
| 4 | + | tags: ['v*'] | |
| 5 | + | permissions: | |
| 6 | + | contents: read | |
| 7 | + | concurrency: | |
| 8 | + | group: release-${{ github.ref }} | |
| 9 | + | cancel-in-progress: false | |
| 10 | + | jobs: | |
| 11 | + | check: | |
| 12 | + | uses: ./.github/workflows/ci.yml | |
| 13 | + | ||
| 14 | + | release: | |
| 15 | + | needs: check | |
| 16 | + | runs-on: ubuntu-latest | |
| 17 | + | permissions: | |
| 18 | + | contents: write # create the release | |
| 19 | + | id-token: write # sign the provenance attestation | |
| 20 | + | attestations: write | |
| 21 | + | steps: | |
| 22 | + | - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| 23 | + | with: | |
| 24 | + | fetch-depth: 0 | |
| 25 | + | persist-credentials: false | |
| 26 | + | - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 | |
| 27 | + | with: | |
| 28 | + | go-version-file: go.mod | |
| 29 | + | - uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7.2.3 | |
| 30 | + | with: | |
| 31 | + | distribution: goreleaser | |
| 32 | + | version: '~> v2' | |
| 33 | + | args: release --clean | |
| 34 | + | env: | |
| 35 | + | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| 36 | + | - uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2 | |
| 37 | + | with: | |
| 38 | + | subject-path: | | |
| 39 | + | dist/*.tar.gz | |
| 40 | + | dist/checksums.txt | |
| 41 | + | - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| 42 | + | with: | |
| 43 | + | name: aur-bin | |
| 44 | + | path: dist/aur/nefercap-bin.pkgbuild | |
| 45 | + | if-no-files-found: error | |
| 46 | + | ||
| 47 | + | # makepkg must run on Arch as a non-root user. publish.sh builds each | |
| 48 | + | # package, verifies every source checksum, then pushes it. | |
| 49 | + | aur: | |
| 50 | + | needs: release | |
| 51 | + | runs-on: ubuntu-latest | |
| 52 | + | container: | |
| 53 | + | image: archlinux:latest | |
| 54 | + | strategy: | |
| 55 | + | fail-fast: false | |
| 56 | + | matrix: | |
| 57 | + | pkg: [nefercap-bin, nefercap-git] | |
| 58 | + | # Pre-releases (v0.2.0-rc1) stay off the AUR: pacman sorts 0.2.0.rc1 | |
| 59 | + | # after 0.2.0, so the final release would not look like an upgrade. | |
| 60 | + | env: | |
| 61 | + | SKIP: ${{ contains(github.ref_name, '-') }} | |
| 62 | + | steps: | |
| 63 | + | - run: >- | |
| 64 | + | pacman -Syu --noconfirm --needed | |
| 65 | + | base-devel git openssh sudo | |
| 66 | + | - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| 67 | + | with: | |
| 68 | + | persist-credentials: false | |
| 69 | + | - if: matrix.pkg == 'nefercap-bin' && env.SKIP != 'true' | |
| 70 | + | uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| 71 | + | with: | |
| 72 | + | name: aur-bin | |
| 73 | + | path: aur-bin | |
| 74 | + | - name: Create the build user | |
| 75 | + | run: | | |
| 76 | + | useradd -m builder | |
| 77 | + | echo 'builder ALL=(root) NOPASSWD: /usr/bin/pacman' >/etc/sudoers.d/builder | |
| 78 | + | - name: Build, verify and publish ${{ matrix.pkg }} | |
| 79 | + | if: env.SKIP != 'true' | |
| 80 | + | env: | |
| 81 | + | AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }} | |
| 82 | + | AUR_USERNAME: ${{ secrets.AUR_USERNAME }} | |
| 83 | + | AUR_EMAIL: ${{ secrets.AUR_EMAIL }} | |
| 84 | + | PKG: ${{ matrix.pkg }} | |
| 85 | + | run: | | |
| 86 | + | if [ "$PKG" = nefercap-bin ]; then | |
| 87 | + | pkgbuild=aur-bin/nefercap-bin.pkgbuild | |
| 88 | + | else | |
| 89 | + | pkgbuild=packaging/aur/nefercap-git/PKGBUILD | |
| 90 | + | fi | |
| 91 | + | chmod -R a+rX "$GITHUB_WORKSPACE" | |
| 92 | + | sudo -u builder --preserve-env=AUR_SSH_PRIVATE_KEY,AUR_USERNAME,AUR_EMAIL \ | |
| 93 | + | packaging/aur/publish.sh "$PKG" "$pkgbuild" | |
| @@ -0,0 +1,93 @@ | |||||
| 1 | + | name: Release | |||
| 2 | + | on: | |||
| 3 | + | push: | |||
| 4 | + | tags: ['v*'] | |||
| 5 | + | permissions: | |||
| 6 | + | contents: read | |||
| 7 | + | concurrency: | |||
| 8 | + | group: release-${{ github.ref }} | |||
| 9 | + | cancel-in-progress: false | |||
| 10 | + | jobs: | |||
| 11 | + | check: | |||
| 12 | + | uses: ./.github/workflows/ci.yml | |||
| 13 | + | ||||
| 14 | + | release: | |||
| 15 | + | needs: check | |||
| 16 | + | runs-on: ubuntu-latest | |||
| 17 | + | permissions: | |||
| 18 | + | contents: write # create the release | |||
| 19 | + | id-token: write # sign the provenance attestation | |||
| 20 | + | attestations: write | |||
| 21 | + | steps: | |||
| 22 | + | - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |||
| 23 | + | with: | |||
| 24 | + | fetch-depth: 0 | |||
| 25 | + | persist-credentials: false | |||
| 26 | + | - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 | |||
| 27 | + | with: | |||
| 28 | + | go-version-file: go.mod | |||
| 29 | + | - uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7.2.3 | |||
| 30 | + | with: | |||
| 31 | + | distribution: goreleaser | |||
| 32 | + | version: '~> v2' | |||
| 33 | + | args: release --clean | |||
| 34 | + | env: | |||
| 35 | + | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |||
| 36 | + | - uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2 | |||
| 37 | + | with: | |||
| 38 | + | subject-path: | | |||
| 39 | + | dist/*.tar.gz | |||
| 40 | + | dist/checksums.txt | |||
| 41 | + | - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |||
| 42 | + | with: | |||
| 43 | + | name: aur-bin | |||
| 44 | + | path: dist/aur/nefercap-bin.pkgbuild | |||
| 45 | + | if-no-files-found: error | |||
| 46 | + | ||||
| 47 | + | # makepkg must run on Arch as a non-root user. publish.sh builds each | |||
| 48 | + | # package, verifies every source checksum, then pushes it. | |||
| 49 | + | aur: | |||
| 50 | + | needs: release | |||
| 51 | + | runs-on: ubuntu-latest | |||
| 52 | + | container: | |||
| 53 | + | image: archlinux:latest | |||
| 54 | + | strategy: | |||
| 55 | + | fail-fast: false | |||
| 56 | + | matrix: | |||
| 57 | + | pkg: [nefercap-bin, nefercap-git] | |||
| 58 | + | # Pre-releases (v0.2.0-rc1) stay off the AUR: pacman sorts 0.2.0.rc1 | |||
| 59 | + | # after 0.2.0, so the final release would not look like an upgrade. | |||
| 60 | + | env: | |||
| 61 | + | SKIP: ${{ contains(github.ref_name, '-') }} | |||
| 62 | + | steps: | |||
| 63 | + | - run: >- | |||
| 64 | + | pacman -Syu --noconfirm --needed | |||
| 65 | + | base-devel git openssh sudo | |||
| 66 | + | - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |||
| 67 | + | with: | |||
| 68 | + | persist-credentials: false | |||
| 69 | + | - if: matrix.pkg == 'nefercap-bin' && env.SKIP != 'true' | |||
| 70 | + | uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |||
| 71 | + | with: | |||
| 72 | + | name: aur-bin | |||
| 73 | + | path: aur-bin | |||
| 74 | + | - name: Create the build user | |||
| 75 | + | run: | | |||
| 76 | + | useradd -m builder | |||
| 77 | + | echo 'builder ALL=(root) NOPASSWD: /usr/bin/pacman' >/etc/sudoers.d/builder | |||
| 78 | + | - name: Build, verify and publish ${{ matrix.pkg }} | |||
| 79 | + | if: env.SKIP != 'true' | |||
| 80 | + | env: | |||
| 81 | + | AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }} | |||
| 82 | + | AUR_USERNAME: ${{ secrets.AUR_USERNAME }} | |||
| 83 | + | AUR_EMAIL: ${{ secrets.AUR_EMAIL }} | |||
| 84 | + | PKG: ${{ matrix.pkg }} | |||
| 85 | + | run: | | |||
| 86 | + | if [ "$PKG" = nefercap-bin ]; then | |||
| 87 | + | pkgbuild=aur-bin/nefercap-bin.pkgbuild | |||
| 88 | + | else | |||
| 89 | + | pkgbuild=packaging/aur/nefercap-git/PKGBUILD | |||
| 90 | + | fi | |||
| 91 | + | chmod -R a+rX "$GITHUB_WORKSPACE" | |||
| 92 | + | sudo -u builder --preserve-env=AUR_SSH_PRIVATE_KEY,AUR_USERNAME,AUR_EMAIL \ | |||
| 93 | + | packaging/aur/publish.sh "$PKG" "$pkgbuild" | |||
A .goreleaser.yaml +136 -0
| @@ -0,0 +1,136 @@ | |||
| 1 | + | # Builds the GitHub release for a pushed v* tag (.github/workflows/release.yml) | |
| 2 | + | # and generates the nefercap-bin PKGBUILD. The workflow verifies that PKGBUILD | |
| 3 | + | # with makepkg before it pushes it to the AUR, so GoReleaser never uploads it. | |
| 4 | + | version: 2 | |
| 5 | + | ||
| 6 | + | project_name: nefercap | |
| 7 | + | ||
| 8 | + | before: | |
| 9 | + | hooks: | |
| 10 | + | - go mod verify | |
| 11 | + | ||
| 12 | + | builds: | |
| 13 | + | - id: nefercap | |
| 14 | + | main: ./cmd/nefercap | |
| 15 | + | binary: nefercap | |
| 16 | + | env: | |
| 17 | + | - CGO_ENABLED=0 | |
| 18 | + | goos: [linux] | |
| 19 | + | goarch: [amd64, arm64] | |
| 20 | + | flags: | |
| 21 | + | - -trimpath | |
| 22 | + | - -buildmode=pie | |
| 23 | + | ldflags: | |
| 24 | + | - -s -w -X main.version={{ .Version }} | |
| 25 | + | # Same inputs give the same binary. | |
| 26 | + | mod_timestamp: "{{ .CommitTimestamp }}" | |
| 27 | + | ||
| 28 | + | archives: | |
| 29 | + | - id: nefercap | |
| 30 | + | ids: [nefercap] | |
| 31 | + | name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" | |
| 32 | + | formats: [tar.gz] | |
| 33 | + | # Everything the PKGBUILD package() installs, at the paths of the repo. | |
| 34 | + | files: | |
| 35 | + | - LICENSE | |
| 36 | + | - README.md | |
| 37 | + | ||
| 38 | + | checksum: | |
| 39 | + | name_template: checksums.txt | |
| 40 | + | algorithm: sha256 | |
| 41 | + | ||
| 42 | + | snapshot: | |
| 43 | + | version_template: "{{ incpatch .Version }}-next" | |
| 44 | + | ||
| 45 | + | git: | |
| 46 | + | tag_sort: -version:refname | |
| 47 | + | ||
| 48 | + | changelog: | |
| 49 | + | sort: asc | |
| 50 | + | use: github | |
| 51 | + | filters: | |
| 52 | + | exclude: | |
| 53 | + | - "^style(\\([^)]+\\))?:" | |
| 54 | + | - "^test(\\([^)]+\\))?:" | |
| 55 | + | groups: | |
| 56 | + | - title: Added | |
| 57 | + | regexp: "^feat(\\([^)]+\\))?!?:" | |
| 58 | + | order: 0 | |
| 59 | + | - title: Fixed | |
| 60 | + | regexp: "^fix(\\([^)]+\\))?!?:" | |
| 61 | + | order: 1 | |
| 62 | + | - title: Performance | |
| 63 | + | regexp: "^perf(\\([^)]+\\))?!?:" | |
| 64 | + | order: 2 | |
| 65 | + | - title: Changed | |
| 66 | + | regexp: "^(refactor|build)(\\([^)]+\\))?!?:" | |
| 67 | + | order: 3 | |
| 68 | + | - title: Documentation | |
| 69 | + | regexp: "^docs(\\([^)]+\\))?!?:" | |
| 70 | + | order: 4 | |
| 71 | + | - title: Maintenance | |
| 72 | + | regexp: "^(chore|ci)(\\([^)]+\\))?!?:" | |
| 73 | + | order: 5 | |
| 74 | + | - title: Other changes | |
| 75 | + | order: 999 | |
| 76 | + | ||
| 77 | + | release: | |
| 78 | + | github: | |
| 79 | + | owner: bnema | |
| 80 | + | name: nefercap | |
| 81 | + | draft: false | |
| 82 | + | prerelease: auto | |
| 83 | + | mode: replace | |
| 84 | + | header: | | |
| 85 | + | ## nefercap {{ .Tag }} | |
| 86 | + | ||
| 87 | + | ### Install on Arch Linux | |
| 88 | + | ||
| 89 | + | ```sh | |
| 90 | + | paru -S nefercap-bin # this release, pre-built (x86_64, aarch64) | |
| 91 | + | paru -S nefercap-git # latest main, built from source | |
| 92 | + | ``` | |
| 93 | + | ||
| 94 | + | ### Install the pre-built archive | |
| 95 | + | ||
| 96 | + | Linux amd64 and arm64. Replace `amd64` with `arm64` on ARM. | |
| 97 | + | ||
| 98 | + | ```sh | |
| 99 | + | gh release download {{ .Tag }} -R bnema/nefercap -p 'nefercap_{{ .Version }}_linux_amd64.tar.gz' -p checksums.txt | |
| 100 | + | sha256sum -c --ignore-missing checksums.txt | |
| 101 | + | gh attestation verify nefercap_{{ .Version }}_linux_amd64.tar.gz -R bnema/nefercap | |
| 102 | + | ``` | |
| 103 | + | ||
| 104 | + | The archive holds the `nefercap` binary, the README and the license. Install the binary as `/usr/bin/nefercap`: NeferWL allows that path to capture by default. See the [README](https://github.com/bnema/nefercap#readme) for runtime dependencies. | |
| 105 | + | footer: | | |
| 106 | + | --- | |
| 107 | + | ||
| 108 | + | **Full changelog**: https://github.com/bnema/nefercap/compare/{{ .PreviousTag }}...{{ .Tag }} | |
| 109 | + | ||
| 110 | + | aurs: | |
| 111 | + | - name: nefercap-bin | |
| 112 | + | ids: [nefercap] | |
| 113 | + | homepage: https://github.com/bnema/nefercap | |
| 114 | + | description: Screenshots and silent screen recording for Wayland, from one shortcut | |
| 115 | + | maintainers: | |
| 116 | + | - "bnema <b at bnema dot dev>" | |
| 117 | + | license: GPL-3.0-only | |
| 118 | + | # The release workflow verifies and publishes the generated PKGBUILD. | |
| 119 | + | skip_upload: true | |
| 120 | + | provides: [nefercap] | |
| 121 | + | conflicts: [nefercap, nefercap-git] | |
| 122 | + | # Loaded at runtime (purego, no cgo): not seen by makepkg's library scan. | |
| 123 | + | depends: | |
| 124 | + | - glibc | |
| 125 | + | - wayland | |
| 126 | + | - libxkbcommon | |
| 127 | + | - vulkan-icd-loader | |
| 128 | + | - vulkan-driver | |
| 129 | + | optdepends: | |
| 130 | + | - "ffmpeg: screen recording (libx264)" | |
| 131 | + | - "wl-clipboard: copy screenshots to the clipboard" | |
| 132 | + | - "neferwl: hidden-workspace capture, HUD exclusion and capture indicator" | |
| 133 | + | package: |- | |
| 134 | + | install -Dm755 ./nefercap "${pkgdir}/usr/bin/nefercap" | |
| 135 | + | install -Dm644 ./README.md "${pkgdir}/usr/share/doc/nefercap/README.md" | |
| 136 | + | install -Dm644 ./LICENSE "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE" | |
| @@ -0,0 +1,136 @@ | |||||
| 1 | + | # Builds the GitHub release for a pushed v* tag (.github/workflows/release.yml) | |||
| 2 | + | # and generates the nefercap-bin PKGBUILD. The workflow verifies that PKGBUILD | |||
| 3 | + | # with makepkg before it pushes it to the AUR, so GoReleaser never uploads it. | |||
| 4 | + | version: 2 | |||
| 5 | + | ||||
| 6 | + | project_name: nefercap | |||
| 7 | + | ||||
| 8 | + | before: | |||
| 9 | + | hooks: | |||
| 10 | + | - go mod verify | |||
| 11 | + | ||||
| 12 | + | builds: | |||
| 13 | + | - id: nefercap | |||
| 14 | + | main: ./cmd/nefercap | |||
| 15 | + | binary: nefercap | |||
| 16 | + | env: | |||
| 17 | + | - CGO_ENABLED=0 | |||
| 18 | + | goos: [linux] | |||
| 19 | + | goarch: [amd64, arm64] | |||
| 20 | + | flags: | |||
| 21 | + | - -trimpath | |||
| 22 | + | - -buildmode=pie | |||
| 23 | + | ldflags: | |||
| 24 | + | - -s -w -X main.version={{ .Version }} | |||
| 25 | + | # Same inputs give the same binary. | |||
| 26 | + | mod_timestamp: "{{ .CommitTimestamp }}" | |||
| 27 | + | ||||
| 28 | + | archives: | |||
| 29 | + | - id: nefercap | |||
| 30 | + | ids: [nefercap] | |||
| 31 | + | name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" | |||
| 32 | + | formats: [tar.gz] | |||
| 33 | + | # Everything the PKGBUILD package() installs, at the paths of the repo. | |||
| 34 | + | files: | |||
| 35 | + | - LICENSE | |||
| 36 | + | - README.md | |||
| 37 | + | ||||
| 38 | + | checksum: | |||
| 39 | + | name_template: checksums.txt | |||
| 40 | + | algorithm: sha256 | |||
| 41 | + | ||||
| 42 | + | snapshot: | |||
| 43 | + | version_template: "{{ incpatch .Version }}-next" | |||
| 44 | + | ||||
| 45 | + | git: | |||
| 46 | + | tag_sort: -version:refname | |||
| 47 | + | ||||
| 48 | + | changelog: | |||
| 49 | + | sort: asc | |||
| 50 | + | use: github | |||
| 51 | + | filters: | |||
| 52 | + | exclude: | |||
| 53 | + | - "^style(\\([^)]+\\))?:" | |||
| 54 | + | - "^test(\\([^)]+\\))?:" | |||
| 55 | + | groups: | |||
| 56 | + | - title: Added | |||
| 57 | + | regexp: "^feat(\\([^)]+\\))?!?:" | |||
| 58 | + | order: 0 | |||
| 59 | + | - title: Fixed | |||
| 60 | + | regexp: "^fix(\\([^)]+\\))?!?:" | |||
| 61 | + | order: 1 | |||
| 62 | + | - title: Performance | |||
| 63 | + | regexp: "^perf(\\([^)]+\\))?!?:" | |||
| 64 | + | order: 2 | |||
| 65 | + | - title: Changed | |||
| 66 | + | regexp: "^(refactor|build)(\\([^)]+\\))?!?:" | |||
| 67 | + | order: 3 | |||
| 68 | + | - title: Documentation | |||
| 69 | + | regexp: "^docs(\\([^)]+\\))?!?:" | |||
| 70 | + | order: 4 | |||
| 71 | + | - title: Maintenance | |||
| 72 | + | regexp: "^(chore|ci)(\\([^)]+\\))?!?:" | |||
| 73 | + | order: 5 | |||
| 74 | + | - title: Other changes | |||
| 75 | + | order: 999 | |||
| 76 | + | ||||
| 77 | + | release: | |||
| 78 | + | github: | |||
| 79 | + | owner: bnema | |||
| 80 | + | name: nefercap | |||
| 81 | + | draft: false | |||
| 82 | + | prerelease: auto | |||
| 83 | + | mode: replace | |||
| 84 | + | header: | | |||
| 85 | + | ## nefercap {{ .Tag }} | |||
| 86 | + | ||||
| 87 | + | ### Install on Arch Linux | |||
| 88 | + | ||||
| 89 | + | ```sh | |||
| 90 | + | paru -S nefercap-bin # this release, pre-built (x86_64, aarch64) | |||
| 91 | + | paru -S nefercap-git # latest main, built from source | |||
| 92 | + | ``` | |||
| 93 | + | ||||
| 94 | + | ### Install the pre-built archive | |||
| 95 | + | ||||
| 96 | + | Linux amd64 and arm64. Replace `amd64` with `arm64` on ARM. | |||
| 97 | + | ||||
| 98 | + | ```sh | |||
| 99 | + | gh release download {{ .Tag }} -R bnema/nefercap -p 'nefercap_{{ .Version }}_linux_amd64.tar.gz' -p checksums.txt | |||
| 100 | + | sha256sum -c --ignore-missing checksums.txt | |||
| 101 | + | gh attestation verify nefercap_{{ .Version }}_linux_amd64.tar.gz -R bnema/nefercap | |||
| 102 | + | ``` | |||
| 103 | + | ||||
| 104 | + | The archive holds the `nefercap` binary, the README and the license. Install the binary as `/usr/bin/nefercap`: NeferWL allows that path to capture by default. See the [README](https://github.com/bnema/nefercap#readme) for runtime dependencies. | |||
| 105 | + | footer: | | |||
| 106 | + | --- | |||
| 107 | + | ||||
| 108 | + | **Full changelog**: https://github.com/bnema/nefercap/compare/{{ .PreviousTag }}...{{ .Tag }} | |||
| 109 | + | ||||
| 110 | + | aurs: | |||
| 111 | + | - name: nefercap-bin | |||
| 112 | + | ids: [nefercap] | |||
| 113 | + | homepage: https://github.com/bnema/nefercap | |||
| 114 | + | description: Screenshots and silent screen recording for Wayland, from one shortcut | |||
| 115 | + | maintainers: | |||
| 116 | + | - "bnema <b at bnema dot dev>" | |||
| 117 | + | license: GPL-3.0-only | |||
| 118 | + | # The release workflow verifies and publishes the generated PKGBUILD. | |||
| 119 | + | skip_upload: true | |||
| 120 | + | provides: [nefercap] | |||
| 121 | + | conflicts: [nefercap, nefercap-git] | |||
| 122 | + | # Loaded at runtime (purego, no cgo): not seen by makepkg's library scan. | |||
| 123 | + | depends: | |||
| 124 | + | - glibc | |||
| 125 | + | - wayland | |||
| 126 | + | - libxkbcommon | |||
| 127 | + | - vulkan-icd-loader | |||
| 128 | + | - vulkan-driver | |||
| 129 | + | optdepends: | |||
| 130 | + | - "ffmpeg: screen recording (libx264)" | |||
| 131 | + | - "wl-clipboard: copy screenshots to the clipboard" | |||
| 132 | + | - "neferwl: hidden-workspace capture, HUD exclusion and capture indicator" | |||
| 133 | + | package: |- | |||
| 134 | + | install -Dm755 ./nefercap "${pkgdir}/usr/bin/nefercap" | |||
| 135 | + | install -Dm644 ./README.md "${pkgdir}/usr/share/doc/nefercap/README.md" | |||
| 136 | + | install -Dm644 ./LICENSE "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE" | |||
M Makefile +25 -2
| @@ -1,4 +1,4 @@ | |||
| 1 | - | .PHONY: bin build test vet race mod-check mocks mocks-check fakes-check arch perf-check check | |
| 1 | + | .PHONY: bin build test vet race fmt-check mod-check mocks mocks-check fakes-check arch perf-check check pkg install | |
| 2 | 2 | ||
| 3 | 3 | GOBIN_DIR := $(or $(shell go env GOBIN),$(firstword $(subst :, ,$(shell go env GOPATH)))/bin) | |
| 4 | 4 | MOCKERY ?= $(GOBIN_DIR)/mockery | |
| @@ -16,6 +16,9 @@ vet: | |||
| 16 | 16 | race: | |
| 17 | 17 | # cgo is enabled only for the race test binary. | |
| 18 | 18 | NEFERCAP_REQUIRE_WAYLAND=1 CGO_ENABLED=1 go test -race ./... | |
| 19 | + | # Go sources must be gofmt-clean (lists the offending files and fails otherwise). | |
| 20 | + | fmt-check: | |
| 21 | + | test -z "$$(gofmt -l ./internal ./cmd)" || { gofmt -l ./internal ./cmd >&2; exit 1; } | |
| 19 | 22 | # go.mod and go.sum must be tidy (prints the diff and fails otherwise). | |
| 20 | 23 | mod-check: | |
| 21 | 24 | go mod tidy -diff | |
| @@ -56,4 +59,24 @@ perf-check: | |||
| 56 | 59 | printf '%s\n' "$$out" | grep -qx "$$t" || { echo "perf-check: test $$t not found in $(PERF_PKGS)" >&2; exit 1; }; \ | |
| 57 | 60 | done | |
| 58 | 61 | NEFERCAP_REQUIRE_WAYLAND=1 CGO_ENABLED=0 go test $(PERF_PKGS) -run '$(PERF_RE)' -count=1 | |
| 59 | - | check: mod-check vet test arch fakes-check perf-check | |
| 62 | + | check: fmt-check mod-check vet test arch fakes-check perf-check | |
| 63 | + | ||
| 64 | + | # Arch package of the committed HEAD (packaging/arch/PKGBUILD). Go modules | |
| 65 | + | # come from the module proxy in prepare(); the build itself runs offline. | |
| 66 | + | # pacman-ordered version: 0.0.0.r<commits>.g<hash>; a tag replaces 0.0.0. | |
| 67 | + | pkg: SHELL := bash | |
| 68 | + | pkg: .SHELLFLAGS := -eo pipefail -c | |
| 69 | + | pkg: | |
| 70 | + | @test -z "$$(git status --porcelain)" || echo "warning: uncommitted changes are not packaged" >&2 | |
| 71 | + | v=$$(t=$$(git describe --tags --abbrev=0 2>/dev/null | sed 's/^v//; s/-/_/g'); \ | |
| 72 | + | echo "$${t:-0.0.0}.r$$(git rev-list --count HEAD).g$$(git rev-parse --short HEAD)"); \ | |
| 73 | + | d=$$(mktemp -d /tmp/nefercap-pkg.XXXXXX); trap 'rm -rf "$$d"' EXIT; \ | |
| 74 | + | git archive --prefix=nefercap-$$v/ -o "$$d/nefercap-$$v.tar.gz" HEAD; \ | |
| 75 | + | cp packaging/arch/PKGBUILD "$$d/"; \ | |
| 76 | + | cd "$$d" && sed -i "s/^pkgver=.*/pkgver=$$v/; s/^sha256sums=.*/sha256sums=('$$(sha256sum *.tar.gz | cut -d' ' -f1)')/" PKGBUILD; \ | |
| 77 | + | makepkg -f --noconfirm; mkdir -p $(CURDIR)/dist; rm -f $(CURDIR)/dist/nefercap-*.pkg.tar.zst; mv *.pkg.tar.zst $(CURDIR)/dist/ | |
| 78 | + | @ls dist/*.pkg.tar.zst | |
| 79 | + | ||
| 80 | + | # Build the package, then install it with pacman (asks for sudo). | |
| 81 | + | install: pkg | |
| 82 | + | sudo pacman -U dist/nefercap-*.pkg.tar.zst | |
| @@ -1,4 +1,4 @@ | |||||
| 1 | - | .PHONY: bin build test vet race mod-check mocks mocks-check fakes-check arch perf-check check | 1 | + | .PHONY: bin build test vet race fmt-check mod-check mocks mocks-check fakes-check arch perf-check check pkg install |
| 2 | 2 | ||||
| 3 | GOBIN_DIR := $(or $(shell go env GOBIN),$(firstword $(subst :, ,$(shell go env GOPATH)))/bin) | 3 | GOBIN_DIR := $(or $(shell go env GOBIN),$(firstword $(subst :, ,$(shell go env GOPATH)))/bin) | ||
| 4 | MOCKERY ?= $(GOBIN_DIR)/mockery | 4 | MOCKERY ?= $(GOBIN_DIR)/mockery | ||
| @@ -16,6 +16,9 @@ vet: | |||||
| 16 | race: | 16 | race: | ||
| 17 | # cgo is enabled only for the race test binary. | 17 | # cgo is enabled only for the race test binary. | ||
| 18 | NEFERCAP_REQUIRE_WAYLAND=1 CGO_ENABLED=1 go test -race ./... | 18 | NEFERCAP_REQUIRE_WAYLAND=1 CGO_ENABLED=1 go test -race ./... | ||
| 19 | + | # Go sources must be gofmt-clean (lists the offending files and fails otherwise). | |||
| 20 | + | fmt-check: | |||
| 21 | + | test -z "$$(gofmt -l ./internal ./cmd)" || { gofmt -l ./internal ./cmd >&2; exit 1; } | |||
| 19 | # go.mod and go.sum must be tidy (prints the diff and fails otherwise). | 22 | # go.mod and go.sum must be tidy (prints the diff and fails otherwise). | ||
| 20 | mod-check: | 23 | mod-check: | ||
| 21 | go mod tidy -diff | 24 | go mod tidy -diff | ||
| @@ -56,4 +59,24 @@ perf-check: | |||||
| 56 | printf '%s\n' "$$out" | grep -qx "$$t" || { echo "perf-check: test $$t not found in $(PERF_PKGS)" >&2; exit 1; }; \ | 59 | printf '%s\n' "$$out" | grep -qx "$$t" || { echo "perf-check: test $$t not found in $(PERF_PKGS)" >&2; exit 1; }; \ | ||
| 57 | done | 60 | done | ||
| 58 | NEFERCAP_REQUIRE_WAYLAND=1 CGO_ENABLED=0 go test $(PERF_PKGS) -run '$(PERF_RE)' -count=1 | 61 | NEFERCAP_REQUIRE_WAYLAND=1 CGO_ENABLED=0 go test $(PERF_PKGS) -run '$(PERF_RE)' -count=1 | ||
| 59 | - | check: mod-check vet test arch fakes-check perf-check | 62 | + | check: fmt-check mod-check vet test arch fakes-check perf-check |
| 63 | + | ||||
| 64 | + | # Arch package of the committed HEAD (packaging/arch/PKGBUILD). Go modules | |||
| 65 | + | # come from the module proxy in prepare(); the build itself runs offline. | |||
| 66 | + | # pacman-ordered version: 0.0.0.r<commits>.g<hash>; a tag replaces 0.0.0. | |||
| 67 | + | pkg: SHELL := bash | |||
| 68 | + | pkg: .SHELLFLAGS := -eo pipefail -c | |||
| 69 | + | pkg: | |||
| 70 | + | @test -z "$$(git status --porcelain)" || echo "warning: uncommitted changes are not packaged" >&2 | |||
| 71 | + | v=$$(t=$$(git describe --tags --abbrev=0 2>/dev/null | sed 's/^v//; s/-/_/g'); \ | |||
| 72 | + | echo "$${t:-0.0.0}.r$$(git rev-list --count HEAD).g$$(git rev-parse --short HEAD)"); \ | |||
| 73 | + | d=$$(mktemp -d /tmp/nefercap-pkg.XXXXXX); trap 'rm -rf "$$d"' EXIT; \ | |||
| 74 | + | git archive --prefix=nefercap-$$v/ -o "$$d/nefercap-$$v.tar.gz" HEAD; \ | |||
| 75 | + | cp packaging/arch/PKGBUILD "$$d/"; \ | |||
| 76 | + | cd "$$d" && sed -i "s/^pkgver=.*/pkgver=$$v/; s/^sha256sums=.*/sha256sums=('$$(sha256sum *.tar.gz | cut -d' ' -f1)')/" PKGBUILD; \ | |||
| 77 | + | makepkg -f --noconfirm; mkdir -p $(CURDIR)/dist; rm -f $(CURDIR)/dist/nefercap-*.pkg.tar.zst; mv *.pkg.tar.zst $(CURDIR)/dist/ | |||
| 78 | + | @ls dist/*.pkg.tar.zst | |||
| 79 | + | ||||
| 80 | + | # Build the package, then install it with pacman (asks for sudo). | |||
| 81 | + | install: pkg | |||
| 82 | + | sudo pacman -U dist/nefercap-*.pkg.tar.zst | |||
M README.md +13 -2
| @@ -76,6 +76,7 @@ nefercap shot # selector, screenshot only | |||
| 76 | 76 | nefercap rec # selector, recording only | |
| 77 | 77 | nefercap stop # stop the active recording | |
| 78 | 78 | nefercap status # print the recording state | |
| 79 | + | nefercap version # print the version | |
| 79 | 80 | ``` | |
| 80 | 81 | ||
| 81 | 82 | Scripted commands never open the selector: | |
| @@ -108,7 +109,7 @@ NeferWL's optional capture extension adds workspace and region sources and keeps | |||
| 108 | 109 | | KDE Plasma | assumed | no | no | no | | |
| 109 | 110 | | GNOME | unsupported | no | no | no | | |
| 110 | 111 | ||
| 111 | - | Workspaces need `ext-workspace-v1`; with NeferWL's extension the selector outlines the workspace frame (a workspace smaller than its monitor), and the HUD sits in it. Without the extension `W` outlines the whole monitor and captures the workspace currently shown, as its monitor. Without the extension a region is cropped from the monitor frame. *Assumed* means it follows from the protocols the compositor advertises; only NeferWL is tested. | |
| 112 | + | Workspaces need `ext-workspace-v1`; with NeferWL's extension the selector outlines the workspace frame (a workspace smaller than its monitor), and the HUD sits in it (placed when the recording starts: if the workspace frame changes during the recording, the video follows it and the HUD stays in place). Without the extension `W` outlines the whole monitor and captures the workspace currently shown, as its monitor. Without the extension a region is cropped from the monitor frame. *Assumed* means it follows from the protocols the compositor advertises; only NeferWL is tested. | |
| 112 | 113 | ||
| 113 | 114 | A compositor may refuse a capture client; the capture then ends with an error. | |
| 114 | 115 | ||
| @@ -116,11 +117,21 @@ Output is SDR, and video is silent H.264 in fragmented MP4. Audio, pause and cur | |||
| 116 | 117 | ||
| 117 | 118 | ## Install | |
| 118 | 119 | ||
| 120 | + | On Arch Linux, install from the AUR: | |
| 121 | + | ||
| 122 | + | ```sh | |
| 123 | + | paru -S nefercap-bin # latest release, pre-built | |
| 124 | + | paru -S nefercap-git # latest main, built from source | |
| 125 | + | ``` | |
| 126 | + | ||
| 127 | + | Pre-built archives are on the [releases page](https://github.com/bnema/nefercap/releases). To build from a checkout: | |
| 128 | + | ||
| 119 | 129 | ```sh | |
| 120 | 130 | make bin # builds bin/nefercap | |
| 131 | + | make pkg # builds an Arch package of HEAD into dist/ | |
| 121 | 132 | ``` | |
| 122 | 133 | ||
| 123 | - | Install it as `/usr/bin/nefercap`: NeferWL allows that path to capture by default. Elsewhere (for example `~/.local/bin`, or `go run`), NeferWL refuses capture until root lists the path in `/etc/neferwl/capture-allow`. | |
| 134 | + | The packages install `/usr/bin/nefercap`: NeferWL allows that path to capture by default. Elsewhere (for example `~/.local/bin`, or `go run`), NeferWL refuses capture until root lists the path in `/etc/neferwl/capture-allow`. | |
| 124 | 135 | ||
| 125 | 136 | ## Development | |
| 126 | 137 | ||
| @@ -76,6 +76,7 @@ nefercap shot # selector, screenshot only | |||||
| 76 | nefercap rec # selector, recording only | 76 | nefercap rec # selector, recording only | ||
| 77 | nefercap stop # stop the active recording | 77 | nefercap stop # stop the active recording | ||
| 78 | nefercap status # print the recording state | 78 | nefercap status # print the recording state | ||
| 79 | + | nefercap version # print the version | |||
| 79 | ``` | 80 | ``` | ||
| 80 | 81 | ||||
| 81 | Scripted commands never open the selector: | 82 | Scripted commands never open the selector: | ||
| @@ -108,7 +109,7 @@ NeferWL's optional capture extension adds workspace and region sources and keeps | |||||
| 108 | | KDE Plasma | assumed | no | no | no | | 109 | | KDE Plasma | assumed | no | no | no | | ||
| 109 | | GNOME | unsupported | no | no | no | | 110 | | GNOME | unsupported | no | no | no | | ||
| 110 | 111 | ||||
| 111 | - | Workspaces need `ext-workspace-v1`; with NeferWL's extension the selector outlines the workspace frame (a workspace smaller than its monitor), and the HUD sits in it. Without the extension `W` outlines the whole monitor and captures the workspace currently shown, as its monitor. Without the extension a region is cropped from the monitor frame. *Assumed* means it follows from the protocols the compositor advertises; only NeferWL is tested. | 112 | + | Workspaces need `ext-workspace-v1`; with NeferWL's extension the selector outlines the workspace frame (a workspace smaller than its monitor), and the HUD sits in it (placed when the recording starts: if the workspace frame changes during the recording, the video follows it and the HUD stays in place). Without the extension `W` outlines the whole monitor and captures the workspace currently shown, as its monitor. Without the extension a region is cropped from the monitor frame. *Assumed* means it follows from the protocols the compositor advertises; only NeferWL is tested. |
| 112 | 113 | ||||
| 113 | A compositor may refuse a capture client; the capture then ends with an error. | 114 | A compositor may refuse a capture client; the capture then ends with an error. | ||
| 114 | 115 | ||||
| @@ -116,11 +117,21 @@ Output is SDR, and video is silent H.264 in fragmented MP4. Audio, pause and cur | |||||
| 116 | 117 | ||||
| 117 | ## Install | 118 | ## Install | ||
| 118 | 119 | ||||
| 120 | + | On Arch Linux, install from the AUR: | |||
| 121 | + | ||||
| 122 | + | ```sh | |||
| 123 | + | paru -S nefercap-bin # latest release, pre-built | |||
| 124 | + | paru -S nefercap-git # latest main, built from source | |||
| 125 | + | ``` | |||
| 126 | + | ||||
| 127 | + | Pre-built archives are on the [releases page](https://github.com/bnema/nefercap/releases). To build from a checkout: | |||
| 128 | + | ||||
| 119 | ```sh | 129 | ```sh | ||
| 120 | make bin # builds bin/nefercap | 130 | make bin # builds bin/nefercap | ||
| 131 | + | make pkg # builds an Arch package of HEAD into dist/ | |||
| 121 | ``` | 132 | ``` | ||
| 122 | 133 | ||||
| 123 | - | Install it as `/usr/bin/nefercap`: NeferWL allows that path to capture by default. Elsewhere (for example `~/.local/bin`, or `go run`), NeferWL refuses capture until root lists the path in `/etc/neferwl/capture-allow`. | 134 | + | The packages install `/usr/bin/nefercap`: NeferWL allows that path to capture by default. Elsewhere (for example `~/.local/bin`, or `go run`), NeferWL refuses capture until root lists the path in `/etc/neferwl/capture-allow`. |
| 124 | 135 | ||||
| 125 | ## Development | 136 | ## Development | ||
| 126 | 137 | ||||
M cmd/nefercap/main.go +18 -0
| @@ -5,6 +5,7 @@ import ( | |||
| 5 | 5 | "errors" | |
| 6 | 6 | "flag" | |
| 7 | 7 | "fmt" | |
| 8 | + | "io" | |
| 8 | 9 | "os" | |
| 9 | 10 | "os/signal" | |
| 10 | 11 | "syscall" | |
| @@ -14,6 +15,9 @@ import ( | |||
| 14 | 15 | "github.com/bnema/nefercap/internal/logging" | |
| 15 | 16 | ) | |
| 16 | 17 | ||
| 18 | + | // version is set at build time: -ldflags "-X main.version=1.2.3". | |
| 19 | + | var version = "dev" | |
| 20 | + | ||
| 17 | 21 | func main() { os.Exit(run()) } | |
| 18 | 22 | ||
| 19 | 23 | // Only the invocation's bare signal cancellation is a successful interruption. | |
| @@ -22,7 +26,21 @@ func interrupted(ctx context.Context, err error) bool { | |||
| 22 | 26 | return err == context.Canceled && ctx.Err() != nil | |
| 23 | 27 | } | |
| 24 | 28 | ||
| 29 | + | // printVersion implements `nefercap version`. It runs before flag parsing so | |
| 30 | + | // it needs no Wayland connection or config. | |
| 31 | + | func printVersion(args []string, stdout, stderr io.Writer) int { | |
| 32 | + | if len(args) > 0 { | |
| 33 | + | fmt.Fprintf(stderr, "unexpected argument %q\n", args[0]) | |
| 34 | + | return 2 | |
| 35 | + | } | |
| 36 | + | fmt.Fprintln(stdout, version) | |
| 37 | + | return 0 | |
| 38 | + | } | |
| 39 | + | ||
| 25 | 40 | func run() int { | |
| 41 | + | if len(os.Args) > 1 && os.Args[1] == "version" { | |
| 42 | + | return printVersion(os.Args[2:], os.Stdout, os.Stderr) | |
| 43 | + | } | |
| 26 | 44 | options, err := cli.Parse(os.Args[1:], os.Stderr) | |
| 27 | 45 | if errors.Is(err, flag.ErrHelp) { | |
| 28 | 46 | return 0 | |
| @@ -5,6 +5,7 @@ import ( | |||||
| 5 | "errors" | 5 | "errors" | ||
| 6 | "flag" | 6 | "flag" | ||
| 7 | "fmt" | 7 | "fmt" | ||
| 8 | + | "io" | |||
| 8 | "os" | 9 | "os" | ||
| 9 | "os/signal" | 10 | "os/signal" | ||
| 10 | "syscall" | 11 | "syscall" | ||
| @@ -14,6 +15,9 @@ import ( | |||||
| 14 | "github.com/bnema/nefercap/internal/logging" | 15 | "github.com/bnema/nefercap/internal/logging" | ||
| 15 | ) | 16 | ) | ||
| 16 | 17 | ||||
| 18 | + | // version is set at build time: -ldflags "-X main.version=1.2.3". | |||
| 19 | + | var version = "dev" | |||
| 20 | + | ||||
| 17 | func main() { os.Exit(run()) } | 21 | func main() { os.Exit(run()) } | ||
| 18 | 22 | ||||
| 19 | // Only the invocation's bare signal cancellation is a successful interruption. | 23 | // Only the invocation's bare signal cancellation is a successful interruption. | ||
| @@ -22,7 +26,21 @@ func interrupted(ctx context.Context, err error) bool { | |||||
| 22 | return err == context.Canceled && ctx.Err() != nil | 26 | return err == context.Canceled && ctx.Err() != nil | ||
| 23 | } | 27 | } | ||
| 24 | 28 | ||||
| 29 | + | // printVersion implements `nefercap version`. It runs before flag parsing so | |||
| 30 | + | // it needs no Wayland connection or config. | |||
| 31 | + | func printVersion(args []string, stdout, stderr io.Writer) int { | |||
| 32 | + | if len(args) > 0 { | |||
| 33 | + | fmt.Fprintf(stderr, "unexpected argument %q\n", args[0]) | |||
| 34 | + | return 2 | |||
| 35 | + | } | |||
| 36 | + | fmt.Fprintln(stdout, version) | |||
| 37 | + | return 0 | |||
| 38 | + | } | |||
| 39 | + | ||||
| 25 | func run() int { | 40 | func run() int { | ||
| 41 | + | if len(os.Args) > 1 && os.Args[1] == "version" { | |||
| 42 | + | return printVersion(os.Args[2:], os.Stdout, os.Stderr) | |||
| 43 | + | } | |||
| 26 | options, err := cli.Parse(os.Args[1:], os.Stderr) | 44 | options, err := cli.Parse(os.Args[1:], os.Stderr) | ||
| 27 | if errors.Is(err, flag.ErrHelp) { | 45 | if errors.Is(err, flag.ErrHelp) { | ||
| 28 | return 0 | 46 | return 0 | ||
M cmd/nefercap/main_test.go +34 -0
| @@ -1,10 +1,12 @@ | |||
| 1 | 1 | package main | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | + | "bytes" | |
| 4 | 5 | "context" | |
| 5 | 6 | "errors" | |
| 6 | 7 | "fmt" | |
| 7 | 8 | "os" | |
| 9 | + | "strings" | |
| 8 | 10 | "testing" | |
| 9 | 11 | ) | |
| 10 | 12 | ||
| @@ -41,3 +43,35 @@ func TestUsageExitCodes(t *testing.T) { | |||
| 41 | 43 | } | |
| 42 | 44 | } | |
| 43 | 45 | } | |
| 46 | + | ||
| 47 | + | func TestVersion(t *testing.T) { | |
| 48 | + | original := version | |
| 49 | + | defer func() { version = original }() | |
| 50 | + | ||
| 51 | + | var out, errOut bytes.Buffer | |
| 52 | + | if code := printVersion(nil, &out, &errOut); code != 0 || out.String() != "dev\n" || errOut.Len() != 0 { | |
| 53 | + | t.Fatalf("default: exit %d, stdout %q, stderr %q", code, out.String(), errOut.String()) | |
| 54 | + | } | |
| 55 | + | version = "1.2.3" | |
| 56 | + | out.Reset() | |
| 57 | + | if code := printVersion(nil, &out, &errOut); code != 0 || out.String() != "1.2.3\n" { | |
| 58 | + | t.Fatalf("set: exit %d, stdout %q", code, out.String()) | |
| 59 | + | } | |
| 60 | + | out.Reset() | |
| 61 | + | if code := printVersion([]string{"extra"}, &out, &errOut); code != 2 || out.Len() != 0 || !strings.Contains(errOut.String(), `"extra"`) { | |
| 62 | + | t.Fatalf("extra: exit %d, stdout %q, stderr %q", code, out.String(), errOut.String()) | |
| 63 | + | } | |
| 64 | + | } | |
| 65 | + | ||
| 66 | + | func TestVersionCommandRuns(t *testing.T) { | |
| 67 | + | original := os.Args | |
| 68 | + | defer func() { os.Args = original }() | |
| 69 | + | os.Args = []string{"nefercap", "version"} | |
| 70 | + | if code := run(); code != 0 { | |
| 71 | + | t.Fatalf("version: exit %d", code) | |
| 72 | + | } | |
| 73 | + | os.Args = []string{"nefercap", "version", "x"} | |
| 74 | + | if code := run(); code != 2 { | |
| 75 | + | t.Fatalf("version x: exit %d, want 2", code) | |
| 76 | + | } | |
| 77 | + | } | |
| @@ -1,10 +1,12 @@ | |||||
| 1 | package main | 1 | package main | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | + | "bytes" | |||
| 4 | "context" | 5 | "context" | ||
| 5 | "errors" | 6 | "errors" | ||
| 6 | "fmt" | 7 | "fmt" | ||
| 7 | "os" | 8 | "os" | ||
| 9 | + | "strings" | |||
| 8 | "testing" | 10 | "testing" | ||
| 9 | ) | 11 | ) | ||
| 10 | 12 | ||||
| @@ -41,3 +43,35 @@ func TestUsageExitCodes(t *testing.T) { | |||||
| 41 | } | 43 | } | ||
| 42 | } | 44 | } | ||
| 43 | } | 45 | } | ||
| 46 | + | ||||
| 47 | + | func TestVersion(t *testing.T) { | |||
| 48 | + | original := version | |||
| 49 | + | defer func() { version = original }() | |||
| 50 | + | ||||
| 51 | + | var out, errOut bytes.Buffer | |||
| 52 | + | if code := printVersion(nil, &out, &errOut); code != 0 || out.String() != "dev\n" || errOut.Len() != 0 { | |||
| 53 | + | t.Fatalf("default: exit %d, stdout %q, stderr %q", code, out.String(), errOut.String()) | |||
| 54 | + | } | |||
| 55 | + | version = "1.2.3" | |||
| 56 | + | out.Reset() | |||
| 57 | + | if code := printVersion(nil, &out, &errOut); code != 0 || out.String() != "1.2.3\n" { | |||
| 58 | + | t.Fatalf("set: exit %d, stdout %q", code, out.String()) | |||
| 59 | + | } | |||
| 60 | + | out.Reset() | |||
| 61 | + | if code := printVersion([]string{"extra"}, &out, &errOut); code != 2 || out.Len() != 0 || !strings.Contains(errOut.String(), `"extra"`) { | |||
| 62 | + | t.Fatalf("extra: exit %d, stdout %q, stderr %q", code, out.String(), errOut.String()) | |||
| 63 | + | } | |||
| 64 | + | } | |||
| 65 | + | ||||
| 66 | + | func TestVersionCommandRuns(t *testing.T) { | |||
| 67 | + | original := os.Args | |||
| 68 | + | defer func() { os.Args = original }() | |||
| 69 | + | os.Args = []string{"nefercap", "version"} | |||
| 70 | + | if code := run(); code != 0 { | |||
| 71 | + | t.Fatalf("version: exit %d", code) | |||
| 72 | + | } | |||
| 73 | + | os.Args = []string{"nefercap", "version", "x"} | |||
| 74 | + | if code := run(); code != 2 { | |||
| 75 | + | t.Fatalf("version x: exit %d, want 2", code) | |||
| 76 | + | } | |||
| 77 | + | } | |||
M internal/adapters/cli/cli.go +3 -0
| @@ -92,6 +92,9 @@ Script commands (never open the selector): | |||
| 92 | 92 | screenshot save a PNG screenshot | |
| 93 | 93 | record record silent video until interrupted or target video length is reached | |
| 94 | 94 | ||
| 95 | + | Other: | |
| 96 | + | version print the nefercap version | |
| 97 | + | ||
| 95 | 98 | Flags: | |
| 96 | 99 | -clipboard copy PNG with wl-copy; no file unless -file is given (shot, screenshot) | |
| 97 | 100 | -debug enable debug logging (all commands) | |
| @@ -92,6 +92,9 @@ Script commands (never open the selector): | |||||
| 92 | screenshot save a PNG screenshot | 92 | screenshot save a PNG screenshot | ||
| 93 | record record silent video until interrupted or target video length is reached | 93 | record record silent video until interrupted or target video length is reached | ||
| 94 | 94 | ||||
| 95 | + | Other: | |||
| 96 | + | version print the nefercap version | |||
| 97 | + | ||||
| 95 | Flags: | 98 | Flags: | ||
| 96 | -clipboard copy PNG with wl-copy; no file unless -file is given (shot, screenshot) | 99 | -clipboard copy PNG with wl-copy; no file unless -file is given (shot, screenshot) | ||
| 97 | -debug enable debug logging (all commands) | 100 | -debug enable debug logging (all commands) | ||
M internal/adapters/indicator/indicator.go +3 -1
| @@ -62,7 +62,9 @@ type Indicator struct { | |||
| 62 | 62 | // Frame optionally is the part of the output that is recorded, in | |
| 63 | 63 | // output-local logical pixels (a workspace smaller than its output): the | |
| 64 | 64 | // HUD sits at the top centre of it. The zero value is the whole output. | |
| 65 | - | // Set it before Run. | |
| 65 | + | // The position is fixed when the recording starts: if the workspace frame | |
| 66 | + | // changes during the recording, the video follows it and the HUD stays in | |
| 67 | + | // place. Set it before Run. | |
| 66 | 68 | Frame ports.Region | |
| 67 | 69 | ||
| 68 | 70 | authorize func(context.Context, nefergui.WaylandSurface) error | |
| @@ -62,7 +62,9 @@ type Indicator struct { | |||||
| 62 | // Frame optionally is the part of the output that is recorded, in | 62 | // Frame optionally is the part of the output that is recorded, in | ||
| 63 | // output-local logical pixels (a workspace smaller than its output): the | 63 | // output-local logical pixels (a workspace smaller than its output): the | ||
| 64 | // HUD sits at the top centre of it. The zero value is the whole output. | 64 | // HUD sits at the top centre of it. The zero value is the whole output. | ||
| 65 | - | // Set it before Run. | 65 | + | // The position is fixed when the recording starts: if the workspace frame |
| 66 | + | // changes during the recording, the video follows it and the HUD stays in | |||
| 67 | + | // place. Set it before Run. | |||
| 66 | Frame ports.Region | 68 | Frame ports.Region | ||
| 67 | 69 | ||||
| 68 | authorize func(context.Context, nefergui.WaylandSurface) error | 70 | authorize func(context.Context, nefergui.WaylandSurface) error | ||
M internal/adapters/wayland/extensions.go +1 -1
| @@ -42,7 +42,7 @@ const ( | |||
| 42 | 42 | reqNeferwlWorkspaceSource = 1 | |
| 43 | 43 | reqNeferwlRegionSource = 2 | |
| 44 | 44 | reqGetWorkspaceFrame = 3 | |
| 45 | - | reqWorkspaceFrameDestroy = 0 | |
| 45 | + | reqWorkspaceFrameDestroy = 0 | |
| 46 | 46 | reqGetExclusion = 1 | |
| 47 | 47 | reqAttachSurface = 2 | |
| 48 | 48 | reqManagerDestroy = 0 | |
| @@ -42,7 +42,7 @@ const ( | |||||
| 42 | reqNeferwlWorkspaceSource = 1 | 42 | reqNeferwlWorkspaceSource = 1 | ||
| 43 | reqNeferwlRegionSource = 2 | 43 | reqNeferwlRegionSource = 2 | ||
| 44 | reqGetWorkspaceFrame = 3 | 44 | reqGetWorkspaceFrame = 3 | ||
| 45 | - | reqWorkspaceFrameDestroy = 0 | 45 | + | reqWorkspaceFrameDestroy = 0 |
| 46 | reqGetExclusion = 1 | 46 | reqGetExclusion = 1 | ||
| 47 | reqAttachSurface = 2 | 47 | reqAttachSurface = 2 | ||
| 48 | reqManagerDestroy = 0 | 48 | reqManagerDestroy = 0 | ||
M internal/adapters/wayland/testserver/neferwl/neferwl-image-capture-v1.xml +4 -2
| @@ -67,7 +67,8 @@ | |||
| 67 | 67 | <description summary="learn the frame of a workspace"> | |
| 68 | 68 | ext-workspace-v1 carries no geometry. The frame object reports the | |
| 69 | 69 | rectangle that a workspace source of the same handle captures. For a | |
| 70 | - | handle already removed, the object never sends frame. | |
| 70 | + | handle already removed, the object never sends frame. It needs no | |
| 71 | + | capture authorization: the frame is layout information, not pixels. | |
| 71 | 72 | </description> | |
| 72 | 73 | <arg name="id" type="new_id" interface="neferwl_workspace_frame_v1"/> | |
| 73 | 74 | <arg name="workspace" type="object" interface="ext_workspace_handle_v1"/> | |
| @@ -89,7 +90,8 @@ | |||
| 89 | 90 | <description summary="the workspace frame"> | |
| 90 | 91 | The frame in output-local logical pixels. Sent when the object is | |
| 91 | 92 | created and again whenever the frame changes. Not sent once the | |
| 92 | - | workspace is removed. | |
| 93 | + | workspace is removed. Not sent while the session is locked; the | |
| 94 | + | current frame is sent after unlock if it changed. | |
| 93 | 95 | </description> | |
| 94 | 96 | <arg name="x" type="int"/> | |
| 95 | 97 | <arg name="y" type="int"/> | |
| @@ -67,7 +67,8 @@ | |||||
| 67 | <description summary="learn the frame of a workspace"> | 67 | <description summary="learn the frame of a workspace"> | ||
| 68 | ext-workspace-v1 carries no geometry. The frame object reports the | 68 | ext-workspace-v1 carries no geometry. The frame object reports the | ||
| 69 | rectangle that a workspace source of the same handle captures. For a | 69 | rectangle that a workspace source of the same handle captures. For a | ||
| 70 | - | handle already removed, the object never sends frame. | 70 | + | handle already removed, the object never sends frame. It needs no |
| 71 | + | capture authorization: the frame is layout information, not pixels. | |||
| 71 | </description> | 72 | </description> | ||
| 72 | <arg name="id" type="new_id" interface="neferwl_workspace_frame_v1"/> | 73 | <arg name="id" type="new_id" interface="neferwl_workspace_frame_v1"/> | ||
| 73 | <arg name="workspace" type="object" interface="ext_workspace_handle_v1"/> | 74 | <arg name="workspace" type="object" interface="ext_workspace_handle_v1"/> | ||
| @@ -89,7 +90,8 @@ | |||||
| 89 | <description summary="the workspace frame"> | 90 | <description summary="the workspace frame"> | ||
| 90 | The frame in output-local logical pixels. Sent when the object is | 91 | The frame in output-local logical pixels. Sent when the object is | ||
| 91 | created and again whenever the frame changes. Not sent once the | 92 | created and again whenever the frame changes. Not sent once the | ||
| 92 | - | workspace is removed. | 93 | + | workspace is removed. Not sent while the session is locked; the |
| 94 | + | current frame is sent after unlock if it changed. | |||
| 93 | </description> | 95 | </description> | ||
| 94 | <arg name="x" type="int"/> | 96 | <arg name="x" type="int"/> | ||
| 95 | <arg name="y" type="int"/> | 97 | <arg name="y" type="int"/> | ||
M internal/adapters/wayland/workspaces.go +4 -1
| @@ -274,7 +274,10 @@ func (s *Source) workspaceList() []ports.Workspace { | |||
| 274 | 274 | ||
| 275 | 275 | // workspaceRegion is where the workspace is on o, in output-local logical | |
| 276 | 276 | // pixels: its frame from NeferWL's extension, clipped to the output, else the | |
| 277 | - | // whole output (ext-workspace carries no geometry). | |
| 277 | + | // whole output (ext-workspace carries no geometry). NeferWL computes the frame | |
| 278 | + | // clamped to the output and centred, so it never lies outside it; the clip and | |
| 279 | + | // the whole-output fallback are safeguards only (core's Picker.clip is the | |
| 280 | + | // single clip applied to the selection). | |
| 278 | 281 | func workspaceRegion(w *workspace, o *output) ports.Region { | |
| 279 | 282 | lw, lh := o.logicalSize() | |
| 280 | 283 | whole := ports.Region{Width: lw, Height: lh} | |
| @@ -274,7 +274,10 @@ func (s *Source) workspaceList() []ports.Workspace { | |||||
| 274 | 274 | ||||
| 275 | // workspaceRegion is where the workspace is on o, in output-local logical | 275 | // workspaceRegion is where the workspace is on o, in output-local logical | ||
| 276 | // pixels: its frame from NeferWL's extension, clipped to the output, else the | 276 | // pixels: its frame from NeferWL's extension, clipped to the output, else the | ||
| 277 | - | // whole output (ext-workspace carries no geometry). | 277 | + | // whole output (ext-workspace carries no geometry). NeferWL computes the frame |
| 278 | + | // clamped to the output and centred, so it never lies outside it; the clip and | |||
| 279 | + | // the whole-output fallback are safeguards only (core's Picker.clip is the | |||
| 280 | + | // single clip applied to the selection). | |||
| 278 | func workspaceRegion(w *workspace, o *output) ports.Region { | 281 | func workspaceRegion(w *workspace, o *output) ports.Region { | ||
| 279 | lw, lh := o.logicalSize() | 282 | lw, lh := o.logicalSize() | ||
| 280 | whole := ports.Region{Width: lw, Height: lh} | 283 | whole := ports.Region{Width: lw, Height: lh} | ||
M internal/app/record_interactive.go +3 -0
| @@ -184,6 +184,9 @@ func recordedFrame(ctx context.Context, source *wayland.Source, t ports.Target) | |||
| 184 | 184 | } | |
| 185 | 185 | list, err := source.Workspaces(ctx) | |
| 186 | 186 | if err != nil { | |
| 187 | + | if ctx.Err() != nil { | |
| 188 | + | return ports.Region{} | |
| 189 | + | } | |
| 187 | 190 | log := logging.For(ctx, "app") | |
| 188 | 191 | log.Warn().Err(err).Msg("workspace frame unavailable: HUD on the whole output") | |
| 189 | 192 | return ports.Region{} | |
| @@ -184,6 +184,9 @@ func recordedFrame(ctx context.Context, source *wayland.Source, t ports.Target) | |||||
| 184 | } | 184 | } | ||
| 185 | list, err := source.Workspaces(ctx) | 185 | list, err := source.Workspaces(ctx) | ||
| 186 | if err != nil { | 186 | if err != nil { | ||
| 187 | + | if ctx.Err() != nil { | |||
| 188 | + | return ports.Region{} | |||
| 189 | + | } | |||
| 187 | log := logging.For(ctx, "app") | 190 | log := logging.For(ctx, "app") | ||
| 188 | log.Warn().Err(err).Msg("workspace frame unavailable: HUD on the whole output") | 191 | log.Warn().Err(err).Msg("workspace frame unavailable: HUD on the whole output") | ||
| 189 | return ports.Region{} | 192 | return ports.Region{} | ||
A packaging/arch/PKGBUILD +41 -0
| @@ -0,0 +1,41 @@ | |||
| 1 | + | # Built by `make pkg` at the repo root, from `git archive HEAD`. | |
| 2 | + | pkgname=nefercap | |
| 3 | + | pkgver=0.0.0 | |
| 4 | + | pkgrel=1 | |
| 5 | + | pkgdesc='Screenshots and silent screen recording for Wayland, from one shortcut' | |
| 6 | + | arch=('x86_64' 'aarch64') | |
| 7 | + | url='https://github.com/bnema/nefercap' | |
| 8 | + | license=('GPL-3.0-only') | |
| 9 | + | # Loaded at runtime (purego, no cgo): not seen by makepkg's library scan. | |
| 10 | + | depends=('glibc' 'wayland' 'libxkbcommon' 'vulkan-icd-loader' 'vulkan-driver') | |
| 11 | + | makedepends=('go>=2:1.27') | |
| 12 | + | optdepends=('ffmpeg: screen recording (libx264)' | |
| 13 | + | 'wl-clipboard: copy screenshots to the clipboard' | |
| 14 | + | 'neferwl: hidden-workspace capture, HUD exclusion and capture indicator') | |
| 15 | + | source=("$pkgname-$pkgver.tar.gz") | |
| 16 | + | sha256sums=('SKIP') | |
| 17 | + | ||
| 18 | + | prepare() { | |
| 19 | + | cd "$pkgname-$pkgver" | |
| 20 | + | export GOPATH="$srcdir/gopath" GOFLAGS='-mod=readonly -modcacherw' GOWORK=off GOTOOLCHAIN=local | |
| 21 | + | go mod download | |
| 22 | + | go mod vendor | |
| 23 | + | } | |
| 24 | + | ||
| 25 | + | build() { | |
| 26 | + | cd "$pkgname-$pkgver" | |
| 27 | + | export CGO_ENABLED=0 GOFLAGS='-mod=vendor -trimpath -buildvcs=false -buildmode=pie' GOPROXY=off GOWORK=off GOTOOLCHAIN=local | |
| 28 | + | go build -ldflags "-X main.version=$pkgver" -o nefercap ./cmd/nefercap | |
| 29 | + | } | |
| 30 | + | ||
| 31 | + | check() { | |
| 32 | + | cd "$pkgname-$pkgver" | |
| 33 | + | ./nefercap version | |
| 34 | + | } | |
| 35 | + | ||
| 36 | + | package() { | |
| 37 | + | cd "$pkgname-$pkgver" | |
| 38 | + | install -Dm755 nefercap "$pkgdir/usr/bin/nefercap" | |
| 39 | + | install -Dm644 README.md "$pkgdir/usr/share/doc/nefercap/README.md" | |
| 40 | + | install -Dm644 LICENSE "$pkgdir/usr/share/licenses/nefercap/LICENSE" | |
| 41 | + | } | |
| @@ -0,0 +1,41 @@ | |||||
| 1 | + | # Built by `make pkg` at the repo root, from `git archive HEAD`. | |||
| 2 | + | pkgname=nefercap | |||
| 3 | + | pkgver=0.0.0 | |||
| 4 | + | pkgrel=1 | |||
| 5 | + | pkgdesc='Screenshots and silent screen recording for Wayland, from one shortcut' | |||
| 6 | + | arch=('x86_64' 'aarch64') | |||
| 7 | + | url='https://github.com/bnema/nefercap' | |||
| 8 | + | license=('GPL-3.0-only') | |||
| 9 | + | # Loaded at runtime (purego, no cgo): not seen by makepkg's library scan. | |||
| 10 | + | depends=('glibc' 'wayland' 'libxkbcommon' 'vulkan-icd-loader' 'vulkan-driver') | |||
| 11 | + | makedepends=('go>=2:1.27') | |||
| 12 | + | optdepends=('ffmpeg: screen recording (libx264)' | |||
| 13 | + | 'wl-clipboard: copy screenshots to the clipboard' | |||
| 14 | + | 'neferwl: hidden-workspace capture, HUD exclusion and capture indicator') | |||
| 15 | + | source=("$pkgname-$pkgver.tar.gz") | |||
| 16 | + | sha256sums=('SKIP') | |||
| 17 | + | ||||
| 18 | + | prepare() { | |||
| 19 | + | cd "$pkgname-$pkgver" | |||
| 20 | + | export GOPATH="$srcdir/gopath" GOFLAGS='-mod=readonly -modcacherw' GOWORK=off GOTOOLCHAIN=local | |||
| 21 | + | go mod download | |||
| 22 | + | go mod vendor | |||
| 23 | + | } | |||
| 24 | + | ||||
| 25 | + | build() { | |||
| 26 | + | cd "$pkgname-$pkgver" | |||
| 27 | + | export CGO_ENABLED=0 GOFLAGS='-mod=vendor -trimpath -buildvcs=false -buildmode=pie' GOPROXY=off GOWORK=off GOTOOLCHAIN=local | |||
| 28 | + | go build -ldflags "-X main.version=$pkgver" -o nefercap ./cmd/nefercap | |||
| 29 | + | } | |||
| 30 | + | ||||
| 31 | + | check() { | |||
| 32 | + | cd "$pkgname-$pkgver" | |||
| 33 | + | ./nefercap version | |||
| 34 | + | } | |||
| 35 | + | ||||
| 36 | + | package() { | |||
| 37 | + | cd "$pkgname-$pkgver" | |||
| 38 | + | install -Dm755 nefercap "$pkgdir/usr/bin/nefercap" | |||
| 39 | + | install -Dm644 README.md "$pkgdir/usr/share/doc/nefercap/README.md" | |||
| 40 | + | install -Dm644 LICENSE "$pkgdir/usr/share/licenses/nefercap/LICENSE" | |||
| 41 | + | } | |||
A packaging/aur/nefercap-git/PKGBUILD +52 -0
| @@ -0,0 +1,52 @@ | |||
| 1 | + | # Maintainer: bnema <b at bnema dot dev> | |
| 2 | + | # The release workflow sets pkgver and publishes this file to the AUR on every | |
| 3 | + | # release tag, so the AUR version never falls behind the latest tag. | |
| 4 | + | pkgname=nefercap-git | |
| 5 | + | pkgver=0.0.0.r0.g0000000 | |
| 6 | + | pkgrel=1 | |
| 7 | + | pkgdesc='Screenshots and silent screen recording for Wayland, from one shortcut (git version)' | |
| 8 | + | arch=('x86_64' 'aarch64') | |
| 9 | + | url='https://github.com/bnema/nefercap' | |
| 10 | + | license=('GPL-3.0-only') | |
| 11 | + | # Loaded at runtime (purego, no cgo): not seen by makepkg's library scan. | |
| 12 | + | depends=('glibc' 'wayland' 'libxkbcommon' 'vulkan-icd-loader' 'vulkan-driver') | |
| 13 | + | makedepends=('go>=2:1.27' 'git') | |
| 14 | + | optdepends=('ffmpeg: screen recording (libx264)' | |
| 15 | + | 'wl-clipboard: copy screenshots to the clipboard' | |
| 16 | + | 'neferwl: hidden-workspace capture, HUD exclusion and capture indicator') | |
| 17 | + | provides=('nefercap') | |
| 18 | + | conflicts=('nefercap' 'nefercap-bin') | |
| 19 | + | source=('git+https://github.com/bnema/nefercap.git') | |
| 20 | + | sha256sums=('SKIP') | |
| 21 | + | ||
| 22 | + | pkgver() { | |
| 23 | + | cd nefercap | |
| 24 | + | # v0.2.0-5-gabc1234 -> 0.2.0.r5.gabc1234. Pre-release tags (v0.3.0-rc1) | |
| 25 | + | # are skipped: pacman sorts 0.3.0.rc1 after 0.3.0. | |
| 26 | + | git describe --long --tags --abbrev=7 --exclude='*-*' | sed 's/^v//;s/\([^-]*-g\)/r\1/;s/-/./g' | |
| 27 | + | } | |
| 28 | + | ||
| 29 | + | prepare() { | |
| 30 | + | cd nefercap | |
| 31 | + | export GOPATH="$srcdir/gopath" GOFLAGS='-mod=readonly -modcacherw' GOTOOLCHAIN=local | |
| 32 | + | go mod download | |
| 33 | + | } | |
| 34 | + | ||
| 35 | + | build() { | |
| 36 | + | cd nefercap | |
| 37 | + | export GOPATH="$srcdir/gopath" CGO_ENABLED=0 GOTOOLCHAIN=local GOPROXY=off | |
| 38 | + | export GOFLAGS='-mod=readonly -modcacherw -trimpath -buildvcs=false -buildmode=pie' | |
| 39 | + | go build -ldflags "-X main.version=$pkgver" -o nefercap ./cmd/nefercap | |
| 40 | + | } | |
| 41 | + | ||
| 42 | + | check() { | |
| 43 | + | cd nefercap | |
| 44 | + | ./nefercap version | |
| 45 | + | } | |
| 46 | + | ||
| 47 | + | package() { | |
| 48 | + | cd nefercap | |
| 49 | + | install -Dm755 nefercap "$pkgdir/usr/bin/nefercap" | |
| 50 | + | install -Dm644 README.md "$pkgdir/usr/share/doc/nefercap/README.md" | |
| 51 | + | install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE" | |
| 52 | + | } | |
| @@ -0,0 +1,52 @@ | |||||
| 1 | + | # Maintainer: bnema <b at bnema dot dev> | |||
| 2 | + | # The release workflow sets pkgver and publishes this file to the AUR on every | |||
| 3 | + | # release tag, so the AUR version never falls behind the latest tag. | |||
| 4 | + | pkgname=nefercap-git | |||
| 5 | + | pkgver=0.0.0.r0.g0000000 | |||
| 6 | + | pkgrel=1 | |||
| 7 | + | pkgdesc='Screenshots and silent screen recording for Wayland, from one shortcut (git version)' | |||
| 8 | + | arch=('x86_64' 'aarch64') | |||
| 9 | + | url='https://github.com/bnema/nefercap' | |||
| 10 | + | license=('GPL-3.0-only') | |||
| 11 | + | # Loaded at runtime (purego, no cgo): not seen by makepkg's library scan. | |||
| 12 | + | depends=('glibc' 'wayland' 'libxkbcommon' 'vulkan-icd-loader' 'vulkan-driver') | |||
| 13 | + | makedepends=('go>=2:1.27' 'git') | |||
| 14 | + | optdepends=('ffmpeg: screen recording (libx264)' | |||
| 15 | + | 'wl-clipboard: copy screenshots to the clipboard' | |||
| 16 | + | 'neferwl: hidden-workspace capture, HUD exclusion and capture indicator') | |||
| 17 | + | provides=('nefercap') | |||
| 18 | + | conflicts=('nefercap' 'nefercap-bin') | |||
| 19 | + | source=('git+https://github.com/bnema/nefercap.git') | |||
| 20 | + | sha256sums=('SKIP') | |||
| 21 | + | ||||
| 22 | + | pkgver() { | |||
| 23 | + | cd nefercap | |||
| 24 | + | # v0.2.0-5-gabc1234 -> 0.2.0.r5.gabc1234. Pre-release tags (v0.3.0-rc1) | |||
| 25 | + | # are skipped: pacman sorts 0.3.0.rc1 after 0.3.0. | |||
| 26 | + | git describe --long --tags --abbrev=7 --exclude='*-*' | sed 's/^v//;s/\([^-]*-g\)/r\1/;s/-/./g' | |||
| 27 | + | } | |||
| 28 | + | ||||
| 29 | + | prepare() { | |||
| 30 | + | cd nefercap | |||
| 31 | + | export GOPATH="$srcdir/gopath" GOFLAGS='-mod=readonly -modcacherw' GOTOOLCHAIN=local | |||
| 32 | + | go mod download | |||
| 33 | + | } | |||
| 34 | + | ||||
| 35 | + | build() { | |||
| 36 | + | cd nefercap | |||
| 37 | + | export GOPATH="$srcdir/gopath" CGO_ENABLED=0 GOTOOLCHAIN=local GOPROXY=off | |||
| 38 | + | export GOFLAGS='-mod=readonly -modcacherw -trimpath -buildvcs=false -buildmode=pie' | |||
| 39 | + | go build -ldflags "-X main.version=$pkgver" -o nefercap ./cmd/nefercap | |||
| 40 | + | } | |||
| 41 | + | ||||
| 42 | + | check() { | |||
| 43 | + | cd nefercap | |||
| 44 | + | ./nefercap version | |||
| 45 | + | } | |||
| 46 | + | ||||
| 47 | + | package() { | |||
| 48 | + | cd nefercap | |||
| 49 | + | install -Dm755 nefercap "$pkgdir/usr/bin/nefercap" | |||
| 50 | + | install -Dm644 README.md "$pkgdir/usr/share/doc/nefercap/README.md" | |||
| 51 | + | install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE" | |||
| 52 | + | } | |||
A packaging/aur/publish.sh +66 -0
| @@ -0,0 +1,66 @@ | |||
| 1 | + | #!/usr/bin/env bash | |
| 2 | + | # Builds an AUR package with makepkg, then pushes its PKGBUILD and .SRCINFO. | |
| 3 | + | # makepkg downloads every source and checks its sha256sum, so a PKGBUILD that | |
| 4 | + | # does not build, or does not match its release assets, is never published. | |
| 5 | + | # | |
| 6 | + | # Usage: publish.sh <pkgname> <PKGBUILD> | |
| 7 | + | # Env: AUR_SSH_PRIVATE_KEY, AUR_USERNAME, AUR_EMAIL | |
| 8 | + | # DRY_RUN=1 builds and prints the files without pushing. | |
| 9 | + | # Runs as a non-root user that may run pacman through sudo (makepkg -s). | |
| 10 | + | set -euo pipefail | |
| 11 | + | CARCH=$(uname -m) | |
| 12 | + | ||
| 13 | + | pkgname=$1 | |
| 14 | + | work=$(mktemp -d) | |
| 15 | + | trap 'rm -rf "$work"' EXIT | |
| 16 | + | cp "$2" "$work/PKGBUILD" | |
| 17 | + | cd "$work" | |
| 18 | + | ||
| 19 | + | makepkg --syncdeps --force --noconfirm | |
| 20 | + | # A -git pkgver() rewrites pkgver in PKGBUILD; .SRCINFO must match it. | |
| 21 | + | makepkg --printsrcinfo >.SRCINFO | |
| 22 | + | pkgver=$(sed -n 's/^\tpkgver = //p' .SRCINFO) | |
| 23 | + | ||
| 24 | + | # makepkg only fetches the sources of this machine's arch: check the others too. | |
| 25 | + | while read -r arch; do | |
| 26 | + | if [[ $arch == "$CARCH" ]] || ! grep -q "^source_$arch=" PKGBUILD; then | |
| 27 | + | continue | |
| 28 | + | fi | |
| 29 | + | { cat /etc/makepkg.conf; echo "CARCH=$arch"; } >"makepkg-$arch.conf" | |
| 30 | + | makepkg --config "makepkg-$arch.conf" --verifysource --noconfirm | |
| 31 | + | done < <(sed -n 's/^\tarch = //p' .SRCINFO) | |
| 32 | + | ||
| 33 | + | # pacman versions use _ where the tag has - (0.2.0-rc1 -> 0.2.0_rc1). | |
| 34 | + | got=$("pkg/$pkgname/usr/bin/nefercap" version) | |
| 35 | + | if [[ $got != "$pkgver" && $got != "${pkgver//_/-}" ]]; then | |
| 36 | + | echo "packaged nefercap reports version $got, want $pkgver" >&2 | |
| 37 | + | exit 1 | |
| 38 | + | fi | |
| 39 | + | ||
| 40 | + | if [[ ${DRY_RUN:-} == 1 ]]; then | |
| 41 | + | cat PKGBUILD .SRCINFO | |
| 42 | + | exit 0 | |
| 43 | + | fi | |
| 44 | + | ||
| 45 | + | ssh_dir=$HOME/.ssh | |
| 46 | + | key=$ssh_dir/aur | |
| 47 | + | install -d -m 700 "$ssh_dir" | |
| 48 | + | (umask 077 && printf '%s\n' "$AUR_SSH_PRIVATE_KEY" >"$key") | |
| 49 | + | # Pinned host key. Fingerprint SHA256:RFzBCUItH9LZS0cKB5UE6ceAYhBD5C8GeOBip8Z11+4 | |
| 50 | + | # is published on https://aur.archlinux.org. | |
| 51 | + | echo 'aur.archlinux.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEuBKrPzbawxA/k2g6NcyV5jmqwJ2s+zpgZGZ7tpLIcN' >"$ssh_dir/known_hosts" | |
| 52 | + | export GIT_SSH_COMMAND="ssh -i $key -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes -o UserKnownHostsFile=$ssh_dir/known_hosts" | |
| 53 | + | ||
| 54 | + | git clone "ssh://aur@aur.archlinux.org/$pkgname.git" aur | |
| 55 | + | cp PKGBUILD .SRCINFO aur/ | |
| 56 | + | cd aur | |
| 57 | + | git add PKGBUILD .SRCINFO | |
| 58 | + | if git diff --cached --quiet; then | |
| 59 | + | echo "$pkgname $pkgver is already on the AUR" | |
| 60 | + | exit 0 | |
| 61 | + | fi | |
| 62 | + | # Commits are signed with the AUR SSH key. | |
| 63 | + | git -c user.name="$AUR_USERNAME" -c user.email="$AUR_EMAIL" \ | |
| 64 | + | -c gpg.format=ssh -c user.signingkey="$key" \ | |
| 65 | + | commit -S -m "Update to $pkgver" | |
| 66 | + | git push origin HEAD:master | |
| @@ -0,0 +1,66 @@ | |||||
| 1 | + | #!/usr/bin/env bash | |||
| 2 | + | # Builds an AUR package with makepkg, then pushes its PKGBUILD and .SRCINFO. | |||
| 3 | + | # makepkg downloads every source and checks its sha256sum, so a PKGBUILD that | |||
| 4 | + | # does not build, or does not match its release assets, is never published. | |||
| 5 | + | # | |||
| 6 | + | # Usage: publish.sh <pkgname> <PKGBUILD> | |||
| 7 | + | # Env: AUR_SSH_PRIVATE_KEY, AUR_USERNAME, AUR_EMAIL | |||
| 8 | + | # DRY_RUN=1 builds and prints the files without pushing. | |||
| 9 | + | # Runs as a non-root user that may run pacman through sudo (makepkg -s). | |||
| 10 | + | set -euo pipefail | |||
| 11 | + | CARCH=$(uname -m) | |||
| 12 | + | ||||
| 13 | + | pkgname=$1 | |||
| 14 | + | work=$(mktemp -d) | |||
| 15 | + | trap 'rm -rf "$work"' EXIT | |||
| 16 | + | cp "$2" "$work/PKGBUILD" | |||
| 17 | + | cd "$work" | |||
| 18 | + | ||||
| 19 | + | makepkg --syncdeps --force --noconfirm | |||
| 20 | + | # A -git pkgver() rewrites pkgver in PKGBUILD; .SRCINFO must match it. | |||
| 21 | + | makepkg --printsrcinfo >.SRCINFO | |||
| 22 | + | pkgver=$(sed -n 's/^\tpkgver = //p' .SRCINFO) | |||
| 23 | + | ||||
| 24 | + | # makepkg only fetches the sources of this machine's arch: check the others too. | |||
| 25 | + | while read -r arch; do | |||
| 26 | + | if [[ $arch == "$CARCH" ]] || ! grep -q "^source_$arch=" PKGBUILD; then | |||
| 27 | + | continue | |||
| 28 | + | fi | |||
| 29 | + | { cat /etc/makepkg.conf; echo "CARCH=$arch"; } >"makepkg-$arch.conf" | |||
| 30 | + | makepkg --config "makepkg-$arch.conf" --verifysource --noconfirm | |||
| 31 | + | done < <(sed -n 's/^\tarch = //p' .SRCINFO) | |||
| 32 | + | ||||
| 33 | + | # pacman versions use _ where the tag has - (0.2.0-rc1 -> 0.2.0_rc1). | |||
| 34 | + | got=$("pkg/$pkgname/usr/bin/nefercap" version) | |||
| 35 | + | if [[ $got != "$pkgver" && $got != "${pkgver//_/-}" ]]; then | |||
| 36 | + | echo "packaged nefercap reports version $got, want $pkgver" >&2 | |||
| 37 | + | exit 1 | |||
| 38 | + | fi | |||
| 39 | + | ||||
| 40 | + | if [[ ${DRY_RUN:-} == 1 ]]; then | |||
| 41 | + | cat PKGBUILD .SRCINFO | |||
| 42 | + | exit 0 | |||
| 43 | + | fi | |||
| 44 | + | ||||
| 45 | + | ssh_dir=$HOME/.ssh | |||
| 46 | + | key=$ssh_dir/aur | |||
| 47 | + | install -d -m 700 "$ssh_dir" | |||
| 48 | + | (umask 077 && printf '%s\n' "$AUR_SSH_PRIVATE_KEY" >"$key") | |||
| 49 | + | # Pinned host key. Fingerprint SHA256:RFzBCUItH9LZS0cKB5UE6ceAYhBD5C8GeOBip8Z11+4 | |||
| 50 | + | # is published on https://aur.archlinux.org. | |||
| 51 | + | echo 'aur.archlinux.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEuBKrPzbawxA/k2g6NcyV5jmqwJ2s+zpgZGZ7tpLIcN' >"$ssh_dir/known_hosts" | |||
| 52 | + | export GIT_SSH_COMMAND="ssh -i $key -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes -o UserKnownHostsFile=$ssh_dir/known_hosts" | |||
| 53 | + | ||||
| 54 | + | git clone "ssh://aur@aur.archlinux.org/$pkgname.git" aur | |||
| 55 | + | cp PKGBUILD .SRCINFO aur/ | |||
| 56 | + | cd aur | |||
| 57 | + | git add PKGBUILD .SRCINFO | |||
| 58 | + | if git diff --cached --quiet; then | |||
| 59 | + | echo "$pkgname $pkgver is already on the AUR" | |||
| 60 | + | exit 0 | |||
| 61 | + | fi | |||
| 62 | + | # Commits are signed with the AUR SSH key. | |||
| 63 | + | git -c user.name="$AUR_USERNAME" -c user.email="$AUR_EMAIL" \ | |||
| 64 | + | -c gpg.format=ssh -c user.signingkey="$key" \ | |||
| 65 | + | commit -S -m "Update to $pkgver" | |||
| 66 | + | git push origin HEAD:master | |||