summaryrefslogtreecommitdiffhomepage
path: root/.github/workflows/test.yml
blob: 7142c86b90d4d6eb56e3f8fe85586d2166d04a2c (plain)
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
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
# This is our main "CI tests" workflow. It runs everything that should run on
# both PRs and merged commits, and for the latter reports failures to slack.
name: CI

env:
  # Our fuzz job, powered by OSS-Fuzz, fails periodically because we upgrade to
  # new Go versions very eagerly. OSS-Fuzz is a little more conservative, and
  # ends up being unable to compile our code.
  #
  # When this happens, we want to disable the fuzz target until OSS-Fuzz catches
  # up. However, we also don't want to forget to turn it back on when OSS-Fuzz
  # can once again build our code.
  #
  # This variable toggles the fuzz job between two modes:
  #  - false: we expect fuzzing to be happy, and should report failure if it's not.
  #  - true: we expect fuzzing is broken, and should report failure if it start working.
  TS_FUZZ_CURRENTLY_BROKEN: false

on:
  push:
    branches:
      - "main"
      - "release-branch/*"
  pull_request:
    # all PRs on all branches
  merge_group:
    branches:
      - "main"

concurrency:
  # For PRs, later CI runs preempt previous ones. e.g. a force push on a PR
  # cancels running CI jobs and starts all new ones.
  #
  # For non-PR pushes, concurrency.group needs to be unique for every distinct
  # CI run we want to have happen. Use run_id, which in practice means all
  # non-PR CI runs will be allowed to run without preempting each other.
  group: ${{ github.workflow }}-$${{ github.pull_request.number || github.run_id }}
  cancel-in-progress: true

