/* Copyright (C) 2011 Mobile Sorcery AB This program is free software; you can redistribute it and/or modify it under the terms of the Eclipse Public License v1.0. This program 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 Eclipse Public License v1.0 for more details. You should have received a copy of the Eclipse Public License v1.0 along with this program. It is also available at http://www.eclipse.org/legal/epl-v10.html */ package com.mobilesorcery.sdk.core.launch; import java.io.File; import java.io.IOException; import java.io.PipedInputStream; import java.io.PipedOutputStream; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import org.eclipse.cdt.core.CCorePlugin; import org.eclipse.cdt.core.IBinaryParser.IBinaryObject; import org.eclipse.cdt.core.ICDescriptor; import org.eclipse.cdt.core.model.ICProject; import org.eclipse.cdt.debug.core.cdi.ICDISession; import org.eclipse.cdt.internal.core.model.CModelManager; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Status; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.ILaunch; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; import org.eclipse.debug.core.model.IDebugTarget; import org.eclipse.debug.core.model.IProcess; import com.mobilesorcery.sdk.core.BuildVariant; import com.mobilesorcery.sdk.core.CoreMoSyncPlugin; import com.mobilesorcery.sdk.core.IBuildConfiguration; import com.mobilesorcery.sdk.core.IBuildVariant; import com.mobilesorcery.sdk.core.ILaunchConstants; import com.mobilesorcery.sdk.core.IPackager; import com.mobilesorcery.sdk.core.IProcessUtil; import com.mobilesorcery.sdk.core.MoSyncProject; import com.mobilesorcery.sdk.core.MoSyncTool; import com.mobilesorcery.sdk.core.SpawnedProcess; import com.mobilesorcery.sdk.core.Util; import com.mobilesorcery.sdk.internal.EmulatorOutputParser; import com.mobilesorcery.sdk.internal.OSPipeInputStream; import com.mobilesorcery.sdk.internal.debug.MoSyncCDebugTarget; import com.mobilesorcery.sdk.internal.debug.MoSyncDebugger; import com.mobilesorcery.sdk.internal.launch.EmulatorLaunchConfigurationDelegate; import com.mobilesorcery.sdk.internal.launch.EmulatorParseEventHandler; import com.mobilesorcery.sdk.profiles.IProfile; /** * This is the app launcher for the (default) MoRe emulator. * @author Mattias Bybro, mattias@bybro.com * TODO: Quite a bunch of remains from the emulatorlaunchconfigurationdelegate class. */ public class MoReLauncher extends AbstractEmulatorLauncher { public MoReLauncher() { super("MoRe Emulator"); } public final static String ID = "default"; @Override public String getId() { return ID; } @Override public void launch(ILaunchConfiguration launchConfig, String mode, ILaunch launch, int emulatorId, IProgressMonitor monitor) throws CoreException { boolean debug = EmulatorLaunchConfigurationDelegate.isDebugMode(mode); String width = launchConfig.getAttribute(ILaunchConstants.SCREEN_SIZE_WIDTH, "320"); String height = launchConfig.getAttribute(ILaunchConstants.SCREEN_SIZE_HEIGHT, "480"); IProject project = EmulatorLaunchConfigurationDelegate.getProject(launchConfig); IBuildVariant variant = EmulatorLaunchConfigurationDelegate.getVariant(launchConfig, mode); MoSyncProject mosyncProject = MoSyncProject.create(project); IBuildConfiguration buildConfiguration = mosyncProject.getBuildConfiguration(variant.getConfigurationId()); if (launchConfig.getAttribute(ILaunchConstants.SCREEN_SIZE_OF_TARGET, true)) { IProfile profile = mosyncProject.getTargetProfile(); if (profile != null) { Object profileWidth = profile.getProperties().get(IProfile.SCREEN_SIZE_X); Object profileHeight = profile.getProperties().get(IProfile.SCREEN_SIZE_Y); if (isNumber(profileWidth) && isNumber(profileHeight)) { width = "" + profileWidth; height = "" + profileHeight; } } } IProcessUtil pu = CoreMoSyncPlugin.getDefault().getProcessUtil(); int fds[] = new int[2]; pu.pipe_create(fds); int readFd = fds[0]; int writeFd = fds[1]; int dupWriteFd = pu.pipe_dup(writeFd); pu.pipe_close(writeFd); String[] cmdline = getCommandLine(project, variant, width, height, dupWriteFd, emulatorId, debug); final EmulatorParseEventHandler handler = new EmulatorParseEventHandler(mosyncProject, buildConfiguration); PipedOutputStream messageOutputStream = new PipedOutputStream(); PipedInputStream messageInputStream = null; try { messageInputStream = new PipedInputStream(messageOutputStream) { @Override public int read() throws IOException { return super.read(); } }; } catch (IOException e) { throw new CoreException(new Status(IStatus.ERROR, CoreMoSyncPlugin.PLUGIN_ID, e.getMessage())); } handler.setMessageOutputStream(messageOutputStream); handler.setEmulatorId(emulatorId); if (CoreMoSyncPlugin.getDefault().isDebugging()) { CoreMoSyncPlugin.trace("Emulator command line:\n " + Util.join(Util.ensureQuoted(cmdline), " ")); } IPath outputPath = EmulatorLaunchConfigurationDelegate.getLaunchDir(mosyncProject, variant); File dir = outputPath.toFile(); //String command = Util.join(Util.ensureQuoted(cmdline), " "); final SpawnedProcess process = new SpawnedProcess(getMoREExe(), cmdline, dir); final EmulatorOutputParser parser = new EmulatorOutputParser(emulatorId, handler); startEmulatorListener(process, parser, readFd, dupWriteFd); process.setInputStream(messageInputStream); process.setShutdownHook(new Runnable() { @Override public void run() { parser.awaitParseEventsToBeHandled(2000); } }); try { process.start(); CoreMoSyncPlugin.getDefault().getEmulatorProcessManager().processStarted(emulatorId); IProcess p = DebugPlugin.newProcess(launch, process, project.getName()); IPath program = outputPath.append("program"); if (debug) { attachDebugger(launch, p, program); } try { int errcode = process.waitFor(); if (errcode != 0) { handler.setExitMessage(getErrorMessage(errcode)); } } catch (InterruptedException e) { CoreMoSyncPlugin.getDefault().log(e); } finally { CoreMoSyncPlugin.getDefault().getEmulatorProcessManager().processStopped(emulatorId); } } catch (Exception e) { throw new CoreException(new Status(IStatus.ERROR, CoreMoSyncPlugin.PLUGIN_ID, e.getMessage(), e)); } pu.pipe_close(dupWriteFd); } private boolean isNumber(Object profileHeight) { try { return profileHeight instanceof Number || Integer.parseInt("" + profileHeight) >= 0; } catch (Exception e) { // } return false; } private void attachDebugger(ILaunch launch, IProcess process, IPath program) throws CoreException { IFile[] programFiles = ResourcesPlugin.getWorkspace().getRoot().findFilesForLocation(program); IProject project = EmulatorLaunchConfigurationDelegate.getProject(launch.getLaunchConfiguration()); IFile programFile = null; for (int i = 0; programFile == null && i < programFiles.length; i++) { if (programFiles[i].getProject().equals(project)) { programFile = programFiles[i]; } } MoSyncDebugger dbg = new MoSyncDebugger(); ICDISession targetSession = dbg.createSession(launch, program.toFile(), new NullProgressMonitor()); IBinaryObject binaryFile = (IBinaryObject) CModelManager.getDefault().createBinaryFile(programFile); //IDebugTarget debugTarget = CDIDebugModel.newDebugTarget(launch, project, targetSession.getTargets()[0], launch.getLaunchConfiguration().getName(), process, binaryFile, true, false, true); IDebugTarget debugTarget = MoSyncCDebugTarget.newDebugTarget(launch, project, targetSession.getTargets()[0], launch.getLaunchConfiguration().getName(), process, binaryFile, true, false, null, true); } private void addBinaryParser(IProject project) throws CoreException { ICDescriptor cDescriptor = CCorePlugin.getDefault().getCDescriptorManager().getDescriptor(project); cDescriptor.create("org.eclipse.cdt.core.BinaryParser", "org.eclipse.cdt.core.ELF"); } private ICProject getCProject(IProject project) { return CModelManager.getDefault().getCModel().findCProject(project); } private String getErrorMessage(int errcode) throws IOException { String msg = MessageFormat.format("Exited with error code {0}: {1}", errcode, findErrorMessage(errcode)); return msg; } private String findErrorMessage(int errcode) { String panicMessage = CoreMoSyncPlugin.getDefault().getPanicMessage(errcode); return panicMessage == null ? "<No error message>" : panicMessage; } private void startEmulatorListener(SpawnedProcess process, final EmulatorOutputParser parser, final int readFd, final int writeFd) { final OSPipeInputStream input = new OSPipeInputStream(readFd); Runnable emulatorListener = new Runnable() { @Override public void run() { try { parser.parse(input); } catch (IOException e) { e.printStackTrace(); } finally { input.close(); } } }; Thread thread = new Thread(emulatorListener, "Reading from pipe"); thread.setDaemon(true); thread.start(); } private String[] getCommandLine(IProject project, IBuildVariant variant, String width, String height, int fd, int id, boolean debug) throws CoreException { IPath outputPath = EmulatorLaunchConfigurationDelegate.getLaunchDir(MoSyncProject.create(project), variant); IPath program = outputPath.append("program"); IPath resources = outputPath.append("resources"); if (!program.toFile().exists()) { throw new CoreException(new Status(IStatus.ERROR, CoreMoSyncPlugin.PLUGIN_ID, "Could find no executable - please rebuild")); } ArrayList<String> args = new ArrayList<String>(); args.addAll(Arrays.asList(new String[] { getMoREExe(), "-program", program.toOSString(), "-resource", resources.toOSString(), "-resolution", "" + width, "" + height, "-fd", Integer.toString(fd), "-id", Integer.toString(id) /*,"-icon", outputPath.append("more.png").toOSString()*/ })); if (debug) { args.add("-gdb"); } return args.toArray(new String[args.size()]); } private String getMoREExe() { return MoSyncTool.getDefault().getBinary("MoRE").toOSString(); } @Override public int isLaunchable(ILaunchConfiguration config, String mode) { return LAUNCHABLE; } @Override public void setDefaultAttributes(ILaunchConfigurationWorkingCopy wc) { wc.setAttribute(ILaunchConstants.SCREEN_SIZE_HEIGHT, "480"); wc.setAttribute(ILaunchConstants.SCREEN_SIZE_WIDTH, "320"); } @Override public int getLaunchType(IPackager packager) { return LAUNCH_TYPE_DEFAULT; } }