Skip to content

Centralize JAR handling - #26331

Merged
SolalPirelli merged 17 commits into
scala:mainfrom
dotty-staging:solal/jar
Jun 22, 2026
Merged

Centralize JAR handling#26331
SolalPirelli merged 17 commits into
scala:mainfrom
dotty-staging:solal/jar

Conversation

@SolalPirelli

@SolalPirelli SolalPirelli commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Prep for #26232
Part of #26492

Delete lots of dead code, remove some of our JAR "abstractions".

This leaves JarArchive and ZipArchive, the latter of which handles multi-release JARs. I'll move JAR handling to the former in a future PR.

How much have you relied on LLM-based tools in this contribution?

Not at all

How was the solution tested?

Covered by existing tests (this is a refactoring)

for
file <- expandPath(path, expandStar = false)
dir <- Option(AbstractFile.getDirectory(file))
dir <- Option(AbstractFile.getDirectory(file, ctx.settings.javaOutputVersion.value))

@SolalPirelli SolalPirelli Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

theme 1: it's no longer optional to pass a version when (potentially) dealing with JARs through this API

(and yes it's weird that getDirectory also handles ZIPs and JARs... but I'm not sure how easy it would be to avoid that)

file.exists && (file.ext.isTasty || (file.ext.isClass && !file.hasSiblingTasty))
}

/**

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

theme 2: delete lots of dead code

val jar = JarArchive.open(Path(arg), create = false)
def tastyFiles(file: AbstractFile): Iterator[AbstractFile] =
if file.isDirectory then file.iterator.flatMap(tastyFiles)
else if file.ext.isTasty then Iterator.single(file) else Iterator.empty

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TASTYRun also needed this so I centralized deepIterator into AbstractFile

if (is == null) throw new PluginLoadException(jarp.path, s"Missing $PluginFile in $jarp")
else fromFile(is, jarp)

val fileEntry = new java.util.jar.JarEntry(PluginFile)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

theme 3: go through our abstractions for JARs (so we can enforce the use of a version... JarArchive doesn't do that yet)

new JarArchive(path, Directory(root))
}

// See http://download.java.net/jdk7/docs/api/java/nio/file/Path.html

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this stuff here is moved from deleted classes

try Files.isDirectory(jpath)
catch { case ex: SecurityException => jpath.toString == "." }
def isAbsolute: Boolean = jpath.isAbsolute()
def isEmpty: Boolean = path.length == 0

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this one was just evil... directory.isEmpty doesn't check what one would assume it does

def jpath: JPath | Null = null
def input: InputStream = throw UnsupportedOperationException("NoAbstractFile.input")
def isDirectory: Boolean = false
override def absolute: AbstractFile = this

@SolalPirelli SolalPirelli Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

theme 4: add overrides so we can detect dead code in subclasses easily

@SolalPirelli SolalPirelli added the needs-squashing PR whose commits should be squashed by the author or via the "Squash and Merge" button label Jun 15, 2026

def readRun(using ctx: Context): ReadOnlyRun = new:
val suspendedAtTyperPhase = ctx.run.nn.suspendedAtTyperPhase
val suspendedAtTyperPhase = ctx.run != null && ctx.run.nn.suspendedAtTyperPhase

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so it can be used in scripting where we don't have a Run but need to eagerly evaluate this; this pointless abstraction is going away in #26236 anyway

@SolalPirelli
SolalPirelli marked this pull request as ready for review June 16, 2026 14:08
@SolalPirelli

This comment was marked as outdated.

@tgodzik tgodzik left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@lrytz lrytz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

super nice cleanup!

Comment thread compiler/src/dotty/tools/io/AbstractFile.scala Outdated

def create(zipFile: AbstractFile)(using Context): ClassPath =
val release = Option(ctx.settings.javaOutputVersion.value).filter(_.nonEmpty)
val release = ctx.settings.javaOutputVersion.value

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If -java-output-version is not set this is now "", it used to be None. So we pass Some("") to FileZipArchive and we end up in the other branch here

    release match {
      case Some(r) if file.nn.getName.endsWith(".jar") =>
        new JarFile(file, true, ZipFile.OPEN_READ, if r == "" then Runtime.version() else Runtime.Version.parse(r))
      case _ =>
        new ZipFile(file)
    }

The new ZipFile branch is no longer used for jars.

Multi-release Jars are now used by default, and the version the compiler sees depends on the JDK version being used in compilation.

Is all of that intended?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, for #26232 and more generally, I don't think we should ever not use a version-specific file if we can?

In my view "not specifying a target release" == "using the current JDK".

Also, I can imagine library authors getting very confusing bug reports if someone uses their library in a way that javac doesn't and that doesn't immediately make sense (using JDK N but a classfile meant for JDK <N).

Comment thread compiler/src/dotty/tools/scripting/Main.scala Outdated
@SolalPirelli
SolalPirelli merged commit 823d107 into scala:main Jun 22, 2026
45 checks passed
@SolalPirelli
SolalPirelli deleted the solal/jar branch June 22, 2026 08:49
@WojciechMazur WojciechMazur added this to the 3.10.0 milestone Aug 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-squashing PR whose commits should be squashed by the author or via the "Squash and Merge" button

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants