proc-cli 2.2.0

Semantic CLI tool for process management. Target by port, PID, name or path.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

env:
  CARGO_TERM_COLOR: always
  RUST_BACKTRACE: 1
  FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

jobs:
  test:
    name: Test
    timeout-minutes: 30
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]

    steps:
      - uses: actions/checkout@v4

      - name: Install Rust toolchain
        uses: dtolnay/rust-toolchain@stable

      - name: Cache cargo registry
        uses: actions/cache@v4
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            target
          key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
          restore-keys: |
            ${{ runner.os }}-cargo-

      - name: Run tests
        run: cargo test --verbose

      - name: Run clippy
        run: cargo clippy --all-targets -- -D warnings

  format:
    name: Format
    timeout-minutes: 5
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install Rust toolchain
        uses: dtolnay/rust-toolchain@stable
        with:
          components: rustfmt

      - name: Check formatting
        run: cargo fmt -- --check

      # The website's changelog is written from CHANGELOG.md, never by hand
      - name: Check the website changelog matches CHANGELOG.md
        run: |
          python3 -m unittest scripts/test_changelog_site.py
          python3 scripts/changelog_site.py --check

  build:
    name: Build
    timeout-minutes: 10
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        include:
          # Same Linux targets as release.yml, so every push builds what a release ships
          - os: ubuntu-latest
            target: x86_64-unknown-linux-musl
            artifact: proc-linux-x86_64
          - os: ubuntu-latest
            target: aarch64-unknown-linux-musl
            artifact: proc-linux-aarch64
          - os: macos-latest
            target: x86_64-apple-darwin
            artifact: proc-darwin-x86_64
          - os: macos-latest
            target: aarch64-apple-darwin
            artifact: proc-darwin-aarch64
          - os: windows-latest
            target: x86_64-pc-windows-msvc
            artifact: proc-windows-x86_64.exe
          # FreeBSD and NetBSD build in their own VMs (the bsd job below)

    steps:
      - uses: actions/checkout@v4

      - name: Install Rust toolchain
        uses: dtolnay/rust-toolchain@stable
        with:
          targets: ${{ matrix.target }}

      - name: Install cross-compilation tools (Linux ARM64)
        if: matrix.target == 'aarch64-unknown-linux-musl'
        run: |
          sudo apt-get update
          sudo apt-get install -y gcc-aarch64-linux-gnu
          echo '[target.aarch64-unknown-linux-musl]' >> ~/.cargo/config.toml
          echo 'linker = "aarch64-linux-gnu-gcc"' >> ~/.cargo/config.toml

      - name: Build release
        run: cargo build --release --target ${{ matrix.target }}

      # Same check as release.yml: the runners have the Visual C++ runtime, so
      # only the import table shows that proc.exe would need it
      - name: Check proc.exe needs no Visual C++ runtime
        if: runner.os == 'Windows'
        shell: pwsh
        run: |
          $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
          $dumpbin = & $vswhere -latest -products * -find 'VC\Tools\MSVC\*\bin\Hostx64\x64\dumpbin.exe' | Select-Object -First 1
          $deps = & $dumpbin /nologo /dependents "target/${{ matrix.target }}/release/proc.exe"
          $deps
          if ($deps -match 'VCRUNTIME|MSVCP|api-ms-win-crt') {
            echo "::error::proc.exe imports the Visual C++ runtime; it must link it statically"
            exit 1
          }

      - name: Rename binary (Unix)
        if: runner.os != 'Windows'
        run: cp target/${{ matrix.target }}/release/proc ${{ matrix.artifact }}

      - name: Rename binary (Windows)
        if: runner.os == 'Windows'
        shell: pwsh
        run: Copy-Item "target/${{ matrix.target }}/release/proc.exe" -Destination "${{ matrix.artifact }}"

      - name: Upload artifact
        uses: actions/upload-artifact@v4
        with:
          name: ${{ matrix.artifact }}
          path: ${{ matrix.artifact }}
          if-no-files-found: error

  # GitHub has no BSD runners: a FreeBSD or NetBSD VM that vmactions boots on
  # the Linux runner runs the whole suite, then builds the release binary
  # natively (scripts/bsd-build.sh: cross's images lack libraries proc links)
  # and runs it there. Nothing but Rust is installed: proc reads ports with
  # sockstat and open files with fstat, both in the base system. The target
  # directory stays outside the workspace, which is copied back to the runner
  # for the binary
  bsd:
    name: Test (${{ matrix.os }})
    timeout-minutes: 60
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        os: [freebsd, netbsd]

    steps:
      - uses: actions/checkout@v4

      - name: Test and build on FreeBSD
        if: matrix.os == 'freebsd'
        uses: vmactions/freebsd-vm@c46abacb49f09938ca4e1702d15d836285d694cc # v1.5.9
        with:
          release: "15.1"
          cpu: 4
          mem: 8192
          prepare: pkg install -y rust
          run: |
            cd "$GITHUB_WORKSPACE"
            export CARGO_TARGET_DIR="$HOME/proc-target"
            rustc --version
            cargo test -- --test-threads=4
            sh scripts/bsd-build.sh proc-freebsd-x86_64

      - name: Test and build on NetBSD
        if: matrix.os == 'netbsd'
        uses: vmactions/netbsd-vm@c8a0d7ddb84619a7a8cc4e652efa7950b3fd9f92 # v1.5.2
        with:
          release: "11.0"
          cpu: 4
          mem: 8192
          prepare: /usr/sbin/pkg_add rust
          run: |
            cd "$GITHUB_WORKSPACE"
            export CARGO_TARGET_DIR="$HOME/proc-target"
            rustc --version
            cargo test -- --test-threads=4
            sh scripts/bsd-build.sh proc-netbsd-x86_64

      - name: Upload the binary
        uses: actions/upload-artifact@v4
        with:
          name: proc-${{ matrix.os }}-x86_64
          path: proc-${{ matrix.os }}-x86_64
          if-no-files-found: error

  # The .deb and .rpm files a release attaches, built the way release.yml's
  # package-linux job builds them, from this push's static Linux binaries
  packages:
    name: Package .deb and .rpm
    timeout-minutes: 15
    needs: build
    runs-on: ubuntu-latest
    env:
      # The same pin as release.yml; change both together
      NFPM_VERSION: 2.48.0
      NFPM_SHA256: 6d7f9a28b2f397934403950fcbba83f598b08d02edbe79a0815b3b4897341750

    steps:
      - uses: actions/checkout@v4

      - name: Download the Linux binaries
        uses: actions/download-artifact@v4
        with:
          pattern: proc-linux-*
          merge-multiple: true
          path: linux

      - name: Install nfpm
        run: |
          curl -fsSL -o nfpm.tar.gz "https://github.com/goreleaser/nfpm/releases/download/v${NFPM_VERSION}/nfpm_${NFPM_VERSION}_Linux_x86_64.tar.gz"
          echo "${NFPM_SHA256}  nfpm.tar.gz" | sha256sum -c -
          mkdir -p "$HOME/.local/bin"
          tar -xzf nfpm.tar.gz -C "$HOME/.local/bin" nfpm
          echo "$HOME/.local/bin" >> "$GITHUB_PATH"

      - name: Build the packages
        run: |
          chmod +x linux/proc-linux-x86_64 linux/proc-linux-aarch64
          pkg/nfpm/generate-docs.sh linux/proc-linux-x86_64 docs-out
          version=$(sed -n 's/^version = "\(.*\)"$/\1/p' Cargo.toml | head -n1)
          pkg/nfpm/build.sh "$version" amd64 linux/proc-linux-x86_64 docs-out packages
          pkg/nfpm/build.sh "$version" arm64 linux/proc-linux-aarch64 docs-out packages
          ls -la packages
          test "$(find packages -type f | wc -l)" -eq 4

      - name: Upload the packages
        uses: actions/upload-artifact@v4
        with:
          name: linux-packages
          path: packages/*
          if-no-files-found: error

  # Each package installs with apt or dnf, runs, puts the man page and
  # completions where its distro looks, and removes cleanly; arm64 on an
  # arm64 runner, so nothing is emulated
  packages-install:
    name: Install the packages (${{ matrix.arch }})
    timeout-minutes: 20
    needs: packages
    runs-on: ${{ matrix.runner }}
    strategy:
      fail-fast: false
      matrix:
        include:
          - arch: amd64
            rpm_arch: x86_64
            runner: ubuntu-latest
          - arch: arm64
            rpm_arch: aarch64
            runner: ubuntu-24.04-arm

    steps:
      - name: Download the packages
        uses: actions/download-artifact@v4
        with:
          name: linux-packages
          path: packages

      - name: Install, run and remove in Debian and Ubuntu
        run: |
          for img in debian:bookworm debian:trixie ubuntu:24.04; do
            echo "== $img"
            # Ubuntu's image tells dpkg to skip /usr/share/man and docs
            # (a normal install doesn't), so the man page check removes that
            docker run --rm -v "$PWD/packages":/p:ro "$img" sh -ec '
              rm -f /etc/dpkg/dpkg.cfg.d/excludes
              apt-get update -qq
              apt-get install -y -qq /p/proc-cli_*_${{ matrix.arch }}.deb >/dev/null
              proc --version
              proc list --json >/dev/null
              test -f /usr/share/man/man1/proc.1.gz
              test -f /usr/share/bash-completion/completions/proc
              test -f /usr/share/zsh/vendor-completions/_proc
              test -f /usr/share/fish/vendor_completions.d/proc.fish
              apt-get remove -y -qq proc-cli >/dev/null
              ! test -e /usr/bin/proc'
          done

      - name: Install, run and remove in Fedora
        run: |
          docker run --rm -v "$PWD/packages":/p:ro fedora:latest sh -ec '
            dnf install -y -q /p/proc-cli-*.${{ matrix.rpm_arch }}.rpm
            proc --version
            proc list --json >/dev/null
            test -f /usr/share/man/man1/proc.1.gz
            test -f /usr/share/bash-completion/completions/proc
            test -f /usr/share/zsh/site-functions/_proc
            test -f /usr/share/fish/vendor_completions.d/proc.fish
            dnf remove -y -q proc-cli
            ! test -e /usr/bin/proc'

  # The flake builds proc from Cargo.lock and runs the whole test suite in
  # Nix's sandbox (nix flake check); nix build then installs it with its man
  # page and completions, and the binary is run
  nix:
    name: Nix (${{ matrix.os }})
    timeout-minutes: 45
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-latest]

    steps:
      - uses: actions/checkout@v4

      # Nix leaves macOS builds unsandboxed by default, so tests the sandbox
      # stops passed here and failed for nixpkgs reviewers (sandbox = relaxed,
      # as nixpkgs-review-gha builds on macOS); Linux always sandboxes
      - name: Install Nix
        uses: cachix/install-nix-action@75c75c31ebece974e48ebc36b4ad125bd97bd97b # v31.11.3
        with:
          extra_nix_config: ${{ runner.os == 'macOS' && 'sandbox = relaxed' || '' }}

      - name: Check the flake (builds proc and runs its tests in the sandbox)
        run: nix flake check -L

      # The package as nixpkgs carries it (pkg/nixpkgs/package.nix), which
      # builds the release it names, with that release's tests
      - name: Build the nixpkgs package
        run: nix build -L --impure -f pkg/nixpkgs/build.nix -o result-nixpkgs && ./result-nixpkgs/bin/proc --version

      - name: Build the package
        run: nix build -L

      - name: Run the built binary and look for what it installs
        shell: bash
        run: |
          ./result/bin/proc --version
          test "$(./result/bin/proc --version | head -n1)" = "proc $(sed -n 's/^version = "\(.*\)"$/\1/p' Cargo.toml | head -n1)"
          test -f result/share/man/man1/proc.1.gz
          test -f result/share/bash-completion/completions/proc.bash
          test -f result/share/zsh/site-functions/_proc
          test -f result/share/fish/vendor_completions.d/proc.fish

  security-audit:
    name: Security Audit
    timeout-minutes: 10
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install Rust toolchain
        uses: dtolnay/rust-toolchain@stable

      - name: Install cargo-audit
        run: cargo install cargo-audit

      - name: Run security audit
        run: cargo audit

  coverage:
    name: Coverage (${{ matrix.os }})
    timeout-minutes: 45
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]

    steps:
      - uses: actions/checkout@v4

      - name: Install Rust toolchain
        uses: dtolnay/rust-toolchain@stable
        with:
          components: llvm-tools-preview

      - name: Install cargo-llvm-cov
        uses: taiki-e/install-action@v2
        with:
          tool: cargo-llvm-cov@0.9.1

      - name: Cache cargo registry
        uses: actions/cache@v4
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            target
          key: ${{ runner.os }}-coverage-${{ hashFiles('**/Cargo.lock') }}
          restore-keys: |
            ${{ runner.os }}-coverage-

      # Plain `cargo test` under cargo-llvm-cov's environment, not `cargo llvm-cov`
      # itself: that runs `cargo test --tests`, which doesn't build the listener
      # example the flag matrix runs
      - name: Measure line coverage
        shell: bash
        run: |
          # eval, not `source <(...)`: macOS runs bash 3.2, where that reads nothing
          eval "$(cargo llvm-cov show-env --sh)"
          cargo llvm-cov clean --workspace
          cargo test -- --test-threads=4
          cargo llvm-cov report --lcov --output-path lcov-${{ matrix.os }}.info

      - name: Upload lcov
        uses: actions/upload-artifact@v4
        with:
          name: lcov-${{ matrix.os }}
          path: lcov-${{ matrix.os }}.info
          if-no-files-found: error

  coverage-gate:
    name: Coverage gate
    timeout-minutes: 5
    runs-on: ubuntu-latest
    needs: coverage
    if: ${{ !cancelled() }}
    steps:
      - uses: actions/checkout@v4

      - name: Download lcov files
        uses: actions/download-artifact@v4
        with:
          pattern: lcov-*
          path: lcov
          merge-multiple: true

      - name: Self-test the gate
        run: python3 -m unittest scripts/test_coverage_gate.py

      # --require: all three OS files must be present, so a failed OS job fails
      # the gate instead of passing as a partial merge
      - name: Enforce coverage
        run: >
          python3 scripts/coverage_gate.py lcov/*.info
          --exclusions coverage-exclusions.txt --enforce
          --require ubuntu-latest --require macos-latest --require windows-latest