/* -*- mode: jde; r-basic-offset: 2; indent-tabs-mode: nil -*- */
/*
Compiler - default compiler class that connects to avr-gcc
Part of the Wiring project - http://wiring.org.co
Copyright (c) 2004-11 Hernando Barragan
Processing version
Copyright (c) 2004-05 Ben Fry and Casey Reas
Copyright (c) 2001-04 Massachusetts Institute of Technology
This program 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 2 of the License, or
(at your option) any later version.
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
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package processing.app.debug;
import processing.app.*;
import processing.core.*;
import java.io.*;
import java.util.*;
import java.util.zip.*;
import javax.swing.*;
public class Compiler implements MessageConsumer {
static final String BUGS_URL =
"http://wiring.org.co/bugs/";
static final String SUPER_BADNESS =
"Compiler error, please submit this code to " + BUGS_URL;
Sketch sketch;
String buildPath;
String primaryClassName;
boolean verbose;
RunnerException exception;
public Compiler() { } // consider this a warning, you werkin soon.
public boolean compile(Sketch sketch,
String buildPath,
String primaryClassName,
boolean verbose) throws RunnerException {
this.sketch = sketch;
this.buildPath = buildPath;
this.primaryClassName = primaryClassName;
this.verbose = verbose;
// the pms object isn't used for anything but storage
MessageStream pms = new MessageStream(this);
String avrBasePath = Base.getAvrBasePath();
Map<String, String> boardPreferences = Base.getBoardPreferences();
String hardware = boardPreferences.get("build.hardware");
if (hardware == null) {
RunnerException re = new RunnerException("No board selected; please choose a board from the Tools > Board menu.");
re.hideStackTrace();
throw re;
}
String hardwarePath;
if (hardware.indexOf(':') == -1) {
Target t = Base.getTarget();
File hardwareFolder = new File(t.getFolder(), hardware);
hardwarePath = hardwareFolder.getAbsolutePath();
} else {
Target t = Base.targetsTable.get(hardware.substring(0, hardware.indexOf(':')));
File hardwaresFolder = t.getFolder();
File hardwareFolder = new File(hardwaresFolder, hardware.substring(hardware.indexOf(':') + 1));
hardwarePath = hardwareFolder.getAbsolutePath();
}
String core = boardPreferences.get("build.core");
String coresPath;
String coresCommonPath;
if (core == null) {
RunnerException re = new RunnerException("The board selected has no core specified, check your board's definition of build.core.");
re.hideStackTrace();
throw re;
}
File coresFolder = Base.coresTable.get(core);
File coresCommonFolder = Base.coresTable.get("Common");
coresPath = coresFolder.getAbsolutePath();
coresCommonPath = coresCommonFolder.getAbsolutePath();
List<File> objectFiles = new ArrayList<File>();
List includePaths = new ArrayList();
includePaths.add(coresCommonPath);
includePaths.add(coresPath);
includePaths.add(hardwarePath);
includePaths.add(buildPath);
sketch.setCompilingProgress(20);
// use library directories as include paths for all libraries
for (File file : sketch.getImportedLibraries()) {
includePaths.add(file.getPath());
}
// 1. compile the sketch (already in the buildPath)
objectFiles.addAll(
compileFiles(avrBasePath, buildPath, includePaths,
findFilesInPath(buildPath, "S", false),
findFilesInPath(buildPath, "c", false),
findFilesInPath(buildPath, "cpp", false),
boardPreferences));
sketch.setCompilingProgress(30);
// 2. compile the libraries, outputting .o files to: <buildPath>/<library>/
for (File libraryFolder : sketch.getImportedLibraries()) {
File outputFolder = new File(buildPath, libraryFolder.getName());
File utilityFolder = new File(libraryFolder, "utility");
createFolder(outputFolder);
// this library can use includes in its utility/ folder
includePaths.add(utilityFolder.getAbsolutePath());
// Barragan: libraries might depend on other libraries, so add all paths for compilation
objectFiles.addAll(
compileFiles(avrBasePath, outputFolder.getAbsolutePath(), includePaths,
findFilesInFolder(libraryFolder, "S", false),
findFilesInFolder(libraryFolder, "c", false),
findFilesInFolder(libraryFolder, "cpp", false),
boardPreferences));
outputFolder = new File(outputFolder, "utility");
createFolder(outputFolder);
objectFiles.addAll(
compileFiles(avrBasePath, outputFolder.getAbsolutePath(), includePaths,
findFilesInFolder(utilityFolder, "S", false),
findFilesInFolder(utilityFolder, "c", false),
findFilesInFolder(utilityFolder, "cpp", false),
boardPreferences));
// other libraries should not see this library's utility/ folder
includePaths.remove(includePaths.size() - 1);
}
sketch.setCompilingProgress(40);
// 3a. compile the hardware, outputting .o files to <buildPath>
includePaths.clear();
includePaths.add(hardwarePath);
includePaths.add(coresCommonPath);
includePaths.add(coresPath);
List<File> hardwareObjectFiles =
compileFiles(avrBasePath, buildPath, includePaths,
findFilesInPath(hardwarePath, "S", true),
findFilesInPath(hardwarePath, "c", true),
findFilesInPath(hardwarePath, "cpp", true),
boardPreferences);
sketch.setCompilingProgress(50);
// 3b. compile the core, outputting .o files to <buildPath> and then
// collecting them into the core.a library file.
includePaths.clear();
includePaths.add(hardwarePath);
includePaths.add(coresCommonPath);
includePaths.add(coresPath);
List<File> coresObjectFiles = new ArrayList<File>();
coresObjectFiles.addAll(
compileFiles(avrBasePath, buildPath, includePaths,
findFilesInPath(coresPath, "S", false),
findFilesInPath(coresPath, "c", false),
findFilesInPath(coresPath, "cpp", false),
boardPreferences));
sketch.setCompilingProgress(60);
coresObjectFiles.addAll(
compileFiles(avrBasePath, buildPath, includePaths,
findFilesInPath(coresCommonPath, "S", false),
findFilesInPath(coresCommonPath, "c", false),
findFilesInPath(coresCommonPath, "cpp", false),
boardPreferences));
coresObjectFiles.addAll(hardwareObjectFiles);
sketch.setCompilingProgress(70);
String runtimeLibraryName = buildPath + File.separator + "core.a";
List baseCommandAR = new ArrayList(Arrays.asList(new String[] {
avrBasePath + "avr-ar",
"rcs",
runtimeLibraryName
}));
for(File file : coresObjectFiles) {
List commandAR = new ArrayList(baseCommandAR);
commandAR.add(file.getAbsolutePath());
execAsynchronously(commandAR);
}
// 4. link it all together into the .elf file
sketch.setCompilingProgress(80);
List baseCommandLinker = new ArrayList(Arrays.asList(new String[] {
avrBasePath + "avr-gcc",
"-Os",
"-Wl,--gc-sections",
"-mmcu=" + boardPreferences.get("build.mcu"),
"-o",
buildPath + File.separator + primaryClassName + ".elf"
}));
for (File file : objectFiles) {
baseCommandLinker.add(file.getAbsolutePath());
}
baseCommandLinker.add(runtimeLibraryName);
baseCommandLinker.add("-L" + buildPath);
baseCommandLinker.add("-lm");
execAsynchronously(baseCommandLinker);
sketch.setCompilingProgress(85);
List baseCommandObjcopy = new ArrayList(Arrays.asList(new String[] {
avrBasePath + "avr-objcopy",
"-O",
"-R",
}));
List commandObjcopy;
// 5. extract EEPROM data (from EEMEM directive) to .eep file.
commandObjcopy = new ArrayList(baseCommandObjcopy);
commandObjcopy.add(2, "ihex");
commandObjcopy.set(3, "-j");
commandObjcopy.add(".eeprom");
commandObjcopy.add("--set-section-flags=.eeprom=alloc,load");
commandObjcopy.add("--no-change-warnings");
commandObjcopy.add("--change-section-lma");
commandObjcopy.add(".eeprom=0");
commandObjcopy.add(buildPath + File.separator + primaryClassName + ".elf");
commandObjcopy.add(buildPath + File.separator + primaryClassName + ".eep");
execAsynchronously(commandObjcopy);
// 6. build the .hex file
commandObjcopy = new ArrayList(baseCommandObjcopy);
commandObjcopy.add(2, "ihex");
commandObjcopy.add(".eeprom"); // remove eeprom data
commandObjcopy.add(buildPath + File.separator + primaryClassName + ".elf");
commandObjcopy.add(buildPath + File.separator + primaryClassName + ".hex");
execAsynchronously(commandObjcopy);
sketch.setCompilingProgress(90);
return true;
}
private List<File> compileFiles(String avrBasePath,
String buildPath, List<File> includePaths,
List<File> sSources,
List<File> cSources, List<File> cppSources,
Map<String, String> boardPreferences)
throws RunnerException {
List<File> objectPaths = new ArrayList<File>();
for (File file : sSources) {
String objectPath = buildPath + File.separator + file.getName() + ".o";
objectPaths.add(new File(objectPath));
execAsynchronously(getCommandCompilerS(avrBasePath, includePaths,
file.getAbsolutePath(),
objectPath,
boardPreferences));
}
for (File file : cSources) {
String objectPath = buildPath + File.separator + file.getName() + ".o";
objectPaths.add(new File(objectPath));
execAsynchronously(getCommandCompilerC(avrBasePath, includePaths,
file.getAbsolutePath(),
objectPath,
boardPreferences));
}
for (File file : cppSources) {
String objectPath = buildPath + File.separator + file.getName() + ".o";
objectPaths.add(new File(objectPath));
execAsynchronously(getCommandCompilerCPP(avrBasePath, includePaths,
file.getAbsolutePath(),
objectPath,
boardPreferences));
}
return objectPaths;
}
/**
* Either succeeds or throws a RunnerException fit for public consumption.
*/
private void execAsynchronously(List commandList) throws RunnerException {
String[] command = new String[commandList.size()];
commandList.toArray(command);
int result = 0;
if (verbose || Preferences.getBoolean("build.verbose")) {
for(int j = 0; j < command.length; j++) {
System.out.print(command[j] + " ");
}
System.out.println();
}
firstErrorFound = false; // haven't found any errors yet
secondErrorFound = false;
Process process;
try {
process = Runtime.getRuntime().exec(command);
} catch (IOException e) {
RunnerException re = new RunnerException(e.getMessage());
re.hideStackTrace();
throw re;
}
MessageSiphon in = new MessageSiphon(process.getInputStream(), this);
MessageSiphon err = new MessageSiphon(process.getErrorStream(), this);
// wait for the process to finish. if interrupted
// before waitFor returns, continue waiting
boolean compiling = true;
while (compiling) {
try {
if (in.thread != null)
in.thread.join();
if (err.thread != null)
err.thread.join();
result = process.waitFor();
//System.out.println("result is " + result);
compiling = false;
} catch (InterruptedException ignored) { }
}
// an error was queued up by message(), barf this back to compile(),
// which will barf it back to Editor. if you're having trouble
// discerning the imagery, consider how cows regurgitate their food
// to digest it, and the fact that they have five stomaches.
//
//System.out.println("throwing up " + exception);
if (exception != null) { throw exception; }
if (result > 1) {
// a failure in the tool (e.g. unable to locate a sub-executable)
System.err.println(command[0] + " returned " + result);
}
if (result != 0) {
RunnerException re = new RunnerException("Error compiling.");
re.hideStackTrace();
throw re;
}
}
boolean firstErrorFound;
boolean secondErrorFound;
/**
* Part of the MessageConsumer interface, this is called
* whenever a piece (usually a line) of error message is spewed
* out from the compiler. The errors are parsed for their contents
* and line number, which is then reported back to Editor.
*/
public void message(String s) {
int i;
// remove the build path so people only see the filename
// can't use replaceAll() because the path may have characters in it which
// have meaning in a regular expression.
if (!verbose) {
while ((i = s.indexOf(buildPath + File.separator)) != -1) {
s = s.substring(0, i) + s.substring(i + (buildPath + File.separator).length());
}
}
// look for error line, which contains file name, line number,
// and at least the first line of the error message
String errorFormat = "([\\w\\d_]+.\\w+):(\\d+):\\s*\\s*(.*)\\s*";
String[] pieces = PApplet.match(s, errorFormat);
if (pieces != null) {
RunnerException e = sketch.placeException(pieces[3], pieces[1], PApplet.parseInt(pieces[2]) - 1);
// replace full file path with the name of the sketch tab (unless we're
// in verbose mode, in which case don't modify the compiler output)
if (e != null && !verbose) {
SketchCode code = sketch.getCode(e.getCodeIndex());
String fileName = code.isExtension(sketch.getDefaultExtension()) ? code.getPrettyName() : code.getFileName();
s = fileName + ":" + e.getCodeLine() + ": error: " + e.getMessage();
}
if (exception == null && e != null) {
exception = e;
exception.hideStackTrace();
}
}
System.err.print(s);
}
static private List getCommandCompilerS(String avrBasePath, List includePaths,
String sourceName, String objectName, Map<String, String> boardPreferences) {
List baseCommandCompiler = new ArrayList(Arrays.asList(new String[] {
avrBasePath + "avr-gcc",
"-c", // compile, don't link
"-g", // include debugging info (so errors include line numbers)
"-assembler-with-cpp",
"-mmcu=" + boardPreferences.get("build.mcu"),
"-DF_CPU=" + boardPreferences.get("build.f_cpu"),
"-DWIRING=" +Base.REVISION,
}));
for (int i = 0; i < includePaths.size(); i++) {
baseCommandCompiler.add("-I" + (String) includePaths.get(i));
}
baseCommandCompiler.add(sourceName);
baseCommandCompiler.add("-o"+ objectName);
return baseCommandCompiler;
}
static private List getCommandCompilerC(String avrBasePath, List includePaths,
String sourceName, String objectName, Map<String, String> boardPreferences) {
List baseCommandCompiler = new ArrayList(Arrays.asList(new String[] {
avrBasePath + "avr-gcc",
"-c", // compile, don't link
"-g", // include debugging info (so errors include line numbers)
"-Os", // optimize for size
"-w", // surpress all warnings
"-ffunction-sections", // place each function in its own section
"-fdata-sections",
"-mmcu=" + boardPreferences.get("build.mcu"),
"-DF_CPU=" + boardPreferences.get("build.f_cpu"),
"-DWIRING=" + Base.REVISION,
}));
for (int i = 0; i < includePaths.size(); i++) {
baseCommandCompiler.add("-I" + (String) includePaths.get(i));
}
baseCommandCompiler.add(sourceName);
baseCommandCompiler.add("-o"+ objectName);
return baseCommandCompiler;
}
static private List getCommandCompilerCPP(String avrBasePath,
List includePaths, String sourceName, String objectName,
Map<String, String> boardPreferences) {
List baseCommandCompilerCPP = new ArrayList(Arrays.asList(new String[] {
avrBasePath + "avr-g++",
"-c", // compile, don't link
"-g", // include debugging info (so errors include line numbers)
"-Os", // optimize for size
"-w", // surpress all warnings
"-fno-exceptions",
"-ffunction-sections", // place each function in its own section
"-fdata-sections",
"-mmcu=" + boardPreferences.get("build.mcu"),
"-DF_CPU=" + boardPreferences.get("build.f_cpu"),
"-DWIRING=" + Base.REVISION,
}));
for (int i = 0; i < includePaths.size(); i++) {
baseCommandCompilerCPP.add("-I" + (String) includePaths.get(i));
}
baseCommandCompilerCPP.add(sourceName);
baseCommandCompilerCPP.add("-o"+ objectName);
return baseCommandCompilerCPP;
}
static private void createFolder(File folder) throws RunnerException {
if (folder.isDirectory()) return;
if (!folder.mkdir())
throw new RunnerException("Couldn't create: " + folder);
}
/**
* Given a folder, return a list of the header files in that folder (but
* not the header files in its sub-folders, as those should be included from
* within the header files at the top-level).
*/
static public String[] headerListFromIncludePath(String path) {
FilenameFilter onlyHFiles = new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.endsWith(".h");
}
};
return (new File(path)).list(onlyHFiles);
}
static public ArrayList<File> findFilesInPath(String path, String extension,
boolean recurse) {
return findFilesInFolder(new File(path), extension, recurse);
}
static public ArrayList<File> findFilesInFolder(File folder, String extension,
boolean recurse) {
ArrayList<File> files = new ArrayList<File>();
if (folder.listFiles() == null) return files;
for (File file : folder.listFiles()) {
if (file.getName().equals(".") || file.getName().equals("..")) continue;
if (file.getName().endsWith("." + extension))
files.add(file);
if (recurse && file.isDirectory()) {
files.addAll(findFilesInFolder(file, extension, true));
}
}
return files;
}
///
/**
* Given a folder, return a list of absolute paths to all jar or zip files
* inside that folder, separated by pathSeparatorChar.
*
* This will prepend a colon (or whatever the path separator is)
* so that it can be directly appended to another path string.
*
* As of 0136, this will no longer add the root folder as well.
*
* This function doesn't bother checking to see if there are any .class
* files in the folder or within a subfolder.
*/
static public String contentsToClassPath(File folder) {
if (folder == null) return "";
StringBuffer abuffer = new StringBuffer();
String sep = System.getProperty("path.separator");
try {
String path = folder.getCanonicalPath();
// When getting the name of this folder, make sure it has a slash
// after it, so that the names of sub-items can be added.
if (!path.endsWith(File.separator)) {
path += File.separator;
}
String list[] = folder.list();
for (int i = 0; i < list.length; i++) {
// Skip . and ._ files. Prior to 0125p3, .jar files that had
// OS X AppleDouble files associated would cause trouble.
if (list[i].startsWith(".")) continue;
if (list[i].toLowerCase().endsWith(".jar") ||
list[i].toLowerCase().endsWith(".zip")) {
abuffer.append(sep);
abuffer.append(path);
abuffer.append(list[i]);
}
}
} catch (IOException e) {
e.printStackTrace(); // this would be odd
}
return abuffer.toString();
}
/**
* A classpath, separated by the path separator, will contain
* a series of .jar/.zip files or directories containing .class
* files, or containing subdirectories that have .class files.
*
* @param path the input classpath
* @return array of possible package names
*/
static public String[] packageListFromClassPath(String path) {
Hashtable table = new Hashtable();
String pieces[] =
PApplet.split(path, File.pathSeparatorChar);
for (int i = 0; i < pieces.length; i++) {
if (pieces[i].length() == 0) continue;
if (pieces[i].toLowerCase().endsWith(".jar") ||
pieces[i].toLowerCase().endsWith(".zip")) {
packageListFromZip(pieces[i], table);
} else { // it's another type of file or directory
File dir = new File(pieces[i]);
if (dir.exists() && dir.isDirectory()) {
packageListFromFolder(dir, null, table);
}
}
}
int tableCount = table.size();
String output[] = new String[tableCount];
int index = 0;
Enumeration e = table.keys();
while (e.hasMoreElements()) {
output[index++] = ((String) e.nextElement()).replace('/', '.');
}
return output;
}
static private void packageListFromZip(String filename, Hashtable table) {
try {
ZipFile file = new ZipFile(filename);
Enumeration entries = file.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = (ZipEntry) entries.nextElement();
if (!entry.isDirectory()) {
String name = entry.getName();
if (name.endsWith(".class")) {
int slash = name.lastIndexOf('/');
if (slash == -1) continue;
String pname = name.substring(0, slash);
if (table.get(pname) == null) {
table.put(pname, new Object());
}
}
}
}
} catch (IOException e) {
System.err.println("Ignoring " + filename + " (" + e.getMessage() + ")");
}
}
/**
* Make list of package names by traversing a directory hierarchy.
* Each time a class is found in a folder, add its containing set
* of folders to the package list. If another folder is found,
* walk down into that folder and continue.
*/
static private void packageListFromFolder(File dir, String sofar,
Hashtable table) {
boolean foundClass = false;
String files[] = dir.list();
for (int i = 0; i < files.length; i++) {
if (files[i].equals(".") || files[i].equals("..")) continue;
File sub = new File(dir, files[i]);
if (sub.isDirectory()) {
String nowfar =
(sofar == null) ? files[i] : (sofar + "." + files[i]);
packageListFromFolder(sub, nowfar, table);
} else if (!foundClass) { // if no classes found in this folder yet
if (files[i].endsWith(".class")) {
table.put(sofar, new Object());
foundClass = true;
}
}
}
}
}