You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
326 lines
9.4 KiB
326 lines
9.4 KiB
// <file> |
|
// <copyright see="prj:///doc/copyright.txt"/> |
|
// <license see="prj:///doc/license.txt"/> |
|
// <owner name="Daniel Grunwald"/> |
|
// <version>$Revision$</version> |
|
// </file> |
|
|
|
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 |
|
{ |
|
[Description("${res:Dialog.Options.IDEOptions.ProjectAndSolutionOptions.WhenRunning.DoNotBuild}")] |
|
DoNotBuild, |
|
[Description("${res:Dialog.Options.IDEOptions.ProjectAndSolutionOptions.WhenRunning.BuildOnlyModified}")] |
|
BuildOnlyModified, |
|
[Description("${res:Dialog.Options.IDEOptions.ProjectAndSolutionOptions.WhenRunning.BuildModifiedAndDependent}")] |
|
BuildModifiedAndDependent, |
|
[Description("${res:Dialog.Options.IDEOptions.ProjectAndSolutionOptions.WhenRunning.RegularBuild}")] |
|
RegularBuild |
|
} |
|
|
|
/// <summary> |
|
/// Tracks changes to projects and causes only modified projects |
|
/// to be recompiled. |
|
/// </summary> |
|
static class BuildModifiedProjectsOnlyService |
|
{ |
|
public static BuildOnExecuteSetting Setting { |
|
get { return PropertyService.Get("BuildOnExecute", BuildOnExecuteSetting.RegularBuild); } |
|
set { PropertyService.Set("BuildOnExecute", value); } |
|
} |
|
|
|
static readonly Dictionary<IProject, CompilationPass> unmodifiedProjects = new Dictionary<IProject, CompilationPass>(); |
|
|
|
static BuildModifiedProjectsOnlyService() |
|
{ |
|
// these actions cause a full recompilation: |
|
ProjectService.SolutionClosed += MarkAllForRecompilation; |
|
ProjectService.SolutionConfigurationChanged += MarkAllForRecompilation; |
|
ProjectService.SolutionSaved += MarkAllForRecompilation; |
|
ProjectService.BuildFinished += ProjectService_BuildFinished; |
|
|
|
FileUtility.FileSaved += OnFileSaved; |
|
} |
|
|
|
public static void Initialize() |
|
{ |
|
// first call to init causes static ctor calls |
|
} |
|
|
|
static void ProjectService_BuildFinished(object sender, BuildEventArgs e) |
|
{ |
|
// at the end of an successful build, mark all built projects as unmodified |
|
if (e.Results.Result == BuildResultCode.Success) { |
|
lock (unmodifiedProjects) { |
|
CompilationPass pass = new CompilationPass(); |
|
foreach (IBuildable b in e.Results.BuiltProjects) { |
|
IProject p = GetProjectFromBuildable(b); |
|
if (p != null) { |
|
unmodifiedProjects[p] = pass; |
|
} |
|
} |
|
} |
|
} |
|
// at the end of a cleaning build, mark all projects as requiring a rebuild |
|
if (e.Options.ProjectTarget == BuildTarget.Clean || e.Options.TargetForDependencies == BuildTarget.Clean) { |
|
lock (unmodifiedProjects) { |
|
unmodifiedProjects.Clear(); |
|
} |
|
} |
|
} |
|
|
|
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) { |
|
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 || FileUtility.IsEqualFileName(p.FileName, e.FileName)) { |
|
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: |
|
lock (unmodifiedProjects) { |
|
foreach (var pair in unmodifiedProjects) { |
|
LoggingService.Debug(pair.Key.Name + ": " + pair.Value); |
|
} |
|
} |
|
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 ProjectBuildOptions CreateProjectBuildOptions(BuildOptions options, bool isRootBuildable) |
|
{ |
|
return null; |
|
} |
|
|
|
public ICollection<IBuildable> GetBuildDependencies(ProjectBuildOptions buildOptions) |
|
{ |
|
return new IBuildable[0]; |
|
} |
|
|
|
public void StartBuild(ThreadSafeServiceContainer buildServices, ProjectBuildOptions buildOptions, IBuildFeedbackSink feedbackSink) |
|
{ |
|
} |
|
} |
|
|
|
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<IBuildable, IBuildable> dict = new Dictionary<IBuildable, IBuildable>(); |
|
|
|
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 |
|
{ |
|
internal readonly IBuildable wrapped; |
|
internal readonly 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; } |
|
} |
|
|
|
public ProjectBuildOptions CreateProjectBuildOptions(BuildOptions options, bool isRootBuildable) |
|
{ |
|
return wrapped.CreateProjectBuildOptions(options, isRootBuildable); |
|
} |
|
|
|
Dictionary<ProjectBuildOptions, ICollection<IBuildable>> cachedBuildDependencies = new Dictionary<ProjectBuildOptions, ICollection<IBuildable>>(); |
|
ICollection<IBuildable> cachedBuildDependenciesForNullOptions; |
|
|
|
public ICollection<IBuildable> GetBuildDependencies(ProjectBuildOptions buildOptions) |
|
{ |
|
List<IBuildable> result = new List<IBuildable>(); |
|
foreach (IBuildable b in wrapped.GetBuildDependencies(buildOptions)) { |
|
result.Add(factory.GetWrapper(b)); |
|
} |
|
lock (cachedBuildDependencies) { |
|
if (buildOptions != null) |
|
cachedBuildDependencies[buildOptions] = result; |
|
else |
|
cachedBuildDependenciesForNullOptions = result; |
|
} |
|
return result; |
|
} |
|
|
|
CompilationPass lastCompilationPass; |
|
|
|
/// <summary> |
|
/// Returns true if "this" was recompiled after "comparisonPass". |
|
/// </summary> |
|
internal bool WasRecompiledAfter(CompilationPass comparisonPass) |
|
{ |
|
Debug.Assert(comparisonPass != null); |
|
|
|
if (lastCompilationPass == null) |
|
return true; |
|
return lastCompilationPass.Index > comparisonPass.Index; |
|
} |
|
|
|
public void StartBuild(ThreadSafeServiceContainer buildServices, ProjectBuildOptions buildOptions, IBuildFeedbackSink feedbackSink) |
|
{ |
|
IProject p = wrapped as IProject; |
|
if (p == null) { |
|
wrapped.StartBuild(buildServices, buildOptions, feedbackSink); |
|
} else { |
|
lock (unmodifiedProjects) { |
|
if (!unmodifiedProjects.TryGetValue(p, out lastCompilationPass)) { |
|
lastCompilationPass = null; |
|
} |
|
} |
|
if (lastCompilationPass != null && Setting == BuildOnExecuteSetting.BuildModifiedAndDependent) { |
|
lock (cachedBuildDependencies) { |
|
var dependencies = buildOptions != null ? cachedBuildDependencies[buildOptions] : cachedBuildDependenciesForNullOptions; |
|
if (dependencies.OfType<Wrapper>().Any(w=>w.WasRecompiledAfter(lastCompilationPass))) { |
|
lastCompilationPass = null; |
|
} |
|
} |
|
} |
|
if (lastCompilationPass != null) { |
|
feedbackSink.ReportMessage( |
|
StringParser.Parse("${res:MainWindow.CompilerMessages.SkipProjectNoChanges}", |
|
new string[,] {{ "Name", p.Name }}) |
|
); |
|
feedbackSink.Done(true); |
|
} else { |
|
lastCompilationPass = factory.CurrentPass; |
|
wrapped.StartBuild(buildServices, buildOptions, new BuildFeedbackSink(p, feedbackSink, factory.CurrentPass)); |
|
} |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Wraps a build feedback sink and marks a project as requiring recompilation when |
|
/// compilation was not successful. |
|
/// </summary> |
|
sealed class BuildFeedbackSink : IBuildFeedbackSink |
|
{ |
|
IProject project; |
|
IBuildFeedbackSink sink; |
|
CompilationPass currentPass; |
|
|
|
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 Gui.IProgressMonitor ProgressMonitor { |
|
get { return sink.ProgressMonitor; } |
|
} |
|
|
|
public void ReportError(BuildError error) |
|
{ |
|
sink.ReportError(error); |
|
} |
|
|
|
public void ReportMessage(string message) |
|
{ |
|
sink.ReportMessage(message); |
|
} |
|
|
|
public void Done(bool success) |
|
{ |
|
if (success) { |
|
lock (unmodifiedProjects) { |
|
unmodifiedProjects[project] = currentPass; |
|
} |
|
} |
|
sink.Done(success); |
|
} |
|
} |
|
} |
|
} |
|
}
|
|
|