/******************************************************************************* * Copyright (c) 2008, 2011 Thomas Holland (thomas@innot.de) and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Thomas Holland - initial API and implementation *******************************************************************************/ package de.innot.avreclipse.ui.actions; import java.io.File; import java.text.MessageFormat; import java.util.List; import org.eclipse.cdt.core.model.ICElement; import org.eclipse.cdt.core.model.ICProject; import org.eclipse.cdt.managedbuilder.core.IConfiguration; import org.eclipse.cdt.managedbuilder.core.IManagedBuildInfo; import org.eclipse.cdt.managedbuilder.core.ManagedBuildManager; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.SubProgressMonitor; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.action.IAction; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.IActionDelegate; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.IWorkbenchWindowActionDelegate; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.actions.ActionDelegate; import org.eclipse.ui.console.MessageConsole; import org.eclipse.ui.progress.UIJob; import de.innot.avreclipse.AVRPlugin; import de.innot.avreclipse.core.avrdude.AVRDudeAction; import de.innot.avreclipse.core.avrdude.AVRDudeException; import de.innot.avreclipse.core.avrdude.AVRDudeSchedulingRule; import de.innot.avreclipse.core.avrdude.BaseBytesProperties; import de.innot.avreclipse.core.avrdude.ProgrammerConfig; import de.innot.avreclipse.core.properties.AVRDudeProperties; import de.innot.avreclipse.core.properties.AVRProjectProperties; import de.innot.avreclipse.core.properties.ProjectPropertyManager; import de.innot.avreclipse.core.toolinfo.AVRDude; import de.innot.avreclipse.core.toolinfo.fuses.FuseType; import de.innot.avreclipse.core.util.AVRMCUidConverter; import de.innot.avreclipse.mbs.BuildMacro; import de.innot.avreclipse.ui.dialogs.AVRDudeErrorDialogJob; /** * @author Thomas Holland * @since 2.2 * @since 2.3 Added optional delay between avrdude invocations * */ public class UploadProjectAction extends ActionDelegate implements IWorkbenchWindowActionDelegate { private final static String TITLE_UPLOAD = "AVRDude Upload"; private final static String SOURCE_BUILDCONFIG = "active build configuration"; private final static String SOURCE_PROJECT = "project"; private final static String MSG_NOPROJECT = "No AVR project selected"; private final static String MSG_NOPROGRAMMER = "No Programmer has been set for the {0}.\n\n" + "Please select a Programmer in the project properties\n" + "(Properties -> AVRDude -> Programmer)"; private final static String MSG_WRONGMCU = "AVRDude does not support the project target MCU [{0}]\n\n" + "Please select a different target MCU if you want to use AVRDude.\n" + "(Properties -> Target Hardware)"; private final static String MSG_NOACTIONS = "The {0} has no options set to upload anything to the device.\n\n" + "Please select at least one item to upload (flash / eeprom / fuses / lockbits)"; private final static String MSG_MISSING_FILE = "The file [{0}] for the {1} memory does not exist or is not readable\n\n" + "Maybe the project needs to be build first."; private final static String MSG_MISSING_FUSES_FILE = "The selected {0} file [{1}] does not exist or is not readable\n\n" + "Please select a different {0} source.\n" + "(Properties -> AVRDude -> {0}"; private final static String MSG_INVALIDFUSEBYTE = "The {0} byte(s) to upload are for an {1} MCU, " + "which is not compatible with the {3} target MCU [{2}]\n\n" + "Please check the fuse byte settings.\n" + "(Properties -> AVRDude -> {0})"; private final static String MSG_NEEDS_REBUILD = "A rebuild is required before upload."; private IProject fProject; /** * Constructor for this Action. */ public UploadProjectAction() { super(); } /** * @see IActionDelegate#selectionChanged(IAction, ISelection) */ @Override public void selectionChanged(IAction action, ISelection selection) { // The user has selected a different Workbench object. // If it is an IProject we keep it. Object item; if (selection instanceof IStructuredSelection) { item = ((IStructuredSelection) selection).getFirstElement(); } else { return; } if (item == null) { return; } IProject project = null; // See if the given is an IProject (directly or via IAdaptable) if (item instanceof IProject) { project = (IProject) item; } else if (item instanceof IResource) { project = ((IResource) item).getProject(); } else if (item instanceof IAdaptable) { IAdaptable adaptable = (IAdaptable) item; project = (IProject) adaptable.getAdapter(IProject.class); if (project == null) { // Try ICProject -> IProject ICProject cproject = (ICProject) adaptable.getAdapter(ICProject.class); if (cproject == null) { // Try ICElement -> ICProject -> IProject ICElement celement = (ICElement) adaptable.getAdapter(ICElement.class); if (celement != null) { cproject = celement.getCProject(); } } if (cproject != null) { project = cproject.getProject(); } } } fProject = project; } /** * @see IActionDelegate#run(IAction) */ @Override public void run(IAction action) { // Check that we have a AVR Project try { if (fProject == null || !fProject.hasNature("de.innot.avreclipse.core.avrnature")) { MessageDialog.openError(getShell(), TITLE_UPLOAD, MSG_NOPROJECT); return; } } catch (CoreException e) { // Log the Exception IStatus status = new Status(Status.ERROR, AVRPlugin.PLUGIN_ID, "Can't access project nature", e); AVRPlugin.getDefault().log(status); } // Get the active build configuration IManagedBuildInfo bi = ManagedBuildManager.getBuildInfo(fProject); IConfiguration activecfg = bi.getDefaultConfiguration(); // Check if project needs to be rebuild (e.g. MCU type changed) if (activecfg.needsRebuild()) { MessageDialog.openError(getShell(), TITLE_UPLOAD, MSG_NEEDS_REBUILD); return; } // Get the avr properties for the active configuration AVRProjectProperties targetprops = ProjectPropertyManager.getPropertyManager(fProject) .getActiveProperties(); // Check if the avrdude properties are valid. // if not the checkProperties() method will display an error message box if (!checkProperties(activecfg, targetprops)) { return; } // Everything is fine -> run avrdude runAVRDude(activecfg, targetprops); } /** * Check that the current properties are valid. * <p> * This method will check that: * <ul> * <li>there has been a Programmer selected</li> * <li>avrdude supports the selected MCU</li> * <li>there are some actions to perform</li> * <li>all source files exist</li> * <li>the fuse bytes (if uploaded) are valid for the target MCU</li> * </ul> * <p> * * @param buildcfg * The current build configuration * @param props * The current Properties * @return <code>true</code> if everything is OK. */ private boolean checkProperties(IConfiguration buildcfg, AVRProjectProperties props) { boolean perconfig = ProjectPropertyManager.getPropertyManager(fProject).isPerConfig(); String source = perconfig ? SOURCE_BUILDCONFIG : SOURCE_PROJECT; // Check that a Programmer has been selected if (props.getAVRDudeProperties().getProgrammer() == null) { String message = MessageFormat.format(MSG_NOPROGRAMMER, source); MessageDialog.openError(getShell(), TITLE_UPLOAD, message); return false; } // Check that the MCU is valid for avrdude String mcuid = props.getMCUId(); if (!AVRDude.getDefault().hasMCU(mcuid)) { String message = MessageFormat.format(MSG_WRONGMCU, AVRMCUidConverter.id2name(mcuid)); MessageDialog.openError(getShell(), TITLE_UPLOAD, message); return false; } // Check that there is actually anything to upload List<String> actionlist = props.getAVRDudeProperties().getActionArguments(buildcfg, true); if (actionlist.size() == 0) { String message = MessageFormat.format(MSG_NOACTIONS, source); MessageDialog.openWarning(getShell(), TITLE_UPLOAD, message); return false; } // Check all referenced files // It would be cumbersome to go through all possible cases. Instead we // convert all action arguments back to AVRDudeActions and get the // filename from it. IPath invalidfile = null; String formemtype = null; for (String argument : actionlist) { AVRDudeAction action = AVRDudeAction.getActionForArgument(argument); String filename = action.getFilename(); if (filename == null) continue; IPath rawfile = new Path(filename); IPath unresolvedfile = rawfile; IPath resolvedfile = rawfile; if (!rawfile.isAbsolute()) { // The filename is relative to the build folder. Get the build // folder and append our filename. Then resolve any macros unresolvedfile = buildcfg.getBuildData().getBuilderCWD().append(rawfile); resolvedfile = new Path(BuildMacro.resolveMacros(buildcfg, unresolvedfile .toString())); } File realfile = resolvedfile.toFile(); if (!realfile.canRead()) { invalidfile = unresolvedfile; formemtype = action.getMemType().toString(); break; } } if (invalidfile != null) { String message = MessageFormat.format(MSG_MISSING_FILE, invalidfile.toString(), formemtype); MessageDialog.openError(getShell(), TITLE_UPLOAD, message); return false; } // Check that the fuses and locks are valid (if they are to be uploaded) for (FuseType type : FuseType.values()) { if (props.getAVRDudeProperties().getBytesProperties(type, buildcfg).getWrite()) { BaseBytesProperties bytesproperties = props.getAVRDudeProperties() .getBytesProperties(type, buildcfg); if (bytesproperties.getMCUId() == null) { // A non-existing file has been selected as source for the fuses String message = MessageFormat.format(MSG_MISSING_FUSES_FILE, type.toString(), bytesproperties.getFileNameResolved()); MessageDialog.openError(getShell(), TITLE_UPLOAD, message); return false; } if (!bytesproperties.isCompatibleWith(props.getMCUId())) { String fusesmcuid = AVRMCUidConverter.id2name(bytesproperties.getMCUId()); String propsmcuid = AVRMCUidConverter.id2name(props.getMCUId()); String message = MessageFormat.format(MSG_INVALIDFUSEBYTE, type.toString(), fusesmcuid, propsmcuid, source); MessageDialog.openError(getShell(), TITLE_UPLOAD, message); return false; } } } // Everything is OK return true; } /** * Start the AVRDude UploadJob. * * @param buildcfg * The build configuration for resolving macros. * @param props * The AVR properties for the project / the current configuration */ private void runAVRDude(IConfiguration buildcfg, AVRProjectProperties props) { AVRDudeProperties avrdudeprops = props.getAVRDudeProperties(); // get the list of normal (non-action) arguments List<String> optionargs = avrdudeprops.getArguments(); // get a list of actions List<String> actionargs = avrdudeprops.getActionArguments(buildcfg, true); // Get the ProgrammerConfig in case we need to display an error // message ProgrammerConfig programmer = avrdudeprops.getProgrammer(); // Set the working directory to the CWD of the active build config, so that // relative paths are resolved correctly. IPath cwdunresolved = buildcfg.getBuildData().getBuilderCWD(); IPath cwd = new Path(BuildMacro.resolveMacros(buildcfg, cwdunresolved.toString())); Job uploadjob = new UploadJob(optionargs, actionargs, cwd, programmer); uploadjob.setRule(new AVRDudeSchedulingRule(programmer)); uploadjob.setPriority(Job.LONG); uploadjob.setUser(true); uploadjob.schedule(); } /** * The background Job to execute the requested avrdude commands. * */ private static class UploadJob extends Job { private final List<String> fOptions; private final List<String> fActions; private final IPath fCwd; private final ProgrammerConfig fProgrammerConfig; public UploadJob(List<String> options, List<String> actions, IPath cwd, ProgrammerConfig programmer) { super("AVRDude Upload"); fOptions = options; fActions = actions; fCwd = cwd; fProgrammerConfig = programmer; } @Override public IStatus run(IProgressMonitor monitor) { try { monitor.beginTask("Running AVRDude", fActions.size()); // init console. Clears the console and puts it on top. // AVRDude is forced to use the console, so the user will always // see the output, regardless of the "use console" flag. MessageConsole console = AVRPlugin.getDefault().getConsole("AVRDude"); console.clearConsole(); console.activate(); AVRDude avrdude = AVRDude.getDefault(); // Append all requested actions // The reason this is done here is because in earlier versions // of the plugin each action was send separately to avrdude to better // track the progress. // However some users complained that this slows the whole upload process down. // So now we sent all actions in one go, as the user can monitor the progress // in the console anyway. fOptions.addAll(fActions); monitor.subTask("Running AVRDude"); // Now avrdude can be started. avrdude.runCommand(fOptions, new SubProgressMonitor(monitor, 1), true, fCwd, fProgrammerConfig); } catch (AVRDudeException ade) { // Show an Error message and exit Display display = PlatformUI.getWorkbench().getDisplay(); if (display != null && !display.isDisposed()) { UIJob messagejob = new AVRDudeErrorDialogJob(display, ade, fProgrammerConfig .getId()); messagejob.setPriority(Job.INTERACTIVE); messagejob.schedule(); try { messagejob.join(); // block until the dialog is closed. } catch (InterruptedException e) { // Don't care if the dialog is interrupted from outside. } } } finally { monitor.done(); } return Status.OK_STATUS; } } /** * Get the current Shell. * * @return <code>Shell</code> of the active Workbench window. */ private Shell getShell() { return PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(); } /* * (non-Javadoc) * @see org.eclipse.ui.IWorkbenchWindowActionDelegate#init(org.eclipse.ui.IWorkbenchWindow) */ public void init(IWorkbenchWindow window) { // TODO Auto-generated method stub } }