jobs:
  race-root-integration:
    runs-on: ubuntu-22.04
    strategy:
      fail-fast: false # don't abort the entire matrix if one element fails
      matrix:
        include:
          - shard: '1/4'
          - shard: '2/4'
          - shard: '3/4'
          - shard: '4/4'
    steps:
    - name: checkout
      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
    - name: build test wrapper
      run: ./tool/go build -o /tmp/testwrapper ./cmd/testwrapper
    - name: integration tests as root
      run: PATH=$PWD/tool:$PATH /tmp/testwrapper -exec "sudo -E" -race ./tstest/integration/
      env:
        TS_TEST_SHARD: ${{ matrix.shard }}

  test:
    strategy:
      fail-fast: false # don't abort the entire matrix if one element fails
      matrix:
        include:
          - goarch: amd64
          - goarch: amd64
            buildflags: "-race"
            shard: '1/3'
          - goarch: amd64
            buildflags: "-race"
            shard: '2/3'
          - goarch: amd64
            buildflags: "-race"
            shard: '3/3'
          - goarch: "386" # thanks yaml
    runs-on: ubuntu-22.04
    steps:
    - name: checkout
      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
    - name: Restore Cache
      uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
      with:
        # Note: unlike the other setups, this is only grabbing the mod download
        # cache, rather than the whole mod directory, as the download cache
        # contains zips that can be unpacked in parallel faster than they can be
        # fetched and extracted by tar
        path: |
          ~/.cache/go-build
          ~/go/pkg/mod/cache
          ~\AppData\Local\go-build
        # The -2- here should be incremented when the scheme of data to be
        # cached changes (e.g. path above changes).
        key: ${{ github.job }}-${{ runner.os }}-${{ matrix.goarch }}-${{ matrix.buildflags }}-go-2-${{ hashFiles('**/go.sum') }}-${{ github.run_id }}
        restore-keys: |
          ${{ github.job }}-${{ runner.os }}-${{ matrix.goarch }}-${{ matrix.buildflags }}-go-2-${{ hashFiles('**/go.sum') }}
          ${{ github.job }}-${{ runner.os }}-${{ matrix.goarch }}-${{ matrix.buildflags }}-go-2-
    - name: build all
      if: matrix.buildflags == '' # skip on race builder
      run: ./tool/go build ${{matrix.buildflags}} ./...
      env:
        GOARCH: ${{ matrix.goarch }}
    - name: build variant CLIs
      if: matrix.buildflags == '' # skip on race builder
      run: |
        export TS_USE_TOOLCHAIN=1
        ./build_dist.sh --extra-small ./cmd/tailscaled
        ./build_dist.sh --box ./cmd/tailscaled
        ./build_dist.sh --extra-small --box ./cmd/tailscaled
        rm -f tailscaled
      env:
        GOARCH: ${{ matrix.goarch }}
    - name: get qemu # for tstest/archtest
      if: matrix.goarch == 'amd64' && matrix.buildflags == ''
      run: |
        sudo apt-get -y update
        sudo apt-get -y install qemu-user
    - name: build test wrapper
      run: ./tool/go build -o /tmp/testwrapper ./cmd/testwrapper
    - name: test all
      run: NOBASHDEBUG=true PATH=$PWD/tool:$PATH /tmp/testwrapper ./... ${{matrix.buildflags}}
      env:
        GOARCH: ${{ matrix.goarch }}
        TS_TEST_SHARD: ${{ matrix.shard }}
    - name: bench all
      run: ./tool/go test ${{matrix.buildflags}} -bench=. -benchtime=1x -run=^$ $(for x in $(git grep -l "^func Benchmark" | xargs dirname | sort | uniq); do echo "./$x"; done)
      env:
        GOARCH: ${{ matrix.goarch }}
    - name: check that no tracked files changed
      run: git diff --no-ext-diff --name-only --exit-code || (echo "Build/test modified the files above."; exit 1)
    - name: check that no new files were added
      run: |
        # Note: The "error: pathspec..." you see below is normal!
        # In the success case in which there are no new untracked files,
        # git ls-files complains about the pathspec not matching anything.
        # That's OK. It's not worth the effort to suppress. Please ignore it.
        if git ls-files --others --exclude-standard --directory --no-empty-directory --error-unmatch -- ':/*'
        then
          echo "Build/test created untracked files in the repo (file names above)."
          exit 1
        fi

  windows:
    runs-on: windows-2022
    steps:
    - name: checkout
      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

    - name: Install Go
      uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0
      with:
        go-version-file: go.mod
        cache: false

    - name: Restore Cache
      uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
      with:
        # Note: unlike the other setups, this is only grabbing the mod download
        # cache, rather than the whole mod directory, as the download cache
        # contains zips that can be unpacked in parallel faster than they can be
        # fetched and extracted by tar
        path: |
          ~/.cache/go-build
          ~/go/pkg/mod/cache
          ~\AppData\Local\go-build
        # The -2- here should be incremented when the scheme of data to be
        # cached changes (e.g. path above changes).
        key: ${{ github.job }}-${{ runner.os }}-go-2-${{ hashFiles('**/go.sum') }}-${{ github.run_id }}
        restore-keys: |
          ${{ github.job }}-${{ runner.os }}-go-2-${{ hashFiles('**/go.sum') }}
          ${{ github.job }}-${{ runner.os }}-go-2-
    - name: test
      run: go run ./cmd/testwrapper ./...
    - name: bench all
      # Don't use -bench=. -benchtime=1x.
      # Somewhere in the layers (powershell?)
      # the equals signs cause great confusion.
      run: go test ./... -bench . -benchtime 1x -run "^$"

  privileged:
    runs-on: ubuntu-22.04
    container:
      image: golang:latest
      options: --privileged
    steps:
    - name: checkout
      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
    - name: chown
      run: chown -R $(id -u):$(id -g) $PWD
    - name: privileged tests
      run: ./tool/go test ./util/linuxfw ./derp/xdp

  vm:
    runs-on: ["self-hosted", "linux", "vm"]
    # VM tests run with some privileges, don't let them run on 3p PRs.
    if: github.repository == 'tailscale/tailscale'
    steps:
    - name: checkout
      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
    - name: Run VM tests
      run: ./tool/go test ./tstest/integration/vms -v -no-s3 -run-vm-tests -run=TestRunUbuntu2004
      env:
        HOME: "/var/lib/ghrunner/home"
        TMPDIR: "/tmp"
        XDG_CACHE_HOME: "/var/lib/ghrunner/cache"

  race-build:
    runs-on: ubuntu-22.04
    steps:
    - name: checkout
      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
    - name: build all
      run: ./tool/go install -race ./cmd/...
    - name: build tests
      run: ./tool/go test -race -exec=true ./...

  cross: # cross-compile checks, build only.
    strategy:
      fail-fast: false # don't abort the entire matrix if one element fails
      matrix:
        include:
          # Note: linux/amd64 is not in this matrix, because that goos/goarch is
          # tested more exhaustively in the 'test' job above.
          - goos: linux
            goarch: arm64
          - goos: linux
            goarch: "386" # thanks yaml
          - goos: linux
            goarch: loong64
          - goos: linux
            goarch: arm
            goarm: "5"
          - goos: linux
            goarch: arm
            goarm: "7"
          # macOS
          - goos: darwin
            goarch: amd64
          - goos: darwin
            goarch: arm64
          # Windows
          - goos: windows
            goarch: amd64
          - goos: windows
            goarch: arm64
          # BSDs
          - goos: freebsd
            goarch: amd64
          - goos: openbsd
            goarch: amd64

    runs-on: ubuntu-22.04
    steps:
    - name: checkout
      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
    - name: Restore Cache
      uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
      with:
        # Note: unlike the other setups, this is only grabbing the mod download
        # cache, rather than the whole mod directory, as the download cache
        # contains zips that can be unpacked in parallel faster than they can be
        # fetched and extracted by tar
        path: |
          ~/.cache/go-build
          ~/go/pkg/mod/cache
          ~\AppData\Local\go-build
        # The -2- here should be incremented when the scheme of data to be
        # cached changes (e.g. path above changes).
        key: ${{ github.job }}-${{ runner.os }}-${{ matrix.goos }}-${{ matrix.goarch }}-go-2-${{ hashFiles('**/go.sum') }}-${{ github.run_id }}
        restore-keys: |
          ${{ github.job }}-${{ runner.os }}-${{ matrix.goos }}-${{ matrix.goarch }}-go-2-${{ hashFiles('**/go.sum') }}
          ${{ github.job }}-${{ runner.os }}-${{ matrix.goos }}-${{ matrix.goarch }}-go-2-
    - name: build all
      run: ./tool/go build ./cmd/...
      env:
        GOOS: ${{ matrix.goos }}
        GOARCH: ${{ matrix.goarch }}
        GOARM: ${{ matrix.goarm }}
        CGO_ENABLED: "0"
    - name: build tests
      run: ./tool/go test -exec=true ./...
      env:
        GOOS: ${{ matrix.goos }}
        GOARCH: ${{ matrix.goarch }}
        CGO_ENABLED: "0"

  ios: # similar to cross above, but iOS can't build most of the repo. So, just
       #make it build a few smoke packages.
    runs-on: ubuntu-22.04
    steps:
    - name: checkout
      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
    - name: build some
      run: ./tool/go build ./ipn/... ./wgengine/ ./types/... ./control/controlclient
      env:
        GOOS: ios
        GOARCH: arm64

  crossmin: # cross-compile for platforms where we only check cmd/tailscale{,d}
    strategy:
      fail-fast: false # don't abort the entire matrix if one element fails
      matrix:
        include:
          # Plan9
          - goos: plan9
            goarch: amd64
          # AIX
          - goos: aix
            goarch: ppc64
          # Solaris
          - goos: solaris
            goarch: amd64
          # illumos
          - goos: illumos
            goarch: amd64

    runs-on: ubuntu-22.04
    steps:
    - name: checkout
      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
    - name: Restore Cache
      uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
      with:
        # Note: unlike the other setups, this is only grabbing the mod download
        # cache, rather than the whole mod directory, as the download cache
        # contains zips that can be unpacked in parallel faster than they can be
        # fetched and extracted by tar
        path: |
          ~/.cache/go-build
          ~/go/pkg/mod/cache
          ~\AppData\Local\go-build
        # The -2- here should be incremented when the scheme of data to be
        # cached changes (e.g. path above changes).
        key: ${{ github.job }}-${{ runner.os }}-${{ matrix.goos }}-${{ matrix.goarch }}-go-2-${{ hashFiles('**/go.sum') }}-${{ github.run_id }}
        restore-keys: |
          ${{ github.job }}-${{ runner.os }}-${{ matrix.goos }}-${{ matrix.goarch }}-go-2-${{ hashFiles('**/go.sum') }}
          ${{ github.job }}-${{ runner.os }}-${{ matrix.goos }}-${{ matrix.goarch }}-go-2-
    - name: build core
      run: ./tool/go build ./cmd/tailscale ./cmd/tailscaled
      env:
        GOOS: ${{ matrix.goos }}
        GOARCH: ${{ matrix.goarch }}
        GOARM: ${{ matrix.goarm }}
        CGO_ENABLED: "0"

  android:
    # similar to cross above, but android fails to build a few pieces of the
    # repo. We should fix those pieces, they're small, but as a stepping stone,
    # only test the subset of android that our past smoke test checked.
    runs-on: ubuntu-22.04
    steps:
    - name: checkout
      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
      # Super minimal Android build that doesn't even use CGO and doesn't build everything that's needed
      # and is only arm64. But it's a smoke build: it's not meant to catch everything. But it'll catch
      # some Android breakages early.
      # TODO(bradfitz): better; see https://github.com/tailscale/tailscale/issues/4482
    - name: build some
      run: ./tool/go install ./net/netns ./ipn/ipnlocal ./wgengine/magicsock/ ./wgengine/ ./wgengine/router/ ./wgengine/netstack ./util/dnsname/ ./ipn/ ./net/netmon ./wgengine/router/ ./tailcfg/ ./types/logger/ ./net/dns ./hostinfo ./version
      env:
        GOOS: android
        GOARCH: arm64

  wasm: # builds tsconnect, which is the only wasm build we support
    runs-on: ubuntu-22.04
    steps:
    - name: checkout
      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
    - name: Restore Cache
      uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
      with:
        # Note: unlike the other setups, this is only grabbing the mod download
        # cache, rather than the whole mod directory, as the download cache
        # contains zips that can be unpacked in parallel faster than they can be
        # fetched and extracted by tar
        path: |
          ~/.cache/go-build
          ~/go/pkg/mod/cache
          ~\AppData\Local\go-build
        # The -2- here should be incremented when the scheme of data to be
        # cached changes (e.g. path above changes).
        key: ${{ github.job }}-${{ runner.os }}-go-2-${{ hashFiles('**/go.sum') }}-${{ github.run_id }}
        restore-keys: |
          ${{ github.job }}-${{ runner.os }}-go-2-${{ hashFiles('**/go.sum') }}
          ${{ github.job }}-${{ runner.os }}-go-2-
    - name: build tsconnect client
      run: ./tool/go build ./cmd/tsconnect/wasm ./cmd/tailscale/cli
      env:
        GOOS: js
        GOARCH: wasm
    - name: build tsconnect server
      # Note, no GOOS/GOARCH in env on this build step, we're running a build
      # tool that handles the build itself.
      run: |
        ./tool/go run ./cmd/tsconnect --fast-compression build
        ./tool/go run ./cmd/tsconnect --fast-compression build-pkg

  tailscale_go: # Subset of tests that depend on our custom Go toolchain.
    runs-on: ubuntu-22.04
    steps:
    - name: checkout
      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
    - name: test tailscale_go
      run: ./tool/go test -tags=tailscale_go,ts_enable_sockstats ./net/sockstats/...


  fuzz:
    # This target periodically breaks (see TS_FUZZ_CURRENTLY_BROKEN at the top
    # of the file), so it's more complex than usual: the 'build fuzzers' step
    # might fail, and depending on the value of 'TS_FUZZ_CURRENTLY_BROKEN', that
    # might or might not be fine. The steps after the build figure out whether
    # the success/failure is expected, and appropriately pass/fail the job
    # overall accordingly.
    #
    # Practically, this means that all steps after 'build fuzzers' must have an
    # explicit 'if' condition, because the default condition for steps is
    # 'success()', meaning "only run this if no previous steps failed".
    if: github.event_name == 'pull_request'
    runs-on: ubuntu-22.04
    steps:
    - name: build fuzzers
      id: build
      uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
      # continue-on-error makes steps.build.conclusion be 'success' even if
      # steps.build.outcome is 'failure'. This means this step does not
      # contribute to the job's overall pass/fail evaluation.
      continue-on-error: true
      with:
       oss-fuzz-project-name: 'tailscale'
       dry-run: false
       language: go
    - name: report unexpectedly broken fuzz build
      if: steps.build.outcome == 'failure' && env.TS_FUZZ_CURRENTLY_BROKEN != 'true'
      run: |
        echo "fuzzer build failed, see above for why"
        echo "if the failure is due to OSS-Fuzz not being on the latest Go yet,"
        echo "set TS_FUZZ_CURRENTLY_BROKEN=true in .github/workflows/test.yml"
        echo "to temporarily disable fuzzing until OSS-Fuzz works again."
        exit 1
    - name: report unexpectedly working fuzz build
      if: steps.build.outcome == 'success' && env.TS_FUZZ_CURRENTLY_BROKEN == 'true'
      run: |
        echo "fuzzer build succeeded, but we expect it to be broken"
        echo "please set TS_FUZZ_CURRENTLY_BROKEN=false in .github/workflows/test.yml"
        echo "to reenable fuzz testing"
        exit 1
    - name: run fuzzers
      id: run
      # Run the fuzzers whenever they're able to build, even if we're going to
      # report a failure because TS_FUZZ_CURRENTLY_BROKEN is set to the wrong
      # value.
      if: steps.build.outcome == 'success'
      uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
      with:
        oss-fuzz-project-name: 'tailscale'
        fuzz-seconds: 300
        dry-run: false
        language: go
    - name: Set artifacts_path in env (workaround for actions/upload-artifact#176)
      if: steps.run.outcome != 'success' && steps.build.outcome == 'success'
      run: |
        echo "artifacts_path=$(realpath .)" >> $GITHUB_ENV
    - name: upload crash
      uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
      if: steps.run.outcome != 'success' && steps.build.outcome == 'success'
      with:
        name: artifacts
        path: ${{ env.artifacts_path }}/out/artifacts

  depaware:
    runs-on: ubuntu-22.04
    steps:
    - name: checkout
      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
    - name: check depaware
      run: |
        export PATH=$(./tool/go env GOROOT)/bin:$PATH
        find . -name 'depaware.txt' | xargs -n1 dirname | xargs ./tool/go run github.com/tailscale/depaware --check --internal

  go_generate:
    runs-on: ubuntu-22.04
    steps:
    - name: checkout
      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
    - name: check that 'go generate' is clean
      run: |
        pkgs=$(./tool/go list ./... | grep -Ev 'dnsfallback|k8s-operator|xdp')
        ./tool/go generate $pkgs
        echo
        echo
        git diff --name-only --exit-code || (echo "The files above need updating. Please run 'go generate'."; exit 1)

  go_mod_tidy:
    runs-on: ubuntu-22.04
    steps:
    - name: checkout
      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
    - name: check that 'go mod tidy' is clean
      run: |
        ./tool/go mod tidy
        echo
        echo
        git diff --name-only --exit-code || (echo "Please run 'go mod tidy'."; exit 1)

  licenses:
    runs-on: ubuntu-22.04
    steps:
    - name: checkout
      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
    - name: check licenses
      run: ./scripts/check_license_headers.sh .

  staticcheck:
    runs-on: ubuntu-22.04
    strategy:
      fail-fast: false # don't abort the entire matrix if one element fails
      matrix:
        goos: ["linux", "windows", "darwin"]
        goarch: ["amd64"]
        include:
          - goos: "windows"
            goarch: "386"
    steps:
    - name: checkout
      uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
    - name: install staticcheck
      run: GOBIN=~/.local/bin ./tool/go install honnef.co/go/tools/cmd/staticcheck
    - name: run staticcheck
      run: |
        export GOROOT=$(./tool/go env GOROOT)
        export PATH=$GOROOT/bin:$PATH
        staticcheck -- $(./tool/go list ./... | grep -v tempfork)
      env:
        GOOS: ${{ matrix.goos }}
        GOARCH: ${{ matrix.goarch }}

  notify_slack:
    if: always()
    # Any of these jobs failing causes a slack notification.
    needs: 
      - android
      - test
      - windows
      - vm
      - cross
      - ios
      - wasm
      - tailscale_go
      - fuzz
      - depaware
      - go_generate
      - go_mod_tidy
      - licenses
      - staticcheck
    runs-on: ubuntu-22.04
    steps:
    - name: notify  
      # Only notify slack for merged commits, not PR failures.
      #
      # It may be tempting to move this condition into the job's 'if' block, but
      # don't: Github only collapses the test list into "everything is OK" if
      # all jobs succeeded. A skipped job results in the list staying expanded.
      # By having the job always run, but skipping its only step as needed, we
      # let the CI output collapse nicely in PRs.
      if: failure() && github.event_name == 'push'
      uses: slackapi/slack-github-action@485a9d42d3a73031f12ec201c457e2162c45d02d # v2.0.0
      with:
        webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
        webhook-type: incoming-webhook
        payload: |
          {
            "attachments": [{
              "title": "Failure: ${{ github.workflow }}",
              "title_link": "https://github.com/${{ github.repository }}/commit/${{ github.sha }}/checks",         
              "text": "${{ github.repository }}@${{ github.ref_name }}: <https://github.com/${{ github.repository }}/commit/${{ github.sha }}|${{ github.sha }}>",
              "fields": [{ "value": ${{ toJson(github.event.head_commit.message) }}, "short": false }],
              "footer": "${{ github.event.head_commit.committer.name }} at ${{ github.event.head_commit.timestamp }}",
              "color": "danger"
            }]
          }

  check_mergeability:
    if: always()
    runs-on: ubuntu-22.04
    needs:
      - android
      - test
      - windows
      - vm
      - cross
      - ios
      - wasm
      - tailscale_go
      - fuzz
      - depaware
      - go_generate
      - go_mod_tidy
      - licenses
      - staticcheck
    steps:
    - name: Decide if change is okay to merge
      if: github.event_name != 'push'
      uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2
      with:
        jobs: ${{ toJSON(needs) }}