[
https://issues.apache.org/jira/browse/GROOVY-12751?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18141329#comment-18141329
]
ASF GitHub Bot commented on GROOVY-12751:
-----------------------------------------
daniellansun opened a new pull request, #3284:
URL: https://github.com/apache/groovy/pull/3284
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
# PR title
GROOVY-12751: publish in-memory class output to a later compilation
# PR body
GROOVY-12711 keeps javac `CLASS_OUTPUT` in memory for one
`JavaAwareCompilationUnit`. A later unit on an ordinary loader still does not
see that output. This adds a publication for embedders that compile more than
once and want an earlier compilation's committed classes to stay visible,
without appending a URL to a shared loader.
`groovyc` and the Ant task already build a new loader per run. Ant's
`includeDestClasses` keeps earlier class files visible by putting the
destination directory on the classpath; the next run still uses a new loader,
and a class file that run does not write stays in the directory. `parseClass`
defines each parse on a new `InnerLoader` whose `getResource` and `addURL`
delegate to the long-lived outer loader, so a URL added on the inner loader
lands on the outer one. groovy-lsp reuses one loader and deletes the previous
javac directory, so overlapping names are a new snapshot rather than a merge.
None of those hosts turns `memClassOutput` on for a series of units, and this
change does not turn it on for them.
`MemClassOutputPublication` is the object the embedder keeps. The loader
passed to it is the long-lived parent: dependencies and any classpath already
installed there. `newCompilationLoader()` returns a child for one
`JavaAwareCompilationUnit`. The child's in-memory URL is appended when that
unit compiles, on the child only, after classpath entries the configuration or
`configure` have added. Parent-first search is unchanged, so a parent class or
an earlier URL still hides the in-memory bytes. The parent is not given a URL
and does not define the compiled classes. `loadClass` on a loader that has
already defined a class does not observe newer bytes; load the recompiled class
from the child that compiled it.
`publish(child)` copies classes that compilation has committed. A later
child resolves a name from its own stage, then from its own committed output,
then from what has been published. An overlapping name uses this compilation. A
name it does not produce stays published. That is parent-visible earlier
output, not a recompile of the union of sources. Publishing is explicit, so a
host whose next compile replaces the previous snapshot simply does not publish.
A failed compile discards its stage, and a compile that has not reached output
has nothing committed to publish. One child serves one compilation unit. The
same unit may compile again. A second unit on that child is rejected.
A single unit on an ordinary `GroovyClassLoader` behaves as in GROOVY-12711:
one new store and one URL per `open`, with no publication behind it. There is
no loader-identity cache. The `x-groovy-mem` handler stays unregistered, and
paths that escape the map are rejected before any published bytes are read.
Depends on #3242 (GROOVY-12711). Stacked on tip `1a509dc4a053`.
> 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
> Priority: Major
>
> 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)