diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java index 21a765726ac..39592dff6f0 100644 --- a/app/src/processing/app/Base.java +++ b/app/src/processing/app/Base.java @@ -318,13 +318,14 @@ public Base(String[] args) throws Exception { // Setup board-dependent variables. onBoardOrPortChange(); - boolean opened = false; boolean doUpload = false; boolean doVerify = false; boolean doVerbose = false; String selectBoard = null; String selectPort = null; String currentDirectory = System.getProperty("user.dir"); + List filenames = new LinkedList(); + // Check if any files were passed in on the command line for (int i = 0; i < args.length; i++) { if (args[i].equals("--upload")) { @@ -343,61 +344,81 @@ public Base(String[] args) throws Exception { i++; if (i < args.length) selectBoard = args[i]; + else + showError(null, "Argument required for --board", 3); continue; } if (args[i].equals("--port")) { i++; if (i < args.length) selectPort = args[i]; + else + showError(null, "Argument required for --port", 3); continue; } if (args[i].equals("--curdir")) { i++; if (i < args.length) currentDirectory = args[i]; + else + showError(null, "Argument required for --curdir", 3); continue; } - String path = args[i]; + if (args[i].equals("--pref")) { + i++; + if (i < args.length) + processPrefArgument(args[i]); + else + showError(null, "Argument required for --pref", 3); + continue; + } + if (args[i].startsWith("--")) + showError(null, I18n.format(_("unknown option: {0}"), args[i]), 3); + + filenames.add(args[i]); + } + + if ((doUpload || doVerify) && filenames.size() != 1) + showError(null, _("Must specify exactly one sketch file"), 3); + + for (String path: filenames) { // Fix a problem with systems that use a non-ASCII languages. Paths are // being passed in with 8.3 syntax, which makes the sketch loader code // unhappy, since the sketch folder naming doesn't match up correctly. // http://dev.processing.org/bugs/show_bug.cgi?id=1089 if (isWindows()) { try { - File file = new File(args[i]); + File file = new File(path); path = file.getCanonicalPath(); } catch (IOException e) { e.printStackTrace(); } } + if (!new File(path).isAbsolute()) { path = new File(currentDirectory, path).getAbsolutePath(); } - if (handleOpen(path) != null) { - opened = true; + + if (handleOpen(path, nextEditorLocation(), !(doUpload || doVerify)) == null) { + String mess = I18n.format(_("Failed to open sketch: \"{0}\""), path); + // Open failure is fatal in upload/verify mode + if (doUpload || doVerify) + showError(null, mess, 2); + else + showWarning(null, mess, null); } } if (doUpload || doVerify) { - if (!opened) { - System.out.println(_("Can't open source sketch!")); - System.exit(2); - } - // Set verbosity for command line build Preferences.set("build.verbose", "" + doVerbose); Preferences.set("upload.verbose", "" + doVerbose); Editor editor = editors.get(0); - // Wait until editor is initialized - while (!editor.status.isInitialized()) - Thread.sleep(10); - // Do board selection if requested - if (selectBoard != null) - selectBoard(selectBoard); - + processBoardArgument(selectBoard); + if (doUpload) { // Build and upload if (selectPort != null) @@ -418,11 +439,10 @@ public Base(String[] args) throws Exception { } // Check if there were previously opened sketches to be restored - if (restoreSketches()) - opened = true; + restoreSketches(); // Create a new empty window (will be replaced with any files to be opened) - if (!opened) { + if (editors.isEmpty()) { handleNew(); } @@ -432,6 +452,62 @@ public Base(String[] args) throws Exception { } } + protected void processBoardArgument(String selectBoard) { + // No board selected? Nothing to do + if (selectBoard == null) + return; + + String[] split = selectBoard.split(":", 4); + + if (split.length < 3) { + showError(null, I18n.format(_("{0}: Invalid board name, it should be of the form \"package:arch:board\" or \"package:arch:board:options\""), selectBoard), 3); + } + + TargetPackage targetPackage = getTargetPackage(split[0]); + if (targetPackage == null) { + showError(null, I18n.format(_("{0}: Unknown package"), split[0]), 3); + } + + TargetPlatform targetPlatform = targetPackage.get(split[1]); + if (targetPlatform == null) { + showError(null, I18n.format(_("{0}: Unknown architecture"), split[1]), 3); + } + + TargetBoard targetBoard = targetPlatform.getBoard(split[2]); + if (targetBoard == null) { + showError(null, I18n.format(_("{0}: Unknown board"), split[2]), 3); + } + + selectBoard(targetBoard); + + if (split.length > 3) { + String[] options = split[3].split(","); + for (String option : options) { + String[] keyValue = option.split("=", 2); + + if (keyValue.length != 2) + showError(null, I18n.format(_("{0}: Invalid option, should be of the form \"name=value\""), option, targetBoard.getId()), 3); + String key = keyValue[0].trim(); + String value = keyValue[1].trim(); + + if (!targetBoard.hasMenu(key)) + showError(null, I18n.format(_("{0}: Invalid option for board \"{1}\""), key, targetBoard.getId()), 3); + if (targetBoard.getMenuLabel(key, value) == null) + showError(null, I18n.format(_("{0}: Invalid option for \"{1}\" option for board \"{2}\""), value, key, targetBoard.getId()), 3); + + Preferences.set("custom_" + key, targetBoard.getId() + "_" + value); + } + } + } + + protected void processPrefArgument(String arg) { + String[] split = arg.split("=", 2); + if (split.length != 2 || split[0].isEmpty()) + showError(null, I18n.format(_("{0}: Invalid argument to --pref, should be of the form \"pref=value\""), arg), 3); + + Preferences.set(split[0], split[1]); + } + public Map> getBoardsViaNetwork() { return new HashMap>(boardsViaNetwork); } @@ -494,7 +570,7 @@ protected boolean restoreSketches() throws Exception { location = nextEditorLocation(); } // If file did not exist, null will be returned for the Editor - if (handleOpen(path, location) != null) { + if (handleOpen(path, location, true) != null) { opened++; } } @@ -808,11 +884,11 @@ public void handleOpenPrompt() throws Exception { * @throws Exception */ public Editor handleOpen(String path) throws Exception { - return handleOpen(path, nextEditorLocation()); + return handleOpen(path, nextEditorLocation(), true); } - protected Editor handleOpen(String path, int[] location) throws Exception { + protected Editor handleOpen(String path, int[] location, boolean showEditor) throws Exception { // System.err.println("entering handleOpen " + path); File file = new File(path); @@ -880,7 +956,8 @@ protected Editor handleOpen(String path, int[] location) throws Exception { // now that we're ready, show the window // (don't do earlier, cuz we might move it based on a window being closed) - editor.setVisible(true); + if (showEditor) + editor.setVisible(true); // System.err.println("exiting handleOpen"); @@ -1347,10 +1424,10 @@ private JRadioButtonMenuItem createBoardMenusAndCustomMenus( @SuppressWarnings("serial") Action action = new AbstractAction(board.getName()) { public void actionPerformed(ActionEvent actionevent) { - selectBoard((String) getValue("b")); + selectBoard((TargetBoard)getValue("b")); } }; - action.putValue("b", packageName + ":" + platformName + ":" + boardId); + action.putValue("b", board); JRadioButtonMenuItem item = new JRadioButtonMenuItem(action); @@ -1373,23 +1450,11 @@ public void actionPerformed(ActionEvent actionevent) { @SuppressWarnings("serial") Action subAction = new AbstractAction(_(boardCustomMenu.get(customMenuOption))) { public void actionPerformed(ActionEvent e) { - Preferences.set("target_package", (String) getValue("package")); - Preferences.set("target_platform", (String) getValue("platform")); - Preferences.set("board", (String) getValue("board")); - Preferences.set("custom_" + menuId, getValue("board") + "_" + getValue("custom_menu_option")); - - filterVisibilityOfSubsequentBoardMenus((String) getValue("board"), currentIndex); - - onBoardOrPortChange(); - Sketch.buildSettingChanged(); - rebuildImportMenu(Editor.importMenu); - rebuildExamplesMenu(Editor.examplesMenu); + Preferences.set("custom_" + menuId, ((TargetBoard)getValue("board")).getId() + "_" + getValue("custom_menu_option")); } }; - subAction.putValue("board", boardId); + subAction.putValue("board", board); subAction.putValue("custom_menu_option", customMenuOption); - subAction.putValue("package", packageName); - subAction.putValue("platform", platformName); if (!buttonGroupsMap.containsKey(menuId)) { buttonGroupsMap.put(menuId, new ButtonGroup()); @@ -1410,12 +1475,12 @@ public void actionPerformed(ActionEvent e) { return item; } - private static void filterVisibilityOfSubsequentBoardMenus(String boardID, int fromIndex) { + private static void filterVisibilityOfSubsequentBoardMenus(TargetBoard board, int fromIndex) { for (int i = fromIndex; i < Editor.boardsMenus.size(); i++) { JMenu menu = Editor.boardsMenus.get(i); for (int m = 0; m < menu.getItemCount(); m++) { JMenuItem menuItem = menu.getItem(m); - menuItem.setVisible(menuItem.getAction().getValue("board").equals(boardID)); + menuItem.setVisible(menuItem.getAction().getValue("board").equals(board)); } menu.setVisible(ifThereAreVisibleItemsOn(menu)); @@ -1488,25 +1553,17 @@ private static JMenuItem selectFirstEnabledMenuItem(JMenu menu) { } - private void selectBoard(String selectBoard) { - String[] split = selectBoard.split(":"); - Preferences.set("target_package", split[0]); - Preferences.set("target_platform", split[1]); - String boardId = split[2]; - Preferences.set("board", boardId); + private void selectBoard(TargetBoard targetBoard) { + TargetPlatform targetPlatform = targetBoard.getContainerPlatform(); + TargetPackage targetPackage = targetPlatform.getContainerPackage(); - if (split.length > 3) { - String[] customsParts = split[3].split(","); - for (String customParts : customsParts) { - String[] keyValue = customParts.split("="); - Preferences.set("custom_" + keyValue[0].trim(), boardId + "_" + keyValue[1].trim()); - } - } + Preferences.set("target_package", targetPackage.getId()); + Preferences.set("target_platform", targetPlatform.getId()); + Preferences.set("board", targetBoard.getId()); - filterVisibilityOfSubsequentBoardMenus(boardId, 1); + filterVisibilityOfSubsequentBoardMenus(targetBoard, 1); onBoardOrPortChange(); - Sketch.buildSettingChanged(); rebuildImportMenu(Editor.importMenu); rebuildExamplesMenu(Editor.examplesMenu); } @@ -2008,6 +2065,15 @@ static public String getAvrBasePath() { return path; } + /** + * Returns a specific TargetPackage + * + * @param packageName + * @return + */ + static public TargetPackage getTargetPackage(String packageName) { + return packages.get(packageName); + } /** * Returns the currently selected TargetPlatform. @@ -2352,12 +2418,20 @@ static public void showWarning(String title, String message, Exception e) { } + static public void showError(String title, String message, Throwable e) { + showError(title, message, e, 1); + } + + static public void showError(String title, String message, int exit_code) { + showError(title, message, null, exit_code); + } + /** * Show an error message that's actually fatal to the program. * This is an error that can't be recovered. Use showWarning() * for errors that allow P5 to continue running. */ - static public void showError(String title, String message, Throwable e) { + static public void showError(String title, String message, Throwable e, int exit_code) { if (title == null) title = _("Error"); if (commandLine) { @@ -2368,7 +2442,7 @@ static public void showError(String title, String message, Throwable e) { JOptionPane.ERROR_MESSAGE); } if (e != null) e.printStackTrace(); - System.exit(1); + System.exit(exit_code); } diff --git a/app/src/processing/app/Editor.java b/app/src/processing/app/Editor.java index a6b6442eeea..278b548409e 100644 --- a/app/src/processing/app/Editor.java +++ b/app/src/processing/app/Editor.java @@ -2007,8 +2007,6 @@ public void internalCloseRunner() { try { stopHandler.run(); } catch (Exception e) { } - - sketch.cleanup(); } diff --git a/app/src/processing/app/EditorConsole.java b/app/src/processing/app/EditorConsole.java index 786fd8a16e2..bfd20e152ae 100644 --- a/app/src/processing/app/EditorConsole.java +++ b/app/src/processing/app/EditorConsole.java @@ -333,13 +333,11 @@ public void write(byte b[], int offset, int length) { if (currentConsole != null) { currentConsole.write(b, offset, length, err); } else { - try { - if (err) { - systemErr.write(b); - } else { - systemOut.write(b); - } - } catch (IOException e) { } // just ignore, where would we write? + if (err) { + systemErr.write(b, offset, length); + } else { + systemOut.write(b, offset, length); + } } OutputStream echo = err ? stderrFile : stdoutFile; diff --git a/app/src/processing/app/EditorStatus.java b/app/src/processing/app/EditorStatus.java index 5dd330b1a9b..e09b5b0f975 100644 --- a/app/src/processing/app/EditorStatus.java +++ b/app/src/processing/app/EditorStatus.java @@ -113,7 +113,8 @@ public void empty() { public void notice(String message) { mode = NOTICE; this.message = message; - copyErrorButton.setVisible(false); + if (copyErrorButton != null) + copyErrorButton.setVisible(false); //update(); repaint(); } @@ -126,7 +127,8 @@ public void unnotice(String unmessage) { public void error(String message) { mode = ERR; this.message = message; - copyErrorButton.setVisible(true); + if (copyErrorButton != null) + copyErrorButton.setVisible(true); repaint(); } diff --git a/app/src/processing/app/Sketch.java b/app/src/processing/app/Sketch.java index 3f38f80ca25..c8a2967ec0f 100644 --- a/app/src/processing/app/Sketch.java +++ b/app/src/processing/app/Sketch.java @@ -31,6 +31,7 @@ import processing.app.debug.Compiler; import processing.app.forms.PasswordAuthorizationDialog; import processing.app.helpers.PreferencesMap; +import processing.app.helpers.FileUtils; import processing.app.packages.Library; import processing.app.packages.LibraryList; import processing.app.preproc.*; @@ -96,6 +97,12 @@ public class Sketch { */ private LibraryList importedLibraries; + /** + * File inside the build directory that contains the build options + * used for the last build. + */ + static final String BUILD_PREFS_FILE = "buildprefs.txt"; + /** * path is location of the main .pde file, because this is also * simplest to use when opening the file from the finder/explorer. @@ -151,7 +158,7 @@ public Sketch(Editor editor, String path) throws IOException { * Another exception is when an external editor is in use, * in which case the load happens each time "run" is hit. */ - protected void load() { + protected void load() throws IOException { codeFolder = new File(folder, "code"); dataFolder = new File(folder, "data"); @@ -193,6 +200,10 @@ protected void load() { } } } + + if (codeCount == 0) + throw new IOException(_("No valid code files found")); + // Remove any code that wasn't proper code = (SketchCode[]) PApplet.subset(code, 0, codeCount); @@ -1178,12 +1189,12 @@ protected void setCurrentCode(String findName) { /** * Cleanup temporary files used during a build/run. */ - protected void cleanup() { + protected void cleanup(boolean force) { // if the java runtime is holding onto any files in the build dir, we // won't be able to delete them, so we need to force a gc here System.gc(); - if (deleteFilesOnNextBuild) { + if (force) { // delete the entire directory and all contents // when we know something changed and all objects // need to be recompiled, or if the board does not @@ -1195,8 +1206,6 @@ protected void cleanup() { // work because the build dir won't exist at startup, so the classloader // will ignore the fact that that dir is in the CLASSPATH in run.sh Base.removeDescendants(tempBuildFolder); - - deleteFilesOnNextBuild = false; } else { // delete only stale source files, from the previously // compiled sketch. This allows multiple windows to be @@ -1247,18 +1256,11 @@ protected void cleanup() { */ //protected String compile() throws RunnerException { - // called when any setting changes that requires all files to be recompiled - public static void buildSettingChanged() { - deleteFilesOnNextBuild = true; - } - - private static boolean deleteFilesOnNextBuild = true; - /** * When running from the editor, take care of preparations before running * the build. */ - public void prepare() { + public void prepare() throws IOException { // make sure the user didn't hide the sketch folder ensureExistence(); @@ -1282,12 +1284,6 @@ public void prepare() { load(); } - // in case there were any boogers left behind - // do this here instead of after exiting, since the exit - // can happen so many different ways.. and this will be - // better connected to the dataFolder stuff below. - cleanup(); - // // handle preprocessing the main file's code // return build(tempBuildFolder.getAbsolutePath()); } @@ -1308,11 +1304,11 @@ public void prepare() { * @param buildPath Location to copy all the .java files * @return null if compilation failed, main class name if not */ - public String preprocess(String buildPath) throws RunnerException { - return preprocess(buildPath, new PdePreprocessor()); + public void preprocess(String buildPath) throws RunnerException { + preprocess(buildPath, new PdePreprocessor()); } - public String preprocess(String buildPath, PdePreprocessor preprocessor) throws RunnerException { + public void preprocess(String buildPath, PdePreprocessor preprocessor) throws RunnerException { // make sure the user didn't hide the sketch folder ensureExistence(); @@ -1368,18 +1364,12 @@ public String preprocess(String buildPath, PdePreprocessor preprocessor) throws // 2. run preproc on that code using the sugg class name // to create a single .java file and write to buildpath - String primaryClassName = null; - try { // Output file File streamFile = new File(buildPath, name + ".cpp"); FileOutputStream outputStream = new FileOutputStream(streamFile); preprocessor.write(outputStream); outputStream.close(); - - // store this for the compiler and the runtime - primaryClassName = name + ".cpp"; - } catch (FileNotFoundException fnfe) { fnfe.printStackTrace(); String msg = _("Build folder disappeared or could not be written"); @@ -1428,7 +1418,6 @@ public String preprocess(String buildPath, PdePreprocessor preprocessor) throws sc.addPreprocOffset(headerOffset); } } - return primaryClassName; } @@ -1521,6 +1510,46 @@ public String build(boolean verbose) throws RunnerException { return build(tempBuildFolder.getAbsolutePath(), verbose); } + /** + * Check if the build preferences used on the previous build in + * buildPath match the ones given. + */ + protected boolean buildPreferencesChanged(File buildPrefsFile, String newBuildPrefs) { + // No previous build, so no match + if (!buildPrefsFile.exists()) + return true; + + String previousPrefs; + try { + previousPrefs = FileUtils.readFileToString(buildPrefsFile); + } catch (IOException e) { + System.err.println(_("Could not read prevous build preferences file, rebuilding all")); + return true; + } + + if (!previousPrefs.equals(newBuildPrefs)) { + System.out.println(_("Build options changed, rebuilding all")); + return true; + } else { + return false; + } + } + + /** + * Returns the build preferences of the given compiler as a string. + * Only includes build-specific preferences, to make sure unrelated + * preferences don't cause a rebuild (in particular preferences that + * change on every start, like last.ide.xxx.daterun). */ + protected String buildPrefsString(Compiler compiler) { + PreferencesMap buildPrefs = compiler.getBuildPreferences(); + String res = ""; + SortedSet treeSet = new TreeSet(buildPrefs.keySet()); + for (String k : treeSet) { + if (k.startsWith("build.") || k.startsWith("compiler.") || k.startsWith("recipes.")) + res += k + " = " + buildPrefs.get(k) + "\n"; + } + return res; + } /** * Preprocess and compile all the code for this sketch. @@ -1532,14 +1561,33 @@ public String build(boolean verbose) throws RunnerException { * @return null if compilation failed, main class name if not */ public String build(String buildPath, boolean verbose) throws RunnerException { + String primaryClassName = name + ".cpp"; + Compiler compiler = new Compiler(this, buildPath, primaryClassName); + File buildPrefsFile = new File(buildPath, BUILD_PREFS_FILE); + String newBuildPrefs = buildPrefsString(compiler); + + // Do a forced cleanup (throw everything away) if the previous + // build settings do not match the previous ones + boolean prefsChanged = buildPreferencesChanged(buildPrefsFile, newBuildPrefs); + cleanup(prefsChanged); + + if (prefsChanged) { + try { + PrintWriter out = new PrintWriter(buildPrefsFile); + out.print(newBuildPrefs); + out.close(); + } catch (IOException e) { + System.err.println(_("Could not write build preferences file")); + } + } + // run the preprocessor editor.status.progressUpdate(20); - String primaryClassName = preprocess(buildPath); + preprocess(buildPath); // compile the program. errors will happen as a RunnerException // that will bubble up to whomever called build(). - Compiler compiler = new Compiler(); - if (compiler.compile(this, buildPath, primaryClassName, verbose)) { + if (compiler.compile(verbose)) { size(compiler.getBuildPreferences()); return primaryClassName; } diff --git a/app/src/processing/app/debug/Compiler.java b/app/src/processing/app/debug/Compiler.java index 7cbba74c503..5bdb7d2e989 100644 --- a/app/src/processing/app/debug/Compiler.java +++ b/app/src/processing/app/debug/Compiler.java @@ -62,26 +62,30 @@ public class Compiler implements MessageConsumer { private String targetArch; private RunnerException exception; - + /** - * Compile sketch. - * + * 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 - * @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) + 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(); - prefs = createBuildPreferences(_buildPath, _primaryClassName); - // 0. include paths for core + all libraries sketch.setCompilingProgress(20); List includePaths = new ArrayList();