Skip to content

Commit 2ce53e9

Browse files
Adding missing files.
1 parent 77d8a6c commit 2ce53e9

File tree

6 files changed

+242
-40
lines changed

6 files changed

+242
-40
lines changed

.gitignore

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Scala
2+
*.class
3+
*.log
4+
*.cache
5+
*.settings/
6+
*.project
7+
*.idea/
8+
*.classpath
9+
10+
# sbt specific
11+
target/
12+
lib_managed/
13+
src_managed/
14+
project/target/
15+
project/project/
16+
.history
17+
.cache
18+
19+
# IntelliJ IDEA
20+
*.iml
21+
.idea/
22+
.idea_modules/
23+
*.ipr
24+
*.iws
25+
out/
26+
27+
# Mac OS X
28+
.DS_Store
29+
30+
# Eclipse
31+
.metadata
32+
*.tmp
33+
*.bak
34+
*.swp
35+
*~.nib
36+
local.properties
37+
.loadpath
38+
39+
# Vim
40+
*.swp
41+
*~
42+
43+
# VS Code
44+
.vscode/
45+
46+
# Log files
47+
logs/
48+
*.log
49+
50+
# sbt build cache
51+
target/streams/

.scalafmt.conf

Whitespace-only changes.

LICENSE

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
The MIT License (MIT)
2+
=====================
3+
4+
Copyright © 2004 RAW Labs SA
5+
6+
Permission is hereby granted, free of charge, to any person
7+
obtaining a copy of this software and associated documentation
8+
files (the “Software”), to deal in the Software without
9+
restriction, including without limitation the rights to use,
10+
copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
copies of the Software, and to permit persons to whom the
12+
Software is furnished to do so, subject to the following
13+
conditions:
14+
15+
The above copyright notice and this permission notice shall be
16+
included in all copies or substantial portions of the Software.
17+
18+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
19+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
20+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
22+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
23+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
25+
OTHER DEALINGS IN THE SOFTWARE.

README.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
Sure, here's the updated and completed README.md for the `sbt-module-patcher` plugin:
2+
3+
# sbt-module-patcher
4+
5+
This sbt plugin tries to alleviate some of the pain associated with creating JPMS-friendly JARs in the Scala world.
6+
7+
Specifically, when publishing a JAR, the Scala compiler version number is appended to the JAR name. For instance, a typical JAR name may be `foo_2.12-1.0.0.jar`.
8+
9+
In the JPMS world, a non-modularized JAR becomes an automatic module where the name is derived from the JAR name. In the generation of the automatic module name, underscores are replaced with dots. In the example above, the module name would therefore be `foo.2.12`. This, however, is not a valid module name; if you try to use it in a requires statement in a `module-info.java`, you will get an error.
10+
11+
If the JAR is something you control, this is not an issue: just fix the JAR name in your process, e.g., in sbt:
12+
13+
```scala
14+
Compile / packageBin / packageOptions += Package.ManifestAttributes("Automatic-Module-Name" -> "foo")
15+
```
16+
17+
But what if this is a dependency you have no control over? That's where this plugin comes in.
18+
19+
It will patch the JAR name in the JAR file itself, so that the automatic module name is correct.
20+
21+
## Usage
22+
23+
This plugin currently supports only Scala 2.12.
24+
25+
Add the plugin to your `project/plugins.sbt` file:
26+
27+
```scala
28+
addSbtPlugin("com.raw-labs.sbt" % "sbt-module-patcher" % "0.0.1")
29+
```
30+
31+
Then, in your `build.sbt` file, apply the plugin to your project:
32+
33+
```scala
34+
lazy val root = (project in file("."))
35+
.doPatchDependencies()
36+
```
37+
38+
## How it works
39+
40+
This plugin performs the following steps to patch the JARs:
41+
42+
1. **Identifies JARs to Patch:** It scans the project's classpath for JAR files that are not already modularized.
43+
2. **Modifies the Manifest:** For each JAR that needs patching, it updates the manifest to include a valid `Automatic-Module-Name`.
44+
3. **Updates Checksums:** After modifying the JAR, it recalculates and updates the checksums (SHA-1, MD5) to ensure integrity.
45+
46+
## Tasks
47+
48+
The plugin provides the following tasks:
49+
50+
- `patchDependencies`: This task patches compile dependencies in the project classpath. It ensures that the JAR files have a valid `Automatic-Module-Name`.
51+
52+
### Example
53+
54+
Here's a detailed example of how you might configure your project to use this plugin:
55+
56+
```scala
57+
// project/plugins.sbt
58+
addSbtPlugin("com.raw-labs.sbt" % "sbt-module-patcher" % "0.0.1")
59+
60+
// build.sbt
61+
lazy val root = (project in file("."))
62+
.settings(
63+
name := "MyProject",
64+
version := "1.0.0",
65+
scalaVersion := "2.12.12"
66+
)
67+
.doPatchDependencies()
68+
```
69+
70+
## Behind the Scenes
71+
72+
### Source Code
73+
74+
The plugin's core functionality is implemented in the `SbtModulePatcher` object. Here's a breakdown of what it does:
75+
76+
1. **Task Definition**: Defines the `patchDependencies` task which scans the project classpath for JAR files.
77+
2. **Patch Dependencies**: Checks each JAR to see if it's already a module. If not, it modifies the JAR manifest to add a valid `Automatic-Module-Name`.
78+
3. **Modify JAR**: Creates a temporary JAR file with the updated manifest and replaces the original JAR with this new file.
79+
4. **Update Checksums**: Calculates and updates the checksums for the modified JAR file to ensure that the JAR file remains valid.
80+
81+
For more details, refer to the source code in the `SbtModulePatcher` object.
82+
83+
## Summary
84+
85+
The `sbt-module-patcher` plugin simplifies the process of making Scala JARs JPMS-friendly by automating the patching of JAR manifests to include a valid `Automatic-Module-Name`. This can be especially useful when dealing with dependencies that you cannot control.
86+
87+
By incorporating this plugin into your sbt build process, you can ensure that your JARs are compatible with JPMS without manual intervention.
88+
89+
(Special thanks to ChatGPT for helping with this README!)
90+
91+
---
92+
93+
Feel free to [reach out]([email protected]) if you encounter any issues or have any questions regarding the `sbt-module-patcher` plugin.

