Skip to content

Commit 0a4b151

Browse files
Vampireleonard84
andauthored
Write GitHub Actions workflow files in Kotlin instead of YAML (#1630)
For now a 1:1 translation from previous Yaml. --------- Co-authored-by: Leonard Brünings <lbruenings@gradle.com>
1 parent 2c4e38c commit 0a4b151

File tree

17 files changed

+1306
-230
lines changed

17 files changed

+1306
-230
lines changed

.editorconfig

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,10 @@ indent_size = 2
1414
# The file contains important whitespace at the end of the line in a multi-line string.
1515
# and editorconfig doesn't seem to respect multi-line strings.
1616
trim_trailing_whitespace = false
17+
18+
[*.{kt,kts}]
19+
indent_size = 4
20+
ij_kotlin_allow_trailing_comma = false
21+
ij_kotlin_allow_trailing_comma_on_call_site = false
22+
ktlint_code_style = intellij_idea
23+
ktlint_standard_function-signature = disabled

.github/actions/setup-jdks/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ runs:
2121
uses: actions/setup-java@v4
2222
with:
2323
# Temurin JDK 8 for macos on ARM is not available: https://github.com/adoptium/adoptium/issues/96
24-
distribution: ${{ runner.os == 'macOS' && 'zulu' || 'temurin' }}
24+
distribution: ${{ ((runner.os == 'macOS') && (runner.arch == 'ARM64')) && 'zulu' || 'temurin' }}
2525
java-version: 8
2626
- name: Prepare JDK8 env var
2727
shell: bash

.github/workflows/README.adoc

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
== The YAML workflow files vs. the `*.main.kts` files
2+
3+
The YAML workflow files are generated from the `*.main.kts` files.
4+
5+
These use the https://github.com/typesafegithub/github-workflows-kt[github-workflows-kt]
6+
Kotlin DSL library to conveniently and type-safely write GitHub Action workflow files.
7+
8+
As there is no official built-in support in GitHub Actions yet until
9+
https://github.com/orgs/community/discussions/15904 is considered, the YAML files
10+
need to be generated manually.
11+
12+
There is a safeguard check in all the generated files that this is not forgotten.
13+
Running a workflow where the according `*.main.kts` produces a different output will
14+
fail the execution. Additionally, the workflow that runs for pull requests checks
15+
the consistency of all the YAML files as not all are run for pull requests.
16+
17+
18+
19+
== Ways to generate the YAML workflow files
20+
21+
There are multiple ways to generate the YAML files and all of them are fine,
22+
but be aware of the last one of the caveats below if you are not using the Gradle method:
23+
24+
* If you are in a `sh` derivate like e.g. `bash` and Kotlin is installed and
25+
available in the `PATH`, you can just call the `*.main.kts` script like any
26+
other shell script:
27+
+
28+
[source,bash]
29+
----
30+
$ ./release.main.kts
31+
----
32+
33+
* If Kotlin is installed somewhere you can call it with the `*.main.kts` script
34+
as argument:
35+
+
36+
[source,bash]
37+
----
38+
$ path/to/kotlin release.main.kts
39+
----
40+
41+
* From the IDE you can create a run configuration that executes the `*.main.kts` script.
42+
43+
* There is a Gradle task `preprocessWorkflows` that generates all YAML files from the
44+
according `*.main.kts` files. Additionally, there is also one task per workflow to
45+
only generate that one:
46+
+
47+
[source,bash]
48+
----
49+
$ ./gradlew preprocessReleaseWorkflow
50+
$ ./gradlew preprocessWorkflows
51+
----
52+
53+
54+
55+
== Caveats
56+
57+
There are currently three known caveats with the approach we follow.
58+
59+
* https://youtrack.jetbrains.com/issue/KTIJ-16532
60+
+
61+
If you navigate to a file in the dependencies, only a decompiled file is opened,
62+
even though the source JAR would be available. Also the quick documentation is missing.
63+
+
64+
This can easily by mitigated by attaching the library to the normal project
65+
dependencies while having the need to navigate the source files or while editing them,
66+
which makes them properly viewable and documentation displayable in the editor.
67+
68+
* https://youtrack.jetbrains.com/issue/KTIJ-14580
69+
+
70+
We use `@file:Import` to reduce code duplication by having common code in a common file.
71+
Unfortunately, this triggers a Kotlin IntelliJ plugin bug where the imported file cannot
72+
be loaded properly and so the things supplied by it like dependencies or common functions
73+
are not available. This makes most of the workflow `*.main.kts` files red as hell in the
74+
IDE currently.
75+
+
76+
To reduce risk for eye-cancer while reading the `*.main.kts` scripts or to be able to
77+
sanely edit them, temporarily add the `@file:DependsOn` from the imported file to the
78+
importing file and wait a second, then remove the line again once you are done.
79+
80+
* https://youtrack.jetbrains.com/issue/KT-42101
81+
+
82+
We use `@file:Import` to reduce code duplication by having common code in a common file.
83+
Unfortunately, this triggers a Kotlin bug where the compilation cache becomes confused
84+
if the imported file is changed without the importing file being changed too.
85+
+
86+
If only the imported file is changed, it could happen that an old version is used,
87+
or it could also happen that classes added by a `@file:DependsOn` in the imported file
88+
are not available to the importing file. So if there was a change in the imported file,
89+
you either need to also change the importing file, or to properly execute the script,
90+
you need to delete the stale entry from the compilation cache which can be found at for example
91+
`~/.cache/main.kts.compiled.cache/` on Linux and `%LOCALAPPDATA%\main.kts.compiled.cache\`
92+
on Windows. Alternatively, you can also delete the whole cache directory.
93+
+
94+
Another option is to disable the compilation cache for the execution by setting the
95+
environment variable `KOTLIN_MAIN_KTS_COMPILED_SCRIPTS_CACHE_DIR` or the system property
96+
`kotlin.main.kts.compiled.scripts.cache.dir` to an empty value, depending on the run
97+
method you chose. The Gradle tasks already do that, so when using the Gradle tasks you
98+
do not have this problem and it just works.
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
#!/usr/bin/env kotlin
2+
3+
/*
4+
* Copyright 2023 the original author or authors.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* https://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
@file:Import("common.main.kts")
20+
@file:Repository("https://bindings.krzeminski.it/")
21+
@file:DependsOn("actions:checkout:v4")
22+
@file:DependsOn("codecov:codecov-action:v5")
23+
24+
import io.github.typesafegithub.workflows.actions.actions.Checkout
25+
import io.github.typesafegithub.workflows.actions.actions.Checkout.FetchDepth
26+
import io.github.typesafegithub.workflows.actions.codecov.CodecovAction
27+
import io.github.typesafegithub.workflows.domain.Concurrency
28+
import io.github.typesafegithub.workflows.domain.RunnerType
29+
import io.github.typesafegithub.workflows.domain.RunnerType.UbuntuLatest
30+
import io.github.typesafegithub.workflows.domain.triggers.MergeGroup
31+
import io.github.typesafegithub.workflows.domain.triggers.PullRequest
32+
import io.github.typesafegithub.workflows.domain.triggers.Push
33+
import io.github.typesafegithub.workflows.dsl.expressions.Contexts.github
34+
import io.github.typesafegithub.workflows.dsl.expressions.expr
35+
import io.github.typesafegithub.workflows.dsl.workflow
36+
37+
workflow(
38+
name = "Verify Branches and PRs",
39+
on = listOf(
40+
Push(
41+
branchesIgnore = listOf(
42+
"master",
43+
"gh-pages"
44+
)
45+
),
46+
PullRequest(),
47+
MergeGroup()
48+
),
49+
sourceFile = __FILE__,
50+
targetFileName = "${__FILE__.name.substringBeforeLast(".main.kts")}.yml",
51+
// https://stackoverflow.com/a/72408109/16358266
52+
concurrency = Concurrency(
53+
group = "${expr { github.workflow }}-${expr("${github.eventPullRequest.pull_request.number} || ${github.ref}")}",
54+
cancelInProgress = true
55+
)
56+
) {
57+
job(
58+
id = "check_all_workflow_yaml_consistency",
59+
name = "Check all Workflow YAML Consistency",
60+
runsOn = UbuntuLatest
61+
) {
62+
uses(
63+
name = "Checkout Repository",
64+
action = Checkout()
65+
)
66+
run(
67+
name = "Regenerate all Workflow YAMLs",
68+
command = """find .github/workflows -mindepth 1 -maxdepth 1 -name '*.main.kts' -exec {} \;"""
69+
)
70+
run(
71+
name = "Check for Modifications",
72+
command = """
73+
git add --intent-to-add .
74+
git diff --exit-code
75+
""".trimIndent()
76+
)
77+
}
78+
79+
job(
80+
id = "build-and-verify",
81+
name = "Build and Verify",
82+
runsOn = RunnerType.Custom(expr(Matrix.operatingSystem)),
83+
strategy = Strategy(
84+
matrix = Matrix.full
85+
)
86+
) {
87+
uses(
88+
name = "Checkout Repository",
89+
action = Checkout(
90+
// Codecov needs fetch-depth > 1
91+
fetchDepth = FetchDepth.Value(2)
92+
)
93+
)
94+
uses(
95+
name = "Set up JDKs",
96+
action = SetupBuildEnv(
97+
additionalJavaVersion = expr(Matrix.javaVersion)
98+
)
99+
)
100+
run(
101+
name = "Build Spock",
102+
command = listOf(
103+
"./gradlew",
104+
"--stacktrace",
105+
"ghActionsBuild",
106+
""""-Dvariant=${expr(Matrix.variant)}"""",
107+
""""-DjavaVersion=${expr(Matrix.javaVersion)}""""
108+
).joinToString(" "),
109+
// secrets are not injected for pull requests
110+
env = commonCredentials
111+
)
112+
uses(
113+
name = "Upload to Codecov.io",
114+
action = CodecovAction()
115+
)
116+
}
117+
}

0 commit comments

Comments
 (0)