diff --git a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj index 00cebf1b63..6530ecf132 100644 --- a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj +++ b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj @@ -648,7 +648,6 @@ - diff --git a/src/Main/Base/Project/Src/Project/BuildEngine.cs b/src/Main/Base/Project/Src/Project/BuildEngine.cs index c9e5359bb6..8c77acc14a 100644 --- a/src/Main/Base/Project/Src/Project/BuildEngine.cs +++ b/src/Main/Base/Project/Src/Project/BuildEngine.cs @@ -468,6 +468,7 @@ namespace ICSharpCode.SharpDevelop.Project node.hasErrors = !success; projectsCurrentlyBuilding.Remove(node); + results.AddBuiltProject(node.project); if (progressMonitor != null) { progressMonitor.WorkDone += 1; } diff --git a/src/Main/Base/Project/Src/Project/BuildResults.cs b/src/Main/Base/Project/Src/Project/BuildResults.cs index 3a025eed2a..c2e67edc10 100644 --- a/src/Main/Base/Project/Src/Project/BuildResults.cs +++ b/src/Main/Base/Project/Src/Project/BuildResults.cs @@ -33,6 +33,10 @@ namespace ICSharpCode.SharpDevelop.Project { List errors = new List(); ReadOnlyCollection readOnlyErrors; + + List builtProjects = new List(); + ReadOnlyCollection readOnlyBuiltProjects; + BuildResultCode result; int errorCount, warningCount; @@ -54,6 +58,20 @@ namespace ICSharpCode.SharpDevelop.Project } } + /// + /// Adds a project to the list of built projects. + /// This method is thread-sage. + /// + public void AddBuiltProject(IBuildable buildable) + { + if (buildable == null) + throw new ArgumentNullException("buildable"); + lock (builtProjects) { + readOnlyBuiltProjects = null; + builtProjects.Add(buildable); + } + } + /// /// Gets the list of build errors or warnings. /// This property is thread-safe. @@ -69,6 +87,20 @@ namespace ICSharpCode.SharpDevelop.Project } } + /// + /// Gets the list of projects that were built. This property is thread-safe. + /// + public ReadOnlyCollection BuiltProjects { + get { + lock (builtProjects) { + if (readOnlyBuiltProjects == null) { + readOnlyBuiltProjects = Array.AsReadOnly(builtProjects.ToArray()); + } + return readOnlyBuiltProjects; + } + } + } + public BuildResultCode Result { get { return result; } set { result = value; } diff --git a/src/Main/Base/Project/Src/Services/ProjectService/CompileModifiedProjectsOnly.cs b/src/Main/Base/Project/Src/Services/ProjectService/CompileModifiedProjectsOnly.cs index 53e00287b4..0ec673f40e 100644 --- a/src/Main/Base/Project/Src/Services/ProjectService/CompileModifiedProjectsOnly.cs +++ b/src/Main/Base/Project/Src/Services/ProjectService/CompileModifiedProjectsOnly.cs @@ -37,7 +37,7 @@ namespace ICSharpCode.SharpDevelop.Project set { PropertyService.Set("BuildOnExecute", value); } } - static readonly HashSet unmodifiedProjects = new HashSet(); + static readonly Dictionary unmodifiedProjects = new Dictionary(); static BuildModifiedProjectsOnlyService() { @@ -57,16 +57,27 @@ namespace ICSharpCode.SharpDevelop.Project static void ProjectService_EndBuild(object sender, BuildEventArgs e) { - // at the end of an successful build, mark all projects as unmodified + // at the end of an successful build, mark all built projects as unmodified if (e.Results.Result == BuildResultCode.Success) { - if (ProjectService.OpenSolution != null) { - lock (unmodifiedProjects) { - unmodifiedProjects.AddRange(ProjectService.OpenSolution.Projects); + lock (unmodifiedProjects) { + CompilationPass pass = new CompilationPass(); + foreach (IBuildable b in e.Results.BuiltProjects) { + IProject p = GetProjectFromBuildable(b); + if (p != null) { + unmodifiedProjects[p] = pass; + } } } } } + static IProject GetProjectFromBuildable(IBuildable b) + { + while (b is Wrapper) + b = ((Wrapper)b).wrapped; + return b as IProject; + } + static void MarkAllForRecompilation(object sender, EventArgs e) { lock (unmodifiedProjects) { @@ -94,6 +105,11 @@ namespace ICSharpCode.SharpDevelop.Project return new DummyBuildable(buildable); case BuildOnExecuteSetting.BuildModifiedAndDependent: case BuildOnExecuteSetting.BuildOnlyModified: + lock (unmodifiedProjects) { + foreach (var pair in unmodifiedProjects) { + LoggingService.Debug(pair.Key.Name + ": " + pair.Value); + } + } return new WrapperFactory().GetWrapper(buildable); case BuildOnExecuteSetting.RegularBuild: return buildable; @@ -129,8 +145,26 @@ namespace ICSharpCode.SharpDevelop.Project } } + sealed class CompilationPass + { + public readonly int Index; + + static int nextIndex; + + public CompilationPass() + { + Index = System.Threading.Interlocked.Increment(ref nextIndex); + } + + public override string ToString() + { + return "[CompilationPass " + Index + "]"; + } + } + sealed class WrapperFactory { + public readonly CompilationPass CurrentPass = new CompilationPass(); readonly Dictionary dict = new Dictionary(); public IBuildable GetWrapper(IBuildable wrapped) @@ -146,8 +180,8 @@ namespace ICSharpCode.SharpDevelop.Project sealed class Wrapper : IBuildable { - IBuildable wrapped; - WrapperFactory factory; + internal readonly IBuildable wrapped; + internal readonly WrapperFactory factory; public Wrapper(IBuildable wrapped, WrapperFactory factory) { @@ -177,7 +211,19 @@ namespace ICSharpCode.SharpDevelop.Project return result; } - internal bool wasRecompiled; + CompilationPass lastCompilationPass; + + /// + /// Returns true if "this" was recompiled after "comparisonPass". + /// + internal bool WasRecompiledAfter(CompilationPass comparisonPass) + { + Debug.Assert(comparisonPass != null); + + if (lastCompilationPass == null) + return true; + return lastCompilationPass.Index > comparisonPass.Index; + } public void StartBuild(ProjectBuildOptions buildOptions, IBuildFeedbackSink feedbackSink) { @@ -185,28 +231,27 @@ namespace ICSharpCode.SharpDevelop.Project if (p == null) { wrapped.StartBuild(buildOptions, feedbackSink); } else { - bool isUnmodified; lock (unmodifiedProjects) { - isUnmodified = unmodifiedProjects.Contains(p); - // mark project as unmodified - unmodifiedProjects.Add(p); + if (!unmodifiedProjects.TryGetValue(p, out lastCompilationPass)) { + lastCompilationPass = null; + } } - if (isUnmodified && Setting == BuildOnExecuteSetting.BuildModifiedAndDependent) { + if (lastCompilationPass != null && Setting == BuildOnExecuteSetting.BuildModifiedAndDependent) { lock (cachedBuildDependencies) { - if (cachedBuildDependencies[buildOptions].OfType().Any(w=>w.wasRecompiled)) { - isUnmodified = false; + if (cachedBuildDependencies[buildOptions].OfType().Any(w=>w.WasRecompiledAfter(lastCompilationPass))) { + lastCompilationPass = null; } } } - if (isUnmodified) { + if (lastCompilationPass != null) { feedbackSink.ReportMessage( StringParser.Parse("${res:MainWindow.CompilerMessages.SkipProjectNoChanges}", new string[,] {{ "Name", p.Name }}) ); feedbackSink.Done(true); } else { - wasRecompiled = true; - wrapped.StartBuild(buildOptions, new BuildFeedbackSink(p, feedbackSink)); + lastCompilationPass = factory.CurrentPass; + wrapped.StartBuild(buildOptions, new BuildFeedbackSink(p, feedbackSink, factory.CurrentPass)); } } } @@ -219,13 +264,16 @@ namespace ICSharpCode.SharpDevelop.Project { IProject project; IBuildFeedbackSink sink; + CompilationPass currentPass; - public BuildFeedbackSink(IProject p, IBuildFeedbackSink sink) + public BuildFeedbackSink(IProject p, IBuildFeedbackSink sink, CompilationPass currentPass) { Debug.Assert(p != null); Debug.Assert(sink != null); + Debug.Assert(currentPass != null); this.project = p; this.sink = sink; + this.currentPass = currentPass; } public void ReportError(BuildError error) @@ -240,10 +288,9 @@ namespace ICSharpCode.SharpDevelop.Project public void Done(bool success) { - if (!success) { - // force recompilation if there was a build error + if (success) { lock (unmodifiedProjects) { - unmodifiedProjects.Remove(project); + unmodifiedProjects[project] = currentPass; } } sink.Done(success); diff --git a/src/Main/Base/Project/Src/Services/StatusBar/StatusBarService.cs b/src/Main/Base/Project/Src/Services/StatusBar/StatusBarService.cs index d90037c5ce..4d8bf20649 100644 --- a/src/Main/Base/Project/Src/Services/StatusBar/StatusBarService.cs +++ b/src/Main/Base/Project/Src/Services/StatusBar/StatusBarService.cs @@ -6,8 +6,9 @@ // using System; -using System.Linq; +using System.Collections.Generic; using System.Drawing; +using System.Linq; using System.Windows.Forms; using ICSharpCode.Core; diff --git a/src/Main/Base/Project/Src/Util/HashSet.cs b/src/Main/Base/Project/Src/Util/HashSet.cs deleted file mode 100644 index 8d25f9bc45..0000000000 --- a/src/Main/Base/Project/Src/Util/HashSet.cs +++ /dev/null @@ -1,194 +0,0 @@ -// -// -// -// -// $Revision$ -// - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.Specialized; - -namespace ICSharpCode.SharpDevelop -{ - /// - /// Represents a set of items. The set does not preserve the order of items and does not allow items to - /// be added twice. - /// It is cloned by sharing the underlying data structure and delaying the actual copy until the next change. - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] - public sealed class HashSet : ICollection, ICollection, ICloneable - where T : class - { - Dictionary _dict; - bool _copyOnWrite; - - /// - /// Creates a new, empty set. - /// - public HashSet() - { - _dict = new Dictionary(); - } - - /// - /// Creates a copy of the existing set. - /// - public HashSet(HashSet existingSet) - { - existingSet._copyOnWrite = true; - this._copyOnWrite = true; - _dict = existingSet._dict; - } - - /// - /// Adds the item to the set. - /// Trying to add null will return false without changing the collection. - /// - /// True when the item was added, false when it was not added because it already is in the set - public bool Add(T item) - { - if (item == null) - return false; - if (_dict.ContainsKey(item)) { - return false; - } else { - CopyIfRequired(); - _dict.Add(item, null); - return true; - } - } - - /// - /// Adds a list of items to the set. This is equivalent to calling for each item in . - /// - public void AddRange(IEnumerable items) - { - foreach (T item in items) { - Add(item); - } - } - - private void CopyIfRequired() - { - if (_copyOnWrite) { - _copyOnWrite = false; - _dict = new Dictionary(_dict); - } - } - - /// - /// Removes all items from the set. - /// - public void Clear() - { - _dict.Clear(); - } - - /// - /// Tests if this set contains the specified item. - /// Checking for null always returns false. - /// - public bool Contains(T item) - { - if (item == null) - return false; - else - return _dict.ContainsKey(item); - } - - /// - /// Gets the number of items in the collection. - /// - public int Count - { - get { return _dict.Count; } - } - - /// - /// Removes an item from the set. - /// Trying to remove null will return false without changing the collection. - /// - public bool Remove(T item) - { - if (item == null) - return false; - CopyIfRequired(); - if (_dict.Remove(item)) { - return true; - } else { - return false; - } - } - - /// - /// Copy all items to the specified array. - /// - void ICollection.CopyTo(T[] array, int arrayIndex) - { - _dict.Keys.CopyTo(array, arrayIndex); - } - - void ICollection.Add(T item) - { - this.Add(item); - } - - bool ICollection.IsReadOnly - { - get { return false; } - } - - #region IEnumerable Members - /// - /// Gets an enumerator to enumerate the items in the set. - /// - public IEnumerator GetEnumerator() - { - return _dict.Keys.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return _dict.Keys.GetEnumerator(); - } - #endregion - - #region ICollection Members - - void ICollection.CopyTo(Array array, int index) - { - ((ICollection)_dict).CopyTo(array, index); - } - - bool ICollection.IsSynchronized - { - get { return false; } - } - - object ICollection.SyncRoot - { - get { return null; } - } - - #endregion - - #region ICloneable Members - - /// - /// Create a copy of this set. - /// - public HashSet Clone() - { - return new HashSet(this); - } - - object ICloneable.Clone() - { - return this.Clone(); - } - - #endregion - } -}