mirror of
https://github.com/esp8266/Arduino.git
synced 2025-06-16 11:21:18 +03:00
544 lines
19 KiB
Java
544 lines
19 KiB
Java
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
|
|
|
/*
|
|
Part of the Processing project - http://processing.org
|
|
|
|
Copyright (c) 2004-08 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.Base;
|
|
import processing.app.Preferences;
|
|
import processing.app.Sketch;
|
|
import processing.app.SketchCode;
|
|
import processing.core.*;
|
|
|
|
import java.io.*;
|
|
import java.util.*;
|
|
import java.util.zip.*;
|
|
|
|
|
|
public class Compiler implements MessageConsumer {
|
|
static final String BUGS_URL =
|
|
"http://code.google.com/p/arduino/issues/list";
|
|
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() { }
|
|
|
|
/**
|
|
* Compile with avr-gcc.
|
|
*
|
|
* @param sketch Sketch object to be compiled.
|
|
* @param buildPath Where the temporary files live and will be built from.
|
|
* @param primaryClassName the name of the combined sketch file w/ extension
|
|
* @return true if successful.
|
|
* @throws RunnerException Only if there's a problem. Only then.
|
|
*/
|
|
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 core = boardPreferences.get("build.core");
|
|
if (core == null) {
|
|
RunnerException re = new RunnerException("No board selected; please choose a board from the Tools > Board menu.");
|
|
re.hideStackTrace();
|
|
throw re;
|
|
}
|
|
String corePath;
|
|
|
|
if (core.indexOf(':') == -1) {
|
|
Target t = Base.getTarget();
|
|
File coreFolder = new File(new File(t.getFolder(), "cores"), core);
|
|
corePath = coreFolder.getAbsolutePath();
|
|
} else {
|
|
Target t = Base.targetsTable.get(core.substring(0, core.indexOf(':')));
|
|
File coreFolder = new File(t.getFolder(), "cores");
|
|
coreFolder = new File(coreFolder, core.substring(core.indexOf(':') + 1));
|
|
corePath = coreFolder.getAbsolutePath();
|
|
}
|
|
|
|
String variant = boardPreferences.get("build.variant");
|
|
String variantPath = null;
|
|
|
|
if (variant != null) {
|
|
if (variant.indexOf(':') == -1) {
|
|
Target t = Base.getTarget();
|
|
File variantFolder = new File(new File(t.getFolder(), "variants"), variant);
|
|
variantPath = variantFolder.getAbsolutePath();
|
|
} else {
|
|
Target t = Base.targetsTable.get(variant.substring(0, variant.indexOf(':')));
|
|
File variantFolder = new File(t.getFolder(), "variants");
|
|
variantFolder = new File(variantFolder, variant.substring(variant.indexOf(':') + 1));
|
|
variantPath = variantFolder.getAbsolutePath();
|
|
}
|
|
}
|
|
|
|
List<File> objectFiles = new ArrayList<File>();
|
|
|
|
// 0. include paths for core + all libraries
|
|
|
|
sketch.setCompilingProgress(20);
|
|
List includePaths = new ArrayList();
|
|
includePaths.add(corePath);
|
|
if (variantPath != null) includePaths.add(variantPath);
|
|
for (File file : sketch.getImportedLibraries()) {
|
|
includePaths.add(file.getPath());
|
|
}
|
|
|
|
// 1. compile the sketch (already in the buildPath)
|
|
|
|
sketch.setCompilingProgress(30);
|
|
objectFiles.addAll(
|
|
compileFiles(avrBasePath, buildPath, includePaths,
|
|
findFilesInPath(buildPath, "S", false),
|
|
findFilesInPath(buildPath, "c", false),
|
|
findFilesInPath(buildPath, "cpp", false),
|
|
boardPreferences));
|
|
|
|
// 2. compile the libraries, outputting .o files to: <buildPath>/<library>/
|
|
|
|
sketch.setCompilingProgress(40);
|
|
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());
|
|
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);
|
|
}
|
|
|
|
// 3. compile the core, outputting .o files to <buildPath> and then
|
|
// collecting them into the core.a library file.
|
|
|
|
sketch.setCompilingProgress(50);
|
|
includePaths.clear();
|
|
includePaths.add(corePath); // include path for core only
|
|
if (variantPath != null) includePaths.add(variantPath);
|
|
List<File> coreObjectFiles =
|
|
compileFiles(avrBasePath, buildPath, includePaths,
|
|
findFilesInPath(corePath, "S", true),
|
|
findFilesInPath(corePath, "c", true),
|
|
findFilesInPath(corePath, "cpp", true),
|
|
boardPreferences);
|
|
|
|
String runtimeLibraryName = buildPath + File.separator + "core.a";
|
|
List baseCommandAR = new ArrayList(Arrays.asList(new String[] {
|
|
avrBasePath + "avr-ar",
|
|
"rcs",
|
|
runtimeLibraryName
|
|
}));
|
|
for(File file : coreObjectFiles) {
|
|
List commandAR = new ArrayList(baseCommandAR);
|
|
commandAR.add(file.getAbsolutePath());
|
|
execAsynchronously(commandAR);
|
|
}
|
|
|
|
// 4. link it all together into the .elf file
|
|
|
|
sketch.setCompilingProgress(60);
|
|
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);
|
|
|
|
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.
|
|
sketch.setCompilingProgress(70);
|
|
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
|
|
sketch.setCompilingProgress(80);
|
|
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;
|
|
}
|
|
|
|
|
|
boolean firstErrorFound;
|
|
boolean secondErrorFound;
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* 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*error:\\s*(.*)\\s*";
|
|
String[] pieces = PApplet.match(s, errorFormat);
|
|
|
|
// if (pieces != null && exception == null) {
|
|
// exception = sketch.placeException(pieces[3], pieces[1], PApplet.parseInt(pieces[2]) - 1);
|
|
// if (exception != null) exception.hideStackTrace();
|
|
// }
|
|
|
|
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 (pieces[3].trim().equals("SPI.h: No such file or directory")) {
|
|
e = new RunnerException("Please import the SPI library from the Sketch > Import Library menu.");
|
|
s += "\nAs of Arduino 0019, the Ethernet library depends on the SPI library." +
|
|
"\nYou appear to be using it or another library that depends on the SPI library.";
|
|
}
|
|
|
|
if (pieces[3].trim().equals("'BYTE' was not declared in this scope")) {
|
|
e = new RunnerException("The 'BYTE' keyword is no longer supported.");
|
|
s += "\nAs of Arduino 1.0, the 'BYTE' keyword is no longer supported." +
|
|
"\nPlease use Serial.write() instead.";
|
|
}
|
|
|
|
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"),
|
|
"-DARDUINO=" + 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"),
|
|
"-DARDUINO=" + 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"),
|
|
"-DARDUINO=" + 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().startsWith(".")) continue; // skip hidden files
|
|
|
|
if (file.getName().endsWith("." + extension))
|
|
files.add(file);
|
|
|
|
if (recurse && file.isDirectory()) {
|
|
files.addAll(findFilesInFolder(file, extension, true));
|
|
}
|
|
}
|
|
|
|
return files;
|
|
}
|
|
}
|