/* * Copyright (c) 2009, Swedish Institute of Computer Science. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the Institute nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ package org.contikios.cooja.dialogs; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.Reader; import java.util.ArrayList; import java.util.regex.Pattern; import java.util.regex.Matcher; import javax.swing.Action; import org.apache.log4j.Logger; import org.contikios.cooja.Cooja; import org.contikios.cooja.MoteType; import org.contikios.cooja.MoteType.MoteTypeCreationException; import org.contikios.cooja.contikimote.ContikiMoteType; /** * Contiki compiler library. * Uses notion of Contiki platforms to compile a Contiki firmware. * * @author Fredrik Osterlind */ public class CompileContiki { private static Logger logger = Logger.getLogger(CompileContiki.class); /** * Executes a Contiki compilation command. * * @param command Command * @param env (Optional) Environment. May be null. * @param outputFile Expected output. May be null. * @param directory Directory in which to execute command * @param onSuccess Action called if compilation succeeds * @param onFailure Action called if compilation fails * @param compilationOutput Is written both std and err process output * @param synchronous If true, method blocks until process completes * @return Sub-process if called asynchronously * @throws Exception If process returns error, or outputFile does not exist */ public static Process compile( final String command, final String[] env, final File outputFile, final File directory, final Action onSuccess, final Action onFailure, final MessageList compilationOutput, boolean synchronous) throws Exception { Pattern p = Pattern.compile("([^\\s\"']+|\"[^\"]*\"|'[^']*')"); Matcher m = p.matcher(command); ArrayList commandList = new ArrayList(); while(m.find()) { String arg = m.group(); if (arg.length() > 1 && (arg.charAt(0) == '"' || arg.charAt(0) == '\'')) { arg = arg.substring(1, arg.length() - 1); } commandList.add(arg); } return compile(commandList.toArray(new String[commandList.size()]), env, outputFile, directory, onSuccess, onFailure, compilationOutput, synchronous); } /** * Executes a Contiki compilation command. * * @param command Command * @param env (Optional) Environment. May be null. * @param outputFile Expected output. May be null. * @param directory Directory in which to execute command * @param onSuccess Action called if compilation succeeds * @param onFailure Action called if compilation fails * @param messageDialog Is written both std and err process output * @param synchronous If true, method blocks until process completes * @return Sub-process if called asynchronously * @throws Exception If process returns error, or outputFile does not exist */ public static Process compile( final String command[], final String[] env, final File outputFile, final File directory, final Action onSuccess, final Action onFailure, final MessageList compilationOutput, boolean synchronous) throws Exception { /* TODO Fix me */ final MessageList messageDialog; if (compilationOutput == null) { messageDialog = new MessageList(); } else { messageDialog = compilationOutput; } { String cmd = ""; for (String c: command) { cmd += c + " "; } logger.info("> " + cmd); messageDialog.addMessage("", MessageList.NORMAL); messageDialog.addMessage("> " + cmd, MessageList.NORMAL); } final Process compileProcess; try { compileProcess = Runtime.getRuntime().exec(command, env, directory); final BufferedReader processNormal = new BufferedReader( new InputStreamReader(compileProcess.getInputStream())); final BufferedReader processError = new BufferedReader( new InputStreamReader(compileProcess.getErrorStream())); if (outputFile != null) { if (outputFile.exists()) { outputFile.delete(); } if (outputFile.exists()) { messageDialog.addMessage("Error when deleting old " + outputFile.getName(), MessageList.ERROR); if (onFailure != null) { onFailure.actionPerformed(null); } throw new MoteTypeCreationException("Error when deleting old " + outputFile.getName()); } } Thread readInput = new Thread(new Runnable() { public void run() { try { String readLine; while ((readLine = processNormal.readLine()) != null) { if (messageDialog != null) { messageDialog.addMessage(readLine, MessageList.NORMAL); } } } catch (IOException e) { logger.warn("Error while reading from process"); } } }, "read input stream thread"); Thread readError = new Thread(new Runnable() { public void run() { try { String readLine; while ((readLine = processError.readLine()) != null) { if (messageDialog != null) { messageDialog.addMessage(readLine, MessageList.ERROR); } } } catch (IOException e) { logger.warn("Error while reading from process"); } } }, "read error stream thread"); final MoteTypeCreationException syncException = new MoteTypeCreationException(""); Thread handleCompilationResultThread = new Thread(new Runnable() { public void run() { /* Wait for compilation to end */ try { compileProcess.waitFor(); } catch (Exception e) { messageDialog.addMessage(e.getMessage(), MessageList.ERROR); syncException.setCompilationOutput(new MessageList()); syncException.fillInStackTrace(); return; } /* Check return value */ if (compileProcess.exitValue() != 0) { messageDialog.addMessage("Process returned error code " + compileProcess.exitValue(), MessageList.ERROR); if (onFailure != null) { onFailure.actionPerformed(null); } syncException.setCompilationOutput(new MessageList()); syncException.fillInStackTrace(); return; } if (outputFile == null) { /* No firmware to generate: OK */ if (onSuccess != null) { onSuccess.actionPerformed(null); } return; } if (!outputFile.exists()) { messageDialog.addMessage("No firmware file: " + outputFile, MessageList.ERROR); if (onFailure != null) { onFailure.actionPerformed(null); } syncException.setCompilationOutput(new MessageList()); syncException.fillInStackTrace(); return; } messageDialog.addMessage("", MessageList.NORMAL); messageDialog.addMessage("Compilation succeded", MessageList.NORMAL); if (onSuccess != null) { onSuccess.actionPerformed(null); } } }, "handle compilation results"); readInput.start(); readError.start(); handleCompilationResultThread.start(); if (synchronous) { try { handleCompilationResultThread.join(); } catch (Exception e) { /* Make sure process has exited */ compileProcess.destroy(); String msg = e.getMessage(); if (e instanceof InterruptedException) { msg = "Aborted by user"; } throw (MoteTypeCreationException) new MoteTypeCreationException( "Compilation error: " + msg).initCause(e); } /* Detect error manually */ if (syncException.hasCompilationOutput()) { throw (MoteTypeCreationException) new MoteTypeCreationException( "Bad return value").initCause(syncException); } } } catch (IOException ex) { if (onFailure != null) { onFailure.actionPerformed(null); } throw (MoteTypeCreationException) new MoteTypeCreationException( "Compilation error: " + ex.getMessage()).initCause(ex); } return compileProcess; } /** * Generate JNI enabled Contiki main source file. * Used by Contiki Mote Type. * * @param sourceFile Source file to generate * @param javaClass JNI Java class * @param sensors Contiki sensors * @param coreInterfaces Core interfaces * @throws Exception At error * * @see ContikiMoteType */ public static void generateSourceFile( File sourceFile, String javaClass, String[] sensors, String[] coreInterfaces ) throws Exception { if (sourceFile == null) { throw new Exception("No output source file defined"); } if (javaClass == null) { throw new Exception("No Java class defined"); } if (sensors == null) { throw new Exception("No Contiki sensors defined"); } if (coreInterfaces == null) { throw new Exception("No Contiki dependencies defined"); } /* SENSORS */ String sensorString = ""; String externSensorDefs = ""; for (String sensor : sensors) { if (!sensorString.equals("")) { sensorString += ", "; } sensorString += "&" + sensor; externSensorDefs += "extern const struct sensors_sensor " + sensor + ";\n"; } if (!sensorString.equals("")) { sensorString = "SENSORS(" + sensorString + ");"; } else { sensorString = "SENSORS(NULL);"; } /* CORE INTERFACES */ String interfaceString = ""; String externInterfaceDefs = ""; for (String coreInterface : coreInterfaces) { if (!interfaceString.equals("")) { interfaceString += ", "; } interfaceString += "&" + coreInterface; externInterfaceDefs += "SIM_INTERFACE_NAME(" + coreInterface + ");\n"; } if (!interfaceString.equals("")) { interfaceString = "SIM_INTERFACES(" + interfaceString + ");"; } else { interfaceString = "SIM_INTERFACES(NULL);"; } /* If directory does not exist, try creating it */ if (!sourceFile.getParentFile().exists()) { logger.info("Creating source file directory: " + sourceFile.getParentFile().getAbsolutePath()); sourceFile.getParentFile().mkdir(); } /* GENERATE SOURCE FILE */ BufferedReader templateReader = null; BufferedWriter sourceFileWriter = null; try { Reader reader; String mainTemplate = Cooja.getExternalToolsSetting("CONTIKI_MAIN_TEMPLATE_FILENAME"); if ((new File(mainTemplate)).exists()) { reader = new FileReader(mainTemplate); } else { /* Try JAR, or fail */ InputStream input = CompileContiki.class.getResourceAsStream('/' + mainTemplate); if (input == null) { throw new FileNotFoundException(mainTemplate + " not found"); } reader = new InputStreamReader(input); } templateReader = new BufferedReader(reader); sourceFileWriter = new BufferedWriter(new OutputStreamWriter( new FileOutputStream(sourceFile))); // Replace special fields in template String line; while ((line = templateReader.readLine()) != null) { line = line.replaceFirst("\\[SENSOR_DEFINITIONS\\]", externSensorDefs); line = line.replaceFirst("\\[SENSOR_ARRAY\\]", sensorString); line = line.replaceFirst("\\[INTERFACE_DEFINITIONS\\]", externInterfaceDefs); line = line.replaceFirst("\\[INTERFACE_ARRAY\\]", interfaceString); line = line.replaceFirst("\\[CLASS_NAME\\]", javaClass); sourceFileWriter.write(line + "\n"); } sourceFileWriter.close(); templateReader.close(); } catch (Exception e) { try { if (templateReader != null) { templateReader.close(); } if (sourceFileWriter != null) { sourceFileWriter.close(); } } catch (Exception e2) { } // Forward exception throw e; } if (!sourceFile.exists()) { throw new Exception("Output source file does not exist: " + sourceFile.getAbsolutePath()); } logger.info("Generated Contiki main source: " + sourceFile.getName()); } /** * Generate compiler environment using external tools settings. * Used by Contiki Mote Type. * * @param identifier Mote type identifier, "mtype123" * @param contikiApp Contiki application source, "hello-world.c" * @param mapFile Output map file, "mtype123.map" * @param libFile Output JNI library, "mtype123.cooja" * @param archiveFile Output archive, "mtype123.a" * @param javaClass Java JNI library class, "Lib4" * @return Compilation environment * @throws Exception At errors */ public static String[][] createCompilationEnvironment( String identifier, File contikiApp, File mapFile, File libFile, File archiveFile, String javaClass) throws Exception { if (identifier == null) { throw new Exception("No identifier specified"); } if (contikiApp == null) { throw new Exception("No Contiki application specified"); } if (mapFile == null) { throw new Exception("No map file specified"); } if (libFile == null) { throw new Exception("No library output specified"); } if (archiveFile == null) { throw new Exception("No archive file specified"); } if (javaClass == null) { throw new Exception("No Java library class name specified"); } boolean includeSymbols = false; /* TODO */ /* Fetch configuration from external tools */ String link1 = Cooja.getExternalToolsSetting("LINK_COMMAND_1", ""); String link2 = Cooja.getExternalToolsSetting("LINK_COMMAND_2", ""); String ar1 = Cooja.getExternalToolsSetting("AR_COMMAND_1", ""); String ar2 = Cooja.getExternalToolsSetting("AR_COMMAND_2", ""); String ccFlags = Cooja.getExternalToolsSetting("COMPILER_ARGS", ""); /* Replace MAPFILE variable */ link1 = link1.replace("$(MAPFILE)", "obj_cooja/" + mapFile.getName()); link2 = link2.replace("$(MAPFILE)", "obj_cooja/" + mapFile.getName()); ar1 = ar1.replace("$(MAPFILE)", "obj_cooja/" + mapFile.getName()); ar2 = ar2.replace("$(MAPFILE)", "obj_cooja/" + mapFile.getName()); ccFlags = ccFlags.replace("$(MAPFILE)", "obj_cooja/" + mapFile.getName()); /* Replace LIBFILE variable */ link1 = link1.replace("$(LIBFILE)", "obj_cooja/" + libFile.getName()); link2 = link2.replace("$(LIBFILE)", "obj_cooja/" + libFile.getName()); ar1 = ar1.replace("$(LIBFILE)", "obj_cooja/" + libFile.getName()); ar2 = ar2.replace("$(LIBFILE)", "obj_cooja/" + libFile.getName()); ccFlags = ccFlags.replace("$(LIBFILE)", "obj_cooja/" + libFile.getName()); /* Replace ARFILE variable */ link1 = link1.replace("$(ARFILE)", "obj_cooja/" + archiveFile.getName()); link2 = link2.replace("$(ARFILE)", "obj_cooja/" + archiveFile.getName()); ar1 = ar1.replace("$(ARFILE)", "obj_cooja/" + archiveFile.getName()); ar2 = ar2.replace("$(ARFILE)", "obj_cooja/" + archiveFile.getName()); ccFlags = ccFlags.replace("$(ARFILE)", "obj_cooja/" + archiveFile.getName()); /* Replace JAVA_HOME variable */ String javaHome = System.getenv().get("JAVA_HOME"); if (javaHome == null) { javaHome = ""; } link1 = link1.replace("$(JAVA_HOME)", javaHome); link2 = link2.replace("$(JAVA_HOME)", javaHome); ar1 = ar1.replace("$(JAVA_HOME)", javaHome); ar2 = ar2.replace("$(JAVA_HOME)", javaHome); ccFlags = ccFlags.replace("$(JAVA_HOME)", javaHome); /* Strip away contiki application .c extension */ String contikiAppNoExtension = contikiApp.getName().substring(0, contikiApp.getName().length()-2); /* Create environment */ ArrayList env = new ArrayList(); env.add(new String[] { "LIBNAME", identifier }); env.add(new String[] { "CLASSNAME", javaClass }); env.add(new String[] { "CONTIKI_APP", contikiAppNoExtension }); env.add(new String[] { "COOJA_SOURCEDIRS", "" }); env.add(new String[] { "COOJA_SOURCEFILES", "" }); env.add(new String[] { "CC", Cooja.getExternalToolsSetting("PATH_C_COMPILER") }); env.add(new String[] { "OBJCOPY", Cooja.getExternalToolsSetting("PATH_OBJCOPY") }); env.add(new String[] { "EXTRA_CC_ARGS", ccFlags }); env.add(new String[] { "LD", Cooja.getExternalToolsSetting("PATH_LINKER") }); env.add(new String[] { "LINK_COMMAND_1", link1 }); env.add(new String[] { "LINK_COMMAND_2", link2 }); env.add(new String[] { "AR", Cooja.getExternalToolsSetting("PATH_AR") }); env.add(new String[] { "AR_COMMAND_1", ar1 }); env.add(new String[] { "AR_COMMAND_2", ar2 }); env.add(new String[] { "SYMBOLS", includeSymbols?"1":"" }); env.add(new String[] { "PATH", System.getenv("PATH") }); return env.toArray(new String[0][0]); } public static void redefineCOOJASources(MoteType moteType, String[][] env) { if (moteType == null || env == null) { return; } /* Check whether cooja projects include additional sources */ String[] coojaSources = moteType.getConfig().getStringArrayValue(ContikiMoteType.class, "C_SOURCES"); if (coojaSources == null) { return; } String sources = ""; String dirs = ""; for (String s: coojaSources) { if (s.trim().isEmpty()) { continue; } File p = moteType.getConfig().getUserProjectDefining(ContikiMoteType.class, "C_SOURCES", s); if (p == null) { logger.warn("Project defining C_SOURCES$" + s + " not found"); continue; } /* Redefine sources. TODO Move to createCompilationEnvironment. */ sources += s + " "; dirs += p.getPath() + " "; /* XXX Cygwin specific directory style */ if (dirs.contains("C:\\")) { dirs += p.getPath().replace("C:\\", "/cygdrive/c/") + " "; } } if (!sources.trim().isEmpty()) { for (int i=0; i < env.length; i++) { if (env[i][0].equals("COOJA_SOURCEFILES")) { env[i][1] = sources; break; } } } if (!dirs.trim().isEmpty()) { for (int i=0; i < env.length; i++) { if (env[i][0].equals("COOJA_SOURCEDIRS")) { env[i][1] = dirs.replace("\\", "/"); break; } } } } }