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.
This commit is contained in:
Matthijs Kooijman 2013-10-29 17:50:58 +01:00
parent 7b7f447a4a
commit 4592acc213
3 changed files with 69 additions and 23 deletions

View File

@ -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);
}

View File

@ -2007,8 +2007,6 @@ public class Editor extends JFrame implements RunnerListener {
try {
stopHandler.run();
} catch (Exception e) { }
sketch.cleanup();
}

View File

@ -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<String> treeSet = new TreeSet<String>(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;