Status Update
Comments
ga...@google.com <ga...@google.com>
je...@google.com <je...@google.com>
so...@google.com <so...@google.com> #2
As discussed "offline":
(1) accessing .class
files via the VFS is already a problem because it pollutes it with unnecessary often changing content
(2) an alternative is to access files directly but the VFS is, at least for now, convenient to deal with .jar
files
(3) the proposal we greed on is to return file urls (used in IDEA) from class finders that can be translated to Path
in the case of local files and can still be used with the VFS when dealing with .jar
files.
so...@google.com <so...@google.com> #3
There are several areas not strictly related to this issue that still need attention:
-
We need to make sure that the first time
build
directory is created under a Gradle project during build it appears in the project view when build finishes (without switching to other apps i.e. without triggering the VFS refresh manually) -
Various class loader implementations involved in compose preview (and other layout related components) use the VFS to load
.class
files produced by Gradle build. This was done as a simplification/unification which allowed us to handle both.class
and.jar
files similarly as we rely on the VFS support for paths likejar://<path-to-jar>!/path/to/.class
.Diego confirmed that this should be possible to separate execution paths and implement
.jar
handling directly( or at least use the VFS) for.jar
files only. As the first step we need to replace our APIs that currently returnVirtualFile
s with APIs that return VFS urls and resolve them in the caller code. (I believe it can be pure refactoring). Once done the core team should be able to deal with.jar
and.class
files differently and eventually stop bringing.class
files to the VFS storage. As we know it might have a substantial performance impact.
ga...@google.com <ga...@google.com> #4
Post ag/Ia1a5ea0c0be0bf29478d99559e4cd06e80ae832d, PSI is built for the R class, which requires using VirtualFile
i.e. having up-to-date VFS state. This complicates things a bit, and requires R.jar refresh.
ga...@google.com <ga...@google.com> #5
I collected some numbers. Here is how long it takes to collect all compiler output paths (can be cached) and to do a recursive VFS refresh:
- Simple MyApplication
2023-10-02 18:55:53,370 [ 234586] INFO - STDERR - Collecting outputs took 2[ms] and refresh took 210[ms]
- 1000 subprojects
2023-10-02 19:22:44,012 [1845228] INFO - STDERR - Collecting outputs took 815[ms] and refresh took 2548[ms]
2023-10-02 19:24:24,854 [1946070] INFO - STDERR - Collecting outputs took 551[ms] and refresh took 604[ms]
2023-10-02 19:30:19,490 [2300706] INFO - STDERR - Collecting outputs took 953[ms] and refresh took 7621[ms]
2023-10-02 19:31:08,210 [2349426] INFO - STDERR - Collecting outputs took 660[ms] and refresh took 711[ms]
In IDEA, there is a non-recursive refresh with comment:
// refresh on output roots is required in order for the order enumerator to see all roots via VFS
// have to refresh in case of errors too, because run configuration may be set to ignore errors
CompilerUtil.refreshOutputRoots(affectedRoots);
which implies that we should probably do the same. Note: doing this will also address build
dir to be loaded in VFS (as it's parent of javac/kotlinc/R.jar outputs).
This still implies that we need to move rendering off of VFS. It should be able to get the module roots using order enumerator, but it needs to use NIO to access actual bytes.
ga...@google.com <ga...@google.com> #6
Data from doing a non-recursive, compiler outputs refresh. Again, we can cache outputs collection but the refresh needs to run synchronously. The project has 1000 subprojects:
For build that takes 4 seconds: Collecting outputs took 349[ms] and refresh took 2124[ms]
On full project clean that takes 800ms: Collecting outputs took 358[ms] and refresh took 1482[ms]
This is not great, we should try to figure out a way to do this only when needed.
One option is to monitor task execution, capture Gradle project paths for tasks that run, and do a refresh only for those modules. However, it does not help with running clean on the entire project as all modules are impacted.
ga...@google.com <ga...@google.com> #7
Some findings:
-
com.intellij.openapi.roots.OrderEnumerator#classes
returns only classes output roots that are in the VFS. I.e. untiljavac
/kotlinc
/R.jar
dirs/files are actually created, they won't be returned by this API. -
ProjectRootManagerComponent
in IDEA gets all roots, but for classes it only gets the JPS classes output (for Android Studio, onlyjavac
output is registered). Ifjavac
output is added/removed, roots change event is triggered, which triggers RootIndex to be rebuilt and it triggeres scanning.2.1 Because of this, triggering synchronous compiler outputs refresh on build finished (in
com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListener#onEnd
) is a no-go. -
GradleClassFileFinder
usesParameterizedCachedValueProvider
that depends onProjectSyncModificationTracker
andProjectBuildTracker
. Also, it fetches module compiler outputs withGradleClassFinderUtil.getModuleCompileOutputs(it, includeAndroidTests)
that returnsFile
instances. This is important because of 1): we don't need to refresh VFS in order to get up-to-date information here. -
Design previews already listen to build finished events, and they are able to trigger redraw in the right moment.
Because of issues outlined in 2.1) and because of how 3) works, my plan is to:
- Trigger async compiler outputs refresh when build is done. This is to make sure users of
OrderEnumerator#classes
API are using up-to-date information. - Migrate
com.android.tools.idea.projectsystem.ClassFileFinder#findClassFile
to return byte array, as it is just used to feed classes to class loaders. We also need to handle jar files efficiently.
ga...@google.com <ga...@google.com> #8
Done in ag/q/topic:%22class-finder%22.
We don't use VFS any more to load classes, bytes are loaded directly from disk. Also, extraction of R class final resources IDs is done with ASM (not PSI).
an...@google.com <an...@google.com> #9
Thank you for your patience while our engineering team worked to resolve this issue. A fix for this issue is now available in:
- Android Studio Iguana | 2023.2.1 Canary 11
- Android Gradle Plugin 8.3.0-alpha11
We encourage you to try the latest update.
If you notice further issues or have questions, please file a new bug report.
Thank you for taking the time to submit feedback — we really appreciate it!
Description
at com.android.tools.idea.rendering.RenderSecurityException.create(RenderSecurityException.java:52)
at com.android.tools.idea.rendering.RenderSecurityManager.checkWrite(RenderSecurityManager.java:558)
at java.base/java.io.FileOutputStream.<init>(FileOutputStream.java:225)
at java.base/java.io.FileOutputStream.<init>(FileOutputStream.java:187)
at java.base/java.io.FileWriter.<init>(FileWriter.java:96)
at com.intellij.openapi.vfs.newvfs.persistent.FSRecords$DbConnection.createBrokenMarkerFile(FSRecords.java:224)
at com.intellij.openapi.vfs.newvfs.persistent.FSRecords$DbConnection.handleError(FSRecords.java:508)
at com.intellij.openapi.vfs.newvfs.persistent.FSRecords.readAndHandleErrors(FSRecords.java:926)
at com.intellij.openapi.vfs.newvfs.persistent.FSRecords.getNameByNameId(FSRecords.java:1207)
at com.intellij.openapi.vfs.newvfs.persistent.PersistentFSImpl.findExistingChildInfo(PersistentFSImpl.java:435)
at com.intellij.openapi.vfs.newvfs.persistent.PersistentFSImpl.lambda$findChildInfo$4(PersistentFSImpl.java:381)
at com.intellij.openapi.vfs.newvfs.persistent.FSRecords.update(FSRecords.java:966)
at com.intellij.openapi.vfs.newvfs.persistent.PersistentFSImpl.findChildInfo(PersistentFSImpl.java:416)
at com.intellij.openapi.vfs.newvfs.impl.VirtualDirectoryImpl.findInPersistence(VirtualDirectoryImpl.java:149)
at com.intellij.openapi.vfs.newvfs.impl.VirtualDirectoryImpl.doFindChild(VirtualDirectoryImpl.java:131)
at com.intellij.openapi.vfs.newvfs.impl.VirtualDirectoryImpl.findChild(VirtualDirectoryImpl.java:77)
at com.intellij.openapi.vfs.newvfs.impl.VirtualDirectoryImpl.findChild(VirtualDirectoryImpl.java:500)
at com.intellij.openapi.vfs.newvfs.impl.VirtualDirectoryImpl.findChild(VirtualDirectoryImpl.java:44)
at com.intellij.openapi.vfs.VfsUtil.findRelativeFile(VfsUtil.java:182)
at com.android.tools.idea.projectsystem.ClassFileFinderUtil.findClassFileInOutputRoot(ClassFileFinder.kt:56)
at com.android.tools.idea.projectsystem.gradle.GradleClassFileFinder.findClassFileInModule(GradleClassFileFinder.java:60)
at com.android.tools.idea.project.ModuleBasedClassFileFinder.findClassFileInModuleWithLogging(ModuleBasedClassFileFinder.kt:76)
at com.android.tools.idea.project.ModuleBasedClassFileFinder.findClassFile(ModuleBasedClassFileFinder.kt:53)
at com.android.tools.idea.project.ModuleBasedClassFileFinder.findClassFile(ModuleBasedClassFileFinder.kt:41)
at org.jetbrains.android.uipreview.ModuleClassLoader.loadClassFromModuleOrDependency(ModuleClassLoader.java:428)
at org.jetbrains.android.uipreview.ModuleClassLoader.load(ModuleClassLoader.java:374)
at com.android.tools.idea.rendering.classloading.RenderClassLoader.findClass(RenderClassLoader.java:169)
at org.jetbrains.android.uipreview.ModuleClassLoader.findClass(ModuleClassLoader.java:332)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:589)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
at org.jetbrains.android.uipreview.ModuleClassLoader.loadClass(ModuleClassLoader.java:287)
at androidx.appcompat.app.AppCompatViewInflater.<clinit>(AppCompatViewInflater.java:93)
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490)
at android.view.BridgeInflater.createViewFromCustomInflater(BridgeInflater.java:255)
at android.view.BridgeInflater.onCreateView(BridgeInflater.java:122)
at android.view.LayoutInflater.onCreateView(LayoutInflater.java:928)
at android.view.LayoutInflater.onCreateView(LayoutInflater.java:948)
at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:1002)
at android.view.BridgeInflater.createViewFromTag(BridgeInflater.java:309)
at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:959)
at android.view.LayoutInflater.rInflate_Original(LayoutInflater.java:1121)
at android.view.LayoutInflater_Delegate.rInflate(LayoutInflater_Delegate.java:72)
at android.view.LayoutInflater.rInflate(LayoutInflater.java:1095)
at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:1082)
at android.view.LayoutInflater.inflate(LayoutInflater.java:680)
at android.view.LayoutInflater.inflate(LayoutInflater.java:499)
at com.android.layoutlib.bridge.impl.RenderSessionImpl.inflate(RenderSessionImpl.java:353)
at com.android.layoutlib.bridge.Bridge.createSession(Bridge.java:431)
at com.android.tools.idea.layoutlib.LayoutLibrary.createSession(LayoutLibrary.java:141)
at com.android.tools.idea.rendering.RenderTask.createRenderSession(RenderTask.java:714)
at com.android.tools.idea.rendering.RenderTask.lambda$inflate$7(RenderTask.java:870)
at com.android.tools.idea.rendering.RenderExecutor$runAsyncActionWithTimeout$2.run(RenderExecutor.kt:187)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)