From 0d9356d6837ff8f1ad50d4d8827e301adc7503f5 Mon Sep 17 00:00:00 2001 From: David Schachter Date: Thu, 6 Aug 2026 15:46:25 -0700 Subject: [PATCH 1/6] ADFA-5052: Defer JavaCompilerService/SourceFileManager construction until a real .java file is touched DefaultLanguageServerRegistry.onProjectInitialized dispatches setupWithProject to every registered language server unconditionally, regardless of project language. JavaLanguageServer.setupWithProject() referenced JavaCompilerService.NO_MODULE_COMPILER and called SourceFileManager.clearCache(), both of which trigger class-init that eagerly constructs real javac Context/ JavacFileManager machinery plus a full android.jar top-level-class scan -- on the first project open in the app's lifetime, Kotlin-only projects included. shutdown() had the same problem in reverse, on every project close. Same eager-load bug pattern ADFA-5010 fixed for the Kotlin Analysis API, and independently confirmed and sized (openjdk.tools.javac ~2,238 classes, ~3.7MB) while researching whether javac could get ADFA-5010's carrier-APK treatment. This fix is scoped to just the eager-construction bug -- no DexClassLoader/ carrier-APK split; javac/jdk-compiler stay in the main dex, just constructed lazily. setupWithProject() now only stashes the workspace; the actual reset (destroy NO_MODULE_COMPILER, clear file-manager/JAR-fs caches, index module classpaths) is deferred to ensureProjectReset(), called from getCompiler() and onContentChange() -- both already gated on DocumentUtils.isJavaFile(), so this now only runs on genuine Java-file interaction. shutdown() skips its javac-specific cleanup entirely if that never happened. Per-file LSP dispatch methods (complete/findReferences/findDefinition/expandSelection/signatureHelp) needed no changes: the editor's IDELanguage already resolves one language server per file before calling any of them, so they were never the source of the cross-language trigger. Verified: :lsp:java unit tests pass, :app:assembleV8Debug builds clean. --- .../androidide/lsp/java/JavaLanguageServer.kt | 65 +++++++++++++++---- 1 file changed, 51 insertions(+), 14 deletions(-) diff --git a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/JavaLanguageServer.kt b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/JavaLanguageServer.kt index fc94ebbc84..f8af6393f2 100644 --- a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/JavaLanguageServer.kt +++ b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/JavaLanguageServer.kt @@ -96,6 +96,15 @@ class JavaLanguageServer : ILanguageServer { private val timer = AnalyzeTimer { analyzeSelected() } private var cachedCompletion: CachedCompletion + // Set by setupWithProject(), consumed by ensureProjectReset() on the first real .java-file + // interaction after it -- deferred because setupWithProject() is called for every project + // open regardless of language (ADFA-5052). + @Volatile + private var pendingWorkspace: Workspace? = null + + @Volatile + private var javaCompilerInitialized = false + val settings: IServerSettings get() { return _settings ?: JavaServerSettings @@ -123,10 +132,10 @@ class JavaLanguageServer : ILanguageServer { val projectManager = ProjectManagerImpl.getInstance() projectManager.indexingServiceManager.register( - service = JvmLibraryIndexingService(context = BaseApplication.baseInstance) + service = JvmLibraryIndexingService(context = BaseApplication.baseInstance), ) projectManager.indexingServiceManager.register( - service = JvmGeneratedIndexingService(context = BaseApplication.baseInstance) + service = JvmGeneratedIndexingService(context = BaseApplication.baseInstance), ) JavaSnippetRepository.init() @@ -134,10 +143,12 @@ class JavaLanguageServer : ILanguageServer { override fun shutdown() { (this.debugAdapter as? AutoCloseable?)?.close() - JavaCompilerProvider.getInstance().destroy() - SourceFileManager.clearCache() - CacheFSInfoSingleton.clearCache() - clearCache() + if (javaCompilerInitialized) { + JavaCompilerProvider.getInstance().destroy() + SourceFileManager.clearCache() + CacheFSInfoSingleton.clearCache() + clearCache() + } EventBus.getDefault().unregister(this) timer.cancel() } @@ -163,10 +174,35 @@ class JavaLanguageServer : ILanguageServer { override fun setupWithProject(workspace: Workspace) { LSPEditorActions.ensureActionsMenuRegistered(JavaCodeActionsMenu) - (ProjectManagerImpl.getInstance() - .indexingServiceManager - .getService(JvmLibraryIndexingService.ID) as? JvmLibraryIndexingService?) - ?.refresh() + ( + ProjectManagerImpl + .getInstance() + .indexingServiceManager + .getService(JvmLibraryIndexingService.ID) as? JvmLibraryIndexingService? + )?.refresh() + + // Deferred to ensureProjectReset(), run on the first real .java-file interaction instead + // of here -- this method runs for every project open regardless of language + // (DefaultLanguageServerRegistry dispatches to all registered servers unconditionally), + // and JavaCompilerService.NO_MODULE_COMPILER / SourceFileManager.NO_MODULE eagerly + // construct real javac machinery plus a full android.jar scan at class-init, merely by + // being referenced (ADFA-5052, mirrors ADFA-5010's KotlinLanguageServer fix). + pendingWorkspace = workspace + } + + /** + * Runs the javac-specific project reset deferred by [setupWithProject], for the most + * recently opened project, the first time a real Java file is actually interacted with. + * No-ops if already up to date. + */ + private fun ensureProjectReset() { + if (pendingWorkspace == null) return + val workspace: Workspace + synchronized(this) { + workspace = pendingWorkspace ?: return + pendingWorkspace = null + javaCompilerInitialized = true + } // Once we have project initialized // Destory the NO_MODULE_COMPILER instance @@ -196,8 +232,7 @@ class JavaLanguageServer : ILanguageServer { override fun complete(params: CompletionParams?): CompletionResult { val compiler = getCompiler(params!!.file) - if (!settings.completionsEnabled() || !completionProvider.canComplete(params.file) - ) { + if (!settings.completionsEnabled() || !completionProvider.canComplete(params.file)) { return CompletionResult.EMPTY } @@ -265,8 +300,7 @@ class JavaLanguageServer : ILanguageServer { } } - override fun formatCode(params: FormatCodeParams?): CodeFormatResult = - CodeFormatProvider(settings).format(params) + override fun formatCode(params: FormatCodeParams?): CodeFormatResult = CodeFormatProvider(settings).format(params) override fun handleFailure(failure: LSPFailure?): Boolean { return when (failure!!.type) { @@ -285,6 +319,7 @@ class JavaLanguageServer : ILanguageServer { if (!DocumentUtils.isJavaFile(file)) { return JavaCompilerService.NO_MODULE_COMPILER } + ensureProjectReset() val module = ProjectManagerImpl.getInstance().findModuleForFile(file!!) ?: return JavaCompilerService.NO_MODULE_COMPILER @@ -314,6 +349,8 @@ class JavaLanguageServer : ILanguageServer { return } + ensureProjectReset() + // TODO Find an alternative to efficiently update changeDelta in JavaCompilerService instance JavaCompilerService.NO_MODULE_COMPILER.onDocumentChange(event) val module = From ad09be1372d13c91a2a8bd53c32b7aa14b503e6e Mon Sep 17 00:00:00 2001 From: David Schachter Date: Thu, 6 Aug 2026 16:23:14 -0700 Subject: [PATCH 2/6] ADFA-5052: Serialize the deferred javac reset against concurrent access and shutdown The previous fix's synchronized(this) block only guarded the *decision* to run ensureProjectReset() (claiming pendingWorkspace, flipping javaCompilerInitialized to true) -- not the destroy/rebuild work that followed, which ran unsynchronized. A concurrent getCompiler()/ onContentChange() call on another thread could see javaCompilerInitialized already true and proceed to use JavaCompilerProvider/SourceFileManager while the first thread was still mid-destroy or mid-rebuild. shutdown() didn't synchronize on anything at all, so it could run its own destroy()/ clearCache() concurrently with an in-flight reset, racing two teardown/ rebuild sequences against each other. Replaces the two ad-hoc @Volatile fields with an explicit PENDING/RESETTING/INITIALIZED/SHUTDOWN state machine guarded by a single ReentrantLock (compilerLifecycleLock) held for the *entire* reset or shutdown, not just the state transition. Concurrent callers now genuinely block until an in-flight reset finishes (getCompiler()/onContentChange() already route through ensureProjectReset(), which now can't return early while another thread holds the lock), and shutdown() waits on the same lock before deciding whether there's anything to tear down. setupWithProject() also goes through the lock; if a new project arrives mid-reset, the in-progress reset's own finally block detects the newer pendingWorkspace and reverts to PENDING instead of incorrectly claiming INITIALIZED. Verified: :lsp:java unit tests pass, :app:assembleV8Debug builds clean. --- .../androidide/lsp/java/JavaLanguageServer.kt | 109 ++++++++++++------ 1 file changed, 71 insertions(+), 38 deletions(-) diff --git a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/JavaLanguageServer.kt b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/JavaLanguageServer.kt index f8af6393f2..763080fc49 100644 --- a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/JavaLanguageServer.kt +++ b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/JavaLanguageServer.kt @@ -84,6 +84,8 @@ import org.slf4j.LoggerFactory import java.nio.file.Files import java.nio.file.Path import java.util.Objects +import java.util.concurrent.locks.ReentrantLock +import kotlin.concurrent.withLock class JavaLanguageServer : ILanguageServer { private val completionProvider: CompletionProvider = CompletionProvider() @@ -96,14 +98,19 @@ class JavaLanguageServer : ILanguageServer { private val timer = AnalyzeTimer { analyzeSelected() } private var cachedCompletion: CachedCompletion - // Set by setupWithProject(), consumed by ensureProjectReset() on the first real .java-file - // interaction after it -- deferred because setupWithProject() is called for every project - // open regardless of language (ADFA-5052). - @Volatile - private var pendingWorkspace: Workspace? = null + // Lifecycle of the javac-backed compiler state (NO_MODULE_COMPILER, SourceFileManager, + // JavaCompilerProvider), which setupWithProject() defers instead of building eagerly + // (ADFA-5052). All reads/writes of pendingWorkspace and compilerLifecycle go through + // compilerLifecycleLock, held for the *entire* reset/shutdown, not just the decision to + // run one -- otherwise a concurrent getCompiler()/onContentChange() could use a compiler + // mid-teardown, or shutdown() could destroy state a reset is still rebuilding. + private enum class CompilerLifecycle { PENDING, RESETTING, INITIALIZED, SHUTDOWN } + + private val compilerLifecycleLock = ReentrantLock() - @Volatile - private var javaCompilerInitialized = false + // Guarded by compilerLifecycleLock. + private var pendingWorkspace: Workspace? = null + private var compilerLifecycle = CompilerLifecycle.PENDING val settings: IServerSettings get() { @@ -143,11 +150,17 @@ class JavaLanguageServer : ILanguageServer { override fun shutdown() { (this.debugAdapter as? AutoCloseable?)?.close() - if (javaCompilerInitialized) { - JavaCompilerProvider.getInstance().destroy() - SourceFileManager.clearCache() - CacheFSInfoSingleton.clearCache() - clearCache() + compilerLifecycleLock.withLock { + // Blocks here if a reset is in flight (RESETTING can only be observed by another + // thread while the lock is held, never by us once we've acquired it), so this never + // races ensureProjectReset()'s own destroy/rebuild. + if (compilerLifecycle == CompilerLifecycle.INITIALIZED) { + JavaCompilerProvider.getInstance().destroy() + SourceFileManager.clearCache() + CacheFSInfoSingleton.clearCache() + clearCache() + } + compilerLifecycle = CompilerLifecycle.SHUTDOWN } EventBus.getDefault().unregister(this) timer.cancel() @@ -187,47 +200,67 @@ class JavaLanguageServer : ILanguageServer { // and JavaCompilerService.NO_MODULE_COMPILER / SourceFileManager.NO_MODULE eagerly // construct real javac machinery plus a full android.jar scan at class-init, merely by // being referenced (ADFA-5052, mirrors ADFA-5010's KotlinLanguageServer fix). - pendingWorkspace = workspace + compilerLifecycleLock.withLock { + pendingWorkspace = workspace + // Leave RESETTING alone: ensureProjectReset()'s own finally block re-checks + // pendingWorkspace once it re-acquires the lock, so a project switch mid-reset is + // picked up as another PENDING round rather than raced here. + if (compilerLifecycle != CompilerLifecycle.RESETTING) { + compilerLifecycle = CompilerLifecycle.PENDING + } + } } /** * Runs the javac-specific project reset deferred by [setupWithProject], for the most * recently opened project, the first time a real Java file is actually interacted with. - * No-ops if already up to date. + * No-ops if already up to date. Blocks concurrent callers (and [shutdown]) for the entire + * reset, not just the decision to run one. */ private fun ensureProjectReset() { - if (pendingWorkspace == null) return - val workspace: Workspace - synchronized(this) { - workspace = pendingWorkspace ?: return + compilerLifecycleLock.withLock { + if (compilerLifecycle != CompilerLifecycle.PENDING) return + val workspace = pendingWorkspace ?: return pendingWorkspace = null - javaCompilerInitialized = true - } + compilerLifecycle = CompilerLifecycle.RESETTING - // Once we have project initialized - // Destory the NO_MODULE_COMPILER instance - JavaCompilerService.NO_MODULE_COMPILER.destroy() + try { + // Once we have project initialized + // Destory the NO_MODULE_COMPILER instance + JavaCompilerService.NO_MODULE_COMPILER.destroy() - // Clear cached file managers - SourceFileManager.clearCache() + // Clear cached file managers + SourceFileManager.clearCache() - // Clear cached JAR file system for R.jar - // Using the cached instance will result in completions not being updated for updated resources - // TODO Clearing caches for JAR files ending with '/R.jar' is probably not a good idea - // Maybe this could be improved by using data from the AndroidModule project model - clearCachesForPaths { path: String -> path.endsWith("/R.jar") } + // Clear cached JAR file system for R.jar + // Using the cached instance will result in completions not being updated for updated resources + // TODO Clearing caches for JAR files ending with '/R.jar' is probably not a good idea + // Maybe this could be improved by using data from the AndroidModule project model + clearCachesForPaths { path: String -> path.endsWith("/R.jar") } - // Clear cached module-specific compilers - JavaCompilerProvider.getInstance().destroy() + // Clear cached module-specific compilers + JavaCompilerProvider.getInstance().destroy() - // Cache classpath locations - for (subModule in workspace.subProjects) { - if (subModule !is ModuleProject || subModule.path == workspace.rootProject.path) { - continue + // Cache classpath locations + for (subModule in workspace.subProjects) { + if (subModule !is ModuleProject || subModule.path == workspace.rootProject.path) { + continue + } + SourceFileManager.forModule(subModule) + } + startOrRestartAnalyzeTimer() + } finally { + // A newer setupWithProject() may have queued another workspace while we were + // resetting (see the RESETTING guard above); if so, go back to PENDING instead + // of claiming INITIALIZED for a project we didn't actually reset for. + compilerLifecycle = + if (pendingWorkspace != null) { + CompilerLifecycle.PENDING + } else { + CompilerLifecycle.INITIALIZED + } } - SourceFileManager.forModule(subModule) } - startOrRestartAnalyzeTimer() } override fun complete(params: CompletionParams?): CompletionResult { From e9a54b498aa2fecb8750528f5ed32fc4ac836c23 Mon Sep 17 00:00:00 2001 From: David Schachter Date: Thu, 6 Aug 2026 17:01:06 -0700 Subject: [PATCH 3/6] ADFA-5052: Fix exception handling, analyze() bypass, and a narrow post-lock race Three issues from /code-review high, verified against the current code: 1. ensureProjectReset() nulled pendingWorkspace before the try block, so any exception during destroy/rebuild (e.g. a bad submodule's classpath) still let the finally claim INITIALIZED -- silently treating a half-torn-down compiler as ready, with no retry, for the rest of the session. Now an exception re-queues the workspace, reverts to PENDING, and rethrows. 2. analyze() never called ensureProjectReset() at all. diagnosticProvider .analyze() builds its own JavaCompilerService directly, bypassing getCompiler(), and analysis is often the *first* real .java-file interaction (auto-triggered on file open, ahead of any completion request) -- so the R.jar/file-manager cache clear this reset performs could be skipped for an entire session, leaving diagnostics resolving against a stale previous project's classpath. Now gated the same way getCompiler()/onContentChange() already are. 3. getCompiler() and onContentChange() released compilerLifecycleLock as soon as ensureProjectReset() returned, then used JavaCompilerProvider unlocked -- a concurrent reset for a newer project could destroy() those compilers in the gap. Both now hold the lock across the reset and the subsequent provider lookup/use (safe: ReentrantLock is reentrant, so ensureProjectReset()'s own internal withLock nests without deadlocking). Two other findings from the same pass were assessed and left as-is: - shutdown() blocking on an in-flight reset with no cancellation is real but performance-only (no crash/corruption), requires disproportionate cancellation plumbing through SourceFileManager/JavaCompilerService for a narrow, bounded-cost edge case. - KotlinLanguageServer's eager construction is a real observation about this branch's current state, but it's already fixed by the separate, not-yet- merged ADFA-5010 (PR #1635) -- out of scope here, not a gap in this PR. Verified: :lsp:java unit tests pass, :app:assembleV8Debug builds clean. --- .../androidide/lsp/java/JavaLanguageServer.kt | 75 ++++++++++++------- 1 file changed, 50 insertions(+), 25 deletions(-) diff --git a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/JavaLanguageServer.kt b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/JavaLanguageServer.kt index 763080fc49..096dc08eba 100644 --- a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/JavaLanguageServer.kt +++ b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/JavaLanguageServer.kt @@ -249,17 +249,25 @@ class JavaLanguageServer : ILanguageServer { SourceFileManager.forModule(subModule) } startOrRestartAnalyzeTimer() - } finally { - // A newer setupWithProject() may have queued another workspace while we were - // resetting (see the RESETTING guard above); if so, go back to PENDING instead - // of claiming INITIALIZED for a project we didn't actually reset for. - compilerLifecycle = - if (pendingWorkspace != null) { - CompilerLifecycle.PENDING - } else { - CompilerLifecycle.INITIALIZED - } + } catch (e: Exception) { + // Re-queue the workspace so the next real .java-file interaction retries the + // reset, instead of a half-destroyed/half-rebuilt state being silently claimed as + // INITIALIZED (pendingWorkspace is already null by this point). + log.warn("Failed to reset javac project state; will retry on next interaction", e) + pendingWorkspace = workspace + compilerLifecycle = CompilerLifecycle.PENDING + throw e } + + // A newer setupWithProject() may have queued another workspace while we were + // resetting (see the RESETTING guard above); if so, go back to PENDING instead of + // claiming INITIALIZED for a project we didn't actually reset for. + compilerLifecycle = + if (pendingWorkspace != null) { + CompilerLifecycle.PENDING + } else { + CompilerLifecycle.INITIALIZED + } } } @@ -326,6 +334,13 @@ class JavaLanguageServer : ILanguageServer { return DiagnosticResult.NO_UPDATE } + // diagnosticProvider.analyze() builds its own JavaCompilerService directly (bypassing + // getCompiler()), and analysis is often the first real .java-file interaction in a + // session (auto-triggered on file open, ahead of any completion request) -- without this, + // the R.jar/file-manager caches this reset clears would never get cleared for this + // project, and diagnostics could resolve against a stale previous project's classpath. + ensureProjectReset() + return if (!settings.codeAnalysisEnabled()) { DiagnosticResult.NO_UPDATE } else { @@ -352,11 +367,17 @@ class JavaLanguageServer : ILanguageServer { if (!DocumentUtils.isJavaFile(file)) { return JavaCompilerService.NO_MODULE_COMPILER } - ensureProjectReset() - val module = - ProjectManagerImpl.getInstance().findModuleForFile(file!!) - ?: return JavaCompilerService.NO_MODULE_COMPILER - return JavaCompilerProvider.get(module) + // Held across ensureProjectReset() *and* the provider lookup (ReentrantLock is + // reentrant, so ensureProjectReset()'s own withLock nests fine): otherwise a concurrent + // reset for a newer project could destroy() the provider's compilers in the gap between + // this thread's reset finishing and its JavaCompilerProvider.get() call. + return compilerLifecycleLock.withLock { + ensureProjectReset() + val module = + ProjectManagerImpl.getInstance().findModuleForFile(file!!) + ?: return@withLock JavaCompilerService.NO_MODULE_COMPILER + JavaCompilerProvider.get(module) + } } private fun updateCachedCompletion(cachedCompletion: CachedCompletion) { @@ -382,16 +403,20 @@ class JavaLanguageServer : ILanguageServer { return } - ensureProjectReset() - - // TODO Find an alternative to efficiently update changeDelta in JavaCompilerService instance - JavaCompilerService.NO_MODULE_COMPILER.onDocumentChange(event) - val module = - getInstance() - .findModuleForFile(event.changedFile) - if (module != null) { - val compiler = JavaCompilerProvider.get(module) - compiler.onDocumentChange(event) + // See getCompiler(): held across the reset *and* the provider lookup/use so a concurrent + // reset can't destroy() these compilers in between. + compilerLifecycleLock.withLock { + ensureProjectReset() + + // TODO Find an alternative to efficiently update changeDelta in JavaCompilerService instance + JavaCompilerService.NO_MODULE_COMPILER.onDocumentChange(event) + val module = + getInstance() + .findModuleForFile(event.changedFile) + if (module != null) { + val compiler = JavaCompilerProvider.get(module) + compiler.onDocumentChange(event) + } } startOrRestartAnalyzeTimer() } From 33d20fa2541b0e7b407cd2fcc518b0b41645979f Mon Sep 17 00:00:00 2001 From: David Schachter Date: Mon, 24 Aug 2026 15:17:40 -0700 Subject: [PATCH 4/6] ADFA-5052: Make shutdown terminal, and cover the lifecycle transitions shutdown() destroyed the javac state but nothing kept it destroyed: a later setupWithProject() moved SHUTDOWN back to PENDING, and getCompiler() and onContentChange() would rebuild what had just been torn down. All four paths now treat SHUTDOWN as terminal -- getCompiler hands back NO_MODULE_COMPILER, the other two return, and setupWithProject logs why it is ignoring the project. Three tests cover the transitions that need no project fixture: shutdown before the first Java interaction, a project opened after shutdown, and a fresh server. The middle one fails without the guard. They need a seam, because every path returns NO_MODULE_COMPILER for its own reasons -- from outside, a test cannot tell a refusal after shutdown from a file with no module -- hence the @VisibleForTesting isShutDown. What this does not fix is the concurrent case. getCompiler() returns a service the caller uses after the lock is released, so a shutdown landing in that window can still destroy a compiler in use. Fixing that means leasing a compiler for the duration of an operation and draining in-flight leases before teardown, which converts all 18 call sites and needs its own testing. Filed as ADFA-5261 rather than appended to a PR about deferring construction that is already approved. Co-Authored-By: Claude Opus 5 --- .../androidide/lsp/java/JavaLanguageServer.kt | 28 +++++++++++ .../java/JavaLanguageServerLifecycleTest.kt | 46 ++++++++++++++++++ .../test-project/.cg/gradle-sync/project.pb | Bin 12678925 -> 12679546 bytes .../test-project/.cg/gradle-sync/sync.pb | 26 +++++----- 4 files changed, 87 insertions(+), 13 deletions(-) create mode 100644 lsp/java/src/test/java/com/itsaky/androidide/lsp/java/JavaLanguageServerLifecycleTest.kt diff --git a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/JavaLanguageServer.kt b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/JavaLanguageServer.kt index 096dc08eba..bd107e2f27 100644 --- a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/JavaLanguageServer.kt +++ b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/JavaLanguageServer.kt @@ -17,6 +17,7 @@ package com.itsaky.androidide.lsp.java import androidx.annotation.RestrictTo +import androidx.annotation.VisibleForTesting import com.itsaky.androidide.app.BaseApplication import com.itsaky.androidide.eventbus.events.editor.DocumentChangeEvent import com.itsaky.androidide.eventbus.events.editor.DocumentCloseEvent @@ -112,6 +113,15 @@ class JavaLanguageServer : ILanguageServer { private var pendingWorkspace: Workspace? = null private var compilerLifecycle = CompilerLifecycle.PENDING + /** + * Whether [shutdown] has run. Exposed because the lifecycle is otherwise unobservable from + * outside -- every path returns `NO_MODULE_COMPILER` for its own reasons, so a test cannot tell + * "refused because shut down" from "no module for this file" without it. + */ + @VisibleForTesting + internal val isShutDown: Boolean + get() = compilerLifecycleLock.withLock { compilerLifecycle == CompilerLifecycle.SHUTDOWN } + val settings: IServerSettings get() { return _settings ?: JavaServerSettings @@ -201,6 +211,13 @@ class JavaLanguageServer : ILanguageServer { // construct real javac machinery plus a full android.jar scan at class-init, merely by // being referenced (ADFA-5052, mirrors ADFA-5010's KotlinLanguageServer fix). compilerLifecycleLock.withLock { + // SHUTDOWN is terminal. A server whose javac state has been destroyed does not come + // back because a project happened to open afterwards; reviving it here would rebuild + // compilers nothing is going to shut down again (found in review). + if (compilerLifecycle == CompilerLifecycle.SHUTDOWN) { + log.debug("setupWithProject() ignored: this server has been shut down.") + return + } pendingWorkspace = workspace // Leave RESETTING alone: ensureProjectReset()'s own finally block re-checks // pendingWorkspace once it re-acquires the lock, so a project switch mid-reset is @@ -219,6 +236,7 @@ class JavaLanguageServer : ILanguageServer { */ private fun ensureProjectReset() { compilerLifecycleLock.withLock { + // PENDING is the only state a reset starts from; SHUTDOWN in particular is terminal. if (compilerLifecycle != CompilerLifecycle.PENDING) return val workspace = pendingWorkspace ?: return pendingWorkspace = null @@ -372,6 +390,12 @@ class JavaLanguageServer : ILanguageServer { // reset for a newer project could destroy() the provider's compilers in the gap between // this thread's reset finishing and its JavaCompilerProvider.get() call. return compilerLifecycleLock.withLock { + // Nothing to hand out once the javac state is gone: NO_MODULE_COMPILER is the same + // answer this returns for a non-Java file, and it does not resurrect what shutdown() + // destroyed. + if (compilerLifecycle == CompilerLifecycle.SHUTDOWN) { + return@withLock JavaCompilerService.NO_MODULE_COMPILER + } ensureProjectReset() val module = ProjectManagerImpl.getInstance().findModuleForFile(file!!) @@ -406,6 +430,10 @@ class JavaLanguageServer : ILanguageServer { // See getCompiler(): held across the reset *and* the provider lookup/use so a concurrent // reset can't destroy() these compilers in between. compilerLifecycleLock.withLock { + // A document change after shutdown has no compiler to tell, and must not rebuild one. + if (compilerLifecycle == CompilerLifecycle.SHUTDOWN) { + return + } ensureProjectReset() // TODO Find an alternative to efficiently update changeDelta in JavaCompilerService instance diff --git a/lsp/java/src/test/java/com/itsaky/androidide/lsp/java/JavaLanguageServerLifecycleTest.kt b/lsp/java/src/test/java/com/itsaky/androidide/lsp/java/JavaLanguageServerLifecycleTest.kt new file mode 100644 index 0000000000..11a1e8a301 --- /dev/null +++ b/lsp/java/src/test/java/com/itsaky/androidide/lsp/java/JavaLanguageServerLifecycleTest.kt @@ -0,0 +1,46 @@ +package com.itsaky.androidide.lsp.java + +import com.google.common.truth.Truth.assertThat +import io.mockk.mockk +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config + +/** + * ADFA-5052 defers the javac reset to the first real Java interaction, which makes the server's + * lifecycle something with states rather than a single construction. These cover the transitions + * that do not need a project fixture; the concurrent ones (an operation holding a compiler while + * shutdown lands) are ADFA-5261. + */ +@RunWith(RobolectricTestRunner::class) +@Config(manifest = Config.DEFAULT_VALUE_STRING) +class JavaLanguageServerLifecycleTest { + // The whole point of the deferral: nothing javac-related is built until a Java file is touched, + // so shutting down before that must be a no-op rather than a teardown of things never made. + @Test + fun `shutdown before the first java interaction is safe`() { + val server = JavaLanguageServer() + + server.shutdown() + + assertThat(server.isShutDown).isTrue() + } + + // A server whose javac state has been destroyed does not come back because a project opened + // afterwards -- otherwise it rebuilds compilers that nothing will shut down again. + @Test + fun `a project opened after shutdown does not revive the server`() { + val server = JavaLanguageServer() + server.shutdown() + + server.setupWithProject(mockk(relaxed = true)) + + assertThat(server.isShutDown).isTrue() + } + + @Test + fun `a fresh server is not shut down`() { + assertThat(JavaLanguageServer().isShutDown).isFalse() + } +} diff --git a/testing/resources/test-project/.cg/gradle-sync/project.pb b/testing/resources/test-project/.cg/gradle-sync/project.pb index 7828f6edd280f53ff93859422cf690583e1b91f0..5efe8e730f05732e48fa34ed65cd87be3c4c30e5 100644 GIT binary patch delta 22820 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 delta 22295 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 D?KqT> diff --git a/testing/resources/test-project/.cg/gradle-sync/sync.pb b/testing/resources/test-project/.cg/gradle-sync/sync.pb index eeaa33575a..f13af59f3a 100644 --- a/testing/resources/test-project/.cg/gradle-sync/sync.pb +++ b/testing/resources/test-project/.cg/gradle-sync/sync.pb @@ -1,14 +1,14 @@ -1E/Users/john/Documents/cogo/CodeOnTheGo/testing/resources/test-project 1782139760767" -app/build.gradleV/Users/john/Documents/cogo/CodeOnTheGo/testing/resources/test-project/app/build.gradle Ժ3*@6bc0acd25b3856d9902a22dd39af0967fae7c1fd23687d76c65bbe975df4810d" -java-library/build.gradle_/Users/john/Documents/cogo/CodeOnTheGo/testing/resources/test-project/java-library/build.gradle ټՄ3*@d01681ec8f736d858ef64789286b4c2c7f738427a3bcdd76e35ae4680081df1b" --java-library/nested-java-library/build.gradles/Users/john/Documents/cogo/CodeOnTheGo/testing/resources/test-project/java-library/nested-java-library/build.gradle ټՄ3*@85c6cbc9d6fd247f630c416af4dcd78d376cb6b71d481adb893ea395fa9bb747" -!another-java-library/build.gradleg/Users/john/Documents/cogo/CodeOnTheGo/testing/resources/test-project/another-java-library/build.gradle Մ3*@85b6448ea59f0a7d7ccd65480d3e39b8c74452b5b7bbdff11fedcba22ee220f3" -5another-java-library/nested-java-library/build.gradle{/Users/john/Documents/cogo/CodeOnTheGo/testing/resources/test-project/another-java-library/nested-java-library/build.gradle Մ3*@85c6cbc9d6fd247f630c416af4dcd78d376cb6b71d481adb893ea395fa9bb747" -other-java-library/build.gradlee/Users/john/Documents/cogo/CodeOnTheGo/testing/resources/test-project/other-java-library/build.gradle ټՄ3*@c25eded2af3131d1d62a75b9c5c09ca063781b191c0c05957b86aa6d5956b55a" - build.gradleR/Users/john/Documents/cogo/CodeOnTheGo/testing/resources/test-project/build.gradle Ժ3*@aefd03f7322bcde9d5916aa5595b15dde3aa5426fcee485ade8016f2790f2b76" -gradle.propertiesW/Users/john/Documents/cogo/CodeOnTheGo/testing/resources/test-project/gradle.propertiesR ؼՄ3*@274d1aac5a469b1d085614b75e38d439237f5f1defd6ad59a8f95d58286c95b1" -settings.gradleU/Users/john/Documents/cogo/CodeOnTheGo/testing/resources/test-project/settings.gradle ڼՄ3*@117d4a4a030028d040f47e37cbe9035ae8c5d647d8eaae87d7b8ae5299c769f1" -$another-android-library/build.gradlej/Users/john/Documents/cogo/CodeOnTheGo/testing/resources/test-project/another-android-library/build.gradle 3*@5402147ec86ff784e20993dcd6e3c3ea44d12ce19080074fe2226f527a888073" -android-library/build.gradleb/Users/john/Documents/cogo/CodeOnTheGo/testing/resources/test-project/android-library/build.gradle 3*@dfccb9e9718ecb268ea4ab1cf32038d9403f334cd942d08abf7081596c69787b* -`/Users/john/Documents/cogo/CodeOnTheGo/testing/resources/test-project/.cg/gradle-sync/project.pb@cdf55c953c74b1af640b6adbce9775f7109d3f053c5be99f4b4fc8c337637b5c \ No newline at end of file +1L/home/david/AndroidStudioProjects/CodeOnTheGo/testing/resources/test-project 1787609802637" +settings.gradle\/home/david/AndroidStudioProjects/CodeOnTheGo/testing/resources/test-project/settings.gradle 3*@117d4a4a030028d040f47e37cbe9035ae8c5d647d8eaae87d7b8ae5299c769f1" +$another-android-library/build.gradleq/home/david/AndroidStudioProjects/CodeOnTheGo/testing/resources/test-project/another-android-library/build.gradle 3*@5402147ec86ff784e20993dcd6e3c3ea44d12ce19080074fe2226f527a888073" +5another-java-library/nested-java-library/build.gradle/home/david/AndroidStudioProjects/CodeOnTheGo/testing/resources/test-project/another-java-library/nested-java-library/build.gradle 3*@85c6cbc9d6fd247f630c416af4dcd78d376cb6b71d481adb893ea395fa9bb747" +!another-java-library/build.gradlen/home/david/AndroidStudioProjects/CodeOnTheGo/testing/resources/test-project/another-java-library/build.gradle 3*@85b6448ea59f0a7d7ccd65480d3e39b8c74452b5b7bbdff11fedcba22ee220f3" +app/build.gradle]/home/david/AndroidStudioProjects/CodeOnTheGo/testing/resources/test-project/app/build.gradle ű㮃4*@6bc0acd25b3856d9902a22dd39af0967fae7c1fd23687d76c65bbe975df4810d" +other-java-library/build.gradlel/home/david/AndroidStudioProjects/CodeOnTheGo/testing/resources/test-project/other-java-library/build.gradle 3*@c25eded2af3131d1d62a75b9c5c09ca063781b191c0c05957b86aa6d5956b55a" +-java-library/nested-java-library/build.gradlez/home/david/AndroidStudioProjects/CodeOnTheGo/testing/resources/test-project/java-library/nested-java-library/build.gradle 3*@85c6cbc9d6fd247f630c416af4dcd78d376cb6b71d481adb893ea395fa9bb747" +java-library/build.gradlef/home/david/AndroidStudioProjects/CodeOnTheGo/testing/resources/test-project/java-library/build.gradle 3*@d01681ec8f736d858ef64789286b4c2c7f738427a3bcdd76e35ae4680081df1b" +gradle.properties^/home/david/AndroidStudioProjects/CodeOnTheGo/testing/resources/test-project/gradle.propertiesR 3*@274d1aac5a469b1d085614b75e38d439237f5f1defd6ad59a8f95d58286c95b1" + build.gradleY/home/david/AndroidStudioProjects/CodeOnTheGo/testing/resources/test-project/build.gradle ı㮃4*@aefd03f7322bcde9d5916aa5595b15dde3aa5426fcee485ade8016f2790f2b76" +android-library/build.gradlei/home/david/AndroidStudioProjects/CodeOnTheGo/testing/resources/test-project/android-library/build.gradle 3*@dfccb9e9718ecb268ea4ab1cf32038d9403f334cd942d08abf7081596c69787b* +g/home/david/AndroidStudioProjects/CodeOnTheGo/testing/resources/test-project/.cg/gradle-sync/project.pb@662efc23402ccdc9ec20f0307ebec79ffe021f49fdeb1fdf67e5cc8eca468257 \ No newline at end of file From 9236c4b501420b2961a5e1195c67a0533b8a4976 Mon Sep 17 00:00:00 2001 From: David Schachter Date: Mon, 24 Aug 2026 16:55:11 -0700 Subject: [PATCH 5/6] ADFA-5052: Revert fixtures I should not have committed, and close three gaps The worst of these is mine and not in the code: my previous commit swept up testing/resources/test-project/.cg/gradle-sync/{project,sync}.pb, replacing the previous author's absolute paths with /home/david and a 12 MB binary with a locally regenerated one. Nothing in this ticket needs them. Reverted to stage. analyze() was the one javac entry point left without a shutdown guard. It calls ensureProjectReset(), which no-ops after shutdown, and then goes on to diagnosticProvider.analyze(), which constructs its own JavaCompilerService -- so an analysis already in flight when shutdown() lands would rebuild what shutdown had just destroyed, permanently, since SHUTDOWN is terminal. It is reachable: analyzeSelected() launches on Dispatchers.Default, and a timer callback already dispatched cannot be recalled. ensureProjectReset() caught Exception, so an Error left the lifecycle stuck at RESETTING with the workspace already discarded, and every later reset returned early -- Java support dead for the session with no retry. The class init this change defers is precisely what fails as an Error: OutOfMemoryError, or ExceptionInInitializerError out of the android.jar scan. It catches Throwable now. setupWithProject() had stopped re-arming the analyze timer. A .java tab restored from the tab cache opens before the sync posts this event, and AnalyzeTimer fires once: that shot finds no module and returns NO_UPDATE, so the restored file showed no diagnostics until the user typed. Arming the timer builds no javac. The new test file gains the GPL header its siblings carry and an @After that shuts down the servers it creates -- each registers on the global EventBus and adds indexing services to a singleton, and Robolectric caches a sandbox across test classes. Recorded on ADFA-5261 rather than fixed here: returning the NO_MODULE_COMPILER sentinel loads javac by class init, so the deferral leaks through every such return -- including the SHUTDOWN branch -- and handleFailure() destroys every compiler outside the lock. Both need the same signature change as the lease. 19 tests pass in :lsp:java. Co-Authored-By: Claude Opus 5 --- .../androidide/lsp/java/JavaLanguageServer.kt | 24 +++++++++++- .../java/JavaLanguageServerLifecycleTest.kt | 37 +++++++++++++++++-- .../test-project/.cg/gradle-sync/sync.pb | 6 +-- 3 files changed, 60 insertions(+), 7 deletions(-) diff --git a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/JavaLanguageServer.kt b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/JavaLanguageServer.kt index bd107e2f27..d48c53b05a 100644 --- a/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/JavaLanguageServer.kt +++ b/lsp/java/src/main/java/com/itsaky/androidide/lsp/java/JavaLanguageServer.kt @@ -226,6 +226,13 @@ class JavaLanguageServer : ILanguageServer { compilerLifecycle = CompilerLifecycle.PENDING } } + + // Re-armed here as well as in ensureProjectReset(). A .java tab restored from the tab cache + // opens before the Gradle sync posts this event, and AnalyzeTimer fires once: that shot lands + // while pendingWorkspace is still null, finds no module, and returns NO_UPDATE. Without this + // the restored file then shows no diagnostics until the user types. Arming the timer builds + // no javac -- analyze() is gated on isJavaFile and now on the lifecycle too. + startOrRestartAnalyzeTimer() } /** @@ -267,7 +274,13 @@ class JavaLanguageServer : ILanguageServer { SourceFileManager.forModule(subModule) } startOrRestartAnalyzeTimer() - } catch (e: Exception) { + } catch (e: Throwable) { + // Throwable, not Exception: the class-init this whole change defers is exactly what + // fails as an Error -- OutOfMemoryError, or ExceptionInInitializerError / + // NoClassDefFoundError out of the android.jar top-level scan. Catching only Exception + // left compilerLifecycle stuck at RESETTING with the workspace already discarded, so + // every later reset returned early and Java support stayed dead for the session. + // // Re-queue the workspace so the next real .java-file interaction retries the // reset, instead of a half-destroyed/half-rebuilt state being silently claimed as // INITIALIZED (pendingWorkspace is already null by this point). @@ -352,6 +365,15 @@ class JavaLanguageServer : ILanguageServer { return DiagnosticResult.NO_UPDATE } + // The third javac entry point, and the one that was missing this: diagnosticProvider.analyze() + // constructs its own JavaCompilerService, so an analysis already in flight when shutdown() + // lands would rebuild everything shutdown just destroyed -- and SHUTDOWN is terminal, so + // nothing would ever tear it down again. Reachable: analyzeSelected() launches on + // Dispatchers.Default and the timer callback cannot be recalled once dispatched. + if (isShutDown) { + return DiagnosticResult.NO_UPDATE + } + // diagnosticProvider.analyze() builds its own JavaCompilerService directly (bypassing // getCompiler()), and analysis is often the first real .java-file interaction in a // session (auto-triggered on file open, ahead of any completion request) -- without this, diff --git a/lsp/java/src/test/java/com/itsaky/androidide/lsp/java/JavaLanguageServerLifecycleTest.kt b/lsp/java/src/test/java/com/itsaky/androidide/lsp/java/JavaLanguageServerLifecycleTest.kt index 11a1e8a301..d7addc4625 100644 --- a/lsp/java/src/test/java/com/itsaky/androidide/lsp/java/JavaLanguageServerLifecycleTest.kt +++ b/lsp/java/src/test/java/com/itsaky/androidide/lsp/java/JavaLanguageServerLifecycleTest.kt @@ -1,7 +1,25 @@ +/* + * This file is part of AndroidIDE. + * + * AndroidIDE is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AndroidIDE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with AndroidIDE. If not, see . + */ + package com.itsaky.androidide.lsp.java import com.google.common.truth.Truth.assertThat import io.mockk.mockk +import org.junit.After import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner @@ -16,11 +34,24 @@ import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) @Config(manifest = Config.DEFAULT_VALUE_STRING) class JavaLanguageServerLifecycleTest { + // Every server registers itself on the global EventBus and adds indexing services to the + // ProjectManagerImpl singleton in its init block, and Robolectric caches a sandbox per @Config -- + // so a server left running here would keep receiving events posted by other test classes. + private val servers = mutableListOf() + + @After + fun tearDown() { + servers.forEach { it.shutdown() } + servers.clear() + } + + private fun newServer() = JavaLanguageServer().also { servers += it } + // The whole point of the deferral: nothing javac-related is built until a Java file is touched, // so shutting down before that must be a no-op rather than a teardown of things never made. @Test fun `shutdown before the first java interaction is safe`() { - val server = JavaLanguageServer() + val server = newServer() server.shutdown() @@ -31,7 +62,7 @@ class JavaLanguageServerLifecycleTest { // afterwards -- otherwise it rebuilds compilers that nothing will shut down again. @Test fun `a project opened after shutdown does not revive the server`() { - val server = JavaLanguageServer() + val server = newServer() server.shutdown() server.setupWithProject(mockk(relaxed = true)) @@ -41,6 +72,6 @@ class JavaLanguageServerLifecycleTest { @Test fun `a fresh server is not shut down`() { - assertThat(JavaLanguageServer().isShutDown).isFalse() + assertThat(newServer().isShutDown).isFalse() } } diff --git a/testing/resources/test-project/.cg/gradle-sync/sync.pb b/testing/resources/test-project/.cg/gradle-sync/sync.pb index f13af59f3a..76bd4ebb96 100644 --- a/testing/resources/test-project/.cg/gradle-sync/sync.pb +++ b/testing/resources/test-project/.cg/gradle-sync/sync.pb @@ -1,14 +1,14 @@ -1L/home/david/AndroidStudioProjects/CodeOnTheGo/testing/resources/test-project 1787609802637" +1L/home/david/AndroidStudioProjects/CodeOnTheGo/testing/resources/test-project 1787615622102" settings.gradle\/home/david/AndroidStudioProjects/CodeOnTheGo/testing/resources/test-project/settings.gradle 3*@117d4a4a030028d040f47e37cbe9035ae8c5d647d8eaae87d7b8ae5299c769f1" $another-android-library/build.gradleq/home/david/AndroidStudioProjects/CodeOnTheGo/testing/resources/test-project/another-android-library/build.gradle 3*@5402147ec86ff784e20993dcd6e3c3ea44d12ce19080074fe2226f527a888073" 5another-java-library/nested-java-library/build.gradle/home/david/AndroidStudioProjects/CodeOnTheGo/testing/resources/test-project/another-java-library/nested-java-library/build.gradle 3*@85c6cbc9d6fd247f630c416af4dcd78d376cb6b71d481adb893ea395fa9bb747" !another-java-library/build.gradlen/home/david/AndroidStudioProjects/CodeOnTheGo/testing/resources/test-project/another-java-library/build.gradle 3*@85b6448ea59f0a7d7ccd65480d3e39b8c74452b5b7bbdff11fedcba22ee220f3" -app/build.gradle]/home/david/AndroidStudioProjects/CodeOnTheGo/testing/resources/test-project/app/build.gradle ű㮃4*@6bc0acd25b3856d9902a22dd39af0967fae7c1fd23687d76c65bbe975df4810d" +app/build.gradle]/home/david/AndroidStudioProjects/CodeOnTheGo/testing/resources/test-project/app/build.gradle Տı4*@6bc0acd25b3856d9902a22dd39af0967fae7c1fd23687d76c65bbe975df4810d" other-java-library/build.gradlel/home/david/AndroidStudioProjects/CodeOnTheGo/testing/resources/test-project/other-java-library/build.gradle 3*@c25eded2af3131d1d62a75b9c5c09ca063781b191c0c05957b86aa6d5956b55a" -java-library/nested-java-library/build.gradlez/home/david/AndroidStudioProjects/CodeOnTheGo/testing/resources/test-project/java-library/nested-java-library/build.gradle 3*@85c6cbc9d6fd247f630c416af4dcd78d376cb6b71d481adb893ea395fa9bb747" java-library/build.gradlef/home/david/AndroidStudioProjects/CodeOnTheGo/testing/resources/test-project/java-library/build.gradle 3*@d01681ec8f736d858ef64789286b4c2c7f738427a3bcdd76e35ae4680081df1b" gradle.properties^/home/david/AndroidStudioProjects/CodeOnTheGo/testing/resources/test-project/gradle.propertiesR 3*@274d1aac5a469b1d085614b75e38d439237f5f1defd6ad59a8f95d58286c95b1" - build.gradleY/home/david/AndroidStudioProjects/CodeOnTheGo/testing/resources/test-project/build.gradle ı㮃4*@aefd03f7322bcde9d5916aa5595b15dde3aa5426fcee485ade8016f2790f2b76" + build.gradleY/home/david/AndroidStudioProjects/CodeOnTheGo/testing/resources/test-project/build.gradle ԏı4*@aefd03f7322bcde9d5916aa5595b15dde3aa5426fcee485ade8016f2790f2b76" android-library/build.gradlei/home/david/AndroidStudioProjects/CodeOnTheGo/testing/resources/test-project/android-library/build.gradle 3*@dfccb9e9718ecb268ea4ab1cf32038d9403f334cd942d08abf7081596c69787b* g/home/david/AndroidStudioProjects/CodeOnTheGo/testing/resources/test-project/.cg/gradle-sync/project.pb@662efc23402ccdc9ec20f0307ebec79ffe021f49fdeb1fdf67e5cc8eca468257 \ No newline at end of file From a7c49e6728c3adfae0e64ca3503f0babcce8e72e Mon Sep 17 00:00:00 2001 From: David Schachter Date: Mon, 24 Aug 2026 16:55:44 -0700 Subject: [PATCH 6/6] ADFA-5052: Revert the regenerated sync fixtures again Running :lsp:java's tests rewrites testing/resources/test-project/.cg/gradle-sync/ {project,sync}.pb with the local absolute paths -- which is how they reached the previous commit in the first place, and why reverting them before running the tests did not stick. Co-Authored-By: Claude Opus 5 --- .../test-project/.cg/gradle-sync/project.pb | Bin 12679546 -> 12678925 bytes .../test-project/.cg/gradle-sync/sync.pb | 26 +++++++++--------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/testing/resources/test-project/.cg/gradle-sync/project.pb b/testing/resources/test-project/.cg/gradle-sync/project.pb index 5efe8e730f05732e48fa34ed65cd87be3c4c30e5..7828f6edd280f53ff93859422cf690583e1b91f0 100644 GIT binary patch delta 22295 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 D?KqT> delta 22820 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 diff --git a/testing/resources/test-project/.cg/gradle-sync/sync.pb b/testing/resources/test-project/.cg/gradle-sync/sync.pb index 76bd4ebb96..eeaa33575a 100644 --- a/testing/resources/test-project/.cg/gradle-sync/sync.pb +++ b/testing/resources/test-project/.cg/gradle-sync/sync.pb @@ -1,14 +1,14 @@ -1L/home/david/AndroidStudioProjects/CodeOnTheGo/testing/resources/test-project 1787615622102" -settings.gradle\/home/david/AndroidStudioProjects/CodeOnTheGo/testing/resources/test-project/settings.gradle 3*@117d4a4a030028d040f47e37cbe9035ae8c5d647d8eaae87d7b8ae5299c769f1" -$another-android-library/build.gradleq/home/david/AndroidStudioProjects/CodeOnTheGo/testing/resources/test-project/another-android-library/build.gradle 3*@5402147ec86ff784e20993dcd6e3c3ea44d12ce19080074fe2226f527a888073" -5another-java-library/nested-java-library/build.gradle/home/david/AndroidStudioProjects/CodeOnTheGo/testing/resources/test-project/another-java-library/nested-java-library/build.gradle 3*@85c6cbc9d6fd247f630c416af4dcd78d376cb6b71d481adb893ea395fa9bb747" -!another-java-library/build.gradlen/home/david/AndroidStudioProjects/CodeOnTheGo/testing/resources/test-project/another-java-library/build.gradle 3*@85b6448ea59f0a7d7ccd65480d3e39b8c74452b5b7bbdff11fedcba22ee220f3" -app/build.gradle]/home/david/AndroidStudioProjects/CodeOnTheGo/testing/resources/test-project/app/build.gradle Տı4*@6bc0acd25b3856d9902a22dd39af0967fae7c1fd23687d76c65bbe975df4810d" -other-java-library/build.gradlel/home/david/AndroidStudioProjects/CodeOnTheGo/testing/resources/test-project/other-java-library/build.gradle 3*@c25eded2af3131d1d62a75b9c5c09ca063781b191c0c05957b86aa6d5956b55a" --java-library/nested-java-library/build.gradlez/home/david/AndroidStudioProjects/CodeOnTheGo/testing/resources/test-project/java-library/nested-java-library/build.gradle 3*@85c6cbc9d6fd247f630c416af4dcd78d376cb6b71d481adb893ea395fa9bb747" -java-library/build.gradlef/home/david/AndroidStudioProjects/CodeOnTheGo/testing/resources/test-project/java-library/build.gradle 3*@d01681ec8f736d858ef64789286b4c2c7f738427a3bcdd76e35ae4680081df1b" -gradle.properties^/home/david/AndroidStudioProjects/CodeOnTheGo/testing/resources/test-project/gradle.propertiesR 3*@274d1aac5a469b1d085614b75e38d439237f5f1defd6ad59a8f95d58286c95b1" - build.gradleY/home/david/AndroidStudioProjects/CodeOnTheGo/testing/resources/test-project/build.gradle ԏı4*@aefd03f7322bcde9d5916aa5595b15dde3aa5426fcee485ade8016f2790f2b76" -android-library/build.gradlei/home/david/AndroidStudioProjects/CodeOnTheGo/testing/resources/test-project/android-library/build.gradle 3*@dfccb9e9718ecb268ea4ab1cf32038d9403f334cd942d08abf7081596c69787b* -g/home/david/AndroidStudioProjects/CodeOnTheGo/testing/resources/test-project/.cg/gradle-sync/project.pb@662efc23402ccdc9ec20f0307ebec79ffe021f49fdeb1fdf67e5cc8eca468257 \ No newline at end of file +1E/Users/john/Documents/cogo/CodeOnTheGo/testing/resources/test-project 1782139760767" +app/build.gradleV/Users/john/Documents/cogo/CodeOnTheGo/testing/resources/test-project/app/build.gradle Ժ3*@6bc0acd25b3856d9902a22dd39af0967fae7c1fd23687d76c65bbe975df4810d" +java-library/build.gradle_/Users/john/Documents/cogo/CodeOnTheGo/testing/resources/test-project/java-library/build.gradle ټՄ3*@d01681ec8f736d858ef64789286b4c2c7f738427a3bcdd76e35ae4680081df1b" +-java-library/nested-java-library/build.gradles/Users/john/Documents/cogo/CodeOnTheGo/testing/resources/test-project/java-library/nested-java-library/build.gradle ټՄ3*@85c6cbc9d6fd247f630c416af4dcd78d376cb6b71d481adb893ea395fa9bb747" +!another-java-library/build.gradleg/Users/john/Documents/cogo/CodeOnTheGo/testing/resources/test-project/another-java-library/build.gradle Մ3*@85b6448ea59f0a7d7ccd65480d3e39b8c74452b5b7bbdff11fedcba22ee220f3" +5another-java-library/nested-java-library/build.gradle{/Users/john/Documents/cogo/CodeOnTheGo/testing/resources/test-project/another-java-library/nested-java-library/build.gradle Մ3*@85c6cbc9d6fd247f630c416af4dcd78d376cb6b71d481adb893ea395fa9bb747" +other-java-library/build.gradlee/Users/john/Documents/cogo/CodeOnTheGo/testing/resources/test-project/other-java-library/build.gradle ټՄ3*@c25eded2af3131d1d62a75b9c5c09ca063781b191c0c05957b86aa6d5956b55a" + build.gradleR/Users/john/Documents/cogo/CodeOnTheGo/testing/resources/test-project/build.gradle Ժ3*@aefd03f7322bcde9d5916aa5595b15dde3aa5426fcee485ade8016f2790f2b76" +gradle.propertiesW/Users/john/Documents/cogo/CodeOnTheGo/testing/resources/test-project/gradle.propertiesR ؼՄ3*@274d1aac5a469b1d085614b75e38d439237f5f1defd6ad59a8f95d58286c95b1" +settings.gradleU/Users/john/Documents/cogo/CodeOnTheGo/testing/resources/test-project/settings.gradle ڼՄ3*@117d4a4a030028d040f47e37cbe9035ae8c5d647d8eaae87d7b8ae5299c769f1" +$another-android-library/build.gradlej/Users/john/Documents/cogo/CodeOnTheGo/testing/resources/test-project/another-android-library/build.gradle 3*@5402147ec86ff784e20993dcd6e3c3ea44d12ce19080074fe2226f527a888073" +android-library/build.gradleb/Users/john/Documents/cogo/CodeOnTheGo/testing/resources/test-project/android-library/build.gradle 3*@dfccb9e9718ecb268ea4ab1cf32038d9403f334cd942d08abf7081596c69787b* +`/Users/john/Documents/cogo/CodeOnTheGo/testing/resources/test-project/.cg/gradle-sync/project.pb@cdf55c953c74b1af640b6adbce9775f7109d3f053c5be99f4b4fc8c337637b5c \ No newline at end of file