From c49f7e80457e81d1bea4e4ea6a5c4cab1bc1bd3f Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Wed, 30 Jul 2008 11:41:41 +0000 Subject: [PATCH] Add option to build only projects that were modified inside SharpDevelop to improve compilation time. git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@3253 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61 --- .../Misc/UnitTesting/Src/RunTestCommands.cs | 2 +- .../Project/ICSharpCode.SharpDevelop.csproj | 1 + .../ProjectAndSolutionOptionsPanel.xfrm | 20 +- .../Project/Src/Commands/BuildCommands.cs | 34 ++- .../Project/Src/Commands/DebugCommands.cs | 2 +- .../ProjectAndSolutionOptionsPanel.cs | 17 ++ .../Project/Src/Gui/WorkbenchSingleton.cs | 1 + src/Main/Base/Project/Src/Project/IProject.cs | 26 +- .../Src/Project/Solution/ISolutionFolder.cs | 7 - .../CompileModifiedProjectsOnly.cs | 252 ++++++++++++++++++ .../Services/ProjectService/ProjectService.cs | 18 +- 11 files changed, 360 insertions(+), 20 deletions(-) create mode 100644 src/Main/Base/Project/Src/Services/ProjectService/CompileModifiedProjectsOnly.cs 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; + } + } }