Skip to content

Commit 4cfe97d

Browse files
authored
Initial implementation (#1)
1 parent 89a1bb7 commit 4cfe97d

File tree

20 files changed

+367
-0
lines changed

20 files changed

+367
-0
lines changed

.github/workflows/release.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name: Release
2+
on:
3+
push:
4+
branches: [main]
5+
tags: ["*"]
6+
jobs:
7+
publish:
8+
runs-on: ubuntu-latest
9+
steps:
10+
- uses: actions/checkout@v4
11+
with:
12+
fetch-depth: 0
13+
- uses: coursier/cache-action@v6
14+
- uses: coursier/setup-action@v1
15+
with:
16+
jvm: temurin:11
17+
- run: sbt ci-release
18+
env:
19+
PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }}
20+
PGP_SECRET: ${{ secrets.PGP_SECRET }}
21+
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
22+
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}

.github/workflows/test.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
name: Test
2+
on:
3+
push:
4+
branches:
5+
- main
6+
pull_request:
7+
branches:
8+
- main
9+
schedule:
10+
- cron: '0 12 * * 1'
11+
jobs:
12+
build:
13+
strategy:
14+
matrix:
15+
os: [ubuntu-latest, windows-latest]
16+
runs-on: ${{ matrix.os }}
17+
steps:
18+
- uses: actions/checkout@v4
19+
- uses: coursier/cache-action@v6
20+
- uses: coursier/setup-action@v1
21+
with:
22+
jvm: temurin:11
23+
- name: Run tests
24+
run: sbt scalafmtSbtCheck scalafmtCheckAll test scripted

.gitignore

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# sbt target folders
2+
target/
3+
4+
# eclipse project files
5+
/.settings
6+
/.classpath
7+
/.project
8+
9+
/.bsp
10+
/.idea
11+
12+
**/sbt-test/**/.bsp
13+
**/sbt-test/**/.idea
14+
**/sbt-test/**/target
15+
**/sbt-test/**/build.properties
16+
**/sbt-test/**/node_modules
17+
**/sbt-test/**/package-lock.json
18+
**/sbt-test/**/yarn.lock
19+
**/sbt-test/**/pnpm-lock.yaml

.scalafmt.conf

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version = 3.7.17
2+
runner.dialect=scala212
3+
lineEndings=preserve

build.sbt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
inThisBuild(
2+
List(
3+
scalaVersion := "2.12.18",
4+
organization := "me.ptrdom",
5+
homepage := Some(url("https://github.com/ptrdom/sbt-scripted-sources")),
6+
licenses := List(License.MIT),
7+
developers := List(
8+
Developer(
9+
"ptrdom",
10+
"Domantas Petrauskas",
11+
"dom.petrauskas@gmail.com",
12+
url("https://ptrdom.me/")
13+
)
14+
),
15+
sonatypeCredentialHost := "s01.oss.sonatype.org",
16+
sonatypeRepository := "https://s01.oss.sonatype.org/service/local",
17+
versionScheme := Some("semver-spec")
18+
)
19+
)
20+
21+
lazy val root = (project in file("."))
22+
.enablePlugins(SbtPlugin)
23+
.settings(
24+
name := "scripted-sbt-sources",
25+
scriptedLaunchOpts ++= Seq(
26+
"-Dplugin.version=" + version.value
27+
),
28+
scriptedBufferLog := false
29+
)

project/build.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
sbt.version = 1.9.8

