diff --git a/src/Microsoft.VisualStudio.Editors/OptionPages/GeneralOptionPageControl.xaml b/src/Microsoft.VisualStudio.Editors/OptionPages/GeneralOptionPageControl.xaml index 57e38860ffe..16153962a0d 100644 --- a/src/Microsoft.VisualStudio.Editors/OptionPages/GeneralOptionPageControl.xaml +++ b/src/Microsoft.VisualStudio.Editors/OptionPages/GeneralOptionPageControl.xaml @@ -6,7 +6,7 @@ - + diff --git a/src/Microsoft.VisualStudio.Editors/OptionPages/GeneralOptionPageControl.xaml.vb b/src/Microsoft.VisualStudio.Editors/OptionPages/GeneralOptionPageControl.xaml.vb index a004504a7c5..031f3576eda 100644 --- a/src/Microsoft.VisualStudio.Editors/OptionPages/GeneralOptionPageControl.xaml.vb +++ b/src/Microsoft.VisualStudio.Editors/OptionPages/GeneralOptionPageControl.xaml.vb @@ -18,7 +18,7 @@ Namespace Microsoft.VisualStudio.Editors.OptionPages Dim binding = New Binding() With { .Source = _generalOptions, - .Path = New Windows.PropertyPath(NameOf(GeneralOptions.FastUpToDateCheckDisabled)), + .Path = New Windows.PropertyPath(NameOf(GeneralOptions.FastUpToDateCheckEnabled)), .UpdateSourceTrigger = UpdateSourceTrigger.Explicit } @@ -27,11 +27,11 @@ Namespace Microsoft.VisualStudio.Editors.OptionPages binding = New Binding() With { .Source = _generalOptions, - .Path = New Windows.PropertyPath(NameOf(GeneralOptions.OutputPaneEnabled)), + .Path = New Windows.PropertyPath(NameOf(GeneralOptions.VerboseFastUpToDateLogging)), .UpdateSourceTrigger = UpdateSourceTrigger.Explicit } - bindingExpression = OutputPane.SetBinding(CheckBox.IsCheckedProperty, binding) + bindingExpression = VerboseFastUpToDateLogging.SetBinding(CheckBox.IsCheckedProperty, binding) AddBinding(bindingExpression) End Sub End Class diff --git a/src/Microsoft.VisualStudio.Editors/OptionPages/GeneralOptionPageResources.Designer.vb b/src/Microsoft.VisualStudio.Editors/OptionPages/GeneralOptionPageResources.Designer.vb index 6f0854cb439..92120bb8c67 100644 --- a/src/Microsoft.VisualStudio.Editors/OptionPages/GeneralOptionPageResources.Designer.vb +++ b/src/Microsoft.VisualStudio.Editors/OptionPages/GeneralOptionPageResources.Designer.vb @@ -49,23 +49,23 @@ Namespace My.Resources Return resourceMan End Get End Property - + ''' ''' Overrides the current thread's CurrentUICulture property for all ''' resource lookups using this strongly typed resource class. ''' - _ + Public Shared Property Culture() As Global.System.Globalization.CultureInfo Get Return resourceCulture End Get Set - resourceCulture = value + resourceCulture = Value End Set End Property - + ''' - ''' Looks up a localized string similar to Always call MSBuild, even if a project appears to be up to date.. + ''' Looks up a localized string similar to Don't call MSBuild if a project appears to be up to date.. ''' Public Shared ReadOnly Property General_FastUpToDateCheck() As String Get @@ -74,11 +74,11 @@ Namespace My.Resources End Property ''' - ''' Looks up a localized string similar to Log project information to the Output window.. + ''' Looks up a localized string similar to Log diagnostic information for project up to date checks to the output window.. ''' - Public Shared ReadOnly Property General_OutputPane() As String + Public Shared ReadOnly Property General_VerboseFastUpToDateLogging() As String Get - Return ResourceManager.GetString("General_OutputPane", resourceCulture) + Return ResourceManager.GetString("General_VerboseFastUpToDateLogging", resourceCulture) End Get End Property End Class diff --git a/src/Microsoft.VisualStudio.Editors/OptionPages/GeneralOptionPageResources.resx b/src/Microsoft.VisualStudio.Editors/OptionPages/GeneralOptionPageResources.resx index ebb5f0f6ff2..bdba7df8263 100644 --- a/src/Microsoft.VisualStudio.Editors/OptionPages/GeneralOptionPageResources.resx +++ b/src/Microsoft.VisualStudio.Editors/OptionPages/GeneralOptionPageResources.resx @@ -118,9 +118,9 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Always call MSBuild, even if a project appears to be up to date. + Don't call MSBuild if a project appears to be up to date. - - Log project information to the Output window. + + Log diagnostic information for project up to date checks to the output window. \ No newline at end of file diff --git a/src/Microsoft.VisualStudio.Editors/OptionPages/GeneralOptions.vb b/src/Microsoft.VisualStudio.Editors/OptionPages/GeneralOptions.vb index 798a3270e52..bda823eed99 100644 --- a/src/Microsoft.VisualStudio.Editors/OptionPages/GeneralOptions.vb +++ b/src/Microsoft.VisualStudio.Editors/OptionPages/GeneralOptions.vb @@ -9,26 +9,26 @@ Namespace Microsoft.VisualStudio.Editors.OptionPages Private Class SVsSettingsPersistenceManager End Class - Private Const FastUpToDateSettingKey As String = "ManagedProjectSystem\FastUpToDateCheckDisabled" - Private Const OutputPaneSettingKey As String = "ManagedProjectSystem\OutputPaneEnabled" + Private Const FastUpToDateEnabledSettingKey As String = "ManagedProjectSystem\FastUpToDateCheckEnabled" + Private Const VerboseFastUpToDateLoggingSettingKey As String = "ManagedProjectSystem\VerboseFastUpToDateLogging" Private ReadOnly _settingsManager As ISettingsManager - Public Property FastUpToDateCheckDisabled As Boolean + Public Property FastUpToDateCheckEnabled As Boolean Get - Return If(_settingsManager?.GetValueOrDefault(FastUpToDateSettingKey, False), False) + Return If(_settingsManager?.GetValueOrDefault(FastUpToDateEnabledSettingKey, True), True) End Get Set - _settingsManager.SetValueAsync(FastUpToDateSettingKey, Value, isMachineLocal:=False) + _settingsManager.SetValueAsync(FastUpToDateEnabledSettingKey, Value, isMachineLocal:=False) End Set End Property - Public Property OutputPaneEnabled As Boolean + Public Property VerboseFastUpToDateLogging As Boolean Get - Return If(_settingsManager?.GetValueOrDefault(OutputPaneSettingKey, False), False) + Return If(_settingsManager?.GetValueOrDefault(VerboseFastUpToDateLoggingSettingKey, False), False) End Get Set - _settingsManager.SetValueAsync(OutputPaneSettingKey, Value, isMachineLocal:=False) + _settingsManager.SetValueAsync(VerboseFastUpToDateLoggingSettingKey, Value, isMachineLocal:=False) End Set End Property diff --git a/src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/EnvironmentVariableProjectSystemOptions.cs b/src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/EnvironmentVariableProjectSystemOptions.cs deleted file mode 100644 index 9c68be6d90c..00000000000 --- a/src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/EnvironmentVariableProjectSystemOptions.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.ComponentModel.Composition; -using Microsoft.VisualStudio.ProjectSystem.Utilities; - -namespace Microsoft.VisualStudio.ProjectSystem.VS -{ - [Export(typeof(IProjectSystemOptions))] - internal class EnvironmentVariableProjectSystemOptions : IProjectSystemOptions - { - private readonly IEnvironmentHelper _environment; - - [ImportingConstructor] - public EnvironmentVariableProjectSystemOptions(IEnvironmentHelper environment) - { - Requires.NotNull(environment, nameof(environment)); - - _environment = environment; - } - - public bool IsProjectOutputPaneEnabled - { - get - { -#if DEBUG - return true; -#else - return IsEnabled("PROJECTSYSTEM_PROJECTOUTPUTPANEENABLED"); -#endif - } - } - - private bool IsEnabled(string variable) - { - string value = _environment.GetEnvironmentVariable(variable); - - return string.Equals(value, "1", StringComparison.OrdinalIgnoreCase); - } - } -} diff --git a/src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/ProjectSystemOptions.cs b/src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/ProjectSystemOptions.cs new file mode 100644 index 00000000000..ea98298d143 --- /dev/null +++ b/src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/ProjectSystemOptions.cs @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.ComponentModel.Composition; +using Microsoft.VisualStudio.ProjectSystem.Utilities; +using Microsoft.VisualStudio.Settings; +using Microsoft.VisualStudio.Shell; +using System.Runtime.InteropServices; +using System.Threading.Tasks; + +namespace Microsoft.VisualStudio.ProjectSystem.VS +{ + [Export(typeof(IProjectSystemOptions))] + internal class ProjectSystemOptions : IProjectSystemOptions + { + [Guid("9B164E40-C3A2-4363-9BC5-EB4039DEF653")] + private class SVsSettingsPersistenceManager { } + + private const string FastUpToDateEnabledSettingKey = @"ManagedProjectSystem\FastUpToDateCheckEnabled"; + private const string VerboseFastUpToDateLoggingSettingKey = @"ManagedProjectSystem\VerboseFastUpToDateLogging"; + + private readonly IVsOptionalService _settingsManager; + private readonly IEnvironmentHelper _environment; + + [ImportingConstructor] + private ProjectSystemOptions(IEnvironmentHelper environment, IVsOptionalService settingsManager) + { + Requires.NotNull(environment, nameof(environment)); + + _environment = environment; + _settingsManager = settingsManager; + } + + public bool IsProjectOutputPaneEnabled + { + get + { +#if DEBUG + return true; +#else + return IsEnabled("PROJECTSYSTEM_PROJECTOUTPUTPANEENABLED"); +#endif + } + } + + public async Task GetIsFastUpToDateCheckEnabledAsync() + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + return _settingsManager.Value?.GetValueOrDefault(FastUpToDateEnabledSettingKey, true) ?? true; + } + + public async Task GetVerboseFastUpToDateLoggingAsync() + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + return _settingsManager.Value?.GetValueOrDefault(VerboseFastUpToDateLoggingSettingKey, false) ?? false; + } + + private bool IsEnabled(string variable) + { + string value = _environment.GetEnvironmentVariable(variable); + + return string.Equals(value, "1", StringComparison.OrdinalIgnoreCase); + } + } +} diff --git a/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/BuildUpToDateCheck.cs b/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/BuildUpToDateCheck.cs new file mode 100644 index 00000000000..dfa2430a0d1 --- /dev/null +++ b/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/BuildUpToDateCheck.cs @@ -0,0 +1,219 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.VisualStudio.ProjectSystem.Build; +using Microsoft.VisualStudio.ProjectSystem.References; +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Build.Framework; +using System.Collections.Immutable; + +namespace Microsoft.VisualStudio.ProjectSystem +{ + [AppliesTo(ProjectCapability.CSharpOrVisualBasicOrFSharp)] + [Export(typeof(IBuildUpToDateCheckProvider))] + internal class BuildUpToDateCheck : IBuildUpToDateCheckProvider + { + private static string[] KnownOutputGroups = { "Symbols", "Built", "ContentFiles", "Documentation", "LocalizedResourceDlls" }; + + private readonly IProjectLockService _projectLockService; + private readonly IProjectSystemOptions _projectSystemOptions; + private readonly ConfiguredProject _configuredProject; + private readonly ProjectProperties _projectProperties; + private readonly IProjectItemProvider _projectItemProvider; + private readonly IProjectItemSchemaService _projectItemsSchema; + private readonly Lazy _fileTimestampCache; + + [ImportingConstructor] + public BuildUpToDateCheck( + IProjectLockService projectLockService, + IProjectSystemOptions projectSystemOptions, + ConfiguredProject configuredProject, + ProjectProperties projectProperties, + [Import(ExportContractNames.ProjectItemProviders.SourceFiles)] IProjectItemProvider projectItemProvider, + IProjectItemSchemaService projectItemSchema, + Lazy fileTimestampCache) + { + _projectLockService = projectLockService; + _projectSystemOptions = projectSystemOptions; + _configuredProject = configuredProject; + _projectProperties = projectProperties; + _projectItemProvider = projectItemProvider; + _projectItemsSchema = projectItemSchema; + _fileTimestampCache = fileTimestampCache; + } + + private void Log(TextWriter logger, string message, params object[] values) => logger?.WriteLine("FastUpToDate: " + string.Format(message, values)); + + private DateTime? GetLatestTimestamp(TextWriter logger, IEnumerable paths, IDictionary timestampCache) + { + var latestTime = DateTime.MinValue; + + foreach (var path in paths) + { + if (!timestampCache.TryGetValue(path, out var time)) + { + var info = new FileInfo(path); + if (info.Exists) + { + time = info.LastWriteTimeUtc; + timestampCache[path] = time; + Log(logger, "Path '{0}' had live timestamp '{1}'.", path, time); + } + else + { + Log(logger, "Path '{0}' did not exist.", path); + return null; + } + } + else + { + Log(logger, "Path '{0}' had cached timestamp '{1}'.", path, time); + } + + if (latestTime < time) + { + latestTime = time; + } + } + + return latestTime; + } + + private async Task CheckReferencesAsync(TextWriter logger, string name, IResolvableReferencesService service, HashSet inputs) + where TUnresolvedReference : IProjectItem, TResolvedReference + where TResolvedReference : class, IReference + { + if (service != null) + { + Log(logger, "Checking reference type '{0}'.", name); + + foreach (var resolvedReference in await service.GetResolvedReferencesAsync()) + { + var fullPath = await resolvedReference.GetFullPathAsync(); + Log(logger, "Adding input reference '{0}'.", fullPath); + inputs.Add(fullPath); + } + } + } + + public async Task IsUpToDateAsync(BuildAction buildAction, TextWriter logger, CancellationToken cancellationToken = default(CancellationToken)) + { + EventHandler designTimeBuildNotifier = (sender, e) => { Log(logger, "Design time build started!"); }; + cancellationToken.ThrowIfCancellationRequested(); + + if (buildAction != BuildAction.Build) + { + return false; + } + + if (!await _projectSystemOptions.GetVerboseFastUpToDateLoggingAsync()) + { + logger = null; + } + + using (var access = await _projectLockService.ReadLockAsync(cancellationToken)) + { + var project = await access.GetProjectAsync(_configuredProject, cancellationToken); + var timestampCache = _fileTimestampCache != null ? _fileTimestampCache.Value.TimestampCache : new Dictionary(StringComparer.OrdinalIgnoreCase); + + Log(logger, "Starting check for project '{0}'.", project.FullPath); + + ConfigurationGeneral general = await _projectProperties.GetConfigurationGeneralPropertiesAsync(); + + if ((bool?)await general.DisableFastUpToDateCheck.GetValueAsync() == true) + { + Log(logger, "Disabled because the 'DisableFastUpToDateCheckProperty' property is true."); + return false; + } + + var inputs = new HashSet(); + + // add the project file + if (!string.IsNullOrEmpty(_configuredProject.UnconfiguredProject.FullPath)) + { + inputs.Add(project.FullPath); + Log(logger, "Adding input project file {0}.", project.FullPath); + } + + IEnumerable imports = project.Imports.Select(i => i.ImportedProject.FullPath); + foreach (var import in imports) + { + Log(logger, "Adding input project import {0}.", import); + inputs.Add(import); + } + + var projectItemSchemaValue = (await _projectItemsSchema.GetSchemaAsync(cancellationToken)).Value; + + foreach (var item in await _projectItemProvider.GetItemsAsync()) + { + var itemType = projectItemSchemaValue.GetItemType(item); + if (itemType != null && itemType.UpToDateCheckInput) + { + var path = item.EvaluatedIncludeAsFullPath; + Log(logger, "Input item type '{0}' path '{1}'.", itemType.Name, path); + inputs.Add(path); + } + } + + await CheckReferencesAsync(logger, nameof(_configuredProject.Services.AssemblyReferences), _configuredProject.Services.AssemblyReferences, inputs); + await CheckReferencesAsync(logger, nameof(_configuredProject.Services.ComReferences), _configuredProject.Services.ComReferences, inputs); + await CheckReferencesAsync(logger, nameof(_configuredProject.Services.PackageReferences), _configuredProject.Services.PackageReferences, inputs); + await CheckReferencesAsync(logger, nameof(_configuredProject.Services.ProjectReferences), _configuredProject.Services.ProjectReferences, inputs); + await CheckReferencesAsync(logger, nameof(_configuredProject.Services.SdkReferences), _configuredProject.Services.SdkReferences, inputs); + await CheckReferencesAsync(logger, nameof(_configuredProject.Services.WinRTReferences), _configuredProject.Services.WinRTReferences, inputs); + + // UpToDateCheckInput is the special item group for customized projects to add explicit inputs + var upToDateCheckInputItems = project.GetItems("UpToDateCheckInput").Select(file => file.GetMetadataValue("FullPath")); + + Log(logger, "Checking item type 'UpToDateCheckInput'."); + foreach (var upToDateCheckInputItem in upToDateCheckInputItems) + { + Log(logger, "Input item path '{0}'.", upToDateCheckInputItem); + inputs.Add(upToDateCheckInputItem); + } + + var latestInput = GetLatestTimestamp(logger, inputs, timestampCache); + if (latestInput != null) + { + Log(logger, "Lastest write timestamp on input is {0}.", latestInput.Value); + } + + var outputs = new HashSet(); + + outputs.AddRange(project.GetItems("UpToDateCheckOutput") + .Select(file => file.GetMetadataValue("FullPath"))); + + IOutputGroupsService outputGroupsService = _configuredProject.Services.OutputGroups; + foreach (var outputGroup in KnownOutputGroups) + { + Log(logger, "Checking known output group {0}.", outputGroup); + + foreach (var output in (await outputGroupsService.GetOutputGroupAsync(outputGroup, cancellationToken)).Outputs) + { + Log(logger, "Output path '{0}'.", output.Key); + outputs.Add(output.Key); + } + } + + var latestOutput = GetLatestTimestamp(logger, outputs, timestampCache); + if (latestOutput != null) + { + Log(logger, "Lastest write timestamp on output is {0}.", latestOutput.Value); + } + + var isUpToDate = latestOutput != null && latestInput != null && latestOutput.Value >= latestInput.Value; + Log(logger, "Project is{0} up to date.", (!isUpToDate ? " not" : "")); + + return isUpToDate; + } + } + + public async Task IsUpToDateCheckEnabledAsync(CancellationToken cancellationToken = default(CancellationToken)) => + await _projectSystemOptions.GetIsFastUpToDateCheckEnabledAsync(); + } +} diff --git a/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/IProjectSystemOptions.cs b/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/IProjectSystemOptions.cs index 87e2d806472..b22930fb268 100644 --- a/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/IProjectSystemOptions.cs +++ b/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/IProjectSystemOptions.cs @@ -1,5 +1,7 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Threading.Tasks; + namespace Microsoft.VisualStudio.ProjectSystem { /// @@ -17,5 +19,21 @@ bool IsProjectOutputPaneEnabled { get; } + + /// + /// Gets a value indicating if the project fast up to date check is enabled. + /// + /// + /// if the project fast up to date check is enabled; otherwise, + /// + Task GetIsFastUpToDateCheckEnabledAsync(); + + /// + /// Gets a value indicating if fast up to date check logging should be verbose. + /// + /// + /// if the project fast up to date logging is verbose; otherwise, + /// + Task GetVerboseFastUpToDateLoggingAsync(); } } diff --git a/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/Rules/ConfigurationGeneral.xaml b/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/Rules/ConfigurationGeneral.xaml index 32584d3ef33..f5e0de66fb7 100644 --- a/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/Rules/ConfigurationGeneral.xaml +++ b/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/Rules/ConfigurationGeneral.xaml @@ -55,6 +55,11 @@ + + + + + diff --git a/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/Rules/ProjectItemsSchema.xaml b/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/Rules/ProjectItemsSchema.xaml index 778f5f27eea..aa31df800a1 100644 --- a/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/Rules/ProjectItemsSchema.xaml +++ b/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/Rules/ProjectItemsSchema.xaml @@ -58,7 +58,7 @@ - +