Skip to content

Commit f8a521f

Browse files
authored
Implement :jar (deprecate :require) (scala#22343)
scala#21658
2 parents 8a99f9f + 42c4e89 commit f8a521f

File tree

14 files changed

+189
-6
lines changed

14 files changed

+189
-6
lines changed

compiler/src/dotty/tools/dotc/config/JavaPlatform.scala

+7
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ class JavaPlatform extends Platform {
2727
case _ => false
2828
})
2929

30+
def addToClassPath(cPath: ClassPath)(using Context): Unit = classPath match {
31+
case AggregateClassPath(entries) =>
32+
currentClassPath = Some(AggregateClassPath(entries :+ cPath))
33+
case cp: ClassPath =>
34+
currentClassPath = Some(AggregateClassPath(cp :: cPath :: Nil))
35+
}
36+
3037
/** Update classpath with a substituted subentry */
3138
def updateClassPath(subst: Map[ClassPath, ClassPath]): Unit = currentClassPath.get match {
3239
case AggregateClassPath(entries) =>

compiler/src/dotty/tools/dotc/config/Platform.scala

+3
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ abstract class Platform {
2121
/** Update classpath with a substitution that maps entries to entries */
2222
def updateClassPath(subst: Map[ClassPath, ClassPath]): Unit
2323

24+
/** Add new entry to classpath */
25+
def addToClassPath(cPath: ClassPath)(using Context): Unit
26+
2427
/** Any platform-specific phases. */
2528
//def platformPhases: List[SubComponent]
2629

compiler/src/dotty/tools/dotc/core/SymbolLoaders.scala

+28-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import java.nio.channels.ClosedByInterruptException
77

88
import scala.util.control.NonFatal
99

10+
import dotty.tools.dotc.classpath.{ ClassPathFactory, PackageNameUtils }
1011
import dotty.tools.dotc.classpath.FileUtils.{hasTastyExtension, hasBetastyExtension}
1112
import dotty.tools.io.{ ClassPath, ClassRepresentation, AbstractFile, NoAbstractFile }
1213
import dotty.tools.backend.jvm.DottyBackendInterface.symExtensions
@@ -272,7 +273,7 @@ object SymbolLoaders {
272273
def maybeModuleClass(classRep: ClassRepresentation): Boolean =
273274
classRep.name.nonEmpty && classRep.name.last == '$'
274275

275-
private def enterClasses(root: SymDenotation, packageName: String, flat: Boolean)(using Context) = {
276+
def enterClasses(root: SymDenotation, packageName: String, flat: Boolean)(using Context) = {
276277
def isAbsent(classRep: ClassRepresentation) =
277278
!root.unforcedDecls.lookup(classRep.name.toTypeName).exists
278279

@@ -316,6 +317,32 @@ object SymbolLoaders {
316317
}
317318
}
318319
}
320+
321+
def mergeNewEntries(
322+
packageClass: ClassSymbol, fullPackageName: String,
323+
jarClasspath: ClassPath, fullClasspath: ClassPath,
324+
)(using Context): Unit =
325+
if jarClasspath.classes(fullPackageName).nonEmpty then
326+
// if the package contains classes in jarClasspath, the package is invalidated (or removed if there are no more classes in it)
327+
val packageVal = packageClass.sourceModule.asInstanceOf[TermSymbol]
328+
if packageClass.isRoot then
329+
val loader = new PackageLoader(packageVal, fullClasspath)
330+
loader.enterClasses(defn.EmptyPackageClass, fullPackageName, flat = false)
331+
loader.enterClasses(defn.EmptyPackageClass, fullPackageName, flat = true)
332+
else if packageClass.ownersIterator.contains(defn.ScalaPackageClass) then
333+
() // skip
334+
else if fullClasspath.hasPackage(fullPackageName) then
335+
packageClass.info = new PackageLoader(packageVal, fullClasspath)
336+
else
337+
packageClass.owner.info.decls.openForMutations.unlink(packageVal)
338+
else
339+
for p <- jarClasspath.packages(fullPackageName) do
340+
val subPackageName = PackageNameUtils.separatePkgAndClassNames(p.name)._2.toTermName
341+
val subPackage = packageClass.info.decl(subPackageName).orElse:
342+
// package does not exist in symbol table, create a new symbol
343+
enterPackage(packageClass, subPackageName, (module, modcls) => new PackageLoader(module, fullClasspath))
344+
mergeNewEntries(subPackage.asSymDenotation.moduleClass.asClass, p.name, jarClasspath, fullClasspath)
345+
end mergeNewEntries
319346
}
320347

321348
/** A lazy type that completes itself by calling parameter doComplete.

compiler/src/dotty/tools/repl/ParseResult.scala

+16
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,20 @@ object Load {
5252
val command: String = ":load"
5353
}
5454

55+
/** `:require` is a deprecated alias for :jar`
56+
*/
57+
case class Require(path: String) extends Command
58+
object Require {
59+
val command: String = ":require"
60+
}
61+
62+
/** `:jar <path>` adds a jar to the classpath
63+
*/
64+
case class JarCmd(path: String) extends Command
65+
object JarCmd {
66+
val command: String = ":jar"
67+
}
68+
5569
/** `:kind <type>` display the kind of a type. see also :help kind
5670
*/
5771
case class KindOf(expr: String) extends Command
@@ -151,8 +165,10 @@ object ParseResult {
151165
Help.command -> (_ => Help),
152166
Reset.command -> (arg => Reset(arg)),
153167
Imports.command -> (_ => Imports),
168+
JarCmd.command -> (arg => JarCmd(arg)),
154169
KindOf.command -> (arg => KindOf(arg)),
155170
Load.command -> (arg => Load(arg)),
171+
Require.command -> (arg => Require(arg)),
156172
TypeOf.command -> (arg => TypeOf(arg)),
157173
DocOf.command -> (arg => DocOf(arg)),
158174
Settings.command -> (arg => Settings(arg)),

compiler/src/dotty/tools/repl/ReplDriver.scala

+59-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import java.nio.charset.StandardCharsets
77

88
import dotty.tools.dotc.ast.Trees.*
99
import dotty.tools.dotc.ast.{tpd, untpd}
10+
import dotty.tools.dotc.classpath.ClassPathFactory
1011
import dotty.tools.dotc.config.CommandLineParser.tokenize
1112
import dotty.tools.dotc.config.Properties.{javaVersion, javaVmName, simpleVersionString}
1213
import dotty.tools.dotc.core.Contexts.*
@@ -21,6 +22,7 @@ import dotty.tools.dotc.core.NameOps.*
2122
import dotty.tools.dotc.core.Names.Name
2223
import dotty.tools.dotc.core.StdNames.*
2324
import dotty.tools.dotc.core.Symbols.{Symbol, defn}
25+
import dotty.tools.dotc.core.SymbolLoaders
2426
import dotty.tools.dotc.interfaces
2527
import dotty.tools.dotc.interactive.Completion
2628
import dotty.tools.dotc.printing.SyntaxHighlighting
@@ -39,6 +41,7 @@ import scala.annotation.tailrec
3941
import scala.collection.mutable
4042
import scala.compiletime.uninitialized
4143
import scala.jdk.CollectionConverters.*
44+
import scala.tools.asm.ClassReader
4245
import scala.util.control.NonFatal
4346
import scala.util.Using
4447

@@ -512,10 +515,65 @@ class ReplDriver(settings: Array[String],
512515
state
513516
}
514517

518+
case Require(path) =>
519+
out.println(":require is no longer supported, but has been replaced with :jar. Please use :jar")
520+
state
521+
522+
case JarCmd(path) =>
523+
val jarFile = AbstractFile.getDirectory(path)
524+
if (jarFile == null)
525+
out.println(s"""Cannot add "$path" to classpath.""")
526+
state
527+
else
528+
def flatten(f: AbstractFile): Iterator[AbstractFile] =
529+
if (f.isClassContainer) f.iterator.flatMap(flatten)
530+
else Iterator(f)
531+
532+
def tryClassLoad(classFile: AbstractFile): Option[String] = {
533+
val input = classFile.input
534+
try {
535+
val reader = new ClassReader(input)
536+
val clsName = reader.getClassName.replace('/', '.')
537+
rendering.myClassLoader.loadClass(clsName)
538+
Some(clsName)
539+
} catch
540+
case _: ClassNotFoundException => None
541+
finally {
542+
input.close()
543+
}
544+
}
545+
546+
try {
547+
val entries = flatten(jarFile)
548+
549+
val existingClass = entries.filter(_.ext.isClass).find(tryClassLoad(_).isDefined)
550+
if (existingClass.nonEmpty)
551+
out.println(s"The path '$path' cannot be loaded, it contains a classfile that already exists on the classpath: ${existingClass.get}")
552+
else inContext(state.context):
553+
val jarClassPath = ClassPathFactory.newClassPath(jarFile)
554+
val prevOutputDir = ctx.settings.outputDir.value
555+
556+
// add to compiler class path
557+
ctx.platform.addToClassPath(jarClassPath)
558+
SymbolLoaders.mergeNewEntries(defn.RootClass, ClassPath.RootPackage, jarClassPath, ctx.platform.classPath)
559+
560+
// new class loader with previous output dir and specified jar
561+
val prevClassLoader = rendering.classLoader()
562+
val jarClassLoader = fromURLsParallelCapable(
563+
jarClassPath.asURLs, prevClassLoader)
564+
rendering.myClassLoader = new AbstractFileClassLoader(
565+
prevOutputDir, jarClassLoader)
566+
567+
out.println(s"Added '$path' to classpath.")
568+
} catch {
569+
case e: Throwable =>
570+
out.println(s"Failed to load '$path' to classpath: ${e.getMessage}")
571+
}
572+
state
573+
515574
case KindOf(expr) =>
516575
out.println(s"""The :kind command is not currently supported.""")
517576
state
518-
519577
case TypeOf(expr) =>
520578
expr match {
521579
case "" => out.println(s":type <expression>")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/**
2+
* JAR used for testing repl :jar
3+
* Generated using: mkdir out; scalac -d out MyLibrary.scala; jar cf mylibrary.jar -C out .
4+
*/
5+
package mylibrary
6+
7+
object Utils:
8+
def greet(name: String): String = s"Hello, $name!"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* JAR used for testing repl :jar
3+
* Generated using: mkdir out2; scalac -d out MyLibrary2.scala; jar cf mylibrary2.jar -C out2 .
4+
*/
5+
package mylibrary2
6+
7+
object Utils2:
8+
def greet(name: String): String = s"Greetings, $name!"
9+
2.25 KB
Binary file not shown.
2.28 KB
Binary file not shown.
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
scala> val z = 1
2+
val z: Int = 1
3+
4+
scala>:jar sbt-test/source-dependencies/canon/actual/a.jar
5+
Added 'sbt-test/source-dependencies/canon/actual/a.jar' to classpath.
6+
7+
scala> import A.x
8+
9+
scala> x
10+
val res0: Int = 3
11+
12+
scala> z
13+
val res1: Int = 1
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
scala>:jar path/does/not/exist
2+
Cannot add "path/does/not/exist" to classpath.
3+
4+
scala>:jar sbt-test/source-dependencies/canon/actual/a.jar
5+
Added 'sbt-test/source-dependencies/canon/actual/a.jar' to classpath.
6+
7+
scala>:jar sbt-test/source-dependencies/canon/actual/a.jar
8+
The path 'sbt-test/source-dependencies/canon/actual/a.jar' cannot be loaded, it contains a classfile that already exists on the classpath: sbt-test/source-dependencies/canon/actual/a.jar(A.class)
9+
10+
scala>:require sbt-test/source-dependencies/canon/actual/a.jar
11+
:require is no longer supported, but has been replaced with :jar. Please use :jar
+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
scala> val z = 1
2+
val z: Int = 1
3+
4+
scala>:jar compiler/test-resources/jars/mylibrary.jar
5+
Added 'compiler/test-resources/jars/mylibrary.jar' to classpath.
6+
7+
scala> import mylibrary.Utils
8+
9+
scala> Utils.greet("Alice")
10+
val res0: String = Hello, Alice!
11+
12+
scala>:jar compiler/test-resources/jars/mylibrary2.jar
13+
Added 'compiler/test-resources/jars/mylibrary2.jar' to classpath.
14+
15+
scala> import mylibrary2.Utils2
16+
17+
scala> Utils2.greet("Alice")
18+
val res1: String = Greetings, Alice!
19+
20+
scala> Utils.greet("Alice")
21+
val res2: String = Hello, Alice!
22+
23+
scala> import mylibrary.Utils.greet
24+
25+
scala> greet("Tom")
26+
val res3: String = Hello, Tom!
27+
28+
scala> Utils.greet("Alice")
29+
val res4: String = Hello, Alice!
30+
31+
scala> z
32+
val res5: Int = 1

compiler/test/dotty/tools/repl/ReplTest.scala

+1-4
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,7 @@ extends ReplDriver(options, new PrintStream(out, true, StandardCharsets.UTF_8.na
9898
FileDiff.dump(checkFile.toPath.toString, actualOutput)
9999
println(s"Wrote updated script file to $checkFile")
100100
else
101-
println("expected =========>")
102-
println(expectedOutput.mkString(EOL))
103-
println("actual ===========>")
104-
println(actualOutput.mkString(EOL))
101+
println(dotc.util.DiffUtil.mkColoredHorizontalLineDiff(actualOutput.mkString(EOL), expectedOutput.mkString(EOL)))
105102

106103
fail(s"Error in script $name, expected output did not match actual")
107104
end if

compiler/test/dotty/tools/repl/TabcompleteTests.scala

+2
Original file line numberDiff line numberDiff line change
@@ -213,9 +213,11 @@ class TabcompleteTests extends ReplTest {
213213
":exit",
214214
":help",
215215
":imports",
216+
":jar",
216217
":kind",
217218
":load",
218219
":quit",
220+
":require",
219221
":reset",
220222
":settings",
221223
":sh",

0 commit comments

Comments
 (0)