/* Library.java - Library System for Wiring Copyright (c) 2006 Nicholas Zambetti. All right reserved. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package processing.app; import processing.app.syntax.*; import java.io.*; import java.util.*; import java.util.zip.*; import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.event.*; /* * Provides information about and builds a library */ public class Library implements MessageConsumer{ private File libFolder; private File utilityFolder; private LibraryManager libManager; RunnerException exception; static final String BUGS_URL = "https://developer.berlios.de/bugs/?group_id=3590"; static final String SUPER_BADNESS = "Compiler error, please submit this code to " + BUGS_URL; /* * Create a Library */ public Library(LibraryManager manager, File folder) { libFolder = folder; libManager = manager; utilityFolder = getUtilityFolder(); // for debug output /* System.out.println("library: " + getName()); System.out.println("folder: " + getFolder()); System.out.println("utility: " + utilityFolder); System.out.println("built: " + isBuilt()); System.out.println("buildable: " + isBuildable()); System.out.println("o files: " + getObjectFiles().length); System.out.println("c files: " + getCSourceFiles().length); System.out.println("cpp files: " + getCPPSourceFiles().length); */ } /* * Directory of library * @return File object of library's folder */ public File getFolder() { return libFolder; } /* * The name of library * @return String with library name, derived from folder * note: this will be eventually taken from xml description file */ public String getName() { return libFolder.getName(); } /* * Tests if library is built * @return True if library has .o files, false otherwise */ public boolean isBuilt() { if(getObjectFiles().length >= (getCSourceFiles().length + getCPPSourceFiles().length)){ return true; } return false; } /* * Tests if library is buildable * @return True if library has source files, false otherwise */ public boolean isBuildable() { if(0 < (getCSourceFiles().length + getCPPSourceFiles().length)){ return true; } return false; } /* * Tests if library is unbuilt but buildable * @return True if library has .cpp files but no .o files, false otherwise */ public boolean isUnbuiltBuildable() { if(isBuildable()){ if(!isBuilt()){ return true; } } return false; } /* * Scans for library "utility" folder * @return File object of library's "utility" folder, or null */ private File getUtilityFolder() { FileFilter filter = new FileFilter() { public boolean accept(File file) { if(file.isDirectory()){ if((file.getName()).equalsIgnoreCase("utility")){ return true; } } return false; } }; File[] files = libFolder.listFiles(filter); if(files.length > 0){ return files[0]; } return null; } /* * Finds examples folder * @return "examples" folder as file object or null */ private File getExamplesFolder() { FileFilter filter = new FileFilter() { public boolean accept(File file) { if(file.isDirectory()){ if((file.getName()).equalsIgnoreCase("examples")){ return true; } } return false; } }; File[] files = libFolder.listFiles(filter); if(files.length > 0){ return files[0]; } return null; } /* * Populates example menu or submenu with files */ private void populateWithExamples(File folder, JMenu menu, ActionListener listener) { FileFilter onlyfolders = new FileFilter() { public boolean accept(File file) { return file.isDirectory(); } }; File[] folders = folder.listFiles(onlyfolders); File file; JMenu submenu; JMenuItem item; for(int i = 0; i < folders.length; ++i){ file = new File(folders[i], folders[i].getName() + ".pde"); if(file.exists()){ item = new JMenuItem(folders[i].getName()); item.setActionCommand(file.getAbsolutePath()); item.addActionListener(listener); menu.add(item); }else{ submenu = new JMenu(folders[i].getName()); populateWithExamples(folders[i], submenu, listener); menu.add(submenu); } } } /* * Builds and returns an examples menu * @return JMenu object with example files, or null if none */ public JMenu getExamplesMenu(ActionListener listener) { JMenu submenu; File examplesFolder = getExamplesFolder(); if(null != examplesFolder){ submenu = new JMenu("Library-" + getName()); populateWithExamples(examplesFolder, submenu, listener); return submenu; } return null; } /* * List of object files for linking * @return Array of object files as File objects */ private File[] getObjectFiles(File folder) { FileFilter onlyObjectFiles = new FileFilter() { public boolean accept(File file) { return (file.getName()).endsWith(".o"); } }; return folder.listFiles(onlyObjectFiles); } public File[] getObjectFiles() { if(null == utilityFolder){ return getObjectFiles(libFolder); } File[] libraryObjects = getObjectFiles(libFolder); File[] utilityObjects = getObjectFiles(utilityFolder); File[] objects = new File[libraryObjects.length + utilityObjects.length]; System.arraycopy(libraryObjects, 0, objects, 0, libraryObjects.length); System.arraycopy(utilityObjects, 0, objects, libraryObjects.length, utilityObjects.length); return objects; } /* * List of header source files for inclusion * @return Array of header source files as File objects */ public File[] getHeaderFiles() { FileFilter onlyHFiles = new FileFilter() { public boolean accept(File file) { return (file.getName()).endsWith(".h"); } }; return libFolder.listFiles(onlyHFiles); } /* * List of library's C source files for compiling * @return Array of C source files as File objects */ private File[] getCSourceFiles(File folder) { FileFilter onlyCFiles = new FileFilter() { public boolean accept(File file) { return (file.getName()).endsWith(".c"); } }; return folder.listFiles(onlyCFiles); } private File[] getCSourceFiles() { if(null == utilityFolder){ return getCSourceFiles(libFolder); } File[] librarySources = getCSourceFiles(libFolder); File[] utilitySources = getCSourceFiles(utilityFolder); File[] sources = new File[librarySources.length + utilitySources.length]; System.arraycopy(librarySources, 0, sources, 0, librarySources.length); System.arraycopy(utilitySources, 0, sources, librarySources.length, utilitySources.length); return sources; } /* * List of C++ source files for compiling * @return Array of C++ source files as File objects */ private File[] getCPPSourceFiles(File folder) { FileFilter onlyCPPFiles = new FileFilter() { public boolean accept(File file) { return (file.getName()).endsWith(".cpp"); } }; return folder.listFiles(onlyCPPFiles); } private File[] getCPPSourceFiles() { if(null == utilityFolder){ return getCPPSourceFiles(libFolder); } File[] librarySources = getCPPSourceFiles(libFolder); File[] utilitySources = getCPPSourceFiles(utilityFolder); File[] sources = new File[librarySources.length + utilitySources.length]; System.arraycopy(librarySources, 0, sources, 0, librarySources.length); System.arraycopy(utilitySources, 0, sources, librarySources.length, utilitySources.length); return sources; } /* * Attempt to build library * @return true on successful build, false otherwise */ public boolean build() throws RunnerException { // fail if library is not buildable (contains no sources) if(!isBuildable()){ return false; } String userDir = System.getProperty("user.dir") + File.separator; String[] baseCompileCommandC = new String[] { ((!Base.isMacOS()) ? "tools/avr/bin/avr-gcc" : userDir + "tools/avr/bin/avr-gcc"), "-c", "-g", "-Os", "-Wall", "-mmcu=" + Preferences.get("build.mcu"), "-DF_CPU=" + Preferences.get("build.f_cpu"), "-I" + libManager.getTarget().getPath(), "-I" + getFolder(), }; String[] baseCompileCommandCPP = new String[] { ((!Base.isMacOS()) ? "tools/avr/bin/avr-g++" : userDir + "tools/avr/bin/avr-g++"), "-c", "-g", "-Os", "-Wall", "-fno-exceptions", "-mmcu=" + Preferences.get("build.mcu"), "-DF_CPU=" + Preferences.get("build.f_cpu"), "-I" + libManager.getTarget().getPath(), "-I" + getFolder(), }; // use built lib directories in include paths when searching for headers // this allows libs to use other libs easily int extraSpots = 2; // two spots for file path and -o portions utilityFolder = getUtilityFolder(); // refresh status of utility folder if(null != utilityFolder){ extraSpots = 3; // an extra spot for utility folder as include } String[] libDirs = libManager.getFolderPaths(); String[] compileCommandC = new String[baseCompileCommandC.length + libDirs.length + extraSpots]; String[] compileCommandCPP = new String[baseCompileCommandCPP.length + libDirs.length + extraSpots]; System.arraycopy(baseCompileCommandC, 0, compileCommandC, 0, baseCompileCommandC.length); System.arraycopy(baseCompileCommandCPP, 0, compileCommandCPP, 0, baseCompileCommandCPP.length); for (int i = 0; i < libDirs.length; ++i) { compileCommandC[baseCompileCommandC.length + i] = "-I" + libDirs[i]; compileCommandCPP[baseCompileCommandCPP.length + i] = "-I" + libDirs[i]; } // add this library's "utility" folder to inclusion paths if(null != utilityFolder){ compileCommandC[compileCommandC.length - 3] = "-I" + utilityFolder.getPath(); compileCommandCPP[compileCommandCPP.length - 3] = "-I" + utilityFolder.getPath(); } File[] sourcesC = getCSourceFiles(); File[] sourcesCPP = getCPPSourceFiles(); // execute the compiler, and create threads to deal // with the input and error streams // int result = 0; try { String pathSansExtension; Process process; boolean compiling = true; // compile c sources for(int i = 0; i < sourcesC.length; ++i) { pathSansExtension = sourcesC[i].getPath(); pathSansExtension = pathSansExtension.substring(0, pathSansExtension.length() - 2); // -2 because ".c" compileCommandC[compileCommandC.length - 2] = sourcesC[i].getPath(); compileCommandC[compileCommandC.length - 1] = "-o" + pathSansExtension + ".o"; process = Runtime.getRuntime().exec(compileCommandC); new MessageSiphon(process.getInputStream(), this); new MessageSiphon(process.getErrorStream(), this); // wait for the process to finish. if interrupted // before waitFor returns, continue waiting // compiling = true; while (compiling) { try { result = process.waitFor(); //System.out.println("result is " + result); compiling = false; } catch (InterruptedException ignored) { } } if (exception != null) { exception.hideStackTrace = true; throw exception; } if(result != 0){ return false; } } // compile c++ sources for(int i = 0; i < sourcesCPP.length; ++i) { pathSansExtension = sourcesCPP[i].getPath(); pathSansExtension = pathSansExtension.substring(0, pathSansExtension.length() - 4); // -4 because ".cpp" compileCommandCPP[compileCommandCPP.length - 2] = sourcesCPP[i].getPath(); compileCommandCPP[compileCommandCPP.length - 1] = "-o" + pathSansExtension + ".o"; process = Runtime.getRuntime().exec(compileCommandCPP); new MessageSiphon(process.getInputStream(), this); new MessageSiphon(process.getErrorStream(), this); // wait for the process to finish. if interrupted // before waitFor returns, continue waiting // compiling = true; while (compiling) { try { result = process.waitFor(); //System.out.println("result is " + result); compiling = false; } catch (InterruptedException ignored) { } } if (exception != null) { exception.hideStackTrace = true; throw exception; } if(result != 0){ return false; } } } catch (Exception e) { String msg = e.getMessage(); if ((msg != null) && (msg.indexOf("avr-gcc: not found") != -1)) { Base.showWarning("Compiler error", "Could not find the compiler.\n" + "avr-gcc is missing from your PATH,\n" + "see readme.txt for help.", null); return false; } else if ((msg != null) && (msg.indexOf("avr-g++: not found") != -1)) { Base.showWarning("Compiler error", "Could not find the compiler.\n" + "avr-g++ is missing from your PATH,\n" + "see readme.txt for help.", null); return false; } else { e.printStackTrace(); result = -1; } } // an error was queued up by message() if (exception != null) { throw exception; } if (result != 0 && result != 1 ) { Base.openURL(BUGS_URL); throw new RunnerException(SUPER_BADNESS); } // success would mean that 'result' is set to zero return (result == 0); // ? true : false; } /** * 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 inString) { // This receives messages as full lines, so a newline needs // to be added as they're printed to the console. // always print all compilation output for library writers! String outString = ""; // shorten file paths so that they are friendlier int start = 0; int end = 0; String substring = libFolder.getPath() + File.separator; StringBuffer result = new StringBuffer(); while ((end = inString.indexOf(substring, start)) >= 0) { result.append(inString.substring(start, end)); start = end + substring.length(); } result.append(inString.substring(start)); outString = result.toString(); System.err.print(outString); // prepare error for throwing if (inString.indexOf("error") != -1){ exception = new RunnerException("Error building library \"" + getName() + "\""); } } /** * Handles loading of keywords file. * It is recommended that a # sign be used for comments * inside keywords.txt. */ public void addSyntaxColors(PdeKeywords keywords) { File keywordsFile = new File(libFolder.getPath() + File.separator + "keywords.txt"); // do not bother if no keywords file to read // should reprimand negligent library writers?! if(!keywordsFile.exists() || !keywordsFile.canRead()){ return; } try{ // open file stream in the verbose java way InputStream input = new FileInputStream(keywordsFile); InputStreamReader isr = new InputStreamReader(input); BufferedReader reader = new BufferedReader(isr); String line = null; while ((line = reader.readLine()) != null) { // skip empty and whitespace lines if (line.trim().length() == 0){ continue; } // skip lines without tabs if (line.indexOf('\t') == -1){ continue; } String pieces[] = Base.split(line, '\t'); if (pieces.length >= 2) { String keyword = pieces[0].trim(); String coloring = pieces[1].trim(); if (coloring.length() > 0) { // text will be KEYWORD or LITERAL boolean isKey = (coloring.charAt(0) == 'K'); // KEYWORD1 -> 0, KEYWORD2 -> 1, etc int num = coloring.charAt(coloring.length() - 1) - '1'; byte id = (byte)((isKey ? Token.KEYWORD1 : Token.LITERAL1) + num); //System.out.println("got " + (isKey ? "keyword" : "literal") + (num+1) + " for " + keyword); PdeKeywords.getKeywordColoring().add(keyword, id); } } } // close file stream reader.close(); } catch (Exception e) { Base.showError("Problem Loading Keywords", "Could not load or interpret 'keywords.txt' in " + getName() + " library.\n" + "This must be corrected before distributing.", e); } } }