Skip to content

Commit 4f89b40

Browse files
committed
0 parents  commit 4f89b40

File tree

6 files changed

+452
-0
lines changed

6 files changed

+452
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules/

action.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: 'Meson Format Action'
2+
author: Totto16
3+
description: 'Check if meson files are formatted correctly'
4+
inputs:
5+
format-file:
6+
description: 'Path of format file'
7+
required: false
8+
default: ''
9+
only-git-files:
10+
description: 'Only check files, that are check into git'
11+
required: false
12+
default: 'true'
13+
runs:
14+
using: 'composite'
15+
steps:
16+
- name: Install Node.Js
17+
uses: actions/setup-node@v4
18+
id: setup-js
19+
with:
20+
node-version: 22
21+
22+
- name: Install Node Dependencies
23+
id: install-dependencies
24+
shell: bash
25+
run: |
26+
cd "${{github.action_path}}"
27+
npm ci --production
28+
29+
# NOTE: meson has no dependencies, so --break-system-packages doesn't really break anything!
30+
- name: Setup meson
31+
id: install-meson
32+
shell: bash
33+
run: |
34+
pip install meson --break-system-packages
35+
36+
- name: Run checker
37+
shell: bash
38+
id: meson-format-checker
39+
env:
40+
INPUT_FORMAT-FILE: ${{ inputs.format-file }}
41+
INPUT_ONLY-GIT-FILES: ${{ inputs.only-git-files }}
42+
run: |
43+
node "${{github.action_path}}/index.js"

