diff --git a/src/Components/Blazor/Server/src/AutoRebuild/AutoRebuildExtensions.cs b/src/Components/Blazor/Server/src/AutoRebuild/AutoRebuildExtensions.cs deleted file mode 100644 index 6a43600bb9..0000000000 --- a/src/Components/Blazor/Server/src/AutoRebuild/AutoRebuildExtensions.cs +++ /dev/null @@ -1,184 +0,0 @@ -// Copyright (c) .NET Foundation. 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.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Blazor.Server; -using Microsoft.AspNetCore.Blazor.Server.AutoRebuild; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; - -namespace Microsoft.AspNetCore.Builder -{ - internal static class AutoRebuildExtensions - { - // Note that we don't need to watch typical static-file extensions (.css, .js, etc.) - // because anything in wwwroot is just served directly from disk on each reload. - // TODO: Make the set of extensions and exclusions configurable in csproj - private static string[] _includedSuffixes = new[] { ".cs", ".cshtml" }; - private static string[] _excludedDirectories = new[] { "obj", "bin" }; - - // To ensure the FileSystemWatchers aren't collected, reference them - // in this static list. They never need to be removed because there's no - // way to remove middleware once it's registered. - private static List _uncollectableWatchers = new List(); - - public static void UseHostedAutoRebuild(this IApplicationBuilder app, BlazorConfig config, string hostAppContentRootPath) - { - var isFirstFileWrite = true; - WatchFileSystem(config, () => - { - if (isFirstFileWrite) - { - try - { - // Touch any .cs file to force the host project to rebuild - // (which in turn rebuilds the client, since it's referenced) - var fileToTouch = Directory.EnumerateFiles( - hostAppContentRootPath, - "*.cs", - SearchOption.AllDirectories).FirstOrDefault(); - - if (!string.IsNullOrEmpty(fileToTouch)) - { - File.SetLastWriteTime(fileToTouch, DateTime.Now); - } - } - catch (Exception ex) - { - // If we don't have permission to write these files, autorebuild will not be enabled - var loggerFactory = app.ApplicationServices.GetRequiredService(); - var logger = loggerFactory.CreateLogger(typeof (AutoRebuildExtensions)); - logger?.LogWarning(ex, - "Cannot autorebuild because there was an error when writing to a file in '{0}'.", - hostAppContentRootPath); - } - - isFirstFileWrite = false; - } - }); - } - - public static void UseDevServerAutoRebuild(this IApplicationBuilder app, BlazorConfig config) - { - // Currently this only supports VS for Windows. Later on we can add - // an IRebuildService implementation for VS for Mac, etc. - if (!VSForWindowsRebuildService.TryCreate(out var rebuildService)) - { - return; // You're not on Windows, or you didn't launch this process from VS - } - - // Assume we're up to date when the app starts. - var buildToken = new RebuildToken(new DateTime(1970, 1, 1)) { BuildTask = Task.CompletedTask, }; - - WatchFileSystem(config, () => - { - // Don't start the recompilation immediately. We only start it when the next - // HTTP request arrives, because it's annoying if the IDE is constantly rebuilding - // when you're making changes to multiple files and aren't ready to reload - // in the browser yet. - // - // Replacing the token means that new requests that come in will trigger a rebuild, - // and will all 'join' that build until a new file change occurs. - buildToken = new RebuildToken(DateTime.Now); - }); - - app.Use(async (context, next) => - { - try - { - var token = buildToken; - if (token.BuildTask == null) - { - // The build is out of date, but a new build is not yet started. - // - // We can count on VS to only allow one build at a time, this is a safe race - // because if we request a second concurrent build, it will 'join' the current one. - var task = rebuildService.PerformRebuildAsync( - config.SourceMSBuildPath, - token.LastChange); - token.BuildTask = task; - } - - // In the general case it's safe to await this task, it will be a completed task - // if everything is up to date. - await token.BuildTask; - } - catch (Exception) - { - // If there's no listener on the other end of the pipe, or if anything - // else goes wrong, we just let the incoming request continue. - // There's nowhere useful to log this information so if people report - // problems we'll just have to get a repro and debug it. - // If it was an error on the VS side, it logs to the output window. - } - - await next(); - }); - } - - private static void WatchFileSystem(BlazorConfig config, Action onWrite) - { - var clientAppRootDir = Path.GetDirectoryName(config.SourceMSBuildPath); - var excludePathPrefixes = _excludedDirectories.Select(subdir - => Path.Combine(clientAppRootDir, subdir) + Path.DirectorySeparatorChar); - - var fsw = new FileSystemWatcher(clientAppRootDir); - fsw.Created += OnEvent; - fsw.Changed += OnEvent; - fsw.Deleted += OnEvent; - fsw.Renamed += OnEvent; - fsw.IncludeSubdirectories = true; - fsw.EnableRaisingEvents = true; - - // Ensure the watcher is not GCed for as long as the app lives - lock (_uncollectableWatchers) - { - _uncollectableWatchers.Add(fsw); - } - - void OnEvent(object sender, FileSystemEventArgs eventArgs) - { - if (!File.Exists(eventArgs.FullPath)) - { - // It's probably a directory rather than a file - return; - } - - if (!_includedSuffixes.Any(ext => eventArgs.Name.EndsWith(ext, StringComparison.OrdinalIgnoreCase))) - { - // Not a candidate file type - return; - } - - if (excludePathPrefixes.Any(prefix => eventArgs.FullPath.StartsWith(prefix, StringComparison.Ordinal))) - { - // In an excluded subdirectory - return; - } - - onWrite(); - } - } - - // Represents a three-state value for the state of the build - // - // BuildTask == null means the build is out of date, but no build has started - // BuildTask.IsCompleted == false means the build has been started, but has not completed - // BuildTask.IsCompleted == true means the build has completed - private class RebuildToken - { - public RebuildToken(DateTime lastChange) - { - LastChange = lastChange; - } - - public DateTime LastChange { get; } - - public Task BuildTask; - } - } -} diff --git a/src/Components/Blazor/Server/src/AutoRebuild/IRebuildService.cs b/src/Components/Blazor/Server/src/AutoRebuild/IRebuildService.cs deleted file mode 100644 index 6e9616e4bf..0000000000 --- a/src/Components/Blazor/Server/src/AutoRebuild/IRebuildService.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) .NET Foundation. 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.Threading.Tasks; - -namespace Microsoft.AspNetCore.Blazor.Server.AutoRebuild -{ - /// - /// Represents a mechanism for rebuilding a .NET project. For example, it - /// could be a way of signalling to a VS process to perform a build. - /// - internal interface IRebuildService - { - Task PerformRebuildAsync(string projectFullPath, DateTime ifNotBuiltSince); - } -} diff --git a/src/Components/Blazor/Server/src/AutoRebuild/ProcessUtils.cs b/src/Components/Blazor/Server/src/AutoRebuild/ProcessUtils.cs deleted file mode 100644 index af63853ed9..0000000000 --- a/src/Components/Blazor/Server/src/AutoRebuild/ProcessUtils.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) .NET Foundation. 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; -using System.Diagnostics; -using System.Runtime.InteropServices; - -namespace Microsoft.AspNetCore.Blazor.Server.AutoRebuild -{ - internal static class ProcessUtils - { - // Based on https://stackoverflow.com/a/3346055 - - public static Process GetParent(Process process) - { - var result = new ProcessBasicInformation(); - var handle = process.Handle; - var status = NtQueryInformationProcess(handle, 0, ref result, Marshal.SizeOf(result), out var returnLength); - if (status != 0) - { - throw new Win32Exception(status); - } - - try - { - var parentProcessId = result.InheritedFromUniqueProcessId.ToInt32(); - return parentProcessId > 0 ? Process.GetProcessById(parentProcessId) : null; - } - catch (ArgumentException) - { - return null; // Process not found - } - } - - [DllImport("ntdll.dll")] - private static extern int NtQueryInformationProcess(IntPtr processHandle, int processInformationClass, ref ProcessBasicInformation processInformation, int processInformationLength, out int returnLength); - - [StructLayout(LayoutKind.Sequential)] - struct ProcessBasicInformation - { - // These members must match PROCESS_BASIC_INFORMATION - public IntPtr Reserved1; - public IntPtr PebBaseAddress; - public IntPtr Reserved2_0; - public IntPtr Reserved2_1; - public IntPtr UniqueProcessId; - public IntPtr InheritedFromUniqueProcessId; - } - } -} diff --git a/src/Components/Blazor/Server/src/AutoRebuild/StreamProtocolExtensions.cs b/src/Components/Blazor/Server/src/AutoRebuild/StreamProtocolExtensions.cs deleted file mode 100644 index 394b26073d..0000000000 --- a/src/Components/Blazor/Server/src/AutoRebuild/StreamProtocolExtensions.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) .NET Foundation. 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.IO; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.AspNetCore.Blazor.Server.AutoRebuild -{ - internal static class StreamProtocolExtensions - { - public static async Task WriteStringAsync(this Stream stream, string str) - { - var utf8Bytes = Encoding.UTF8.GetBytes(str); - await stream.WriteAsync(BitConverter.GetBytes(utf8Bytes.Length), 0, 4); - await stream.WriteAsync(utf8Bytes, 0, utf8Bytes.Length); - } - - public static async Task WriteDateTimeAsync(this Stream stream, DateTime value) - { - var ticksBytes = BitConverter.GetBytes(value.Ticks); - await stream.WriteAsync(ticksBytes, 0, 8); - } - - public static async Task ReadBoolAsync(this Stream stream) - { - var responseBuf = new byte[1]; - await stream.ReadAsync(responseBuf, 0, 1); - return responseBuf[0] == 1; - } - - public static async Task ReadIntAsync(this Stream stream) - { - var responseBuf = new byte[4]; - await stream.ReadAsync(responseBuf, 0, 4); - return BitConverter.ToInt32(responseBuf, 0); - } - } -} diff --git a/src/Components/Blazor/Server/src/AutoRebuild/VSForWindowsRebuildService.cs b/src/Components/Blazor/Server/src/AutoRebuild/VSForWindowsRebuildService.cs deleted file mode 100644 index 92d99d57dd..0000000000 --- a/src/Components/Blazor/Server/src/AutoRebuild/VSForWindowsRebuildService.cs +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright (c) .NET Foundation. 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.Diagnostics; -using System.IO.Pipes; -using System.Runtime.InteropServices; -using System.Threading.Tasks; - -namespace Microsoft.AspNetCore.Blazor.Server.AutoRebuild -{ - /// - /// Finds the VS process that launched this app process (if any), and uses - /// named pipes to communicate with its AutoRebuild listener (if any). - /// - internal class VSForWindowsRebuildService : IRebuildService - { - private const int _connectionTimeoutMilliseconds = 3000; - private readonly Process _vsProcess; - - public static bool TryCreate(out VSForWindowsRebuildService result) - { - var vsProcess = FindAncestorVSProcess(); - if (vsProcess != null) - { - result = new VSForWindowsRebuildService(vsProcess); - return true; - } - else - { - result = null; - return false; - } - } - - public async Task PerformRebuildAsync(string projectFullPath, DateTime ifNotBuiltSince) - { - var pipeName = $"BlazorAutoRebuild\\{_vsProcess.Id}"; - using (var pipeClient = new NamedPipeClientStream(pipeName)) - { - await pipeClient.ConnectAsync(_connectionTimeoutMilliseconds); - - // Protocol: - // 1. Receive protocol version number from the VS listener - // If we're incompatible with it, send back special string "abort" and end - // 2. Send the project path to the VS listener - // 3. Send the 'if not rebuilt since' timestamp to the VS listener - // 4. Wait for it to send back a bool representing the result - // Keep in sync with AutoRebuildService.cs in the BlazorExtension project - // In the future we may extend this to getting back build error details - var remoteProtocolVersion = await pipeClient.ReadIntAsync(); - if (remoteProtocolVersion == 1) - { - await pipeClient.WriteStringAsync(projectFullPath); - await pipeClient.WriteDateTimeAsync(ifNotBuiltSince); - return await pipeClient.ReadBoolAsync(); - } - else - { - await pipeClient.WriteStringAsync("abort"); - return false; - } - } - } - - private VSForWindowsRebuildService(Process vsProcess) - { - _vsProcess = vsProcess ?? throw new ArgumentNullException(nameof(vsProcess)); - } - - private static Process FindAncestorVSProcess() - { - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return null; - } - - var candidateProcess = Process.GetCurrentProcess(); - try - { - while (candidateProcess != null && !candidateProcess.HasExited) - { - // It's unlikely that anyone's going to have a non-VS process in the process - // hierarchy called 'devenv', but if that turns out to be a scenario, we could - // (for example) write the VS PID to the obj directory during build, and then - // only consider processes with that ID. We still want to be sure there really - // is such a process in our ancestor chain, otherwise if you did "dotnet run" - // in a command prompt, we'd be confused and think it was launched from VS. - if (candidateProcess.ProcessName.Equals("devenv", StringComparison.OrdinalIgnoreCase)) - { - return candidateProcess; - } - - candidateProcess = ProcessUtils.GetParent(candidateProcess); - } - } - catch (Exception) - { - // There's probably some permissions issue that prevents us from seeing - // further up the ancestor list, so we have to stop looking here. - } - - return null; - } - } -} diff --git a/src/Components/Blazor/Server/src/BlazorConfig.cs b/src/Components/Blazor/Server/src/BlazorConfig.cs index 5438a7e36a..3f574ccb62 100644 --- a/src/Components/Blazor/Server/src/BlazorConfig.cs +++ b/src/Components/Blazor/Server/src/BlazorConfig.cs @@ -14,7 +14,6 @@ namespace Microsoft.AspNetCore.Blazor.Server public string WebRootPath { get; } public string DistPath => Path.Combine(Path.GetDirectoryName(SourceOutputAssemblyPath), "dist"); - public bool EnableAutoRebuilding { get; } public bool EnableDebugging { get; } public static BlazorConfig Read(string assemblyPath) @@ -44,7 +43,6 @@ namespace Microsoft.AspNetCore.Blazor.Server WebRootPath = webRootPath; } - EnableAutoRebuilding = configLines.Contains("autorebuild:true", StringComparer.Ordinal); EnableDebugging = configLines.Contains("debug:true", StringComparer.Ordinal); }