feat(projects): implement project deserialization and transformation (2/2)

This commit is contained in:
Akash Yadav
2023-08-06 18:59:02 +05:30
parent e38348c9ff
commit e725d1cacf
44 changed files with 215 additions and 199 deletions
@@ -67,7 +67,7 @@ class QuickRunAction(context: Context) : BaseBuildAction() {
return@execTasks
}
val (_, main) = foundVariant.get()
val main = foundVariant.get().mainArtifact
val outputListingFile = main.assembleTaskOutputListingFile
if (outputListingFile == null) {
log.error("No output listing file provided with project model")
@@ -44,8 +44,8 @@ class FindInProjectAction() : EditorActivityAction() {
return
}
val root = ProjectManager.rootProject
if (root == null || root.subModules.isEmpty()) {
val project = ProjectManager.rootProject
if (project == null || project.subProjects.isEmpty()) {
markInvisible()
return
}
@@ -457,6 +457,7 @@ abstract class BaseEditorActivity :
}
private fun onBuildStatusChanged() {
log.debug("onBuildStatusChanged: isInitializing: ${viewModel.isInitializing}, isBuildInProgress: ${viewModel.isBuildInProgress}")
val visible = viewModel.isBuildInProgress || viewModel.isInitializing
binding.buildProgressIndicator.visibility = if (visible) View.VISIBLE else View.GONE
invalidateOptionsMenu()
@@ -44,7 +44,7 @@ import com.itsaky.androidide.projects.ProjectManager.projectInitialized
import com.itsaky.androidide.projects.ProjectManager.projectPath
import com.itsaky.androidide.projects.ProjectManager.rootProject
import com.itsaky.androidide.projects.ProjectManager.setupProject
import com.itsaky.androidide.projects.api.Project
import com.itsaky.androidide.projects.api.GradleProject
import com.itsaky.androidide.projects.builder.BuildService
import com.itsaky.androidide.services.builder.GradleBuildService
import com.itsaky.androidide.services.builder.GradleBuildServiceConnnection
@@ -214,7 +214,7 @@ abstract class ProjectHandlerActivity : BaseEditorActivity(), IProjectHandler {
override fun initializeProject() {
val projectDir = File(projectPath)
if (!projectDir.exists()) {
log.error("Project directory does not exist. Cannot initialize project")
log.error("GradleProject directory does not exist. Cannot initialize project")
return
}
@@ -250,7 +250,7 @@ abstract class ProjectHandlerActivity : BaseEditorActivity(), IProjectHandler {
// In this case, we should not start another project initialization
log.debug("Using cached initialize result as the project is already initialized")
CompletableFuture.supplyAsync {
log.warn("Project has already been initialized. Skipping initialization process.")
log.warn("GradleProject has already been initialized. Skipping initialization process.")
cachedInitResult
}
}
@@ -336,7 +336,7 @@ abstract class ProjectHandlerActivity : BaseEditorActivity(), IProjectHandler {
val moduleDirs =
try {
rootProject!!.subModules.stream().map(Project::projectDir).collect(Collectors.toList())
rootProject!!.subProjects.stream().map(GradleProject::projectDir).collect(Collectors.toList())
} catch (e: Throwable) {
flashError(getString(string.msg_no_modules))
emptyList()
@@ -429,13 +429,13 @@ abstract class ProjectHandlerActivity : BaseEditorActivity(), IProjectHandler {
private fun initialSetup() {
lastOpenedProject = getProjectDirPath()
try {
val rootProject = rootProject
if (rootProject == null) {
log.warn("Project not initialized. Skipping initial setup...")
val project = rootProject
if (project == null) {
log.warn("GradleProject not initialized. Skipping initial setup...")
return
}
var projectName = rootProject.name
var projectName = project.rootProject.name
if (projectName.isEmpty()) {
projectName = File(getProjectDirPath()).name
}
@@ -23,7 +23,7 @@ import androidx.recyclerview.widget.RecyclerView
import com.itsaky.androidide.adapters.RunTasksListAdapter.VH
import com.itsaky.androidide.databinding.LayoutRunTaskItemBinding
import com.itsaky.androidide.models.Checkable
import com.itsaky.androidide.tooling.api.model.GradleTask
import com.itsaky.androidide.tooling.api.models.GradleTask
/**
* Adapter for showing tasks list in [RunTaskDialogFragment]
@@ -49,7 +49,7 @@ import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.builder.BuildService
import com.itsaky.androidide.resources.R
import com.itsaky.androidide.tasks.executeAsync
import com.itsaky.androidide.tooling.api.model.GradleTask
import com.itsaky.androidide.tooling.api.models.GradleTask
import com.itsaky.androidide.utils.ILogger
import com.itsaky.androidide.utils.SingleTextWatcher
import com.itsaky.androidide.utils.doOnApplyWindowInsets
@@ -183,10 +183,11 @@ class RunTasksDialogFragment : BottomSheetDialogFragment() {
viewModel.displayedChild = CHILD_LOADING
executeAsync({
val project = ProjectManager.rootProject
val rootProject =
ProjectManager.rootProject ?: return@executeAsync emptyList<Checkable<GradleTask>>()
project?.rootProject ?: return@executeAsync emptyList<Checkable<GradleTask>>()
val tasks = rootProject.tasks.map { Checkable(false, it) }.toMutableList()
tasks.addAll(rootProject.subModules.flatMap { it.tasks }.map { Checkable(false, it) })
tasks.addAll(project.subProjects.flatMap { it.tasks }.map { Checkable(false, it) })
return@executeAsync tasks
}) { tasks ->
viewModel.tasks = tasks ?: emptyList()
@@ -18,7 +18,7 @@
package com.itsaky.androidide.models
import androidx.annotation.StringRes
import com.itsaky.androidide.tooling.api.model.GradleTask
import com.itsaky.androidide.tooling.api.models.GradleTask
/** @author Akash Yadav */
data class RunTasksCategory(@StringRes val title: Int, val tasks: List<Checkable<GradleTask>>)
@@ -220,7 +220,7 @@ public class GradleBuildService extends Service implements BuildService, IToolin
this.server = server;
Lookup.getDefault().update(BuildService.KEY_PROJECT_PROXY, projectProxy);
this.isToolingServerStarted = true;
ProjectManager.INSTANCE.setupProject(projectProxy);
ProjectManager.INSTANCE.setupProject(projectProxy, false);
}
@Override
@@ -72,7 +72,7 @@ internal class ToolingServerRunner(
"java.base/java.io=ALL-UNNAMED", // The JAR file to run
"-jar", Environment.TOOLING_API_JAR.absolutePath)
val launcher = ToolingApiLauncher.newClientLauncher(observer?.getClient(),
val launcher = ToolingApiLauncher.newClientLauncher(observer!!.getClient(),
serverStreams.`in`, serverStreams.out)
val future = launcher.startListening()
@@ -22,7 +22,7 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModel
import com.itsaky.androidide.models.Checkable
import com.itsaky.androidide.tooling.api.model.GradleTask
import com.itsaky.androidide.tooling.api.models.GradleTask
/** @author Akash Yadav */
class RunTasksViewModel : ViewModel() {
@@ -21,8 +21,9 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.itsaky.androidide.eventbus.events.project.ProjectInitializedEvent;
import com.itsaky.androidide.projects.api.Project;
import com.itsaky.androidide.projects.api.GradleProject;
import com.itsaky.androidide.projects.api.Project;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
@@ -52,8 +52,9 @@ import com.itsaky.androidide.lsp.models.SignatureHelpParams;
import com.itsaky.androidide.models.Range;
import com.itsaky.androidide.progress.ICancelChecker;
import com.itsaky.androidide.projects.ProjectManager;
import com.itsaky.androidide.projects.api.Project;
import com.itsaky.androidide.projects.api.GradleProject;
import com.itsaky.androidide.projects.api.Project;
import java.nio.file.Path;
import java.util.Collections;
@@ -63,6 +63,7 @@ import com.itsaky.androidide.progress.ICancelChecker;
import com.itsaky.androidide.projects.FileManager;
import com.itsaky.androidide.projects.ProjectManager;
import com.itsaky.androidide.projects.api.ModuleProject;
import com.itsaky.androidide.projects.api.GradleProject;
import com.itsaky.androidide.projects.api.Project;
import com.itsaky.androidide.utils.DocumentUtils;
import com.itsaky.androidide.utils.ILogger;
@@ -164,7 +165,7 @@ public class JavaLanguageServer implements ILanguageServer {
JavaCompilerProvider.getInstance().destroy();
// Cache classpath locations
for (final Project subModule : project.getSubModules()) {
for (final GradleProject subModule : project.getSubProjects()) {
if (!(subModule instanceof ModuleProject)) {
continue;
}
@@ -284,7 +285,7 @@ public class JavaLanguageServer implements ILanguageServer {
return JavaCompilerService.NO_MODULE_COMPILER;
}
final Project root = ProjectManager.INSTANCE.getRootProject();
final var root = ProjectManager.INSTANCE.getRootProject();
if (root == null) {
return JavaCompilerService.NO_MODULE_COMPILER;
}
@@ -36,6 +36,7 @@ import com.itsaky.androidide.models.Range;
import com.itsaky.androidide.projects.api.ModuleProject;
import com.itsaky.androidide.projects.util.StringSearch;
import com.itsaky.androidide.tooling.api.IProject;
import com.itsaky.androidide.tooling.api.ProjectType;
import com.itsaky.androidide.utils.ClassTrie;
import com.itsaky.androidide.utils.SourceClassTrie;
import com.itsaky.androidide.utils.StopWatch;
@@ -150,7 +151,7 @@ public class CompileBatch implements AutoCloseable {
// This won't be used if the current module is Android module project
System.setProperty(PROP_ANDROIDIDE_JAVA_HOME, JAVA_HOME.getAbsolutePath());
if (this.parent.module != null && this.parent.module.getType() == IProject.Type.Android) {
if (this.parent.module != null && this.parent.module.getType() == ProjectType.Android) {
setLatestSourceVersion(SourceVersion.RELEASE_8);
setLatestSupportedSourceVersion(SourceVersion.RELEASE_11);
disableModules();
@@ -44,8 +44,8 @@ import com.itsaky.androidide.lsp.xml.providers.CodeFormatProvider;
import com.itsaky.androidide.lsp.xml.providers.XmlCompletionProvider;
import com.itsaky.androidide.models.Range;
import com.itsaky.androidide.progress.ICancelChecker;
import com.itsaky.androidide.projects.api.Project;
import com.itsaky.androidide.projects.api.Project;
import com.itsaky.androidide.utils.DocumentUtils;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
@@ -22,7 +22,6 @@ import java.io.Serializable
/** @author Akash Yadav */
class DefaultJavaCompileOptions : IJavaCompilerSettings(), JavaCompileOptions, Serializable {
private val serialVersionUID = 1L
override var encoding: String = ""
override var isCoreLibraryDesugaringEnabled: Boolean = false
@@ -25,6 +25,7 @@ package com.itsaky.androidide.builder.model
abstract class IJavaCompilerSettings {
private val gsonType: String = javaClass.name
private val serialVersionUID = 1L
/** Source java version. */
abstract val javaSourceVersion: String
@@ -35,16 +35,16 @@ import com.itsaky.androidide.tooling.api.messages.result.InitializeResult
import com.itsaky.androidide.utils.DocumentUtils
import com.itsaky.androidide.utils.ILogger
import com.itsaky.androidide.utils.flashError
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode.ASYNC
import org.greenrobot.eventbus.ThreadMode.BACKGROUND
import java.io.File
import java.nio.file.Files
import java.nio.file.Path
import kotlin.io.path.extension
import kotlin.io.path.isDirectory
import kotlin.io.path.pathString
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode.ASYNC
import org.greenrobot.eventbus.ThreadMode.BACKGROUND
/**
* Manages projects in AndroidIDE.
@@ -52,6 +52,7 @@ import org.greenrobot.eventbus.ThreadMode.BACKGROUND
* @author Akash Yadav
*/
object ProjectManager : EventReceiver {
private val log = ILogger.newInstance(javaClass.simpleName)
lateinit var projectPath: String
@@ -62,12 +63,14 @@ object ProjectManager : EventReceiver {
var cachedInitResult: InitializeResult? = null
@JvmOverloads
fun setupProject(project: IProject = Lookup.getDefault().lookup(BuildService.KEY_PROJECT_PROXY)!!) {
val caching = CachingProject(project)
this.rootProject = ProjectTransformer().transform(caching)
fun setupProject(
project: IProject = Lookup.getDefault().lookup(BuildService.KEY_PROJECT_PROXY)!!,
isInitialized: Boolean = true
) {
this.rootProject = if (isInitialized) ProjectTransformer().transform(CachingProject(project)) else null
if (this.rootProject != null) {
this.app = this.rootProject!!.findFirstAndroidAppModule()
this.rootProject!!.subModules.filterIsInstance(ModuleProject::class.java).forEach {
this.rootProject!!.subProjects.filterIsInstance(ModuleProject::class.java).forEach {
it.indexSourcesAndClasspaths()
if (it is AndroidModule) {
it.readResources()
@@ -155,7 +158,7 @@ object ProjectManager : EventReceiver {
executeAsync {
if (rootProject != null) {
// Update the source file index
rootProject!!.subModules.forEach {
rootProject!!.subProjects.forEach {
if (it is ModuleProject) {
it.indexSources()
}
@@ -191,7 +194,7 @@ object ProjectManager : EventReceiver {
return false
}
for (module in this.rootProject!!.subModules) {
for (module in this.rootProject!!.subProjects) {
if (module !is ModuleProject) {
continue
}
@@ -212,7 +215,7 @@ object ProjectManager : EventReceiver {
return true
}
log.warn("Project is not initialized yet!")
log.warn("GradleProject is not initialized yet!")
return false
}
@@ -32,7 +32,6 @@ import com.itsaky.androidide.builder.model.DefaultSourceSetContainer
import com.itsaky.androidide.builder.model.DefaultViewBindingOptions
import com.itsaky.androidide.builder.model.UNKNOWN_PACKAGE
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.tooling.api.IAndroidProject
import com.itsaky.androidide.tooling.api.ProjectType.Android
import com.itsaky.androidide.tooling.api.models.BasicAndroidVariantMetadata
import com.itsaky.androidide.tooling.api.models.GradleTask
@@ -47,7 +46,7 @@ import java.io.File
import java.util.concurrent.CompletableFuture
/**
* A [Project] model implementation for Android modules which is exposed to other modules and
* A [GradleProject] model implementation for Android modules which is exposed to other modules and
* provides additional helper methods.
*
* @param name The display name of the project.
@@ -190,11 +189,11 @@ open class AndroidModule( // Class must be open because BaseXMLTest mocks this..
}
override fun getCompileClasspaths(): Set<File> {
val root = ProjectManager.rootProject ?: return emptySet()
val project = ProjectManager.rootProject ?: return emptySet()
val result = mutableSetOf<File>()
result.addAll(getModuleClasspaths())
collectLibraries(root, this.libraries, result)
collectLibraries(project, this.libraries, result)
return result
}
@@ -0,0 +1,54 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.projects.api
import com.android.builder.model.v2.ide.ProjectType.APPLICATION
import com.itsaky.androidide.tooling.api.ProjectType
import com.itsaky.androidide.tooling.api.models.GradleTask
import java.io.File
import java.nio.file.Path
import java.util.concurrent.CompletableFuture
/**
* A Gradle project model which is identical to [IGradleProject][com.itsaky.androidide.tooling.api.IGradleProject]. This project module caches all the data
* from an [IGradleProject][com.itsaky.androidide.tooling.api.IGradleProject] eliminating the use of [CompletableFuture] s.
*
* @param name The display name of the project.
* @param description The project description.
* @param path The project path (same as Gradle project paths). For example, `:app`,
* `:module:submodule`, etc. Root project is always represented by path `:`.
* @param projectDir The project directory.
* @param buildDir The build directory of the project.
* @param buildScript The Gradle buildscript file of the project.
* @param tasks The tasks of the project.
* @param subModules The submodules of the project.
* @author Akash Yadav
*/
open class GradleProject(
val name: String,
val description: String,
val path: String,
val projectDir: File,
val buildDir: File,
val buildScript: File,
val tasks: List<GradleTask>
) {
var type: ProjectType = ProjectType.Gradle
protected set
}
@@ -28,7 +28,7 @@ import com.itsaky.androidide.tooling.api.models.JavaModuleDependency
import java.io.File
/**
* A [Project] model implementation for Java library modules which is exposed to other modules and
* A [GradleProject] model implementation for Java library modules which is exposed to other modules and
* provides additional helper methods.
*
* @param name The display name of the project.
@@ -23,7 +23,6 @@ import com.itsaky.androidide.javac.services.fs.CacheFSInfoSingleton
import com.itsaky.androidide.lookup.Lookup
import com.itsaky.androidide.projects.classpath.JarFsClasspathReader
import com.itsaky.androidide.projects.util.BootClasspathProvider
import com.itsaky.androidide.tooling.api.IModuleProject
import com.itsaky.androidide.tooling.api.models.GradleTask
import com.itsaky.androidide.utils.ClassTrie
import com.itsaky.androidide.utils.DocumentUtils
@@ -49,7 +48,7 @@ abstract class ModuleProject(
buildScript: File,
tasks: List<GradleTask>
) :
Project(name, description, path, projectDir, buildDir, buildScript, tasks) {
GradleProject(name, description, path, projectDir, buildDir, buildScript, tasks) {
private val log = ILogger.newInstance(javaClass.simpleName)
@@ -17,55 +17,29 @@
package com.itsaky.androidide.projects.api
import com.android.builder.model.v2.ide.ProjectType.APPLICATION
import com.itsaky.androidide.tooling.api.IProject
import com.itsaky.androidide.tooling.api.ProjectType
import com.itsaky.androidide.tooling.api.models.GradleTask
import com.android.builder.model.v2.ide.ProjectType
import com.itsaky.androidide.builder.model.DefaultProjectSyncIssues
import java.io.File
import java.nio.file.Path
import java.util.concurrent.*
/**
* A Gradle project model which is identical to [IProject]. This project module caches all the data
* from an [IProject] eliminating the use of [CompletableFuture] s.
* Model for representing the whole project that is opened in the IDE (including the root project).
*
* @param name The display name of the project.
* @param description The project description.
* @param path The project path (same as Gradle project paths). For example, `:app`,
* `:module:submodule`, etc. Root project is always represented by path `:`.
* @param projectDir The project directory.
* @param buildDir The build directory of the project.
* @param buildScript The Gradle buildscript file of the project.
* @param parentPath The parent project path. Always `null` for root project.
* @param tasks The tasks of the project.
* @param subModules The submodules of the project.
* @property rootProject The root Gradle project.
* @property subProjects List of all project that are included the project.
* @property syncIssues The issues that occurred while syncing the project.
* @author Akash Yadav
*/
open class Project(
val name: String,
val description: String,
val path: String,
val projectDir: File,
val buildDir: File,
val buildScript: File,
val tasks: List<GradleTask>,
val subModules: List<Project> = mutableListOf()
) {
var type: ProjectType = ProjectType.Gradle
protected set
data class Project(val rootProject: GradleProject, val subProjects: List<GradleProject>,
val syncIssues: Map<String, DefaultProjectSyncIssues>) {
/**
* Finds the project by the given path.
*
* @return The project with the given path or `null` if no project is available with that path.
*/
fun findByPath(path: String): Project? {
if (path == this.path) {
return this
}
return this.subModules.find { it.path == path }
fun findByPath(path: String): GradleProject? {
return this.subProjects.find { it.path == path }
}
/**
@@ -75,10 +49,7 @@ open class Project(
* @return The first Android module.
*/
fun findFirstAndroidModule(): AndroidModule? {
if (this is AndroidModule) {
return this
}
val android = this.subModules.find { it is AndroidModule }
val android = this.subProjects.find { it is AndroidModule }
if (android != null) {
return android as AndroidModule
}
@@ -93,10 +64,7 @@ open class Project(
* @return The first Android module.
*/
fun findFirstAndroidAppModule(): AndroidModule? {
if (this is AndroidModule && this.projectType == APPLICATION) {
return this
}
val app = this.subModules.find { it is AndroidModule && it.projectType == APPLICATION }
val app = this.subProjects.find { it is AndroidModule && it.projectType == ProjectType.APPLICATION }
if (app != null) {
return app as AndroidModule
@@ -111,12 +79,7 @@ open class Project(
* @return The list of android modules.
*/
fun findAndroidModules(): List<AndroidModule> {
val androidModules = this.subModules.filterIsInstance(AndroidModule::class.java).toMutableList()
if (this is AndroidModule) {
androidModules.add(0, this)
}
return androidModules
return subProjects.filterIsInstance(AndroidModule::class.java).toMutableList()
}
fun findModuleForFile(file: Path): ModuleProject? {
@@ -134,7 +97,7 @@ open class Project(
var longestPath = ""
var moduleWithLongestPath: ModuleProject? = null
for (module in subModules) {
for (module in subProjects) {
if (module !is ModuleProject) {
continue
}
@@ -152,4 +115,4 @@ open class Project(
return moduleWithLongestPath
}
}
}
@@ -1,32 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.projects.api
import com.itsaky.androidide.tooling.api.messages.result.SimpleModuleData
/**
* Any project model which contains
* [com.itsaky.androidide.tooling.api.messages.result.SimpleModuleData].
*
* @author Akash Yadav
*/
interface WithModuleData {
/** The module data. */
var moduleData: SimpleModuleData?
}
@@ -18,9 +18,11 @@
package com.itsaky.androidide.projects.util
import com.itsaky.androidide.projects.api.AndroidModule
import com.itsaky.androidide.projects.api.GradleProject
import com.itsaky.androidide.projects.api.JavaModule
import com.itsaky.androidide.projects.api.Project
import com.itsaky.androidide.tooling.api.IAndroidProject
import com.itsaky.androidide.tooling.api.IGradleProject
import com.itsaky.androidide.tooling.api.IJavaProject
import com.itsaky.androidide.tooling.api.IProject
import com.itsaky.androidide.tooling.api.ProjectType
@@ -49,32 +51,36 @@ class ProjectTransformer {
}
val rootProject = when (project.getType().get()) {
ProjectType.Gradle -> project.asGradleProject()
ProjectType.Android -> project.asGradleProject()
ProjectType.Gradle -> transform(project.asGradleProject())
ProjectType.Android -> transform(project.asAndroidProject())
else -> throw IllegalStateException(
"Root project must be either an Android project or a Gradle project")
}
val metadata = rootProject.getMetadata().get()
return Project(
name = metadata.name ?: IProject.PROJECT_UNKNOWN,
description = metadata.description ?: "",
path = metadata.projectPath,
projectDir = metadata.projectDir,
buildDir = metadata.buildDir,
buildScript = metadata.buildScript,
// As these lists will never change, we could make these thread-safe with
// CopyOnWriteArrayList
tasks = CopyOnWriteArrayList(rootProject.getTasks().get() ?: listOf()),
subModules = CopyOnWriteArrayList(transform(allProjects, project))
)
return Project(rootProject, CopyOnWriteArrayList(transform(allProjects, project)),
project.getProjectSyncIssues().get())
} catch (error: Throwable) {
log.error("Unable to transform project", error)
return null
}
}
private fun transform(rootProject: IGradleProject): GradleProject {
val metadata = rootProject.getMetadata().get()
return GradleProject(
name = metadata.name ?: IProject.PROJECT_UNKNOWN,
description = metadata.description ?: "",
path = metadata.projectPath,
projectDir = metadata.projectDir,
buildDir = metadata.buildDir,
buildScript = metadata.buildScript,
// The list will never change, we could make these thread-safe with
// CopyOnWriteArrayList
tasks = CopyOnWriteArrayList(rootProject.getTasks().get() ?: listOf()),
)
}
private fun transform(
project: IAndroidProject
): AndroidModule {
@@ -109,7 +115,7 @@ class ProjectTransformer {
}
private fun transform(project: IJavaProject): JavaModule {
val metadata = project.getMetadata() as JavaProjectMetadata
val metadata = project.getMetadata().get() as JavaProjectMetadata
return JavaModule(
name = metadata.name ?: IProject.PROJECT_UNKNOWN,
description = metadata.description ?: "",
@@ -125,15 +131,15 @@ class ProjectTransformer {
)
}
private fun transform(modules: List<BasicProjectMetadata>, root: IProject): List<Project> {
return mutableListOf<Project>().apply {
private fun transform(modules: List<BasicProjectMetadata>, root: IProject): List<GradleProject> {
return mutableListOf<GradleProject>().apply {
for (module in modules) {
add(createProject(module, root))
}
}
}
private fun createProject(moduleMetadata: BasicProjectMetadata, root: IProject): Project {
private fun createProject(moduleMetadata: BasicProjectMetadata, root: IProject): GradleProject {
val selectionResult = root.selectProject(StringParameter(moduleMetadata.projectPath)).get()
check(selectionResult.isSuccessful) {
"Selection failed for project '${moduleMetadata.projectPath}' but it is included in all projects."
@@ -143,9 +149,10 @@ class ProjectTransformer {
return when (type) {
ProjectType.Gradle,
ProjectType.Unknown -> transform(project)!!
ProjectType.Android -> transform(project as IAndroidProject)
ProjectType.Java -> transform(project as IJavaProject)
ProjectType.Unknown -> transform(root.asGradleProject())
ProjectType.Android -> transform(root.asAndroidProject())
ProjectType.Java -> transform(root.asJavaProject())
}
}
}
@@ -24,6 +24,7 @@ import com.itsaky.androidide.eventbus.events.file.FileRenameEvent
import com.itsaky.androidide.lookup.Lookup
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.builder.BuildService
import com.itsaky.androidide.tooling.api.IAndroidProject
import com.itsaky.androidide.tooling.api.messages.InitializeProjectMessage
import com.itsaky.androidide.tooling.testing.ToolingApiTestLauncher
import com.itsaky.androidide.utils.FileProvider
@@ -51,15 +52,20 @@ class ModuleProjectTest {
verifyProjectManagerAPIs()
val root = ProjectManager.rootProject
assertThat(project).isNotNull()
val rootProject = ProjectManager.rootProject
assertThat(rootProject).isNotNull()
val root = rootProject!!.rootProject
assertThat(root).isNotNull()
assertThat(root!!.name).isEqualTo("TestApp")
assertThat(root.name).isEqualTo("TestApp")
val rootDir = root.projectDir
assertThat(rootDir).isNotNull()
assertThat(Files.isSameFile(rootDir.toPath(), FileProvider.testProjectRoot())).isTrue()
val app = root.findByPath(":app")
val app = rootProject.findByPath(":app")
assertThat(app).isNotNull()
assertThat(app).isInstanceOf(AndroidModule::class.java)
@@ -75,7 +81,7 @@ class ModuleProjectTest {
assertThat(app.compilerSettings.javaBytecodeVersion)
.isEqualTo(app.compilerSettings.targetCompatibility)
val anotherAndroidLib = root.findByPath(":another-android-library")
val anotherAndroidLib = rootProject.findByPath(":another-android-library")
assertThat(anotherAndroidLib).isNotNull()
assertThat(anotherAndroidLib).isInstanceOf(AndroidModule::class.java)
@@ -90,11 +96,11 @@ class ModuleProjectTest {
.containsAtLeast(
File(
anotherAndroidLibDir,
"build/${com.itsaky.androidide.tooling.api.model.AndroidModule.FD_INTERMEDIATES}/compile_library_classes_jar/debug/classes.jar"
"build/${IAndroidProject.FD_INTERMEDIATES}/compile_library_classes_jar/debug/classes.jar"
),
File(
anotherAndroidLibDir,
"build/${com.itsaky.androidide.tooling.api.model.AndroidModule.FD_INTERMEDIATES}/compile_r_class_jar/debug/R.jar"
"build/${IAndroidProject.FD_INTERMEDIATES}/compile_r_class_jar/debug/R.jar"
)
)
assertThat(
@@ -129,15 +135,15 @@ class ModuleProjectTest {
.containsAtLeast(
File(
anotherAndroidLibDir,
"build/${com.itsaky.androidide.tooling.api.model.AndroidModule.FD_INTERMEDIATES}/compile_library_classes_jar/debug/classes.jar"
"build/${IAndroidProject.FD_INTERMEDIATES}/compile_library_classes_jar/debug/classes.jar"
),
File(
app.projectDir,
"build/${com.itsaky.androidide.tooling.api.model.AndroidModule.FD_INTERMEDIATES}/compile_and_runtime_not_namespaced_r_class_jar/debug/R.jar"
"build/${IAndroidProject.FD_INTERMEDIATES}/compile_and_runtime_not_namespaced_r_class_jar/debug/R.jar"
),
File(
anotherAndroidLibDir,
"build/${com.itsaky.androidide.tooling.api.model.AndroidModule.FD_INTERMEDIATES}/compile_r_class_jar/debug/R.jar"
"build/${IAndroidProject.FD_INTERMEDIATES}/compile_r_class_jar/debug/R.jar"
)
)
assertThat(
@@ -150,7 +156,7 @@ class ModuleProjectTest {
.hasSize(3)
}
val javaLibrary = root.findByPath(":java-library")
val javaLibrary = rootProject.findByPath(":java-library")
assertThat(javaLibrary).isNotNull()
assertThat(javaLibrary).isInstanceOf(JavaModule::class.java)
@@ -172,7 +178,7 @@ class ModuleProjectTest {
assertThat(classes.map { it::class.java })
.containsExactly(SourceNode::class.java, SourceNode::class.java)
root.findByPath(":another-java-library").run {
rootProject.findByPath(":another-java-library").run {
assertThat(this).isInstanceOf(JavaModule::class.java)
assertThat((this as JavaModule).compilerSettings).isNotNull()
assertThat(compilerSettings.javaSourceVersion).isEqualTo("1.8")
@@ -199,13 +205,16 @@ class ModuleProjectTest {
ProjectManager.setupProject()
val root = ProjectManager.rootProject
val rootProject = ProjectManager.rootProject
assertThat(rootProject).isNotNull()
val root = rootProject?.rootProject
assertThat(root).isNotNull()
assertThat(root!!.path).isEqualTo(":")
val source =
root.projectDir.toPath().resolve("app/src/main/java/com/itsaky/test/app/MainActivity.java")
val module = root.findModuleForFile(source)
val module = rootProject.findModuleForFile(source)
assertThat(module).isNotNull()
assertThat(module!!.path).isEqualTo(":app")
assertThat(module.projectDir.path).isEqualTo(File(root.projectDir, "app").path)
@@ -17,7 +17,6 @@
package com.itsaky.androidide.tooling.impl
import com.itsaky.androidide.builder.model.DefaultProjectSyncIssues
import com.itsaky.androidide.tooling.api.IProject
import com.itsaky.androidide.tooling.api.IToolingApiClient
import com.itsaky.androidide.tooling.api.IToolingApiServer
@@ -34,7 +33,6 @@ import com.itsaky.androidide.tooling.api.messages.result.TaskExecutionResult.Fai
import com.itsaky.androidide.tooling.api.messages.result.TaskExecutionResult.Failure.BUILD_FAILED
import com.itsaky.androidide.tooling.api.messages.result.TaskExecutionResult.Failure.CONNECTION_CLOSED
import com.itsaky.androidide.tooling.api.messages.result.TaskExecutionResult.Failure.CONNECTION_ERROR
import com.itsaky.androidide.tooling.api.messages.result.TaskExecutionResult.Failure.PROJECT_NOT_FOUND
import com.itsaky.androidide.tooling.api.messages.result.TaskExecutionResult.Failure.UNKNOWN
import com.itsaky.androidide.tooling.api.messages.result.TaskExecutionResult.Failure.UNSUPPORTED_BUILD_ARGUMENT
import com.itsaky.androidide.tooling.api.messages.result.TaskExecutionResult.Failure.UNSUPPORTED_CONFIGURATION
@@ -55,7 +53,8 @@ import org.gradle.tooling.UnsupportedVersionException
import org.gradle.tooling.exceptions.UnsupportedBuildArgumentException
import org.gradle.tooling.exceptions.UnsupportedOperationConfigurationException
import java.io.File
import java.util.concurrent.*
import java.util.concurrent.CompletableFuture
import java.util.concurrent.CompletionException
/**
* Implementation for the Gradle Tooling API server.
@@ -104,21 +103,19 @@ internal class ToolingApiServerImpl(private val project: ProjectImpl) :
val connection = this.connector!!.connect()
stopWatch.lapFromLast("Project connection established")
val issues: MutableMap<String, DefaultProjectSyncIssues> = mutableMapOf()
val project = RootModelBuilder.build(connection to "debug") as? ProjectImpl?
project ?: throw ModelBuilderException("Failed to build project model")
this.project.rootProject = project.rootProject
this.project.projects = project.projects
stopWatch.lapFromLast("Project read successful")
connection.close()
stopWatch.log()
this.project.rootProject = project.rootProject
this.project.projects = project.projects
initialized = true
notifyBuildSuccess(emptyList())
return@computeAsync InitializeResult(issues)
return@computeAsync InitializeResult(emptyMap())
} catch (err: Throwable) {
log.error(err)
notifyBuildFailure(emptyList())
@@ -151,10 +148,10 @@ internal class ToolingApiServerImpl(private val project: ProjectImpl) :
projectPath = IProject.ROOT_PROJECT_PATH
}
val project = this.project!!.run {
val project = this.project.run {
selectProject(StringParameter(projectPath)).get()
asGradleProject()
} ?: return@computeAsync TaskExecutionResult(false, PROJECT_NOT_FOUND)
}
this.connector!!.forProjectDirectory(project.getMetadata().get().projectDir)
setupConnectorForGradleInstallation(this.connector!!, message.gradleInstallation)
@@ -28,7 +28,7 @@ import java.util.concurrent.CompletableFuture
/**
* @author Akash Yadav
*/
internal open class GradleProjectImpl(
internal open class GradleProjectImpl (
protected val gradleProject: GradleProject
) : IGradleProject, Serializable {
@@ -106,7 +106,10 @@ internal class JavaProjectImpl(
}
private fun getClassesJar(): File {
val metadata = getMetadata().get()
return getClassesJar(getMetadata().get())
}
private fun getClassesJar(metadata: ProjectMetadata): File {
var jar = File(metadata.buildDir, "libs/${metadata.name}.jar")
if (jar.exists()) {
return jar
@@ -128,7 +131,11 @@ internal class JavaProjectImpl(
override fun getMetadata(): CompletableFuture<ProjectMetadata> {
return CompletableFuture.supplyAsync {
val base = super.getMetadata().get()
return@supplyAsync JavaProjectMetadata(base, compilerSettings, getClassesJar())
// do not call getClassesJar() here
// it'll try to fetch metadata which will in return call this method
// this will result in an infinite loop
return@supplyAsync JavaProjectMetadata(base, compilerSettings, getClassesJar(base))
}
}
}
@@ -40,5 +40,6 @@ data class AndroidArtifactMetadata(
val compileTaskName: String,
val targetSdkVersionOverride: Int
) : Serializable {
private val gsonType: String = javaClass.name
private val serialVersionUID = 1L
}
@@ -21,7 +21,6 @@ import com.itsaky.androidide.builder.model.DefaultAndroidGradlePluginProjectFlag
import com.itsaky.androidide.builder.model.DefaultJavaCompileOptions
import com.itsaky.androidide.builder.model.DefaultViewBindingOptions
import com.itsaky.androidide.tooling.api.ProjectType
import com.itsaky.androidide.tooling.api.models.result.IModuleMetadata
import java.io.File
/**
@@ -30,6 +30,4 @@ open class AndroidVariantMetadata(
name: String,
mainArtifact: AndroidArtifactMetadata,
val otherArtifacts: Map<String, AndroidArtifactMetadata>
) : BasicAndroidVariantMetadata(name, mainArtifact), Serializable {
private val serialVersionUID = 1L
}
) : BasicAndroidVariantMetadata(name, mainArtifact)
@@ -37,5 +37,5 @@ open class BasicProjectMetadata(
) : Serializable {
protected val gsonType: String = javaClass.name
private val serialVersionUID = 1L
protected val serialVersionUID = 1L
}
@@ -17,10 +17,15 @@
package com.itsaky.androidide.tooling.api.models
import java.io.Serializable
/**
* Represents a Gradle dependency artifact.
*
* @author Akash Yadav
*/
data class GradleArtifact(val group: String, val name: String, val version: String) :
java.io.Serializable
data class GradleArtifact(val group: String, val name: String, val version: String) : Serializable {
private val gsonType: String = javaClass.name
private val serialVersionUID = 1L
}
@@ -37,4 +37,4 @@ open class GradleTask(
displayName: String?,
isPublic: Boolean?,
val projectPath: String?
) : Launchable(displayName, isPublic ?: false), Serializable
) : Launchable(displayName, isPublic ?: false)
@@ -15,7 +15,7 @@
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.tooling.api.models.result
package com.itsaky.androidide.tooling.api.models
import java.io.File
@@ -29,8 +29,6 @@ class JavaModuleCompilerSettings(
override val javaBytecodeVersion: String
) : IJavaCompilerSettings(), java.io.Serializable {
private val serialVersionUID = 1L
// IMPORTANT
// Do not use javax.lang.model.SourceVersion reference here
// When running on Android, this class is preset as jdkx.lang.model.SourceVersion
@@ -37,4 +37,4 @@ class JavaModuleProjectDependency(
/** Whether the dependency is exported. */
exported: Boolean
) : JavaModuleDependency(null, scope, exported), Serializable {}
) : JavaModuleDependency(null, scope, exported), Serializable
@@ -19,7 +19,6 @@ package com.itsaky.androidide.tooling.api.models
import com.itsaky.androidide.builder.model.IJavaCompilerSettings
import com.itsaky.androidide.tooling.api.ProjectType
import com.itsaky.androidide.tooling.api.models.result.IModuleMetadata
import java.io.File
/**
@@ -38,7 +37,8 @@ class JavaProjectMetadata(
type: ProjectType,
val compilerSettings: IJavaCompilerSettings,
override val classesJar: File?
) : ProjectMetadata(name, path, projectDir, buildDir, description, buildScript, type), IModuleMetadata {
) : ProjectMetadata(name, path, projectDir, buildDir, description, buildScript, type),
IModuleMetadata {
constructor(base: ProjectMetadata, compilerSettings: IJavaCompilerSettings, classesJar: File?) :
this(
@@ -25,4 +25,7 @@ import java.io.Serializable
*
* @author Akash Yadav
*/
class JavaSourceDirectory(val directory: File, val isGenerated: Boolean) : Serializable {}
class JavaSourceDirectory(val directory: File, val isGenerated: Boolean) : Serializable {
private val gsonType: String = javaClass.name
private val serialVersionUID = 1L
}
@@ -196,11 +196,11 @@ object ToolingApiLauncher {
}
private fun <T> GsonBuilder.runtimeTypeAdapter(baseClass: Class<T>,
vararg subtype: Class<out T>) {
vararg subtypes: Class<out T>) {
registerTypeAdapterFactory(
RuntimeTypeAdapterFactory.of(baseClass, "gsonType", true)
.registerSubtype(baseClass).also { factory ->
subtype.forEach { subtype ->
.registerSubtype(baseClass, baseClass.name).also { factory ->
subtypes.forEach { subtype ->
factory.registerSubtype(subtype, subtype.name)
}
}
@@ -93,7 +93,7 @@ private fun generateTemplateProject(name: String, languages: Array<Language>,
for (language in languages) {
val packageName = "com.itsaky.androidide.template.${language.lang}"
run {
val projectName = "${name}Project${language.name}WithoutKts"
val projectName = "${name}GradleProject${language.name}WithoutKts"
File(testProjectsDir, projectName).apply {
if (exists()) deleteRecursively()
@@ -108,7 +108,7 @@ private fun generateTemplateProject(name: String, languages: Array<Language>,
}
run {
val projectName = "${name}Project${language.name}WithKts"
val projectName = "${name}GradleProject${language.name}WithKts"
File(testProjectsDir, projectName).apply {
if (exists()) deleteRecursively()
@@ -93,7 +93,7 @@ fun processXmlFile(file: File, expectedType: AaptResourceType): Pair<XmlProcesso
}
if (ProjectManager.rootProject == null) {
throw InflateException("Project is not initialized!")
throw InflateException("GradleProject is not initialized!")
}
val module =
@@ -228,7 +228,7 @@ class LayoutInflaterTest {
}
private fun layoutFile(name: String): File {
val app = ProjectManager.app ?: throw IllegalStateException("Project is not initialized")
val app = ProjectManager.app ?: throw IllegalStateException("GradleProject is not initialized")
return File(app.projectDir, "src/main/res/layout/$name.xml")
}