mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
Implemented logic to run tasks using the Tooling API
This commit is contained in:
Generated
+17
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="deploymentTargetDropDown">
|
||||
<runningDeviceTargetSelectedWithDropDown>
|
||||
<Target>
|
||||
<type value="RUNNING_DEVICE_TARGET" />
|
||||
<deviceKey>
|
||||
<Key>
|
||||
<type value="SERIAL_NUMBER" />
|
||||
<value value="b36e6c44" />
|
||||
</Key>
|
||||
</deviceKey>
|
||||
</Target>
|
||||
</runningDeviceTargetSelectedWithDropDown>
|
||||
<timeTargetWasSelectedWithDropDown value="2022-04-30T06:17:07.089058600Z" />
|
||||
</component>
|
||||
</project>
|
||||
Binary file not shown.
@@ -85,7 +85,6 @@ import com.itsaky.androidide.fragments.SearchResultFragment;
|
||||
import com.itsaky.androidide.fragments.sheets.OptionsListFragment;
|
||||
import com.itsaky.androidide.fragments.sheets.ProgressSheet;
|
||||
import com.itsaky.androidide.fragments.sheets.TextSheetFragment;
|
||||
import com.itsaky.androidide.handlers.BuildServiceHandler;
|
||||
import com.itsaky.androidide.handlers.EditorEventListener;
|
||||
import com.itsaky.androidide.handlers.FileOptionsHandler;
|
||||
import com.itsaky.androidide.handlers.IDEHandler;
|
||||
@@ -94,6 +93,7 @@ import com.itsaky.androidide.interfaces.EditorActivityProvider;
|
||||
import com.itsaky.androidide.lsp.IDELanguageClientImpl;
|
||||
import com.itsaky.androidide.managers.PreferenceManager;
|
||||
import com.itsaky.androidide.managers.ToolsManager;
|
||||
import com.itsaky.androidide.models.ApkMetadata;
|
||||
import com.itsaky.androidide.models.DiagnosticGroup;
|
||||
import com.itsaky.androidide.models.LogLine;
|
||||
import com.itsaky.androidide.models.SaveResult;
|
||||
@@ -103,9 +103,9 @@ import com.itsaky.androidide.project.AndroidProject;
|
||||
import com.itsaky.androidide.project.IDEProject;
|
||||
import com.itsaky.androidide.services.GradleBuildService;
|
||||
import com.itsaky.androidide.services.LogReceiver;
|
||||
import com.itsaky.androidide.services.builder.IDEService;
|
||||
import com.itsaky.androidide.shell.ShellServer;
|
||||
import com.itsaky.androidide.tasks.TaskExecutor;
|
||||
import com.itsaky.androidide.tooling.api.messages.result.TaskExecutionResult;
|
||||
import com.itsaky.androidide.tooling.api.model.IdeGradleProject;
|
||||
import com.itsaky.androidide.utils.DialogUtils;
|
||||
import com.itsaky.androidide.utils.EditorActivityActions;
|
||||
@@ -142,6 +142,7 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -174,7 +175,6 @@ public class EditorActivity extends StudioActivity
|
||||
private FileTreeFragment mFileTreeFragment;
|
||||
private TreeNode mLastHeld;
|
||||
private SymbolInputView symbolInput;
|
||||
private BuildServiceHandler mBuildServiceHandler;
|
||||
private FileOptionsHandler mFileOptionsHandler;
|
||||
private QuickAction mTabCloseAction;
|
||||
private TextSheetFragment mDaemonStatusFragment;
|
||||
@@ -184,6 +184,8 @@ public class EditorActivity extends StudioActivity
|
||||
private ActivityResultLauncher<Intent> mUIDesignerLauncher;
|
||||
private EditorBottomSheetBehavior<? extends View> mEditorBottomSheet;
|
||||
private EditorViewModel mViewModel;
|
||||
|
||||
private IdeGradleProject mRootProject;
|
||||
private GradleBuildService mBuildService;
|
||||
private final ServiceConnection mGradleServiceConnection =
|
||||
new ServiceConnection() {
|
||||
@@ -324,14 +326,6 @@ public class EditorActivity extends StudioActivity
|
||||
}
|
||||
}
|
||||
|
||||
public void setStatus(final CharSequence text) {
|
||||
try {
|
||||
runOnUiThread(() -> mBinding.bottomSheet.statusText.setText(text));
|
||||
} catch (Throwable th) {
|
||||
LOG.error("Failed to update status text", th);
|
||||
}
|
||||
}
|
||||
|
||||
public void handleSearchResults(Map<File, List<SearchResult>> results) {
|
||||
setSearchResultAdapter(
|
||||
new com.itsaky.androidide.adapters.SearchListAdapter(
|
||||
@@ -701,20 +695,6 @@ public class EditorActivity extends StudioActivity
|
||||
return saveAll(true);
|
||||
}
|
||||
|
||||
public void install(@NonNull File apk) {
|
||||
LOG.debug("Installing APK:", apk);
|
||||
if (apk.exists()) {
|
||||
Intent i = IntentUtils.getInstallAppIntent(apk);
|
||||
if (i != null) {
|
||||
startActivity(i);
|
||||
} else {
|
||||
getApp().toast(R.string.msg_apk_install_intent_failed, Toaster.Type.ERROR);
|
||||
}
|
||||
} else {
|
||||
LOG.error("APK file does not exist!");
|
||||
}
|
||||
}
|
||||
|
||||
public void updateServices() {
|
||||
new TaskExecutor()
|
||||
.executeAsync(
|
||||
@@ -732,6 +712,14 @@ public class EditorActivity extends StudioActivity
|
||||
__ -> setStatus(getString(R.string.msg_service_started)));
|
||||
}
|
||||
|
||||
public void setStatus(final CharSequence text) {
|
||||
try {
|
||||
runOnUiThread(() -> mBinding.bottomSheet.statusText.setText(text));
|
||||
} catch (Throwable th) {
|
||||
LOG.error("Failed to update status text", th);
|
||||
}
|
||||
}
|
||||
|
||||
public void closeFile(int index) {
|
||||
if (index >= 0 && index < mViewModel.getOpenedFileCount()) {
|
||||
var opened = mViewModel.getOpenedFile(index);
|
||||
@@ -894,9 +882,8 @@ public class EditorActivity extends StudioActivity
|
||||
notifySyncNeeded();
|
||||
}
|
||||
|
||||
if (result.xmlSaved && canProcessResources && getBuildService() != null) {
|
||||
getBuildService().updateResourceClasses();
|
||||
}
|
||||
// TODO Notify language servers about changed XML files
|
||||
// Maybe use a file watcher to do these kind of things.
|
||||
|
||||
return result.gradleSaved;
|
||||
}
|
||||
@@ -911,19 +898,14 @@ public class EditorActivity extends StudioActivity
|
||||
}
|
||||
|
||||
private void notifySyncNeeded() {
|
||||
if (getBuildService() != null && !getBuildService().isBuilding()) {
|
||||
if (mBuildService != null && !mBuildService.isBuildInProgress()) {
|
||||
getSyncBanner()
|
||||
.setNegative(android.R.string.cancel, null)
|
||||
.setPositive(
|
||||
android.R.string.ok, v -> getBuildServiceHandler().assembleDebug(false))
|
||||
.setPositive(android.R.string.ok, v -> assembleDebug(false))
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
private IDEService getBuildService() {
|
||||
return mBuildServiceHandler.getService();
|
||||
}
|
||||
|
||||
public void saveResult(int index, SaveResult result) {
|
||||
if (index >= 0 && index < mViewModel.getOpenedFileCount()) {
|
||||
var frag = getEditorAtIndex(index);
|
||||
@@ -971,8 +953,116 @@ public class EditorActivity extends StudioActivity
|
||||
.setContentText(R.string.msg_sync_needed);
|
||||
}
|
||||
|
||||
public BuildServiceHandler getBuildServiceHandler() {
|
||||
return mBuildServiceHandler;
|
||||
public void assembleDebug(boolean installApk) {
|
||||
execTasks(installApk ? installableTaskResultConsumer("debug") : null, "assembleDebug");
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public CompletableFuture<TaskExecutionResult> execTasks(
|
||||
Consumer<TaskExecutionResult> resultHandler, String... tasks) {
|
||||
return mBuildService
|
||||
.executeTasks(tasks)
|
||||
.whenComplete(
|
||||
((executionResult, throwable) -> {
|
||||
if (executionResult == null || throwable != null) {
|
||||
LOG.error("Tasks failed to execute", TextUtils.join(", ", tasks));
|
||||
}
|
||||
|
||||
if (resultHandler != null) {
|
||||
resultHandler.accept(executionResult);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public Consumer<TaskExecutionResult> installableTaskResultConsumer(
|
||||
@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());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void install(@NonNull File apk) {
|
||||
LOG.debug("Installing APK:", apk);
|
||||
if (apk.exists()) {
|
||||
Intent i = IntentUtils.getInstallAppIntent(apk);
|
||||
if (i != null) {
|
||||
startActivity(i);
|
||||
} else {
|
||||
getApp().toast(R.string.msg_apk_install_intent_failed, Toaster.Type.ERROR);
|
||||
}
|
||||
} else {
|
||||
LOG.error("APK file does not exist!");
|
||||
}
|
||||
}
|
||||
|
||||
public void assembleRelease() {
|
||||
execTasks(null, "assembleRelease");
|
||||
}
|
||||
|
||||
public void build() {
|
||||
execTasks(null, "build");
|
||||
}
|
||||
|
||||
public void clean() {
|
||||
execTasks(null, "clean");
|
||||
}
|
||||
|
||||
public void bundle() {
|
||||
execTasks(null, "bundle");
|
||||
}
|
||||
|
||||
public void lint() {
|
||||
execTasks(null, "lint");
|
||||
}
|
||||
|
||||
public void lintDebug() {
|
||||
execTasks(null, "lintDebug");
|
||||
}
|
||||
|
||||
public void lintRelease() {
|
||||
execTasks(null, "lintRelease");
|
||||
}
|
||||
|
||||
public void cleanAndRebuild() {
|
||||
execTasks(null, "clean", "build");
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
@@ -990,6 +1080,10 @@ public class EditorActivity extends StudioActivity
|
||||
}
|
||||
}
|
||||
|
||||
public GradleBuildService getBuildService() {
|
||||
return mBuildService;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -1013,7 +1107,6 @@ public class EditorActivity extends StudioActivity
|
||||
createQuickActions();
|
||||
|
||||
mBuildEventListener.setActivity(this);
|
||||
mBuildServiceHandler = new BuildServiceHandler(this);
|
||||
mFileOptionsHandler = new FileOptionsHandler(this);
|
||||
|
||||
startServices();
|
||||
@@ -1181,6 +1274,8 @@ public class EditorActivity extends StudioActivity
|
||||
}
|
||||
|
||||
private void initializeProject() {
|
||||
// TODO Do not create unnecessary models. Instead, just keep a reference to the root
|
||||
// directory of the project.
|
||||
final var androidProject = getAndroidProject();
|
||||
if (androidProject == null) {
|
||||
LOG.error("Cannot initialize project. Project model is null.");
|
||||
@@ -1212,6 +1307,7 @@ public class EditorActivity extends StudioActivity
|
||||
}
|
||||
|
||||
protected void onProjectInitialized(IdeGradleProject root) {
|
||||
mRootProject = root;
|
||||
CompletableFuture.runAsync(
|
||||
() -> {
|
||||
final var app = mBuildService.findFirstAndroidModule(root);
|
||||
@@ -1487,8 +1583,6 @@ public class EditorActivity extends StudioActivity
|
||||
// Because it would work anyway
|
||||
// But still we do...
|
||||
mFileOptionsHandler.start();
|
||||
mBuildServiceHandler.start();
|
||||
// getBuildServiceHandler().assembleDebug(false);
|
||||
}
|
||||
|
||||
private void initializeLanguageServers() {
|
||||
@@ -1514,14 +1608,7 @@ public class EditorActivity extends StudioActivity
|
||||
|
||||
shutdownLanguageServers();
|
||||
|
||||
if (getBuildService() != null) {
|
||||
getBuildService().setListener(null);
|
||||
if (mBuildServiceHandler != null) {
|
||||
mBuildServiceHandler.stop();
|
||||
} else {
|
||||
getBuildService().exit();
|
||||
}
|
||||
}
|
||||
// TODO Send shutdown request to Tooling API server
|
||||
|
||||
if (mFileOptionsHandler != null) {
|
||||
mFileOptionsHandler.stop();
|
||||
|
||||
@@ -42,7 +42,7 @@ abstract class BaseBuildAction : EditorActivityAction() {
|
||||
enabled = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun postExec(data: ActionData, result: Any) {
|
||||
val context = getActivity(data) ?: return
|
||||
context.invalidateOptionsMenu()
|
||||
@@ -51,5 +51,5 @@ abstract class BaseBuildAction : EditorActivityAction() {
|
||||
fun shouldPrepare() = visible && enabled
|
||||
|
||||
private fun isBuildInProgress(activity: EditorActivity): Boolean =
|
||||
activity.buildServiceHandler.isBuilding
|
||||
activity.buildService == null || activity.buildService.isBuildInProgress
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ class AssembleDebugAction() : BaseBuildAction() {
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val activity = getActivity(data) ?: return false
|
||||
|
||||
activity.buildServiceHandler.assembleDebug(true)
|
||||
activity.assembleDebug(true)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ class AssembleReleaseAction() : BaseBuildAction() {
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val activity = getActivity(data) ?: return false
|
||||
|
||||
activity.buildServiceHandler.service.assembleRelease()
|
||||
activity.assembleRelease()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ class BuildAction() : BaseBuildAction() {
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val activity = getActivity(data) ?: return false
|
||||
|
||||
activity.buildServiceHandler.service.build()
|
||||
activity.build()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ class CleanAction() : BaseBuildAction() {
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
|
||||
context.buildServiceHandler.service.clean()
|
||||
context.clean()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ class CreateAABAction() : BaseBuildAction() {
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
|
||||
context.buildServiceHandler.service.bundle()
|
||||
context.bundle()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ class QuickRunAction() : BaseBuildAction() {
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
context.buildServiceHandler.assembleDebug(true)
|
||||
context.assembleDebug(true)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ class RebuildAction() : BaseBuildAction() {
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
|
||||
context.buildServiceHandler.service.cleanAndRebuild()
|
||||
context.cleanAndRebuild()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,8 +40,7 @@ class StopDaemonsAction() : EditorActivityAction() {
|
||||
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
|
||||
context.buildServiceHandler.service.stopAllDaemons()
|
||||
context.app.stopAllDaemons()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ class LintAction() : BaseBuildAction() {
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
|
||||
context.buildServiceHandler.service.lint()
|
||||
context.lint()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ class LintDebugAction() : BaseBuildAction() {
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
|
||||
context.buildServiceHandler.service.lintDebug()
|
||||
context.lintDebug()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ class LintReleaseAction() : BaseBuildAction() {
|
||||
override fun execAction(data: ActionData): Boolean {
|
||||
val context = getActivity(data) ?: return false
|
||||
|
||||
context.buildServiceHandler.service.lintRelease()
|
||||
context.lintRelease()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,222 +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.handlers;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.managers.PreferenceManager;
|
||||
import com.itsaky.androidide.project.AndroidProject;
|
||||
import com.itsaky.androidide.project.IDEModule;
|
||||
import com.itsaky.androidide.project.IDEProject;
|
||||
import com.itsaky.androidide.services.builder.BuildListener;
|
||||
import com.itsaky.androidide.services.builder.IDEService;
|
||||
import com.itsaky.androidide.tasks.GradleTask;
|
||||
import com.itsaky.androidide.tasks.gradle.build.ApkGeneratingTask;
|
||||
import com.itsaky.androidide.utils.Environment;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class BuildServiceHandler extends IDEHandler implements BuildListener {
|
||||
|
||||
private IDEService service;
|
||||
private boolean shouldInstallApk = false;
|
||||
|
||||
public BuildServiceHandler(Provider provider) {
|
||||
super(provider);
|
||||
}
|
||||
|
||||
public boolean isBuilding() {
|
||||
return getService().isBuilding();
|
||||
}
|
||||
|
||||
public IDEService getService() {
|
||||
return service;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
Objects.requireNonNull(this.activity());
|
||||
|
||||
AndroidProject project = provider.provideAndroidProject();
|
||||
Objects.requireNonNull(project);
|
||||
Objects.requireNonNull(project.getProjectPath());
|
||||
|
||||
this.service = new IDEService(new File(project.getProjectPath()));
|
||||
this.service.setListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
this.service.stopAllDaemons();
|
||||
this.service.exit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBuildModified() {
|
||||
// Does nothing currently
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProjectLoaded(IDEProject project, Optional<IDEModule> appModule) {
|
||||
project.iconPath = provider.provideAndroidProject().getIconPath();
|
||||
activity().setIDEProject(project);
|
||||
|
||||
if (appModule.isPresent()) {
|
||||
IDEModule app = appModule.get();
|
||||
activity().setStatus(activity().getString(R.string.msg_starting_completion));
|
||||
|
||||
final List<String> paths =
|
||||
app.dependencies.stream()
|
||||
.filter(this::isClasspathValid)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
provider.provideAndroidProject().setClassPaths(paths);
|
||||
|
||||
File androidJar =
|
||||
new File(
|
||||
Environment.ANDROID_HOME,
|
||||
String.format("platforms/%s/android.jar", app.compileSdkVersion));
|
||||
if (androidJar.exists()) {
|
||||
Environment.setBootClasspath(androidJar);
|
||||
provider.provideAndroidProject().addClasspath(androidJar.getAbsolutePath());
|
||||
activity().updateServices();
|
||||
} else {
|
||||
activity().setStatus("android.jar not found!");
|
||||
}
|
||||
} else {
|
||||
activity().setStatus("Cannot get :app module...");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartingGradleDaemon(GradleTask task) {
|
||||
activity().setStatus(activity().getString(R.string.msg_starting_daemon));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRunTask(GradleTask task, String name) {
|
||||
if (task != null && task.canOutput()) {
|
||||
activity().setStatus(service.typeString(task.getTaskID()) + " " + name);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBuildSuccessful(GradleTask task, String msg) {
|
||||
appendOutput(task, msg);
|
||||
analyzeCurrentFile();
|
||||
if (task == null || activity() == null) {
|
||||
return;
|
||||
}
|
||||
if (task.canOutput()) {
|
||||
activity().setStatus(msg);
|
||||
}
|
||||
|
||||
// If this task generates APK, ask user if he/she wants to install them
|
||||
if (shouldInstallApk && task instanceof ApkGeneratingTask) {
|
||||
final IDEProject project = activity().provideIDEProject();
|
||||
final Optional<IDEModule> app =
|
||||
project.getModuleByPath(":app"); // TODO Handle multiple application modules
|
||||
if (app.isPresent()) {
|
||||
final String path = app.get().projectDir;
|
||||
if (path != null) {
|
||||
final File buildDir = new File(path, "build");
|
||||
// installApks(((ApkGeneratingTask) task).getApks(buildDir));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
activity()
|
||||
.getApp()
|
||||
.getPrefManager()
|
||||
.putBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, false);
|
||||
activity().getBinding().buildProgressIndicator.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBuildFailed(GradleTask task, String msg) {
|
||||
appendOutput(task, msg);
|
||||
analyzeCurrentFile();
|
||||
if (task == null) {
|
||||
return;
|
||||
}
|
||||
if (task.canOutput()) {
|
||||
activity().setStatus(msg);
|
||||
}
|
||||
|
||||
activity()
|
||||
.getApp()
|
||||
.getPrefManager()
|
||||
.putBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, false);
|
||||
activity().getBinding().buildProgressIndicator.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveFiles() {
|
||||
activity().saveAll(false, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendOutput(GradleTask task, CharSequence text) {
|
||||
if (text == null) {
|
||||
return;
|
||||
}
|
||||
if (task != null && !task.canOutput()) {
|
||||
return;
|
||||
}
|
||||
|
||||
activity().appendBuildOut(text.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepareBuild() {}
|
||||
|
||||
private void analyzeCurrentFile() {
|
||||
final var editorView = activity().getCurrentEditor();
|
||||
if (editorView != null) {
|
||||
final var editor = editorView.getEditor();
|
||||
if (editor != null) {
|
||||
editor.analyze();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isClasspathValid(String path) {
|
||||
if (path == null || path.trim().length() <= 0 || path.trim().equals("/")) {
|
||||
return false;
|
||||
}
|
||||
File file = new File(path);
|
||||
return file.isFile()
|
||||
&& file.getName().endsWith(".jar")
|
||||
// Release R.jar shouldn't be included in classpath
|
||||
&& !file.getAbsolutePath().endsWith("/release/R.jar");
|
||||
}
|
||||
|
||||
public void assembleDebug(boolean shouldInstallApk) {
|
||||
setShouldInstallApk(shouldInstallApk);
|
||||
getService().assembleDebug();
|
||||
}
|
||||
|
||||
public void setShouldInstallApk(boolean install) {
|
||||
this.shouldInstallApk = install;
|
||||
}
|
||||
}
|
||||
@@ -56,15 +56,40 @@ class EditorEventListener : GradleBuildService.EventListener {
|
||||
activity().binding.buildProgressIndicator.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
override fun onBuildSuccessful(tasks: MutableList<String>) {
|
||||
analyzeCurrentFile()
|
||||
activity().app.prefManager.putBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, false)
|
||||
activity().binding.buildProgressIndicator.visibility = View.GONE
|
||||
|
||||
// TODO If the tasks generate APKs, start the APK install intent
|
||||
}
|
||||
|
||||
override fun onBuildFailed(tasks: MutableList<String>) {
|
||||
analyzeCurrentFile()
|
||||
|
||||
activity().app.prefManager.putBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, false)
|
||||
activity().binding.buildProgressIndicator.visibility = View.GONE
|
||||
}
|
||||
|
||||
override fun onOutput(line: String?) {
|
||||
activity().appendBuildOut(line)
|
||||
|
||||
// TODO This can be handled better when ProgressEvents are received from Tooling API server
|
||||
if (line!!.startsWith("> Task")) {
|
||||
if (line!!.startsWith("> Task") ||
|
||||
line.contains("BUILD SUCCESSFUL") ||
|
||||
line.contains("BUILD FAILED")) {
|
||||
activity().setStatus(line)
|
||||
}
|
||||
}
|
||||
|
||||
private fun analyzeCurrentFile() {
|
||||
val editorView = activity().currentEditor
|
||||
if (editorView != null) {
|
||||
val editor = editorView.editor
|
||||
editor?.analyze()
|
||||
}
|
||||
}
|
||||
|
||||
fun activity(): EditorActivity {
|
||||
return activityReference.get()
|
||||
?: throw IllegalStateException("Activity reference has been destroyed!")
|
||||
|
||||
@@ -17,15 +17,25 @@
|
||||
*/
|
||||
package com.itsaky.androidide.models;
|
||||
|
||||
import static com.itsaky.androidide.utils.ILogger.newInstance;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import com.itsaky.androidide.utils.ILogger;
|
||||
import com.itsaky.androidide.utils.JSONUtility;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class ApkMetadata {
|
||||
|
||||
private static final ILogger LOG = newInstance("ApkMetadata");
|
||||
|
||||
@SerializedName("version")
|
||||
private int version;
|
||||
|
||||
@@ -44,12 +54,84 @@ public class ApkMetadata {
|
||||
@SerializedName("elementType")
|
||||
private String elementType;
|
||||
|
||||
public int getVersion() {
|
||||
return version;
|
||||
@Nullable
|
||||
public static File findApkFile(@NonNull File metadataFile) {
|
||||
try {
|
||||
final var dir = metadataFile.getParentFile();
|
||||
final var metadata =
|
||||
JSONUtility.gson.fromJson(new FileReader(metadataFile), ApkMetadata.class);
|
||||
if (!isValid(metadata)) {
|
||||
LOG.warn("Invalid APK metadata:", metadata);
|
||||
return null;
|
||||
}
|
||||
|
||||
for (Element element : metadata.getElements()) {
|
||||
if (element == null || element.getOutputFile() == null) {
|
||||
LOG.warn("No output file specified in APK metadata element:", element);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (element.getOutputFile().endsWith(".apk")) {
|
||||
final File apk = new File(dir, element.getOutputFile());
|
||||
if (apk.exists() && apk.isFile()) {
|
||||
LOG.info("Found apk in metadata:", apk);
|
||||
return apk;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (FileNotFoundException e) {
|
||||
LOG.error("Metadata file not found...", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void setVersion(int version) {
|
||||
this.version = version;
|
||||
private static boolean isValid(ApkMetadata metadata) {
|
||||
|
||||
// Null checks
|
||||
if (metadata == null
|
||||
|| metadata.getArtifactType() == null
|
||||
|| metadata.getArtifactType().getType() == null
|
||||
|| metadata.getElements() == null) {
|
||||
LOG.warn("APK metadata null check failed. Metadata:", metadata);
|
||||
return false;
|
||||
}
|
||||
|
||||
final ArtifactType type = metadata.getArtifactType();
|
||||
final List<Element> elements = metadata.getElements();
|
||||
|
||||
if (!type.getType().equals(ArtifactType.TYPE_APK)) {
|
||||
LOG.warn("Artifact is not of type APK. Metadata:", metadata);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (elements.isEmpty()) {
|
||||
LOG.warn("No output elements found for metadata:", metadata);
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean atLeastOneApk = false;
|
||||
for (Element element : elements) {
|
||||
if (element == null
|
||||
|| element.getOutputFile() == null
|
||||
|| !element.getOutputFile().endsWith(".apk")) {
|
||||
LOG.warn("Skipping output element because file is not APK:", element);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (element.getOutputFile().endsWith(".apk")) {
|
||||
atLeastOneApk = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
LOG.debug("Output metadata validation succeeded");
|
||||
return atLeastOneApk;
|
||||
}
|
||||
|
||||
public List<Element> getElements() {
|
||||
return elements;
|
||||
}
|
||||
|
||||
public ArtifactType getArtifactType() {
|
||||
@@ -60,6 +142,18 @@ public class ApkMetadata {
|
||||
this.artifactType = artifactType;
|
||||
}
|
||||
|
||||
public void setElements(List<Element> elements) {
|
||||
this.elements = elements;
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(int version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public String getApplicationId() {
|
||||
return applicationId;
|
||||
}
|
||||
@@ -76,14 +170,6 @@ public class ApkMetadata {
|
||||
this.variantName = variantName;
|
||||
}
|
||||
|
||||
public List<Element> getElements() {
|
||||
return elements;
|
||||
}
|
||||
|
||||
public void setElements(List<Element> elements) {
|
||||
this.elements = elements;
|
||||
}
|
||||
|
||||
public String getElementType() {
|
||||
return elementType;
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -72,6 +73,7 @@ public class GradleBuildService extends Service implements BuildService, IToolin
|
||||
private final ILogger SERVER_LOGGER = newInstance("ToolingApiServer");
|
||||
private final IBinder mBinder = new GradleServiceBinder();
|
||||
private boolean isToolingServerStarted = false;
|
||||
private boolean isBuildInProgress = false;
|
||||
private Thread toolingServerThread;
|
||||
private NotificationManager notificationManager;
|
||||
private IToolingApiServer server;
|
||||
@@ -145,12 +147,34 @@ public class GradleBuildService extends Service implements BuildService, IToolin
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBuildSuccessful(@NonNull List<String> tasks) {
|
||||
if (eventListener != null) {
|
||||
eventListener.onBuildSuccessful(tasks);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBuildFailed(@NonNull List<String> tasks) {
|
||||
if (eventListener != null) {
|
||||
eventListener.onBuildFailed(tasks);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public CompletableFuture<InitializeResult> initializeProject(@NonNull String rootDir) {
|
||||
checkServerStarted();
|
||||
ensureTmpdir();
|
||||
return server.initialize(new InitializeProjectMessage(rootDir));
|
||||
|
||||
if (isBuildInProgress) {
|
||||
logBuildInProgress();
|
||||
return failedFutureForBuildInProgress();
|
||||
}
|
||||
|
||||
isBuildInProgress = true;
|
||||
return server.initialize(new InitializeProjectMessage(rootDir))
|
||||
.thenApply(this::markBuildAsFinished);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@@ -158,7 +182,15 @@ public class GradleBuildService extends Service implements BuildService, IToolin
|
||||
public CompletableFuture<TaskExecutionResult> executeTasks(@NonNull String... tasks) {
|
||||
checkServerStarted();
|
||||
ensureTmpdir();
|
||||
return server.executeTasks(new TaskExecutionMessage(":", Arrays.asList(tasks)));
|
||||
|
||||
if (isBuildInProgress) {
|
||||
logBuildInProgress();
|
||||
return failedFutureForBuildInProgress();
|
||||
}
|
||||
|
||||
isBuildInProgress = true;
|
||||
return server.executeTasks(new TaskExecutionMessage(":", Arrays.asList(tasks)))
|
||||
.thenApply(this::markBuildAsFinished);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@@ -167,7 +199,15 @@ public class GradleBuildService extends Service implements BuildService, IToolin
|
||||
@NonNull String projectPath, @NonNull String... tasks) {
|
||||
checkServerStarted();
|
||||
ensureTmpdir();
|
||||
return server.executeTasks(new TaskExecutionMessage(projectPath, Arrays.asList(tasks)));
|
||||
|
||||
if (isBuildInProgress) {
|
||||
logBuildInProgress();
|
||||
return failedFutureForBuildInProgress();
|
||||
}
|
||||
|
||||
isBuildInProgress = true;
|
||||
return server.executeTasks(new TaskExecutionMessage(projectPath, Arrays.asList(tasks)))
|
||||
.thenApply(this::markBuildAsFinished);
|
||||
}
|
||||
|
||||
private void checkServerStarted() {
|
||||
@@ -180,6 +220,27 @@ public class GradleBuildService extends Service implements BuildService, IToolin
|
||||
Environment.mkdirIfNotExits(Environment.TMP_DIR);
|
||||
}
|
||||
|
||||
private void logBuildInProgress() {
|
||||
LOG.warn("A build is already in progress!");
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private <T> CompletableFuture<T> failedFutureForBuildInProgress() {
|
||||
final var future = new CompletableFuture<T>();
|
||||
future.completeExceptionally(
|
||||
new CompletionException(new IllegalStateException("A build is already running!")));
|
||||
return future;
|
||||
}
|
||||
|
||||
protected <T> T markBuildAsFinished(T t) {
|
||||
isBuildInProgress = false;
|
||||
return t;
|
||||
}
|
||||
|
||||
public boolean isBuildInProgress() {
|
||||
return isBuildInProgress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the first direct subproject of the given root project that is an {@link
|
||||
* IdeAndroidModule}.
|
||||
@@ -303,7 +364,7 @@ public class GradleBuildService extends Service implements BuildService, IToolin
|
||||
void onServerStarted();
|
||||
}
|
||||
|
||||
/** Handles output received from a Gradle build. */
|
||||
/** Handles events received from a Gradle build. */
|
||||
public interface EventListener {
|
||||
|
||||
/**
|
||||
@@ -313,6 +374,22 @@ public class GradleBuildService extends Service implements BuildService, IToolin
|
||||
*/
|
||||
void prepareBuild();
|
||||
|
||||
/**
|
||||
* Called when a build is successful.
|
||||
*
|
||||
* @param tasks The tasks that were run.
|
||||
* @see IToolingApiClient#onBuildSuccessful(List)
|
||||
*/
|
||||
void onBuildSuccessful(@NonNull List<String> tasks);
|
||||
|
||||
/**
|
||||
* Called when a build fails.
|
||||
*
|
||||
* @param tasks The tasks that were run.
|
||||
* @see IToolingApiClient#onBuildFailed(List)
|
||||
*/
|
||||
void onBuildFailed(@NonNull List<String> tasks);
|
||||
|
||||
/**
|
||||
* Called when the output line is received.
|
||||
*
|
||||
|
||||
@@ -61,7 +61,7 @@ public abstract class ApkGeneratingTask extends BaseGradleTask {
|
||||
LOG.error("No apk directories found. buildDir=" + buildDir);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
for (File dir : dirs) {
|
||||
final File jsonData = new File(dir, "output-metadata.json");
|
||||
if (!jsonData.exists() || !FileUtils.isUtf8(jsonData)) {
|
||||
|
||||
Binary file not shown.
+16
@@ -45,4 +45,20 @@ interface IToolingApiClient {
|
||||
|
||||
/** Called just before a build is started. */
|
||||
@JsonNotification fun prepareBuild()
|
||||
|
||||
/**
|
||||
* Called when a build is successful.
|
||||
*
|
||||
* @param tasks The tasks that were run. Maybe an empty list if no tasks were specified or if
|
||||
* the build was not related to any tasks.
|
||||
*/
|
||||
@JsonNotification fun onBuildSuccessful(tasks: List<String>)
|
||||
|
||||
/**
|
||||
* Called when a build fails.
|
||||
*
|
||||
* @param tasks The tasks that were run. Maybe an empty list if no tasks were specified or if
|
||||
* the build was not related to any tasks.
|
||||
*/
|
||||
@JsonNotification fun onBuildFailed(tasks: List<String>)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user