index.js

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
const core = require('@actions/core')
2+
const exec = require('@actions/exec')
3+
const io = require('@actions/io')
4+
5+
/**
6+
*
7+
* @param {string} executable
8+
* @param {string[]} args
9+
* @async
10+
* @returns {Promise<string[]>}
11+
*/
12+
async function execAndGetStdout(executable, args) {
13+
/** @type {string} */
14+
let output = ''
15+
16+
/** @type {exec.ExecOptions} */
17+
const options = {
18+
failOnStdErr: true,
19+
ignoreReturnCode: false,
20+
listeners: {
21+
stdout: (data) => {
22+
output += data.toString()
23+
},
24+
},
25+
}
26+
27+
await io.which(executable, true)
28+
29+
/** @type {number} */
30+
const exitCode = await exec.exec(executable, args, options)
31+
32+
if (exitCode != 0) {
33+
throw new Error(`${executable} exited with exit code ${exitCode}`)
34+
}
35+
36+
/** @type {string[]} */
37+
const result = output === '' ? [] : output.split('\n')
38+
39+
return result
40+
}
41+
42+
/**
43+
* @param {boolean} onlyGitFiles
44+
* @async
45+
* @returns {Promise<string[]>}
46+
*/
47+
async function getMesonFiles(onlyGitFiles) {
48+
/** @type {string[]} */
49+
let files
50+
51+
/** @type {string[]} */
52+
const mesonFiles = ['meson.build', 'meson.options', 'meson_options.txt']
53+
54+
if (onlyGitFiles) {
55+
/** @type {string[]} */
56+
const gitFiles = mesonFiles.flatMap((file) => ['--exclude', file])
57+
58+
files = await execAndGetStdout('git', [
59+
'ls-files',
60+
...gitFiles,
61+
'--ignored',
62+
'-c',
63+
])
64+
} else {
65+
/** @type {string[][]} */
66+
const findFiles = mesonFiles.map((file) => ['-name', file])
67+
68+
/** @type {string[]} */
69+
const finalFindFiles = findFiles.reduce((acc, elem, index) => {
70+
if (index != 0) {
71+
acc.push('-o')
72+
}
73+
74+
acc.push(...elem)
75+
76+
return acc
77+
}, [])
78+
79+
files = await execAndGetStdout('find', [
80+
'.',
81+
'(',
82+
...finalFindFiles,
83+
')',
84+
])
85+
}
86+
87+
return files
88+
}
89+
90+
/**
91+
*
92+
* @param {string} file
93+
* @param {string} formatFile
94+
* @async
95+
* @returns {Promise<boolean>}
96+
*/
97+
async function checkFile(file, formatFile) {
98+
/** @type {exec.ExecOptions} */
99+
const options = {
100+
ignoreReturnCode: true,
101+
}
102+
103+
/** @type {string[]} */
104+
const additionalArgs = formatFile === '' ? [] : ['-c', formatFile]
105+
106+
/** @type {number} */
107+
const exitCode = await exec.exec(
108+
'meson',
109+
['format', '--check-only', ...additionalArgs, file],
110+
options
111+
)
112+
113+
return exitCode === 0
114+
}
115+
116+
/**
117+
*
118+
* @param {string[]} items
119+
* @param {boolean} ordered
120+
* @returns {string}
121+
*/
122+
function getMarkdownListOf(items, ordered) {
123+
/** @type {string[]} */
124+
const itemsContent = items.map((item) => `<li>${item}</li>`)
125+
126+
/** @type {string} */
127+
const listType = ordered ? 'ol' : 'ul'
128+
129+
return `<${listType}>${itemsContent}</${listType}>`
130+
}
131+
132+
/**
133+
* @async
134+
* @returns {Promise<void>}
135+
*/
136+
async function main() {
137+
try {
138+
/** @type {string} */
139+
const os = core.platform.platform
140+
141+
if (os != 'linux') {
142+
throw new Error(
143+
`Action atm only supported on linux: but are on: ${os}`
144+
)
145+
}
146+
147+
/** @type {string} */
148+
const formatFile = core.getInput('format-file', { required: false })
149+
150+
/** @type {boolean} */
151+
const onlyGitFiles = core.getBooleanInput('only-git-files', {
152+
required: false,
153+
})
154+
155+
/** @type {string[]} */
156+
const files = await getMesonFiles(onlyGitFiles)
157+
158+
await io.which('meson', true)
159+
160+
/** @type {string[]} */
161+
const notFormattedFiles = []
162+
163+
core.startGroup('Check all files')
164+
165+
//TODO: maybe parallelize this
166+
for (const file of files) {
167+
core.info(`Checking file: '${file}'`)
168+
169+
/** @type {boolean} */
170+
const result = await checkFile(file, formatFile)
171+
172+
core.info(
173+
result
174+
? 'File is formatted correctly'
175+
: 'File has formatting errors'
176+
)
177+
core.info('')
178+
179+
if (!result) {
180+
notFormattedFiles.push(file)
181+
182+
/** @type {core.AnnotationProperties} */
183+
const properties = {
184+
file,
185+
title: 'File not formatted correctly',
186+
}
187+
188+
core.error('File not formatted correctly', properties)
189+
}
190+
}
191+
192+
core.endGroup()
193+
194+
if (notFormattedFiles.length === 0) {
195+
core.summary.clear()
196+
197+
core.summary.addHeading('Result', 1)
198+
core.summary.addRaw(
199+
':white_check_mark: All files are correctly formatted',
200+
true
201+
)
202+
203+
core.summary.write({ overwrite: true })
204+
return
205+
}
206+
207+
core.summary.clear()
208+
209+
core.summary.addHeading('Result', 1)
210+
core.summary.addRaw(':x: Some files are not formatted correctly', true)
211+
core.summary.addBreak()
212+
213+
/** @type {string} */
214+
const fileList = getMarkdownListOf(notFormattedFiles, false)
215+
216+
core.summary.addDetails('Affected Files', fileList)
217+
core.summary.addSeparator()
218+
219+
core.summary.addRaw(
220+
'To format the files run the following command',
221+
true
222+
)
223+
core.summary.addBreak()
224+
225+
/** @type {string} */
226+
const additionalArgs = formatFile === '' ? '' : `-c "${formatFile}"`
227+
228+
/** @type {string} */
229+
const finalFileList = notFormattedFiles
230+
.map((file) => `"${file}"`)
231+
.join(' ')
232+
233+
core.summary.addCodeBlock(
234+
`meson format ${additionalArgs} -i ${finalFileList}`,
235+
'bash'
236+
)
237+
238+
core.summary.write({ overwrite: true })
239+
240+
throw new Error('Some files are not formatted correctly')
241+
} catch (error) {
242+
if (error instanceof Error) {
243+
core.setFailed(error)
244+
} else {
245+
core.setFailed(`Invalid error thrown: ${error}`)
246+
}
247+
}
248+
}
249+
250+
main()

jsconfig.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"compilerOptions": {
3+
"strict": true,
4+
"noImplicitAny": true,
5+
"strictFunctionTypes": true,
6+
"strictPropertyInitialization": true,
7+
"strictBindCallApply": true,
8+
"strictNullChecks": true,
9+
"noImplicitThis": true,
10+
"noImplicitReturns": true,
11+
"alwaysStrict": true,
12+
"esModuleInterop": true,
13+
"checkJs": true,
14+
"allowJs": true,
15+
"declaration": true,
16+
"noEmit": true,
17+
"exactOptionalPropertyTypes": true
18+
},
19+
"files": ["index.js"]
20+
}

0 commit comments

Comments
 (0)