diff --git a/arduino-core/src/processing/app/debug/Compiler.java b/arduino-core/src/processing/app/debug/Compiler.java index dd5d7128f65..ad07d61deae 100644 --- a/arduino-core/src/processing/app/debug/Compiler.java +++ b/arduino-core/src/processing/app/debug/Compiler.java @@ -30,6 +30,7 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileReader; +import java.io.InputStreamReader; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; @@ -358,18 +359,46 @@ public boolean compile(boolean _verbose) throws RunnerException, PreferencesMapE // 0. include paths for core + all libraries progressListener.progress(20); List includeFolders = new ArrayList(); - includeFolders.add(prefs.getFile("build.core.path")); - if (prefs.getFile("build.variant.path") != null) - includeFolders.add(prefs.getFile("build.variant.path")); - for (Library lib : importedLibraries) { - 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) + boolean addRequiredLibrary; + do { + includeFolders.clear(); + includeFolders.add(prefs.getFile("build.core.path")); + if (prefs.getFile("build.variant.path") != null) + includeFolders.add(prefs.getFile("build.variant.path")); + for (Library lib : importedLibraries) { + includeFolders.add(lib.getSrcFolder()); + } + Library required = null; + addRequiredLibrary = false; + for (Library lib : importedLibraries) { + String header = dependencyCheckLibrary(lib, includeFolders); + if (header != null) { + required = BaseNoGui.importToLibraryTable.get(header); + if (required != null && !importedLibraries.contains(required)) { + if (verbose) { + System.out.println(I18n.format(_("Adding library {0} required by {1}"), + required.getName(), lib.getName())); + } + addRequiredLibrary = true; + // When any library must be added to the importedLibraries list + // immediately drop out of this loop, so only a single library + // is added. Each new library can cause different #define + // behavior in other code, so building the includeFolders list + // must be restarted from scratch with each new library. + break; + } + } + } + if (addRequiredLibrary) importedLibraries.add(required); + } while (addRequiredLibrary); + + if (verbose) { + for (Library lib : importedLibraries) { + System.out.println(I18n.format(_("Using library {0} in folder: {1} {2}"), + lib.getName(), lib.getFolder(), lib.isLegacy() ? "(legacy)" : "")); + } System.out.println(); + } List archs = new ArrayList(); archs.add(BaseNoGui.getTargetPlatform().getId()); @@ -881,6 +910,145 @@ static public List findFilesInFolder(File folder, String extension, return files; } + + // 0. dependency check library + // returns the name of any header file not found (on all includeFolders) + // or null if every file the library includes is found + private String dependencyCheckLibrary(Library lib, List includeFolders) + throws RunnerException, PreferencesMapException { + File libFolder = lib.getSrcFolder(); + String header = null; + if (lib.useRecursion()) { + header = recursiveDependencyCheckFolder(libFolder, includeFolders); + } else { + File utilityFolder = new File(libFolder, "utility"); + includeFolders.add(utilityFolder); + header = dependencyCheckFolder(libFolder, includeFolders); + if (header == null) { + header = dependencyCheckFolder(utilityFolder, includeFolders); + } + includeFolders.remove(utilityFolder); + } + return header; + } + + private String recursiveDependencyCheckFolder(File srcFolder, List includeFolders) + throws RunnerException, PreferencesMapException { + String header = dependencyCheckFolder(srcFolder, includeFolders); + if (header != null) return header; + for (File subFolder : srcFolder.listFiles(new OnlyDirs())) { + header = recursiveDependencyCheckFolder(subFolder, includeFolders); + if (header != null) return header; + } + return null; + } + + private String dependencyCheckFolder(File sourcePath, List includeFolders) + throws RunnerException, PreferencesMapException { + for (File file : findFilesInFolder(sourcePath, "S", false)) { + String header = execDependencyCommand(includeFolders, file, "recipe.S.o.pattern"); + if (header != null) return header; + } + for (File file : findFilesInFolder(sourcePath, "c", false)) { + String header = execDependencyCommand(includeFolders, file, "recipe.c.o.pattern"); + if (header != null) return header; + } + for (File file : findFilesInFolder(sourcePath, "cpp", false)) { + String header = execDependencyCommand(includeFolders, file, "recipe.cpp.o.pattern"); + if (header != null) return header; + } + return null; + } + + private String execDependencyCommand(List includeFolders, File sourceFile, String recipe) + throws PreferencesMapException, RunnerException { + // Generate the normal compiler command from its recipe + String[] cmd = getCommandCompilerByRecipe(includeFolders, + sourceFile, new File("dummy.o"), recipe); + // Remove options that interfere with preprocessor-only dependency analysis + int count = 0; + for (int i=1; i < cmd.length; i++) { + if (cmd[i].equals("-c") || cmd[i].equals("-S") || cmd[i].startsWith("-M")) { + cmd[i] = null; + } else if (cmd[i].equals("-o")) { + cmd[i++] = null; + if (i < cmd.length) cmd[i] = null; + } else { + count++; + } + } + // Create a modified compiler command to output the dependency list + String[] dep = new String[count + 4]; + dep[0] = cmd[0]; + dep[1] = "-E"; + dep[2] = "-M"; + dep[3] = "-MG"; + int j = 1; + for (int i=0; i < count; i++) { + while (cmd[j] == null) j++; + dep[i + 4] = cmd[j++]; + } + // Run the compiler to analyze dependencies + // Only the (hopefully fast) preprocessor is run. The slow + // compile and code generation steps are skipped. + Process process; + try { + process = ProcessUtils.exec(dep); + } catch (IOException e) { + RunnerException re = new RunnerException(e.getMessage()); + re.hideStackTrace(); + throw re; + } + //uncomment this to see the actual compiler commands + //for (String c : dep) System.out.print(c + " "); + //System.out.println(); + BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream())); + try { + process.waitFor(); + } catch (InterruptedException e) { + try { + in.close(); + } catch (IOException io) { } + return null; + } + // Read the dependency info and check if the files + try { + while (true) { + String line = in.readLine(); + // gcc provides dependency analysis in Makefile rule format. Files + // that exist have full pathnames. Those which don't exist are given + // as the appeared in the #include. + // For example: + // Sd2Card.o: \ + // /home/paul/Arduino/libraries/SD/src/utility/Sd2Card.cpp \ + // /home/paul/Arduino/hardware/arduino/avr/cores/arduino/Arduino.h \ + // /home/paul/Arduino/hardware/tools/avr/avr/include/stdlib.h \ + // SPI.h + if (line == null) break; + if (line.endsWith("\\")) line = line.substring(0, line.length() - 1); + line = line.trim(); + // Sometimes gcc places more than 1 file per line, delimited by a space. + // Spaces in filenames a preceeded with a backslash. This weird regex + // matches to a space, but not when it follows a backslash. + String[] filenames = line.split("(? includeFolders) throws RunnerException, PreferencesMapException { File buildPath = prefs.getFile("build.path");