Skip to content

Commit 86bafce

Browse files
Merge pull request #1068 from alexarchambault/java-class-name-external-binary
Use external binary to extract class name from stdin Java sources
2 parents 7734ec3 + bfc96d6 commit 86bafce

File tree

18 files changed

+276
-128
lines changed

18 files changed

+276
-128
lines changed

build.sc

Lines changed: 8 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ object `cli-options` extends CliOptions
7171
object `build-macros` extends Cross[BuildMacros](Scala.mainVersions: _*)
7272
object options extends Cross[Options](Scala.mainVersions: _*)
7373
object scalaparse extends ScalaParse
74-
object javaparse extends JavaParse
7574
object directives extends Cross[Directives](Scala.mainVersions: _*)
7675
object core extends Cross[Core](Scala.mainVersions: _*)
7776
object `build-module` extends Cross[Build](Scala.mainVersions: _*)
@@ -400,6 +399,7 @@ class Core(val crossScalaVersion: String) extends BuildLikeModule {
400399
| def defaultGraalVMVersion = "${deps.graalVmVersion}"
401400
|
402401
| def scalaCliSigningVersion = "${Deps.signingCli.dep.version}"
402+
| def javaClassNameVersion = "${Deps.javaClassName.dep.version}"
403403
|
404404
| def libsodiumVersion = "${deps.libsodiumVersion}"
405405
| def libsodiumjniVersion = "${Deps.libsodiumjni.dep.version}"
@@ -414,8 +414,8 @@ class Core(val crossScalaVersion: String) extends BuildLikeModule {
414414

415415
class Directives(val crossScalaVersion: String) extends BuildLikeModule {
416416
def moduleDeps = Seq(
417-
`options`(),
418-
`core`()
417+
options(),
418+
core()
419419
)
420420
def scalacOptions = T {
421421
super.scalacOptions() ++ asyncScalacOptions(scalaVersion())
@@ -470,7 +470,7 @@ class Directives(val crossScalaVersion: String) extends BuildLikeModule {
470470

471471
class Options(val crossScalaVersion: String) extends BuildLikeModule {
472472
def moduleDeps = Seq(
473-
`core`(),
473+
core(),
474474
`build-macros`()
475475
)
476476
def scalacOptions = T {
@@ -498,34 +498,6 @@ trait ScalaParse extends SbtModule with ScalaCliPublishModule with ScalaCliCompi
498498
def scalaVersion = Scala.scala213
499499
}
500500

501-
trait JavaParse extends SbtModule with ScalaCliPublishModule with ScalaCliCompile {
502-
def ivyDeps = super.ivyDeps() ++ Agg(Deps.scala3Compiler(scalaVersion()))
503-
504-
// pin scala3-library suffix, so that 2.13 modules can have us as moduleDep fine
505-
def mandatoryIvyDeps = T {
506-
super.mandatoryIvyDeps().map { dep =>
507-
val isScala3Lib =
508-
dep.dep.module.organization.value == "org.scala-lang" &&
509-
dep.dep.module.name.value == "scala3-library" &&
510-
(dep.cross match {
511-
case _: CrossVersion.Binary => true
512-
case _ => false
513-
})
514-
if (isScala3Lib)
515-
dep.copy(
516-
dep = dep.dep.withModule(
517-
dep.dep.module.withName(
518-
coursier.ModuleName(dep.dep.module.name.value + "_3")
519-
)
520-
),
521-
cross = CrossVersion.empty(dep.cross.platformed)
522-
)
523-
else dep
524-
}
525-
}
526-
def scalaVersion = Scala.scala3
527-
}
528-
529501
trait Scala3Runtime extends SbtModule with ScalaCliPublishModule with ScalaCliCompile {
530502
def ivyDeps = super.ivyDeps()
531503
def scalaVersion = Scala.scala3
@@ -550,7 +522,7 @@ class Scala3Graal(val crossScalaVersion: String) extends BuildLikeModule {
550522
}
551523
}
552524

553-
trait Scala3GraalProcessor extends ScalaModule {
525+
trait Scala3GraalProcessor extends ScalaModule with ScalaCliPublishModule {
554526
def moduleDeps = Seq(`scala3-graal`(Scala.scala3))
555527
def scalaVersion = Scala.scala3
556528
def finalMainClass = "scala.cli.graal.CoursierCacheProcessor"
@@ -559,10 +531,9 @@ trait Scala3GraalProcessor extends ScalaModule {
559531
class Build(val crossScalaVersion: String) extends BuildLikeModule {
560532
def millSourcePath = super.millSourcePath / os.up / "build"
561533
def moduleDeps = Seq(
562-
`options`(),
534+
options(),
563535
scalaparse,
564-
javaparse,
565-
`directives`(),
536+
directives(),
566537
`scala-cli-bsp`,
567538
`test-runner`(),
568539
`tasty-lib`()
@@ -578,6 +549,7 @@ class Build(val crossScalaVersion: String) extends BuildLikeModule {
578549
def ivyDeps = super.ivyDeps() ++ Agg(
579550
Deps.asm,
580551
Deps.collectionCompat,
552+
Deps.javaClassName,
581553
Deps.jsoniterCore,
582554
Deps.nativeTestRunner,
583555
Deps.osLib,
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package scala.build.internal;
2+
3+
import com.oracle.svm.core.annotate.Substitute;
4+
import com.oracle.svm.core.annotate.TargetClass;
5+
6+
/**
7+
* This makes [[JavaParserProxyMaker.get]] provide a [[JavaParserProxyBinary]]
8+
* rather than a [[JavaParserProxyJvm]], from native launchers.
9+
*
10+
* See [[JavaParserProxyMaker]] for more details.
11+
*/
12+
@TargetClass(className = "scala.build.internal.JavaParserProxyMaker")
13+
public final class JavaParserProxyMakerSubst {
14+
@Substitute
15+
public JavaParserProxy get(
16+
Object archiveCache,
17+
scala.Option<String> javaClassNameVersionOpt,
18+
scala.build.Logger logger
19+
) {
20+
return new JavaParserProxyBinary(archiveCache, logger, javaClassNameVersionOpt);
21+
}
22+
}

modules/build/src/main/scala/scala/build/Build.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,9 @@ object Build {
152152
CrossSources.forInputs(
153153
inputs,
154154
Sources.defaultPreprocessors(
155-
options.scriptOptions.codeWrapper.getOrElse(CustomCodeWrapper)
155+
options.scriptOptions.codeWrapper.getOrElse(CustomCodeWrapper),
156+
options.archiveCache,
157+
options.internal.javaClassNameVersionOpt
156158
),
157159
logger
158160
)

modules/build/src/main/scala/scala/build/Sources.scala

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package scala.build
22

3+
import coursier.cache.ArchiveCache
4+
import coursier.util.Task
5+
36
import scala.build.internal.CodeWrapper
47
import scala.build.options.{BuildOptions, Scope}
58
import scala.build.preprocessing.*
@@ -69,10 +72,26 @@ object Sources {
6972
topWrapperLen: Int
7073
)
7174

72-
def defaultPreprocessors(codeWrapper: CodeWrapper): Seq[Preprocessor] =
75+
/** The default preprocessor list.
76+
*
77+
* @param codeWrapper
78+
* used by the Scala script preprocessor to "wrap" user code
79+
* @param archiveCache
80+
* used from native launchers by the Java preprocessor, to download a java-class-name binary,
81+
* used to infer the class name of unnamed Java sources (like stdin)
82+
* @param javaClassNameVersionOpt
83+
* if using a java-class-name binary, the version we should download. If empty, the default
84+
* version is downloaded.
85+
* @return
86+
*/
87+
def defaultPreprocessors(
88+
codeWrapper: CodeWrapper,
89+
archiveCache: ArchiveCache[Task],
90+
javaClassNameVersionOpt: Option[String]
91+
): Seq[Preprocessor] =
7392
Seq(
7493
ScriptPreprocessor(codeWrapper),
75-
JavaPreprocessor,
94+
JavaPreprocessor(archiveCache, javaClassNameVersionOpt),
7695
ScalaPreprocessor,
7796
DataPreprocessor
7897
)

modules/build/src/main/scala/scala/build/bsp/BspImpl.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ final class BspImpl(
6161
CrossSources.forInputs(
6262
inputs,
6363
Sources.defaultPreprocessors(
64-
buildOptions.scriptOptions.codeWrapper.getOrElse(CustomCodeWrapper)
64+
buildOptions.scriptOptions.codeWrapper.getOrElse(CustomCodeWrapper),
65+
buildOptions.archiveCache,
66+
buildOptions.internal.javaClassNameVersionOpt
6567
),
6668
persistentLogger
6769
).left.map((_, Scope.Main))
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package scala.build.internal
2+
3+
import scala.build.errors.BuildException
4+
5+
/** Helper to get class names from Java sources
6+
*
7+
* See [[JavaParserProxyJvm]] for the implementation that runs things in memory using
8+
* java-class-name from the class path, and [[JavaParserProxyBinary]] for the implementation that
9+
* downloads and runs a java-class-name binary.
10+
*/
11+
trait JavaParserProxy {
12+
13+
/** Extracts the class name of a Java source, using the dotty Java parser.
14+
*
15+
* @param content
16+
* the Java source to extract a class name from
17+
* @return
18+
* either some class name (if one was found) or none (if none was found), or a
19+
* [[BuildException]]
20+
*/
21+
def className(content: Array[Byte]): Either[BuildException, Option[String]]
22+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package scala.build.internal
2+
3+
import coursier.cache.ArchiveCache
4+
import coursier.util.Task
5+
6+
import scala.build.EitherCps.{either, value}
7+
import scala.build.Logger
8+
import scala.build.errors.BuildException
9+
import scala.util.Properties
10+
11+
/** Downloads and runs java-class-name as an external binary. */
12+
class JavaParserProxyBinary(
13+
archiveCache: ArchiveCache[Task],
14+
javaClassNameVersionOpt: Option[String],
15+
logger: Logger
16+
) extends JavaParserProxy {
17+
18+
/** For internal use only
19+
*
20+
* Passing archiveCache as an Object, to work around issues with higher-kind type params from
21+
* Java code.
22+
*/
23+
def this(
24+
archiveCache: Object,
25+
logger: Logger,
26+
javaClassNameVersionOpt: Option[String]
27+
) =
28+
this(archiveCache.asInstanceOf[ArchiveCache[Task]], javaClassNameVersionOpt, logger)
29+
30+
def className(content: Array[Byte]): Either[BuildException, Option[String]] = either {
31+
32+
val platformSuffix = FetchExternalBinary.platformSuffix()
33+
val version = javaClassNameVersionOpt.getOrElse(Constants.javaClassNameVersion)
34+
val (tag, changing) =
35+
if (version == "latest") ("nightly", true)
36+
else ("v" + version, false)
37+
val ext = if (Properties.isWin) ".zip" else ".gz"
38+
val url =
39+
s"https://github.com/scala-cli/java-class-name/releases/download/$tag/java-class-name-$platformSuffix$ext"
40+
41+
val binary =
42+
value(FetchExternalBinary.fetch(url, changing, archiveCache, logger, "java-class-name"))
43+
44+
val source =
45+
os.temp(content, suffix = ".java", perms = if (Properties.isWin) null else "rw-------")
46+
val output =
47+
try {
48+
logger.debug(s"Running $binary $source")
49+
val res = os.proc(binary, source).call()
50+
res.out.text().trim
51+
}
52+
finally os.remove(source)
53+
54+
if (output.isEmpty) None
55+
else Some(output)
56+
}
57+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package scala.build.internal
2+
3+
import scala.build.errors.BuildException
4+
import scala.cli.javaclassname.JavaParser
5+
6+
/** A [[JavaParserProxy]] that relies on java-class-name in the class path, rather than downloading
7+
* it and running it as an external binary.
8+
*
9+
* Should be used from Scala CLI when it's run on the JVM.
10+
*/
11+
class JavaParserProxyJvm extends JavaParserProxy {
12+
override def className(content: Array[Byte]): Either[BuildException, Option[String]] =
13+
Right(JavaParser.parseRootPublicClassName(content))
14+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package scala.build.internal
2+
3+
import scala.build.Logger
4+
5+
/** On the JVM, provides [[JavaParserProxyJvm]] as [[JavaParserProxy]] instance.
6+
*
7+
* From native launchers, [[JavaParserProxyMakerSubst]] takes over this, and gives
8+
* [[JavaParserProxyBinary]] instead.
9+
*
10+
* That way, no reference to [[JavaParserProxyJvm]] remains in the native call graph, and that
11+
* class and those it pulls (the java-class-name classes, which includes parts of the dotty parser)
12+
* are not embedded the native launcher.
13+
*
14+
* Note that this is a class and not an object, to make it easier to write substitutions for that
15+
* in Java.
16+
*/
17+
class JavaParserProxyMaker {
18+
def get(
19+
archiveCache: Object, // Actually a ArchiveCache[Task], but having issues with the higher-kind type param from Java…
20+
javaClassNameVersionOpt: Option[String],
21+
logger: Logger
22+
): JavaParserProxy =
23+
new JavaParserProxyJvm
24+
}

0 commit comments

Comments
 (0)