diff --git a/src/AddIns/Analysis/UnitTesting/Interfaces/UnitTestBuildProjectFactory.cs b/src/AddIns/Analysis/UnitTesting/Interfaces/UnitTestBuildProjectFactory.cs deleted file mode 100644 index c3cd89c71a..0000000000 --- a/src/AddIns/Analysis/UnitTesting/Interfaces/UnitTestBuildProjectFactory.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) -// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) - -using System; -using System.Collections.Generic; -using ICSharpCode.SharpDevelop.Project; -using ICSharpCode.SharpDevelop.Project.Commands; - -namespace ICSharpCode.UnitTesting -{ - public class UnitTestBuildProjectFactory : IBuildProjectFactory - { - public BuildProject CreateBuildProjectBeforeTestRun(IEnumerable projects) - { - return new BuildProjectBeforeExecute(new MultipleProjectBuildable(projects)); - } - } -} diff --git a/src/AddIns/Analysis/UnitTesting/Model/ITestProject.cs b/src/AddIns/Analysis/UnitTesting/Model/ITestProject.cs index 1d28961183..5ceee71f85 100644 --- a/src/AddIns/Analysis/UnitTesting/Model/ITestProject.cs +++ b/src/AddIns/Analysis/UnitTesting/Model/ITestProject.cs @@ -28,11 +28,9 @@ namespace ICSharpCode.UnitTesting IEnumerable GetTestsForEntity(IEntity entity); /// - /// Returns a SharpDevelop that builds the project - /// for test execution. - /// May return null if the project does not require compilation. + /// Gets whether the project needs to be compiled before the tests can be run. /// - IBuildable GetBuildableForTesting(); + bool IsBuildNeededBeforeTestRun { get; } /// /// Notifies the project that the parse information was changed. diff --git a/src/AddIns/Analysis/UnitTesting/Model/TestProjectBase.cs b/src/AddIns/Analysis/UnitTesting/Model/TestProjectBase.cs index a7d205ff4e..d1b5dac61a 100644 --- a/src/AddIns/Analysis/UnitTesting/Model/TestProjectBase.cs +++ b/src/AddIns/Analysis/UnitTesting/Model/TestProjectBase.cs @@ -57,9 +57,8 @@ namespace ICSharpCode.UnitTesting get { return project.Name; } } - public virtual IBuildable GetBuildableForTesting() - { - return project; + public virtual bool IsBuildNeededBeforeTestRun { + get { return true; } } public override ImmutableStack FindPathToDescendant(ITest test) diff --git a/src/AddIns/Analysis/UnitTesting/Test/NUnit/CreateNUnitTestRunnerTestFixture.cs b/src/AddIns/Analysis/UnitTesting/Test/NUnit/CreateNUnitTestRunnerTestFixture.cs index f5db69ca2b..f0772ed772 100644 --- a/src/AddIns/Analysis/UnitTesting/Test/NUnit/CreateNUnitTestRunnerTestFixture.cs +++ b/src/AddIns/Analysis/UnitTesting/Test/NUnit/CreateNUnitTestRunnerTestFixture.cs @@ -39,7 +39,7 @@ namespace UnitTesting.Tests.NUnit [Test] public void NUnitTestProjectBuildsTheProject() { - Assert.AreSame(project, testProject.GetBuildableForTesting()); + Assert.IsTrue(testProject.IsBuildNeededBeforeTestRun); } } } diff --git a/src/AddIns/Analysis/UnitTesting/TestRunner/TestExecutionManager.cs b/src/AddIns/Analysis/UnitTesting/TestRunner/TestExecutionManager.cs index ad56784ea5..cb224310b0 100644 --- a/src/AddIns/Analysis/UnitTesting/TestRunner/TestExecutionManager.cs +++ b/src/AddIns/Analysis/UnitTesting/TestRunner/TestExecutionManager.cs @@ -23,7 +23,7 @@ namespace ICSharpCode.UnitTesting.Frameworks /// public class TestExecutionManager { - readonly IBuildProjectFactory buildProjectFactory; + readonly IBuildService buildService; readonly IUnitTestTaskService taskService; readonly IUnitTestSaveAllFilesCommand saveAllFilesCommand; readonly ITestService testService; @@ -34,7 +34,7 @@ namespace ICSharpCode.UnitTesting.Frameworks public TestExecutionManager() { - this.buildProjectFactory = new UnitTestBuildProjectFactory(); + this.buildService = SD.BuildService; this.taskService = new UnitTestTaskService(); this.saveAllFilesCommand = new UnitTestSaveAllFilesCommand(); this.testService = SD.GetRequiredService(); @@ -62,12 +62,15 @@ namespace ICSharpCode.UnitTesting.Frameworks saveAllFilesCommand.SaveAllFiles(); // Run the build, if necessary: - var projectsToBuild = testsByProject.Keys.Select(p => p.GetBuildableForTesting()).Where(b => b != null).ToList(); + var projectsToBuild = testsByProject.Keys.Where(p => p.IsBuildNeededBeforeTestRun).Select(p => p.Project).ToList(); if (projectsToBuild.Count > 0) { - var buildCommand = buildProjectFactory.CreateBuildProjectBeforeTestRun(projectsToBuild); - var buildResults = await buildCommand.BuildAsync(cancellationToken); - if (buildResults.Result != BuildResultCode.Success) - return; + using (cancellationToken.Register(buildService.CancelBuild)) { + var buildOptions = new BuildOptions(BuildTarget.Build); + buildOptions.BuildDetection = BuildOptions.BuildOnExecute; + var buildResults = await buildService.BuildAsync(projectsToBuild, buildOptions); + if (buildResults.Result != BuildResultCode.Success) + return; + } } cancellationToken.ThrowIfCancellationRequested(); diff --git a/src/AddIns/Analysis/UnitTesting/UnitTesting.csproj b/src/AddIns/Analysis/UnitTesting/UnitTesting.csproj index 8a18443bfd..689f7266f0 100644 --- a/src/AddIns/Analysis/UnitTesting/UnitTesting.csproj +++ b/src/AddIns/Analysis/UnitTesting/UnitTesting.csproj @@ -80,7 +80,6 @@ - @@ -100,7 +99,6 @@ - diff --git a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Project/CSharpProject.cs b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Project/CSharpProject.cs index a7d4869c15..b02a28aea3 100644 --- a/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Project/CSharpProject.cs +++ b/src/AddIns/BackendBindings/CSharpBinding/Project/Src/Project/CSharpProject.cs @@ -7,9 +7,12 @@ using System.ComponentModel; using System.IO; using System.Linq; using System.Threading; +using System.Threading.Tasks; + using ICSharpCode.NRefactory.CSharp; using ICSharpCode.NRefactory.TypeSystem; using ICSharpCode.SharpDevelop; +using ICSharpCode.SharpDevelop.Gui; using ICSharpCode.SharpDevelop.Internal.Templates; using ICSharpCode.SharpDevelop.Project; using ICSharpCode.SharpDevelop.Project.Converter; @@ -80,16 +83,15 @@ namespace CSharpBinding InitializeProjectContent(new CSharpProjectContent()); } - public override void StartBuild(ProjectBuildOptions options, IBuildFeedbackSink feedbackSink) + public override Task BuildAsync(ProjectBuildOptions options, IBuildFeedbackSink feedbackSink, IProgressMonitor progressMonitor) { if (this.MinimumSolutionVersion == Solution.SolutionVersionVS2005) { - MSBuildEngine.StartBuild(this, - options, - feedbackSink, - MSBuildEngine.AdditionalTargetFiles.Concat( - new [] { Path.Combine(MSBuildEngine.SharpDevelopBinPath, "SharpDevelop.CheckMSBuild35Features.targets") })); + return MSBuildEngine.BuildAsync( + this, options, feedbackSink, progressMonitor.CancellationToken, + MSBuildEngine.AdditionalTargetFiles.Concat( + new [] { Path.Combine(MSBuildEngine.SharpDevelopBinPath, "SharpDevelop.CheckMSBuild35Features.targets") })); } else { - base.StartBuild(options, feedbackSink); + return base.BuildAsync(options, feedbackSink, progressMonitor); } } diff --git a/src/AddIns/Misc/UsageDataCollector/UsageDataCollector/UsageDataSessionWriter.cs b/src/AddIns/Misc/UsageDataCollector/UsageDataCollector/UsageDataSessionWriter.cs index 6e8eb0547f..b046c0e318 100644 --- a/src/AddIns/Misc/UsageDataCollector/UsageDataCollector/UsageDataSessionWriter.cs +++ b/src/AddIns/Misc/UsageDataCollector/UsageDataCollector/UsageDataSessionWriter.cs @@ -44,6 +44,7 @@ namespace ICSharpCode.UsageDataCollector SQLiteConnectionStringBuilder conn = new SQLiteConnectionStringBuilder(); conn.DataSource = databaseFileName; + Directory.CreateDirectory(Path.GetDirectoryName(databaseFileName)); connection = new SQLiteConnection(conn.ConnectionString); connection.Open(); try { diff --git a/src/Main/Base/Project/ICSharpCode.SharpDevelop.addin b/src/Main/Base/Project/ICSharpCode.SharpDevelop.addin index bc6756df0f..32c0e33fd5 100755 --- a/src/Main/Base/Project/ICSharpCode.SharpDevelop.addin +++ b/src/Main/Base/Project/ICSharpCode.SharpDevelop.addin @@ -70,6 +70,8 @@ class="ICSharpCode.SharpDevelop.Dom.ModelFactory"/> + diff --git a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj index 5c92ac387b..932d89f2fa 100644 --- a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj +++ b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj @@ -123,6 +123,18 @@ + + + + + + + + + + + + @@ -389,7 +401,6 @@ - @@ -397,7 +408,6 @@ Code - @@ -411,7 +421,6 @@ - @@ -460,7 +469,6 @@ - @@ -777,8 +785,6 @@ - - @@ -797,13 +803,11 @@ - - @@ -867,7 +871,9 @@ + + diff --git a/src/Main/Base/Project/Project/Build/BuildDetection.cs b/src/Main/Base/Project/Project/Build/BuildDetection.cs new file mode 100644 index 0000000000..908a2dba1e --- /dev/null +++ b/src/Main/Base/Project/Project/Build/BuildDetection.cs @@ -0,0 +1,21 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.ComponentModel; +using System.Collections.Generic; + +namespace ICSharpCode.SharpDevelop.Project +{ + public enum BuildDetection + { + [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 + } +} diff --git a/src/Main/Base/Project/Src/Project/BuildError.cs b/src/Main/Base/Project/Project/Build/BuildError.cs similarity index 94% rename from src/Main/Base/Project/Src/Project/BuildError.cs rename to src/Main/Base/Project/Project/Build/BuildError.cs index e6b4d1dbc1..eedd09b192 100644 --- a/src/Main/Base/Project/Src/Project/BuildError.cs +++ b/src/Main/Base/Project/Project/Build/BuildError.cs @@ -42,7 +42,8 @@ namespace ICSharpCode.SharpDevelop.Project string errorText; string fileName; int line; - bool warning; + bool isWarning; + bool isMessage; [NonSerialized] object tag; string contextMenuAddInTreeEntry; @@ -105,12 +106,13 @@ namespace ICSharpCode.SharpDevelop.Project } public bool IsWarning { - get { - return warning; - } - set { - warning = value; - } + get { return isWarning; } + set { isWarning = value; } + } + + public bool IsMessage { + get { return isMessage; } + set { isMessage = value; } } /// diff --git a/src/Main/Base/Project/Project/Build/BuildEventArgs.cs b/src/Main/Base/Project/Project/Build/BuildEventArgs.cs new file mode 100644 index 0000000000..969fbdd3ee --- /dev/null +++ b/src/Main/Base/Project/Project/Build/BuildEventArgs.cs @@ -0,0 +1,43 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; + +namespace ICSharpCode.SharpDevelop.Project +{ + public class BuildEventArgs : EventArgs + { + /// + /// The projects to be built. + /// + public readonly IReadOnlyList Projects; + + /// + /// The build options. + /// + public readonly BuildOptions Options; + + /// + /// Gets the build results. + /// This property is null for build started events. + /// + public readonly BuildResults Results; + + public BuildEventArgs(IReadOnlyList projects, BuildOptions options) + : this(projects, options, null) + { + } + + public BuildEventArgs(IReadOnlyList projects, BuildOptions options, BuildResults results) + { + if (projects == null) + throw new ArgumentNullException("projects"); + if (options == null) + throw new ArgumentNullException("options"); + this.Projects = projects; + this.Options = options; + this.Results = results; + } + } +} diff --git a/src/Main/Base/Project/Src/Project/BuildOptions.cs b/src/Main/Base/Project/Project/Build/BuildOptions.cs similarity index 71% rename from src/Main/Base/Project/Src/Project/BuildOptions.cs rename to src/Main/Base/Project/Project/Build/BuildOptions.cs index 297fd46cd6..0e2fb4c77d 100644 --- a/src/Main/Base/Project/Src/Project/BuildOptions.cs +++ b/src/Main/Base/Project/Project/Build/BuildOptions.cs @@ -4,60 +4,15 @@ using System; using System.Collections.Generic; using ICSharpCode.Core; -using Microsoft.Build.Framework; namespace ICSharpCode.SharpDevelop.Project { - public delegate void BuildCallback(BuildResults results); - - /// - /// Specifies options for building a single project. - /// - public class ProjectBuildOptions - { - BuildTarget target; - IDictionary properties = new SortedList(); - - public BuildTarget Target { - get { return target; } - } - - public IDictionary Properties { - get { return properties; } - } - - public ProjectBuildOptions(BuildTarget target) - { - this.target = target; - } - - /// - /// Specifies the project configuration used for the build. - /// - public string Configuration { get; set; } - - /// - /// Specifies the project platform used for the build. - /// - public string Platform { get; set; } - - /// - /// Gets/Sets the verbosity of build output. - /// - public BuildOutputVerbosity BuildOutputVerbosity { get; set; } - } - - public enum BuildOutputVerbosity - { - Normal, - Diagnostic - } - /// /// Specifies options when starting a build. /// public class BuildOptions { + #region static settings public static bool ShowErrorListAfterBuild { get { return PropertyService.Get("SharpDevelop.ShowErrorListAfterBuild", true); @@ -76,6 +31,15 @@ namespace ICSharpCode.SharpDevelop.Project } } + public static BuildDetection BuildOnExecute { + get { + return PropertyService.Get("SharpDevelop.BuildOnExecute", BuildDetection.RegularBuild); + } + set { + PropertyService.Set("SharpDevelop.BuildOnExecute", value); + } + } + public static BuildOutputVerbosity DefaultBuildOutputVerbosity { get { return PropertyService.Get("SharpDevelop.DefaultBuildOutputVerbosity", BuildOutputVerbosity.Normal); @@ -84,6 +48,7 @@ namespace ICSharpCode.SharpDevelop.Project PropertyService.Set("SharpDevelop.DefaultBuildOutputVerbosity", value); } } + #endregion IDictionary globalAdditionalProperties = new SortedList(); IDictionary projectAdditionalProperties = new SortedList(); @@ -113,24 +78,21 @@ namespace ICSharpCode.SharpDevelop.Project /// public BuildOutputVerbosity BuildOutputVerbosity { get; set; } - public BuildOptions(BuildTarget target, BuildCallback callback) + /// + /// Gets/Sets whether to build all projects or only modified ones. + /// The default is to build all projects. + /// + public BuildDetection BuildDetection { get; set; } + + public BuildOptions(BuildTarget target) { - this.callback = callback; this.projectTarget = target; this.TargetForDependencies = target; this.BuildDependentProjects = true; this.ParallelProjectCount = DefaultParallelProjectCount; this.BuildOutputVerbosity = DefaultBuildOutputVerbosity; - } - - readonly BuildCallback callback; - - /// - /// Gets the method to call when the build has finished. - /// - public BuildCallback Callback { - get { return callback; } + this.BuildDetection = BuildDetection.RegularBuild; } readonly BuildTarget projectTarget; diff --git a/src/AddIns/Analysis/UnitTesting/Interfaces/IBuildProjectFactory.cs b/src/Main/Base/Project/Project/Build/BuildOutputVerbosity.cs similarity index 51% rename from src/AddIns/Analysis/UnitTesting/Interfaces/IBuildProjectFactory.cs rename to src/Main/Base/Project/Project/Build/BuildOutputVerbosity.cs index dd76aa6930..b20b0715cf 100644 --- a/src/AddIns/Analysis/UnitTesting/Interfaces/IBuildProjectFactory.cs +++ b/src/Main/Base/Project/Project/Build/BuildOutputVerbosity.cs @@ -3,13 +3,14 @@ using System; using System.Collections.Generic; -using ICSharpCode.SharpDevelop.Project; -using ICSharpCode.SharpDevelop.Project.Commands; -namespace ICSharpCode.UnitTesting +namespace ICSharpCode.SharpDevelop.Project { - public interface IBuildProjectFactory + public enum BuildOutputVerbosity { - BuildProject CreateBuildProjectBeforeTestRun(IEnumerable projects); + Normal, + Diagnostic } + + } diff --git a/src/Main/Base/Project/Src/Project/BuildResults.cs b/src/Main/Base/Project/Project/Build/BuildResults.cs similarity index 68% rename from src/Main/Base/Project/Src/Project/BuildResults.cs rename to src/Main/Base/Project/Project/Build/BuildResults.cs index 9c3827b528..c72b9e9cdd 100644 --- a/src/Main/Base/Project/Src/Project/BuildResults.cs +++ b/src/Main/Base/Project/Project/Build/BuildResults.cs @@ -30,11 +30,8 @@ namespace ICSharpCode.SharpDevelop.Project List errors = new List(); ReadOnlyCollection readOnlyErrors; - List builtProjects = new List(); - ReadOnlyCollection readOnlyBuiltProjects; - BuildResultCode result; - int errorCount, warningCount; + int errorCount, warningCount, messageCount; /// /// Adds a build error/warning to the results. @@ -47,27 +44,15 @@ namespace ICSharpCode.SharpDevelop.Project lock (errors) { readOnlyErrors = null; errors.Add(error); - if (error.IsWarning) + if (error.IsMessage) + messageCount++; + else if (error.IsWarning) warningCount++; else errorCount++; } } - /// - /// Adds a project to the list of built projects. - /// This method is thread-safe. - /// - 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. @@ -83,20 +68,6 @@ 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; } @@ -109,5 +80,9 @@ namespace ICSharpCode.SharpDevelop.Project public int WarningCount { get { return warningCount; } } + + public int MessageCount { + get { return messageCount; } + } } } diff --git a/src/Main/Base/Project/Src/Project/BuildTarget.cs b/src/Main/Base/Project/Project/Build/BuildTarget.cs similarity index 100% rename from src/Main/Base/Project/Src/Project/BuildTarget.cs rename to src/Main/Base/Project/Project/Build/BuildTarget.cs diff --git a/src/Main/Base/Project/Src/Project/IBuildFeedbackSink.cs b/src/Main/Base/Project/Project/Build/IBuildFeedbackSink.cs similarity index 56% rename from src/Main/Base/Project/Src/Project/IBuildFeedbackSink.cs rename to src/Main/Base/Project/Project/Build/IBuildFeedbackSink.cs index a0d68a345e..2d2c0b45d6 100644 --- a/src/Main/Base/Project/Src/Project/IBuildFeedbackSink.cs +++ b/src/Main/Base/Project/Project/Build/IBuildFeedbackSink.cs @@ -7,19 +7,16 @@ namespace ICSharpCode.SharpDevelop.Project { /// /// Interface for reporting build results in real-time. - /// Project-specific build engines use this interface to report results to the main build engine. /// + /// + /// Implementations of this interface must be thread-safe. + /// Project-specific build engines use this interface to report results to the main build engine, + /// and the main build engine uses this interface to report the combined results to the IDE. + /// public interface IBuildFeedbackSink { /// - /// Gets the progress monitor associated with this build. - /// Does not return null. - /// This member is thread-safe. - /// - Gui.IProgressMonitor ProgressMonitor { get; } - - /// - /// Reports an build error by adding it to the error list. + /// Reports an build error/warning/message by adding it to the error list. /// This member is thread-safe. /// void ReportError(BuildError error); @@ -29,12 +26,5 @@ namespace ICSharpCode.SharpDevelop.Project /// This member is thread-safe. /// void ReportMessage(string message); - - /// - /// Notifies the build engine that the build of a project has finished. - /// You should not call any methods after the Done() call. - /// This member is thread-safe. - /// - void Done(bool success); } } diff --git a/src/Main/Base/Project/Project/Build/IBuildService.cs b/src/Main/Base/Project/Project/Build/IBuildService.cs new file mode 100644 index 0000000000..7b5cef2b35 --- /dev/null +++ b/src/Main/Base/Project/Project/Build/IBuildService.cs @@ -0,0 +1,77 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +using ICSharpCode.Core; +using ICSharpCode.SharpDevelop.Gui; + +namespace ICSharpCode.SharpDevelop.Project +{ + /// + /// The service interface for accessing the build engine. + /// The build engine is responsible for constructing a dependency graph + /// between the s, performing a topological sort + /// and scheduling the actual build. + /// + [SDService] + public interface IBuildService + { + /// + /// Builds the specified projects. + /// If tests are already running, the existing run is cancelled. + /// + /// + /// The build progress will be shown in the SharpDevelop UI; + /// and the build can be cancelled by the user. + /// This method can only be used on the main thread. + /// + Task BuildAsync(IEnumerable projects, BuildOptions options); + + Task BuildAsync(IProject project, BuildOptions options); + Task BuildAsync(Solution solution, BuildOptions options); + + /// + /// Raised when a build is started. + /// + /// This event always occurs on the main thread. + event EventHandler BuildStarted; + + /// + /// Raised when a build is finished. + /// + /// This event always occurs on the main thread. + event EventHandler BuildFinished; + + /// + /// Gets whether a build is currently running. + /// + bool IsBuilding { get; } + + /// + /// Aborts the current build. + /// This method has no effect if no build is running. + /// + void CancelBuild(); + + /// + /// Performs a build in the background (not visible in the UI). + /// + /// The root buildable + /// The build options that should be used + /// The build feedback sink that receives the build output. + /// Progress monitor used to report progress about this build. + /// + /// This method does not set and cannot be cancelled using + /// . + /// Cancellation is possible using the progress monitor's cancellation token, but + /// will not cause an - instead, the build results + /// will use BuildResultCode.Cancelled. + /// It does not raise the / events. + /// This method is thread-safe, and multiple background builds can run concurrently. + /// + Task BuildInBackgroundAsync(IBuildable buildable, BuildOptions options, IBuildFeedbackSink buildFeedbackSink, IProgressMonitor progressMonitor); + } +} diff --git a/src/Main/Base/Project/Project/Build/IBuildable.cs b/src/Main/Base/Project/Project/Build/IBuildable.cs new file mode 100644 index 0000000000..c37a8ac9e9 --- /dev/null +++ b/src/Main/Base/Project/Project/Build/IBuildable.cs @@ -0,0 +1,46 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using ICSharpCode.SharpDevelop.Gui; + +namespace ICSharpCode.SharpDevelop.Project +{ + /// + /// A project or solution. + /// The IBuildable interface members are thread-safe. + /// + public interface IBuildable + { + /// + /// Gets the list of projects on which this project depends. + /// This method is thread-safe. + /// + IEnumerable GetBuildDependencies(ProjectBuildOptions buildOptions); + + /// + /// Starts building the project using the specified options. + /// This member must be implemented thread-safe. + /// + Task BuildAsync(ProjectBuildOptions options, IBuildFeedbackSink feedbackSink, IProgressMonitor progressMonitor); + + /// + /// Gets the name of the buildable item. + /// This property is thread-safe. + /// + string Name { get; } + + /// + /// Creates the project-specific build options. + /// This member must be implemented thread-safe. + /// + /// The global build options. + /// Specifies whether this project is the main buildable item. + /// The root buildable is the buildable for which and apply. + /// The dependencies of that root buildable are the non-root buildables. + /// The project-specific build options. + ProjectBuildOptions CreateProjectBuildOptions(BuildOptions options, bool isRootBuildable); + } +} diff --git a/src/Main/Base/Project/Src/Project/MultipleProjectBuildable.cs b/src/Main/Base/Project/Project/Build/MultipleProjectBuildable.cs similarity index 77% rename from src/Main/Base/Project/Src/Project/MultipleProjectBuildable.cs rename to src/Main/Base/Project/Project/Build/MultipleProjectBuildable.cs index ec732783bd..a797e73af6 100644 --- a/src/Main/Base/Project/Src/Project/MultipleProjectBuildable.cs +++ b/src/Main/Base/Project/Project/Build/MultipleProjectBuildable.cs @@ -4,6 +4,9 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; + +using ICSharpCode.SharpDevelop.Gui; using ICSharpCode.SharpDevelop.Project; namespace ICSharpCode.SharpDevelop.Project @@ -24,19 +27,15 @@ namespace ICSharpCode.SharpDevelop.Project get { return string.Empty; } } - public Solution ParentSolution { - get { return projects.Length > 0 ? projects[0].ParentSolution : null; } - } - - public ICollection GetBuildDependencies(ProjectBuildOptions buildOptions) + public IEnumerable GetBuildDependencies(ProjectBuildOptions buildOptions) { return projects; } - public void StartBuild(ProjectBuildOptions buildOptions, IBuildFeedbackSink feedbackSink) + public Task BuildAsync(ProjectBuildOptions options, IBuildFeedbackSink feedbackSink, IProgressMonitor progressMonitor) { // SharpDevelop already has built our dependencies, so we're done immediately. - feedbackSink.Done(true); + return Task.FromResult(true); } public ProjectBuildOptions CreateProjectBuildOptions(BuildOptions options, bool isRootBuildable) diff --git a/src/Main/Base/Project/Project/Build/ProjectBuildOptions.cs b/src/Main/Base/Project/Project/Build/ProjectBuildOptions.cs new file mode 100644 index 0000000000..bfb47392eb --- /dev/null +++ b/src/Main/Base/Project/Project/Build/ProjectBuildOptions.cs @@ -0,0 +1,47 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; + +namespace ICSharpCode.SharpDevelop.Project +{ + /// + /// Specifies options for building a single project. + /// + public class ProjectBuildOptions + { + BuildTarget target; + IDictionary properties = new SortedList(); + + public BuildTarget Target { + get { return target; } + } + + public IDictionary Properties { + get { return properties; } + } + + public ProjectBuildOptions(BuildTarget target) + { + this.target = target; + } + + /// + /// Specifies the project configuration used for the build. + /// + public string Configuration { get; set; } + + /// + /// Specifies the project platform used for the build. + /// + public string Platform { get; set; } + + /// + /// Gets/Sets the verbosity of build output. + /// + public BuildOutputVerbosity BuildOutputVerbosity { get; set; } + } + + +} diff --git a/src/Main/Base/Project/Services/SD.cs b/src/Main/Base/Project/Services/SD.cs index 765e37f8c4..d58479d2c3 100644 --- a/src/Main/Base/Project/Services/SD.cs +++ b/src/Main/Base/Project/Services/SD.cs @@ -11,6 +11,7 @@ using ICSharpCode.SharpDevelop.Dom; using ICSharpCode.SharpDevelop.Editor; using ICSharpCode.SharpDevelop.Gui; using ICSharpCode.SharpDevelop.Parser; +using ICSharpCode.SharpDevelop.Project; using ICSharpCode.SharpDevelop.WinForms; using ICSharpCode.SharpDevelop.Workbench; @@ -174,5 +175,9 @@ namespace ICSharpCode.SharpDevelop public static IWinFormsService WinForms { get { return GetRequiredService(); } } + + public static IBuildService BuildService { + get { return GetRequiredService(); } + } } } diff --git a/src/Main/Base/Project/Src/Commands/BuildCommands.cs b/src/Main/Base/Project/Src/Commands/BuildCommands.cs index a628c8988d..85d10e3a72 100644 --- a/src/Main/Base/Project/Src/Commands/BuildCommands.cs +++ b/src/Main/Base/Project/Src/Commands/BuildCommands.cs @@ -25,9 +25,7 @@ namespace ICSharpCode.SharpDevelop.Project.Commands public virtual void BeforeBuild() { TaskService.BuildMessageViewCategory.ClearText(); - TaskService.InUpdate = true; TaskService.ClearExceptCommentTasks(); - TaskService.InUpdate = false; ICSharpCode.SharpDevelop.Commands.SaveAllFiles.SaveAll(); } @@ -77,26 +75,6 @@ namespace ICSharpCode.SharpDevelop.Project.Commands } } - public Task BuildAsync(CancellationToken cancellationToken) - { - var registration = cancellationToken.Register(BuildEngine.CancelGuiBuild, true); - var tcs = new TaskCompletionSource(); - this.BuildComplete += delegate { - registration.Dispose(); - if (cancellationToken.IsCancellationRequested) - tcs.TrySetCanceled(); - else - tcs.TrySetResult(this.LastBuildResults); - }; - try { - StartBuild(); - } catch (Exception ex) { - registration.Dispose(); - tcs.TrySetException(ex); - } - return tcs.Task; - } - /// /// Notifies the user that #develp's internal MSBuildEngine /// implementation only supports compiling solutions and projects; @@ -113,9 +91,9 @@ namespace ICSharpCode.SharpDevelop.Project.Commands public class Build : AbstractBuildMenuCommand { - public override void StartBuild() + public override async void StartBuild() { - BuildEngine.BuildInGui(ProjectService.OpenSolution, new BuildOptions(BuildTarget.Build, CallbackMethod)); + CallbackMethod(await SD.BuildService.BuildAsync(ProjectService.OpenSolution, new BuildOptions(BuildTarget.Build))); } } @@ -123,7 +101,7 @@ namespace ICSharpCode.SharpDevelop.Project.Commands { public override void Run() { - if (BuildModifiedProjectsOnlyService.Setting == BuildOnExecuteSetting.DoNotBuild) { + if (BuildOptions.BuildOnExecute == BuildDetection.DoNotBuild) { LastBuildResults = new BuildResults { Result = BuildResultCode.Success }; OnBuildComplete(EventArgs.Empty); } else { @@ -131,22 +109,22 @@ namespace ICSharpCode.SharpDevelop.Project.Commands } } - public override void StartBuild() + public override async void StartBuild() { - BuildEngine.BuildInGui(BuildModifiedProjectsOnlyService.WrapBuildable(ProjectService.OpenSolution), - new BuildOptions(BuildTarget.Build, CallbackMethod)); + var options = new BuildOptions(BuildTarget.Build) { BuildDetection = BuildOptions.BuildOnExecute }; + CallbackMethod(await SD.BuildService.BuildAsync(ProjectService.OpenSolution, options)); } } public class BuildProjectBeforeExecute : BuildProject { - public BuildProjectBeforeExecute(IBuildable project) : base(project) + public BuildProjectBeforeExecute(IProject project) : base(project) { } public override void Run() { - if (BuildModifiedProjectsOnlyService.Setting == BuildOnExecuteSetting.DoNotBuild) { + if (BuildOptions.BuildOnExecute == BuildDetection.DoNotBuild) { LastBuildResults = new BuildResults { Result = BuildResultCode.Success }; OnBuildComplete(EventArgs.Empty); } else { @@ -154,33 +132,33 @@ namespace ICSharpCode.SharpDevelop.Project.Commands } } - public override void StartBuild() + public override async void StartBuild() { - BuildEngine.BuildInGui(BuildModifiedProjectsOnlyService.WrapBuildable(this.ProjectToBuild), - new BuildOptions(BuildTarget.Build, CallbackMethod)); + var options = new BuildOptions(BuildTarget.Build) { BuildDetection = BuildOptions.BuildOnExecute }; + CallbackMethod(await SD.BuildService.BuildAsync(this.ProjectToBuild, options)); } } public class Rebuild : Build { - public override void StartBuild() + public override async void StartBuild() { - BuildEngine.BuildInGui(ProjectService.OpenSolution, new BuildOptions(BuildTarget.Rebuild, CallbackMethod)); + CallbackMethod(await SD.BuildService.BuildAsync(ProjectService.OpenSolution, new BuildOptions(BuildTarget.Rebuild))); } } public class Clean : AbstractBuildMenuCommand { - public override void StartBuild() + public override async void StartBuild() { - BuildEngine.BuildInGui(ProjectService.OpenSolution, new BuildOptions(BuildTarget.Clean, CallbackMethod)); + CallbackMethod(await SD.BuildService.BuildAsync(ProjectService.OpenSolution, new BuildOptions(BuildTarget.Clean))); } } public abstract class AbstractProjectBuildMenuCommand : AbstractBuildMenuCommand { - protected IBuildable targetProject; - protected IBuildable ProjectToBuild { + protected IProject targetProject; + protected IProject ProjectToBuild { get { return targetProject ?? ProjectService.CurrentProject; } @@ -197,33 +175,33 @@ namespace ICSharpCode.SharpDevelop.Project.Commands public BuildProject() { } - public BuildProject(IBuildable targetProject) + public BuildProject(IProject targetProject) { this.targetProject = targetProject; } - public override void StartBuild() + public override async void StartBuild() { - BuildEngine.BuildInGui(this.ProjectToBuild, new BuildOptions(BuildTarget.Build, CallbackMethod)); + CallbackMethod(await SD.BuildService.BuildAsync(this.ProjectToBuild, new BuildOptions(BuildTarget.Build))); } } public class RebuildProject : BuildProject { public RebuildProject() {} - public RebuildProject(IBuildable targetProject) : base(targetProject) {} + public RebuildProject(IProject targetProject) : base(targetProject) {} - public override void StartBuild() + public override async void StartBuild() { - BuildEngine.BuildInGui(this.ProjectToBuild, new BuildOptions(BuildTarget.Rebuild, CallbackMethod)); + CallbackMethod(await SD.BuildService.BuildAsync(this.ProjectToBuild, new BuildOptions(BuildTarget.Rebuild))); } } public class CleanProject : AbstractProjectBuildMenuCommand { - public override void StartBuild() + public override async void StartBuild() { - BuildEngine.BuildInGui(this.ProjectToBuild, new BuildOptions(BuildTarget.Clean, CallbackMethod)); + CallbackMethod(await SD.BuildService.BuildAsync(this.ProjectToBuild, new BuildOptions(BuildTarget.Clean))); } } @@ -231,11 +209,11 @@ namespace ICSharpCode.SharpDevelop.Project.Commands { public override void Run() { - BuildEngine.CancelGuiBuild(); + SD.BuildService.CancelBuild(); } public override bool IsEnabled { - get { return BuildEngine.IsGuiBuildRunning; } + get { return SD.BuildService.IsBuilding; } set { } } } diff --git a/src/Main/Base/Project/Src/Gui/Dialogs/OptionPanels/IDEOptions/ProjectAndSolutionOptions.xaml.cs b/src/Main/Base/Project/Src/Gui/Dialogs/OptionPanels/IDEOptions/ProjectAndSolutionOptions.xaml.cs index 0ecffa1ca2..a932c39de1 100644 --- a/src/Main/Base/Project/Src/Gui/Dialogs/OptionPanels/IDEOptions/ProjectAndSolutionOptions.xaml.cs +++ b/src/Main/Base/Project/Src/Gui/Dialogs/OptionPanels/IDEOptions/ProjectAndSolutionOptions.xaml.cs @@ -17,13 +17,13 @@ namespace ICSharpCode.SharpDevelop.Gui.OptionPanels { InitializeComponent(); - FillComboBoxWithEnumValues(typeof(Project.BuildOnExecuteSetting), onExecuteComboBox); + FillComboBoxWithEnumValues(typeof(Project.BuildDetection), onExecuteComboBox); FillComboBoxWithEnumValues(typeof(Project.BuildOutputVerbosity), verbosityComboBox); } void FillComboBoxWithEnumValues(Type type, ComboBox comboBox) { - foreach (Project.BuildOnExecuteSetting element in Enum.GetValues(type)) { + foreach (Project.BuildDetection element in Enum.GetValues(type)) { object[] attr = type.GetField(Enum.GetName(type, element)).GetCustomAttributes(typeof(DescriptionAttribute), false); string description; if (attr.Length > 0) { @@ -48,7 +48,7 @@ namespace ICSharpCode.SharpDevelop.Gui.OptionPanels { base.LoadOptions(); parallelBuildCount.Value = Project.BuildOptions.DefaultParallelProjectCount; - onExecuteComboBox.SelectedIndex = (int)Project.BuildModifiedProjectsOnlyService.Setting; + onExecuteComboBox.SelectedIndex = (int)Project.BuildOptions.BuildOnExecute; verbosityComboBox.SelectedIndex = (int)Project.BuildOptions.DefaultBuildOutputVerbosity; } @@ -63,7 +63,7 @@ namespace ICSharpCode.SharpDevelop.Gui.OptionPanels } } Project.BuildOptions.DefaultParallelProjectCount = (int)parallelBuildCount.Value; - Project.BuildModifiedProjectsOnlyService.Setting = (Project.BuildOnExecuteSetting)onExecuteComboBox.SelectedIndex; + Project.BuildOptions.BuildOnExecute = (Project.BuildDetection)onExecuteComboBox.SelectedIndex; Project.BuildOptions.DefaultBuildOutputVerbosity = (Project.BuildOutputVerbosity)verbosityComboBox.SelectedIndex; return base.SaveOptions(); } diff --git a/src/Main/Base/Project/Src/Gui/IProgressMonitor.cs b/src/Main/Base/Project/Src/Gui/IProgressMonitor.cs index c7959b6335..21c9a7664a 100644 --- a/src/Main/Base/Project/Src/Gui/IProgressMonitor.cs +++ b/src/Main/Base/Project/Src/Gui/IProgressMonitor.cs @@ -7,7 +7,7 @@ using System.ComponentModel; using System.Diagnostics; using System.Threading; -namespace ICSharpCode.SharpDevelop.Gui +namespace ICSharpCode.SharpDevelop { /// /// Represents a target where an active task reports progress to. diff --git a/src/Main/Base/Project/Src/Gui/WorkbenchSingleton.cs b/src/Main/Base/Project/Src/Gui/WorkbenchSingleton.cs index 6c0013c844..275e377718 100644 --- a/src/Main/Base/Project/Src/Gui/WorkbenchSingleton.cs +++ b/src/Main/Base/Project/Src/Gui/WorkbenchSingleton.cs @@ -74,7 +74,6 @@ namespace ICSharpCode.SharpDevelop.Gui TaskService.Initialize(); Bookmarks.BookmarkManager.Initialize(); Project.CustomToolsService.Initialize(); - Project.BuildModifiedProjectsOnlyService.Initialize(); workbench.Initialize(); workbench.SetMemento(PropertyService.NestedProperties(workbenchMemento)); diff --git a/src/Main/Base/Project/Src/Project/AbstractProject.cs b/src/Main/Base/Project/Src/Project/AbstractProject.cs index 5722e71f5c..8bc16a3ae3 100644 --- a/src/Main/Base/Project/Src/Project/AbstractProject.cs +++ b/src/Main/Base/Project/Src/Project/AbstractProject.cs @@ -10,6 +10,7 @@ using System.IO; using System.Linq; using System.Text; using System.Threading; +using System.Threading.Tasks; using System.Xml.Linq; using ICSharpCode.Core; using ICSharpCode.NRefactory.TypeSystem; @@ -495,14 +496,14 @@ namespace ICSharpCode.SharpDevelop.Project return referenceItems; } - public virtual void StartBuild(ProjectBuildOptions options, IBuildFeedbackSink feedbackSink) + public virtual Task BuildAsync(ProjectBuildOptions options, IBuildFeedbackSink feedbackSink, IProgressMonitor progressMonitor) { feedbackSink.ReportError(new BuildError { ErrorText = "Building project " + Name + " is not supported.", IsWarning = true }); // we don't know how to build anything, report that we're done. - feedbackSink.Done(true); + return Task.FromResult(true); } - public virtual ICollection GetBuildDependencies(ProjectBuildOptions buildOptions) + public virtual IEnumerable GetBuildDependencies(ProjectBuildOptions buildOptions) { lock (SyncRoot) { List result = new List(); @@ -525,8 +526,11 @@ namespace ICSharpCode.SharpDevelop.Project { if (options == null) throw new ArgumentNullException("options"); + string solutionConfiguration = options.SolutionConfiguration ?? ParentSolution.Preferences.ActiveConfiguration; + string solutionPlatform = options.SolutionPlatform ?? ParentSolution.Preferences.ActivePlatform; + // start of default implementation - var configMatchings = this.ParentSolution.GetActiveConfigurationsAndPlatformsForProjects(options.SolutionConfiguration, options.SolutionPlatform); + var configMatchings = this.ParentSolution.GetActiveConfigurationsAndPlatformsForProjects(solutionConfiguration, solutionPlatform); ProjectBuildOptions projectOptions = new ProjectBuildOptions(isRootBuildable ? options.ProjectTarget : options.TargetForDependencies); projectOptions.BuildOutputVerbosity = options.BuildOutputVerbosity; // find the project configuration @@ -538,9 +542,9 @@ namespace ICSharpCode.SharpDevelop.Project } // fall back to solution config if we don't find any entries for the project if (string.IsNullOrEmpty(projectOptions.Configuration)) - projectOptions.Configuration = options.SolutionConfiguration; + projectOptions.Configuration = solutionConfiguration; if (string.IsNullOrEmpty(projectOptions.Platform)) - projectOptions.Platform = options.SolutionPlatform; + projectOptions.Platform = solutionPlatform; // copy global properties to project options foreach (var pair in options.GlobalAdditionalProperties) diff --git a/src/Main/Base/Project/Src/Project/BeforeBuildCustomToolProjectItems.cs b/src/Main/Base/Project/Src/Project/BeforeBuildCustomToolProjectItems.cs index b710b670e0..3a425f14aa 100644 --- a/src/Main/Base/Project/Src/Project/BeforeBuildCustomToolProjectItems.cs +++ b/src/Main/Base/Project/Src/Project/BeforeBuildCustomToolProjectItems.cs @@ -10,11 +10,11 @@ namespace ICSharpCode.SharpDevelop.Project { public class BeforeBuildCustomToolProjectItems { - IBuildable buildable; + IReadOnlyList projects; - public BeforeBuildCustomToolProjectItems(IBuildable buildable) + public BeforeBuildCustomToolProjectItems(IReadOnlyList projects) { - this.buildable = buildable; + this.projects = projects; } public IEnumerable GetProjectItems() @@ -26,17 +26,7 @@ namespace ICSharpCode.SharpDevelop.Project IEnumerable GetProjects() { - IProject project = buildable as IProject; - if (project != null) { - return new IProject[] { project }; - } - - var solution = buildable as Solution; - if (solution != null) { - return solution.Projects; - } - - return new IProject[0]; + return projects; } IEnumerable GetConfiguredCustomToolProjectItems(IProject project) diff --git a/src/Main/Base/Project/Src/Project/BeforeBuildCustomToolRunner.cs b/src/Main/Base/Project/Src/Project/BeforeBuildCustomToolRunner.cs index 3532490e7c..e867d8ff72 100644 --- a/src/Main/Base/Project/Src/Project/BeforeBuildCustomToolRunner.cs +++ b/src/Main/Base/Project/Src/Project/BeforeBuildCustomToolRunner.cs @@ -16,7 +16,7 @@ namespace ICSharpCode.SharpDevelop.Project void ProjectBuildStarted(object sender, BuildEventArgs e) { - var projectItems = new BeforeBuildCustomToolProjectItems(e.Buildable); + var projectItems = new BeforeBuildCustomToolProjectItems(e.Projects); RunCustomTool(projectItems.GetProjectItems()); } diff --git a/src/Main/Base/Project/Src/Project/ConfigurationGuiHelper.cs b/src/Main/Base/Project/Src/Project/ConfigurationGuiHelper.cs index 4c7ac86895..59e9808f85 100644 --- a/src/Main/Base/Project/Src/Project/ConfigurationGuiHelper.cs +++ b/src/Main/Base/Project/Src/Project/ConfigurationGuiHelper.cs @@ -37,6 +37,7 @@ namespace ICSharpCode.SharpDevelop.Project /// /// Class that helps connecting configuration GUI controls to MsBuild properties. /// + [Obsolete] public class ConfigurationGuiHelper : ICanBeDirty { MSBuildBasedProject project; diff --git a/src/Main/Base/Project/Src/Project/IProject.cs b/src/Main/Base/Project/Src/Project/IProject.cs index e7b14f3f78..85d99cdf82 100644 --- a/src/Main/Base/Project/Src/Project/IProject.cs +++ b/src/Main/Base/Project/Src/Project/IProject.cs @@ -330,48 +330,12 @@ namespace ICSharpCode.SharpDevelop.Project /// Never returns null, but may return a permanently empty collection if this project does not support such models. /// ITypeDefinitionModelCollection TypeDefinitionModels { get; } - } - - /// - /// A project or solution. - /// The IBuildable interface members are thread-safe. - /// - public interface IBuildable - { - /// - /// Gets the list of projects on which this project depends. - /// This method is thread-safe. - /// - ICollection GetBuildDependencies(ProjectBuildOptions buildOptions); - - /// - /// Starts building the project using the specified options. - /// This member must be implemented thread-safe. - /// - void StartBuild(ProjectBuildOptions buildOptions, IBuildFeedbackSink feedbackSink); - - /// - /// Gets the name of the buildable item. - /// This property is thread-safe. - /// - string Name { get; } /// /// Gets the parent solution. /// This property is thread-safe. /// Solution ParentSolution { get; } - - /// - /// Creates the project-specific build options. - /// This member must be implemented thread-safe. - /// - /// The global build options. - /// Specifies whether this project is the main buildable item. - /// The root buildable is the buildable for which and apply. - /// The dependencies of that root buildable are the non-root buildables. - /// The project-specific build options. - ProjectBuildOptions CreateProjectBuildOptions(BuildOptions options, bool isRootBuildable); } /// diff --git a/src/Main/Base/Project/Src/Project/MSBuildBasedProject.cs b/src/Main/Base/Project/Src/Project/MSBuildBasedProject.cs index ab65dbf18b..c3f14425e1 100644 --- a/src/Main/Base/Project/Src/Project/MSBuildBasedProject.cs +++ b/src/Main/Base/Project/Src/Project/MSBuildBasedProject.cs @@ -10,6 +10,7 @@ using System.IO; using System.Linq; using System.Text; using System.Threading; +using System.Threading.Tasks; using System.Xml; using System.Xml.Linq; @@ -1101,9 +1102,9 @@ namespace ICSharpCode.SharpDevelop.Project #endregion #region Building - public override ICollection GetBuildDependencies(ProjectBuildOptions buildOptions) + public override IEnumerable GetBuildDependencies(ProjectBuildOptions buildOptions) { - ICollection result = base.GetBuildDependencies(buildOptions); + var result = base.GetBuildDependencies(buildOptions).ToList(); foreach (ProjectItem item in GetItemsOfType(ItemType.ProjectReference)) { ProjectReferenceProjectItem prpi = item as ProjectReferenceProjectItem; if (prpi != null && prpi.ReferencedProject != null) @@ -1112,16 +1113,19 @@ namespace ICSharpCode.SharpDevelop.Project return result; } - public override void StartBuild(ProjectBuildOptions options, IBuildFeedbackSink feedbackSink) + public override Task BuildAsync(ProjectBuildOptions options, IBuildFeedbackSink feedbackSink, IProgressMonitor progressMonitor) { - MSBuildEngine.StartBuild(this, options, feedbackSink, MSBuildEngine.AdditionalTargetFiles); + return MSBuildEngine.BuildAsync(this, options, feedbackSink, progressMonitor.CancellationToken, MSBuildEngine.AdditionalTargetFiles); } public override ProjectBuildOptions CreateProjectBuildOptions(BuildOptions options, bool isRootBuildable) { ProjectBuildOptions projectOptions = base.CreateProjectBuildOptions(options, isRootBuildable); Solution solution = this.ParentSolution; - var configMatchings = solution.GetActiveConfigurationsAndPlatformsForProjects(options.SolutionConfiguration, options.SolutionPlatform); + string solutionConfiguration = options.SolutionConfiguration ?? solution.Preferences.ActiveConfiguration; + string solutionPlatform = options.SolutionPlatform ?? solution.Preferences.ActivePlatform; + + var configMatchings = solution.GetActiveConfigurationsAndPlatformsForProjects(solutionConfiguration, solutionPlatform); // Find the project configuration, and build an XML string containing all configurations from the solution StringWriter solutionConfigurationXml = new StringWriter(); using (XmlTextWriter solutionConfigurationWriter = new XmlTextWriter(solutionConfigurationXml)) { diff --git a/src/Main/Base/Project/Src/Project/MSBuildEngine/BuildWorkerManager.cs b/src/Main/Base/Project/Src/Project/MSBuildEngine/BuildWorkerManager.cs index f68709c5b7..e32c93eb6f 100644 --- a/src/Main/Base/Project/Src/Project/MSBuildEngine/BuildWorkerManager.cs +++ b/src/Main/Base/Project/Src/Project/MSBuildEngine/BuildWorkerManager.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Threading; - +using System.Threading.Tasks; using ICSharpCode.Core; using ICSharpCode.SharpDevelop.BuildWorker; using ICSharpCode.SharpDevelop.Project; diff --git a/src/Main/Base/Project/Src/Project/MSBuildEngine/MSBuildEngine.cs b/src/Main/Base/Project/Src/Project/MSBuildEngine/MSBuildEngine.cs index 88c9b42417..d74b10f692 100755 --- a/src/Main/Base/Project/Src/Project/MSBuildEngine/MSBuildEngine.cs +++ b/src/Main/Base/Project/Src/Project/MSBuildEngine/MSBuildEngine.cs @@ -6,10 +6,12 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; +using System.Threading; +using System.Threading.Tasks; using System.Xml; - using ICSharpCode.Core; using ICSharpCode.SharpDevelop.BuildWorker; +using ICSharpCode.SharpDevelop.Gui; using Microsoft.Build.Framework; namespace ICSharpCode.SharpDevelop.Project @@ -89,7 +91,7 @@ namespace ICSharpCode.SharpDevelop.Project MSBuildLoggerFilters = AddInTree.BuildItems(LoggerFiltersPath, null, false); } - public static void StartBuild(IProject project, ProjectBuildOptions options, IBuildFeedbackSink feedbackSink, IEnumerable additionalTargetFiles) + public static Task BuildAsync(IProject project, ProjectBuildOptions options, IBuildFeedbackSink feedbackSink, CancellationToken cancellationToken, IEnumerable additionalTargetFiles) { if (project == null) throw new ArgumentNullException("project"); @@ -105,7 +107,7 @@ namespace ICSharpCode.SharpDevelop.Project if (project.MinimumSolutionVersion >= Solution.SolutionVersionVS2010) { engine.additionalTargetFiles.Add(Path.Combine(Path.GetDirectoryName(typeof(MSBuildEngine).Assembly.Location), "SharpDevelop.TargetingPack.targets")); } - engine.StartBuild(); + return engine.RunBuildAsync(cancellationToken); } readonly string projectFileName; @@ -192,7 +194,7 @@ namespace ICSharpCode.SharpDevelop.Project List loggers = new List(); IMSBuildChainedLoggerFilter loggerChain; - void StartBuild() + Task RunBuildAsync(CancellationToken cancellationToken) { Dictionary globalProperties = new Dictionary(); MSBuildBasedProject.InitializeMSBuildProjectProperties(globalProperties); @@ -264,24 +266,28 @@ namespace ICSharpCode.SharpDevelop.Project logger.Initialize(eventSource); } + tcs = new TaskCompletionSource(); if (projectMinimumSolutionVersion <= Solution.SolutionVersionVS2008) { if (DotnetDetection.IsDotnet35SP1Installed()) { - BuildWorkerManager.MSBuild35.RunBuildJob(job, loggerChain, OnDone, feedbackSink.ProgressMonitor.CancellationToken); + BuildWorkerManager.MSBuild35.RunBuildJob(job, loggerChain, OnDone, cancellationToken); } else { loggerChain.HandleError(new BuildError(job.ProjectFileName, ".NET 3.5 SP1 is required to build this project.")); - OnDone(false); + tcs.SetResult(false); } } else { - BuildWorkerManager.MSBuild40.RunBuildJob(job, loggerChain, OnDone, feedbackSink.ProgressMonitor.CancellationToken); + BuildWorkerManager.MSBuild40.RunBuildJob(job, loggerChain, OnDone, cancellationToken); } + return tcs.Task; } + TaskCompletionSource tcs; + void OnDone(bool success) { foreach (ILogger logger in loggers) { logger.Shutdown(); } - feedbackSink.Done(success); + tcs.SetResult(success); } void WriteAdditionalTargetsToTempFile(Dictionary globalProperties) diff --git a/src/Main/Base/Project/Src/Project/MSBuildFileProject.cs b/src/Main/Base/Project/Src/Project/MSBuildFileProject.cs index 00df286ba5..8885890d30 100644 --- a/src/Main/Base/Project/Src/Project/MSBuildFileProject.cs +++ b/src/Main/Base/Project/Src/Project/MSBuildFileProject.cs @@ -2,7 +2,9 @@ // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) using System; +using System.Threading.Tasks; using ICSharpCode.Core; +using ICSharpCode.SharpDevelop.Gui; namespace ICSharpCode.SharpDevelop.Project { @@ -18,9 +20,9 @@ namespace ICSharpCode.SharpDevelop.Project TypeGuid = "{00000000-0000-0000-0000-000000000000}"; } - public override void StartBuild(ProjectBuildOptions options, IBuildFeedbackSink feedbackSink) + public override Task BuildAsync(ProjectBuildOptions options, IBuildFeedbackSink feedbackSink, IProgressMonitor progressMonitor) { - MSBuildEngine.StartBuild(this, options, feedbackSink, MSBuildEngine.AdditionalTargetFiles); + return MSBuildEngine.BuildAsync(this, options, feedbackSink, progressMonitor.CancellationToken, MSBuildEngine.AdditionalTargetFiles); } } } diff --git a/src/Main/Base/Project/Src/Project/ProjectLoadInformation.cs b/src/Main/Base/Project/Src/Project/ProjectLoadInformation.cs index 4c6332d939..e4f1de8a1a 100644 --- a/src/Main/Base/Project/Src/Project/ProjectLoadInformation.cs +++ b/src/Main/Base/Project/Src/Project/ProjectLoadInformation.cs @@ -29,13 +29,13 @@ namespace ICSharpCode.SharpDevelop.Project internal bool? upgradeToolsVersion; - Gui.IProgressMonitor progressMonitor = new Gui.DummyProgressMonitor(); + IProgressMonitor progressMonitor = new DummyProgressMonitor(); /// /// Gets/Sets the progress monitor used during the load. /// This property never returns null. /// - public Gui.IProgressMonitor ProgressMonitor { + public IProgressMonitor ProgressMonitor { get { return progressMonitor; } set { if (value == null) diff --git a/src/Main/Base/Project/Src/Project/Solution/Solution.cs b/src/Main/Base/Project/Src/Project/Solution/Solution.cs index 6dbaaeffe8..82b9c5ea23 100644 --- a/src/Main/Base/Project/Src/Project/Solution/Solution.cs +++ b/src/Main/Base/Project/Src/Project/Solution/Solution.cs @@ -8,7 +8,7 @@ using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; - +using System.Threading.Tasks; using ICSharpCode.Core; using ICSharpCode.SharpDevelop.Gui; @@ -1253,18 +1253,15 @@ namespace ICSharpCode.SharpDevelop.Project #endregion #region Building - ICollection IBuildable.GetBuildDependencies(ProjectBuildOptions buildOptions) + IEnumerable IBuildable.GetBuildDependencies(ProjectBuildOptions buildOptions) { - List result = new List(); - foreach (IProject p in this.Projects) - result.Add(p); - return result; + return this.Projects; } - void IBuildable.StartBuild(ProjectBuildOptions buildOptions, IBuildFeedbackSink feedbackSink) + Task IBuildable.BuildAsync(ProjectBuildOptions options, IBuildFeedbackSink feedbackSink, IProgressMonitor progressMonitor) { // building a solution finishes immediately: we only care for the dependencies - feedbackSink.Done(true); + return Task.FromResult(true); } ProjectBuildOptions IBuildable.CreateProjectBuildOptions(BuildOptions options, bool isRootBuildable) diff --git a/src/Main/Base/Project/Src/Services/ProjectService/ProjectService.cs b/src/Main/Base/Project/Src/Services/ProjectService/ProjectService.cs index fca7418659..0aec04c2ac 100644 --- a/src/Main/Base/Project/Src/Services/ProjectService/ProjectService.cs +++ b/src/Main/Base/Project/Src/Services/ProjectService/ProjectService.cs @@ -540,7 +540,7 @@ namespace ICSharpCode.SharpDevelop.Project // If a build is running, cancel it. // If we would let a build run but unload the MSBuild projects, the next project.StartBuild call // could cause an exception. - BuildEngine.CancelGuiBuild(); + SD.BuildService.CancelBuild(); if (openSolution != null) { CurrentProject = null; @@ -610,44 +610,13 @@ namespace ICSharpCode.SharpDevelop.Project } } - static bool building; - + [Obsolete] public static bool IsBuilding { get { - return building; + return SD.BuildService.IsBuilding; } } - /// - /// Raises the event. - /// - /// You do not need to call this method if you use BuildEngine.BuildInGui - the build - /// engine will call these events itself. - /// - public static void RaiseEventBuildStarted(BuildEventArgs e) - { - if (e == null) - throw new ArgumentNullException("e"); - WorkbenchSingleton.AssertMainThread(); - building = true; - BuildStarted.RaiseEvent(null, e); - } - - /// - /// Raises the event. - /// - /// You do not need to call this method if you use BuildEngine.BuildInGui - the build - /// engine will call these events itself. - /// - public static void RaiseEventBuildFinished(BuildEventArgs e) - { - if (e == null) - throw new ArgumentNullException("e"); - WorkbenchSingleton.AssertMainThread(); - building = false; - BuildFinished.RaiseEvent(null, e); - } - public static void RemoveSolutionFolder(string guid) { if (OpenSolution == null) { @@ -740,8 +709,16 @@ namespace ICSharpCode.SharpDevelop.Project /// public static event SolutionFolderEventHandler SolutionFolderRemoved; - public static event EventHandler BuildStarted; - public static event EventHandler BuildFinished; + [Obsolete] + public static event EventHandler BuildStarted { + add { SD.BuildService.BuildStarted += value; } + remove { SD.BuildService.BuildFinished -= value; } + } + [Obsolete] + public static event EventHandler BuildFinished { + add { SD.BuildService.BuildFinished += value; } + remove { SD.BuildService.BuildFinished -= value; } + } public static event SolutionConfigurationEventHandler SolutionConfigurationChanged; @@ -774,39 +751,4 @@ namespace ICSharpCode.SharpDevelop.Project public static event EventHandler ProjectItemAdded; public static event EventHandler ProjectItemRemoved; } - - public class BuildEventArgs : EventArgs - { - /// - /// The project/solution to be built. - /// - public readonly IBuildable Buildable; - - /// - /// The build options. - /// - public readonly BuildOptions Options; - - /// - /// Gets the build results. - /// This property is null for build started events. - /// - public readonly BuildResults Results; - - public BuildEventArgs(IBuildable buildable, BuildOptions options) - : this(buildable, options, null) - { - } - - public BuildEventArgs(IBuildable buildable, BuildOptions options, BuildResults results) - { - if (buildable == null) - throw new ArgumentNullException("buildable"); - if (options == null) - throw new ArgumentNullException("options"); - this.Buildable = buildable; - this.Options = options; - this.Results = results; - } - } } diff --git a/src/Main/Base/Test/Project/BeforeBuildCustomToolProjectItemsTests.cs b/src/Main/Base/Test/Project/BeforeBuildCustomToolProjectItemsTests.cs index 0dc200d491..e7b3f7c108 100644 --- a/src/Main/Base/Test/Project/BeforeBuildCustomToolProjectItemsTests.cs +++ b/src/Main/Base/Test/Project/BeforeBuildCustomToolProjectItemsTests.cs @@ -5,8 +5,9 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; - +using System.Threading.Tasks; using ICSharpCode.Core; +using ICSharpCode.SharpDevelop.Gui; using ICSharpCode.SharpDevelop.Project; using ICSharpCode.SharpDevelop.Tests.Utils; using NUnit.Framework; @@ -21,27 +22,6 @@ namespace ICSharpCode.SharpDevelop.Tests.Project BeforeBuildCustomToolProjectItems beforeBuildCustomToolProjectItems; Solution solution; - class UnknownBuildable : IBuildable - { - public string Name { get; set; } - - public Solution ParentSolution { get; set; } - - public ICollection GetBuildDependencies(ProjectBuildOptions buildOptions) - { - return new IBuildable[0]; - } - - public void StartBuild(ProjectBuildOptions buildOptions, IBuildFeedbackSink feedbackSink) - { - } - - public ProjectBuildOptions CreateProjectBuildOptions(BuildOptions options, bool isRootBuildable) - { - return new ProjectBuildOptions(BuildTarget.Build); - } - } - IProject CreateProject(string fileName = @"d:\MyProject\MyProject.csproj") { projectHelper = new ProjectHelper(fileName); @@ -84,17 +64,17 @@ namespace ICSharpCode.SharpDevelop.Tests.Project void CreateBeforeBuildCustomToolProjectItems() { - CreateBeforeBuildCustomToolProjectItems(projectHelper.Project as IBuildable); + CreateBeforeBuildCustomToolProjectItems(new[] { projectHelper.Project }); } - void CreateBeforeBuildCustomToolProjectItems(IBuildable buildable) + void CreateBeforeBuildCustomToolProjectItems(IReadOnlyList projects) { - beforeBuildCustomToolProjectItems = new BeforeBuildCustomToolProjectItems(buildable); + beforeBuildCustomToolProjectItems = new BeforeBuildCustomToolProjectItems(projects); } void CreateBeforeBuildCustomToolProjectItemsUsingSolution() { - CreateBeforeBuildCustomToolProjectItems(solution as IBuildable); + CreateBeforeBuildCustomToolProjectItems(solution.Projects.ToList()); } FileProjectItem AddFileToProject(string include) @@ -104,12 +84,6 @@ namespace ICSharpCode.SharpDevelop.Tests.Project return projectItem; } - void CreateBeforeBuildCustomToolProjectItemsWithUnknownIBuildableDerivedClass() - { - var unknownBuildable = new UnknownBuildable(); - CreateBeforeBuildCustomToolProjectItems(unknownBuildable); - } - [Test] public void GetProjectItems_BuildSingleProjectNotConfiguredToRunCustomToolsOnBuild_ReturnsNoItems() { @@ -290,15 +264,5 @@ namespace ICSharpCode.SharpDevelop.Tests.Project }; CollectionAssert.AreEqual(expectedProjectItems, projectItems); } - - [Test] - public void GetProjectItems_UnknownIBuildableDerivedClass_NullReferenceExceptionIsNotThrown() - { - CreateBeforeBuildCustomToolProjectItemsWithUnknownIBuildableDerivedClass(); - - List projectItems = GetProjectItems(); - - Assert.AreEqual(0, projectItems.Count); - } } } diff --git a/src/Main/Core/Project/Src/Services/PropertyService/PropertyServiceImpl.cs b/src/Main/Core/Project/Src/Services/PropertyService/PropertyServiceImpl.cs index ff7bf0ddb5..2a241be050 100644 --- a/src/Main/Core/Project/Src/Services/PropertyService/PropertyServiceImpl.cs +++ b/src/Main/Core/Project/Src/Services/PropertyService/PropertyServiceImpl.cs @@ -35,6 +35,7 @@ namespace ICSharpCode.Core this.configDirectory = configDirectory; this.dataDirectory = dataDirectory; this.propertyFileName = propertiesName + ".xml"; + Directory.CreateDirectory(configDirectory); LoadPropertiesFromStream(Path.Combine(configDirectory, propertyFileName)); } diff --git a/src/Main/Base/Project/Src/Project/BuildEngine.cs b/src/Main/SharpDevelop/Project/Build/BuildEngine.cs similarity index 91% rename from src/Main/Base/Project/Src/Project/BuildEngine.cs rename to src/Main/SharpDevelop/Project/Build/BuildEngine.cs index 041bb39566..6b974904dc 100644 --- a/src/Main/Base/Project/Src/Project/BuildEngine.cs +++ b/src/Main/SharpDevelop/Project/Build/BuildEngine.cs @@ -8,6 +8,7 @@ using System.IO; using System.Linq; using System.Text; using System.Threading; +using System.Threading.Tasks; using ICSharpCode.Core; using ICSharpCode.SharpDevelop.Gui; @@ -20,10 +21,10 @@ namespace ICSharpCode.SharpDevelop.Project /// /// This class is not related to MSBuild: it simply "builds" a project by calling IBuildable.StartBuild. /// - public sealed class BuildEngine + sealed class BuildEngine { #region Building in the SharpDevelop GUI - static CancellationTokenSource guiBuildCancellation; + /*static CancellationTokenSource guiBuildCancellation; static IAnalyticsMonitorTrackedFeature guiBuildTrackedFeature; /// @@ -151,7 +152,7 @@ namespace ICSharpCode.SharpDevelop.Project ProjectService.RaiseEventBuildFinished(new BuildEventArgs(buildable, options, results)); }); } - } + }*/ #endregion #region StartBuild @@ -165,26 +166,17 @@ namespace ICSharpCode.SharpDevelop.Project /// will ensure that output from two projects building in parallel isn't interleaved. /// The progress monitor that receives build progress. The monitor will be disposed /// when the build completes. - public static void StartBuild(IBuildable project, BuildOptions options, IBuildFeedbackSink realtimeBuildFeedbackSink) + public static Task BuildAsync(IBuildable project, BuildOptions options, IBuildFeedbackSink buildFeedbackSink, IProgressMonitor progressMonitor) { if (project == null) throw new ArgumentNullException("solution"); if (options == null) throw new ArgumentNullException("options"); - Solution solution = project.ParentSolution; - if (solution == null) - throw new ArgumentException("project.ParentSolution must not be null", "project"); - - if (string.IsNullOrEmpty(options.SolutionConfiguration)) - options.SolutionConfiguration = solution.Preferences.ActiveConfiguration; - if (string.IsNullOrEmpty(options.SolutionPlatform)) - options.SolutionPlatform = solution.Preferences.ActivePlatform; - BuildEngine engine = new BuildEngine(options, project); engine.buildStart = DateTime.Now; - engine.combinedBuildFeedbackSink = realtimeBuildFeedbackSink; - engine.progressMonitor = realtimeBuildFeedbackSink.ProgressMonitor; + engine.combinedBuildFeedbackSink = buildFeedbackSink; + engine.progressMonitor = progressMonitor; try { engine.rootNode = engine.CreateBuildGraph(project); } catch (CyclicDependencyException ex) { @@ -201,7 +193,7 @@ namespace ICSharpCode.SharpDevelop.Project engine.results.Result = BuildResultCode.BuildFileError; engine.ReportDone(); - return; + return engine.tcs.Task; } engine.workersToStart = options.ParallelProjectCount; @@ -213,6 +205,7 @@ namespace ICSharpCode.SharpDevelop.Project engine.ReportMessageLine("${res:MainWindow.CompilerMessages.BuildStarted}"); engine.StartBuildProjects(); engine.UpdateProgressTaskName(); + return engine.tcs.Task; } #endregion @@ -280,12 +273,13 @@ namespace ICSharpCode.SharpDevelop.Project this.project = project; } - public void DoStartBuild(object state) + public async void DoStartBuild(object state) { string name = string.Empty; try { name = project.Name; - project.StartBuild(options, this); + bool success = await project.BuildAsync(options, this, perNodeProgressMonitor).ConfigureAwait(false); + Done(success); } catch (ObjectDisposedException) { // Handle ObjectDisposedException that occurs when trying to build a project that was unloaded. ReportError(new BuildError(null, "The project '" + name + "' was unloaded.")); @@ -313,15 +307,6 @@ namespace ICSharpCode.SharpDevelop.Project { engine.OnBuildFinished(this, success); } - - IProgressMonitor IBuildFeedbackSink.ProgressMonitor { - get { - // property should be accessed only while build is running and progress monitor available - if (perNodeProgressMonitor == null) - throw new InvalidOperationException(); - return perNodeProgressMonitor; - } - } } #endregion @@ -330,6 +315,7 @@ namespace ICSharpCode.SharpDevelop.Project readonly BuildOptions options; IProgressMonitor progressMonitor; CancellationTokenRegistration cancellationRegistration; + readonly TaskCompletionSource tcs = new TaskCompletionSource(); BuildNode rootNode; readonly IBuildable rootProject; readonly BuildResults results = new BuildResults(); @@ -500,7 +486,6 @@ namespace ICSharpCode.SharpDevelop.Project node.hasErrors = !success; projectsCurrentlyBuilding.Remove(node); - results.AddBuiltProject(node.project); foreach (BuildNode n in node.dependentOnThis) { n.outstandingDependencies--; @@ -546,7 +531,6 @@ namespace ICSharpCode.SharpDevelop.Project ReportMessageLine("${res:MainWindow.CompilerMessages.BuildFinished}" + buildTime); } cancellationRegistration.Dispose(); - progressMonitor.Dispose(); ReportDone(); } @@ -555,20 +539,7 @@ namespace ICSharpCode.SharpDevelop.Project /// void ReportDone() { - if (combinedBuildFeedbackSink != null) { - if (combinedBuildFeedbackSink is MessageViewSink) { - // Special case GUI-builds so that they have more information available: - // (non-GUI builds can get the same information from the options.Callback, - // but the GUI cannot use the callback because the build options are set by - // the code triggering the build) - ((MessageViewSink)combinedBuildFeedbackSink).Done(rootProject, options, results); - } else { - combinedBuildFeedbackSink.Done(results.Result == BuildResultCode.Success); - } - } - if (options.Callback != null) { - Gui.WorkbenchSingleton.SafeThreadAsyncCall(delegate { options.Callback(results); }); - } + tcs.SetResult(results); } #endregion diff --git a/src/Main/Base/Project/Src/Services/ProjectService/CompileModifiedProjectsOnly.cs b/src/Main/SharpDevelop/Project/Build/BuildModifiedProjectsOnlyService.cs similarity index 59% rename from src/Main/Base/Project/Src/Services/ProjectService/CompileModifiedProjectsOnly.cs rename to src/Main/SharpDevelop/Project/Build/BuildModifiedProjectsOnlyService.cs index ed5b8f1a7b..7ae9a71342 100644 --- a/src/Main/Base/Project/Src/Services/ProjectService/CompileModifiedProjectsOnly.cs +++ b/src/Main/SharpDevelop/Project/Build/BuildModifiedProjectsOnlyService.cs @@ -3,65 +3,43 @@ using System; using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; using System.Linq; +using System.Threading.Tasks; + using ICSharpCode.Core; -using System.Diagnostics; -using System.ComponentModel; +using ICSharpCode.SharpDevelop.Gui; 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 - } - /// /// Tracks changes to projects and causes only modified projects /// to be recompiled. /// - static class BuildModifiedProjectsOnlyService + class BuildModifiedProjectsOnlyService { - public static BuildOnExecuteSetting Setting { - get { return PropertyService.Get("BuildOnExecute", BuildOnExecuteSetting.RegularBuild); } - set { PropertyService.Set("BuildOnExecute", value); } - } - static readonly Dictionary unmodifiedProjects = new Dictionary(); - static BuildModifiedProjectsOnlyService() + public BuildModifiedProjectsOnlyService(IBuildService buildService) { // these actions cause a full recompilation: ProjectService.SolutionClosed += MarkAllForRecompilation; ProjectService.SolutionConfigurationChanged += MarkAllForRecompilation; ProjectService.SolutionSaved += MarkAllForRecompilation; - ProjectService.BuildFinished += ProjectService_BuildFinished; + buildService.BuildFinished += BuildService_BuildFinished; FileUtility.FileSaved += OnFileSaved; } - public static void Initialize() - { - // first call to init causes static ctor calls - } - - static void ProjectService_BuildFinished(object sender, BuildEventArgs e) + void BuildService_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; - } + foreach (IProject p in e.Projects) { + unmodifiedProjects[p] = pass; } } } @@ -73,21 +51,14 @@ namespace ICSharpCode.SharpDevelop.Project } } - static IProject GetProjectFromBuildable(IBuildable b) - { - while (b is Wrapper) - b = ((Wrapper)b).wrapped; - return b as IProject; - } - - static void MarkAllForRecompilation(object sender, EventArgs e) + void MarkAllForRecompilation(object sender, EventArgs e) { lock (unmodifiedProjects) { unmodifiedProjects.Clear(); } } - static void OnFileSaved(object sender, FileNameEventArgs e) + void OnFileSaved(object sender, FileNameEventArgs e) { if (ProjectService.OpenSolution != null) { foreach (IProject p in ProjectService.OpenSolution.Projects) { @@ -100,20 +71,20 @@ namespace ICSharpCode.SharpDevelop.Project } } - public static IBuildable WrapBuildable(IBuildable buildable) + public IBuildable WrapBuildable(IBuildable buildable, BuildDetection setting) { - switch (Setting) { - case BuildOnExecuteSetting.DoNotBuild: + switch (setting) { + case BuildDetection.DoNotBuild: return new DummyBuildable(buildable); - case BuildOnExecuteSetting.BuildModifiedAndDependent: - case BuildOnExecuteSetting.BuildOnlyModified: + case BuildDetection.BuildModifiedAndDependent: + case BuildDetection.BuildOnlyModified: lock (unmodifiedProjects) { foreach (var pair in unmodifiedProjects) { LoggingService.Debug(pair.Key.Name + ": " + pair.Value); } } - return new WrapperFactory().GetWrapper(buildable); - case BuildOnExecuteSetting.RegularBuild: + return new WrapperFactory(setting).GetWrapper(buildable); + case BuildDetection.RegularBuild: return buildable; default: throw new NotSupportedException(); @@ -133,22 +104,19 @@ namespace ICSharpCode.SharpDevelop.Project get { return wrappedBuildable.Name; } } - public Solution ParentSolution { - get { return wrappedBuildable.ParentSolution; } - } - public ProjectBuildOptions CreateProjectBuildOptions(BuildOptions options, bool isRootBuildable) { return null; } - public ICollection GetBuildDependencies(ProjectBuildOptions buildOptions) + public IEnumerable GetBuildDependencies(ProjectBuildOptions buildOptions) { - return new IBuildable[0]; + return Enumerable.Empty(); } - public void StartBuild(ProjectBuildOptions buildOptions, IBuildFeedbackSink feedbackSink) + public Task BuildAsync(ProjectBuildOptions options, IBuildFeedbackSink feedbackSink, IProgressMonitor progressMonitor) { + return Task.FromResult(true); } } @@ -171,9 +139,15 @@ namespace ICSharpCode.SharpDevelop.Project sealed class WrapperFactory { + public readonly BuildDetection Setting; public readonly CompilationPass CurrentPass = new CompilationPass(); readonly Dictionary dict = new Dictionary(); + public WrapperFactory(BuildDetection setting) + { + this.Setting = setting; + } + public IBuildable GetWrapper(IBuildable wrapped) { IBuildable b; @@ -200,10 +174,6 @@ namespace ICSharpCode.SharpDevelop.Project get { return wrapped.Name; } } - public Solution ParentSolution { - get { return wrapped.ParentSolution; } - } - public ProjectBuildOptions CreateProjectBuildOptions(BuildOptions options, bool isRootBuildable) { return wrapped.CreateProjectBuildOptions(options, isRootBuildable); @@ -211,8 +181,8 @@ namespace ICSharpCode.SharpDevelop.Project Dictionary> cachedBuildDependencies = new Dictionary>(); ICollection cachedBuildDependenciesForNullOptions; - - public ICollection GetBuildDependencies(ProjectBuildOptions buildOptions) + + public IEnumerable GetBuildDependencies(ProjectBuildOptions buildOptions) { List result = new List(); foreach (IBuildable b in wrapped.GetBuildDependencies(buildOptions)) { @@ -241,20 +211,20 @@ namespace ICSharpCode.SharpDevelop.Project return lastCompilationPass.Index > comparisonPass.Index; } - public void StartBuild(ProjectBuildOptions buildOptions, IBuildFeedbackSink feedbackSink) + public async Task BuildAsync(ProjectBuildOptions options, IBuildFeedbackSink feedbackSink, IProgressMonitor progressMonitor) { IProject p = wrapped as IProject; if (p == null) { - wrapped.StartBuild(buildOptions, feedbackSink); + return await wrapped.BuildAsync(options, feedbackSink, progressMonitor); } else { lock (unmodifiedProjects) { if (!unmodifiedProjects.TryGetValue(p, out lastCompilationPass)) { lastCompilationPass = null; } } - if (lastCompilationPass != null && Setting == BuildOnExecuteSetting.BuildModifiedAndDependent) { + if (lastCompilationPass != null && factory.Setting == BuildDetection.BuildModifiedAndDependent) { lock (cachedBuildDependencies) { - var dependencies = buildOptions != null ? cachedBuildDependencies[buildOptions] : cachedBuildDependenciesForNullOptions; + var dependencies = options != null ? cachedBuildDependencies[options] : cachedBuildDependenciesForNullOptions; if (dependencies.OfType().Any(w=>w.WasRecompiledAfter(lastCompilationPass))) { lastCompilationPass = null; } @@ -265,56 +235,17 @@ namespace ICSharpCode.SharpDevelop.Project StringParser.Parse("${res:MainWindow.CompilerMessages.SkipProjectNoChanges}", new StringTagPair("Name", p.Name)) ); - feedbackSink.Done(true); + return true; } else { lastCompilationPass = factory.CurrentPass; - wrapped.StartBuild(buildOptions, new BuildFeedbackSink(p, feedbackSink, factory.CurrentPass)); - } - } - } - - /// - /// 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; - 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; + var success = await wrapped.BuildAsync(options, feedbackSink, progressMonitor); + if (success) { + lock (unmodifiedProjects) { + unmodifiedProjects[p] = factory.CurrentPass; + } } + return success; } - sink.Done(success); } } } diff --git a/src/Main/SharpDevelop/Project/Build/BuildService.cs b/src/Main/SharpDevelop/Project/Build/BuildService.cs new file mode 100644 index 0000000000..6478cda4fc --- /dev/null +++ b/src/Main/SharpDevelop/Project/Build/BuildService.cs @@ -0,0 +1,117 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +using ICSharpCode.Core; + +namespace ICSharpCode.SharpDevelop.Project +{ + sealed class BuildService : IBuildService + { + readonly BuildModifiedProjectsOnlyService buildModifiedProjectsOnly; + + public BuildService() + { + this.buildModifiedProjectsOnly = new BuildModifiedProjectsOnlyService(this); + } + + public event EventHandler BuildStarted; + public event EventHandler BuildFinished; + + CancellationTokenSource guiBuildCancellation; + + public bool IsBuilding { + get { return guiBuildCancellation != null; } + } + + public void CancelBuild() + { + if (guiBuildCancellation != null) + guiBuildCancellation.Cancel(); + } + + public async Task BuildAsync(IEnumerable projects, BuildOptions options) + { + if (projects == null) + throw new ArgumentNullException("projects"); + if (options == null) + throw new ArgumentNullException("options"); + SD.MainThread.VerifyAccess(); + if (guiBuildCancellation != null) { + BuildResults results = new BuildResults(); + SD.StatusBar.SetMessage(ResourceService.GetString("MainWindow.CompilerMessages.MSBuildAlreadyRunning")); + BuildError error = new BuildError(null, ResourceService.GetString("MainWindow.CompilerMessages.MSBuildAlreadyRunning")); + results.Add(error); + TaskService.Add(new SDTask(error)); + results.Result = BuildResultCode.MSBuildAlreadyRunning; + return results; + } + var projectsList = projects.ToList(); + guiBuildCancellation = new CancellationTokenSource(); + using (var progressMonitor = SD.StatusBar.CreateProgressMonitor(guiBuildCancellation.Token)) { + if (BuildStarted != null) + BuildStarted(this, new BuildEventArgs(projectsList, options)); + + var trackedFeature = SD.AnalyticsMonitor.TrackFeature("ICSharpCode.SharpDevelop.Project.BuildEngine.Build"); + SD.StatusBar.SetMessage(StringParser.Parse("${res:MainWindow.CompilerMessages.BuildVerb}...")); + IBuildable buildable; + if (projectsList.Count == 1) + buildable = projectsList[0]; + else + buildable = new MultipleProjectBuildable(projectsList); + + buildable = buildModifiedProjectsOnly.WrapBuildable(buildable, options.BuildDetection); + + var sink = new UIBuildFeedbackSink(TaskService.BuildMessageViewCategory, SD.StatusBar); + var results = await BuildInBackgroundAsync(buildable, options, sink, progressMonitor); + + string message; + if (results.Result == BuildResultCode.Cancelled) { + message = "${res:MainWindow.CompilerMessages.BuildCancelled}"; + } else { + if (results.Result == BuildResultCode.Success) + message = "${res:MainWindow.CompilerMessages.BuildFinished}"; + else + message = "${res:MainWindow.CompilerMessages.BuildFailed}"; + + if (results.ErrorCount > 0) + message += " " + results.ErrorCount + " error(s)"; + if (results.WarningCount > 0) + message += " " + results.WarningCount + " warning(s)"; + } + SD.StatusBar.SetMessage(message); + trackedFeature.EndTracking(); + if (BuildFinished != null) + BuildFinished(this, new BuildEventArgs(projectsList, options, results)); + + return results; + } + } + + public Task BuildAsync(IProject project, BuildOptions options) + { + if (project != null) + return BuildAsync(new[] { project }, options); + else + return Task.FromResult(new BuildResults { Result = BuildResultCode.Error }); + } + + public Task BuildAsync(Solution solution, BuildOptions options) + { + if (solution != null) + return BuildAsync(solution.Projects, options); + else + return Task.FromResult(new BuildResults { Result = BuildResultCode.Error }); + } + + public Task BuildInBackgroundAsync(IBuildable buildable, BuildOptions options, IBuildFeedbackSink buildFeedbackSink, IProgressMonitor progressMonitor) + { + return BuildEngine.BuildAsync(buildable, options, buildFeedbackSink, progressMonitor); + } + } +} diff --git a/src/Main/SharpDevelop/Project/Build/UIBuildFeedbackSink.cs b/src/Main/SharpDevelop/Project/Build/UIBuildFeedbackSink.cs new file mode 100644 index 0000000000..d0a2808f57 --- /dev/null +++ b/src/Main/SharpDevelop/Project/Build/UIBuildFeedbackSink.cs @@ -0,0 +1,46 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) +// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +using ICSharpCode.Core; +using ICSharpCode.SharpDevelop.Gui; + +namespace ICSharpCode.SharpDevelop.Project +{ + /// + /// This error message sink is used for GUI builds. + /// + sealed class UIBuildFeedbackSink : IBuildFeedbackSink + { + MessageViewCategory messageView; + IStatusBarService statusBarService; + + public UIBuildFeedbackSink(MessageViewCategory messageView, IStatusBarService statusBarService) + { + Debug.Assert(messageView != null); + Debug.Assert(statusBarService != null); + + this.messageView = messageView; + this.statusBarService = statusBarService; + } + + public void ReportError(BuildError error) + { + WorkbenchSingleton.SafeThreadAsyncCall( + delegate { + TaskService.Add(new SDTask(error)); + }); + } + + public void ReportMessage(string message) + { + messageView.AppendLine(message); + } + } +} diff --git a/src/Main/SharpDevelop/SharpDevelop.csproj b/src/Main/SharpDevelop/SharpDevelop.csproj index 66bfd4b9da..bbd32a9c51 100644 --- a/src/Main/SharpDevelop/SharpDevelop.csproj +++ b/src/Main/SharpDevelop/SharpDevelop.csproj @@ -93,6 +93,10 @@ + + + + @@ -216,6 +220,8 @@ + +