-
-
Notifications
You must be signed in to change notification settings - Fork 0
200 lines (186 loc) · 7 KB
/
Copy pathelixir-ci-reusable.yml
File metadata and controls
200 lines (186 loc) · 7 KB
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
# SPDX-License-Identifier: MPL-2.0
# elixir-ci-reusable.yml — Reusable Elixir CI bundle (RSR).
#
# Replaces the per-repo `elixir-ci.yml` template that copy-drifted (and
# in several cases got corrupted) across the estate. Estate audit
# (2026-05-26) found 9 repos shipping their own copy, with 9 unique
# SHAs — 100% drift. One copy (`bofig`) was YAML-broken with literal
# `npermissions:` lines from a botched permissions injection.
#
# Recurring failure modes observed across the estate:
#
# * Elixir version pinned to 1.15 while mix.exs declared ~> 1.17,
# producing `(Mix) … but it has declared in its mix.exs file
# it supports only Elixir ~> 1.17` (tma-mark2 #41 lived this).
# * `mix compile --warnings-as-errors` applied to the whole
# build, so transitive-dep warnings (e.g. rustler's
# `:json.decode` reference needing Elixir 1.18) failed CI even
# when the project's own code was clean.
# * Inconsistent `permissions:` placement (some at top, some
# mid-file, some missing).
#
# This reusable:
# * Pins to the tma-mark2 #41-validated canonical shape.
# * Compiles deps WITHOUT --warnings-as-errors first, then app code
# with the strict flag — so deps warnings don't fail us but our
# own code still gets the hygiene gate.
# * Gates dialyzer behind an opt-in input (~5-minute cold-cache run
# on most repos).
# * Guards every job on `mix.exs` presence so consumers can add
# the wrapper unconditionally.
#
# Caller example:
#
# jobs:
# elixir-ci:
# uses: hyperpolymath/standards/.github/workflows/elixir-ci-reusable.yml@d135b05bfc647d0c0fbfedc7e80f37ea50f49236
#
# With dialyzer + customised versions:
#
# jobs:
# elixir-ci:
# uses: hyperpolymath/standards/.github/workflows/elixir-ci-reusable.yml@d135b05bfc647d0c0fbfedc7e80f37ea50f49236
# with:
# elixir-version: "1.18"
# enable_dialyzer: true
#
# Sub-directory project (mix.exs lives in a subfolder — applies to
# burble's server/, feedback-o-tron, verisimdb):
#
# jobs:
# elixir-ci:
# uses: hyperpolymath/standards/.github/workflows/elixir-ci-reusable.yml@d135b05bfc647d0c0fbfedc7e80f37ea50f49236
# with:
# working_directory: server
name: Elixir CI (reusable)
on:
workflow_call:
inputs:
runs-on:
description: Runner label for the Elixir CI job
type: string
required: false
default: ubuntu-latest
otp-version:
description: OTP version selector for erlef/setup-beam
type: string
required: false
default: "26"
elixir-version:
description: Elixir version selector for erlef/setup-beam
type: string
required: false
default: "1.17"
rebar3-version:
description: >-
rebar3 version for erlef/setup-beam. When set, setup-beam installs
rebar3 from GitHub releases instead of letting `mix local.rebar`
fetch it from builds.hex.pm — a workaround for the OTP TLS
`key_usage_mismatch` cert error on that host. Empty = unchanged.
type: string
required: false
default: ""
enable_dialyzer:
description: Run `mix dialyzer` (slow cold-cache; off by default)
type: boolean
required: false
default: false
enable_credo:
description: Run `mix credo --strict`
type: boolean
required: false
default: true
working_directory:
description: |
Directory containing `mix.exs` (relative to the repo root). All
mix invocations and cache lookups consult this directory.
Default `.` keeps single-app repos unchanged. Set to e.g.
`server` for a sub-app layout (burble, feedback-o-tron,
verisimdb at audit time).
type: string
required: false
default: "."
permissions:
contents: read
jobs:
# Guard on mix.exs so the wrapper is safe to add unconditionally. This must
# be a checked-out step, NOT a job-level `if: hashFiles(...)`: job `if:` is
# evaluated server-side before any checkout, so hashFiles() sees an empty
# workspace and always returns '' — which silently skipped the test job on
# EVERY repo, Elixir or not. The detect job checks out and exposes a real
# boolean output the test job gates on.
detect:
timeout-minutes: 5
name: Detect mix.exs
runs-on: ${{ inputs.runs-on }}
permissions:
contents: read
outputs:
has_mix: ${{ steps.detect.outputs.has_mix }}
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
repository: ${{ github.repository }}
ref: ${{ github.ref }}
- name: Detect mix.exs
id: detect
run: |
if [ -f "${{ inputs.working_directory }}/mix.exs" ]; then
echo "has_mix=true" >> "$GITHUB_OUTPUT"
else
echo "has_mix=false" >> "$GITHUB_OUTPUT"
fi
test:
timeout-minutes: 20
name: Compile + test
runs-on: ${{ inputs.runs-on }}
needs: detect
if: ${{ needs.detect.outputs.has_mix == 'true' }}
permissions:
contents: read
env:
MIX_ENV: test
defaults:
run:
working-directory: ${{ inputs.working_directory }}
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
repository: ${{ github.repository }}
ref: ${{ github.ref }}
- name: Set up BEAM (OTP + Elixir)
uses: erlef/setup-beam@54075bcc5e249e4758d363f27d099f55d843f124 # v1.24.1
with:
otp-version: ${{ inputs.otp-version }}
elixir-version: ${{ inputs.elixir-version }}
rebar3-version: ${{ inputs.rebar3-version }}
- name: Cache deps
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ${{ inputs.working_directory }}/deps
key: deps-${{ inputs.elixir-version }}-${{ hashFiles(format('{0}/mix.lock', inputs.working_directory)) }}
- name: Cache _build
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ${{ inputs.working_directory }}/_build
key: build-${{ inputs.elixir-version }}-${{ hashFiles(format('{0}/mix.lock', inputs.working_directory)) }}
- name: Install deps
run: mix deps.get
# Compile deps WITHOUT --warnings-as-errors so upstream warnings
# (rustler's :json.decode needing Elixir 1.18, deprecated
# `use Bitwise`, etc.) don't fail the build. Strict mode then
# applies only to the project's own modules in the next step.
- name: Compile dependencies
run: mix deps.compile
- name: Compile project (strict)
run: mix compile --warnings-as-errors
- name: Credo lint
if: ${{ inputs.enable_credo }}
run: mix credo --strict
- name: Dialyzer
if: ${{ inputs.enable_dialyzer }}
run: mix dialyzer
- name: Run tests
run: mix test --cover