Daniel Sun created GROOVY-12751:
-----------------------------------

             Summary: In-memory javac class output across compilation units 
(follow-up to GROOVY-12711)
                 Key: GROOVY-12751
                 URL: https://issues.apache.org/jira/browse/GROOVY-12751
             Project: Groovy
          Issue Type: Improvement
          Components: Compiler
            Reporter: Daniel Sun


h3. Background

GROOVY-12711 ([PR #3242|https://github.com/apache/groovy/pull/3242]) adds the 
joint-compilation option {{memClassOutput}} 
({{CompilerConfiguration.MEM_CLASS_OUTPUT}}). Together with {{memStub}}, it 
keeps javac's {{CLASS_OUTPUT}} in memory, so a {{JavaAwareCompilationUnit}} 
needs neither a stub directory nor a target directory on disk.

In that change, the in-memory store is owned by a single compilation unit. An 
earlier revision also kept a map from {{GroovyClassLoader}} to store, so that a 
second compilation unit created on the same loader could see the classes 
published by the first. That map was removed during review: the only scenario 
it served was a test that deliberately built two units on one loader, and none 
of the normal entry points ({{groovyc}}, the Ant task, 
{{GroovyClassLoader#parseClass}}, groovy-lsp) actually reaches it.

The underlying question is still a fair one, though, and deserves to be looked 
at on its own merits: when a host keeps a long-lived class loader and compiles 
more than once, how should in-memory class output from earlier compilations be 
made visible to later ones?

h3. Goal

Define, and if justified implement, a publication rule for in-memory class 
output that spans compilation units, driven by how real hosts use Groovy's 
existing APIs rather than by a hand-built test scenario.

Concretely, the outcome should answer:
* Which loader is long-lived, and which loader defines the classes of each 
compilation?
* Where must the in-memory bytes live so that the loader actually searched by 
{{getResource}} and {{loadClass}} can find them?
* How do overlapping class names behave when a later compilation redefines a 
class that an earlier one already produced?

h3. Facts any design has to respect

* {{URLClassLoader}} cannot remove a URL, and {{getResource}} returns the first 
match in parent-first, insertion order. Appending one more in-memory URL per 
compilation to a long-lived loader therefore lets the *oldest* bytes win for 
overlapping names. That cannot serve as an update mechanism.
* A class that a loader has already defined cannot be replaced. Recompiling a 
loaded class always requires a new defining loader.
* {{GroovyClassLoader#parseClass}} already separates the two roles: the outer 
loader is long-lived, while each parse defines its classes on a fresh, 
short-lived {{InnerLoader}} whose {{getResource}} and {{addURL}} delegate to 
the outer loader. Joint compilation does not use this split today. A map keyed 
on the compilation unit's loader does not follow it either.
* groovy-lsp reuses one loader across compilation units while the compiler 
settings are unchanged, and its recompiles usually overlap the previous ones. 
It sets {{memStub}} but still writes javac output to a temporary directory. If 
it ever adopts {{memClassOutput}}, overlapping names will be the common case, 
not the exception.
* The Ant task's incremental mode keeps earlier classes visible by putting the 
destination directory on the classpath ({{includeDestClasses}}). An in-memory 
equivalent has to state whether it means parent delegation to earlier output, 
or a fresh compilation of the union of sources.

h3. Proposed approach

# Pick one or more concrete hosts (for example an embedder built on 
{{parseClass}}, or groovy-lsp) and describe its loader lifetime through 
existing, public API only.
# Follow the {{parseClass}} shape: one long-lived parent that holds previously 
published output only when the host asks for it, plus a new child loader per 
compilation that defines that compilation's classes and is searched first.
# Put the in-memory bytes on the loader that the child actually searches, 
rather than appending a sticky URL to a shared loader.
# Specify what happens for overlapping names and for classes that are already 
defined, and do not suggest that {{loadClass}} on a loader that already defined 
a class will ever see new bytes.
# Cover it with tests that go through the normal API 
({{CompilerConfiguration}}, {{JavaAwareCompilationUnit}}, 
{{GroovyClassLoader}}), not with two units manually attached to one loader.

h3. Out of scope

* Enabling {{memClassOutput}} inside {{parseClass}} or groovy-lsp. Those are 
potential consumers of whatever rule this issue settles, and can be changed 
separately.
* Any change to the single-unit behaviour delivered by GROOVY-12711.



--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

Reply via email to