From 57654f10231a2ed9242d66fb975fa233d6252386 Mon Sep 17 00:00:00 2001 From: Mikayla Hutchinson Date: Fri, 13 Jul 2018 14:59:02 -0400 Subject: [Ide] Generalize CheckAndBuildForExecute This will allow it to be used when executing tests as well --- .../MonoDevelop.Ide/ProjectOperations.cs | 253 +++++++++++++-------- 1 file changed, 161 insertions(+), 92 deletions(-) (limited to 'main') diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/ProjectOperations.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/ProjectOperations.cs index b4d4f2e968..f8a4faf04f 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/ProjectOperations.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/ProjectOperations.cs @@ -1355,11 +1355,48 @@ namespace MonoDevelop.Ide } } - async Task CheckAndBuildForExecute (IBuildTarget executionTarget, ExecutionContext context, ConfigurationSelector configuration, RunConfiguration runConfiguration) + Task 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); + } + ); + } + + /// + /// Prepares projects/solutions for execution by building them and their execution dependencies if necessary. + /// + /// Whether the operation was successful. + /// The projects and/or solution to build. If there are multiple projects, they must be in the same solution. + /// The configuration selector. + /// Whether to prompt the user before building, when building is necessary. + /// Whether to cancel the execution operation if there is a build warning. + /// + /// 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. + /// + public async Task CheckAndBuildForExecute ( + ICollection executionTargets, ConfigurationSelector configuration, + bool buildWithoutPrompting = true, bool cancelOnWarning = false, + Func createPrepareExecutionTask = null, + CancellationToken? token = null) { 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; } @@ -1368,122 +1405,154 @@ namespace MonoDevelop.Ide if (r.Failed) return false; - var buildTarget = executionTarget; + //validate there's only one solution and all items are in it + if (executionTargets.Count > 1) { + Solution sln = null; + foreach (var executionTarget in executionTargets) { + switch (executionTarget) { + case SolutionItem si: + if (sln == null) { + sln = si.ParentSolution; + } else if (sln != si.ParentSolution) { + throw new ArgumentException ("All items must be in the same solution", nameof (executionTargets)); + } + continue; + case Solution s: + throw new ArgumentException ("Only one solution is permitted", nameof (executionTargets)); + } + } + } - // 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) { + var buildTargets = new HashSet (); + + foreach (var target in executionTargets) { + foreach (var dep in target.GetExecutionDependencies ()) { + buildTargets.Add (dep); + } + } + + var needsBuild = FastCheckNeedsBuild (buildTargets, configuration); + if (needsBuild.Count == 0) { return true; } - 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; + if (!buildWithoutPrompting) { + 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); - if (result.HasErrors || (!IdeApp.Preferences.RunWithWarnings && result.HasWarnings)) { - cs.Cancel (); - return false; - } - else { - await prepareExecution; + // 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 false; + } } - 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); + CancellationTokenSource prepareOpTokenSource = null; - // 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 (); + // 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 (); + } - if (res == bRun) { - return true; + //TODO: parallelize this and deduplicate shared dependencies + foreach (var target in buildTargets) { + var result = await Build (target, true, token).Task; + if (result.HasErrors || (cancelOnWarning && result.HasWarnings)) { + prepareOpTokenSource?.Cancel (); + return false; + } } - 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 (); + building = false; + if (prepareExecutionTask != null) { + await prepareExecutionTask; + } - 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; + return true; - if (result.HasErrors || (!IdeApp.Preferences.RunWithWarnings && result.HasWarnings)) { - cs.Cancel (); - return false; - } - else { - await prepareExecution; - return true; + async Task RunPrepareExecutionTasks () + { + var targetsToPrepare = new Queue (executionTargets); + + while (targetsToPrepare.Count > 0 && building) { + var target = targetsToPrepare.Dequeue (); + var monitor = new ProgressMonitor ().WithCancellationSource (prepareOpTokenSource); + await createPrepareExecutionTask (target, monitor); } } - - return false; } - - bool FastCheckNeedsBuild (IBuildTarget target, ConfigurationSelector configuration) + + /// + /// From a list of build targets, determines which of them need building. + /// + /// The targets that need to be built. + /// The build targets to check. + /// The build configuration selector. + HashSet FastCheckNeedsBuild (HashSet targets, ConfigurationSelector configuration) { var env = Environment.GetEnvironmentVariable ("DisableFastUpToDateCheck"); if (!string.IsNullOrEmpty (env) && env != "0" && !env.Equals ("false", StringComparison.OrdinalIgnoreCase)) - return true; + return targets; - 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 (); - CollectReferencedItems (sei, deps, configuration); - foreach (var dep in deps.OfType ()) { - if (dep.FastCheckNeedsBuild (configuration, InitOperationContext (target, new TargetEvaluationContext ()))) - return true; - } - return false; - } + var targetsNeedBuild = new HashSet (); + var checkedTargets = new HashSet (); - var sln = target as Solution; - if (sln != null) { - foreach (var item in sln.GetAllProjects ()) { - if (item.FastCheckNeedsBuild (configuration, InitOperationContext (target, new TargetEvaluationContext ()))) - return true; + foreach (var target in targets) { + if (!checkedTargets.Add (target)) { + continue; + } + switch (target) { + case Project proj: { + if (proj.FastCheckNeedsBuild (configuration, InitOperationContext (target, new TargetEvaluationContext ()))) { + targetsNeedBuild.Add (proj); + continue; + } + //TODO: respect solution level dependencies + var deps = new HashSet (); + CollectReferencedItems (proj, deps, configuration); + foreach (var dep in deps.OfType ()) { + if (checkedTargets.Add (target) && dep.FastCheckNeedsBuild (configuration, InitOperationContext (target, new TargetEvaluationContext ()))) { + targetsNeedBuild.Add (proj); + continue; + } + } + continue; + } + case Solution sln: + foreach (var item in sln.GetAllProjects ()) { + if (checkedTargets.Add (item) && item.FastCheckNeedsBuild (configuration, InitOperationContext (target, new TargetEvaluationContext ()))) + targetsNeedBuild.Add (sln); + continue; + } + //TODO: handle other IBuildTargets in the sln + continue; + default: + targetsNeedBuild.Add (target); + continue; } - return false; } - //TODO: handle other IBuildTargets - return true; + return targetsNeedBuild; } void CollectReferencedItems (SolutionItem item, HashSet collected, ConfigurationSelector configuration) -- cgit v1.2.3 From 71b9ea7bd1893ccbbaf313406009e6b20701b67a Mon Sep 17 00:00:00 2001 From: Mikayla Hutchinson Date: Fri, 13 Jul 2018 17:07:47 -0400 Subject: [UnitTesting] Use fast build check before running tests Fixes #5108 Running unit tests aways does full build --- .../Services/UnitTestService.cs | 36 +++++++++------------- 1 file changed, 15 insertions(+), 21 deletions(-) (limited to 'main') 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 (); 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 (tests.Select ((v) => v.FullName)); + var test_names = new HashSet (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 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; } -- cgit v1.2.3 From a4959dccd7957a931c10f6b7be208d43fc615c8c Mon Sep 17 00:00:00 2001 From: Mikayla Hutchinson Date: Mon, 16 Jul 2018 12:19:56 -0400 Subject: [Ide] Improve readability with some code cleanup Apply fixes for var, pattern matching and event invocation. --- .../MonoDevelop.Projects/Solution.cs | 165 ++++++------- .../MonoDevelop.Projects/SolutionFolder.cs | 269 +++++++++------------ 2 files changed, 188 insertions(+), 246 deletions(-) (limited to 'main') diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/Solution.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/Solution.cs index 6fcc011e57..e653e6eb7c 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 GetConfigurations () { - List configs = new List (); + var configs = new List (); foreach (SolutionConfiguration conf in Configurations) configs.Add (conf.Id); return configs.AsReadOnly (); @@ -857,16 +854,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 +874,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 (); var monitors = new List (); 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,18 +915,15 @@ namespace MonoDevelop.Projects } /*protected virtual*/ void OnStartupItemChanged(EventArgs e) - { - if (StartupItemChanged != null) - StartupItemChanged (this, e); + { + StartupItemChanged?.Invoke (this, e); } void OnStartupConfigurationChanged (EventArgs e) - { - if (StartupConfigurationChanged != null) - StartupConfigurationChanged (this, e); + { + StartupConfigurationChanged?.Invoke (this, e); } - [ThreadSafe] public MSBuildFileFormat FileFormat { get { @@ -988,37 +978,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 ()) 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 +1020,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 ()) 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 +1064,7 @@ namespace MonoDevelop.Projects if (project == projectToRemove) continue; - List toDelete = new List (); + var toDelete = new List (); foreach (ProjectReference pref in project.References) { if (pref.ReferenceType == ReferenceType.Project && pref.Reference == projectToRemove.Name) @@ -1100,7 +1082,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 +1158,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 +1371,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..9846a2a686 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 ().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 ()) 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; @@ -423,15 +420,15 @@ namespace MonoDevelop.Projects public ReadOnlyCollection GetAllItemsWithTopologicalSort (ConfigurationSelector configuration) where T: SolutionItem { - List list = new List (); + var list = new List (); GetAllItems (list, this); return SolutionItem.TopologicalSort (list, configuration); } public ReadOnlyCollection GetAllProjects () { - List list = new List (); - GetAllItems (list, this); + var list = new List (); + GetAllItems (list, this); return list.AsReadOnly (); } @@ -439,9 +436,9 @@ namespace MonoDevelop.Projects // they should be compiled, acording to their references. public ReadOnlyCollection GetAllProjectsWithTopologicalSort (ConfigurationSelector configuration) { - List list = new List (); - GetAllItems (list, this); - return SolutionItem.TopologicalSort (list, configuration); + var list = new List (); + GetAllItems (list, this); + return SolutionItem.TopologicalSort (list, configuration); } void GetAllItems (List list, SolutionFolderItem item) where T: SolutionFolderItem @@ -450,48 +447,45 @@ namespace MonoDevelop.Projects list.Add ((T)item); } - if (item is SolutionFolder) { - foreach (SolutionFolderItem ce in ((SolutionFolder)item).Items) - GetAllItems (list, ce); + if (item is SolutionFolder sf) { + foreach (SolutionFolderItem ce in (sf).Items) + GetAllItems (list, ce); } } public ReadOnlyCollection GetAllBuildableEntries (ConfigurationSelector configuration, bool topologicalSort, bool includeExternalReferences) { - var list = new List (); - GetAllBuildableEntries (list, configuration, includeExternalReferences); + var list = new List (); + if (ParentSolution != null) + return list.AsReadOnly (); + + SolutionConfiguration conf = ParentSolution.GetConfiguration (configuration); + if (conf == null) + return list.AsReadOnly (); + + GetAllBuildableEntries (list, configuration, conf, includeExternalReferences); + if (topologicalSort) - return SolutionItem.TopologicalSort (list, configuration); + return SolutionItem.TopologicalSort (list, configuration); else return list.AsReadOnly (); } - - public ReadOnlyCollection GetAllBuildableEntries (ConfigurationSelector configuration) - { - return GetAllBuildableEntries (configuration, false, false); - } - - void GetAllBuildableEntries (List list, ConfigurationSelector configuration, bool includeExternalReferences) - { - if (ParentSolution == null) - return; - SolutionConfiguration conf = ParentSolution.GetConfiguration (configuration); - if (conf == null) - return; + void GetAllBuildableEntries (List list, ConfigurationSelector configuration, SolutionConfiguration slnConf, bool includeExternalReferences) + { 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); + if (item is SolutionFolder sf) + GetAllBuildableEntries (list, configuration, slnConf, includeExternalReferences); + else if ((item is SolutionItem) && slnConf.BuildEnabledForItem ((SolutionItem)item) && ((SolutionItem)item).SupportsBuild ()) + GetAllBuildableReferences (list, (SolutionItem)item, configuration, slnConf, includeExternalReferences, false); } - } - - void GetAllBuildableReferences (List list, SolutionItem item, ConfigurationSelector configuration, SolutionConfiguration conf, bool includeExternalReferences, bool isDirectReference) + } + + static void GetAllBuildableReferences (List 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 + return; + // Skip unsupported projects which are not directly referenced by other (supported) projects if (!isDirectReference && item.IsUnsupportedProject) return; list.Add (item); @@ -499,6 +493,11 @@ namespace MonoDevelop.Projects foreach (var it in item.GetReferencedItems (configuration)) GetAllBuildableReferences (list, it, configuration, conf, includeExternalReferences, true); } + } + + public ReadOnlyCollection GetAllBuildableEntries (ConfigurationSelector configuration) + { + return GetAllBuildableEntries (configuration, false, false); } [Obsolete("Use GetProjectsContainingFile() (plural) instead")] @@ -542,15 +541,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; @@ -775,7 +773,7 @@ namespace MonoDevelop.Projects sf.Files.Remove (fileName); } foreach (Project projectEntry in GetAllProjects()) { - List toDelete = new List (); + var toDelete = new List (); foreach (ProjectFile fInfo in projectEntry.Files) { if (fInfo.Name == fileName) toDelete.Add (fInfo); @@ -881,15 +879,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 +915,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 +926,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 +1080,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 +1109,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; } + } /// /// Keeps track of slots available for executing an operation -- cgit v1.2.3 From a668a4244dbb686928d12d8100b6ed57c67b35b6 Mon Sep 17 00:00:00 2001 From: Mikayla Hutchinson Date: Mon, 16 Jul 2018 13:27:36 -0400 Subject: [Ide] Refactor to expose parallel build functionality The parallel build functionality from SolutionFolder has been generalized and moved to Solution.BuildItems and Solution.CleanItems. Related methods have been moved or obsoleted. --- .../MonoDevelop.Projects/Solution.cs | 197 +++++++++++++++++++- .../MonoDevelop.Projects/SolutionFolder.cs | 206 ++++----------------- .../MonoDevelop.Projects/SolutionItem.cs | 31 +--- 3 files changed, 237 insertions(+), 197 deletions(-) (limited to 'main') diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/Solution.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/Solution.cs index e653e6eb7c..f4c0340f94 100644 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/Solution.cs +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/Solution.cs @@ -442,12 +442,13 @@ namespace MonoDevelop.Projects public ReadOnlyCollection GetAllSolutionItemsWithTopologicalSort (ConfigurationSelector configuration) where T: SolutionItem { - return RootFolder.GetAllItemsWithTopologicalSort (configuration); + var list = new List (GetAllItems ()); + return SolutionItem.TopologicalSort (list, configuration); } public ReadOnlyCollection GetAllProjectsWithTopologicalSort (ConfigurationSelector configuration) { - return RootFolder.GetAllProjectsWithTopologicalSort (configuration); + return GetAllSolutionItemsWithTopologicalSort (configuration); } public override IEnumerable GetProjectsContainingFile (FilePath fileName) @@ -922,6 +923,198 @@ namespace MonoDevelop.Projects void OnStartupConfigurationChanged (EventArgs e) { StartupConfigurationChanged?.Invoke (this, e); + } + + /// + /// Builds a set of SolutionItems from this solution and their dependencies. They will be built in parallel, and common dependencies will be deduplicated. + /// + public async Task CleanItems (ProgressMonitor monitor, ConfigurationSelector configuration, IEnumerable items, OperationContext operationContext = null) + { + SolutionConfiguration slnConf = GetConfiguration (configuration); + if (slnConf == null) + return new BuildResult (); + + ReadOnlyCollection 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 (GettextCatalog.GetString ("Cleaning Solution: {0} ({1})", 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 (); + } + } + + /// + /// Builds a set of SolutionItems from this solution and their dependencies. They will be built in parallel, and common dependencies will be deduplicated. + /// + public async Task BuildItems (ProgressMonitor monitor, ConfigurationSelector configuration, IEnumerable items, OperationContext operationContext = null) + { + SolutionConfiguration slnConf = GetConfiguration (configuration); + if (slnConf == null) + return new BuildResult (); + + ReadOnlyCollection allProjects; + + try { + allProjects = 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) + 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); + + operationStarted = await BeginBuildOperation (monitor, configuration, operationContext); + + return result = await RunParallelBuildOperation (monitor, configuration, allProjects, (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 RunParallelBuildOperation (ProgressMonitor monitor, ConfigurationSelector configuration, IEnumerable sortedItems, Func> buildAction, bool ignoreFailed) + { + var toBuild = new List (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 (); + 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; + } + + /// + /// Given a set of SolutionItems from this solution, collects them and their buildable dependencies, and toplogically sorts them in preparation for a build. + /// + ReadOnlyCollection GetItemsAndDependenciesSortedForBuild (IEnumerable items, ConfigurationSelector configuration) + { + var slnConf = GetConfiguration (configuration); + var collected = new HashSet (); + + 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); + } + + /// + /// Recursively collects buildable dependencies. + /// + internal static void CollectBuildableDependencies (HashSet 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] diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/SolutionFolder.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/SolutionFolder.cs index 9846a2a686..ee7a1e1fd0 100644 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/SolutionFolder.cs +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/SolutionFolder.cs @@ -418,6 +418,7 @@ namespace MonoDevelop.Projects return GetAllItems (); } + [Obsolete("This method will be removed in future releases")] public ReadOnlyCollection GetAllItemsWithTopologicalSort (ConfigurationSelector configuration) where T: SolutionItem { var list = new List (); @@ -434,6 +435,7 @@ namespace MonoDevelop.Projects // 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 GetAllProjectsWithTopologicalSort (ConfigurationSelector configuration) { var list = new List (); @@ -453,6 +455,7 @@ namespace MonoDevelop.Projects } } + [Obsolete("This method will be removed in future releases")] public ReadOnlyCollection GetAllBuildableEntries (ConfigurationSelector configuration, bool topologicalSort, bool includeExternalReferences) { var list = new List (); @@ -463,41 +466,33 @@ namespace MonoDevelop.Projects if (conf == null) return list.AsReadOnly (); - GetAllBuildableEntries (list, configuration, conf, includeExternalReferences); + var collected = new HashSet (); + CollectBuildableEntries (collected, configuration, conf, includeExternalReferences); + list.AddRange (collected); if (topologicalSort) return SolutionItem.TopologicalSort (list, configuration); else return list.AsReadOnly (); + } + + [Obsolete("This method will be removed in future releases")] + public ReadOnlyCollection GetAllBuildableEntries (ConfigurationSelector configuration) + { + return GetAllBuildableEntries (configuration, false, false); } - void GetAllBuildableEntries (List list, ConfigurationSelector configuration, SolutionConfiguration slnConf, bool includeExternalReferences) + void CollectBuildableEntries (HashSet collected, ConfigurationSelector configuration, SolutionConfiguration slnConf, bool includeDependencies) { foreach (SolutionFolderItem item in Items) { if (item is SolutionFolder sf) - GetAllBuildableEntries (list, configuration, slnConf, includeExternalReferences); - else if ((item is SolutionItem) && slnConf.BuildEnabledForItem ((SolutionItem)item) && ((SolutionItem)item).SupportsBuild ()) - GetAllBuildableReferences (list, (SolutionItem)item, configuration, slnConf, includeExternalReferences, false); - } - } - - static void GetAllBuildableReferences (List 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); + 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); + } + } } - } - - public ReadOnlyCollection GetAllBuildableEntries (ConfigurationSelector configuration) - { - return GetAllBuildableEntries (configuration, false, false); } [Obsolete("Use GetProjectsContainingFile() (plural) instead")] @@ -575,161 +570,32 @@ namespace MonoDevelop.Projects return true; } - public async Task Clean (ProgressMonitor monitor, ConfigurationSelector configuration, OperationContext operationContext = null) + public Task 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 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 (); + CollectBuildableEntries (collected, configuration, slnConf, false); - class BuildStatus - { - public bool Failed; - public Task Task; - public BuildResult Result; + return ParentSolution.CleanItems (monitor, configuration, collected, operationContext); } - public async Task Build (ProgressMonitor monitor, ConfigurationSelector configuration, bool buildReferencedTargets = false, OperationContext operationContext = null) + public Task Build (ProgressMonitor monitor, ConfigurationSelector configuration, bool buildReferencedTargets = false, OperationContext operationContext = null) { - ReadOnlyCollection allProjects; - - try { - allProjects = GetAllBuildableEntries (configuration, true, true); - } catch (CyclicDependencyException) { - monitor.ReportError (GettextCatalog.GetString ("Cyclic dependencies are not supported."), null); - return new BuildResult ("", 1, 1); - } + var slnConf = ParentSolution?.GetConfiguration (configuration); + if (slnConf == null) + return Task.FromResult (new BuildResult ()); - if (operationContext == null) - operationContext = new OperationContext (); + //don't collect dependencies, BuildItems will do it + var collected = new HashSet (); + CollectBuildableEntries (collected, configuration, slnConf, false); - 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); - - operationStarted = ParentSolution != null && await ParentSolution.BeginBuildOperation (monitor, configuration, operationContext); - - 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); } - internal static async Task RunParallelBuildOperation (ProgressMonitor monitor, ConfigurationSelector configuration, IEnumerable sortedItems, Func> buildAction, bool ignoreFailed) - { - List toBuild = new List (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 (); - 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) { diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/SolutionItem.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/SolutionItem.cs index 23801eb582..314b518bb5 100644 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/SolutionItem.cs +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/SolutionItem.cs @@ -635,35 +635,16 @@ 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 (); - var visited = new Set (); - 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); } finally { - monitor.EndTask (); tt.End (); - if (operationStarted) await ParentSolution.EndBuildOperation (monitor, solutionConfiguration, operationContext, result); } -- cgit v1.2.3 From 0ba3c7d4d7eab89cf439316e925c611b57997e13 Mon Sep 17 00:00:00 2001 From: Mikayla Hutchinson Date: Mon, 16 Jul 2018 18:45:43 -0400 Subject: [Ide] Parallelize CheckAndBuildForExecute --- .../core/MonoDevelop.Ide/MonoDevelop.Ide.csproj | 1 + .../ProjectOperations.SolutionItemBuildBatch.cs | 133 +++++++++ .../MonoDevelop.Ide/ProjectOperations.cs | 307 ++++++++++----------- 3 files changed, 278 insertions(+), 163 deletions(-) create mode 100644 main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/ProjectOperations.SolutionItemBuildBatch.cs (limited to 'main') diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.csproj b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.csproj index 6ffdb20e0d..b6d29d6b91 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.csproj +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.csproj @@ -3098,6 +3098,7 @@ + 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 +{ + /// + /// This is the basic interface to the workspace. + /// + partial class ProjectOperations + { + /// + /// Represents a group of solution items being built together. + /// + class SolutionItemBuildBatch : IBuildTarget + { + string name; + Solution sln; + + /// + /// Simplifies a group of build targets + /// + public static IBuildTarget Create (IEnumerable targets) + { + Solution sln = null; + var buildTargets = new HashSet (); + + 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 Items { get; } + + SolutionItemBuildBatch (Solution sln, ICollection 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 Build (ProgressMonitor monitor, ConfigurationSelector configuration, bool buildReferencedTargets = false, OperationContext operationContext = null) + { + return sln.BuildItems (monitor, configuration, Items); + } + + public Task 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 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 f8a4faf04f..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 -{ +{ /// /// This is the basic interface to the workspace. /// - public class ProjectOperations + public partial class ProjectOperations { AsyncOperation currentBuildOperation = new AsyncOperation (Task.FromResult (BuildResult.CreateSuccess ()), null); MultipleAsyncOperation currentRunOperation = MultipleAsyncOperation.CompleteMultipleOperation; @@ -1394,6 +1385,10 @@ namespace MonoDevelop.Ide Func 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 || (cancelOnWarning && bres.HasWarnings)) @@ -1403,60 +1398,18 @@ namespace MonoDevelop.Ide //saves open documents since it may dirty the "needs building" check var r = await DoBeforeCompileAction (); if (r.Failed) - return false; - - //validate there's only one solution and all items are in it - if (executionTargets.Count > 1) { - Solution sln = null; - foreach (var executionTarget in executionTargets) { - switch (executionTarget) { - case SolutionItem si: - if (sln == null) { - sln = si.ParentSolution; - } else if (sln != si.ParentSolution) { - throw new ArgumentException ("All items must be in the same solution", nameof (executionTargets)); - } - continue; - case Solution s: - throw new ArgumentException ("Only one solution is permitted", nameof (executionTargets)); - } - } + return false; + + IBuildTarget buildTarget = SolutionItemBuildBatch.Create (executionTargets.SelectMany (et => et.GetExecutionDependencies ())); + + if (!FastCheckNeedsBuild (buildTarget, configuration)) { + return true; } - var buildTargets = new HashSet (); - - foreach (var target in executionTargets) { - foreach (var dep in target.GetExecutionDependencies ()) { - buildTargets.Add (dep); - } - } - - var needsBuild = FastCheckNeedsBuild (buildTargets, configuration); - if (needsBuild.Count == 0) { - return true; - } - - if (!buildWithoutPrompting) { - 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 false; + if (!buildWithoutPrompting) { + var ret = PromptToBuild (); + if (ret.HasValue) { + return ret.Value; } } @@ -1474,13 +1427,11 @@ namespace MonoDevelop.Ide prepareExecutionTask = RunPrepareExecutionTasks (); } - //TODO: parallelize this and deduplicate shared dependencies - foreach (var target in buildTargets) { - var result = await Build (target, true, token).Task; - if (result.HasErrors || (cancelOnWarning && result.HasWarnings)) { - prepareOpTokenSource?.Cancel (); - return false; - } + BuildResult result = await Build (buildTarget, token).Task; + + if (result.HasErrors || (cancelOnWarning && result.HasWarnings)) { + prepareOpTokenSource?.Cancel (); + return false; } building = false; @@ -1500,66 +1451,96 @@ namespace MonoDevelop.Ide await createPrepareExecutionTask (target, monitor); } } - } - + } + + /// + /// Prompts the user whether they want to build the project + /// + /// True to execute without building, false to cancel, null to build. + 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; + } + /// - /// From a list of build targets, determines which of them need building. + /// Given a build target, determines whether it or its dependencies needs to be built. /// - /// The targets that need to be built. - /// The build targets to check. + /// The build target to check. /// The build configuration selector. - HashSet FastCheckNeedsBuild (HashSet targets, ConfigurationSelector configuration) - { - var env = Environment.GetEnvironmentVariable ("DisableFastUpToDateCheck"); - if (!string.IsNullOrEmpty (env) && env != "0" && !env.Equals ("false", StringComparison.OrdinalIgnoreCase)) - return targets; - - var targetsNeedBuild = new HashSet (); - var checkedTargets = new HashSet (); - - foreach (var target in targets) { - if (!checkedTargets.Add (target)) { - continue; - } - switch (target) { - case Project proj: { - if (proj.FastCheckNeedsBuild (configuration, InitOperationContext (target, new TargetEvaluationContext ()))) { - targetsNeedBuild.Add (proj); - continue; - } - //TODO: respect solution level dependencies - var deps = new HashSet (); - CollectReferencedItems (proj, deps, configuration); - foreach (var dep in deps.OfType ()) { - if (checkedTargets.Add (target) && dep.FastCheckNeedsBuild (configuration, InitOperationContext (target, new TargetEvaluationContext ()))) { - targetsNeedBuild.Add (proj); - continue; - } - } - continue; - } - case Solution sln: - foreach (var item in sln.GetAllProjects ()) { - if (checkedTargets.Add (item) && item.FastCheckNeedsBuild (configuration, InitOperationContext (target, new TargetEvaluationContext ()))) - targetsNeedBuild.Add (sln); - continue; - } - //TODO: handle other IBuildTargets in the sln - continue; - default: - targetsNeedBuild.Add (target); - continue; - } + static bool FastCheckNeedsBuild (IBuildTarget target, ConfigurationSelector configuration) + { + if (FastBuildCheckDisabled ()) { + return true; + } + + IEnumerable items; + + switch (target) { + case Project proj: { + var deps = new HashSet { proj }; + CollectDependencies (proj, deps, configuration); + items = deps; + break; + } + case SolutionItemBuildBatch batch: { + var deps = new HashSet (); + 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; } - return targetsNeedBuild; - } - - void CollectReferencedItems (SolutionItem item, HashSet collected, ConfigurationSelector configuration) + foreach (var item in items) { + if (!(item is Project p) || p.FastCheckNeedsBuild (configuration, InitOperationContext (target, new TargetEvaluationContext ()))) { + return true; + } + } + + return false; + } + + static bool FastBuildCheckDisabled () + { + var env = Environment.GetEnvironmentVariable ("DisableFastUpToDateCheck"); + return !string.IsNullOrEmpty (env) && env != "0" && !env.Equals ("false", StringComparison.OrdinalIgnoreCase); + } + + //TODO: respect solution level dependencies + static void CollectDependencies (SolutionItem item, HashSet collected, ConfigurationSelector configuration) { foreach (var refItem in item.GetReferencedItems (configuration)) { if (collected.Add (refItem)) { - CollectReferencedItems (refItem, collected, configuration); + CollectDependencies (refItem, collected, configuration); } } } @@ -1634,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. /// - T InitOperationContext (IBuildTarget target, T context) where T:OperationContext + static T InitOperationContext (IBuildTarget target, T context) where T:OperationContext { OperationContext ctx = context; if (ctx == null) -- cgit v1.2.3 From 33ee361625af7f71b88823be64061a26dc9d6a97 Mon Sep 17 00:00:00 2001 From: Mikayla Hutchinson Date: Tue, 17 Jul 2018 18:07:23 -0400 Subject: [ProjectTests] Fix tests ProjectReferencingDisabledProject_ProjectBuildFails was incorrect, when building in VS the build does not fail. --- .../MonoDevelop.Projects/ProjectBuildTests.cs | 34 ++++++++++++++++------ 1 file changed, 25 insertions(+), 9 deletions(-) (limited to 'main') 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 (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 ("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 (); } /// @@ -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 ("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 (); } /// @@ -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 ("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 (); } -- cgit v1.2.3 From a2e1a827c3c9cde75e0107fabf82edbd51958bc7 Mon Sep 17 00:00:00 2001 From: Mikayla Hutchinson Date: Tue, 17 Jul 2018 19:07:53 -0400 Subject: [Ide] Update build/clean task messages --- .../MonoDevelop.Projects/Solution.cs | 23 ++++++++++++++-------- .../MonoDevelop.Projects/SolutionFolder.cs | 11 +++++++++-- .../MonoDevelop.Projects/SolutionItem.cs | 9 ++++++--- 3 files changed, 30 insertions(+), 13 deletions(-) (limited to 'main') diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/Solution.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/Solution.cs index f4c0340f94..1086aac0c2 100644 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/Solution.cs +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/Solution.cs @@ -928,7 +928,7 @@ namespace MonoDevelop.Projects /// /// Builds a set of SolutionItems from this solution and their dependencies. They will be built in parallel, and common dependencies will be deduplicated. /// - public async Task CleanItems (ProgressMonitor monitor, ConfigurationSelector configuration, IEnumerable items, OperationContext operationContext = null) + public async Task CleanItems (ProgressMonitor monitor, ConfigurationSelector configuration, IEnumerable items, OperationContext operationContext = null, string beginTaskMessage = null) { SolutionConfiguration slnConf = GetConfiguration (configuration); if (slnConf == null) @@ -945,7 +945,10 @@ namespace MonoDevelop.Projects if (operationContext == null) operationContext = new OperationContext (); - monitor.BeginTask (GettextCatalog.GetString ("Cleaning Solution: {0} ({1})", Name, configuration.ToString ()), sortedItems.Count); + 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; @@ -966,16 +969,16 @@ namespace MonoDevelop.Projects /// /// Builds a set of SolutionItems from this solution and their dependencies. They will be built in parallel, and common dependencies will be deduplicated. /// - public async Task BuildItems (ProgressMonitor monitor, ConfigurationSelector configuration, IEnumerable items, OperationContext operationContext = null) + public async Task BuildItems (ProgressMonitor monitor, ConfigurationSelector configuration, IEnumerable items, OperationContext operationContext = null, string beginTaskMessage = null) { SolutionConfiguration slnConf = GetConfiguration (configuration); if (slnConf == null) return new BuildResult (); - ReadOnlyCollection allProjects; + ReadOnlyCollection sortedItems; try { - allProjects = GetItemsAndDependenciesSortedForBuild (items, configuration); + sortedItems = GetItemsAndDependenciesSortedForBuild (items, configuration); } catch (CyclicDependencyException) { monitor.ReportError (GettextCatalog.GetString ("Cyclic dependencies are not supported."), null); return new BuildResult ("", 1, 1); @@ -990,16 +993,20 @@ namespace MonoDevelop.Projects try { if (Runtime.Preferences.SkipBuildingUnmodifiedProjects) - allProjects = allProjects.Where (si => { + 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 (GettextCatalog.GetString ("Building Solution: {0} ({1})", Name, configuration.ToString ()), allProjects.Count); + + 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, allProjects, (ProgressMonitor m, SolutionItem item) => { + return result = await RunParallelBuildOperation (monitor, configuration, sortedItems, (ProgressMonitor m, SolutionItem item) => { return item.Build (m, configuration, false, operationContext); }, false); diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/SolutionFolder.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/SolutionFolder.cs index ee7a1e1fd0..70c29a0808 100644 --- a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/SolutionFolder.cs +++ b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/SolutionFolder.cs @@ -580,7 +580,10 @@ namespace MonoDevelop.Projects var collected = new HashSet (); CollectBuildableEntries (collected, configuration, slnConf, false); - return ParentSolution.CleanItems (monitor, configuration, collected, operationContext); + return ParentSolution.CleanItems ( + monitor, configuration, collected, operationContext, + IsRoot ? GettextCatalog.GetString ("Cleaning solution {0} ({1})", Name, configuration.ToString ()) : null + ); } public Task Build (ProgressMonitor monitor, ConfigurationSelector configuration, bool buildReferencedTargets = false, OperationContext operationContext = null) @@ -593,7 +596,11 @@ namespace MonoDevelop.Projects var collected = new HashSet (); CollectBuildableEntries (collected, configuration, slnConf, false); - return ParentSolution.BuildItems (monitor, configuration, collected, operationContext); + + return ParentSolution.BuildItems ( + monitor, configuration, collected, operationContext, + IsRoot ? GettextCatalog.GetString ("Building solution {0} ({1})", Name, configuration.ToString ()) : null + ); } [Obsolete("This method will be removed in future releases")] diff --git a/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/SolutionItem.cs b/main/src/core/MonoDevelop.Core/MonoDevelop.Projects/SolutionItem.cs index 314b518bb5..b15280164f 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); @@ -642,7 +642,10 @@ namespace MonoDevelop.Projects ITimeTracker tt = Counters.BuildProjectAndReferencesTimer.BeginTiming ("Building " + Name, CreateProjectEventMetadata (solutionConfiguration)); try { operationStarted = await ParentSolution.BeginBuildOperation (monitor, solutionConfiguration, operationContext); - result = await ParentSolution.BuildItems (monitor, solutionConfiguration, new[] { this }, operationContext); + result = await ParentSolution.BuildItems ( + monitor, solutionConfiguration, new[] { this }, operationContext, + GettextCatalog.GetString ("Building {0} ({1})", Name, solutionConfiguration.ToString ()) + ); } finally { tt.End (); if (operationStarted) @@ -744,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) { -- cgit v1.2.3