From 4592acc213d01bc7ff118bcd49ff137e72de0566 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Tue, 29 Oct 2013 17:50:58 +0100 Subject: [PATCH] Change the logic deciding when to do a full rebuild Previously, a full cleanup of the work directory (and thus a full rebuild) was done on the first build after: - startup, or - a change in the board or board suboption. This did not cooperate nicely with commandline compilation using --verify. Using the build.path option a persistent build path could be used, but the actual files in that path would never be reused. Now, each build saves the preferences used for building in a file "buildprefs.txt" inside the build directory. Subsequent builds will read this file to see if any build options changed and re-use the existing files if the build options are identical. Because the main .cpp file is not handled by Compiler::build, but by Sketch::preprocess, it is still always regenerated, even if the Sketch itself didn't change. This could be fixed later, though it is probably not a problem. When writing buildprefs.txt, only the build preferences starting with "build.", "compiler." or "recipes." are used. These should be enough to ensure files are always rebuilt when needed (probably also sometimes when not needed, when change build.verbose for example). Using all build preferences would cause the files to be rebuild too often, and because of last.ide.xxx.daterun, they would still rebuild on _every_ invocation... This approach is perhaps not ideal, but improving it would require putting more structure in the preferences instead of piling them all together into the build preferences. Because of this new mechanism, the old buildSettingsChanged()/deleteFilesOnNextBuild could be removed. --- app/src/processing/app/Base.java | 2 - app/src/processing/app/Editor.java | 2 - app/src/processing/app/Sketch.java | 88 +++++++++++++++++++++++------- 3 files changed, 69 insertions(+), 23 deletions(-) diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java index 184eedc94..700d3b056 100644 --- a/app/src/processing/app/Base.java +++ b/app/src/processing/app/Base.java @@ -1454,7 +1454,6 @@ public class Base { Action subAction = new AbstractAction(_(boardCustomMenu.get(customMenuOption))) { public void actionPerformed(ActionEvent e) { Preferences.set("custom_" + menuId, ((TargetBoard)getValue("board")).getId() + "_" + getValue("custom_menu_option")); - Sketch.buildSettingChanged(); } }; subAction.putValue("board", board); @@ -1568,7 +1567,6 @@ public class Base { filterVisibilityOfSubsequentBoardMenus(targetBoard, 1); onBoardOrPortChange(); - Sketch.buildSettingChanged(); rebuildImportMenu(Editor.importMenu); rebuildExamplesMenu(Editor.examplesMenu); } diff --git a/app/src/processing/app/Editor.java b/app/src/processing/app/Editor.java index a6b6442ee..278b54840 100644 --- a/app/src/processing/app/Editor.java +++ b/app/src/processing/app/Editor.java @@ -2007,8 +2007,6 @@ public class Editor extends JFrame implements RunnerListener { try { stopHandler.run(); } catch (Exception e) { } - - sketch.cleanup(); } diff --git a/app/src/processing/app/Sketch.java b/app/src/processing/app/Sketch.java index 95feb0cb5..c8a2967ec 100644 --- a/app/src/processing/app/Sketch.java +++ b/app/src/processing/app/Sketch.java @@ -31,6 +31,7 @@ import processing.app.debug.*; 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. @@ -1182,12 +1189,12 @@ public class Sketch { /** * 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 @@ -1199,8 +1206,6 @@ public class Sketch { // 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 @@ -1251,13 +1256,6 @@ public class Sketch { */ //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. @@ -1286,12 +1284,6 @@ public class Sketch { 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()); } @@ -1518,6 +1510,46 @@ public class Sketch { 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. @@ -1529,14 +1561,32 @@ public class Sketch { * @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 = name + ".cpp"; preprocess(buildPath); // compile the program. errors will happen as a RunnerException // that will bubble up to whomever called build(). - Compiler compiler = new Compiler(this, buildPath, primaryClassName); if (compiler.compile(verbose)) { size(compiler.getBuildPreferences()); return primaryClassName;