mirror of
https://github.com/esp8266/Arduino.git
synced 2025-06-16 11:21:18 +03:00
857 lines
30 KiB
Java
857 lines
30 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 static processing.app.I18n._;
|
|
|
|
import java.io.BufferedReader;
|
|
import java.io.File;
|
|
import java.io.FileReader;
|
|
import java.io.IOException;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
|
|
import processing.app.Base;
|
|
import processing.app.I18n;
|
|
import processing.app.Preferences;
|
|
import processing.app.Sketch;
|
|
import processing.app.SketchCode;
|
|
import processing.app.helpers.FileUtils;
|
|
import processing.app.helpers.PreferencesMap;
|
|
import processing.app.helpers.ProcessUtils;
|
|
import processing.app.helpers.StringReplacer;
|
|
import processing.app.helpers.filefilters.OnlyDirs;
|
|
import processing.app.packages.Library;
|
|
import processing.core.PApplet;
|
|
|
|
public class Compiler implements MessageConsumer {
|
|
|
|
private Sketch sketch;
|
|
|
|
private List<File> objectFiles;
|
|
|
|
private PreferencesMap prefs;
|
|
private boolean verbose;
|
|
private boolean sketchIsCompiled;
|
|
private String targetArch;
|
|
|
|
private RunnerException exception;
|
|
|
|
/**
|
|
* Create a new Compiler
|
|
* @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
|
|
*/
|
|
public Compiler(Sketch _sketch, String _buildPath, String _primaryClassName)
|
|
throws RunnerException {
|
|
sketch = _sketch;
|
|
prefs = createBuildPreferences(_buildPath, _primaryClassName);
|
|
}
|
|
|
|
/**
|
|
* Compile sketch.
|
|
*
|
|
* @return true if successful.
|
|
* @throws RunnerException Only if there's a problem. Only then.
|
|
*/
|
|
public boolean compile(boolean _verbose) throws RunnerException {
|
|
verbose = _verbose || Preferences.getBoolean("build.verbose");
|
|
sketchIsCompiled = false;
|
|
objectFiles = new ArrayList<File>();
|
|
|
|
// 0. include paths for core + all libraries
|
|
sketch.setCompilingProgress(20);
|
|
List<File> includeFolders = new ArrayList<File>();
|
|
includeFolders.add(prefs.getFile("build.core.path"));
|
|
if (prefs.getFile("build.variant.path") != null)
|
|
includeFolders.add(prefs.getFile("build.variant.path"));
|
|
for (Library lib : sketch.getImportedLibraries()) {
|
|
if (verbose)
|
|
System.out.println(I18n
|
|
.format(_("Using library {0} in folder: {1} {2}"), lib.getName(),
|
|
lib.getFolder(), lib.isLegacy() ? "(legacy)" : ""));
|
|
includeFolders.add(lib.getSrcFolder());
|
|
}
|
|
if (verbose)
|
|
System.out.println();
|
|
|
|
List<String> archs = new ArrayList<String>();
|
|
archs.add(Base.getTargetPlatform().getId());
|
|
if (prefs.containsKey("architecture.override_check")) {
|
|
String[] overrides = prefs.get("architecture.override_check").split(",");
|
|
archs.addAll(Arrays.asList(overrides));
|
|
}
|
|
for (Library lib : sketch.getImportedLibraries()) {
|
|
if (!lib.supportsArchitecture(archs)) {
|
|
System.err.println(I18n
|
|
.format(_("WARNING: library {0} claims to run on {1} "
|
|
+ "architecture(s) and may be incompatible with your"
|
|
+ " current board which runs on {2} architecture(s)."), lib
|
|
.getName(), lib.getArchitectures(), archs));
|
|
System.err.println();
|
|
}
|
|
}
|
|
|
|
// 1. compile the sketch (already in the buildPath)
|
|
sketch.setCompilingProgress(30);
|
|
compileSketch(includeFolders);
|
|
sketchIsCompiled = true;
|
|
|
|
// 2. compile the libraries, outputting .o files to: <buildPath>/<library>/
|
|
// Doesn't really use configPreferences
|
|
sketch.setCompilingProgress(40);
|
|
compileLibraries(includeFolders);
|
|
|
|
// 3. compile the core, outputting .o files to <buildPath> and then
|
|
// collecting them into the core.a library file.
|
|
sketch.setCompilingProgress(50);
|
|
compileCore();
|
|
|
|
// 4. link it all together into the .elf file
|
|
sketch.setCompilingProgress(60);
|
|
compileLink();
|
|
|
|
// 5. extract EEPROM data (from EEMEM directive) to .eep file.
|
|
sketch.setCompilingProgress(70);
|
|
compileEep();
|
|
|
|
// 6. build the .hex file
|
|
sketch.setCompilingProgress(80);
|
|
compileHex();
|
|
|
|
sketch.setCompilingProgress(90);
|
|
return true;
|
|
}
|
|
|
|
private PreferencesMap createBuildPreferences(String _buildPath,
|
|
String _primaryClassName)
|
|
throws RunnerException {
|
|
|
|
if (Base.getBoardPreferences() == null) {
|
|
RunnerException re = new RunnerException(
|
|
_("No board selected; please choose a board from the Tools > Board menu."));
|
|
re.hideStackTrace();
|
|
throw re;
|
|
}
|
|
|
|
// Check if the board needs a platform from another package
|
|
TargetPlatform targetPlatform = Base.getTargetPlatform();
|
|
TargetPlatform corePlatform = null;
|
|
PreferencesMap boardPreferences = Base.getBoardPreferences();
|
|
String core = boardPreferences.get("build.core");
|
|
if (core.contains(":")) {
|
|
String[] split = core.split(":");
|
|
core = split[1];
|
|
corePlatform = Base.getTargetPlatform(split[0], targetPlatform.getId());
|
|
if (corePlatform == null) {
|
|
RunnerException re = new RunnerException(I18n
|
|
.format(_("Selected board depends on '{0}' core (not installed)."),
|
|
split[0]));
|
|
re.hideStackTrace();
|
|
throw re;
|
|
}
|
|
}
|
|
|
|
// Merge all the global preference configuration in order of priority
|
|
PreferencesMap p = new PreferencesMap();
|
|
p.putAll(Preferences.getMap());
|
|
if (corePlatform != null)
|
|
p.putAll(corePlatform.getPreferences());
|
|
p.putAll(targetPlatform.getPreferences());
|
|
p.putAll(Base.getBoardPreferences());
|
|
for (String k : p.keySet()) {
|
|
if (p.get(k) == null)
|
|
p.put(k, "");
|
|
}
|
|
|
|
p.put("build.path", _buildPath);
|
|
p.put("build.project_name", _primaryClassName);
|
|
targetArch = targetPlatform.getId();
|
|
p.put("build.arch", targetArch.toUpperCase());
|
|
|
|
// Platform.txt should define its own compiler.path. For
|
|
// compatibility with earlier 1.5 versions, we define a (ugly,
|
|
// avr-specific) default for it, but this should be removed at some
|
|
// point.
|
|
if (!p.containsKey("compiler.path")) {
|
|
System.err.println(_("Third-party platform.txt does not define compiler.path. Please report this to the third-party hardware maintainer."));
|
|
p.put("compiler.path", Base.getAvrBasePath());
|
|
}
|
|
|
|
// Core folder
|
|
TargetPlatform tp = corePlatform;
|
|
if (tp == null)
|
|
tp = targetPlatform;
|
|
File coreFolder = new File(tp.getFolder(), "cores");
|
|
coreFolder = new File(coreFolder, core);
|
|
p.put("build.core", core);
|
|
p.put("build.core.path", coreFolder.getAbsolutePath());
|
|
|
|
// System Folder
|
|
File systemFolder = tp.getFolder();
|
|
systemFolder = new File(systemFolder, "system");
|
|
p.put("build.system.path", systemFolder.getAbsolutePath());
|
|
|
|
// Variant Folder
|
|
String variant = p.get("build.variant");
|
|
if (variant != null) {
|
|
TargetPlatform t;
|
|
if (!variant.contains(":")) {
|
|
t = targetPlatform;
|
|
} else {
|
|
String[] split = variant.split(":", 2);
|
|
t = Base.getTargetPlatform(split[0], targetPlatform.getId());
|
|
variant = split[1];
|
|
}
|
|
File variantFolder = new File(t.getFolder(), "variants");
|
|
variantFolder = new File(variantFolder, variant);
|
|
p.put("build.variant.path", variantFolder.getAbsolutePath());
|
|
} else {
|
|
p.put("build.variant.path", "");
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
private List<File> compileFiles(File outputPath, File sourcePath,
|
|
boolean recurse, List<File> includeFolders)
|
|
throws RunnerException {
|
|
List<File> sSources = findFilesInFolder(sourcePath, "S", recurse);
|
|
List<File> cSources = findFilesInFolder(sourcePath, "c", recurse);
|
|
List<File> cppSources = findFilesInFolder(sourcePath, "cpp", recurse);
|
|
List<File> objectPaths = new ArrayList<File>();
|
|
|
|
for (File file : sSources) {
|
|
File objectFile = new File(outputPath, file.getName() + ".o");
|
|
objectPaths.add(objectFile);
|
|
String[] cmd = getCommandCompilerS(includeFolders, file, objectFile);
|
|
execAsynchronously(cmd);
|
|
}
|
|
|
|
for (File file : cSources) {
|
|
File objectFile = new File(outputPath, file.getName() + ".o");
|
|
File dependFile = new File(outputPath, file.getName() + ".d");
|
|
objectPaths.add(objectFile);
|
|
if (isAlreadyCompiled(file, objectFile, dependFile, prefs))
|
|
continue;
|
|
String[] cmd = getCommandCompilerC(includeFolders, file, objectFile);
|
|
execAsynchronously(cmd);
|
|
}
|
|
|
|
for (File file : cppSources) {
|
|
File objectFile = new File(outputPath, file.getName() + ".o");
|
|
File dependFile = new File(outputPath, file.getName() + ".d");
|
|
objectPaths.add(objectFile);
|
|
if (isAlreadyCompiled(file, objectFile, dependFile, prefs))
|
|
continue;
|
|
String[] cmd = getCommandCompilerCPP(includeFolders, file, objectFile);
|
|
execAsynchronously(cmd);
|
|
}
|
|
|
|
return objectPaths;
|
|
}
|
|
|
|
/**
|
|
* Strip escape sequences used in makefile dependency files (.d)
|
|
* https://github.com/arduino/Arduino/issues/2255#issuecomment-57645845
|
|
*
|
|
* @param dep
|
|
* @return
|
|
*/
|
|
protected static String unescapeDepFile(String line) {
|
|
// Replaces: "\\" -> "\"
|
|
// Replaces: "\ " -> " "
|
|
// Replaces: "\#" -> "#"
|
|
line = line.replaceAll("\\\\([ #\\\\])", "$1");
|
|
// Replaces: "$$" -> "$"
|
|
line = line.replace("$$", "$");
|
|
return line;
|
|
}
|
|
|
|
private boolean isAlreadyCompiled(File src, File obj, File dep, Map<String, String> prefs) {
|
|
boolean ret=true;
|
|
try {
|
|
//System.out.println("\n isAlreadyCompiled: begin checks: " + obj.getPath());
|
|
if (!obj.exists()) return false; // object file (.o) does not exist
|
|
if (!dep.exists()) return false; // dep file (.d) does not exist
|
|
long src_modified = src.lastModified();
|
|
long obj_modified = obj.lastModified();
|
|
if (src_modified >= obj_modified) return false; // source modified since object compiled
|
|
if (src_modified >= dep.lastModified()) return false; // src modified since dep compiled
|
|
BufferedReader reader = new BufferedReader(new FileReader(dep.getPath()));
|
|
String line;
|
|
boolean need_obj_parse = true;
|
|
while ((line = reader.readLine()) != null) {
|
|
if (line.endsWith("\\")) {
|
|
line = line.substring(0, line.length() - 1);
|
|
}
|
|
line = line.trim();
|
|
line = unescapeDepFile(line);
|
|
if (line.length() == 0) continue; // ignore blank lines
|
|
if (need_obj_parse) {
|
|
// line is supposed to be the object file - make sure it really is!
|
|
if (line.endsWith(":")) {
|
|
line = line.substring(0, line.length() - 1);
|
|
String objpath = obj.getCanonicalPath();
|
|
File linefile = new File(line);
|
|
String linepath = linefile.getCanonicalPath();
|
|
//System.out.println(" isAlreadyCompiled: obj = " + objpath);
|
|
//System.out.println(" isAlreadyCompiled: line = " + linepath);
|
|
if (objpath.compareTo(linepath) == 0) {
|
|
need_obj_parse = false;
|
|
continue;
|
|
} else {
|
|
ret = false; // object named inside .d file is not the correct file!
|
|
break;
|
|
}
|
|
} else {
|
|
ret = false; // object file supposed to end with ':', but didn't
|
|
break;
|
|
}
|
|
} else {
|
|
// line is a prerequisite file
|
|
File prereq = new File(line);
|
|
if (!prereq.exists()) {
|
|
ret = false; // prerequisite file did not exist
|
|
break;
|
|
}
|
|
if (prereq.lastModified() >= obj_modified) {
|
|
ret = false; // prerequisite modified since object was compiled
|
|
break;
|
|
}
|
|
//System.out.println(" isAlreadyCompiled: prerequisite ok");
|
|
}
|
|
}
|
|
reader.close();
|
|
} catch (Exception e) {
|
|
return false; // any error reading dep file = recompile it
|
|
}
|
|
if (ret && verbose) {
|
|
System.out.println(I18n.format(_("Using previously compiled file: {0}"), obj.getPath()));
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
boolean firstErrorFound;
|
|
boolean secondErrorFound;
|
|
|
|
/**
|
|
* Either succeeds or throws a RunnerException fit for public consumption.
|
|
*/
|
|
private void execAsynchronously(String[] command) throws RunnerException {
|
|
|
|
// eliminate any empty array entries
|
|
List<String> stringList = new ArrayList<String>();
|
|
for (String string : command) {
|
|
string = string.trim();
|
|
if (string.length() != 0)
|
|
stringList.add(string);
|
|
}
|
|
command = stringList.toArray(new String[stringList.size()]);
|
|
if (command.length == 0)
|
|
return;
|
|
int result = 0;
|
|
|
|
if (verbose) {
|
|
for (String c : command)
|
|
System.out.print(c + " ");
|
|
System.out.println();
|
|
}
|
|
|
|
firstErrorFound = false; // haven't found any errors yet
|
|
secondErrorFound = false;
|
|
|
|
Process process;
|
|
try {
|
|
process = ProcessUtils.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 {
|
|
in.join();
|
|
err.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(I18n.format(_("{0} returned {1}"), command[0], 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) {
|
|
String buildPath = prefs.get("build.path");
|
|
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) {
|
|
String error = pieces[3], msg = "";
|
|
|
|
if (pieces[3].trim().equals("SPI.h: No such file or directory")) {
|
|
error = _("Please import the SPI library from the Sketch > Import Library menu.");
|
|
msg = _("\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.\n\n");
|
|
}
|
|
|
|
if (pieces[3].trim().equals("'BYTE' was not declared in this scope")) {
|
|
error = _("The 'BYTE' keyword is no longer supported.");
|
|
msg = _("\nAs of Arduino 1.0, the 'BYTE' keyword is no longer supported." +
|
|
"\nPlease use Serial.write() instead.\n\n");
|
|
}
|
|
|
|
if (pieces[3].trim().equals("no matching function for call to 'Server::Server(int)'")) {
|
|
error = _("The Server class has been renamed EthernetServer.");
|
|
msg = _("\nAs of Arduino 1.0, the Server class in the Ethernet library " +
|
|
"has been renamed to EthernetServer.\n\n");
|
|
}
|
|
|
|
if (pieces[3].trim().equals("no matching function for call to 'Client::Client(byte [4], int)'")) {
|
|
error = _("The Client class has been renamed EthernetClient.");
|
|
msg = _("\nAs of Arduino 1.0, the Client class in the Ethernet library " +
|
|
"has been renamed to EthernetClient.\n\n");
|
|
}
|
|
|
|
if (pieces[3].trim().equals("'Udp' was not declared in this scope")) {
|
|
error = _("The Udp class has been renamed EthernetUdp.");
|
|
msg = _("\nAs of Arduino 1.0, the Udp class in the Ethernet library " +
|
|
"has been renamed to EthernetUdp.\n\n");
|
|
}
|
|
|
|
if (pieces[3].trim().equals("'class TwoWire' has no member named 'send'")) {
|
|
error = _("Wire.send() has been renamed Wire.write().");
|
|
msg = _("\nAs of Arduino 1.0, the Wire.send() function was renamed " +
|
|
"to Wire.write() for consistency with other libraries.\n\n");
|
|
}
|
|
|
|
if (pieces[3].trim().equals("'class TwoWire' has no member named 'receive'")) {
|
|
error = _("Wire.receive() has been renamed Wire.read().");
|
|
msg = _("\nAs of Arduino 1.0, the Wire.receive() function was renamed " +
|
|
"to Wire.read() for consistency with other libraries.\n\n");
|
|
}
|
|
|
|
if (pieces[3].trim().equals("'Mouse' was not declared in this scope")) {
|
|
error = _("'Mouse' only supported on the Arduino Leonardo");
|
|
//msg = _("\nThe 'Mouse' class is only supported on the Arduino Leonardo.\n\n");
|
|
}
|
|
|
|
if (pieces[3].trim().equals("'Keyboard' was not declared in this scope")) {
|
|
error = _("'Keyboard' only supported on the Arduino Leonardo");
|
|
//msg = _("\nThe 'Keyboard' class is only supported on the Arduino Leonardo.\n\n");
|
|
}
|
|
|
|
RunnerException e = null;
|
|
if (!sketchIsCompiled) {
|
|
// Place errors when compiling the sketch, but never while compiling libraries
|
|
// or the core. The user's sketch might contain the same filename!
|
|
e = sketch.placeException(error, 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("ino") || code.isExtension("pde")) ? code.getPrettyName() : code.getFileName();
|
|
int lineNum = e.getCodeLine() + 1;
|
|
s = fileName + ":" + lineNum + ": error: " + pieces[3] + msg;
|
|
}
|
|
|
|
if (exception == null && e != null) {
|
|
exception = e;
|
|
exception.hideStackTrace();
|
|
}
|
|
}
|
|
|
|
if (s.contains("undefined reference to `SPIClass::begin()'") &&
|
|
s.contains("libraries/Robot_Control")) {
|
|
String error = _("Please import the SPI library from the Sketch > Import Library menu.");
|
|
exception = new RunnerException(error);
|
|
}
|
|
|
|
if (s.contains("undefined reference to `Wire'") &&
|
|
s.contains("libraries/Robot_Control")) {
|
|
String error = _("Please import the Wire library from the Sketch > Import Library menu.");
|
|
exception = new RunnerException(error);
|
|
}
|
|
|
|
System.err.print(s);
|
|
}
|
|
|
|
private String[] getCommandCompilerS(List<File> includeFolders,
|
|
File sourceFile, File objectFile)
|
|
throws RunnerException {
|
|
String includes = prepareIncludes(includeFolders);
|
|
PreferencesMap dict = new PreferencesMap(prefs);
|
|
dict.put("ide_version", "" + Base.REVISION);
|
|
dict.put("includes", includes);
|
|
dict.put("source_file", sourceFile.getAbsolutePath());
|
|
dict.put("object_file", objectFile.getAbsolutePath());
|
|
|
|
try {
|
|
String cmd = prefs.get("recipe.S.o.pattern");
|
|
return StringReplacer.formatAndSplit(cmd, dict, true);
|
|
} catch (Exception e) {
|
|
throw new RunnerException(e);
|
|
}
|
|
}
|
|
|
|
private String[] getCommandCompilerC(List<File> includeFolders,
|
|
File sourceFile, File objectFile)
|
|
throws RunnerException {
|
|
String includes = prepareIncludes(includeFolders);
|
|
|
|
PreferencesMap dict = new PreferencesMap(prefs);
|
|
dict.put("ide_version", "" + Base.REVISION);
|
|
dict.put("includes", includes);
|
|
dict.put("source_file", sourceFile.getAbsolutePath());
|
|
dict.put("object_file", objectFile.getAbsolutePath());
|
|
|
|
String cmd = prefs.get("recipe.c.o.pattern");
|
|
try {
|
|
return StringReplacer.formatAndSplit(cmd, dict, true);
|
|
} catch (Exception e) {
|
|
throw new RunnerException(e);
|
|
}
|
|
}
|
|
|
|
private String[] getCommandCompilerCPP(List<File> includeFolders,
|
|
File sourceFile, File objectFile)
|
|
throws RunnerException {
|
|
String includes = prepareIncludes(includeFolders);
|
|
|
|
PreferencesMap dict = new PreferencesMap(prefs);
|
|
dict.put("ide_version", "" + Base.REVISION);
|
|
dict.put("includes", includes);
|
|
dict.put("source_file", sourceFile.getAbsolutePath());
|
|
dict.put("object_file", objectFile.getAbsolutePath());
|
|
|
|
String cmd = prefs.get("recipe.cpp.o.pattern");
|
|
try {
|
|
return StringReplacer.formatAndSplit(cmd, dict, true);
|
|
} catch (Exception e) {
|
|
throw new RunnerException(e);
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
|
|
private void createFolder(File folder) throws RunnerException {
|
|
if (folder.isDirectory())
|
|
return;
|
|
if (!folder.mkdir())
|
|
throw new RunnerException("Couldn't create: " + folder);
|
|
}
|
|
|
|
static public List<File> findFilesInFolder(File folder, String extension,
|
|
boolean recurse) {
|
|
List<File> files = new ArrayList<File>();
|
|
|
|
if (FileUtils.isSCCSOrHiddenFile(folder)) {
|
|
return files;
|
|
}
|
|
|
|
File[] listFiles = folder.listFiles();
|
|
if (listFiles == null) {
|
|
return files;
|
|
}
|
|
|
|
for (File file : listFiles) {
|
|
if (FileUtils.isSCCSOrHiddenFile(file)) {
|
|
continue; // skip hidden files
|
|
}
|
|
|
|
if (file.getName().endsWith("." + extension))
|
|
files.add(file);
|
|
|
|
if (recurse && file.isDirectory()) {
|
|
files.addAll(findFilesInFolder(file, extension, true));
|
|
}
|
|
}
|
|
|
|
return files;
|
|
}
|
|
|
|
// 1. compile the sketch (already in the buildPath)
|
|
void compileSketch(List<File> includeFolders) throws RunnerException {
|
|
File buildPath = prefs.getFile("build.path");
|
|
objectFiles.addAll(compileFiles(buildPath, buildPath, false, includeFolders));
|
|
}
|
|
|
|
// 2. compile the libraries, outputting .o files to:
|
|
// <buildPath>/<library>/
|
|
void compileLibraries(List<File> includeFolders) throws RunnerException {
|
|
for (Library lib : sketch.getImportedLibraries()) {
|
|
compileLibrary(lib, includeFolders);
|
|
}
|
|
}
|
|
|
|
private void compileLibrary(Library lib, List<File> includeFolders)
|
|
throws RunnerException {
|
|
File libFolder = lib.getSrcFolder();
|
|
File libBuildFolder = prefs.getFile(("build.path"), lib.getName());
|
|
|
|
if (lib.useRecursion()) {
|
|
// libBuildFolder == {build.path}/LibName
|
|
// libFolder == {lib.path}/src
|
|
recursiveCompileFilesInFolder(libBuildFolder, libFolder, includeFolders);
|
|
|
|
} else {
|
|
// libFolder == {lib.path}/
|
|
// utilityFolder == {lib.path}/utility
|
|
// libBuildFolder == {build.path}/LibName
|
|
// utilityBuildFolder == {build.path}/LibName/utility
|
|
File utilityFolder = new File(libFolder, "utility");
|
|
File utilityBuildFolder = new File(libBuildFolder, "utility");
|
|
|
|
includeFolders.add(utilityFolder);
|
|
compileFilesInFolder(libBuildFolder, libFolder, includeFolders);
|
|
compileFilesInFolder(utilityBuildFolder, utilityFolder, includeFolders);
|
|
|
|
// other libraries should not see this library's utility/ folder
|
|
includeFolders.remove(utilityFolder);
|
|
}
|
|
}
|
|
|
|
private void recursiveCompileFilesInFolder(File srcBuildFolder, File srcFolder, List<File> includeFolders) throws RunnerException {
|
|
compileFilesInFolder(srcBuildFolder, srcFolder, includeFolders);
|
|
for (File subFolder : srcFolder.listFiles(new OnlyDirs())) {
|
|
File subBuildFolder = new File(srcBuildFolder, subFolder.getName());
|
|
recursiveCompileFilesInFolder(subBuildFolder, subFolder, includeFolders);
|
|
}
|
|
}
|
|
|
|
private void compileFilesInFolder(File buildFolder, File srcFolder, List<File> includeFolders) throws RunnerException {
|
|
createFolder(buildFolder);
|
|
List<File> objects = compileFiles(buildFolder, srcFolder, false, includeFolders);
|
|
objectFiles.addAll(objects);
|
|
}
|
|
|
|
// 3. compile the core, outputting .o files to <buildPath> and then
|
|
// collecting them into the core.a library file.
|
|
// Also compiles the variant (if it supplies actual source files),
|
|
// which are included in the link directly (not through core.a)
|
|
void compileCore()
|
|
throws RunnerException {
|
|
|
|
File coreFolder = prefs.getFile("build.core.path");
|
|
File variantFolder = prefs.getFile("build.variant.path");
|
|
File buildFolder = prefs.getFile("build.path");
|
|
|
|
List<File> includeFolders = new ArrayList<File>();
|
|
includeFolders.add(coreFolder); // include core path only
|
|
if (variantFolder != null)
|
|
includeFolders.add(variantFolder);
|
|
|
|
|
|
if (variantFolder != null)
|
|
objectFiles.addAll(compileFiles(buildFolder, variantFolder, true,
|
|
includeFolders));
|
|
|
|
File afile = new File(buildFolder, "core.a");
|
|
|
|
List<File> coreObjectFiles = compileFiles(buildFolder, coreFolder, true,
|
|
includeFolders);
|
|
|
|
// See if the .a file is already uptodate
|
|
if (afile.exists()) {
|
|
boolean changed = false;
|
|
for (File file : coreObjectFiles) {
|
|
if (file.lastModified() > afile.lastModified()) {
|
|
changed = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If none of the object files is newer than the .a file, don't
|
|
// bother rebuilding the .a file. There is a small corner case
|
|
// here: If a source file was removed, but no other source file
|
|
// was modified, this will not rebuild core.a even when it
|
|
// should. It's hard to fix and not a realistic case, so it
|
|
// shouldn't be a problem.
|
|
if (!changed) {
|
|
if (verbose)
|
|
System.out.println(I18n.format(_("Using previously compiled file: {0}"), afile.getPath()));
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Delete the .a file, to prevent any previous code from lingering
|
|
afile.delete();
|
|
|
|
try {
|
|
for (File file : coreObjectFiles) {
|
|
|
|
PreferencesMap dict = new PreferencesMap(prefs);
|
|
dict.put("ide_version", "" + Base.REVISION);
|
|
dict.put("archive_file", afile.getName());
|
|
dict.put("object_file", file.getAbsolutePath());
|
|
|
|
String[] cmdArray;
|
|
try {
|
|
String cmd = prefs.get("recipe.ar.pattern");
|
|
cmdArray = StringReplacer.formatAndSplit(cmd, dict, true);
|
|
} catch (Exception e) {
|
|
throw new RunnerException(e);
|
|
}
|
|
execAsynchronously(cmdArray);
|
|
}
|
|
} catch (RunnerException e) {
|
|
afile.delete();
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
// 4. link it all together into the .elf file
|
|
void compileLink()
|
|
throws RunnerException {
|
|
|
|
// TODO: Make the --relax thing in configuration files.
|
|
|
|
// For atmega2560, need --relax linker option to link larger
|
|
// programs correctly.
|
|
String optRelax = "";
|
|
if (prefs.get("build.mcu").equals("atmega2560"))
|
|
optRelax = ",--relax";
|
|
|
|
String objectFileList = "";
|
|
for (File file : objectFiles)
|
|
objectFileList += " \"" + file.getAbsolutePath() + '"';
|
|
objectFileList = objectFileList.substring(1);
|
|
|
|
PreferencesMap dict = new PreferencesMap(prefs);
|
|
String flags = dict.get("compiler.c.elf.flags") + optRelax;
|
|
dict.put("compiler.c.elf.flags", flags);
|
|
dict.put("archive_file", "core.a");
|
|
dict.put("object_files", objectFileList);
|
|
dict.put("ide_version", "" + Base.REVISION);
|
|
|
|
String[] cmdArray;
|
|
try {
|
|
String cmd = prefs.get("recipe.c.combine.pattern");
|
|
cmdArray = StringReplacer.formatAndSplit(cmd, dict, true);
|
|
} catch (Exception e) {
|
|
throw new RunnerException(e);
|
|
}
|
|
execAsynchronously(cmdArray);
|
|
}
|
|
|
|
// 5. extract EEPROM data (from EEMEM directive) to .eep file.
|
|
void compileEep() throws RunnerException {
|
|
PreferencesMap dict = new PreferencesMap(prefs);
|
|
dict.put("ide_version", "" + Base.REVISION);
|
|
|
|
String[] cmdArray;
|
|
try {
|
|
String cmd = prefs.get("recipe.objcopy.eep.pattern");
|
|
cmdArray = StringReplacer.formatAndSplit(cmd, dict, true);
|
|
} catch (Exception e) {
|
|
throw new RunnerException(e);
|
|
}
|
|
execAsynchronously(cmdArray);
|
|
}
|
|
|
|
// 6. build the .hex file
|
|
void compileHex() throws RunnerException {
|
|
PreferencesMap dict = new PreferencesMap(prefs);
|
|
dict.put("ide_version", "" + Base.REVISION);
|
|
|
|
String[] cmdArray;
|
|
try {
|
|
String cmd = prefs.get("recipe.objcopy.hex.pattern");
|
|
cmdArray = StringReplacer.formatAndSplit(cmd, dict, true);
|
|
} catch (Exception e) {
|
|
throw new RunnerException(e);
|
|
}
|
|
execAsynchronously(cmdArray);
|
|
}
|
|
|
|
private static String prepareIncludes(List<File> includeFolders) {
|
|
String res = "";
|
|
for (File p : includeFolders)
|
|
res += " \"-I" + p.getAbsolutePath() + '"';
|
|
|
|
// Remove first space
|
|
return res.substring(1);
|
|
}
|
|
|
|
public PreferencesMap getBuildPreferences() {
|
|
return prefs;
|
|
}
|
|
}
|