diff --git a/src/Microsoft.VisualStudio.ProjectSystem.Managed/Microsoft.VisualStudio.ProjectSystem.Managed.csproj b/src/Microsoft.VisualStudio.ProjectSystem.Managed/Microsoft.VisualStudio.ProjectSystem.Managed.csproj
index 9fa046c5ac5..756260c60ee 100644
--- a/src/Microsoft.VisualStudio.ProjectSystem.Managed/Microsoft.VisualStudio.ProjectSystem.Managed.csproj
+++ b/src/Microsoft.VisualStudio.ProjectSystem.Managed/Microsoft.VisualStudio.ProjectSystem.Managed.csproj
@@ -43,6 +43,9 @@
ResolvedAnalyzerReference.xaml
+
+ ResolvedCompilationReference.xaml
+
ResolvedPackageReference.xaml
@@ -91,6 +94,9 @@
UpToDateCheckOutput.xaml
+
+ CopyUpToDateMarker.xaml
+
Folder.xaml
@@ -168,6 +174,10 @@
MSBuild:GenerateRuleSourceFromXaml
Designer
+
+ MSBuild:GenerateRuleSourceFromXaml
+ Designer
+
MSBuild:GenerateRuleSourceFromXaml
Designer
@@ -280,6 +290,10 @@
MSBuild:GenerateRuleSourceFromXaml
Designer
+
+ MSBuild:GenerateRuleSourceFromXaml
+ Designer
+
MSBuild:GenerateRuleSourceFromXaml
Designer
diff --git a/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/BuildUpToDateCheck.cs b/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/BuildUpToDateCheck.cs
index de76b2f7a3f..e689d1d8627 100644
--- a/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/BuildUpToDateCheck.cs
+++ b/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/BuildUpToDateCheck.cs
@@ -48,6 +48,7 @@ private void Log(LogLevel level, string message, params object[] values)
private const string ResolvedPath = "ResolvedPath";
private const string CopyToOutputDirectory = "CopyToOutputDirectory";
private const string Never = "Never";
+ private const string OriginalPath = "OriginalPath";
private static HashSet KnownOutputGroups = new HashSet
{
@@ -60,13 +61,17 @@ private void Log(LogLevel level, string message, params object[] values)
private static ImmutableHashSet ReferenceSchemas => ImmutableHashSet.Empty
.Add(ResolvedAnalyzerReference.SchemaName)
- .Add(ResolvedAssemblyReference.SchemaName)
- .Add(ResolvedCOMReference.SchemaName)
- .Add(ResolvedProjectReference.SchemaName);
+ .Add(ResolvedCompilationReference.SchemaName);
+
+ private static ImmutableHashSet UpToDateSchemas => ImmutableHashSet.Empty
+ .Add(CopyUpToDateMarker.SchemaName)
+ .Add(UpToDateCheckInput.SchemaName)
+ .Add(UpToDateCheckOutput.SchemaName);
private static ImmutableHashSet ProjectPropertiesSchemas => ImmutableHashSet.Empty
.Add(ConfigurationGeneral.SchemaName)
- .Union(ReferenceSchemas);
+ .Union(ReferenceSchemas)
+ .Union(UpToDateSchemas);
private readonly IProjectSystemOptions _projectSystemOptions;
private readonly ConfiguredProject _configuredProject;
@@ -79,11 +84,15 @@ private void Log(LogLevel level, string message, params object[] values)
private bool _isDisabled = true;
private bool _itemsChangedSinceLastCheck = true;
private string _msBuildProjectFullPath;
+ private string _markerFile;
private HashSet _imports = new HashSet();
private HashSet _itemTypes = new HashSet();
private Dictionary> _items = new Dictionary>();
- private Dictionary> _references = new Dictionary>();
+ private HashSet _customInputs = new HashSet();
private HashSet _customOutputs = new HashSet();
+ private HashSet _analyzerReferences = new HashSet();
+ private HashSet _compilationReferences = new HashSet();
+ private HashSet _copyReferenceInputs = new HashSet();
private Dictionary> _outputGroups = new Dictionary>();
[ImportingConstructor]
@@ -117,14 +126,50 @@ private void OnProjectChanged(IProjectSubscriptionUpdate e)
_isDisabled = disableFastUpToDateCheckString != null && string.Equals(disableFastUpToDateCheckString, TrueValue, StringComparison.OrdinalIgnoreCase);
_msBuildProjectFullPath = e.CurrentState.GetPropertyOrDefault(ConfigurationGeneral.SchemaName, ConfigurationGeneral.MSBuildProjectFullPathProperty, _msBuildProjectFullPath);
- foreach (var referenceSchema in ReferenceSchemas)
+
+ if (e.ProjectChanges.TryGetValue(ResolvedAnalyzerReference.SchemaName, out var changes) &&
+ changes.Difference.AnyChanges)
{
- if (e.ProjectChanges.TryGetValue(referenceSchema, out var changes) &&
- changes.Difference.AnyChanges)
+ _analyzerReferences = new HashSet(changes.After.Items.Select(item => item.Value[ResolvedPath]));
+ }
+
+ if (e.ProjectChanges.TryGetValue(ResolvedCompilationReference.SchemaName, out changes) &&
+ changes.Difference.AnyChanges)
+ {
+ _compilationReferences.Clear();
+ _copyReferenceInputs.Clear();
+
+ foreach (var item in changes.After.Items)
{
- _references[referenceSchema] = new HashSet(changes.After.Items.Select(item => item.Value[ResolvedPath]));
+ _compilationReferences.Add(item.Value[ResolvedPath]);
+ if (!string.IsNullOrWhiteSpace(item.Value[CopyUpToDateMarker.SchemaName]))
+ {
+ _copyReferenceInputs.Add(item.Value[CopyUpToDateMarker.SchemaName]);
+ }
+ if (!string.IsNullOrWhiteSpace(item.Value[OriginalPath]))
+ {
+ _copyReferenceInputs.Add(item.Value[OriginalPath]);
+ }
}
}
+
+ if (e.ProjectChanges.TryGetValue(UpToDateCheckInput.SchemaName, out var inputs) &&
+ inputs.Difference.AnyChanges)
+ {
+ _customInputs = new HashSet(inputs.After.Items.Select(item => item.Value[FullPath]));
+ }
+
+ if (e.ProjectChanges.TryGetValue(UpToDateCheckOutput.SchemaName, out var outputs) &&
+ outputs.Difference.AnyChanges)
+ {
+ _customOutputs = new HashSet(outputs.After.Items.Select(item => item.Value[FullPath]));
+ }
+
+ if (e.ProjectChanges.TryGetValue(CopyUpToDateMarker.SchemaName, out var upToDateMarkers) &&
+ upToDateMarkers.Difference.AnyChanges)
+ {
+ _markerFile = upToDateMarkers.After.Items.Count == 1 ? upToDateMarkers.After.Items.Single().Value[FullPath] : null;
+ }
}
private void OnProjectImportsChanged(IProjectImportTreeSnapshot e)
@@ -161,12 +206,6 @@ private void OnSourceItemChanged(IProjectSubscriptionUpdate e, IProjectItemSchem
_items[itemType.Key] = new HashSet(items);
_itemsChangedSinceLastCheck = true;
}
-
- if (e.ProjectChanges.TryGetValue(UpToDateCheckOutput.SchemaName, out var outputs) &&
- outputs.Difference.AnyChanges)
- {
- _customOutputs = new HashSet(outputs.After.Items.Select(item => item.Value[FullPath]));
- }
}
private void OnOutputGroupChanged(IImmutableDictionary e)
@@ -289,10 +328,9 @@ private HashSet CollectInputs(Logger logger)
AddInputs(logger, inputs, pair.Value, pair.Key);
}
- foreach (var pair in _references)
- {
- AddInputs(logger, inputs, pair.Value, pair.Key);
- }
+ AddInputs(logger, inputs, _analyzerReferences, ResolvedAnalyzerReference.SchemaName);
+ AddInputs(logger, inputs, _compilationReferences, ResolvedCompilationReference.SchemaName);
+ AddInputs(logger, inputs, _customInputs, UpToDateCheckInput.SchemaName);
return inputs;
}
@@ -306,12 +344,12 @@ private HashSet CollectOutputs(Logger logger)
AddOutputs(logger, outputs, pair.Value, pair.Key);
}
- AddOutputs(logger, outputs, _customOutputs, "UpToDateCheckOutput");
+ AddOutputs(logger, outputs, _customOutputs, UpToDateCheckOutput.SchemaName);
return outputs;
}
- private (DateTime? time, string path) GetLatestInput(HashSet inputs, IDictionary timestampCache)
+ private (DateTime? time, string path) GetLatestInput(HashSet inputs, IDictionary timestampCache, bool ignoreMissing = false)
{
DateTime? latest = DateTime.MinValue;
string latestPath = null;
@@ -319,7 +357,7 @@ private HashSet CollectOutputs(Logger logger)
foreach (var input in inputs)
{
var time = GetTimestamp(input, timestampCache);
- if (latest != null && (time == null || time > latest))
+ if (latest != null && (time == null && !ignoreMissing || time > latest))
{
latest = time;
latestPath = input;
@@ -347,6 +385,50 @@ private HashSet CollectOutputs(Logger logger)
return (earliest, earliestPath);
}
+ // Reference assembly copy markers are strange. The property is always going to be present on
+ // references to SDK-based projects, regardless of whether or not those referenced projects
+ // will actually produce a marker. And an item always will be present in an SDK-based project,
+ // regardless of whether or not the project produces a marker. So, basically, we only check
+ // here if the project actually produced a marker and we only check it against references that
+ // actually produced a marker.
+ private bool CheckMarkers(Logger logger, IDictionary timestampCache)
+ {
+ if (string.IsNullOrWhiteSpace(_markerFile) || !_copyReferenceInputs.Any())
+ {
+ return true;
+ }
+
+ foreach (var referenceMarkerFile in _copyReferenceInputs)
+ {
+ logger.Verbose("Found possible input marker '{0}'.", referenceMarkerFile);
+ }
+
+ logger.Verbose("Found possible output marker '{0}'.", _markerFile);
+
+ var latestInputMarker = GetLatestInput(_copyReferenceInputs, timestampCache, true);
+ var outputMarkerTime = GetTimestamp(_markerFile, timestampCache);
+
+ if (latestInputMarker.path != null)
+ {
+ logger.Info("Latest write timestamp on input marker is {0} on '{1}'.", latestInputMarker.time.Value, latestInputMarker.path);
+ }
+ else
+ {
+ logger.Info("No input markers exist, skipping marker check.");
+ }
+
+ if (outputMarkerTime != null)
+ {
+ logger.Info("Write timestamp on output marker is {0} on '{1}'.", outputMarkerTime, _markerFile);
+ }
+ else
+ {
+ logger.Info("Output marker '{0}' does not exist, skipping marker check.", _markerFile);
+ }
+
+ return latestInputMarker.path == null || outputMarkerTime == null || outputMarkerTime > latestInputMarker.time;
+ }
+
public async Task IsUpToDateAsync(BuildAction buildAction, TextWriter logWriter, CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
@@ -368,7 +450,7 @@ private HashSet CollectOutputs(Logger logger)
if (latestInput.time != null)
{
- logger.Info("Lastest write timestamp on input is {0} on '{1}'.", latestInput.time.Value, latestInput.path);
+ logger.Info("Latest write timestamp on input is {0} on '{1}'.", latestInput.time.Value, latestInput.path);
}
else
{
@@ -385,7 +467,13 @@ private HashSet CollectOutputs(Logger logger)
}
// We are up to date if the earliest output write happened after the latest input write
- var isUpToDate = latestInput.time != null && earliestOutput.time != null && earliestOutput.time > latestInput.time;
+ var markersUpToDate = CheckMarkers(logger, timestampCache);
+ var isUpToDate =
+ latestInput.time != null
+ && earliestOutput.time != null
+ && earliestOutput.time > latestInput.time
+ && markersUpToDate;
+
logger.Info("Project is{0} up to date.", (!isUpToDate ? " not" : ""));
return isUpToDate;
diff --git a/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/Rules/CopyUpToDateMarker.cs b/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/Rules/CopyUpToDateMarker.cs
new file mode 100644
index 00000000000..d5deeec97e8
--- /dev/null
+++ b/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/Rules/CopyUpToDateMarker.cs
@@ -0,0 +1,12 @@
+// 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.Diagnostics.CodeAnalysis;
+
+namespace Microsoft.VisualStudio.ProjectSystem
+{
+ [ExcludeFromCodeCoverage]
+ [SuppressMessage("Style", "IDE0016:Use 'throw' expression")]
+ partial class CopyUpToDateMarker
+ {
+ }
+}
diff --git a/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/Rules/CopyUpToDateMarker.xaml b/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/Rules/CopyUpToDateMarker.xaml
new file mode 100644
index 00000000000..7666a2d590c
--- /dev/null
+++ b/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/Rules/CopyUpToDateMarker.xaml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/Rules/ProjectItemsSchema.xaml b/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/Rules/ProjectItemsSchema.xaml
index 1c4600a8474..778f5f27eea 100644
--- a/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/Rules/ProjectItemsSchema.xaml
+++ b/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/Rules/ProjectItemsSchema.xaml
@@ -58,23 +58,9 @@
-
-
-
-
-
-
-
-
diff --git a/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/Rules/ResolvedCompilationReference.cs b/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/Rules/ResolvedCompilationReference.cs
new file mode 100644
index 00000000000..485904ab74e
--- /dev/null
+++ b/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/Rules/ResolvedCompilationReference.cs
@@ -0,0 +1,12 @@
+// 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.Diagnostics.CodeAnalysis;
+
+namespace Microsoft.VisualStudio.ProjectSystem
+{
+ [ExcludeFromCodeCoverage]
+ [SuppressMessage("Style", "IDE0016:Use 'throw' expression")]
+ partial class ResolvedCompilationReference
+ {
+ }
+}
diff --git a/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/Rules/ResolvedCompilationReference.xaml b/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/Rules/ResolvedCompilationReference.xaml
new file mode 100644
index 00000000000..a01b05d683b
--- /dev/null
+++ b/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/Rules/ResolvedCompilationReference.xaml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Targets/Microsoft.Managed.DesignTime.targets b/src/Targets/Microsoft.Managed.DesignTime.targets
index 874431234d7..6c825e74dde 100644
--- a/src/Targets/Microsoft.Managed.DesignTime.targets
+++ b/src/Targets/Microsoft.Managed.DesignTime.targets
@@ -135,6 +135,10 @@
File
+
+ File
+
+
File;ProjectSubscriptionService
@@ -240,6 +244,10 @@
ProjectSubscriptionService;BrowseObject
+
+
+ ProjectSubscriptionService
+
@@ -266,5 +274,8 @@
+
+
+
\ No newline at end of file