Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/monodevelop.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/main
diff options
context:
space:
mode:
authorLluis Sanchez <llsan@microsoft.com>2018-07-19 12:01:33 +0300
committerGitHub <noreply@github.com>2018-07-19 12:01:33 +0300
commit827c074a2ee451386a8ddad7ad438f1ac096e21a (patch)
tree5470d29fb53f1373a56c3b778cbe532b5afe9e61 /main
parent493af9dad5e7d641a645768948b5b44acd500432 (diff)
parenta2e1a827c3c9cde75e0107fabf82edbd51958bc7 (diff)
Merge pull request #5408 from mono/fast-build-check-for-test
Fast build check for test
Diffstat (limited to 'main')
-rw-r--r--main/src/addins/MonoDevelop.UnitTesting/Services/UnitTestService.cs36
-rw-r--r--main/src/core/MonoDevelop.Core/MonoDevelop.Projects/Solution.cs365
-rw-r--r--main/src/core/MonoDevelop.Core/MonoDevelop.Projects/SolutionFolder.cs444
-rw-r--r--main/src/core/MonoDevelop.Core/MonoDevelop.Projects/SolutionItem.cs38
-rw-r--r--main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.csproj1
-rw-r--r--main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/ProjectOperations.SolutionItemBuildBatch.cs133
-rw-r--r--main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/ProjectOperations.cs368
-rw-r--r--main/tests/MonoDevelop.Core.Tests/MonoDevelop.Projects/ProjectBuildTests.cs34
8 files changed, 806 insertions, 613 deletions
diff --git a/main/src/addins/MonoDevelop.UnitTesting/Services/UnitTestService.cs b/main/src/addins/MonoDevelop.UnitTesting/Services/UnitTestService.cs
index 865e531c30..8ebf9a13c3 100644
--- a/main/src/addins/MonoDevelop.UnitTesting/Services/UnitTestService.cs
+++ b/main/src/addins/MonoDevelop.UnitTesting/Services/UnitTestService.cs
@@ -105,7 +105,7 @@ namespace MonoDevelop.UnitTesting
public static AsyncOperation RunTest (UnitTest test, MonoDevelop.Projects.ExecutionContext context)
{
- var result = RunTest (test, context, IdeApp.Preferences.BuildBeforeRunningTests);
+ var result = RunTest (test, context, true);
result.Task.ContinueWith (t => OnTestSessionCompleted (), TaskScheduler.FromCurrentSynchronizationContext ());
return result;
}
@@ -126,32 +126,26 @@ namespace MonoDevelop.UnitTesting
if (buildOwnerObject) {
var build_targets = new HashSet<IBuildTarget> ();
foreach (var t in tests) {
- IBuildTarget bt = t.OwnerObject as IBuildTarget;
- if (bt != null)
+ if (t.OwnerObject is IBuildTarget bt)
build_targets.Add (bt);
}
- if (build_targets.Count > 0) {
- if (!IdeApp.ProjectOperations.CurrentRunOperation.IsCompleted) {
- MonoDevelop.Ide.Commands.StopHandler.StopBuildOperations ();
- await IdeApp.ProjectOperations.CurrentRunOperation.Task;
- }
- foreach (var bt in build_targets) {
- var res = await IdeApp.ProjectOperations.Build (bt, cs.Token).Task;
- if (res.HasErrors)
- return;
- }
+ var res = await IdeApp.ProjectOperations.CheckAndBuildForExecute (
+ build_targets, IdeApp.Workspace.ActiveConfiguration, IdeApp.Preferences.BuildBeforeRunningTests,
+ false, null, cs.Token);
+
+ if (!res)
+ return;
- var test_names = new HashSet<string> (tests.Select ((v) => v.FullName));
+ var test_names = new HashSet<string> (tests.Select ((v) => v.FullName));
- await RefreshTests (cs.Token);
+ await RefreshTests (cs.Token);
- tests = test_names.Select ((fullName) => SearchTest (fullName)).Where ((t) => t != null).ToList ();
+ tests = test_names.Select ((fullName) => SearchTest (fullName)).Where ((t) => t != null).ToList ();
- if (tests.Any ())
- await RunTests (tests, context, false, checkCurrentRunOperation, cs);
- return;
- }
+ if (tests.Any ())
+ await RunTests (tests, context, false, checkCurrentRunOperation, cs);
+ return;
}
if (checkCurrentRunOperation && !IdeApp.ProjectOperations.ConfirmExecutionOperation ())
@@ -176,7 +170,7 @@ namespace MonoDevelop.UnitTesting
public static AsyncOperation RunTests (IEnumerable<UnitTest> tests, MonoDevelop.Projects.ExecutionContext context)
{
- var result = RunTests (tests, context, IdeApp.Preferences.BuildBeforeRunningTests);
+ var result = RunTests (tests, context);
result.Task.ContinueWith (t => OnTestSessionCompleted (), TaskScheduler.FromCurrentSynchronizationContext ());
return result;
}
diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/Solution.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/Solution.cs
index 6fcc011e57..1086aac0c2 100644
--- a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/Solution.cs
+++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/Solution.cs
@@ -317,10 +317,9 @@ namespace MonoDevelop.Projects
void CollectItemProperties (PropertyBag props, SolutionFolderItem item, string path)
{
if (!item.UserProperties.IsEmpty && item.ParentFolder != null)
- props.SetValue (path, item.UserProperties);
-
- SolutionFolder sf = item as SolutionFolder;
- if (sf != null) {
+ props.SetValue (path, item.UserProperties);
+
+ if (item is SolutionFolder sf) {
foreach (SolutionFolderItem ci in sf.Items)
CollectItemProperties (props, ci, path + "." + ci.Name);
}
@@ -328,10 +327,9 @@ namespace MonoDevelop.Projects
void CleanItemProperties (PropertyBag props, SolutionFolderItem item, string path)
{
- props.RemoveValue (path);
-
- SolutionFolder sf = item as SolutionFolder;
- if (sf != null) {
+ props.RemoveValue (path);
+
+ if (item is SolutionFolder sf) {
foreach (SolutionFolderItem ci in sf.Items)
CleanItemProperties (props, ci, path + "." + ci.Name);
}
@@ -343,10 +341,9 @@ namespace MonoDevelop.Projects
if (info != null) {
item.LoadUserProperties (info);
props.RemoveValue (path);
- }
-
- SolutionFolder sf = item as SolutionFolder;
- if (sf != null) {
+ }
+
+ if (item is SolutionFolder sf) {
foreach (SolutionFolderItem ci in sf.Items)
LoadItemProperties (props, ci, path + "." + ci.Name);
}
@@ -373,7 +370,7 @@ namespace MonoDevelop.Projects
public SolutionConfiguration AddConfiguration (string id, bool createConfigForItems)
{
- SolutionConfiguration conf = new SolutionConfiguration (id);
+ var conf = new SolutionConfiguration (id);
foreach (SolutionItem item in Items.Where (it => it.SupportsBuild())) {
if (createConfigForItems && item.GetConfiguration (new ItemConfigurationSelector (id)) == null) {
SolutionItemConfiguration newc = item.CreateConfiguration (id);
@@ -389,7 +386,7 @@ namespace MonoDevelop.Projects
public override ReadOnlyCollection<string> GetConfigurations ()
{
- List<string> configs = new List<string> ();
+ var configs = new List<string> ();
foreach (SolutionConfiguration conf in Configurations)
configs.Add (conf.Id);
return configs.AsReadOnly ();
@@ -445,12 +442,13 @@ namespace MonoDevelop.Projects
public ReadOnlyCollection<T> GetAllSolutionItemsWithTopologicalSort<T> (ConfigurationSelector configuration) where T: SolutionItem
{
- return RootFolder.GetAllItemsWithTopologicalSort<T> (configuration);
+ var list = new List<T> (GetAllItems<T> ());
+ return SolutionItem.TopologicalSort (list, configuration);
}
public ReadOnlyCollection<Project> GetAllProjectsWithTopologicalSort (ConfigurationSelector configuration)
{
- return RootFolder.GetAllProjectsWithTopologicalSort (configuration);
+ return GetAllSolutionItemsWithTopologicalSort<Project> (configuration);
}
public override IEnumerable<Project> GetProjectsContainingFile (FilePath fileName)
@@ -857,16 +855,14 @@ namespace MonoDevelop.Projects
/*protected virtual*/ bool OnGetCanExecute(ExecutionContext context, ConfigurationSelector configuration, SolutionRunConfiguration runConfiguration)
{
- var ssc = runConfiguration as SingleItemSolutionRunConfiguration;
- if (ssc != null)
+ if (runConfiguration is SingleItemSolutionRunConfiguration ssc)
return ssc.Item.CanExecute (context, configuration, ssc.RunConfiguration);
- var msc = runConfiguration as MultiItemSolutionRunConfiguration;
- if (msc != null) {
+ if (runConfiguration is MultiItemSolutionRunConfiguration msc) {
var multiProject = context.ExecutionTarget as MultiProjectExecutionTarget;
foreach (StartupItem it in msc.Items) {
- var localContext = context;
- //Set project specific execution target to context if exists
+ var localContext = context;
+ //Set project specific execution target to context if exists
if (multiProject?.GetTarget (it.SolutionItem) != null)
localContext = new ExecutionContext (context.ExecutionHandler, context.ExternalConsoleFactory, multiProject?.GetTarget (it.SolutionItem));
if (it.SolutionItem.CanExecute (localContext, configuration, it.RunConfiguration))
@@ -879,26 +875,24 @@ namespace MonoDevelop.Projects
/*protected virtual*/ async Task OnExecute (ProgressMonitor monitor, ExecutionContext context, ConfigurationSelector configuration, SolutionRunConfiguration runConfiguration)
{
- var ssc = runConfiguration as SingleItemSolutionRunConfiguration;
- if (ssc != null) {
+ if (runConfiguration is SingleItemSolutionRunConfiguration ssc) {
await ssc.Item.Execute (monitor, context, configuration, ssc.RunConfiguration);
return;
}
- var msc = runConfiguration as MultiItemSolutionRunConfiguration;
- if (msc != null) {
+ if (runConfiguration is MultiItemSolutionRunConfiguration msc) {
var tasks = new List<Task> ();
var monitors = new List<AggregatedProgressMonitor> ();
monitor.BeginTask ("Executing projects", 1);
var multiProject = context.ExecutionTarget as MultiProjectExecutionTarget;
foreach (StartupItem it in msc.Items) {
- var localContext = context;
- //Set project specific execution target to context if exists
+ var localContext = context;
+ //Set project specific execution target to context if exists
if (multiProject?.GetTarget (it.SolutionItem) != null)
localContext = new ExecutionContext (context.ExecutionHandler, context.ConsoleFactory, multiProject?.GetTarget (it.SolutionItem));
if (!it.SolutionItem.CanExecute (localContext, configuration, it.RunConfiguration))
continue;
- AggregatedProgressMonitor mon = new AggregatedProgressMonitor ();
+ var mon = new AggregatedProgressMonitor ();
mon.AddFollowerMonitor (monitor, MonitorAction.ReportError | MonitorAction.ReportWarning | MonitorAction.FollowerCancel);
monitors.Add (mon);
tasks.Add (it.SolutionItem.Execute (mon, localContext, configuration, it.RunConfiguration));
@@ -922,17 +916,213 @@ namespace MonoDevelop.Projects
}
/*protected virtual*/ void OnStartupItemChanged(EventArgs e)
- {
- if (StartupItemChanged != null)
- StartupItemChanged (this, e);
+ {
+ StartupItemChanged?.Invoke (this, e);
}
void OnStartupConfigurationChanged (EventArgs e)
+ {
+ StartupConfigurationChanged?.Invoke (this, e);
+ }
+
+ /// <summary>
+ /// Builds a set of SolutionItems from this solution and their dependencies. They will be built in parallel, and common dependencies will be deduplicated.
+ /// </summary>
+ public async Task<BuildResult> CleanItems (ProgressMonitor monitor, ConfigurationSelector configuration, IEnumerable<SolutionItem> items, OperationContext operationContext = null, string beginTaskMessage = null)
+ {
+ SolutionConfiguration slnConf = GetConfiguration (configuration);
+ if (slnConf == null)
+ return new BuildResult ();
+
+ ReadOnlyCollection<SolutionItem> sortedItems;
+ try {
+ sortedItems = GetItemsAndDependenciesSortedForBuild (items, configuration);
+ } catch (CyclicDependencyException) {
+ monitor.ReportError (GettextCatalog.GetString ("Cyclic dependencies are not supported."), null);
+ return new BuildResult ("", 1, 1);
+ }
+
+ if (operationContext == null)
+ operationContext = new OperationContext ();
+
+ monitor.BeginTask (
+ beginTaskMessage ?? GettextCatalog.GetString ("Cleaning {0} items in solution {1} ({2})", sortedItems.Count, Name, configuration.ToString ()),
+ sortedItems.Count
+ );
+
+ bool operationStarted = false;
+ BuildResult result = null;
+
+ try {
+ operationStarted = await BeginBuildOperation (monitor, configuration, operationContext);
+
+ return result = await RunParallelBuildOperation (monitor, configuration, sortedItems, (ProgressMonitor m, SolutionItem item) => {
+ return item.Clean (m, configuration, operationContext);
+ }, false);
+ } finally {
+ if (operationStarted)
+ await EndBuildOperation (monitor, configuration, operationContext, result);
+ monitor.EndTask ();
+ }
+ }
+
+ /// <summary>
+ /// Builds a set of SolutionItems from this solution and their dependencies. They will be built in parallel, and common dependencies will be deduplicated.
+ /// </summary>
+ public async Task<BuildResult> BuildItems (ProgressMonitor monitor, ConfigurationSelector configuration, IEnumerable<SolutionItem> items, OperationContext operationContext = null, string beginTaskMessage = null)
{
- if (StartupConfigurationChanged != null)
- StartupConfigurationChanged (this, e);
+ SolutionConfiguration slnConf = GetConfiguration (configuration);
+ if (slnConf == null)
+ return new BuildResult ();
+
+ ReadOnlyCollection<SolutionItem> sortedItems;
+
+ try {
+ sortedItems = GetItemsAndDependenciesSortedForBuild (items, configuration);
+ } catch (CyclicDependencyException) {
+ monitor.ReportError (GettextCatalog.GetString ("Cyclic dependencies are not supported."), null);
+ return new BuildResult ("", 1, 1);
+ }
+
+ if (operationContext == null)
+ operationContext = new OperationContext ();
+
+ bool operationStarted = false;
+ BuildResult result = null;
+
+ try {
+
+ if (Runtime.Preferences.SkipBuildingUnmodifiedProjects)
+ sortedItems = sortedItems.Where (si => {
+ if (si is Project p)
+ return p.FastCheckNeedsBuild (configuration);
+ return true;//Don't filter things that don't have FastCheckNeedsBuild
+ }).ToList ().AsReadOnly ();
+
+ monitor.BeginTask (
+ beginTaskMessage ?? GettextCatalog.GetString ("Building {0} items in solution {1} ({2})", sortedItems.Count, Name, configuration.ToString ()),
+ sortedItems.Count
+ );
+
+ operationStarted = await BeginBuildOperation (monitor, configuration, operationContext);
+
+ return result = await RunParallelBuildOperation (monitor, configuration, sortedItems, (ProgressMonitor m, SolutionItem item) => {
+ return item.Build (m, configuration, false, operationContext);
+ }, false);
+
+ } finally {
+ if (operationStarted)
+ await EndBuildOperation (monitor, configuration, operationContext, result);
+ monitor.EndTask ();
+ }
+ }
+
+ static async Task<BuildResult> RunParallelBuildOperation (ProgressMonitor monitor, ConfigurationSelector configuration, IEnumerable<SolutionItem> sortedItems, Func<ProgressMonitor, SolutionItem, Task<BuildResult>> buildAction, bool ignoreFailed)
+ {
+ var toBuild = new List<SolutionItem> (sortedItems);
+ var cres = new BuildResult { BuildCount = 0 };
+
+ // Limit the number of concurrent builders to processors / 2
+
+ var slotScheduler = new TaskSlotScheduler (Environment.ProcessorCount / 2);
+
+ // Create a dictionary with the status objects of all items
+
+ var buildStatus = new Dictionary<SolutionItem, BuildStatus> ();
+ foreach (var it in toBuild)
+ buildStatus.Add (it, new BuildStatus ());
+
+ // Start the build tasks for all itemsw
+
+ foreach (var itemToBuild in toBuild) {
+ if (monitor.CancellationToken.IsCancellationRequested)
+ break;
+
+ var item = itemToBuild;
+
+ var myStatus = buildStatus[item];
+
+ var myMonitor = monitor.BeginAsyncStep (1);
+
+ // Get a list of the status objects for all items on which this one depends
+
+ var refStatus = item.GetReferencedItems (configuration).Select (it => {
+ buildStatus.TryGetValue (it, out var bs);
+ return bs;
+ }).Where (t => t != null).ToArray ();
+
+ // Build the item when all its dependencies have been built
+
+ var refTasks = refStatus.Select (bs => bs.Task);
+
+ myStatus.Task = Task.WhenAll (refTasks).ContinueWith (async t => {
+ if (!ignoreFailed && (refStatus.Any (bs => bs.Failed) || t.IsFaulted)) {
+ myStatus.Failed = true;
+ } else {
+ using (await slotScheduler.GetTaskSlot ())
+ myStatus.Result = await buildAction (myMonitor, item);
+ myStatus.Failed = myStatus.Result != null && myStatus.Result.ErrorCount > 0;
+ }
+ myMonitor.Dispose ();
+ }, Runtime.MainTaskScheduler).Unwrap ();
+
+ if (!Runtime.Preferences.ParallelBuild.Value)
+ await myStatus.Task;
+ }
+
+ // Wait for all tasks to end
+
+ await Task.WhenAll (buildStatus.Values.Select (bs => bs.Task));
+
+ // Generate the errors in the order they were supposed to build
+
+ foreach (var it in toBuild) {
+ if (buildStatus.TryGetValue (it, out var bs) && bs.Result != null)
+ cres.Append (bs.Result);
+ }
+
+ return cres;
}
+ class BuildStatus
+ {
+ public bool Failed;
+ public Task Task;
+ public BuildResult Result;
+ }
+
+ /// <summary>
+ /// Given a set of SolutionItems from this solution, collects them and their buildable dependencies, and toplogically sorts them in preparation for a build.
+ /// </summary>
+ ReadOnlyCollection<SolutionItem> GetItemsAndDependenciesSortedForBuild (IEnumerable<SolutionItem> items, ConfigurationSelector configuration)
+ {
+ var slnConf = GetConfiguration (configuration);
+ var collected = new HashSet<SolutionItem> ();
+
+ foreach (var item in items) {
+ if (item.ParentSolution != this) {
+ throw new ArgumentException ("All items must be in this solution", nameof(items));
+ }
+ if (slnConf.BuildEnabledForItem (item) && collected.Add (item)) {
+ CollectBuildableDependencies (collected, item, configuration, slnConf);
+ }
+ }
+
+ return SolutionItem.TopologicalSort (collected, configuration);
+ }
+
+ /// <summary>
+ /// Recursively collects buildable dependencies.
+ /// </summary>
+ internal static void CollectBuildableDependencies (HashSet<SolutionItem> collected, SolutionItem item, ConfigurationSelector configuration, SolutionConfiguration conf)
+ {
+ foreach (var it in item.GetReferencedItems (configuration)) {
+ if (collected.Contains (it) || !conf.BuildEnabledForItem (it))
+ continue;
+ collected.Add (it);
+ CollectBuildableDependencies (collected, it, configuration, conf);
+ }
+ }
[ThreadSafe]
public MSBuildFileFormat FileFormat {
@@ -988,37 +1178,33 @@ namespace MonoDevelop.Projects
solutionItems = null;
- SolutionFolder sf = args.SolutionItem as SolutionFolder;
- if (sf != null) {
+ if (args.SolutionItem is SolutionFolder sf) {
foreach (SolutionFolderItem eitem in sf.GetAllItems<SolutionFolderItem> ())
SetupNewItem (eitem, null);
- }
- else {
+ } else {
SetupNewItem (args.SolutionItem, args.ReplacedItem);
}
- OnRootDirectoriesChanged ();
-
- if (SolutionItemAdded != null)
- SolutionItemAdded (this, args);
+ OnRootDirectoriesChanged ();
+
+ SolutionItemAdded?.Invoke (this, args);
}
void SetupNewItem (SolutionFolderItem item, SolutionFolderItem replacedItem)
{
- SolutionItem eitem = item as SolutionItem;
- if (eitem != null) {
+ if (item is SolutionItem eitem) {
eitem.ConvertToFormat (FileFormat);
eitem.NeedsReload = false;
if (eitem.SupportsConfigurations () || replacedItem != null) {
- if (replacedItem == null) {
- // Register the new entry in every solution configuration
+ if (replacedItem == null) {
+ // Register the new entry in every solution configuration
foreach (SolutionConfiguration conf in Configurations)
- conf.AddItem (eitem);
- // If there is no startup project or it is an invalid one, use the new project as startup if possible
+ conf.AddItem (eitem);
+ // If there is no startup project or it is an invalid one, use the new project as startup if possible
if (!Loading && (StartupItem == null || !StartupItem.SupportsExecute ()) && eitem.SupportsExecute ())
StartupItem = eitem;
- } else {
- // Reuse the configuration information of the replaced item
+ } else {
+ // Reuse the configuration information of the replaced item
foreach (SolutionConfiguration conf in Configurations)
conf.ReplaceItem ((SolutionItem)replacedItem, eitem);
@@ -1034,23 +1220,19 @@ namespace MonoDevelop.Projects
internal /*protected virtual*/ void OnSolutionItemRemoved (SolutionItemChangeEventArgs args)
{
- solutionItems = null;
-
- SolutionFolder sf = args.SolutionItem as SolutionFolder;
- if (sf != null) {
+ solutionItems = null;
+
+ if (args.SolutionItem is SolutionFolder sf) {
foreach (SolutionItem eitem in sf.GetAllItems<SolutionItem> ())
DetachItem (eitem, args.Reloading);
- }
- else {
- SolutionItem item = args.SolutionItem as SolutionItem;
- if (item != null)
+ } else {
+ if (args.SolutionItem is SolutionItem item)
DetachItem (item, args.Reloading);
}
- OnRootDirectoriesChanged ();
-
- if (SolutionItemRemoved != null)
- SolutionItemRemoved (this, args);
+ OnRootDirectoriesChanged ();
+
+ SolutionItemRemoved?.Invoke (this, args);
}
void DetachItem (SolutionItem item, bool reloading)
@@ -1082,7 +1264,7 @@ namespace MonoDevelop.Projects
if (project == projectToRemove)
continue;
- List<ProjectReference> toDelete = new List<ProjectReference> ();
+ var toDelete = new List<ProjectReference> ();
foreach (ProjectReference pref in project.References) {
if (pref.ReferenceType == ReferenceType.Project && pref.Reference == projectToRemove.Name)
@@ -1100,7 +1282,7 @@ namespace MonoDevelop.Projects
internal void ReadSolution (ProgressMonitor monitor)
{
var sln = new SlnFile ();
- sln.Read (this.FileName);
+ sln.Read (FileName);
using (currentLoadContext = new SolutionLoadContext (this))
SolutionExtension.OnReadSolution (monitor, sln);
@@ -1176,63 +1358,53 @@ namespace MonoDevelop.Projects
}
internal /*protected virtual*/ void OnFileAddedToProject (ProjectFileEventArgs args)
- {
- if (FileAddedToProject != null)
- FileAddedToProject (this, args);
+ {
+ FileAddedToProject?.Invoke (this, args);
}
internal /*protected virtual*/ void OnFileRemovedFromProject (ProjectFileEventArgs args)
- {
- if (FileRemovedFromProject != null)
- FileRemovedFromProject (this, args);
+ {
+ FileRemovedFromProject?.Invoke (this, args);
}
internal /*protected virtual*/ void OnFileChangedInProject (ProjectFileEventArgs args)
- {
- if (FileChangedInProject != null)
- FileChangedInProject (this, args);
+ {
+ FileChangedInProject?.Invoke (this, args);
}
internal /*protected virtual*/ void OnFilePropertyChangedInProject (ProjectFileEventArgs args)
- {
- if (FilePropertyChangedInProject != null)
- FilePropertyChangedInProject (this, args);
+ {
+ FilePropertyChangedInProject?.Invoke (this, args);
}
internal /*protected virtual*/ void OnFileRenamedInProject (ProjectFileRenamedEventArgs args)
- {
- if (FileRenamedInProject != null)
- FileRenamedInProject (this, args);
+ {
+ FileRenamedInProject?.Invoke (this, args);
}
internal /*protected virtual*/ void OnReferenceAddedToProject (ProjectReferenceEventArgs args)
- {
- if (ReferenceAddedToProject != null)
- ReferenceAddedToProject (this, args);
+ {
+ ReferenceAddedToProject?.Invoke (this, args);
}
internal /*protected virtual*/ void OnReferenceRemovedFromProject (ProjectReferenceEventArgs args)
- {
- if (ReferenceRemovedFromProject != null)
- ReferenceRemovedFromProject (this, args);
+ {
+ ReferenceRemovedFromProject?.Invoke (this, args);
}
internal /*protected virtual*/ void OnEntryModified (SolutionItemModifiedEventArgs args)
- {
- if (EntryModified != null)
- EntryModified (this, args);
+ {
+ EntryModified?.Invoke (this, args);
}
internal /*protected virtual*/ void OnEntrySaved (SolutionItemSavedEventArgs args)
- {
- if (EntrySaved != null)
- EntrySaved (this, args);
+ {
+ EntrySaved?.Invoke (this, args);
}
internal /*protected virtual*/ void OnItemReloadRequired (SolutionItemEventArgs args)
- {
- if (ItemReloadRequired != null)
- ItemReloadRequired (this, args);
+ {
+ ItemReloadRequired?.Invoke (this, args);
}
#endregion
@@ -1399,9 +1571,8 @@ namespace MonoDevelop.Projects
public Solution Solution { get; private set; }
void IDisposable.Dispose ()
- {
- if (LoadCompleted != null)
- LoadCompleted (this, EventArgs.Empty);
+ {
+ LoadCompleted?.Invoke (this, EventArgs.Empty);
}
}
}
diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/SolutionFolder.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/SolutionFolder.cs
index 6b94381310..70c29a0808 100644
--- a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/SolutionFolder.cs
+++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/SolutionFolder.cs
@@ -140,15 +140,15 @@ namespace MonoDevelop.Projects
foreach (SolutionFolderItem it in Items) {
FilePath subdir;
- if (it is SolutionFolder) {
- SolutionFolder sf = (SolutionFolder) it;
+ if (it is SolutionFolder sf) {
if (sf.HasCustomBaseDirectory)
subdir = sf.BaseDirectory;
else
subdir = sf.GetCommonPathRoot ();
- } else
- subdir = it.BaseDirectory;
-
+ } else {
+ subdir = it.BaseDirectory;
+ }
+
if (subdir.IsNullOrEmpty)
return FilePath.Null;
@@ -211,17 +211,15 @@ namespace MonoDevelop.Projects
if (Items.IndexOf (sitem) == -1)
throw new InvalidOperationException ("Solution item '" + sitem.Name + "' does not belong to folder '" + Name + "'");
- SolutionItem item = sitem as SolutionItem;
- if (item != null) {
- // Load the new item
-
+ if (sitem is SolutionItem item) {
+ // Load the new item
+
SolutionItem newItem;
try {
if (ParentSolution.IsSolutionItemEnabled (item.FileName)) {
using (var ctx = new SolutionLoadContext (ParentSolution))
newItem = await Services.ProjectService.ReadSolutionItem (monitor, item.FileName, null, ctx: ctx, itemGuid: item.ItemId);
- }
- else {
+ } else {
UnknownSolutionItem e = new UnloadedSolutionItem () {
FileName = item.FileName
};
@@ -230,37 +228,37 @@ namespace MonoDevelop.Projects
newItem = e;
}
} catch (Exception ex) {
- UnknownSolutionItem e = new UnknownSolutionItem ();
- e.LoadError = ex.Message;
- e.FileName = item.FileName;
- newItem = e;
+ newItem = new UnknownSolutionItem {
+ LoadError = ex.Message,
+ FileName = item.FileName
+ };
}
- if (!Items.Contains (item)) {
- // The old item is gone, which probably means it has already been reloaded (BXC20615), or maybe removed.
- // In this case, there isn't anything else we can do
- newItem.Dispose ();
-
- // Find the replacement if it exists
+ if (!Items.Contains (item)) {
+ // The old item is gone, which probably means it has already been reloaded (BXC20615), or maybe removed.
+ // In this case, there isn't anything else we can do
+ newItem.Dispose ();
+
+ // Find the replacement if it exists
return Items.OfType<SolutionItem> ().FirstOrDefault (it => it.FileName == item.FileName);
- }
-
- // Replace in the file list
+ }
+
+ // Replace in the file list
Items.Replace (item, newItem);
item.ParentFolder = null;
DisconnectChildEntryEvents (item);
- ConnectChildEntryEvents (newItem);
-
+ ConnectChildEntryEvents (newItem);
+
NotifyModified ("Items");
- OnItemRemoved (new SolutionItemChangeEventArgs (item, ParentSolution, true) { ReplacedItem = item } , true);
- OnItemAdded (new SolutionItemChangeEventArgs (newItem, ParentSolution, true) { ReplacedItem = item }, true);
-
+ OnItemRemoved (new SolutionItemChangeEventArgs (item, ParentSolution, true) { ReplacedItem = item }, true);
+ OnItemAdded (new SolutionItemChangeEventArgs (newItem, ParentSolution, true) { ReplacedItem = item }, true);
+
item.Dispose ();
return newItem;
- }
- else
- return sitem;
+ }
+
+ return sitem;
}
internal void NotifyItemAdded (SolutionFolderItem item, bool newToSolution)
@@ -274,7 +272,7 @@ namespace MonoDevelop.Projects
void ConnectChildEntryEvents (SolutionFolderItem item)
{
if (item is Project) {
- Project project = item as Project;
+ var project = item as Project;
project.FileRemovedFromProject += NotifyFileRemovedFromProject;
project.FileAddedToProject += NotifyFileAddedToProject;
project.FileChangedInProject += NotifyFileChangedInProject;
@@ -287,7 +285,7 @@ namespace MonoDevelop.Projects
}
if (item is SolutionFolder) {
- SolutionFolder folder = item as SolutionFolder;
+ var folder = item as SolutionFolder;
folder.FileRemovedFromProject += NotifyFileRemovedFromProject;
folder.FileAddedToProject += NotifyFileAddedToProject;
folder.FileChangedInProject += NotifyFileChangedInProject;
@@ -327,11 +325,10 @@ namespace MonoDevelop.Projects
public void AddItem (SolutionFolderItem item, bool createSolutionConfigurations)
{
- Items.Add (item);
-
- SolutionItem eitem = item as SolutionItem;
- if (eitem != null && createSolutionConfigurations && eitem.SupportsBuild ()) {
- // Create new solution configurations for item configurations
+ Items.Add (item);
+
+ if (item is SolutionItem eitem && createSolutionConfigurations && eitem.SupportsBuild ()) {
+ // Create new solution configurations for item configurations
foreach (ItemConfiguration iconf in eitem.Configurations) {
bool found = false;
foreach (SolutionConfiguration conf in ParentSolution.Configurations) {
@@ -341,8 +338,8 @@ namespace MonoDevelop.Projects
}
}
if (!found) {
- SolutionConfiguration sconf = new SolutionConfiguration (iconf.Id);
- // Add all items to the new configuration
+ var sconf = new SolutionConfiguration (iconf.Id);
+ // Add all items to the new configuration
foreach (var it in ParentSolution.GetAllItems<SolutionItem> ())
sconf.AddItem (it);
ParentSolution.Configurations.Add (sconf);
@@ -361,7 +358,7 @@ namespace MonoDevelop.Projects
void DisconnectChildEntryEvents (SolutionFolderItem entry)
{
if (entry is Project) {
- Project pce = entry as Project;
+ var pce = entry as Project;
pce.FileRemovedFromProject -= NotifyFileRemovedFromProject;
pce.FileAddedToProject -= NotifyFileAddedToProject;
pce.FileChangedInProject -= NotifyFileChangedInProject;
@@ -374,7 +371,7 @@ namespace MonoDevelop.Projects
}
if (entry is SolutionFolder) {
- SolutionFolder cce = entry as SolutionFolder;
+ var cce = entry as SolutionFolder;
cce.FileRemovedFromProject -= NotifyFileRemovedFromProject;
cce.FileAddedToProject -= NotifyFileAddedToProject;
cce.FileChangedInProject -= NotifyFileChangedInProject;
@@ -421,27 +418,29 @@ namespace MonoDevelop.Projects
return GetAllItems<SolutionFolderItem> ();
}
+ [Obsolete("This method will be removed in future releases")]
public ReadOnlyCollection<T> GetAllItemsWithTopologicalSort<T> (ConfigurationSelector configuration) where T: SolutionItem
{
- List<T> list = new List<T> ();
+ var list = new List<T> ();
GetAllItems<T> (list, this);
return SolutionItem.TopologicalSort<T> (list, configuration);
}
public ReadOnlyCollection<Project> GetAllProjects ()
{
- List<Project> list = new List<Project> ();
- GetAllItems<Project> (list, this);
+ var list = new List<Project> ();
+ GetAllItems (list, this);
return list.AsReadOnly ();
}
// The projects are returned in the order
// they should be compiled, acording to their references.
+ [Obsolete("This method will be removed in future releases")]
public ReadOnlyCollection<Project> GetAllProjectsWithTopologicalSort (ConfigurationSelector configuration)
{
- List<Project> list = new List<Project> ();
- GetAllItems<Project> (list, this);
- return SolutionItem.TopologicalSort<Project> (list, configuration);
+ var list = new List<Project> ();
+ GetAllItems (list, this);
+ return SolutionItem.TopologicalSort (list, configuration);
}
void GetAllItems<T> (List<T> list, SolutionFolderItem item) where T: SolutionFolderItem
@@ -450,54 +449,49 @@ namespace MonoDevelop.Projects
list.Add ((T)item);
}
- if (item is SolutionFolder) {
- foreach (SolutionFolderItem ce in ((SolutionFolder)item).Items)
- GetAllItems<T> (list, ce);
+ if (item is SolutionFolder sf) {
+ foreach (SolutionFolderItem ce in (sf).Items)
+ GetAllItems (list, ce);
}
}
+ [Obsolete("This method will be removed in future releases")]
public ReadOnlyCollection<SolutionItem> GetAllBuildableEntries (ConfigurationSelector configuration, bool topologicalSort, bool includeExternalReferences)
{
- var list = new List<SolutionItem> ();
- GetAllBuildableEntries (list, configuration, includeExternalReferences);
+ var list = new List<SolutionItem> ();
+ if (ParentSolution != null)
+ return list.AsReadOnly ();
+
+ SolutionConfiguration conf = ParentSolution.GetConfiguration (configuration);
+ if (conf == null)
+ return list.AsReadOnly ();
+
+ var collected = new HashSet<SolutionItem> ();
+ CollectBuildableEntries (collected, configuration, conf, includeExternalReferences);
+ list.AddRange (collected);
+
if (topologicalSort)
- return SolutionItem.TopologicalSort<SolutionItem> (list, configuration);
+ return SolutionItem.TopologicalSort (list, configuration);
else
return list.AsReadOnly ();
- }
-
+ }
+
+ [Obsolete("This method will be removed in future releases")]
public ReadOnlyCollection<SolutionItem> GetAllBuildableEntries (ConfigurationSelector configuration)
{
return GetAllBuildableEntries (configuration, false, false);
}
-
- void GetAllBuildableEntries (List<SolutionItem> list, ConfigurationSelector configuration, bool includeExternalReferences)
- {
- if (ParentSolution == null)
- return;
- SolutionConfiguration conf = ParentSolution.GetConfiguration (configuration);
- if (conf == null)
- return;
+ void CollectBuildableEntries (HashSet<SolutionItem> collected, ConfigurationSelector configuration, SolutionConfiguration slnConf, bool includeDependencies)
+ {
foreach (SolutionFolderItem item in Items) {
- if (item is SolutionFolder)
- ((SolutionFolder)item).GetAllBuildableEntries (list, configuration, includeExternalReferences);
- else if ((item is SolutionItem) && conf.BuildEnabledForItem ((SolutionItem) item) && ((SolutionItem)item).SupportsBuild ())
- GetAllBuildableReferences (list, (SolutionItem)item, configuration, conf, includeExternalReferences, false);
- }
- }
-
- void GetAllBuildableReferences (List<SolutionItem> list, SolutionItem item, ConfigurationSelector configuration, SolutionConfiguration conf, bool includeExternalReferences, bool isDirectReference)
- {
- if (list.Contains (item) || !conf.BuildEnabledForItem (item))
- return;
- // Skip unsupported projects which are not directly referenced by other (supported) projects
- if (!isDirectReference && item.IsUnsupportedProject)
- return;
- list.Add (item);
- if (includeExternalReferences) {
- foreach (var it in item.GetReferencedItems (configuration))
- GetAllBuildableReferences (list, it, configuration, conf, includeExternalReferences, true);
+ if (item is SolutionFolder sf)
+ sf.CollectBuildableEntries (collected, configuration, slnConf, includeDependencies);
+ else if (item is SolutionItem si && slnConf.BuildEnabledForItem (si) && si.SupportsBuild () && collected.Add (si)) {
+ if (includeDependencies) {
+ Solution.CollectBuildableDependencies (collected, si, configuration, slnConf);
+ }
+ }
}
}
@@ -542,15 +536,14 @@ namespace MonoDevelop.Projects
{
string path = Path.GetFullPath (fileName);
foreach (SolutionFolderItem it in Items) {
- if (it is SolutionFolder) {
- SolutionItem r = ((SolutionFolder)it).FindSolutionItem (fileName);
+ if (it is SolutionFolder sf) {
+ SolutionItem r = sf.FindSolutionItem (fileName);
if (r != null)
return r;
}
- else if (it is SolutionItem) {
- SolutionItem se = (SolutionItem) it;
+ else if (it is SolutionItem se) {
if (!string.IsNullOrEmpty (se.FileName) && path == Path.GetFullPath (se.FileName))
- return (SolutionItem) it;
+ return (SolutionItem)it;
}
}
return null;
@@ -577,161 +570,39 @@ namespace MonoDevelop.Projects
return true;
}
- public async Task<BuildResult> Clean (ProgressMonitor monitor, ConfigurationSelector configuration, OperationContext operationContext = null)
+ public Task<BuildResult> Clean (ProgressMonitor monitor, ConfigurationSelector configuration, OperationContext operationContext = null)
{
- if (ParentSolution == null)
- return new BuildResult();
- SolutionConfiguration conf = ParentSolution.GetConfiguration (configuration);
- if (conf == null)
- return new BuildResult();
-
- if (operationContext == null)
- operationContext = new OperationContext ();
-
- ReadOnlyCollection<SolutionItem> allProjects;
- try {
- allProjects = GetAllBuildableEntries (configuration, true, true);
- } catch (CyclicDependencyException) {
- monitor.ReportError (GettextCatalog.GetString ("Cyclic dependencies are not supported."), null);
- return new BuildResult ("", 1, 1);
- }
-
- monitor.BeginTask (GettextCatalog.GetString ("Cleaning Solution: {0} ({1})", Name, configuration.ToString ()), allProjects.Count);
-
- bool operationStarted = false;
- BuildResult result = null;
-
- try {
- operationStarted = ParentSolution != null && await ParentSolution.BeginBuildOperation (monitor, configuration, operationContext);
-
- return result = await RunParallelBuildOperation (monitor, configuration, allProjects, (ProgressMonitor m, SolutionItem item) => {
- return item.Clean (m, configuration, operationContext);
- }, false);
- }
- finally {
- if (operationStarted)
- await ParentSolution.EndBuildOperation (monitor, configuration, operationContext, result);
- monitor.EndTask ();
- }
- }
+ var slnConf = ParentSolution?.GetConfiguration (configuration);
+ if (slnConf == null)
+ return Task.FromResult (new BuildResult ());
+
+ //don't collect dependencies, CleanItems will do it
+ var collected = new HashSet<SolutionItem> ();
+ CollectBuildableEntries (collected, configuration, slnConf, false);
- class BuildStatus
- {
- public bool Failed;
- public Task Task;
- public BuildResult Result;
+ return ParentSolution.CleanItems (
+ monitor, configuration, collected, operationContext,
+ IsRoot ? GettextCatalog.GetString ("Cleaning solution {0} ({1})", Name, configuration.ToString ()) : null
+ );
}
- public async Task<BuildResult> Build (ProgressMonitor monitor, ConfigurationSelector configuration, bool buildReferencedTargets = false, OperationContext operationContext = null)
+ public Task<BuildResult> Build (ProgressMonitor monitor, ConfigurationSelector configuration, bool buildReferencedTargets = false, OperationContext operationContext = null)
{
- ReadOnlyCollection<SolutionItem> allProjects;
-
- try {
- allProjects = GetAllBuildableEntries (configuration, true, true);
- } catch (CyclicDependencyException) {
- monitor.ReportError (GettextCatalog.GetString ("Cyclic dependencies are not supported."), null);
- return new BuildResult ("", 1, 1);
- }
-
- if (operationContext == null)
- operationContext = new OperationContext ();
-
- bool operationStarted = false;
- BuildResult result = null;
-
- try {
-
- if (Runtime.Preferences.SkipBuildingUnmodifiedProjects)
- allProjects = allProjects.Where (si => {
- if (si is Project p)
- return p.FastCheckNeedsBuild (configuration);
- return true;//Don't filter things that don't have FastCheckNeedsBuild
- }).ToList ().AsReadOnly ();
- monitor.BeginTask (GettextCatalog.GetString ("Building Solution: {0} ({1})", Name, configuration.ToString ()), allProjects.Count);
+ var slnConf = ParentSolution?.GetConfiguration (configuration);
+ if (slnConf == null)
+ return Task.FromResult (new BuildResult ());
- operationStarted = ParentSolution != null && await ParentSolution.BeginBuildOperation (monitor, configuration, operationContext);
+ //don't collect dependencies, BuildItems will do it
+ var collected = new HashSet<SolutionItem> ();
+ CollectBuildableEntries (collected, configuration, slnConf, false);
- return result = await RunParallelBuildOperation (monitor, configuration, allProjects, (ProgressMonitor m, SolutionItem item) => {
- return item.Build (m, configuration, false, operationContext);
- }, false);
- } finally {
- if (operationStarted)
- await ParentSolution.EndBuildOperation (monitor, configuration, operationContext, result);
- monitor.EndTask ();
- }
+ return ParentSolution.BuildItems (
+ monitor, configuration, collected, operationContext,
+ IsRoot ? GettextCatalog.GetString ("Building solution {0} ({1})", Name, configuration.ToString ()) : null
+ );
}
- internal static async Task<BuildResult> RunParallelBuildOperation (ProgressMonitor monitor, ConfigurationSelector configuration, IEnumerable<SolutionItem> sortedItems, Func<ProgressMonitor,SolutionItem,Task<BuildResult>> buildAction, bool ignoreFailed)
- {
- List<SolutionItem> toBuild = new List<SolutionItem> (sortedItems);
- BuildResult cres = new BuildResult ();
- cres.BuildCount = 0;
-
- // Limit the number of concurrent builders to processors / 2
-
- var slotScheduler = new TaskSlotScheduler (Environment.ProcessorCount / 2);
-
- // Create a dictionary with the status objects of all items
-
- var buildStatus = new Dictionary<SolutionItem, BuildStatus> ();
- foreach (var it in toBuild)
- buildStatus.Add (it, new BuildStatus ());
-
- // Start the build tasks for all itemsw
-
- foreach (var itemToBuild in toBuild) {
- if (monitor.CancellationToken.IsCancellationRequested)
- break;
-
- var item = itemToBuild;
-
- var myStatus = buildStatus [item];
-
- var myMonitor = monitor.BeginAsyncStep (1);
-
- // Get a list of the status objects for all items on which this one depends
-
- var refStatus = item.GetReferencedItems (configuration).Select (it => {
- BuildStatus bs;
- buildStatus.TryGetValue (it, out bs);
- return bs;
- }).Where (t => t != null).ToArray ();
-
- // Build the item when all its dependencies have been built
-
- var refTasks = refStatus.Select (bs => bs.Task);
-
- myStatus.Task = Task.WhenAll (refTasks).ContinueWith (async t => {
- if (!ignoreFailed && (refStatus.Any (bs => bs.Failed) || t.IsFaulted)) {
- myStatus.Failed = true;
- } else {
- using (await slotScheduler.GetTaskSlot ())
- myStatus.Result = await buildAction (myMonitor, item);
- myStatus.Failed = myStatus.Result != null && myStatus.Result.ErrorCount > 0;
- }
- myMonitor.Dispose ();
- }, Runtime.MainTaskScheduler).Unwrap ();
-
- if (!Runtime.Preferences.ParallelBuild.Value)
- await myStatus.Task;
- }
-
- // Wait for all tasks to end
-
- await Task.WhenAll (buildStatus.Values.Select (bs => bs.Task));
-
- // Generate the errors in the order they were supposed to build
-
- foreach (var it in toBuild) {
- BuildStatus bs;
- if (buildStatus.TryGetValue (it, out bs) && bs.Result != null)
- cres.Append (bs.Result);
- }
-
- return cres;
- }
-
[Obsolete("This method will be removed in future releases")]
public bool NeedsBuilding (ConfigurationSelector configuration)
{
@@ -775,7 +646,7 @@ namespace MonoDevelop.Projects
sf.Files.Remove (fileName);
}
foreach (Project projectEntry in GetAllProjects()) {
- List<ProjectFile> toDelete = new List<ProjectFile> ();
+ var toDelete = new List<ProjectFile> ();
foreach (ProjectFile fInfo in projectEntry.Files) {
if (fInfo.Name == fileName)
toDelete.Add (fInfo);
@@ -881,15 +752,13 @@ namespace MonoDevelop.Projects
if (ParentFolder != null)
ParentFolder.NotifyItemAddedToFolder (sender, e, newToSolution);
else if (ParentSolution != null && newToSolution)
- ParentSolution.OnSolutionItemAdded (e);
- if (DescendantItemAdded != null)
- DescendantItemAdded (sender, e);
+ ParentSolution.OnSolutionItemAdded (e);
+ DescendantItemAdded?.Invoke (sender, e);
}
internal void NotifyItemRemovedFromFolder (object sender, SolutionItemChangeEventArgs e, bool removedFromSolution)
- {
- if (DescendantItemRemoved != null)
- DescendantItemRemoved (sender, e);
+ {
+ DescendantItemRemoved?.Invoke (sender, e);
if (ParentFolder != null)
ParentFolder.NotifyItemRemovedFromFolder (sender, e, removedFromSolution);
else if (ParentSolution != null && removedFromSolution)
@@ -919,9 +788,8 @@ namespace MonoDevelop.Projects
}
void OnItemAdded (SolutionItemChangeEventArgs e)
- {
- if (ItemAdded != null)
- ItemAdded (this, e);
+ {
+ ItemAdded?.Invoke (this, e);
}
void OnItemRemoved (SolutionItemChangeEventArgs e, bool removedFromSolution)
@@ -931,108 +799,88 @@ namespace MonoDevelop.Projects
}
void OnItemRemoved (SolutionItemChangeEventArgs e)
- {
- if (ItemRemoved != null)
- ItemRemoved (this, e);
+ {
+ ItemRemoved?.Invoke (this, e);
}
void OnFileRemovedFromProject (ProjectFileEventArgs e)
{
if (ParentFolder == null && ParentSolution != null)
- ParentSolution.OnFileRemovedFromProject (e);
- if (FileRemovedFromProject != null) {
- FileRemovedFromProject (this, e);
- }
+ ParentSolution.OnFileRemovedFromProject (e);
+ FileRemovedFromProject?.Invoke (this, e);
}
void OnFileChangedInProject (ProjectFileEventArgs e)
{
if (ParentFolder == null && ParentSolution != null)
- ParentSolution.OnFileChangedInProject (e);
- if (FileChangedInProject != null) {
- FileChangedInProject (this, e);
- }
+ ParentSolution.OnFileChangedInProject (e);
+ FileChangedInProject?.Invoke (this, e);
}
void OnFilePropertyChangedInProject (ProjectFileEventArgs e)
{
if (ParentFolder == null && ParentSolution != null)
- ParentSolution.OnFilePropertyChangedInProject (e);
- if (FilePropertyChangedInProject != null) {
- FilePropertyChangedInProject (this, e);
- }
+ ParentSolution.OnFilePropertyChangedInProject (e);
+ FilePropertyChangedInProject?.Invoke (this, e);
}
void OnFileAddedToProject (ProjectFileEventArgs e)
{
if (ParentFolder == null && ParentSolution != null)
- ParentSolution.OnFileAddedToProject (e);
- if (FileAddedToProject != null) {
- FileAddedToProject (this, e);
- }
+ ParentSolution.OnFileAddedToProject (e);
+ FileAddedToProject?.Invoke (this, e);
}
void OnFileRenamedInProject (ProjectFileRenamedEventArgs e)
{
if (ParentFolder == null && ParentSolution != null)
- ParentSolution.OnFileRenamedInProject (e);
- if (FileRenamedInProject != null) {
- FileRenamedInProject (this, e);
- }
+ ParentSolution.OnFileRenamedInProject (e);
+ FileRenamedInProject?.Invoke (this, e);
}
void OnReferenceRemovedFromProject (ProjectReferenceEventArgs e)
{
if (ParentFolder == null && ParentSolution != null)
- ParentSolution.OnReferenceRemovedFromProject (e);
- if (ReferenceRemovedFromProject != null) {
- ReferenceRemovedFromProject (this, e);
- }
+ ParentSolution.OnReferenceRemovedFromProject (e);
+ ReferenceRemovedFromProject?.Invoke (this, e);
}
void OnReferenceAddedToProject (ProjectReferenceEventArgs e)
{
if (ParentFolder == null && ParentSolution != null)
- ParentSolution.OnReferenceAddedToProject (e);
- if (ReferenceAddedToProject != null) {
- ReferenceAddedToProject (this, e);
- }
+ ParentSolution.OnReferenceAddedToProject (e);
+ ReferenceAddedToProject?.Invoke (this, e);
}
void OnItemModified (SolutionItemModifiedEventArgs e)
{
if (ParentFolder == null && ParentSolution != null)
- ParentSolution.OnEntryModified (e);
- if (ItemModified != null)
- ItemModified (this, e);
+ ParentSolution.OnEntryModified (e);
+ ItemModified?.Invoke (this, e);
}
void OnItemSaved (SolutionItemSavedEventArgs e)
{
if (ParentFolder == null && ParentSolution != null)
- ParentSolution.OnEntrySaved (e);
- if (ItemSaved != null)
- ItemSaved (this, e);
+ ParentSolution.OnEntrySaved (e);
+ ItemSaved?.Invoke (this, e);
}
void OnSolutionItemFileAdded (SolutionItemFileEventArgs args)
- {
- if (SolutionItemFileAdded != null)
- SolutionItemFileAdded (this, args);
+ {
+ SolutionItemFileAdded?.Invoke (this, args);
}
void OnSolutionItemFileRemoved (SolutionItemFileEventArgs args)
- {
- if (SolutionItemFileRemoved != null)
- SolutionItemFileRemoved (this, args);
+ {
+ SolutionItemFileRemoved?.Invoke (this, args);
}
void OnItemReloadRequired (SolutionItemEventArgs e)
{
if (ParentFolder == null && ParentSolution != null)
- ParentSolution.OnItemReloadRequired (e);
- if (ItemReloadRequired != null)
- ItemReloadRequired (this, e);
+ ParentSolution.OnItemReloadRequired (e);
+ ItemReloadRequired?.Invoke (this, e);
}
public event SolutionItemChangeEventHandler ItemAdded;
@@ -1105,7 +953,7 @@ namespace MonoDevelop.Projects
protected override void ClearItems ()
{
- FilePath[] files = new FilePath [Count];
+ var files = new FilePath [Count];
CopyTo (files, 0);
base.ClearItems();
parent.NotifyFilesRemoved (files);
@@ -1134,18 +982,14 @@ namespace MonoDevelop.Projects
}
public class SolutionItemFileEventArgs: EventArgs
- {
- FilePath file;
-
+ {
public SolutionItemFileEventArgs (FilePath file)
{
- this.file = file;
- }
-
- public FilePath File {
- get { return this.file; }
- }
- }
+ File = file;
+ }
+
+ public FilePath File { get; }
+ }
/// <summary>
/// Keeps track of slots available for executing an operation
diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/SolutionItem.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/SolutionItem.cs
index e24972f81f..b88e6a0fad 100644
--- a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/SolutionItem.cs
+++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/SolutionItem.cs
@@ -618,7 +618,7 @@ namespace MonoDevelop.Projects
try {
SolutionItemConfiguration iconf = GetConfiguration (solutionConfiguration);
string confName = iconf != null ? iconf.Id : solutionConfiguration.ToString ();
- monitor.BeginTask (GettextCatalog.GetString ("Building: {0} ({1})", Name, confName), 1);
+ monitor.BeginTask (GettextCatalog.GetString ("Building {0} ({1})", Name, confName), 1);
operationStarted = ParentSolution != null && await ParentSolution.BeginBuildOperation (monitor, solutionConfiguration, operationContext);
@@ -635,35 +635,19 @@ namespace MonoDevelop.Projects
return result;
}
+ if (ParentSolution == null) {
+ throw new InvalidOperationException ("Cannot build SolutionItem without parent solution");
+ }
+
ITimeTracker tt = Counters.BuildProjectAndReferencesTimer.BeginTiming ("Building " + Name, CreateProjectEventMetadata (solutionConfiguration));
try {
- operationStarted = ParentSolution != null && await ParentSolution.BeginBuildOperation (monitor, solutionConfiguration, operationContext);
-
- // Get a list of all items that need to be built (including this),
- // and build them in the correct order
-
- var referenced = new List<SolutionItem> ();
- var visited = new Set<SolutionItem> ();
- GetBuildableReferencedItems (visited, referenced, this, solutionConfiguration);
- if (Runtime.Preferences.SkipBuildingUnmodifiedProjects)
- referenced.RemoveAll (si => {
- if (si is Project p)
- return !p.FastCheckNeedsBuild (solutionConfiguration);
- return false;//Don't filter things that don't have FastCheckNeedsBuild
- });
- var sortedReferenced = TopologicalSort (referenced, solutionConfiguration);
-
- SolutionItemConfiguration iconf = GetConfiguration (solutionConfiguration);
- string confName = iconf != null ? iconf.Id : solutionConfiguration.ToString ();
- monitor.BeginTask (GettextCatalog.GetString ("Building: {0} ({1})", Name, confName), sortedReferenced.Count);
-
- result = await SolutionFolder.RunParallelBuildOperation (monitor, solutionConfiguration, sortedReferenced, (ProgressMonitor m, SolutionItem item) => {
- return item.Build (m, solutionConfiguration, false, operationContext);
- }, false);
+ operationStarted = await ParentSolution.BeginBuildOperation (monitor, solutionConfiguration, operationContext);
+ result = await ParentSolution.BuildItems (
+ monitor, solutionConfiguration, new[] { this }, operationContext,
+ GettextCatalog.GetString ("Building {0} ({1})", Name, solutionConfiguration.ToString ())
+ );
} finally {
- monitor.EndTask ();
tt.End ();
-
if (operationStarted)
await ParentSolution.EndBuildOperation (monitor, solutionConfiguration, operationContext, result);
}
@@ -763,7 +747,7 @@ namespace MonoDevelop.Projects
try {
SolutionItemConfiguration iconf = GetConfiguration (configuration);
string confName = iconf != null ? iconf.Id : configuration.ToString ();
- monitor.BeginTask (GettextCatalog.GetString ("Cleaning: {0} ({1})", Name, confName), 1);
+ monitor.BeginTask (GettextCatalog.GetString ("Cleaning {0} ({1})", Name, confName), 1);
SolutionItemConfiguration conf = GetConfiguration (configuration);
if (conf != null) {
diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.csproj b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.csproj
index cd13822a7c..5d77650939 100644
--- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.csproj
+++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.csproj
@@ -3138,6 +3138,7 @@
<Compile Include="MonoDevelop.Ide.Gui\ProgressMonitors.cs" />
<Compile Include="MonoDevelop.Ide.TypeSystem\IMonoDevelopHostDocument.cs" />
<Compile Include="MonoDevelop.Ide.TypeSystem\MonoDevelopPersistentStorageLocationService.cs" />
+ <Compile Include="MonoDevelop.Ide\ProjectOperations.SolutionItemBuildBatch.cs" />
<Compile Include="MonoDevelop.Ide\RoslynLogger.cs" />
<Compile Include="MonoDevelop.Ide\Services.cs" />
<Compile Include="MonoDevelop.Ide.Gui.OptionPanels\TasksOptionsPanel.cs" />
diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/ProjectOperations.SolutionItemBuildBatch.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/ProjectOperations.SolutionItemBuildBatch.cs
new file mode 100644
index 0000000000..d38d774908
--- /dev/null
+++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/ProjectOperations.SolutionItemBuildBatch.cs
@@ -0,0 +1,133 @@
+//
+// Copyright (C) Microsoft Corp.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using MonoDevelop.Core;
+using MonoDevelop.Projects;
+
+namespace MonoDevelop.Ide
+{
+ /// <summary>
+ /// This is the basic interface to the workspace.
+ /// </summary>
+ partial class ProjectOperations
+ {
+ /// <summary>
+ /// Represents a group of solution items being built together.
+ /// </summary>
+ class SolutionItemBuildBatch : IBuildTarget
+ {
+ string name;
+ Solution sln;
+
+ /// <summary>
+ /// Simplifies a group of build targets
+ /// </summary>
+ public static IBuildTarget Create (IEnumerable<IBuildTarget> targets)
+ {
+ Solution sln = null;
+ var buildTargets = new HashSet<SolutionItem> ();
+
+ foreach (var target in targets) {
+ if (target is SolutionItem si) {
+ var parent = si.ParentSolution;
+ if (parent == null) {
+ throw new InvalidOperationException ("Items must be part of a solution");
+ }
+ if (sln != null && sln != parent) {
+ throw new InvalidOperationException ("All items must be in the same solution");
+ } else {
+ sln = parent;
+ }
+ buildTargets.Add (si);
+ continue;
+ }
+ if (target is Solution s) {
+ if (sln != null && sln != s) {
+ throw new InvalidOperationException ("All items must be in the same solution");
+ }
+ return sln;
+ }
+ }
+ if (buildTargets.Count == 1) {
+ return buildTargets.First ();
+ }
+ return new SolutionItemBuildBatch (sln, buildTargets);
+ }
+
+ public ICollection<SolutionItem> Items { get; }
+
+ SolutionItemBuildBatch (Solution sln, ICollection<SolutionItem> items)
+ {
+ this.sln = sln;
+ Items = items;
+ }
+
+ public string Name => name ?? (name = string.Join (";", Items.Select (s => s.Name)));
+
+ public bool CanBuild (ConfigurationSelector configuration)
+ {
+ return Items.All (item => ((IBuildTarget)item).CanBuild (configuration));
+ }
+
+ public Task<BuildResult> Build (ProgressMonitor monitor, ConfigurationSelector configuration, bool buildReferencedTargets = false, OperationContext operationContext = null)
+ {
+ return sln.BuildItems (monitor, configuration, Items);
+ }
+
+ public Task<BuildResult> Clean (ProgressMonitor monitor, ConfigurationSelector configuration, OperationContext operationContext = null)
+ {
+ return sln.CleanItems (monitor, configuration, Items);
+ }
+
+ public bool CanExecute (ExecutionContext context, ConfigurationSelector configuration)
+ {
+ throw new NotSupportedException ("Execution not supported for build groups");
+ }
+
+ public Task Execute (ProgressMonitor monitor, ExecutionContext context, ConfigurationSelector configuration)
+ {
+ throw new NotSupportedException ("Execution not supported for build groups");
+ }
+
+ public IEnumerable<IBuildTarget> GetExecutionDependencies ()
+ {
+ throw new NotSupportedException ("Execution not supported for build groups");
+ }
+
+ public Task PrepareExecution (ProgressMonitor monitor, ExecutionContext context, ConfigurationSelector configuration)
+ {
+ throw new NotSupportedException ("Execution not supported for build groups");
+ }
+
+ [Obsolete]
+ public bool NeedsBuilding (ConfigurationSelector configuration)
+ {
+ return Items.Any (item => ((IBuildTarget)item).NeedsBuilding (configuration));
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/ProjectOperations.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/ProjectOperations.cs
index b4d4f2e968..e9571d232a 100644
--- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/ProjectOperations.cs
+++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/ProjectOperations.cs
@@ -1,69 +1,60 @@
-//
-// ProjectOperations.cs
-//
-// Author:
-// Lluis Sanchez Gual
-//
-// Copyright (C) 2005 Novell, Inc (http://www.novell.com)
-//
-// Permission is hereby granted, free of charge, to any person obtaining
-// a copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to
-// permit persons to whom the Software is furnished to do so, subject to
-// the following conditions:
-//
-// The above copyright notice and this permission notice shall be
-// included in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-//
-
-
+//
+// ProjectOperations.cs
+//
+// Author:
+// Lluis Sanchez Gual
+//
+// Copyright (C) 2005 Novell, Inc (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
using System;
-using System.Linq;
-using System.Collections;
using System.Collections.Generic;
+using System.Diagnostics;
using System.IO;
-
-using MonoDevelop.Projects;
-using MonoDevelop.Projects.Text;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
using MonoDevelop.Components;
using MonoDevelop.Core;
using MonoDevelop.Core.Execution;
-using MonoDevelop.Core.ProgressMonitoring;
-using MonoDevelop.Ide.ProgressMonitoring;
-using MonoDevelop.Ide.Gui.Dialogs;
+using MonoDevelop.Core.Instrumentation;
+using MonoDevelop.Core.Text;
+using MonoDevelop.Ide.Editor;
using MonoDevelop.Ide.Gui;
+using MonoDevelop.Ide.Gui.Dialogs;
+using MonoDevelop.Ide.ProgressMonitoring;
using MonoDevelop.Ide.Projects;
-using MonoDevelop.Core.Assemblies;
-using MonoDevelop.Core.Instrumentation;
-using System.Diagnostics;
-using System.Text;
-using MonoDevelop.Ide.TypeSystem;
-using System.Threading.Tasks;
-using System.Threading;
-using ExecutionContext = MonoDevelop.Projects.ExecutionContext;
using MonoDevelop.Ide.Tasks;
+using MonoDevelop.Ide.TypeSystem;
+using MonoDevelop.Projects;
using MonoDevelop.Projects.MSBuild;
-using System.Collections.Immutable;
-using MonoDevelop.Ide.Editor;
-using MonoDevelop.Core.Text;
-using MonoDevelop.Components.Extensions;
+using ExecutionContext = MonoDevelop.Projects.ExecutionContext;
namespace MonoDevelop.Ide
-{
+{
/// <summary>
/// This is the basic interface to the workspace.
/// </summary>
- public class ProjectOperations
+ public partial class ProjectOperations
{
AsyncOperation<BuildResult> currentBuildOperation = new AsyncOperation<BuildResult> (Task.FromResult (BuildResult.CreateSuccess ()), null);
MultipleAsyncOperation currentRunOperation = MultipleAsyncOperation.CompleteMultipleOperation;
@@ -1355,142 +1346,201 @@ namespace MonoDevelop.Ide
}
}
- async Task<bool> CheckAndBuildForExecute (IBuildTarget executionTarget, ExecutionContext context, ConfigurationSelector configuration, RunConfiguration runConfiguration)
+ Task<bool> CheckAndBuildForExecute (IBuildTarget executionTarget, ExecutionContext context, ConfigurationSelector configuration, RunConfiguration runConfiguration)
{
+ var buildTarget = executionTarget;
+
+ // When executing a solution we are actually going to execute the startup project. So we only need to build that project.
+ // TODO: handle multi-startup solutions.
+ if (buildTarget is Solution sol && sol.StartupItem != null)
+ buildTarget = sol.StartupItem;
+
+ return CheckAndBuildForExecute (
+ new [] { buildTarget }, configuration,
+ IdeApp.Preferences.BuildBeforeExecuting, IdeApp.Preferences.RunWithWarnings,
+ (target, monitor) => {
+ if (target is IRunTarget)
+ return ((IRunTarget)target).PrepareExecution (monitor, context, configuration, runConfiguration);
+ return target.PrepareExecution (monitor, context, configuration);
+ }
+ );
+ }
+
+ /// <summary>
+ /// Prepares projects/solutions for execution by building them and their execution dependencies if necessary.
+ /// </summary>
+ /// <returns>Whether the operation was successful.</returns>
+ /// <param name="executionTargets">The projects and/or solution to build. If there are multiple projects, they must be in the same solution.</param>
+ /// <param name="configuration">The configuration selector.</param>
+ /// <param name="buildWithoutPrompting">Whether to prompt the user before building, when building is necessary.</param>
+ /// <param name="cancelOnWarning">Whether to cancel the execution operation if there is a build warning.</param>
+ /// <param name="createPrepareExecutionTask">
+ /// May be executed in parallel with the build to perform additional
+ /// preparation that does not depend on the build, such as launching a simulator.
+ /// There is no guaranteed this will be executed for any target.
+ /// </param>
+ public async Task<bool> CheckAndBuildForExecute (
+ ICollection<IBuildTarget> executionTargets, ConfigurationSelector configuration,
+ bool buildWithoutPrompting = true, bool cancelOnWarning = false,
+ Func<IBuildTarget, ProgressMonitor,Task> createPrepareExecutionTask = null,
+ CancellationToken? token = null)
+ {
+ if (executionTargets.Count == 0) {
+ throw new ArgumentException ("No execution targets specified", nameof (executionTargets)); ;
+ }
+
if (currentBuildOperation != null && !currentBuildOperation.IsCompleted) {
var bres = await currentBuildOperation.Task;
- if (bres.HasErrors || !IdeApp.Preferences.RunWithWarnings && bres.HasWarnings)
+ if (bres.HasErrors || (cancelOnWarning && bres.HasWarnings))
return false;
}
//saves open documents since it may dirty the "needs building" check
var r = await DoBeforeCompileAction ();
if (r.Failed)
- return false;
-
- var buildTarget = executionTarget;
-
- // When executing a solution we are actually going to execute the starup project. So we only need to build that project.
- // TODO: handle multi-startup solutions.
- var sol = buildTarget as Solution;
- if (sol != null && sol.StartupItem != null)
- buildTarget = sol.StartupItem;
-
- var buildDeps = buildTarget.GetExecutionDependencies ().ToList ();
- if (buildDeps.Count > 1)
- throw new NotImplementedException ("Multiple execution dependencies not yet supported");
- if (buildDeps.Count != 0)
- buildTarget = buildDeps [0];
-
- bool needsBuild = FastCheckNeedsBuild (buildTarget, configuration);
- if (!needsBuild) {
- return true;
+ return false;
+
+ IBuildTarget buildTarget = SolutionItemBuildBatch.Create (executionTargets.SelectMany (et => et.GetExecutionDependencies ()));
+
+ if (!FastCheckNeedsBuild (buildTarget, configuration)) {
+ return true;
+ }
+
+ if (!buildWithoutPrompting) {
+ var ret = PromptToBuild ();
+ if (ret.HasValue) {
+ return ret.Value;
+ }
}
- if (IdeApp.Preferences.BuildBeforeExecuting) {
- // Building the project may take some time, so we call PrepareExecution so that the target can
- // prepare the execution (for example, it could start a simulator).
- var cs = new CancellationTokenSource ();
- Task prepareExecution;
- if (buildTarget is IRunTarget)
- prepareExecution = ((IRunTarget)buildTarget).PrepareExecution (new ProgressMonitor ().WithCancellationSource (cs), context, configuration, runConfiguration);
- else
- prepareExecution = buildTarget.PrepareExecution (new ProgressMonitor ().WithCancellationSource (cs), context, configuration);
-
- var result = await Build (buildTarget, true).Task;
+ CancellationTokenSource prepareOpTokenSource = null;
- if (result.HasErrors || (!IdeApp.Preferences.RunWithWarnings && result.HasWarnings)) {
- cs.Cancel ();
- return false;
- }
- else {
- await prepareExecution;
- return true;
- }
+ // Building the project may take some time, so we call PrepareExecution so that the target can
+ // prepare the execution while the build is in progress (for example, it could start a simulator).
+ // As a simple way to avoid starvation, if there are multiple, we run them in sequence.
+ bool building = true;
+ Task prepareExecutionTask = null;
+ if (createPrepareExecutionTask != null) {
+ prepareOpTokenSource = token != null
+ ? CancellationTokenSource.CreateLinkedTokenSource (token.Value)
+ : new CancellationTokenSource ();
+ prepareExecutionTask = RunPrepareExecutionTasks ();
}
- var bBuild = new AlertButton (GettextCatalog.GetString ("Build"));
- var bRun = new AlertButton (Gtk.Stock.Execute, true);
- var res = MessageService.AskQuestion (
- GettextCatalog.GetString ("Outdated Build"),
- GettextCatalog.GetString ("The project you are executing has changes done after the last time it was compiled. Do you want to continue?"),
- 1,
- AlertButton.Cancel,
- bBuild,
- bRun);
+ BuildResult result = await Build (buildTarget, token).Task;
- // This call is a workaround for bug #6907. Without it, the main monodevelop window is left it a weird
- // drawing state after the message dialog is shown. This may be a gtk/mac issue. Still under research.
- DispatchService.RunPendingEvents ();
+ if (result.HasErrors || (cancelOnWarning && result.HasWarnings)) {
+ prepareOpTokenSource?.Cancel ();
+ return false;
+ }
- if (res == bRun) {
- return true;
+ building = false;
+ if (prepareExecutionTask != null) {
+ await prepareExecutionTask;
}
- if (res == bBuild) {
- // Building the project may take some time, so we call PrepareExecution so that the target can
- // prepare the execution (for example, it could start a simulator).
- var cs = new CancellationTokenSource ();
+ return true;
- Task prepareExecution;
- if (buildTarget is IRunTarget)
- prepareExecution = ((IRunTarget)buildTarget).PrepareExecution (new ProgressMonitor ().WithCancellationSource (cs), context, configuration, runConfiguration);
- else
- prepareExecution = buildTarget.PrepareExecution (new ProgressMonitor ().WithCancellationSource (cs), context, configuration);
-
- var result = await Build (buildTarget, true).Task;
+ async Task RunPrepareExecutionTasks ()
+ {
+ var targetsToPrepare = new Queue<IBuildTarget> (executionTargets);
- if (result.HasErrors || (!IdeApp.Preferences.RunWithWarnings && result.HasWarnings)) {
- cs.Cancel ();
- return false;
+ while (targetsToPrepare.Count > 0 && building) {
+ var target = targetsToPrepare.Dequeue ();
+ var monitor = new ProgressMonitor ().WithCancellationSource (prepareOpTokenSource);
+ await createPrepareExecutionTask (target, monitor);
}
- else {
- await prepareExecution;
- return true;
+ }
+ }
+
+ /// <summary>
+ /// Prompts the user whether they want to build the project
+ /// </summary>
+ /// <returns>True to execute without building, false to cancel, null to build.</returns>
+ static bool? PromptToBuild ()
+ {
+ var bBuild = new AlertButton (GettextCatalog.GetString ("Build"));
+ var bRun = new AlertButton (Gtk.Stock.Execute, true);
+ var res = MessageService.AskQuestion (
+ GettextCatalog.GetString ("Outdated Build"),
+ GettextCatalog.GetString ("The project you are executing has changes done after the last time it was compiled. Do you want to continue?"),
+ 1,
+ AlertButton.Cancel,
+ bBuild,
+ bRun);
+
+ // This call is a workaround for bug #6907. Without it, the main monodevelop window is left it a weird
+ // drawing state after the message dialog is shown. This may be a gtk/mac issue. Still under research.
+ DispatchService.RunPendingEvents ();
+
+ if (res == bRun) {
+ return true;
+ }
+
+ if (res == bBuild) {
+ return null;
+ }
+
+ return false;
+ }
+
+ /// <summary>
+ /// Given a build target, determines whether it or its dependencies needs to be built.
+ /// </summary>
+ /// <param name="target">The build target to check.</param>
+ /// <param name="configuration">The build configuration selector.</param>
+ static bool FastCheckNeedsBuild (IBuildTarget target, ConfigurationSelector configuration)
+ {
+ if (FastBuildCheckDisabled ()) {
+ return true;
+ }
+
+ IEnumerable<SolutionItem> items;
+
+ switch (target) {
+ case Project proj: {
+ var deps = new HashSet<SolutionItem> { proj };
+ CollectDependencies (proj, deps, configuration);
+ items = deps;
+ break;
+ }
+ case SolutionItemBuildBatch batch: {
+ var deps = new HashSet<SolutionItem> ();
+ foreach (var item in batch.Items) {
+ deps.Add (item);
+ CollectDependencies (item, deps, configuration);
+ }
+ items = deps;
+ break;
}
+ case Solution sln:
+ items = sln.GetAllSolutionItems ();
+ break;
+ default:
+ return true;
}
+ foreach (var item in items) {
+ if (!(item is Project p) || p.FastCheckNeedsBuild (configuration, InitOperationContext (target, new TargetEvaluationContext ()))) {
+ return true;
+ }
+ }
+
return false;
- }
-
- bool FastCheckNeedsBuild (IBuildTarget target, ConfigurationSelector configuration)
- {
+ }
+
+ static bool FastBuildCheckDisabled ()
+ {
var env = Environment.GetEnvironmentVariable ("DisableFastUpToDateCheck");
- if (!string.IsNullOrEmpty (env) && env != "0" && !env.Equals ("false", StringComparison.OrdinalIgnoreCase))
- return true;
-
- var sei = target as Project;
- if (sei != null) {
- if (sei.FastCheckNeedsBuild (configuration, InitOperationContext (target, new TargetEvaluationContext ())))
- return true;
- //TODO: respect solution level dependencies
- var deps = new HashSet<SolutionItem> ();
- CollectReferencedItems (sei, deps, configuration);
- foreach (var dep in deps.OfType<Project> ()) {
- if (dep.FastCheckNeedsBuild (configuration, InitOperationContext (target, new TargetEvaluationContext ())))
- return true;
- }
- return false;
- }
-
- var sln = target as Solution;
- if (sln != null) {
- foreach (var item in sln.GetAllProjects ()) {
- if (item.FastCheckNeedsBuild (configuration, InitOperationContext (target, new TargetEvaluationContext ())))
- return true;
- }
- return false;
- }
-
- //TODO: handle other IBuildTargets
- return true;
- }
-
- void CollectReferencedItems (SolutionItem item, HashSet<SolutionItem> collected, ConfigurationSelector configuration)
+ return !string.IsNullOrEmpty (env) && env != "0" && !env.Equals ("false", StringComparison.OrdinalIgnoreCase);
+ }
+
+ //TODO: respect solution level dependencies
+ static void CollectDependencies (SolutionItem item, HashSet<SolutionItem> collected, ConfigurationSelector configuration)
{
foreach (var refItem in item.GetReferencedItems (configuration)) {
if (collected.Add (refItem)) {
- CollectReferencedItems (refItem, collected, configuration);
+ CollectDependencies (refItem, collected, configuration);
}
}
}
@@ -1565,7 +1615,7 @@ namespace MonoDevelop.Ide
/// Initializes the context to be used for build operations. It currently just initializes
/// it with the currently selected execution target.
/// </summary>
- T InitOperationContext<T> (IBuildTarget target, T context) where T:OperationContext
+ static T InitOperationContext<T> (IBuildTarget target, T context) where T:OperationContext
{
OperationContext ctx = context;
if (ctx == null)
diff --git a/main/tests/MonoDevelop.Core.Tests/MonoDevelop.Projects/ProjectBuildTests.cs b/main/tests/MonoDevelop.Core.Tests/MonoDevelop.Projects/ProjectBuildTests.cs
index 8a607ff712..e547f9deb2 100644
--- a/main/tests/MonoDevelop.Core.Tests/MonoDevelop.Projects/ProjectBuildTests.cs
+++ b/main/tests/MonoDevelop.Core.Tests/MonoDevelop.Projects/ProjectBuildTests.cs
@@ -319,12 +319,27 @@ namespace MonoDevelop.Projects
sol.Dispose();
}
+ static Solution WrapSolutionItem (SolutionItem item)
+ {
+ var sol = new Solution ();
+ var config = sol.AddConfiguration ("Debug", true);
+ sol.RootFolder.Items.Add (item);
+ config.GetEntryForItem (item).Build = true;
+ return sol;
+ }
+
+ async static Task<(Solution sln, T item)> LoadSampleSolutionItem<T> (params string [] projectName) where T : SolutionItem
+ {
+ string projFile = Util.GetSampleProject (projectName);
+ var item = (T) await Services.ProjectService.ReadSolutionItem (Util.GetMonitor (), projFile);
+ var sol = WrapSolutionItem (item);
+ return (sol, item);
+ }
+
[Test]
public async Task BuildWithCustomProps ()
{
- string projFile = Util.GetSampleProject ("msbuild-tests", "project-with-custom-build-target.csproj");
- var p = (Project)await Services.ProjectService.ReadSolutionItem (Util.GetMonitor (), projFile);
-
+ (var sol, var p) = await LoadSampleSolutionItem<Project> ("msbuild-tests", "project-with-custom-build-target.csproj");
var ctx = new ProjectOperationContext ();
ctx.GlobalProperties.SetValue ("TestProp", "foo");
var res = await p.Build (Util.GetMonitor (), p.Configurations [0].Selector, ctx);
@@ -340,6 +355,7 @@ namespace MonoDevelop.Projects
Assert.AreEqual ("Something failed: show", res.Errors [0].ErrorText);
p.Dispose ();
+ sol.Dispose ();
}
/// <summary>
@@ -350,8 +366,7 @@ namespace MonoDevelop.Projects
[Platform (Exclude = "Win")]
public async Task BuildWithCustomProps2 ()
{
- string projFile = Util.GetSampleProject ("msbuild-tests", "project-with-custom-build-target2.csproj");
- var p = (Project)await Services.ProjectService.ReadSolutionItem (Util.GetMonitor (), projFile);
+ (var sol, var p) = await LoadSampleSolutionItem<Project> ("msbuild-tests", "project-with-custom-build-target2.csproj");
var ctx = new ProjectOperationContext ();
ctx.GlobalProperties.SetValue ("TestProp", "foo");
@@ -368,6 +383,7 @@ namespace MonoDevelop.Projects
Assert.AreEqual ("Something failed (show.targets): show", res.Errors [0].ErrorText);
p.Dispose ();
+ sol.Dispose ();
}
/// <summary>
@@ -379,8 +395,7 @@ namespace MonoDevelop.Projects
[Platform (Exclude = "Win")]
public async Task BuildWithCustomProps3 ()
{
- string projFile = Util.GetSampleProject ("msbuild-tests", "project-with-custom-build-target3.csproj");
- var p = (Project)await Services.ProjectService.ReadSolutionItem (Util.GetMonitor (), projFile);
+ (var sol, var p) = await LoadSampleSolutionItem<Project> ("msbuild-tests", "project-with-custom-build-target3.csproj");
var ctx = new ProjectOperationContext ();
ctx.GlobalProperties.SetValue ("BuildingInsideVisualStudio", "false");
@@ -397,6 +412,7 @@ namespace MonoDevelop.Projects
Assert.AreEqual ("Something failed (true.targets): true", res.Errors [0].ErrorText);
p.Dispose ();
+ sol.Dispose ();
}
[Test]
@@ -509,7 +525,7 @@ namespace MonoDevelop.Projects
}
[Test ()]
- public async Task ProjectReferencingDisabledProject_ProjectBuildFails ()
+ public async Task ProjectReferencingDisabledProject_ProjectBuildWorks ()
{
// If a project references another project that is disabled for the solution configuration, the referenced
// project should build when directly building the referencing project.
@@ -520,7 +536,7 @@ namespace MonoDevelop.Projects
var p = sol.Items.FirstOrDefault (pr => pr.Name == "ReferencingProject");
var res = await p.Build (Util.GetMonitor (), (SolutionConfigurationSelector)"Debug", true);
- Assert.AreEqual (1, res.ErrorCount);
+ Assert.AreEqual (1, res.BuildCount);
sol.Dispose ();
}