diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java index bd4e505870a..75d5f057e84 100644 --- a/app/src/processing/app/Base.java +++ b/app/src/processing/app/Base.java @@ -44,10 +44,11 @@ import processing.app.helpers.FileUtils; import processing.app.helpers.PreferencesMap; import processing.app.helpers.filefilters.OnlyDirs; -import processing.app.helpers.filefilters.OnlyFilesWithExtension; import processing.app.javax.swing.filechooser.FileNameExtensionFilter; +import processing.app.packages.HeuristicResolver; import processing.app.packages.Library; import processing.app.packages.LibraryList; +import processing.app.packages.LibraryResolver; import processing.app.tools.MenuScroller; import processing.app.tools.ZipDeflater; import processing.core.*; @@ -105,7 +106,7 @@ public class Base { static private LibraryList libraries; // maps #included files to their library folder - static Map importToLibraryTable; + static private LibraryResolver libraryResolver; // classpath for all known libraries for p5 // (both those in the p5/libs folder and those with lib subfolders @@ -1341,20 +1342,9 @@ public void onBoardOrPortChange() { showWarning(_("Error"), _("Error loading libraries"), e); } - // Populate importToLibraryTable - importToLibraryTable = new HashMap(); - for (Library lib : libraries) { - try { - String headers[] = headerListFromIncludePath(lib.getSrcFolder()); - for (String header : headers) { - importToLibraryTable.put(header, lib); - } - } catch (IOException e) { - showWarning(_("Error"), I18n - .format("Unable to list header files in {0}", lib.getSrcFolder()), e); - } - } - + // Create library resolver + libraryResolver = new HeuristicResolver(libraries); + // Update editors status bar for (Editor editor : editors) editor.onBoardOrPortChange(); @@ -1755,19 +1745,6 @@ public void actionPerformed(ActionEvent event) { } } - /** - * 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(File path) throws IOException { - String[] list = path.list(new OnlyFilesWithExtension(".h")); - if (list == null) { - throw new IOException(); - } - return list; - } - protected void loadHardware(File folder) { if (!folder.isDirectory()) return; @@ -3001,4 +2978,8 @@ public void handleAddLibrary() { public static DiscoveryManager getDiscoveryManager() { return discoveryManager; } + + public static LibraryResolver getLibraryResolver() { + return libraryResolver; + } } diff --git a/app/src/processing/app/Sketch.java b/app/src/processing/app/Sketch.java index 5d66e18a89e..08b938bb686 100644 --- a/app/src/processing/app/Sketch.java +++ b/app/src/processing/app/Sketch.java @@ -32,15 +32,16 @@ import processing.app.forms.PasswordAuthorizationDialog; import processing.app.helpers.PreferencesMap; import processing.app.helpers.FileUtils; +import processing.app.helpers.filefilters.OnlyFilesWithExtension; import processing.app.packages.Library; import processing.app.packages.LibraryList; +import processing.app.packages.LibraryResolver; import processing.app.preproc.*; import processing.core.*; import static processing.app.I18n._; import java.io.*; import java.util.*; -import java.util.List; import javax.swing.*; @@ -1115,7 +1116,7 @@ public void importLibrary(File jarPath) throws IOException { // make sure the user didn't hide the sketch folder ensureExistence(); - String list[] = Base.headerListFromIncludePath(jarPath); + String list[] = jarPath.list(new OnlyFilesWithExtension(".h")); // import statements into the main sketch file (code[0]) // if the current code is a .java file, insert into current @@ -1388,13 +1389,29 @@ public void preprocess(String buildPath, PdePreprocessor preprocessor) throws Ru // grab the imports from the code just preproc'd importedLibraries = new LibraryList(); + LibraryResolver resolver = Base.getLibraryResolver(); for (String item : preprocessor.getExtraImports()) { - Library lib = Base.importToLibraryTable.get(item); + Library lib = resolver.importToLibrary(item); if (lib != null && !importedLibraries.contains(lib)) { importedLibraries.add(lib); } } - + + // extend the import list with the library dependency tree + while (true) { + LibraryList dependencies = new LibraryList(); + for (Library library : importedLibraries) { + for (Library dependency : library.getResolvedDependencies()) { + if (importedLibraries.contains(dependency)) + continue; + dependencies.addOrReplace(dependency); + } + } + if (dependencies.size() == 0) + break; + importedLibraries.addAll(dependencies); + } + // 3. then loop over the code[] and save each .java file for (SketchCode sc : code) { diff --git a/app/src/processing/app/debug/Compiler.java b/app/src/processing/app/debug/Compiler.java index ae3f7e763cc..d9512df265e 100644 --- a/app/src/processing/app/debug/Compiler.java +++ b/app/src/processing/app/debug/Compiler.java @@ -48,10 +48,6 @@ import processing.core.PApplet; public class Compiler implements MessageConsumer { - static final String BUGS_URL = - _("http://github.com/arduino/Arduino/issues"); - static final String SUPER_BADNESS = - I18n.format(_("Compiler error, please submit this code to {0}"), BUGS_URL); private Sketch sketch; diff --git a/app/src/processing/app/helpers/FileUtils.java b/app/src/processing/app/helpers/FileUtils.java index 592d2dc74e6..4cb0e1fec19 100644 --- a/app/src/processing/app/helpers/FileUtils.java +++ b/app/src/processing/app/helpers/FileUtils.java @@ -1,6 +1,7 @@ package processing.app.helpers; import java.io.*; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Random; @@ -188,4 +189,36 @@ public static String readFileToString(File file) throws IOException { } } } + + /** + * Recursively find all files in a folder with the specified extension + * + * @param folder + * Folder to look into + * @param recursive + * true will recursively find all files in sub-folders + * @param extensions + * A list of file extensions to search + * @return + */ + public static List listAllFilesWithExtension(File folder, + boolean recursive, + String... extensions) { + List result = new ArrayList(); + for (File file : folder.listFiles()) { + if (recursive && file.isDirectory()) { + result.addAll(listAllFilesWithExtension(file, true, extensions)); + continue; + } + + for (String ext : extensions) { + if (file.getName().endsWith(ext)) { + result.add(file); + break; + } + } + } + return result; + } + } diff --git a/app/src/processing/app/packages/HeuristicResolver.java b/app/src/processing/app/packages/HeuristicResolver.java new file mode 100644 index 00000000000..e2344258526 --- /dev/null +++ b/app/src/processing/app/packages/HeuristicResolver.java @@ -0,0 +1,150 @@ +/* + * This file is part of Arduino. + * + * Arduino 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * As a special exception, you may use this file as part of a free software + * library without restriction. Specifically, if other files instantiate + * templates or use macros or inline functions from this file, or you compile + * this file and link it with other files to produce an executable, this + * file does not by itself cause the resulting executable to be covered by + * the GNU General Public License. This exception does not however + * invalidate any other reasons why the executable file might be covered by + * the GNU General Public License. + * + * Copyright 2013 Arduino LLC (http://www.arduino.cc/) + */ + +package processing.app.packages; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import processing.app.helpers.FileUtils; +import processing.app.helpers.filefilters.OnlyFilesWithExtension; +import processing.app.preproc.PdePreprocessor; + +/** + * This resolver uses an heuristic approach to resolve dependencies between + * libraries without looking into libraries metadata. + * + * All libraries headers are inspected to search for #include lines, afterward + * import dependencies are searched in the same way we do for includes in + * sketches, i.e. looking for a library containing the requested headers. + */ +public class HeuristicResolver implements LibraryResolver { + + private LibraryList libraries; + private Map importToLibrary; + + public HeuristicResolver(LibraryList _libraries) { + libraries = _libraries; + importToLibrary = new HashMap(); + + // Populate importToLibrary table + for (Library library : libraries) { + File srcFolder = library.getSrcFolder(); + for (String header : srcFolder.list(new OnlyFilesWithExtension(".h"))) { + importToLibrary.put(header, library); + } + } + + // Resolve all libraries dependencies + for (Library library : libraries) + library.resolvedDependencies = findLibraryDependencies(library); + } + + /** + * Resolve dependencies for a library + * + * @param library + * @param arch + * @return A LibraryList containing the dependencies + */ + private LibraryList findLibraryDependencies(Library library) { + List headers = new ArrayList(); + boolean recursive = library.useRecursion(); + File folder = library.getSrcFolder(); + List files = FileUtils.listAllFilesWithExtension(folder, recursive, + ".h", ".c", ".cpp"); + headers.addAll(files); + LibraryList result = new LibraryList(); + for (File header : headers) + result.addOrReplaceAll(findHeaderDependencies(header, headers, library)); + return result; + } + + /** + * Inspect headerFile and search for dependencies + * + * @param headerFile + * @param exclusionList + * @param library + */ + private LibraryList findHeaderDependencies(File headerFile, + List exclusionList, + Library library) { + LibraryList res = new LibraryList(); + + // Extract #includes from header file + List imports; + try { + PdePreprocessor preprocessor = new PdePreprocessor(); + String header = FileUtils.readFileToString(headerFile); + preprocessor.writePrefix(header); + imports = preprocessor.getExtraImports(); + } catch (IOException e) { + e.printStackTrace(); + return res; + } + + // For every #include found... + for (String libImport : imports) { + + // ...check if there is a matching library... + Library depLib = importToLibrary.get(libImport); + if (depLib == null || depLib == library) + continue; + + // ...and check if the include is not in the exclusion list + boolean exclude = false; + for (File excluded : exclusionList) { + if (excluded.getName().equals(libImport)) + exclude = true; + } + if (exclude) + continue; + + // add the dependency + res.addOrReplace(depLib); + + System.out.println("Found dependency for " + library.getName()); + System.out.println(" " + headerFile + " uses " + libImport + " -> " + + depLib.getName()); + } + + return res; + } + + @Override + public Library importToLibrary(String h) { + return importToLibrary.get(h); + } + +} diff --git a/app/src/processing/app/packages/Library.java b/app/src/processing/app/packages/Library.java index 2ac8e5a4379..a853349cac8 100644 --- a/app/src/processing/app/packages/Library.java +++ b/app/src/processing/app/packages/Library.java @@ -22,6 +22,7 @@ public class Library { private String category; private String license; private List architectures; + protected LibraryList resolvedDependencies; private File folder; private File srcFolder; private boolean useRecursion; @@ -209,6 +210,10 @@ public String getAuthor() { return author; } + public LibraryList getResolvedDependencies() { + return resolvedDependencies; + } + public String getParagraph() { return paragraph; } diff --git a/app/src/processing/app/packages/LibraryResolver.java b/app/src/processing/app/packages/LibraryResolver.java new file mode 100644 index 00000000000..fe01caf2393 --- /dev/null +++ b/app/src/processing/app/packages/LibraryResolver.java @@ -0,0 +1,43 @@ +/* + * This file is part of Arduino. + * + * Arduino 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * As a special exception, you may use this file as part of a free software + * library without restriction. Specifically, if other files instantiate + * templates or use macros or inline functions from this file, or you compile + * this file and link it with other files to produce an executable, this + * file does not by itself cause the resulting executable to be covered by + * the GNU General Public License. This exception does not however + * invalidate any other reasons why the executable file might be covered by + * the GNU General Public License. + * + * Copyright 2013 Arduino LLC (http://www.arduino.cc/) + */ + +package processing.app.packages; + +public interface LibraryResolver { + + /** + * Returns the Library referenced by the include file name + * + * @param header + * The include file name, for example "SPI.h". + * @return The referenced library + */ + public abstract Library importToLibrary(String header); + +}