project/plugins.sbt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.12")
2+
3+
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.2")
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
package me.ptrdom.sbt.scripted.sources
2+
3+
import java.nio.file.Files
4+
import java.nio.file.Path
5+
6+
import sbt.*
7+
import sbt.AutoPlugin
8+
import sbt.Keys.*
9+
import sbt.ScriptedPlugin
10+
import sbt.ScriptedPlugin.autoImport.sbtTestDirectory
11+
import sbt.ScriptedPlugin.autoImport.scripted
12+
import sbt.internal.util.ManagedLogger
13+
import sbt.nio.Keys.watchTriggers
14+
15+
import scala.io.Source
16+
import scala.jdk.CollectionConverters.*
17+
import scala.util.Using
18+
19+
object ScriptedSourcesPlugin extends AutoPlugin {
20+
override def requires = ScriptedPlugin
21+
22+
object autoImport {
23+
val scriptedSourcesSync =
24+
taskKey[Boolean]("Sync scripted tests with their sources")
25+
val scriptedSourcesCheck =
26+
taskKey[Unit]("Check if scripted tests and their sources are in sync")
27+
}
28+
29+
import autoImport.*
30+
31+
private val sourcesConfigFileName = ".sources"
32+
33+
private def processScriptedSources[T](
34+
log: Logger
35+
)(
36+
baseDirectoryV: File,
37+
sbtTestDirectoryV: File
38+
)(
39+
handler: Iterator[(Path, Set[sbt.File])] => T
40+
): T = {
41+
Using(
42+
Files.walk(sbtTestDirectoryV.toPath)
43+
) { stream =>
44+
val dataForHandler = stream
45+
.iterator()
46+
.asScala
47+
.flatMap { testDirectory =>
48+
val sourcesConfig =
49+
new File(testDirectory.toFile, sourcesConfigFileName)
50+
if (!sourcesConfig.exists()) {
51+
log.debug(
52+
s"scripted sources config missing [${sourcesConfig.absolutePath}]"
53+
)
54+
List.empty
55+
} else {
56+
val sourcesForTest: Set[File] =
57+
Using(Source.fromFile(sourcesConfig)) { source =>
58+
source
59+
.getLines()
60+
.map { sourceForTest =>
61+
val sourceForTestDirectory = baseDirectoryV / sourceForTest
62+
if (!sourceForTestDirectory.exists()) {
63+
sys.error(
64+
s"Source for test is missing [${sourceForTestDirectory.getAbsolutePath}]"
65+
)
66+
} else {
67+
log.debug(
68+
s"Source for test [${sourceForTestDirectory.getAbsolutePath}] exists"
69+
)
70+
sourceForTestDirectory
71+
}
72+
}
73+
.toSet
74+
}.fold(
75+
ex =>
76+
throw new RuntimeException(
77+
s"Failed to read source config [${sourcesConfig.getAbsolutePath}]",
78+
ex
79+
),
80+
identity
81+
)
82+
List((testDirectory, sourcesForTest))
83+
}
84+
}
85+
handler(dataForHandler)
86+
}
87+
.fold(ex => throw ex, identity)
88+
}
89+
90+
private def runScriptedSource(
91+
dry: Boolean,
92+
log: ManagedLogger
93+
)(baseDirectoryV: File, sbtTestDirectoryV: File): Boolean = {
94+
processScriptedSources(log)(baseDirectoryV, sbtTestDirectoryV)(_.flatMap {
95+
case (testDirectory, sourcesForTest) =>
96+
sourcesForTest.map(sourceForTest => (testDirectory, sourceForTest))
97+
}
98+
.foldLeft(true) { case (pristine, (testDirectory, sourceForTest)) =>
99+
Using(
100+
Files
101+
.walk(sourceForTest.toPath)
102+
)(
103+
_.iterator().asScala
104+
.map(_.toFile)
105+
.filter(
106+
_.isFile
107+
)
108+
.foldLeft(pristine) { case (pristine, file) =>
109+
val targetFile = new File(
110+
file.getAbsolutePath.replace(
111+
sourceForTest.getAbsolutePath,
112+
testDirectory.toFile.getAbsolutePath
113+
)
114+
)
115+
if (
116+
!Hash(file).sameElements(
117+
Hash(targetFile)
118+
) || !targetFile.exists()
119+
) {
120+
log.debug(
121+
s"File changed [${file.getAbsolutePath}], copying to [${targetFile.getAbsolutePath}]"
122+
)
123+
if (!dry) {
124+
IO.copyFile(
125+
file,
126+
targetFile
127+
)
128+
}
129+
false
130+
} else {
131+
log.debug(
132+
s"File not changed [${file.getAbsolutePath}]"
133+
)
134+
pristine
135+
}
136+
}
137+
).fold(ex => throw ex, identity)
138+
})
139+
}
140+
141+
override lazy val projectSettings: Seq[Setting[?]] = Seq(
142+
scriptedSourcesSync := {
143+
val log = streams.value.log
144+
145+
log.debug("Running scripted sources sync")
146+
147+
val baseDirectoryV = baseDirectory.value
148+
val sbtTestDirectoryV = sbtTestDirectory.value
149+
150+
runScriptedSource(dry = false, log)(baseDirectoryV, sbtTestDirectoryV)
151+
},
152+
scriptedSourcesCheck := {
153+
val log = streams.value.log
154+
155+
log.debug("Running scripted sources check")
156+
157+
val baseDirectoryV = baseDirectory.value
158+
val sbtTestDirectoryV = sbtTestDirectory.value
159+
160+
val pristine =
161+
runScriptedSource(dry = true, log)(baseDirectoryV, sbtTestDirectoryV)
162+
if (!pristine) {
163+
sys.error("Scripted sources not in sync!")
164+
}
165+
},
166+
scripted := scripted.dependsOn(scriptedSourcesSync).evaluated,
167+
scripted / watchTriggers ++= {
168+
val log = Keys.sLog.value
169+
170+
log.debug("Setting scripted sources as watch triggers")
171+
172+
val baseDirectoryV = baseDirectory.value
173+
val sbtTestDirectoryV = sbtTestDirectory.value
174+
175+
processScriptedSources(log)(baseDirectoryV, sbtTestDirectoryV)(
176+
_.flatMap { case (_, sourcesForTest) =>
177+
sourcesForTest
178+
.map { sourceForTest =>
179+
Glob(sourceForTest, RecursiveGlob)
180+
}
181+
}.toList
182+
)
183+
}
184+
)
185+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import me.ptrdom.sbt.scripted.sources.ScriptedSourcesPlugin
2+
3+
ThisBuild / version := "0.1.0-SNAPSHOT"
4+
5+
ThisBuild / scalaVersion := "2.12.18"
6+
7+
lazy val root = (project in file("."))
8+
.enablePlugins(SbtPlugin, ScriptedSourcesPlugin)
9+
.settings(
10+
name := "basic-plugin-project",
11+
scriptedLaunchOpts ++= Seq(
12+
"-Dplugin.version=" + version.value
13+
),
14+
scriptedBufferLog := false
15+
)

src/sbt-test/scripted-sources-plugin/basic-plugin-project/example/index.html

Whitespace-only changes.

0 commit comments

Comments
 (0)