build.sbt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,4 @@ version := "0.0.1"
1414

1515
scalaVersion := "2.12.18"
1616

17-
libraryDependencies += "io.get-coursier" %% "coursier" % "2.1.10"
18-
1917
publishLocalConfiguration := publishLocalConfiguration.value.withOverwrite(true)

src/main/scala/SbtModulePatcher.scala

Lines changed: 73 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,61 @@
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._
1+
import sbt.*
2+
import sbt.Keys.*
3+
4+
import java.io.*
5+
import java.nio.file.*
6+
import java.util.jar.*
7+
import scala.collection.JavaConverters.*
78
import java.security.MessageDigest
8-
import java.nio.file.{Files, Paths}
9+
import java.nio.file.Files
910
import java.nio.file.StandardOpenOption.{CREATE, WRITE}
1011

1112
object SbtModulePatcher extends AutoPlugin {
1213

1314
private val SBT_MODULE_PATCHER_VERSION_KEY = "Sbt-Module-Patcher-Version"
14-
private val SBT_MODULE_PATCHER_VERSION_VALUE = "1"
15+
private val SBT_MODULE_PATCHER_VERSION_VALUE = "2"
1516

16-
override def trigger = allRequirements
17+
object autoImport {
18+
val patchDependencies =
19+
taskKey[Unit]("Patch compile dependencies in the project classpath")
1720

18-
override def requires = plugins.JvmPlugin
21+
implicit class ProjectOps(p: Project) {
1922

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)
23+
def doPatchDependencies(): Project = {
24+
p.settings(
25+
inConfig(Compile)(patchDependenciesSettings),
26+
)
3527
}
28+
29+
private def patchDependenciesSettings: Seq[Setting[_]] = Seq(
30+
patchDependencies := {
31+
val log = streams.value.log
32+
val classpath = dependencyClasspath.value
33+
val jarFiles =
34+
classpath.map(_.data).filter(_.getName.endsWith(".jar"))
35+
36+
log.info("Test if it runs")
37+
38+
jarFiles.foreach { jarFile =>
39+
log.info(s"Checking file ${jarFile.getName}")
40+
if (!isModule(jarFile, log)) {
41+
modifyJar(jarFile, log)
42+
updateChecksums(jarFile, log)
43+
}
44+
}
45+
},
46+
compile := (compile dependsOn patchDependencies).value
47+
)
3648
}
3749
}
3850

