diff --git a/src/AddIns/Misc/UnitTesting/Src/RunTestCommands.cs b/src/AddIns/Misc/UnitTesting/Src/RunTestCommands.cs
index 7bcaa07b59..08d6036a44 100644
--- a/src/AddIns/Misc/UnitTesting/Src/RunTestCommands.cs
+++ b/src/AddIns/Misc/UnitTesting/Src/RunTestCommands.cs
@@ -420,7 +420,7 @@ namespace ICSharpCode.UnitTesting
///
public override void AfterBuild()
{
- ProjectService.RaiseEventEndBuild();
+ ProjectService.RaiseEventEndBuild(new BuildEventArgs(LastBuildResults));
}
}
diff --git a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj
index f4bea9a98c..63bf772694 100644
--- a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj
+++ b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj
@@ -143,6 +143,7 @@
+
diff --git a/src/Main/Base/Project/Resources/ProjectAndSolutionOptionsPanel.xfrm b/src/Main/Base/Project/Resources/ProjectAndSolutionOptionsPanel.xfrm
index 3555df871e..81ad32ef6c 100644
--- a/src/Main/Base/Project/Resources/ProjectAndSolutionOptionsPanel.xfrm
+++ b/src/Main/Base/Project/Resources/ProjectAndSolutionOptionsPanel.xfrm
@@ -49,10 +49,26 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -82,4 +98,4 @@
-
\ No newline at end of file
+
diff --git a/src/Main/Base/Project/Src/Commands/BuildCommands.cs b/src/Main/Base/Project/Src/Commands/BuildCommands.cs
index 596d6aa59d..0652db54e1 100644
--- a/src/Main/Base/Project/Src/Commands/BuildCommands.cs
+++ b/src/Main/Base/Project/Src/Commands/BuildCommands.cs
@@ -56,6 +56,7 @@ namespace ICSharpCode.SharpDevelop.Project.Commands
public BuildResults LastBuildResults {
get { return lastBuildResults; }
+ protected set { lastBuildResults = value; }
}
protected void CallbackMethod(BuildResults results)
@@ -63,15 +64,19 @@ namespace ICSharpCode.SharpDevelop.Project.Commands
lastBuildResults = results;
ShowResults(results);
AfterBuild();
- if (BuildComplete != null)
- BuildComplete(this, EventArgs.Empty);
+ OnBuildComplete(EventArgs.Empty);
}
public abstract void StartBuild();
public event EventHandler BuildComplete;
-
+ protected virtual void OnBuildComplete(EventArgs e)
+ {
+ if (BuildComplete != null) {
+ BuildComplete(this, e);
+ }
+ }
public static void ShowResults(BuildResults results)
{
@@ -116,11 +121,30 @@ namespace ICSharpCode.SharpDevelop.Project.Commands
public override void AfterBuild()
{
- ProjectService.RaiseEventEndBuild();
+ ProjectService.RaiseEventEndBuild(new BuildEventArgs(LastBuildResults));
base.AfterBuild();
}
}
+ public class BuildBeforeExecute : Build
+ {
+ public override void Run()
+ {
+ if (BuildModifiedProjectsOnlyService.Setting == BuildOnExecuteSetting.DoNotBuild) {
+ LastBuildResults = new BuildResults { Result = BuildResultCode.Success };
+ OnBuildComplete(EventArgs.Empty);
+ } else {
+ base.Run();
+ }
+ }
+
+ public override void StartBuild()
+ {
+ BuildEngine.BuildInGui(BuildModifiedProjectsOnlyService.WrapBuildable(ProjectService.OpenSolution),
+ new BuildOptions(BuildTarget.Build, CallbackMethod));
+ }
+ }
+
public class Rebuild : Build
{
public override void StartBuild()
@@ -175,7 +199,7 @@ namespace ICSharpCode.SharpDevelop.Project.Commands
public override void AfterBuild()
{
- ProjectService.RaiseEventEndBuild();
+ ProjectService.RaiseEventEndBuild(new BuildEventArgs(LastBuildResults));
base.AfterBuild();
}
}
diff --git a/src/Main/Base/Project/Src/Commands/DebugCommands.cs b/src/Main/Base/Project/Src/Commands/DebugCommands.cs
index 6f0aa2d3be..5e298562b5 100644
--- a/src/Main/Base/Project/Src/Commands/DebugCommands.cs
+++ b/src/Main/Base/Project/Src/Commands/DebugCommands.cs
@@ -21,7 +21,7 @@ namespace ICSharpCode.SharpDevelop.Project.Commands
public override void Run()
{
- Build build = new Build();
+ Build build = new BuildBeforeExecute();
build.BuildComplete += delegate {
if (build.LastBuildResults.ErrorCount == 0) {
IProject startupProject = ProjectService.OpenSolution.StartupProject;
diff --git a/src/Main/Base/Project/Src/Gui/Dialogs/OptionPanels/IDEOptions/ProjectAndSolutionOptionsPanel.cs b/src/Main/Base/Project/Src/Gui/Dialogs/OptionPanels/IDEOptions/ProjectAndSolutionOptionsPanel.cs
index c1787d90cb..43ce40b5c5 100644
--- a/src/Main/Base/Project/Src/Gui/Dialogs/OptionPanels/IDEOptions/ProjectAndSolutionOptionsPanel.cs
+++ b/src/Main/Base/Project/Src/Gui/Dialogs/OptionPanels/IDEOptions/ProjectAndSolutionOptionsPanel.cs
@@ -6,6 +6,7 @@
//
using System;
+using System.ComponentModel;
using System.IO;
using System.Windows.Forms;
@@ -29,6 +30,20 @@ namespace ICSharpCode.SharpDevelop.Gui.OptionPanels
((NumericUpDown)ControlDictionary["parallelBuildNumericUpDown"]).Value = Project.BuildOptions.DefaultParallelProjectCount;
((Button)ControlDictionary["selectProjectLocationButton"]).Click += new EventHandler(SelectProjectLocationButtonClicked);
+
+ ComboBox onExecuteComboBox = Get("onExecute");
+ Type type = typeof(Project.BuildOnExecuteSetting);
+ foreach (Project.BuildOnExecuteSetting element in Enum.GetValues(type)) {
+ object[] attr = type.GetField(Enum.GetName(type, element)).GetCustomAttributes(typeof(DescriptionAttribute), false);
+ string description;
+ if (attr.Length > 0) {
+ description = StringParser.Parse((attr[0] as DescriptionAttribute).Description);
+ } else {
+ description = Enum.GetName(type, element);
+ }
+ onExecuteComboBox.Items.Add(description);
+ }
+ onExecuteComboBox.SelectedIndex = (int)Project.BuildModifiedProjectsOnlyService.Setting;
}
public override bool StorePanelContents()
@@ -48,6 +63,8 @@ namespace ICSharpCode.SharpDevelop.Gui.OptionPanels
Project.BuildOptions.ShowErrorListAfterBuild = ((CheckBox)ControlDictionary["showErrorListCheckBox"]).Checked;
Project.BuildOptions.DefaultParallelProjectCount = (int)((NumericUpDown)ControlDictionary["parallelBuildNumericUpDown"]).Value;
+ Project.BuildModifiedProjectsOnlyService.Setting = (Project.BuildOnExecuteSetting)Get("onExecute").SelectedIndex;
+
return true;
}
diff --git a/src/Main/Base/Project/Src/Gui/WorkbenchSingleton.cs b/src/Main/Base/Project/Src/Gui/WorkbenchSingleton.cs
index e18f00f129..a81c1ec2fb 100644
--- a/src/Main/Base/Project/Src/Gui/WorkbenchSingleton.cs
+++ b/src/Main/Base/Project/Src/Gui/WorkbenchSingleton.cs
@@ -99,6 +99,7 @@ namespace ICSharpCode.SharpDevelop.Gui
ParserService.InitializeParserService();
Bookmarks.BookmarkManager.Initialize();
Project.CustomToolsService.Initialize();
+ Project.BuildModifiedProjectsOnlyService.Initialize();
MessageService.MainForm = workbench.MainForm;
diff --git a/src/Main/Base/Project/Src/Project/IProject.cs b/src/Main/Base/Project/Src/Project/IProject.cs
index 837b41f398..a1da7d67b0 100644
--- a/src/Main/Base/Project/Src/Project/IProject.cs
+++ b/src/Main/Base/Project/Src/Project/IProject.cs
@@ -82,6 +82,19 @@ namespace ICSharpCode.SharpDevelop.Project
get;
set;
}
+
+ ///
+ /// Gets/Sets the name of the project.
+ ///
+ ///
+ /// Name already exists in ISolutionFolder, it's repeated here to prevent
+ /// the ambiguity with IBuildable.Name.
+ ///
+ new string Name {
+ get;
+ set;
+ }
+
///
/// Gets the directory of the project file.
/// This is equivalent to Path.GetDirectoryName(project.FileName);
@@ -220,8 +233,9 @@ namespace ICSharpCode.SharpDevelop.Project
///
/// A project or solution.
+ /// The IBuildable interface members are thread-safe.
///
- public interface IBuildable : ISolutionFolder
+ public interface IBuildable
{
///
/// Gets the list of projects on which this project depends.
@@ -233,6 +247,16 @@ namespace ICSharpCode.SharpDevelop.Project
/// This member must be implemented thread-safe.
///
void StartBuild(ProjectBuildOptions buildOptions, IBuildFeedbackSink feedbackSink);
+
+ ///
+ /// Gets the name of the buildable item.
+ ///
+ string Name { get; }
+
+ ///
+ /// Gets the parent solution.
+ ///
+ Solution ParentSolution { get; }
}
///
diff --git a/src/Main/Base/Project/Src/Project/Solution/ISolutionFolder.cs b/src/Main/Base/Project/Src/Project/Solution/ISolutionFolder.cs
index 8d89915e84..13277bb9bb 100644
--- a/src/Main/Base/Project/Src/Project/Solution/ISolutionFolder.cs
+++ b/src/Main/Base/Project/Src/Project/Solution/ISolutionFolder.cs
@@ -33,13 +33,6 @@ namespace ICSharpCode.SharpDevelop.Project
set;
}
- ///
- /// Gets the solution the solution folder/project belongs to. This member is thread-safe.
- ///
- Solution ParentSolution {
- get;
- }
-
string TypeGuid {
get;
set;
diff --git a/src/Main/Base/Project/Src/Services/ProjectService/CompileModifiedProjectsOnly.cs b/src/Main/Base/Project/Src/Services/ProjectService/CompileModifiedProjectsOnly.cs
new file mode 100644
index 0000000000..cbde9b6a7e
--- /dev/null
+++ b/src/Main/Base/Project/Src/Services/ProjectService/CompileModifiedProjectsOnly.cs
@@ -0,0 +1,252 @@
+//
+//
+//
+//
+// $Revision$
+//
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using ICSharpCode.Core;
+using System.Diagnostics;
+using System.ComponentModel;
+
+namespace ICSharpCode.SharpDevelop.Project
+{
+ public enum BuildOnExecuteSetting
+ {
+ // TODO: translate
+ [Description("Do not build")]
+ DoNotBuild,
+ [Description("Build modified projects only")]
+ BuildOnlyModified,
+ [Description("Build modified projects and projects depending on them")]
+ BuildModifiedAndDependent,
+ [Description("Build all projects")]
+ RegularBuild
+ }
+
+ ///
+ /// Tracks changes to projects and causes only modified projects
+ /// to be recompiled.
+ ///
+ static class BuildModifiedProjectsOnlyService
+ {
+ public static BuildOnExecuteSetting Setting {
+ get { return PropertyService.Get("BuildOnExecute", BuildOnExecuteSetting.RegularBuild); }
+ set { PropertyService.Set("BuildOnExecute", value); }
+ }
+
+ static readonly HashSet unmodifiedProjects = new HashSet();
+
+ static BuildModifiedProjectsOnlyService()
+ {
+ // these actions cause a full recompilation:
+ ProjectService.SolutionClosed += MarkAllForRecompilation;
+ ProjectService.SolutionConfigurationChanged += MarkAllForRecompilation;
+ ProjectService.SolutionSaved += MarkAllForRecompilation;
+ ProjectService.EndBuild += ProjectService_EndBuild;
+
+ FileUtility.FileSaved += OnFileSaved;
+ }
+
+ public static void Initialize()
+ {
+ // first call to init causes static ctor calls
+ }
+
+ static void ProjectService_EndBuild(object sender, BuildEventArgs e)
+ {
+ // at the end of an successful build, mark all projects as unmodified
+ if (e.Results.Result == BuildResultCode.Success) {
+ if (ProjectService.OpenSolution != null) {
+ lock (unmodifiedProjects) {
+ unmodifiedProjects.AddRange(ProjectService.OpenSolution.Projects);
+ }
+ }
+ }
+ }
+
+ static void MarkAllForRecompilation(object sender, EventArgs e)
+ {
+ lock (unmodifiedProjects) {
+ unmodifiedProjects.Clear();
+ }
+ }
+
+ static void OnFileSaved(object sender, FileNameEventArgs e)
+ {
+ if (ProjectService.OpenSolution != null) {
+ foreach (IProject p in ProjectService.OpenSolution.Projects) {
+ if (p.FindFile(e.FileName) != null) {
+ lock (unmodifiedProjects) {
+ unmodifiedProjects.Remove(p);
+ }
+ }
+ }
+ }
+ }
+
+ public static IBuildable WrapBuildable(IBuildable buildable)
+ {
+ switch (Setting) {
+ case BuildOnExecuteSetting.DoNotBuild:
+ return new DummyBuildable(buildable);
+ case BuildOnExecuteSetting.BuildModifiedAndDependent:
+ case BuildOnExecuteSetting.BuildOnlyModified:
+ return new WrapperFactory().GetWrapper(buildable);
+ case BuildOnExecuteSetting.RegularBuild:
+ return buildable;
+ default:
+ throw new NotSupportedException();
+ }
+ }
+
+ sealed class DummyBuildable : IBuildable
+ {
+ IBuildable wrappedBuildable;
+
+ public DummyBuildable(IBuildable wrappedBuildable)
+ {
+ this.wrappedBuildable = wrappedBuildable;
+ }
+
+ public string Name {
+ get { return wrappedBuildable.Name; }
+ }
+
+ public Solution ParentSolution {
+ get { return wrappedBuildable.ParentSolution; }
+ }
+
+ public ICollection GetBuildDependencies(ProjectBuildOptions buildOptions)
+ {
+ return new IBuildable[0];
+ }
+
+ public void StartBuild(ProjectBuildOptions buildOptions, IBuildFeedbackSink feedbackSink)
+ {
+ }
+ }
+
+ sealed class WrapperFactory
+ {
+ readonly Dictionary dict = new Dictionary();
+
+ public IBuildable GetWrapper(IBuildable wrapped)
+ {
+ IBuildable b;
+ lock (dict) {
+ if (!dict.TryGetValue(wrapped, out b))
+ b = dict[wrapped] = new Wrapper(wrapped, this);
+ }
+ return b;
+ }
+ }
+
+ sealed class Wrapper : IBuildable
+ {
+ IBuildable wrapped;
+ WrapperFactory factory;
+
+ public Wrapper(IBuildable wrapped, WrapperFactory factory)
+ {
+ this.wrapped = wrapped;
+ this.factory = factory;
+ }
+
+ public string Name {
+ get { return wrapped.Name; }
+ }
+
+ public Solution ParentSolution {
+ get { return wrapped.ParentSolution; }
+ }
+
+ Dictionary> cachedBuildDependencies = new Dictionary>();
+
+ public ICollection GetBuildDependencies(ProjectBuildOptions buildOptions)
+ {
+ List result = new List();
+ foreach (IBuildable b in wrapped.GetBuildDependencies(buildOptions)) {
+ result.Add(factory.GetWrapper(b));
+ }
+ lock (cachedBuildDependencies) {
+ cachedBuildDependencies[buildOptions] = result;
+ }
+ return result;
+ }
+
+ internal bool wasRecompiled;
+
+ public void StartBuild(ProjectBuildOptions buildOptions, IBuildFeedbackSink feedbackSink)
+ {
+ IProject p = wrapped as IProject;
+ if (p == null) {
+ wrapped.StartBuild(buildOptions, feedbackSink);
+ } else {
+ bool isUnmodified;
+ lock (unmodifiedProjects) {
+ isUnmodified = unmodifiedProjects.Contains(p);
+ // mark project as unmodified
+ unmodifiedProjects.Add(p);
+ }
+ if (isUnmodified && Setting == BuildOnExecuteSetting.BuildModifiedAndDependent) {
+ lock (cachedBuildDependencies) {
+ if (cachedBuildDependencies[buildOptions].OfType().Any(w=>w.wasRecompiled)) {
+ isUnmodified = false;
+ }
+ }
+ }
+ if (isUnmodified) {
+ feedbackSink.ReportMessage("Skipped " + p.Name + " (no changes inside SharpDevelop)");
+ feedbackSink.Done(true);
+ } else {
+ wasRecompiled = true;
+ wrapped.StartBuild(buildOptions, new BuildFeedbackSink(p, feedbackSink));
+ }
+ }
+ }
+
+ ///
+ /// Wraps a build feedback sink and marks a project as requiring recompilation when
+ /// compilation was not successful.
+ ///
+ sealed class BuildFeedbackSink : IBuildFeedbackSink
+ {
+ IProject project;
+ IBuildFeedbackSink sink;
+
+ public BuildFeedbackSink(IProject p, IBuildFeedbackSink sink)
+ {
+ Debug.Assert(p != null);
+ Debug.Assert(sink != null);
+ this.project = p;
+ this.sink = sink;
+ }
+
+ public void ReportError(BuildError error)
+ {
+ sink.ReportError(error);
+ }
+
+ public void ReportMessage(string message)
+ {
+ sink.ReportMessage(message);
+ }
+
+ public void Done(bool success)
+ {
+ if (!success) {
+ // force recompilation if there was a build error
+ lock (unmodifiedProjects) {
+ unmodifiedProjects.Remove(project);
+ }
+ }
+ sink.Done(success);
+ }
+ }
+ }
+ }
+}
diff --git a/src/Main/Base/Project/Src/Services/ProjectService/ProjectService.cs b/src/Main/Base/Project/Src/Services/ProjectService/ProjectService.cs
index ca3a7448a7..528b290b8d 100644
--- a/src/Main/Base/Project/Src/Services/ProjectService/ProjectService.cs
+++ b/src/Main/Base/Project/Src/Services/ProjectService/ProjectService.cs
@@ -546,17 +546,19 @@ namespace ICSharpCode.SharpDevelop.Project
public static void RaiseEventStartBuild()
{
+ WorkbenchSingleton.AssertMainThread();
building = true;
if (StartBuild != null) {
StartBuild(null, EventArgs.Empty);
}
}
- public static void RaiseEventEndBuild()
+ public static void RaiseEventEndBuild(BuildEventArgs e)
{
+ WorkbenchSingleton.AssertMainThread();
building = false;
if (EndBuild != null) {
- EndBuild(null, EventArgs.Empty);
+ EndBuild(null, e);
}
}
@@ -623,7 +625,7 @@ namespace ICSharpCode.SharpDevelop.Project
public static event SolutionFolderEventHandler SolutionFolderRemoved;
public static event EventHandler StartBuild;
- public static event EventHandler EndBuild;
+ public static event EventHandler EndBuild;
public static event ProjectConfigurationEventHandler ProjectConfigurationChanged;
public static event SolutionConfigurationEventHandler SolutionConfigurationChanged;
@@ -648,5 +650,15 @@ namespace ICSharpCode.SharpDevelop.Project
public static event EventHandler ProjectItemAdded;
public static event EventHandler ProjectItemRemoved;
}
+
+ public class BuildEventArgs : EventArgs
+ {
+ public readonly BuildResults Results;
+
+ public BuildEventArgs(BuildResults results)
+ {
+ this.Results = results;
+ }
+ }
}