Skip to content

Commit 4e90f03

Browse files
author
Jaden Peterson
committed
Wrote an early version of dottyijar
Currently, it just strips private `val`s and `def`s and definition values, but I plan on making it more aggresive in the future.
1 parent 42c8958 commit 4e90f03

19 files changed

+1171
-41
lines changed

scala/private/macros/scala_repositories.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ def _artifact_ids(scala_version):
189189
"io_bazel_rules_scala_scala_library_2",
190190
"io_bazel_rules_scala_scala_reflect_2",
191191
"io_bazel_rules_scala_scala_tasty_core",
192+
"io_bazel_rules_scala_scala_tasty_inspector",
192193
"org_jline_jline_native",
193194
"org_jline_jline_reader",
194195
"org_jline_jline_terminal",

scripts/create_repository.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ def select_root_artifacts(scala_version, scala_major, is_scala_3) -> List[str]:
137137
f'org.scala-lang:scala3-compiler_3:{scala_version}',
138138
f'org.scala-lang:scala3-library_3:{scala_version}',
139139
f'org.scala-lang:scala3-interfaces:{scala_version}',
140+
f'org.scala-lang:scala3-tasty-inspector_3:{scala_version}',
140141
f'org.scala-lang:tasty-core_3:{scala_version}',
141142
f'org.scala-sbt:compiler-interface:{SBT_COMPILER_INTERFACE_VERSION}',
142143
f'org.scala-sbt:util-interface:{SBT_UTIL_INTERFACE_VERSION}',
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
load("//scala:scala.bzl", "scala_library_for_plugin_bootstrapping", "scala_specs2_junit_test")
2+
load("@rules_java//java:defs.bzl", "java_binary")
3+
4+
scala_library_for_plugin_bootstrapping(
5+
name = "dottyijar-library",
6+
srcs = glob(
7+
["*.scala"],
8+
exclude = ["*.spec.scala"],
9+
),
10+
scala_version = "3.6.2",
11+
visibility = ["//visibility:public"],
12+
deps = [
13+
"//src/scala/io/bazel/rules_scala/dottyijar/tasty",
14+
"//src/scala/io/bazel/rules_scala/dottyijar/tasty/format",
15+
"@com_softwaremill_common_tagging_3_6_2",
16+
"@dev_zio_izumi_reflect_3_6_2",
17+
],
18+
)
19+
20+
# TODO: Eventually, we should make a bootstrapping toolchain so we don't have to create a `*_for_plugin_bootstrapping` equivalent for each Scala rule.
21+
java_binary(
22+
name = "dottyijar",
23+
main_class = "io.bazel.rules_scala.dottyijar.DottyIjar",
24+
visibility = ["//visibility:public"],
25+
runtime_deps = [":dottyijar-library"],
26+
)
27+
28+
scala_specs2_junit_test(
29+
name = "specs",
30+
srcs = glob(["*.spec.scala"]),
31+
scala_version = "3.6.2",
32+
suffixes = ["Spec"],
33+
deps = [
34+
":dottyijar-library",
35+
"//src/scala/io/bazel/rules_scala/dottyijar/tasty:test",
36+
"@io_bazel_rules_scala_org_specs2_specs2_common_3_6_2",
37+
"@io_bazel_rules_scala_org_specs2_specs2_core_3_6_2",
38+
"@io_bazel_rules_scala_org_specs2_specs2_junit",
39+
"@io_bazel_rules_scala_org_specs2_specs2_matcher_3_6_2",
40+
"@io_bazel_rules_scala_scala_tasty_inspector_3_6_2",
41+
],
42+
)
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package io.bazel.rules_scala.dottyijar
2+
3+
import io.bazel.rules_scala.dottyijar.tasty.Tasty
4+
import io.bazel.rules_scala.dottyijar.tasty.format.{TastyFormat, TastyReader, TastyWriter}
5+
import java.io.FileOutputStream
6+
import java.nio.file.{Path, Paths}
7+
import java.nio.file.attribute.FileTime
8+
import java.util.zip.{ZipEntry, ZipFile, ZipOutputStream}
9+
import scala.jdk.CollectionConverters.*
10+
11+
object DottyIjar {
12+
private def writeInterfaceJar(inputJar: ZipFile, outputStream: ZipOutputStream): Unit = {
13+
def copyEntryWithContent(entry: ZipEntry, content: Array[Byte]): Unit = {
14+
val newEntry = new ZipEntry(entry.getName)
15+
16+
newEntry.setCreationTime(FileTime.fromMillis(0))
17+
newEntry.setLastAccessTime(FileTime.fromMillis(0))
18+
newEntry.setLastModifiedTime(FileTime.fromMillis(0))
19+
20+
outputStream.putNextEntry(newEntry)
21+
outputStream.write(content, 0, content.length)
22+
}
23+
24+
def copyEntry(entry: ZipEntry): Unit = copyEntryWithContent(entry, inputJar.getInputStream(entry).readAllBytes())
25+
26+
outputStream.setComment(inputJar.getComment)
27+
outputStream.setLevel(0)
28+
29+
val entryNames = inputJar.entries.asScala.map(_.getName).toSet
30+
31+
inputJar.entries.asScala.foreach {
32+
case entry if entry.getName.startsWith("META-INF/") => copyEntry(entry)
33+
case entry if entry.getName.endsWith(".class") =>
34+
val i = entry.getName.lastIndexOf('/')
35+
val directory = entry.getName.slice(0, i)
36+
val filename = entry.getName.slice(i + 1, entry.getName.length)
37+
val j = filename.indexOf("$")
38+
val tastyFileBaseName = if (j == -1) filename.stripSuffix(".class") else filename.slice(0, j)
39+
40+
if (!entryNames(s"$directory/$tastyFileBaseName.tasty")) {
41+
copyEntry(entry)
42+
}
43+
44+
case entry if entry.getName.endsWith(".tasty") =>
45+
val content = inputJar.getInputStream(entry).readAllBytes()
46+
val updatedContent = TastyUpdater.updateTastyFile(content)
47+
48+
copyEntryWithContent(entry, updatedContent)
49+
50+
case entry => copyEntry(entry)
51+
}
52+
}
53+
54+
def main(arguments: Array[String]): Unit = Arguments
55+
.parseArguments(arguments)
56+
.fold(
57+
println,
58+
arguments => {
59+
val inputJar = new ZipFile(arguments.inputPath.toFile)
60+
61+
try {
62+
val outputStream = new ZipOutputStream(new FileOutputStream(arguments.outputPath.toFile))
63+
64+
try {
65+
writeInterfaceJar(inputJar, outputStream)
66+
} finally {
67+
outputStream.close()
68+
}
69+
} finally {
70+
inputJar.close()
71+
}
72+
},
73+
)
74+
}
75+
76+
private case class Arguments(inputPath: Path, outputPath: Path)
77+
78+
object Arguments {
79+
def parseArguments(arguments: Array[String]): Either[String, Arguments] = arguments
80+
.foldLeft[Either[String, UnvalidatedArguments]](Right(UnvalidatedArguments())) {
81+
case (unvalidatedArguments, argument) =>
82+
unvalidatedArguments.flatMap { unvalidatedArguments =>
83+
argument match {
84+
case "-h" | "--help" =>
85+
Left(
86+
"""dottyijar removes information from Scala 3 JARs that aren't needed for compilation.
87+
|
88+
|Usage:
89+
| dottyijar <input> <output>
90+
| dottyijar -h | --help
91+
|
92+
|Options:
93+
| -h --help Show this screen.""".stripMargin,
94+
)
95+
96+
case _ =>
97+
lazy val path = Paths.get(argument)
98+
99+
if (unvalidatedArguments.inputPath.isEmpty) {
100+
Right(unvalidatedArguments.copy(inputPath = Some(path)))
101+
} else if (unvalidatedArguments.outputPath.isEmpty) {
102+
Right(unvalidatedArguments.copy(outputPath = Some(path)))
103+
} else {
104+
Left(s"Unexpected argument: $argument")
105+
}
106+
}
107+
}
108+
}
109+
.flatMap(_.validate)
110+
}
111+
112+
private case class UnvalidatedArguments(inputPath: Option[Path] = None, outputPath: Option[Path] = None) {
113+
def validate: Either[String, Arguments] = (
114+
for {
115+
inputPath <- inputPath
116+
outputPath <- outputPath
117+
} yield Arguments(inputPath, outputPath)
118+
).toRight("Please provide paths to the input and output JARs.")
119+
}

0 commit comments

Comments
 (0)