Skip to content

Commit 77d8a6c

Browse files
First commit.
0 parents  commit 77d8a6c

File tree

4 files changed

+158
-0
lines changed

4 files changed

+158
-0
lines changed

build.sbt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
sbtPlugin := true
2+
3+
moduleName := "sbt-module-patcher"
4+
5+
homepage := Some(url("https://www.raw-labs.com/"))
6+
7+
organization := "com.raw-labs.sbt"
8+
9+
organizationName := "RAW Labs SA"
10+
11+
organizationHomepage := Some(url("https://www.raw-labs.com/"))
12+
13+
version := "0.0.1"
14+
15+
scalaVersion := "2.12.18"
16+
17+
libraryDependencies += "io.get-coursier" %% "coursier" % "2.1.10"
18+
19+
publishLocalConfiguration := publishLocalConfiguration.value.withOverwrite(true)

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.6

project/plugins.sbt

Whitespace-only changes.
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import sbt._
2+
import sbt.Keys._
3+
import java.io._
4+
import java.nio.file._
5+
import java.util.jar._
6+
import scala.collection.JavaConverters._
7+
import java.security.MessageDigest
8+
import java.nio.file.{Files, Paths}
9+
import java.nio.file.StandardOpenOption.{CREATE, WRITE}
10+
11+
object SbtModulePatcher extends AutoPlugin {
12+
13+
private val SBT_MODULE_PATCHER_VERSION_KEY = "Sbt-Module-Patcher-Version"
14+
private val SBT_MODULE_PATCHER_VERSION_VALUE = "1"
15+
16+
override def trigger = allRequirements
17+
18+
override def requires = plugins.JvmPlugin
19+
20+
override lazy val projectSettings = Seq(
21+
update := {
22+
val log = streams.value.log
23+
val result = update.value
24+
modifyDownloadedJars(result, log)
25+
result
26+
}
27+
)
28+
29+
private def modifyDownloadedJars(updateReport: UpdateReport, log: Logger): Unit = {
30+
val jarFiles = updateReport.allFiles.filter(_.getName.endsWith(".jar"))
31+
jarFiles.foreach { jarFile =>
32+
if (!isModule(jarFile, log)) {
33+
modifyJar(jarFile, log)
34+
updateChecksums(jarFile, log)
35+
}
36+
}
37+
}
38+
39+
private def isModule(jarFile: File, log: Logger): Boolean = {
40+
if (!jarFile.getName.contains("_2.12")) {
41+
return true
42+
}
43+
44+
val jar = new JarFile(jarFile)
45+
val entries = jar.entries().asScala
46+
47+
// Check if module-info.class exists
48+
if (entries.exists(_.getName == "module-info.class")) {
49+
jar.close()
50+
log.info(s"JAR file ${jarFile.getName} is already a module (module-info.class found)")
51+
return true
52+
}
53+
54+
// Check if Automatic-Module-Name exists in the manifest
55+
val manifest = jar.getManifest
56+
if (manifest != null && manifest.getMainAttributes.getValue("Automatic-Module-Name") != null) {
57+
jar.close()
58+
log.debug(s"JAR file ${jarFile.getName} is already a module (Automatic-Module-Name found in manifest)")
59+
return true
60+
}
61+
62+
// Check if SBT_MODULE_PATCHER_VERSION_KEY exists in the manifest and matches the current version
63+
if (manifest != null && manifest.getMainAttributes.getValue(SBT_MODULE_PATCHER_VERSION_KEY) == SBT_MODULE_PATCHER_VERSION_VALUE) {
64+
jar.close()
65+
log.debug(s"JAR file ${jarFile.getName} was already patched by SbtModulePatcher version $SBT_MODULE_PATCHER_VERSION_VALUE")
66+
return true
67+
}
68+
69+
jar.close()
70+
false
71+
}
72+
73+
private def modifyJar(jarFile: File, log: Logger): Unit = {
74+
val tempFile = File.createTempFile("temp", ".jar")
75+
Files.copy(jarFile.toPath, tempFile.toPath, StandardCopyOption.REPLACE_EXISTING)
76+
77+
val jar = new JarFile(tempFile)
78+
val entries = jar.entries().asScala
79+
val tempJar = File.createTempFile("temp-modified", ".jar")
80+
val jos = new JarOutputStream(new FileOutputStream(tempJar))
81+
82+
val manifest = jar.getManifest
83+
val manifestOut = if (manifest != null) {
84+
manifest
85+
} else {
86+
new Manifest()
87+
}
88+
val moduleName = jarFile.getName.substring(0, jarFile.getName.indexOf("_2.12")).replaceAll("-", ".").replaceAll("_", ".")
89+
val attrs = manifestOut.getMainAttributes
90+
attrs.putValue("Automatic-Module-Name", moduleName)
91+
// Handy if we need to "migrate and re-generate" in the future.
92+
attrs.putValue(SBT_MODULE_PATCHER_VERSION_KEY, SBT_MODULE_PATCHER_VERSION_VALUE)
93+
jos.putNextEntry(new JarEntry("META-INF/MANIFEST.MF"))
94+
manifestOut.write(jos)
95+
jos.closeEntry()
96+
97+
entries.foreach { entry =>
98+
if (entry.getName != "META-INF/MANIFEST.MF") {
99+
jos.putNextEntry(new JarEntry(entry.getName))
100+
val inputStream = jar.getInputStream(entry)
101+
jos.write(inputStream.readAllBytes())
102+
jos.closeEntry()
103+
}
104+
}
105+
106+
jos.close()
107+
jar.close()
108+
109+
// Replace original JAR with modified JAR
110+
Files.move(tempJar.toPath, jarFile.toPath, StandardCopyOption.REPLACE_EXISTING)
111+
log.info(s"Modified JAR file ${jarFile.getName} by adding Automatic-Module-Name: $moduleName")
112+
}
113+
114+
115+
private def updateChecksums(jarFile: File, log: Logger): Unit = {
116+
val algorithms = Seq("SHA-1", "MD5")
117+
algorithms.foreach { algorithm =>
118+
val checksum = calculateChecksum(jarFile, algorithm)
119+
val checksumFile = new File(jarFile.getAbsolutePath + "." + algorithm.toLowerCase.replace("-", ""))
120+
Files.write(checksumFile.toPath, checksum.getBytes, CREATE, WRITE)
121+
log.info(s"Updated checksum for ${jarFile.getName}: $algorithm = $checksum")
122+
}
123+
}
124+
125+
private def calculateChecksum(file: File, algorithm: String): String = {
126+
val buffer = new Array[Byte](8192)
127+
val messageDigest = MessageDigest.getInstance(algorithm)
128+
val inputStream = new FileInputStream(file)
129+
var read = inputStream.read(buffer)
130+
while (read != -1) {
131+
messageDigest.update(buffer, 0, read)
132+
read = inputStream.read(buffer)
133+
}
134+
inputStream.close()
135+
messageDigest.digest.map("%02x".format(_)).mkString
136+
}
137+
138+
}

0 commit comments

Comments
 (0)