Skip to content

Commit 2fca1ab

Browse files
authored
Merge branch 'main' into darc-main-1378b6cf-3d0f-4e1f-a80c-0e817c450bb2
2 parents 12e3ea3 + 1414d33 commit 2fca1ab

File tree

52 files changed

+1376
-792
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+1376
-792
lines changed

src/BuiltInTools/DotNetWatchTasks/DotNetWatchTasks.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
</ItemGroup>
1111

1212
<ItemGroup>
13-
<Compile Include="..\dotnet-watch\Build\MSBuildFileSetResult.cs" />
13+
<Compile Include="..\dotnet-watch\Watch\MSBuildFileSetResult.cs" />
1414
</ItemGroup>
1515

1616
</Project>
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.DotNet.Watch;
5+
6+
internal static class PropertyNames
7+
{
8+
public const string TargetFramework = nameof(TargetFramework);
9+
public const string TargetFrameworkIdentifier = nameof(TargetFrameworkIdentifier);
10+
public const string TargetPath = nameof(TargetPath);
11+
public const string EnableDefaultItems = nameof(EnableDefaultItems);
12+
public const string TargetFrameworks = nameof(TargetFrameworks);
13+
public const string WebAssemblyHotReloadCapabilities = nameof(WebAssemblyHotReloadCapabilities);
14+
public const string TargetFrameworkVersion = nameof(TargetFrameworkVersion);
15+
public const string TargetName = nameof(TargetName);
16+
public const string IntermediateOutputPath = nameof(IntermediateOutputPath);
17+
public const string HotReloadAutoRestart = nameof(HotReloadAutoRestart);
18+
public const string DefaultItemExcludes = nameof(DefaultItemExcludes);
19+
public const string CustomCollectWatchItems = nameof(CustomCollectWatchItems);
20+
public const string UsingMicrosoftNETSdkRazor = nameof(UsingMicrosoftNETSdkRazor);
21+
public const string DotNetWatchContentFiles = nameof(DotNetWatchContentFiles);
22+
public const string DotNetWatchBuild = nameof(DotNetWatchBuild);
23+
public const string DesignTimeBuild = nameof(DesignTimeBuild);
24+
public const string SkipCompilerExecution = nameof(SkipCompilerExecution);
25+
public const string ProvideCommandLineArgs = nameof(ProvideCommandLineArgs);
26+
}
27+
28+
internal static class ItemNames
29+
{
30+
public const string Watch = nameof(Watch);
31+
public const string AdditionalFiles = nameof(AdditionalFiles);
32+
public const string Compile = nameof(Compile);
33+
public const string Content = nameof(Content);
34+
public const string ProjectCapability = nameof(ProjectCapability);
35+
}
36+
37+
internal static class MetadataNames
38+
{
39+
public const string Watch = nameof(Watch);
40+
}
41+
42+
internal static class TargetNames
43+
{
44+
public const string Compile = nameof(Compile);
45+
public const string Restore = nameof(Restore);
46+
public const string GenerateComputedBuildStaticWebAssets = nameof(GenerateComputedBuildStaticWebAssets);
47+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Collections;
5+
using Microsoft.Build.Framework;
6+
using Microsoft.Build.Logging;
7+
8+
namespace Microsoft.DotNet.Watch;
9+
10+
internal sealed class BuildReporter(IReporter reporter, EnvironmentOptions environmentOptions)
11+
{
12+
public IReporter Reporter => reporter;
13+
public EnvironmentOptions EnvironmentOptions => environmentOptions;
14+
15+
public Loggers GetLoggers(string projectPath, string operationName)
16+
=> new(reporter, environmentOptions.GetTestBinLogPath(projectPath, operationName));
17+
18+
public void ReportWatchedFiles(Dictionary<string, FileItem> fileItems)
19+
{
20+
reporter.Verbose($"Watching {fileItems.Count} file(s) for changes");
21+
22+
if (environmentOptions.TestFlags.HasFlag(TestFlags.RunningAsTest))
23+
{
24+
foreach (var file in fileItems.Values)
25+
{
26+
reporter.Verbose(file.StaticWebAssetPath != null
27+
? $"> {file.FilePath}{Path.PathSeparator}{file.StaticWebAssetPath}"
28+
: $"> {file.FilePath}");
29+
}
30+
}
31+
}
32+
33+
public sealed class Loggers(IReporter reporter, string? binLogPath) : IEnumerable<ILogger>, IDisposable
34+
{
35+
private readonly BinaryLogger? _binaryLogger = binLogPath != null
36+
? new()
37+
{
38+
Verbosity = LoggerVerbosity.Diagnostic,
39+
Parameters = "LogFile=" + binLogPath,
40+
}
41+
: null;
42+
43+
private readonly OutputLogger _outputLogger =
44+
new(reporter)
45+
{
46+
Verbosity = LoggerVerbosity.Minimal
47+
};
48+
49+
public void Dispose()
50+
{
51+
_outputLogger.Clear();
52+
}
53+
54+
public IEnumerator<ILogger> GetEnumerator()
55+
{
56+
yield return _outputLogger;
57+
58+
if (_binaryLogger != null)
59+
{
60+
yield return _binaryLogger;
61+
}
62+
}
63+
64+
public void ReportOutput()
65+
{
66+
if (binLogPath != null)
67+
{
68+
reporter.Verbose($"Binary log: '{binLogPath}'");
69+
}
70+
71+
_outputLogger.ReportOutput();
72+
}
73+
74+
IEnumerator IEnumerable.GetEnumerator()
75+
=> GetEnumerator();
76+
}
77+
78+
private sealed class OutputLogger : ConsoleLogger
79+
{
80+
private readonly IReporter _reporter;
81+
private readonly List<OutputLine> _messages = [];
82+
83+
public OutputLogger(IReporter reporter)
84+
{
85+
WriteHandler = Write;
86+
_reporter = reporter;
87+
}
88+
89+
public IReadOnlyList<OutputLine> Messages
90+
=> _messages;
91+
92+
public void Clear()
93+
=> _messages.Clear();
94+
95+
private void Write(string message)
96+
=> _messages.Add(new OutputLine(message.TrimEnd('\r', '\n'), IsError: false));
97+
98+
public void ReportOutput()
99+
{
100+
_reporter.Output($"MSBuild output:");
101+
BuildOutput.ReportBuildOutput(_reporter, Messages, success: false, projectDisplay: null);
102+
}
103+
}
104+
}

src/BuiltInTools/dotnet-watch/Build/EvaluationResult.cs

Lines changed: 138 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Collections.Immutable;
45
using Microsoft.Build.Graph;
56

67
namespace Microsoft.DotNet.Watch;
78

8-
internal sealed class EvaluationResult(IReadOnlyDictionary<string, FileItem> files, ProjectGraph? projectGraph)
9+
internal sealed class EvaluationResult(IReadOnlyDictionary<string, FileItem> files, ProjectGraph projectGraph)
910
{
1011
public readonly IReadOnlyDictionary<string, FileItem> Files = files;
11-
public readonly ProjectGraph? ProjectGraph = projectGraph;
12+
public readonly ProjectGraph ProjectGraph = projectGraph;
1213

1314
public readonly FilePathExclusions ItemExclusions
1415
= projectGraph != null ? FilePathExclusions.Create(projectGraph) : FilePathExclusions.Empty;
1516

1617
private readonly Lazy<IReadOnlySet<string>> _lazyBuildFiles
1718
= new(() => projectGraph != null ? CreateBuildFileSet(projectGraph) : new HashSet<string>());
1819

19-
public static IReadOnlySet<string> CreateBuildFileSet(ProjectGraph projectGraph)
20+
private static IReadOnlySet<string> CreateBuildFileSet(ProjectGraph projectGraph)
2021
=> projectGraph.ProjectNodes.SelectMany(p => p.ProjectInstance.ImportPaths)
2122
.Concat(projectGraph.ProjectNodes.Select(p => p.ProjectInstance.FullPath))
2223
.ToHashSet(PathUtilities.OSSpecificPathComparer);
@@ -29,4 +30,138 @@ public void WatchFiles(FileWatcher fileWatcher)
2930
fileWatcher.WatchContainingDirectories(Files.Keys, includeSubdirectories: true);
3031
fileWatcher.WatchFiles(BuildFiles);
3132
}
33+
34+
/// <summary>
35+
/// Loads project graph and performs design-time build.
36+
/// </summary>
37+
public static EvaluationResult? TryCreate(
38+
string rootProjectPath,
39+
IEnumerable<string> buildArguments,
40+
IReporter reporter,
41+
EnvironmentOptions environmentOptions,
42+
bool restore,
43+
CancellationToken cancellationToken)
44+
{
45+
var buildReporter = new BuildReporter(reporter, environmentOptions);
46+
47+
// See https://github.com/dotnet/project-system/blob/main/docs/well-known-project-properties.md
48+
49+
var globalOptions = CommandLineOptions.ParseBuildProperties(buildArguments)
50+
.ToImmutableDictionary(keySelector: arg => arg.key, elementSelector: arg => arg.value)
51+
.SetItem(PropertyNames.DotNetWatchBuild, "true")
52+
.SetItem(PropertyNames.DesignTimeBuild, "true")
53+
.SetItem(PropertyNames.SkipCompilerExecution, "true")
54+
.SetItem(PropertyNames.ProvideCommandLineArgs, "true")
55+
// F# targets depend on host path variable:
56+
.SetItem("DOTNET_HOST_PATH", environmentOptions.MuxerPath);
57+
58+
var projectGraph = ProjectGraphUtilities.TryLoadProjectGraph(
59+
rootProjectPath,
60+
globalOptions,
61+
reporter,
62+
projectGraphRequired: true,
63+
cancellationToken);
64+
65+
if (projectGraph == null)
66+
{
67+
return null;
68+
}
69+
70+
var rootNode = projectGraph.GraphRoots.Single();
71+
72+
if (restore)
73+
{
74+
using (var loggers = buildReporter.GetLoggers(rootNode.ProjectInstance.FullPath, "Restore"))
75+
{
76+
if (!rootNode.ProjectInstance.Build([TargetNames.Restore], loggers))
77+
{
78+
reporter.Error($"Failed to restore project '{rootProjectPath}'.");
79+
loggers.ReportOutput();
80+
return null;
81+
}
82+
}
83+
}
84+
85+
var fileItems = new Dictionary<string, FileItem>();
86+
87+
foreach (var project in projectGraph.ProjectNodesTopologicallySorted)
88+
{
89+
// Deep copy so that we can reuse the graph for building additional targets later on.
90+
// If we didn't copy the instance the targets might duplicate items that were already
91+
// populated by design-time build.
92+
var projectInstance = project.ProjectInstance.DeepCopy();
93+
94+
// skip outer build project nodes:
95+
if (projectInstance.GetPropertyValue(PropertyNames.TargetFramework) == "")
96+
{
97+
continue;
98+
}
99+
100+
var customCollectWatchItems = projectInstance.GetStringListPropertyValue(PropertyNames.CustomCollectWatchItems);
101+
102+
using (var loggers = buildReporter.GetLoggers(projectInstance.FullPath, "DesignTimeBuild"))
103+
{
104+
if (!projectInstance.Build([TargetNames.Compile, .. customCollectWatchItems], loggers))
105+
{
106+
reporter.Error($"Failed to build project '{projectInstance.FullPath}'.");
107+
loggers.ReportOutput();
108+
return null;
109+
}
110+
}
111+
112+
var projectPath = projectInstance.FullPath;
113+
var projectDirectory = Path.GetDirectoryName(projectPath)!;
114+
115+
// TODO: Compile and AdditionalItems should be provided by Roslyn
116+
var items = projectInstance.GetItems(ItemNames.Compile)
117+
.Concat(projectInstance.GetItems(ItemNames.AdditionalFiles))
118+
.Concat(projectInstance.GetItems(ItemNames.Watch));
119+
120+
foreach (var item in items)
121+
{
122+
AddFile(item.EvaluatedInclude, staticWebAssetPath: null);
123+
}
124+
125+
if (!environmentOptions.SuppressHandlingStaticContentFiles &&
126+
projectInstance.GetBooleanPropertyValue(PropertyNames.UsingMicrosoftNETSdkRazor) &&
127+
projectInstance.GetBooleanPropertyValue(PropertyNames.DotNetWatchContentFiles, defaultValue: true))
128+
{
129+
foreach (var item in projectInstance.GetItems(ItemNames.Content))
130+
{
131+
if (item.GetBooleanMetadataValue(MetadataNames.Watch, defaultValue: true))
132+
{
133+
var relativeUrl = item.EvaluatedInclude.Replace('\\', '/');
134+
if (relativeUrl.StartsWith("wwwroot/"))
135+
{
136+
AddFile(item.EvaluatedInclude, staticWebAssetPath: relativeUrl);
137+
}
138+
}
139+
}
140+
}
141+
142+
void AddFile(string include, string? staticWebAssetPath)
143+
{
144+
var filePath = Path.GetFullPath(Path.Combine(projectDirectory, include));
145+
146+
if (!fileItems.TryGetValue(filePath, out var existingFile))
147+
{
148+
fileItems.Add(filePath, new FileItem
149+
{
150+
FilePath = filePath,
151+
ContainingProjectPaths = [projectPath],
152+
StaticWebAssetPath = staticWebAssetPath,
153+
});
154+
}
155+
else if (!existingFile.ContainingProjectPaths.Contains(projectPath))
156+
{
157+
// linked files might be included to multiple projects:
158+
existingFile.ContainingProjectPaths.Add(projectPath);
159+
}
160+
}
161+
}
162+
163+
buildReporter.ReportWatchedFiles(fileItems);
164+
165+
return new EvaluationResult(fileItems, projectGraph);
166+
}
32167
}

0 commit comments

Comments
 (0)