Skip to content

Commit 207a4f5

Browse files
committed
Support custom triggers for ci
This patch introduced githubWorkflowTriggers that allows full control of the `on:` trigger part of the ci.yml. To maintain backwards compatibility with existing usage the default value is derived from the existing keys.
1 parent 4b29c3f commit 207a4f5

9 files changed

Lines changed: 350 additions & 40 deletions

File tree

docs/gha.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,11 @@ Any and all settings which affect the behavior of the generative plugin should b
7171
- `githubWorkflowUseSbtThinClient` : `Boolean` – Controls whether or not the `--client` option will be added to `sbt` command invocations, accelerating build times (default: `true` for sbt ≥ 1.4, `false` otherwise)
7272
- `githubWorkflowIncludeClean` : `Boolean` – Controls whether to include the clean.yml file (default: `true`)
7373
- `githubWorkflowDependencyPatterns` : `Seq[String]` – A list of file globs which dictate where dependency information is stored. This is conventionally just `**/*.sbt` and `project/build.properties`. If you store dependency information in some *other* file (for example, `project/Versions.scala`), then you should add a glob which matches that file in this setting. This is used for determining the appropriate cache keys for the Ivy and Coursier caches.
74-
- `githubWorkflowTargetBranches` : `Seq[String]` – A list of globs which will match branches and tags for `push` and `pull-request` event types to trigger the **ci.yml** workflow. Defaults to `[*]`.
75-
- `githubWorkflowTargetTags` : `Seq[String]` – A list of globs which will match tags and tags for `push` event types to trigger the **ci.yml** workflow. Defaults to `[]`.
76-
- `githubWorkflowTargetPaths` : `Paths` – Paths which will match modified files for `push` and `pull_request` event types to trigger the **ci.yml** workflow. May be `Paths.None`, `Paths.Include(patterns)`, or `Paths.Ignore(patterns)`. `Paths.Include` may include negative patterns. Defaults to `Paths.None`.
77-
- `githubWorkflowPREventTypes` : `Seq[PREventType]` – A list of event types which will be used to determine which Pull Request events trigger the **ci.yml** workflow. This follows GitHub's defaults: `[opened, synchronize, reopened]`.
74+
- `githubWorkflowTriggers`: WorkflowTriggers – Specifify the push, pull_request, and merge_group triggers in the on: section of the ci.yml workflow. By default this is derived from githubWorkflowTargetBranches, githubWorkflowTargetTags, githubWorkflowTargetPaths, and githubWorkflowPREventTypes. Setting it explicitly takes precedence, and those four keys are then ignored. This is the only way to express triggers they can't, such as a merge_group trigger (for GitHub merge queues) or branches-ignore/paths-ignore filters. Constructed via WorkflowTriggers, PushTrigger, PullRequestTrigger, and MergeGroupTrigger (with MergeGroupEventType for merge-group activity types).
75+
- `githubWorkflowTargetBranches` : `Seq[String]` – A list of globs which will match branches and tags for `push` and `pull-request` event types to trigger the **ci.yml** workflow. Defaults to `[*]`. (Ignored if githubWorkflowTriggers is set directly.)
76+
- `githubWorkflowTargetTags` : `Seq[String]` – A list of globs which will match tags and tags for `push` event types to trigger the **ci.yml** workflow. Defaults to `[]`. (Ignored if githubWorkflowTriggers is set directly.)
77+
- `githubWorkflowTargetPaths` : `Paths` – Paths which will match modified files for `push` and `pull_request` event types to trigger the **ci.yml** workflow. May be `Paths.None`, `Paths.Include(patterns)`, or `Paths.Ignore(patterns)`. `Paths.Include` may include negative patterns. Defaults to `Paths.None`. (Ignored if githubWorkflowTriggers is set directly.)
78+
- `githubWorkflowPREventTypes` : `Seq[PREventType]` – A list of event types which will be used to determine which Pull Request events trigger the **ci.yml** workflow. This follows GitHub's defaults: `[opened, synchronize, reopened]`. (Ignored if githubWorkflowTriggers is set directly.)
7879
- `githubWorkflowArtifactUpload` : `Boolean` – Controls whether or not to upload target directories in the event that multiple jobs are running sequentially. Can be set on a per-project basis. Defaults to `true`.
7980
- `githubWorkflowJobSetup` : `Seq[WorkflowStep]` – The automatically-generated checkout, setup, and cache steps which are common to all jobs which touch the build (default: autogenerated)
8081
- `githubWorkflowEnv` : `Map[String, String]` – An environment which is global to the entire **ci.yml** workflow. Defaults to `Map("GITHUB_TOKEN" -> "${{ secrets.GITHUB_TOKEN }}")` since it's so commonly needed.