51+
import autoImport._
52+
53+
override def projectSettings: Seq[Setting[_]] = Seq(
54+
patchDependencies := {
55+
// This is a default implementation for the task, but it won't be used directly.
56+
}
57+
)
58+
3959
private def isModule(jarFile: File, log: Logger): Boolean = {
4060
if (!jarFile.getName.contains("_2.12")) {
4161
return true
@@ -47,22 +67,27 @@ object SbtModulePatcher extends AutoPlugin {
4767
// Check if module-info.class exists
4868
if (entries.exists(_.getName == "module-info.class")) {
4969
jar.close()
50-
log.info(s"JAR file ${jarFile.getName} is already a module (module-info.class found)")
70+
log.info(
71+
s"JAR file ${jarFile.getName} is already a module (module-info.class found)")
5172
return true
5273
}
5374

5475
// Check if Automatic-Module-Name exists in the manifest
5576
val manifest = jar.getManifest
56-
if (manifest != null && manifest.getMainAttributes.getValue("Automatic-Module-Name") != null) {
77+
if (manifest != null && manifest.getMainAttributes.getValue(
78+
"Automatic-Module-Name") != null) {
5779
jar.close()
58-
log.debug(s"JAR file ${jarFile.getName} is already a module (Automatic-Module-Name found in manifest)")
80+
log.debug(
81+
s"JAR file ${jarFile.getName} is already a module (Automatic-Module-Name found in manifest)")
5982
return true
6083
}
6184

6285
// 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) {
86+
if (manifest != null && manifest.getMainAttributes.getValue(
87+
SBT_MODULE_PATCHER_VERSION_KEY) == SBT_MODULE_PATCHER_VERSION_VALUE) {
6488
jar.close()
65-
log.debug(s"JAR file ${jarFile.getName} was already patched by SbtModulePatcher version $SBT_MODULE_PATCHER_VERSION_VALUE")
89+
log.debug(
90+
s"JAR file ${jarFile.getName} was already patched by SbtModulePatcher version $SBT_MODULE_PATCHER_VERSION_VALUE")
6691
return true
6792
}
6893

@@ -72,7 +97,9 @@ object SbtModulePatcher extends AutoPlugin {
7297

7398
private def modifyJar(jarFile: File, log: Logger): Unit = {
7499
val tempFile = File.createTempFile("temp", ".jar")
75-
Files.copy(jarFile.toPath, tempFile.toPath, StandardCopyOption.REPLACE_EXISTING)
100+
Files.copy(jarFile.toPath,
101+
tempFile.toPath,
102+
StandardCopyOption.REPLACE_EXISTING)
76103

77104
val jar = new JarFile(tempFile)
78105
val entries = jar.entries().asScala
@@ -85,11 +112,15 @@ object SbtModulePatcher extends AutoPlugin {
85112
} else {
86113
new Manifest()
87114
}
88-
val moduleName = jarFile.getName.substring(0, jarFile.getName.indexOf("_2.12")).replaceAll("-", ".").replaceAll("_", ".")
115+
val moduleName = jarFile.getName
116+
.substring(0, jarFile.getName.indexOf("_2.12"))
117+
.replaceAll("-", ".")
118+
.replaceAll("_", ".")
89119
val attrs = manifestOut.getMainAttributes
90120
attrs.putValue("Automatic-Module-Name", moduleName)
91121
// 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)
122+
attrs.putValue(SBT_MODULE_PATCHER_VERSION_KEY,
123+
SBT_MODULE_PATCHER_VERSION_VALUE)
93124
jos.putNextEntry(new JarEntry("META-INF/MANIFEST.MF"))
94125
manifestOut.write(jos)
95126
jos.closeEntry()
@@ -107,18 +138,22 @@ object SbtModulePatcher extends AutoPlugin {
107138
jar.close()
108139

109140
// 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")
141+
Files.move(tempJar.toPath,
142+
jarFile.toPath,
143+
StandardCopyOption.REPLACE_EXISTING)
144+
log.info(
145+
s"Modified JAR file ${jarFile.getName} by adding Automatic-Module-Name: $moduleName")
112146
}
113147

114-
115148
private def updateChecksums(jarFile: File, log: Logger): Unit = {
116149
val algorithms = Seq("SHA-1", "MD5")
117150
algorithms.foreach { algorithm =>
118151
val checksum = calculateChecksum(jarFile, algorithm)
119-
val checksumFile = new File(jarFile.getAbsolutePath + "." + algorithm.toLowerCase.replace("-", ""))
152+
val checksumFile = new File(
153+
jarFile.getAbsolutePath + "." + algorithm.toLowerCase.replace("-", ""))
120154
Files.write(checksumFile.toPath, checksum.getBytes, CREATE, WRITE)
121-
log.info(s"Updated checksum for ${jarFile.getName}: $algorithm = $checksum")
155+
log.info(
156+
s"Updated checksum for ${jarFile.getName}: $algorithm = $checksum")
122157
}
123158
}
124159

0 commit comments

Comments
 (0)