diff options
author | Lluis Sanchez <llsan@microsoft.com> | 2018-07-19 12:01:33 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-07-19 12:01:33 +0300 |
commit | 827c074a2ee451386a8ddad7ad438f1ac096e21a (patch) | |
tree | 5470d29fb53f1373a56c3b778cbe532b5afe9e61 /main | |
parent | 493af9dad5e7d641a645768948b5b44acd500432 (diff) | |
parent | a2e1a827c3c9cde75e0107fabf82edbd51958bc7 (diff) |
Merge pull request #5408 from mono/fast-build-check-for-test
Fast build check for test
Diffstat (limited to 'main')
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 (); } |