github-actions/src/main/scala/org/typelevel/sbt/gha/GenerativeKeys.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ trait GenerativeKeys {
9191

9292
lazy val githubWorkflowDependencyPatterns = settingKey[Seq[String]](
9393
"A list of file globes within the project which affect dependency information (default: [**/*.sbt, project/build.properties])")
94+
lazy val githubWorkflowTriggers = settingKey[WorkflowTriggers](
95+
"The triggers to use for the workflow. The default is derived from githubWorkflowTargetBranches, githubWorkflowTargetTags, githubWorkflowTargetPaths, githubWorkflowPREventTypes. " +
96+
"Setting this directly means the individual keys (githubWorkflowTargetBranches, etc.) are ignored.")
9497
lazy val githubWorkflowTargetBranches = settingKey[Seq[String]](
9598
"A list of branch patterns on which to trigger push and PR builds (default: [*])")
9699
lazy val githubWorkflowTargetTags = settingKey[Seq[String]](

github-actions/src/main/scala/org/typelevel/sbt/gha/GenerativePlugin.scala

Lines changed: 98 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,21 @@ object GenerativePlugin extends AutoPlugin {
3131
type WorkflowJob = org.typelevel.sbt.gha.WorkflowJob
3232
val WorkflowJob = org.typelevel.sbt.gha.WorkflowJob
3333

34+
type WorkflowTriggers = org.typelevel.sbt.gha.WorkflowTriggers
35+
val WorkflowTriggers = org.typelevel.sbt.gha.WorkflowTriggers
36+
37+
type PushTrigger = org.typelevel.sbt.gha.PushTrigger
38+
val PushTrigger = org.typelevel.sbt.gha.PushTrigger
39+
40+
type PullRequestTrigger = org.typelevel.sbt.gha.PullRequestTrigger
41+
val PullRequestTrigger = org.typelevel.sbt.gha.PullRequestTrigger
42+
43+
type MergeGroupTrigger = org.typelevel.sbt.gha.MergeGroupTrigger
44+
val MergeGroupTrigger = org.typelevel.sbt.gha.MergeGroupTrigger
45+
46+
type MergeGroupEventType = org.typelevel.sbt.gha.MergeGroupEventType
47+
val MergeGroupEventType = org.typelevel.sbt.gha.MergeGroupEventType
48+
3449
type Concurrency = org.typelevel.sbt.gha.Concurrency
3550
val Concurrency = org.typelevel.sbt.gha.Concurrency
3651

@@ -147,6 +162,52 @@ object GenerativePlugin extends AutoPlugin {
147162
}
148163
}
149164

165+
def compileMergeGroupEventType(tpe: MergeGroupEventType): String =
166+
tpe match {
167+
case MergeGroupEventType.ChecksRequested => "checks_requested"
168+
}
169+
170+
private def whenNonEmpty(items: List[String])(f: List[String] => String): String =
171+
if (items.isEmpty) "" else f(items)
172+
173+
private def compilePushOrPullRequestTrigger(trigger: PushOrPullRequestTrigger): String = {
174+
val branches = whenNonEmpty(trigger.branches)(b => s"\n branches:${compileList(b, 3)}")
175+
val branchesIgnore =
176+
whenNonEmpty(trigger.branchesIgnore)(b => s"\n branches-ignore:${compileList(b, 3)}")
177+
val paths = whenNonEmpty(trigger.paths)(p => s"\n paths:${compileList(p, 3)}")
178+
val pathsIgnore =
179+
whenNonEmpty(trigger.pathsIgnore)(p => s"\n paths-ignore:${compileList(p, 3)}")
180+
s"$branches$branchesIgnore$paths$pathsIgnore"
181+
}
182+
183+
def compilePullRequestTrigger(pull: PullRequestTrigger): String = {
184+
val base = compilePushOrPullRequestTrigger(pull)
185+
val types =
186+
whenNonEmpty(pull.types.map(compilePREventType))(t => s"\n types:${compileList(t, 3)}")
187+
s"$base$types"
188+
}
189+
190+
def compilePushTrigger(push: PushTrigger): String = {
191+
val base = compilePushOrPullRequestTrigger(push)
192+
val tags = whenNonEmpty(push.tags)(t => s"\n tags:${compileList(t, 3)}")
193+
val tagsIgnore =
194+
whenNonEmpty(push.tagsIgnore)(t => s"\n tags-ignore:${compileList(t, 3)}")
195+
s"$base$tags$tagsIgnore"
196+
}
197+
198+
def compileMergeGroupTrigger(merge: MergeGroupTrigger): String =
199+
whenNonEmpty(merge.types.map(compileMergeGroupEventType))(t =>
200+
s"\n types:${compileList(t, 3)}")
201+
202+
def compileWorkflowTrigger(trigger: WorkflowTriggers): String = {
203+
val pullRequest =
204+
trigger.pullRequest.fold("")(t => s"\n pull_request:${compilePullRequestTrigger(t)}")
205+
val push = trigger.push.fold("")(t => s"\n push:${compilePushTrigger(t)}")
206+
val mergeGroup =
207+
trigger.mergeGroup.fold("")(t => s"\n merge_group:${compileMergeGroupTrigger(t)}")
208+
s"on:$pullRequest$push$mergeGroup"
209+
}
210+
150211
def compileRef(ref: Ref): String = ref match {
151212
case Ref.Branch(name) => s"refs/heads/$name"
152213
case Ref.Tag(name) => s"refs/tags/$name"
@@ -582,10 +643,7 @@ ${indent(job.steps.map(compileStep(_, sbt, job.sbtStepPreamble, declareShell = d
582643

583644
def compileWorkflow(
584645
name: String,
585-
branches: List[String],
586-
tags: List[String],
587-
paths: Paths,
588-
prEventTypes: List[PREventType],
646+
triggers: WorkflowTriggers,
589647
permissions: Option[Permissions],
590648
env: Map[String, String],
591649
concurrency: Option[Concurrency],
@@ -608,29 +666,6 @@ ${indent(job.steps.map(compileStep(_, sbt, job.sbtStepPreamble, declareShell = d
608666
val renderedConcurrency =
609667
concurrency.map(compileConcurrency).map("\n" + _ + "\n\n").getOrElse("")
610668

611-
val renderedTypesPre = prEventTypes.map(compilePREventType).mkString("[", ", ", "]")
612-
val renderedTypes =
613-
if (prEventTypes.sortBy(_.toString) == PREventType.Defaults)
614-
""
615-
else
616-
"\n" + indent("types: " + renderedTypesPre, 2)
617-
618-
val renderedTags =
619-
if (tags.isEmpty)
620-
""
621-
else
622-
s"""
623-
tags: [${tags.map(wrap).mkString(", ")}]"""
624-
625-
val renderedPaths = paths match {
626-
case Paths.None =>
627-
""
628-
case Paths.Include(paths) =>
629-
"\n" + indent(s"""paths: [${paths.map(wrap).mkString(", ")}]""", 2)
630-
case Paths.Ignore(paths) =>
631-
"\n" + indent(s"""paths-ignore: [${paths.map(wrap).mkString(", ")}]""", 2)
632-
}
633-
634669
s"""# This file was automatically generated by sbt-github-actions using the
635670
# githubWorkflowGenerate task. You should add and commit this file to
636671
# your git repository. It goes without saying that you shouldn't edit
@@ -640,11 +675,7 @@ ${indent(job.steps.map(compileStep(_, sbt, job.sbtStepPreamble, declareShell = d
640675

641676
name: ${wrap(name)}
642677

643-
on:
644-
pull_request:
645-
branches: [${branches.map(wrap).mkString(", ")}]$renderedTypes$renderedPaths
646-
push:
647-
branches: [${branches.map(wrap).mkString(", ")}]$renderedTags$renderedPaths
678+
${compileWorkflowTrigger(triggers)}
648679

649680
${renderedPerm}${renderedEnv}${renderedConcurrency}jobs:
650681
${indent(jobs.map(compileJob(_, sbt)).mkString("\n\n"), 1)}
@@ -676,6 +707,40 @@ ${indent(jobs.map(compileJob(_, sbt)).mkString("\n\n"), 1)}
676707
githubWorkflowPublishPostamble := Seq(),
677708
githubWorkflowPublish := Seq(
678709
WorkflowStep.Sbt(List("+publish"), name = Some("Publish project"))),
710+
711+
githubWorkflowTriggers := {
712+
val targetBranches = githubWorkflowTargetBranches.value.toList
713+
val targetTags = githubWorkflowTargetTags.value.toList
714+
val prEventTypes = githubWorkflowPREventTypes.value.toList
715+
716+
val (paths, pathsIgnore) = githubWorkflowTargetPaths.value match {
717+
case Paths.Include(ps) => (ps, Nil)
718+
case Paths.Ignore(ps) => (Nil, ps)
719+
case Paths.None => (Nil, Nil)
720+
}
721+
722+
val types = {
723+
if (prEventTypes.sortBy(_.toString) == PREventType.Defaults) Nil
724+
else prEventTypes
725+
}
726+
727+
WorkflowTriggers(
728+
push = Some(
729+
PushTrigger(
730+
branches = targetBranches,
731+
tags = targetTags,
732+
paths = paths,
733+
pathsIgnore = pathsIgnore
734+
)),
735+
pullRequest = Some(
736+
PullRequestTrigger(
737+
branches = targetBranches,
738+
types = types,
739+
paths = paths,
740+
pathsIgnore = pathsIgnore
741+
))
742+
)
743+
},
679744
githubWorkflowPublishTargetBranches := Seq(RefPredicate.Equals(Ref.Branch("main"))),
680745
githubWorkflowPublishCond := None,
681746
githubWorkflowPublishTimeoutMinutes := None,
@@ -914,10 +979,7 @@ ${indent(jobs.map(compileJob(_, sbt)).mkString("\n\n"), 1)}
914979
private val generateCiContents = Def task {
915980
compileWorkflow(
916981
"Continuous Integration",
917-
githubWorkflowTargetBranches.value.toList,
918-
githubWorkflowTargetTags.value.toList,
919-
githubWorkflowTargetPaths.value,
920-
githubWorkflowPREventTypes.value.toList,
982+
githubWorkflowTriggers.value,
921983
githubWorkflowPermissions.value,
922984
githubWorkflowEnv.value,
923985
githubWorkflowConcurrency.value,
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright 2026 Typelevel
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.typelevel.sbt.gha
17+
18+
sealed abstract class MergeGroupEventType
19+
20+
object MergeGroupEventType {
21+
case object ChecksRequested extends MergeGroupEventType
22+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2026 Typelevel
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.typelevel.sbt.gha
17+
18+
sealed abstract class MergeGroupTrigger {
19+
def types: List[MergeGroupEventType]
20+
21+
def withTypes(types: List[MergeGroupEventType]): MergeGroupTrigger
22+
}
23+
24+
object MergeGroupTrigger {
25+
def apply(types: List[MergeGroupEventType] = Nil): MergeGroupTrigger =
26+
Impl(types)
27+
28+
private final case class Impl(types: List[MergeGroupEventType]) extends MergeGroupTrigger {
29+
override def withTypes(types: List[MergeGroupEventType]): MergeGroupTrigger =
30+
copy(types = types)
31+
32+
override def productPrefix = "MergeGroupTrigger"
33+
}
34+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright 2026 Typelevel
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.typelevel.sbt.gha
17+
18+
sealed abstract class PullRequestTrigger extends PushOrPullRequestTrigger {
19+
def types: List[PREventType]
20+
21+
def withTypes(types: List[PREventType]): PullRequestTrigger
22+
}
23+
24+
object PullRequestTrigger {
25+
def apply(
26+
branches: List[String] = Nil,
27+
branchesIgnore: List[String] = Nil,
28+
paths: List[String] = Nil,
29+
pathsIgnore: List[String] = Nil,
30+
types: List[PREventType] = Nil
31+
): PullRequestTrigger =
32+
Impl(branches, branchesIgnore, paths, pathsIgnore, types)
33+
34+
private final case class Impl(
35+
branches: List[String],
36+
branchesIgnore: List[String],
37+
paths: List[String],
38+
pathsIgnore: List[String],
39+
types: List[PREventType])
40+
extends PullRequestTrigger {
41+
42+
override def withBranches(branches: List[String]): PullRequestTrigger =
43+
copy(branches = branches)
44+
override def withBranchesIgnore(branchesIgnore: List[String]): PullRequestTrigger =
45+
copy(branchesIgnore = branchesIgnore)
46+
override def withPaths(paths: List[String]): PullRequestTrigger = copy(paths = paths)
47+
override def withPathsIgnore(pathsIgnore: List[String]): PullRequestTrigger =
48+
copy(pathsIgnore = pathsIgnore)
49+
override def withTypes(types: List[PREventType]): PullRequestTrigger =
50+
copy(types = types)
51+
52+
override def productPrefix = "PullRequestTrigger"
53+
}
54+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2026 Typelevel
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.typelevel.sbt.gha
17+
18+
trait PushOrPullRequestTrigger {
19+
def branches: List[String]
20+
def branchesIgnore: List[String]
21+
def paths: List[String]
22+
def pathsIgnore: List[String]
23+
24+
def withBranches(branches: List[String]): PushOrPullRequestTrigger
25+
def withBranchesIgnore(branchesIgnore: List[String]): PushOrPullRequestTrigger
26+
def withPaths(paths: List[String]): PushOrPullRequestTrigger
27+
def withPathsIgnore(pathsIgnore: List[String]): PushOrPullRequestTrigger
28+
}

0 commit comments

Comments
 (0)