From 4e5200e54b164b755bad92f5518a029e545efc08 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Christoph=20L=C3=A4ubrich?= <laeubi@laeubi-soft.de>
Date: Fri, 24 Jan 2025 18:11:11 +0100
Subject: [PATCH] Support specification of an application to run

In some cases one might want a special application (e.g. a full IDE) to
start for a build, this is now possible by using the application
parameter

(cherry picked from commit 46edccf1cc1b8982f86b97d7e6d390efc103babb)
---
 .../osgi/framework/EclipseFramework.java      | 20 +++++++++++++
 .../eclipsebuild/AbstractEclipseBuild.java    | 10 ++++++-
 .../AbstractEclipseBuildMojo.java             | 29 ++++++++++++++++++-
 .../tycho/eclipsebuild/SetTargetPlatform.java |  3 ++
 4 files changed, 60 insertions(+), 2 deletions(-)

diff --git a/tycho-core/src/main/java/org/eclipse/tycho/osgi/framework/EclipseFramework.java b/tycho-core/src/main/java/org/eclipse/tycho/osgi/framework/EclipseFramework.java
index e546276f60..c00432c19e 100644
--- a/tycho-core/src/main/java/org/eclipse/tycho/osgi/framework/EclipseFramework.java
+++ b/tycho-core/src/main/java/org/eclipse/tycho/osgi/framework/EclipseFramework.java
@@ -37,6 +37,7 @@
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.BundleException;
 import org.osgi.framework.launch.Framework;
+import org.osgi.util.tracker.ServiceTracker;
 
 public class EclipseFramework implements AutoCloseable {
 
@@ -84,6 +85,25 @@ public void start() throws Exception {
         }
     }
 
+    public boolean waitForApplicationStart(long timeout) {
+        String[] args = configuration.getNonFrameworkArgs();
+        for (String arg : args) {
+            if (EclipseApplication.ARG_APPLICATION.equals(arg)) {
+                ServiceTracker<ApplicationLauncher, Object> tracker = new ServiceTracker<>(framework.getBundleContext(),
+                        ApplicationLauncher.class, null);
+                tracker.open(true);
+                try {
+                    return tracker.waitForService(timeout) != null;
+                } catch (InterruptedException e) {
+                    return false;
+                } finally {
+                    tracker.close();
+                }
+            }
+        }
+        return true;
+    }
+
     private int launchApplication(BundleContext systemBundleContext, EquinoxConfiguration configuration)
             throws Exception {
         EclipseAppLauncher appLauncher = new EclipseAppLauncher(systemBundleContext, false, true, null, configuration);
diff --git a/tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/AbstractEclipseBuild.java b/tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/AbstractEclipseBuild.java
index 46c449db14..5e67dbec89 100644
--- a/tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/AbstractEclipseBuild.java
+++ b/tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/AbstractEclipseBuild.java
@@ -19,6 +19,7 @@
 import org.eclipse.core.runtime.IPath;
 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.jobs.Job;
 
 /**
  * Abstract class for performing a build and producing a result
@@ -42,7 +43,7 @@ public final Result call() throws Exception {
 		deleteAllProjects();
 		IProject project = importProject();
 		project.build(IncrementalProjectBuilder.CLEAN_BUILD, this);
-		project.build(IncrementalProjectBuilder.FULL_BUILD, this);
+		buildProject(project);
 		Result result = createResult(project);
 		for (IMarker marker : project.findMarkers(IMarker.PROBLEM, true, IResource.DEPTH_INFINITE)) {
 			result.addMarker(marker);
@@ -52,6 +53,13 @@ public final Result call() throws Exception {
 		return result;
 	}
 
+	protected void buildProject(IProject project) throws CoreException {
+		project.build(IncrementalProjectBuilder.FULL_BUILD, this);
+		while (!Job.getJobManager().isIdle()) {
+			Thread.yield();
+		}
+	}
+
 	protected abstract Result createResult(IProject project) throws Exception;
 
 	static void disableAutoBuild() throws CoreException {
diff --git a/tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/AbstractEclipseBuildMojo.java b/tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/AbstractEclipseBuildMojo.java
index 07e4c0206c..ff5b3492f3 100644
--- a/tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/AbstractEclipseBuildMojo.java
+++ b/tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/AbstractEclipseBuildMojo.java
@@ -21,6 +21,7 @@
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
+import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
@@ -80,6 +81,8 @@ public abstract class AbstractEclipseBuildMojo<Result extends EclipseBuildResult
 	@Parameter(defaultValue = "false")
 	private boolean failOnResolutionError;
 
+	@Parameter
+	private String application;
 	/**
 	 * Controls if the local target platform of the project should be used to
 	 * resolve the eclipse application
@@ -137,11 +140,35 @@ public final void execute() throws MojoExecutionException, MojoFailureException
 		} else {
 			application = eclipseApplicationManager.getApplication(eclipseRepository, bundles, features, getName());
 		}
+		List<String> arguments;
+		String applicationName = this.application;
+		boolean useApplication = applicationName != null;
+		if (useApplication) {
+			arguments = List.of(EclipseApplication.ARG_APPLICATION, applicationName);
+		} else {
+			arguments = List.of();
+		}
 		try (EclipseFramework framework = application.startFramework(workspaceManager
-				.getWorkspace(EclipseApplicationManager.getRepository(eclipseRepository).getURL(), this), List.of())) {
+				.getWorkspace(EclipseApplicationManager.getRepository(eclipseRepository).getURL(), this), arguments)) {
 			if (debug) {
 				framework.printState();
 			}
+			if (useApplication) {
+				Thread thread = new Thread(new Runnable() {
+
+					@Override
+					public void run() {
+						try {
+							framework.start();
+						} catch (Exception e) {
+							getLog().error("Running application " + applicationName + " failed", e);
+						}
+					}
+				});
+				thread.setName(getName() + " Application Thread");
+				thread.start();
+				framework.waitForApplicationStart(TimeUnit.SECONDS.toMillis(30));
+			}
 			if (hasPDENature(eclipseProject)) {
 				if (framework.hasBundle(Bundles.BUNDLE_PDE_CORE)) {
 					framework.execute(new SetTargetPlatform(projectDependencies, debug));
diff --git a/tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/SetTargetPlatform.java b/tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/SetTargetPlatform.java
index c0e1473d7b..816956a41d 100644
--- a/tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/SetTargetPlatform.java
+++ b/tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/SetTargetPlatform.java
@@ -22,6 +22,7 @@
 
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.ILogListener;
+import org.eclipse.core.runtime.NullProgressMonitor;
 import org.eclipse.core.runtime.Platform;
 import org.eclipse.core.runtime.jobs.Job;
 import org.eclipse.pde.core.target.ITargetDefinition;
@@ -29,6 +30,7 @@
 import org.eclipse.pde.core.target.ITargetPlatformService;
 import org.eclipse.pde.core.target.LoadTargetDefinitionJob;
 import org.eclipse.pde.core.target.TargetBundle;
+import org.eclipse.pde.internal.core.PluginModelManager;
 import org.eclipse.pde.internal.core.target.TargetPlatformService;
 
 public class SetTargetPlatform implements Callable<Serializable>, Serializable {
@@ -64,6 +66,7 @@ public Serializable call() throws Exception {
 		Job job = new LoadTargetDefinitionJob(target);
 		job.schedule();
 		job.join();
+		Job.getJobManager().join(PluginModelManager.class, new NullProgressMonitor());
 		return null;
 	}