Added support for resolving dependencies of java library projects

This commit is contained in:
Akash Yadav
2022-05-01 13:02:15 +05:30
parent 580707590c
commit 58849d528a
31 changed files with 680 additions and 168 deletions
Binary file not shown.
@@ -105,13 +105,13 @@ import com.itsaky.androidide.services.GradleBuildService;
import com.itsaky.androidide.services.LogReceiver;
import com.itsaky.androidide.shell.ShellServer;
import com.itsaky.androidide.tooling.api.messages.result.TaskExecutionResult;
import com.itsaky.androidide.tooling.api.model.IdeAndroidModule;
import com.itsaky.androidide.tooling.api.model.IdeGradleProject;
import com.itsaky.androidide.utils.DialogUtils;
import com.itsaky.androidide.utils.EditorActivityActions;
import com.itsaky.androidide.utils.EditorBottomSheetBehavior;
import com.itsaky.androidide.utils.Environment;
import com.itsaky.androidide.utils.ILogger;
import com.itsaky.androidide.utils.JSONUtility;
import com.itsaky.androidide.utils.LSPUtils;
import com.itsaky.androidide.utils.RecursiveFileSearcher;
import com.itsaky.androidide.utils.Symbols;
@@ -121,7 +121,6 @@ import com.itsaky.androidide.views.SymbolInputView;
import com.itsaky.androidide.views.editor.CodeEditorView;
import com.itsaky.androidide.views.editor.IDEEditor;
import com.itsaky.inflater.values.ValuesTableFactory;
import com.itsaky.lsp.java.models.JavaServerConfiguration;
import com.itsaky.lsp.java.models.JavaServerSettings;
import com.itsaky.lsp.models.DiagnosticItem;
import com.itsaky.lsp.models.InitializeParams;
@@ -139,7 +138,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.function.Consumer;
@@ -955,43 +953,43 @@ public class EditorActivity extends StudioActivity
@NonNull String variantName) {
return task -> {
if (task != null) {
// TODO Handle multiple application modules
final var app = mRootProject.findFirstApplicationModule();
if (app == null) {
LOG.error("No application module found in root project. Cannot install APKs");
return;
}
final var variants = app.getVariants();
final var foundVariant =
variants.stream()
.filter(variant -> variant.getName().equals(variantName))
.findFirst();
if (foundVariant.isPresent()) {
final var variant = foundVariant.get();
final var main = variant.getMainArtifact();
final var outputListingFile = main.getAssembleTaskOutputListingFile();
if (outputListingFile == null) {
LOG.error("No output listing file provided with project model");
return;
}
final var apkFile = ApkMetadata.findApkFile(outputListingFile);
if (apkFile == null) {
LOG.error(
"No apk file specified in output listing file:", outputListingFile);
return;
}
install(apkFile);
} else {
LOG.error(
"No",
variantName,
"variant found in application module",
app.getProjectPath());
}
// // TODO Handle multiple application modules
// final var app = mRootProject.findFirstApplicationModule();
// if (app == null) {
// LOG.error("No application module found in root project. Cannot install APKs");
// return;
// }
//
// final var variants = app.getVariants();
// final var foundVariant =
// variants.stream()
// .filter(variant -> variant.getName().equals(variantName))
// .findFirst();
//
// if (foundVariant.isPresent()) {
// final var variant = foundVariant.get();
// final var main = variant.getMainArtifact();
// final var outputListingFile = main.getAssembleTaskOutputListingFile();
// if (outputListingFile == null) {
// LOG.error("No output listing file provided with project model");
// return;
// }
//
// final var apkFile = ApkMetadata.findApkFile(outputListingFile);
// if (apkFile == null) {
// LOG.error(
// "No apk file specified in output listing file:", outputListingFile);
// return;
// }
//
// install(apkFile);
// } else {
// LOG.error(
// "No",
// variantName,
// "variant found in application module",
// app.getProjectPath());
// }
}
};
}
@@ -1295,27 +1293,13 @@ public class EditorActivity extends StudioActivity
mRootProject = root;
CompletableFuture.runAsync(
() -> {
final var app = mBuildService.findFirstAndroidModule(root);
if (app == null) {
LOG.error(
"No Android application module is present in root project:",
root.getName());
return;
}
// Notify Java language server about updated dependencies
// TODO Java language server must also be notified about updated source
// paths
final var debugDependencies =
app.getVariantDependencyJars().get("debug");
getApp().getJavaLanguageServer()
.configurationChanged(
new JavaServerConfiguration(
debugDependencies.stream()
.filter(File::exists)
.map(File::toPath)
.collect(Collectors.toSet()),
collectSourcePaths(app)));
// final var app = mBuildService.findFirstAndroidModule(root);
// if (app == null) {
// LOG.error(
// "No Android application module is present in root project:",
// root.getName());
// return;
// }
})
.whenComplete(
(result, error) -> {
@@ -1327,17 +1311,6 @@ public class EditorActivity extends StudioActivity
ThreadUtils.runOnUiThread(() -> mBinding.buildProgressIndicator.setVisibility(View.GONE));
}
private Set<Path> collectSourcePaths(IdeAndroidModule app) {
final var paths = new HashSet<Path>();
final var mainSourceProvider = app.getMainSourceSet().getSourceProvider();
paths.addAll(
mainSourceProvider.getJavaDirectories().stream()
.map(File::toPath)
.collect(Collectors.toList()));
return paths;
}
private void setupDrawerToggle() {
ActionBarDrawerToggle toggle =
new ActionBarDrawerToggle(
@@ -238,31 +238,31 @@ public class GradleBuildService extends Service implements BuildService, IToolin
public boolean isBuildInProgress() {
return isBuildInProgress;
}
/**
* Find the first direct subproject of the given root project that is an {@link
* IdeAndroidModule}.
*
* @param root The project whose subprojects will be searched.
* @return The found {@link IdeAndroidModule} or <code>null</code>.
*/
@Nullable
public IdeAndroidModule findFirstAndroidModule(IdeGradleProject root) {
return root.findFirstApplicationModule();
}
/**
* Find all the direct children of the given root project that are {@link IdeAndroidModule} and
* their <code>projectType</code> is {@link
* com.android.builder.model.v2.ide.ProjectType#APPLICATION APPLICATION}
*
* @param root The project whose subprojects will be searched.
* @return The found application modules. Never <code>null</code>.
*/
@NonNull
public List<IdeAndroidModule> findAndroidModules(@NonNull IdeGradleProject root) {
return root.findApplicationModules();
}
//
// /**
// * Find the first direct subproject of the given root project that is an {@link
// * IdeAndroidModule}.
// *
// * @param root The project whose subprojects will be searched.
// * @return The found {@link IdeAndroidModule} or <code>null</code>.
// */
// @Nullable
// public IdeAndroidModule findFirstAndroidModule(IdeGradleProject root) {
// return root.findFirstApplicationModule();
// }
//
// /**
// * Find all the direct children of the given root project that are {@link IdeAndroidModule} and
// * their <code>projectType</code> is {@link
// * com.android.builder.model.v2.ide.ProjectType#APPLICATION APPLICATION}
// *
// * @param root The project whose subprojects will be searched.
// * @return The found application modules. Never <code>null</code>.
// */
// @NonNull
// public List<IdeAndroidModule> findAndroidModules(@NonNull IdeGradleProject root) {
// return root.findApplicationModules();
// }
public void startToolingServer(@Nullable OnServerStartListener listener) {
if (toolingServerThread != null && toolingServerThread.isAlive()) {
+1
View File
@@ -0,0 +1 @@
/build
+58
View File
@@ -0,0 +1,58 @@
/*
* 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/>.
*/
plugins {
id 'com.android.library'
id 'kotlin-android'
}
android {
compileSdkVersion rootProject.ext.compileSdk
buildToolsVersion rootProject.ext.buildTools
defaultConfig {
minSdkVersion rootProject.ext.minSdk
targetSdkVersion rootProject.ext.targetSdk
}
buildFeatures.viewBinding true
compileOptions {
sourceCompatibility rootProject.ext.javaSourceVersion
targetCompatibility rootProject.ext.javaTargetVersion
}
buildTypes {
release {
minifyEnabled true
}
}
}
dependencies {
implementation "androidx.core:core-ktx:1.7.0"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation project (path: ':build-tools:tooling-api-model')
implementation project (path: ':lsp:api')
implementation project (path: ':lsp:java')
implementation project (path: ':lsp:xml')
testImplementation tests.junit
testImplementation tests.google_truth
testImplementation tests.robolectric
}
+21
View File
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
@@ -0,0 +1,43 @@
/*
* 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.projects;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext () {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation ().getTargetContext ();
assertEquals ("com.itsaky.projects.test", appContext.getPackageName ());
}
}
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ 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/>.
-->
<manifest package="com.itsaky.androidide.projects" />
@@ -0,0 +1,34 @@
/*
* 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
import com.itsaky.androidide.tooling.api.model.IdeGradleProject
import com.itsaky.lsp.java.JavaLanguageServer
/**
* Manages projects in AndroidIDE.
*
* @author Akash Yadav
*/
object ProjectManager {
var rootProject: IdeGradleProject? = null
fun notifyProjectUpdate (server: JavaLanguageServer) {
rootProject!!
}
}
@@ -0,0 +1,34 @@
/*
* 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.projects;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect () {
assertEquals (4, 2 + 2);
}
}
@@ -26,6 +26,12 @@ import com.android.builder.model.v2.models.VariantDependencies;
import com.itsaky.androidide.tooling.api.model.IdeAndroidModule;
import com.itsaky.androidide.tooling.api.model.IdeGradleProject;
import com.itsaky.androidide.tooling.api.model.IdeGradleTask;
import com.itsaky.androidide.tooling.api.model.IdeJavaModule;
import com.itsaky.androidide.tooling.api.model.JavaContentRoot;
import com.itsaky.androidide.tooling.api.model.JavaModuleDependency;
import com.itsaky.androidide.tooling.api.model.JavaModuleExternalDependency;
import com.itsaky.androidide.tooling.api.model.JavaModuleProjectDependency;
import com.itsaky.androidide.tooling.api.model.JavaSourceDirectory;
import com.itsaky.androidide.tooling.api.model.internal.DefaultProjectSyncIssues;
import com.itsaky.androidide.tooling.api.model.internal.DefaultVariant;
import com.itsaky.androidide.tooling.api.model.util.AndroidModulePropertyCopier;
@@ -39,8 +45,15 @@ import org.gradle.tooling.ProjectConnection;
import org.gradle.tooling.UnknownModelException;
import org.gradle.tooling.model.GradleProject;
import org.gradle.tooling.model.GradleTask;
import org.gradle.tooling.model.idea.IdeaDependency;
import org.gradle.tooling.model.idea.IdeaModule;
import org.gradle.tooling.model.idea.IdeaModuleDependency;
import org.gradle.tooling.model.idea.IdeaProject;
import org.gradle.tooling.model.idea.IdeaSingleEntryLibraryDependency;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
@@ -52,48 +65,48 @@ public class ProjectReader {
public static IdeGradleProject read(
ProjectConnection connection, Map<String, DefaultProjectSyncIssues> outIssues) {
final var modelBuilder = connection.model(GradleProject.class);
configureBuilder(modelBuilder);
final var watch = new StopWatch("Create basic models");
LOG.debug("Creating Idea project model...");
final var ideaProject = connection.getModel(IdeaProject.class);
watch.lapFromLast("Idea project model created");
final var gradleModel = modelBuilder.get();
if (gradleModel == null) {
return null;
}
return buildProjectModel(connection, gradleModel, outIssues);
return buildGradleProjectModel(ideaProject, outIssues);
}
private static IdeGradleProject buildProjectModel(
private static IdeGradleProject buildModuleProject(
ProjectConnection connection,
GradleProject gradleModel,
IdeaModule ideaModule,
Map<String, DefaultProjectSyncIssues> outIssues) {
final var gradle = ideaModule.getGradleProject();
final var modelBuilder = connection.model(AndroidProject.class);
addProperty(modelBuilder, AndroidProject.PROPERTY_BUILD_MODEL_ONLY, true);
addProperty(modelBuilder, AndroidProject.PROPERTY_INVOKED_FROM_IDE, true);
try {
final var modelBuilder = connection.model(AndroidProject.class);
configureBuilder(modelBuilder);
addProperty(modelBuilder, AndroidProject.PROPERTY_BUILD_MODEL_ONLY, true);
addProperty(modelBuilder, AndroidProject.PROPERTY_INVOKED_FROM_IDE, true);
final var android = modelBuilder.get();
if (android == null) {
throw new UnknownModelException(
"Project " + gradleModel.getPath() + " is not an Android project.");
"Project " + gradle.getPath() + " is not an Android project.");
}
final var module = buildAndroidProjectModel(gradleModel, android, outIssues);
final var module = buildAndroidModuleProject(gradle, android);
readDependencies(connection, module);
final var issues = readSyncIssues(connection);
outIssues.put(gradleModel.getPath(), issues);
outIssues.put(gradle.getPath(), issues);
return module;
} catch (Throwable error) {
try {
LOG.info("Building IdeGradleProject model for project:", gradleModel.getPath());
LOG.info("Building IdeGradleProject model for project:", gradle.getPath());
if (!(error instanceof UnknownModelException)) {
// If the error is something else than UnknownModelException, we should log it
LOG.error(error);
}
return buildGradleProjectModel(gradleModel, outIssues);
return buildJavaModuleProject(gradle, ideaModule);
} catch (Throwable e) {
LOG.error("Unable to create model for project", e);
return null;
@@ -101,6 +114,69 @@ public class ProjectReader {
}
}
private static IdeJavaModule buildJavaModuleProject(GradleProject gradle, IdeaModule idea) {
final var builder = new ProjectBuilder();
builder.setName(gradle.getName());
builder.setDescription(gradle.getDescription());
builder.setPath(gradle.getPath());
builder.setProjectDir(gradle.getProjectDirectory());
builder.setBuildDir(gradle.getBuildDirectory());
builder.setBuildScript(gradle.getBuildScript().getSourceFile());
builder.setContentRoots (collectContentRoots(idea));
builder.setJavaDependencies(collectJavaDependencies(idea));
final var project = builder.buildJavaModule();
addTasks(gradle, project);
return project;
}
private static List<? extends JavaModuleDependency> collectJavaDependencies(IdeaModule idea) {
final var list = new ArrayList<JavaModuleDependency>();
for (IdeaDependency dependency : idea.getDependencies()) {
// TODO There might be unresolved dependencies here. We need to handle them too.
if (dependency instanceof IdeaSingleEntryLibraryDependency) {
final var external = (IdeaSingleEntryLibraryDependency) dependency;
// There might be multiple entries of same dependency, but with different scope
// So we only add dependencies with 'RUNTIME' scope
if (external.getScope().getScope().equals("RUNTIME")) {
list.add(
new JavaModuleExternalDependency(
external.getFile(),
external.getSource(),
external.getJavadoc()));
}
} else if (dependency instanceof IdeaModuleDependency) {
final var project = ((IdeaModuleDependency) dependency);
list.add(new JavaModuleProjectDependency(project.getTargetModuleName()));
}
}
return list;
}
private static List<JavaContentRoot> collectContentRoots(IdeaModule module) {
final var list = new ArrayList<JavaContentRoot>();
for (var contentRoot : module.getContentRoots()) {
final var thisRoot = new JavaContentRoot();
for (var sourceDir : contentRoot.getSourceDirectories()) {
thisRoot.getSourceDirectories()
.add(
new JavaSourceDirectory(
sourceDir.getDirectory(), sourceDir.isGenerated()));
}
for (var testDir : contentRoot.getTestDirectories()) {
thisRoot.getTestDirectories()
.add(
new JavaSourceDirectory(
testDir.getDirectory(), testDir.isGenerated()));
}
list.add(thisRoot);
}
return list;
}
private static DefaultProjectSyncIssues readSyncIssues(ProjectConnection connection) {
final var builder = connection.model(ProjectSyncIssues.class);
addProperty(builder, AndroidProject.PROPERTY_BUILD_MODEL_ONLY, true);
@@ -130,7 +206,6 @@ public class ProjectReader {
addProperty(modelFinder, AndroidProject.PROPERTY_BUILD_MODEL_ONLY, true);
addProperty(modelFinder, AndroidProject.PROPERTY_INVOKED_FROM_IDE, true);
modelFinder.addProgressListener(new LoggingProgressListener());
configureBuilder(modelFinder);
final var variantDependencies =
AndroidModulePropertyCopier.INSTANCE.copy(modelFinder.run());
android.getVariantDependencies().put(name, variantDependencies);
@@ -141,10 +216,8 @@ public class ProjectReader {
launcher.addArguments(String.format("-P%s=%s", property, value));
}
private static IdeAndroidModule buildAndroidProjectModel(
GradleProject gradle,
AndroidProject android,
Map<String, DefaultProjectSyncIssues> outIssues) {
private static IdeAndroidModule buildAndroidModuleProject(
GradleProject gradle, AndroidProject android) {
LOG.debug("Building IdeAndroidModule for project:", gradle.getPath());
final var builder = new ProjectBuilder();
final var copier = AndroidModulePropertyCopier.INSTANCE;
@@ -164,14 +237,25 @@ public class ProjectReader {
builder.setLintChecksJars(android.getLintChecksJars());
final var module = builder.buildAndroidModule();
addSubprojects(gradle, module, outIssues);
addTasks(gradle, module);
return module;
}
private static IdeGradleProject buildGradleProjectModel(
GradleProject gradle, Map<String, DefaultProjectSyncIssues> outIssues) {
IdeaProject ideaProject, Map<String, DefaultProjectSyncIssues> outIssues) {
final var hasGradle =
ideaProject.getModules().stream()
.map(IdeaModule::getGradleProject)
.filter(it -> it.getPath().equals(":"))
.findAny();
if (hasGradle.isEmpty()) {
throw new IllegalArgumentException(
"No GradleProject model is associated with project path: " + ":");
}
final var gradle = hasGradle.get();
final var builder = new ProjectBuilder();
builder.setName(gradle.getName());
builder.setDescription(gradle.getDescription());
@@ -181,7 +265,7 @@ public class ProjectReader {
builder.setBuildScript(gradle.getBuildScript().getSourceFile());
final var project = builder.buildGradleProject();
addSubprojects(gradle, project, outIssues);
addModules(ideaProject, project, outIssues);
addTasks(gradle, project);
return project;
@@ -193,18 +277,24 @@ public class ProjectReader {
}
}
private static void addSubprojects(
GradleProject gradle,
private static void addModules(
IdeaProject ideaProject,
IdeGradleProject project,
Map<String, DefaultProjectSyncIssues> outIssues) {
for (final var subGradle : gradle.getChildren()) {
for (final var module : ideaProject.getModules()) {
final var gradle = module.getGradleProject();
if (gradle.getPath().equals(":")) {
// Do not try to add the root project again
continue;
}
final var connection =
GradleConnector.newConnector()
.forProjectDirectory(subGradle.getProjectDirectory())
.forProjectDirectory(gradle.getProjectDirectory())
.connect();
final var sub = buildProjectModel(connection, subGradle, outIssues);
final var sub = buildModuleProject(connection, module, outIssues);
if (sub != null) {
project.getSubprojects().add(sub);
project.getModules().add(sub);
}
}
}
@@ -219,6 +309,4 @@ public class ProjectReader {
task.isPublic(),
project.getProjectPath());
}
private static void configureBuilder(ConfigurableLauncher<?> launcher) {}
}
@@ -17,9 +17,9 @@
package com.itsaky.androidide.tooling.impl
import com.android.builder.model.v2.ide.ProjectType
import com.google.common.truth.Truth.assertThat
import com.google.gson.GsonBuilder
import com.itsaky.androidide.models.LogLine
import com.itsaky.androidide.tooling.api.IToolingApiClient
import com.itsaky.androidide.tooling.api.IToolingApiServer
import com.itsaky.androidide.tooling.api.messages.InitializeProjectMessage
@@ -27,13 +27,13 @@ import com.itsaky.androidide.tooling.api.model.IdeAndroidModule
import com.itsaky.androidide.tooling.api.model.IdeGradleProject
import com.itsaky.androidide.tooling.api.util.ToolingApiLauncher
import com.itsaky.androidide.utils.ILogger
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import java.io.BufferedReader
import java.io.File
import java.io.InputStream
import java.io.InputStreamReader
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
/** @author Akash Yadav */
@RunWith(JUnit4::class)
@@ -49,7 +49,6 @@ class ToolingApiImplTest {
val result =
server.initialize(InitializeProjectMessage(getTestProject().absolutePath)).get()
val project = result.project
log.debug(result.syncIssues.asJson())
assertThat(project).isNotNull()
assertThat(project!!).isInstanceOf(IdeGradleProject::class.java)
@@ -69,7 +68,6 @@ class ToolingApiImplTest {
}
private fun assertAndroidModule(android: IdeAndroidModule) {
assertThat(android.projectType).isEqualTo(ProjectType.APPLICATION)
assertThat(android.viewBindingOptions).isNotNull()
assertThat(android.viewBindingOptions!!.isEnabled).isFalse()
}
@@ -110,6 +108,13 @@ class ToolingApiImplTest {
private class TestClient : IToolingApiClient {
private val log = ILogger.newInstance(javaClass.simpleName)
override fun logMessage(line: LogLine) {
log.log(ILogger.priority(line.priorityChar), line.formattedTagAndMessage())
}
override fun logOutput(line: String) {}
override fun prepareBuild() {}
override fun onBuildSuccessful(tasks: List<String>) {}
override fun onBuildFailed(tasks: List<String>) {}
}
private class Reader(val input: InputStream) : Runnable {
@@ -1,6 +1,6 @@
buildscript {
ext {
build_gradle_version = '7.0.2'
build_gradle_version = '7.2.0-rc01'
}
project.ext {
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
@@ -1,5 +1,6 @@
plugins {
id 'java-library'
id 'idea'
}
java {
@@ -7,6 +8,11 @@ java {
targetCompatibility = JavaVersion.VERSION_11
}
dependencies {
implementation 'io.github.itsaky:nb-javac-android:17.0.0.0'
compileOnly 'com.google.android.material:material:1.5.0'
}
repositories {
mavenCentral ()
}
@@ -0,0 +1,29 @@
/*
* 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.tooling.api.model
/**
* Any project that has modules.
*
* @author Akash Yadav
*/
interface HasModules {
/** Get the modules. */
fun getModules(): List<IdeGradleProject>
}
@@ -38,7 +38,6 @@ class IdeAndroidModule(
buildDir: File?,
buildScript: File?,
parent: IdeGradleProject?,
subprojects: List<IdeGradleProject>,
tasks: List<IdeGradleTask>,
override var dynamicFeatures: Collection<String>?,
override var flags: DefaultAndroidGradlePluginProjectFlags,
@@ -49,8 +48,7 @@ class IdeAndroidModule(
override val lintChecksJars: List<File>,
override val modelSyncFiles: List<DefaultModelSyncFile>
) :
IdeGradleProject(
name, description, path, projectDir, buildDir, buildScript, parent, subprojects, tasks),
IdeGradleProject(name, description, path, projectDir, buildDir, buildScript, parent, tasks),
AndroidProject {
var variantDependencies: MutableMap<String, DefaultVariantDependencies> = mutableMapOf()
@@ -64,7 +62,6 @@ class IdeAndroidModule(
buildDir,
buildScript,
parent,
subprojects,
tasks,
dynamicFeatures,
flags,
@@ -19,7 +19,7 @@ package com.itsaky.androidide.tooling.api.model
import java.io.File
/**
* Default implementation for [IdeGradleProject].
* A root Gradle project.
* @author Akash Yadav
*/
open class IdeGradleProject(
@@ -30,34 +30,22 @@ open class IdeGradleProject(
val buildDir: File?,
val buildScript: File?,
val parent: IdeGradleProject?,
val subprojects: List<IdeGradleProject>,
val tasks: List<IdeGradleTask>,
) {
) : HasModules {
protected val gsonType: String = javaClass.name
private val gsonType: String = javaClass.name
private val moduleProjects: MutableList<IdeGradleProject> = mutableListOf()
override fun getModules(): List<IdeGradleProject> = moduleProjects
fun findByPath(path: String): IdeGradleProject? {
if (path == this.projectPath) {
return this
}
for (sub in subprojects) {
val subSub = sub.findByPath(path)
if (subSub != null) {
return subSub
}
}
return null
return getModules().firstOrNull { it.projectPath == path }
}
// TODO Filter by application modules
fun findApplicationModules(): List<IdeAndroidModule> =
this.subprojects.filterIsInstance(IdeAndroidModule::class.java)
fun findFirstApplicationModule(): IdeAndroidModule? = findApplicationModules().firstOrNull()
override fun toString(): String {
return "IdeGradleProject(name=$name, description=$description, projectPath=$projectPath, projectDir=$projectDir, buildDir=$buildDir, buildScript=$buildScript, parent=$parent, subprojects=$subprojects, tasks=$tasks, gsonType='$gsonType')"
return "IdeGradleProject(name=$name, description=$description, projectPath=$projectPath, projectDir=$projectDir, buildDir=$buildDir, buildScript=$buildScript, parent=$parent, subprojects=$moduleProjects, tasks=$tasks, gsonType='$gsonType')"
}
}
@@ -0,0 +1,43 @@
/*
* 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.tooling.api.model
import java.io.File
/**
* A java library model. Modules represented by this model does not apply any of the Android
* plugins.
*
* @author Akash Yadav
*/
class IdeJavaModule(
name: String?,
path: String?,
description: String?,
projectDir: File?,
buildDir: File?,
buildScript: File?,
parent: IdeGradleProject?,
tasks: List<IdeGradleTask>,
/** * Source directories of this project. */
val contentRoots: List<JavaContentRoot>,
/** Dependencies of this project. */
val dependencies: List<JavaModuleDependency>
) : IdeGradleProject(name, description, path, projectDir, buildDir, buildScript, parent, tasks) {}
@@ -0,0 +1,24 @@
/*
* 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.tooling.api.model
/** @author Akash Yadav */
class JavaContentRoot {
val sourceDirectories: List<JavaSourceDirectory> = mutableListOf()
val testDirectories: List<JavaSourceDirectory> = mutableListOf()
}
@@ -0,0 +1,29 @@
/*
* 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.tooling.api.model
import java.io.File
/**
* Dependency of an [IdeJavaModule].
*
* @author Akash Yadav
*/
abstract class JavaModuleDependency(val jarFile: File?) {
private val gsonType = javaClass.name
}
@@ -0,0 +1,35 @@
/*
* 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.tooling.api.model
import java.io.File
/**
* An external dependency of an [IdeJavaModule].
*
* @author Akash Yadav
*/
class JavaModuleExternalDependency(
jar: File,
/** `sources.jar` for this dependency. */
val sources: File? = null,
/** `javadoc.jar` for this dependency. */
val javadoc: File? = null
) : JavaModuleDependency(jar) {}
@@ -0,0 +1,29 @@
/*
* 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.tooling.api.model
/**
* A project dependency of an [IdeJavaModule]. More details about the project can be fetched by
* querying the [projectPath] in the root project.
*
* @author Akash Yadav
*/
class JavaModuleProjectDependency(
/** The name of the module project. */
val moduleName: String
) : JavaModuleDependency(null)
@@ -0,0 +1,27 @@
/*
* 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.tooling.api.model
import java.io.File
/**
* A source directory of an [IdeJavaModule].
*
* @author Akash Yadav
*/
class JavaSourceDirectory(val directory: File, val isGenerated: Boolean) {}
@@ -91,7 +91,6 @@ object AndroidModulePropertyCopier {
module.buildDir,
module.buildScript,
module.parent,
module.subprojects,
module.tasks,
module.dynamicFeatures,
copy(module.flags),
@@ -19,6 +19,9 @@ package com.itsaky.androidide.tooling.api.model.util
import com.itsaky.androidide.tooling.api.model.IdeAndroidModule
import com.itsaky.androidide.tooling.api.model.IdeGradleProject
import com.itsaky.androidide.tooling.api.model.IdeGradleTask
import com.itsaky.androidide.tooling.api.model.IdeJavaModule
import com.itsaky.androidide.tooling.api.model.JavaContentRoot
import com.itsaky.androidide.tooling.api.model.JavaModuleDependency
import com.itsaky.androidide.tooling.api.model.internal.DefaultAndroidGradlePluginProjectFlags
import com.itsaky.androidide.tooling.api.model.internal.DefaultJavaCompileOptions
import com.itsaky.androidide.tooling.api.model.internal.DefaultModelSyncFile
@@ -39,7 +42,7 @@ class ProjectBuilder {
var buildDir: File? = null
var buildScript: File? = null
var parent: IdeGradleProject? = null
var subprojects: List<IdeGradleProject> = mutableListOf()
var modules: List<IdeGradleProject> = mutableListOf()
var tasks: List<IdeGradleTask> = mutableListOf()
var dynamicFeatures: Collection<String>? = mutableListOf()
var flags: DefaultAndroidGradlePluginProjectFlags =
@@ -50,10 +53,26 @@ class ProjectBuilder {
var viewBindingOptions: DefaultViewBindingOptions? = null
var modelSyncFiles: List<DefaultModelSyncFile> = emptyList()
var lintChecksJars: List<File> = mutableListOf()
var contentRoots: List<JavaContentRoot> = mutableListOf()
var javaDependencies: List<JavaModuleDependency> = mutableListOf()
fun buildGradleProject(): IdeGradleProject {
return IdeGradleProject(
name, description, path, projectDir, buildDir, buildScript, parent, subprojects, tasks)
name, description, path, projectDir, buildDir, buildScript, parent, tasks)
}
fun buildJavaModule(): IdeJavaModule {
return IdeJavaModule(
name,
path,
description,
projectDir,
buildDir,
buildScript,
parent,
tasks,
contentRoots,
javaDependencies)
}
fun buildAndroidModule(): IdeAndroidModule =
@@ -65,7 +84,6 @@ class ProjectBuilder {
buildDir,
buildScript,
parent,
subprojects,
tasks,
dynamicFeatures,
flags,
@@ -23,7 +23,11 @@ import com.itsaky.androidide.tooling.api.IToolingApiServer;
import com.itsaky.androidide.tooling.api.model.IdeAndroidModule;
import com.itsaky.androidide.tooling.api.model.IdeGradleProject;
import com.itsaky.androidide.tooling.api.model.IdeGradleTask;
import com.itsaky.androidide.tooling.api.model.IdeJavaModule;
import com.itsaky.androidide.tooling.api.model.IdeLaunchable;
import com.itsaky.androidide.tooling.api.model.JavaModuleDependency;
import com.itsaky.androidide.tooling.api.model.JavaModuleExternalDependency;
import com.itsaky.androidide.tooling.api.model.JavaModuleProjectDependency;
import org.eclipse.lsp4j.jsonrpc.Launcher;
@@ -56,11 +60,20 @@ public class ToolingApiLauncher {
builder.registerTypeAdapterFactory(
RuntimeTypeAdapterFactory.of(IdeGradleProject.class, "gsonType", true)
.registerSubtype(IdeAndroidModule.class, IdeAndroidModule.class.getName())
.registerSubtype(IdeJavaModule.class, IdeJavaModule.class.getName())
.registerSubtype(IdeGradleProject.class, IdeGradleProject.class.getName()));
builder.registerTypeAdapterFactory(
RuntimeTypeAdapterFactory.of(IdeLaunchable.class, "gsonType", true)
.registerSubtype(IdeGradleTask.class, IdeGradleTask.class.getName())
.registerSubtype(IdeLaunchable.class, IdeLaunchable.class.getName()));
builder.registerTypeAdapterFactory(
RuntimeTypeAdapterFactory.of(JavaModuleDependency.class, "gsonType", true)
.registerSubtype(
JavaModuleExternalDependency.class,
JavaModuleExternalDependency.class.getName())
.registerSubtype(
JavaModuleProjectDependency.class,
JavaModuleProjectDependency.class.getName()));
}
public static Launcher<IToolingApiClient> createServerLauncher(
+1 -1
View File
@@ -8,7 +8,7 @@ import java.nio.file.Paths
import java.util.stream.Collectors
buildscript {
ext.kotlin_version = '1.6.20'
ext.kotlin_version = '1.6.21'
project.ext {
compileSdk = 31
+1
View File
@@ -27,3 +27,4 @@ include ':build-tools:tooling-api-model'
include ':build-tools:xml-formatter'
include ':build-tools:builder-api'
include ':build-tools:builder-model'
include ':build-tools:project'