diff --git a/NuGet.config b/NuGet.config index a419e73e558e..419b8d3afd2c 100644 --- a/NuGet.config +++ b/NuGet.config @@ -4,12 +4,13 @@ - - + + + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 97a78c943536..6b98b1565141 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -15,42 +15,42 @@ a23da1c15c737b5e121650cfa5a86805e74e34fc - + https://github.com/dotnet/runtime - 46cfb747b4c22471242dee0d106f5c79cf9fd4c5 + 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815 - + https://github.com/dotnet/runtime - 46cfb747b4c22471242dee0d106f5c79cf9fd4c5 + 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815 - + https://github.com/dotnet/runtime - 46cfb747b4c22471242dee0d106f5c79cf9fd4c5 + 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815 - + https://github.com/dotnet/runtime - 46cfb747b4c22471242dee0d106f5c79cf9fd4c5 + 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815 - + https://github.com/dotnet/runtime - 46cfb747b4c22471242dee0d106f5c79cf9fd4c5 + 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815 - + https://github.com/dotnet/runtime - 46cfb747b4c22471242dee0d106f5c79cf9fd4c5 + 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815 - + https://github.com/dotnet/runtime - 46cfb747b4c22471242dee0d106f5c79cf9fd4c5 + 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815 - + https://github.com/dotnet/runtime - 46cfb747b4c22471242dee0d106f5c79cf9fd4c5 + 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815 - + https://github.com/dotnet/runtime - 46cfb747b4c22471242dee0d106f5c79cf9fd4c5 + 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815 @@ -59,154 +59,154 @@ https://github.com/dotnet/core-setup 7d57652f33493fa022125b7f63aad0d70c52d810 - + https://github.com/dotnet/emsdk - 6e079c23aee94577c17bae34522b52df0d646ca5 + 8e660ff41e91879977e3a9d837e068bd72234c26 - + https://github.com/dotnet/emsdk - 6e079c23aee94577c17bae34522b52df0d646ca5 + 8e660ff41e91879977e3a9d837e068bd72234c26 - + https://github.com/dotnet/msbuild - 988196b1c64418351f46da7d3a9ff403560a4270 + 95c7bf011ac3b849c63cfaf5584878232d223b71 - + https://github.com/dotnet/msbuild - 988196b1c64418351f46da7d3a9ff403560a4270 + 95c7bf011ac3b849c63cfaf5584878232d223b71 - + https://github.com/dotnet/msbuild - 988196b1c64418351f46da7d3a9ff403560a4270 + 95c7bf011ac3b849c63cfaf5584878232d223b71 - + https://github.com/dotnet/fsharp - 3044166cd923167204853d1d9f975bc26864f86f + 6b652cd1d5c02d6f331818ef16479499161b5b6d - + https://github.com/dotnet/fsharp - 3044166cd923167204853d1d9f975bc26864f86f + 6b652cd1d5c02d6f331818ef16479499161b5b6d - + https://github.com/dotnet/roslyn - 5a39d0ad691ee88dcf163808d9ed7a46b168a1c9 + ba2f19abe63a267dc9b2d082d0e1904cd91c1947 - + https://github.com/dotnet/roslyn - 5a39d0ad691ee88dcf163808d9ed7a46b168a1c9 + ba2f19abe63a267dc9b2d082d0e1904cd91c1947 - + https://github.com/dotnet/roslyn - 5a39d0ad691ee88dcf163808d9ed7a46b168a1c9 + ba2f19abe63a267dc9b2d082d0e1904cd91c1947 - + https://github.com/dotnet/roslyn - 5a39d0ad691ee88dcf163808d9ed7a46b168a1c9 + ba2f19abe63a267dc9b2d082d0e1904cd91c1947 - + https://github.com/dotnet/roslyn - 5a39d0ad691ee88dcf163808d9ed7a46b168a1c9 + ba2f19abe63a267dc9b2d082d0e1904cd91c1947 - + https://github.com/dotnet/roslyn - 5a39d0ad691ee88dcf163808d9ed7a46b168a1c9 + ba2f19abe63a267dc9b2d082d0e1904cd91c1947 - + https://github.com/dotnet/roslyn - 5a39d0ad691ee88dcf163808d9ed7a46b168a1c9 + ba2f19abe63a267dc9b2d082d0e1904cd91c1947 - + https://github.com/dotnet/roslyn - 5a39d0ad691ee88dcf163808d9ed7a46b168a1c9 + ba2f19abe63a267dc9b2d082d0e1904cd91c1947 - + https://github.com/dotnet/roslyn - 5a39d0ad691ee88dcf163808d9ed7a46b168a1c9 + ba2f19abe63a267dc9b2d082d0e1904cd91c1947 - + https://github.com/dotnet/aspnetcore - 0d72ad5e4c5b1394e9708f47ed81e9748e4fd819 + 9a34a6e3c7975f41300bd2550a089a85810cafd1 - + https://github.com/dotnet/aspnetcore - 0d72ad5e4c5b1394e9708f47ed81e9748e4fd819 + 9a34a6e3c7975f41300bd2550a089a85810cafd1 - + https://github.com/nuget/nuget.client - e082545cbba30ec8dc9b759f2d9beea8aabc6215 + 675e8a81acb53958e40dc21be5f562d00239a449 - + https://github.com/nuget/nuget.client - e082545cbba30ec8dc9b759f2d9beea8aabc6215 + 675e8a81acb53958e40dc21be5f562d00239a449 - + https://github.com/nuget/nuget.client - e082545cbba30ec8dc9b759f2d9beea8aabc6215 + 675e8a81acb53958e40dc21be5f562d00239a449 - + https://github.com/nuget/nuget.client - e082545cbba30ec8dc9b759f2d9beea8aabc6215 + 675e8a81acb53958e40dc21be5f562d00239a449 - + https://github.com/nuget/nuget.client - e082545cbba30ec8dc9b759f2d9beea8aabc6215 + 675e8a81acb53958e40dc21be5f562d00239a449 - + https://github.com/nuget/nuget.client - e082545cbba30ec8dc9b759f2d9beea8aabc6215 + 675e8a81acb53958e40dc21be5f562d00239a449 - + https://github.com/nuget/nuget.client - e082545cbba30ec8dc9b759f2d9beea8aabc6215 + 675e8a81acb53958e40dc21be5f562d00239a449 - + https://github.com/nuget/nuget.client - e082545cbba30ec8dc9b759f2d9beea8aabc6215 + 675e8a81acb53958e40dc21be5f562d00239a449 - + https://github.com/nuget/nuget.client - e082545cbba30ec8dc9b759f2d9beea8aabc6215 + 675e8a81acb53958e40dc21be5f562d00239a449 - + https://github.com/nuget/nuget.client - e082545cbba30ec8dc9b759f2d9beea8aabc6215 + 675e8a81acb53958e40dc21be5f562d00239a449 - + https://github.com/nuget/nuget.client - e082545cbba30ec8dc9b759f2d9beea8aabc6215 + 675e8a81acb53958e40dc21be5f562d00239a449 - + https://github.com/nuget/nuget.client - e082545cbba30ec8dc9b759f2d9beea8aabc6215 + 675e8a81acb53958e40dc21be5f562d00239a449 - + https://github.com/nuget/nuget.client - e082545cbba30ec8dc9b759f2d9beea8aabc6215 + 675e8a81acb53958e40dc21be5f562d00239a449 - + https://github.com/nuget/nuget.client - e082545cbba30ec8dc9b759f2d9beea8aabc6215 + 675e8a81acb53958e40dc21be5f562d00239a449 - + https://github.com/nuget/nuget.client - e082545cbba30ec8dc9b759f2d9beea8aabc6215 + 675e8a81acb53958e40dc21be5f562d00239a449 - + https://github.com/nuget/nuget.client - e082545cbba30ec8dc9b759f2d9beea8aabc6215 + 675e8a81acb53958e40dc21be5f562d00239a449 - + https://github.com/nuget/nuget.client - e082545cbba30ec8dc9b759f2d9beea8aabc6215 + 675e8a81acb53958e40dc21be5f562d00239a449 https://github.com/microsoft/vstest @@ -226,134 +226,134 @@ 07acde22b65497e72de145d57167b83609a7f7fb - + https://github.com/dotnet/runtime - 46cfb747b4c22471242dee0d106f5c79cf9fd4c5 + 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815 - + https://github.com/dotnet/runtime - 46cfb747b4c22471242dee0d106f5c79cf9fd4c5 + 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815 - + https://github.com/dotnet/runtime - 46cfb747b4c22471242dee0d106f5c79cf9fd4c5 + 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815 - + https://github.com/dotnet/runtime - 46cfb747b4c22471242dee0d106f5c79cf9fd4c5 + 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815 - + https://github.com/dotnet/runtime - 46cfb747b4c22471242dee0d106f5c79cf9fd4c5 + 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815 - + https://github.com/dotnet/runtime - 46cfb747b4c22471242dee0d106f5c79cf9fd4c5 + 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815 - + https://github.com/dotnet/windowsdesktop - f37a996ae93c3aaad652c36d43a6245ad090d775 + bb957f983a1377b7b9e25f2d24539ec4ab890fcc - + https://github.com/dotnet/windowsdesktop - f37a996ae93c3aaad652c36d43a6245ad090d775 + bb957f983a1377b7b9e25f2d24539ec4ab890fcc - + https://github.com/dotnet/windowsdesktop - f37a996ae93c3aaad652c36d43a6245ad090d775 + bb957f983a1377b7b9e25f2d24539ec4ab890fcc - + https://github.com/dotnet/windowsdesktop - f37a996ae93c3aaad652c36d43a6245ad090d775 + bb957f983a1377b7b9e25f2d24539ec4ab890fcc - + https://github.com/dotnet/wpf - d8b93d9e0c5ca81cfae3ff691824c3cdef74f5b3 + 32546f1aeee78cb85fe80191126ea3ecbe47aeed - + https://github.com/dotnet/aspnetcore - 0d72ad5e4c5b1394e9708f47ed81e9748e4fd819 + 9a34a6e3c7975f41300bd2550a089a85810cafd1 - + https://github.com/dotnet/aspnetcore - 0d72ad5e4c5b1394e9708f47ed81e9748e4fd819 + 9a34a6e3c7975f41300bd2550a089a85810cafd1 - + https://github.com/dotnet/aspnetcore - 0d72ad5e4c5b1394e9708f47ed81e9748e4fd819 + 9a34a6e3c7975f41300bd2550a089a85810cafd1 - + https://github.com/dotnet/aspnetcore - 0d72ad5e4c5b1394e9708f47ed81e9748e4fd819 + 9a34a6e3c7975f41300bd2550a089a85810cafd1 - + https://github.com/dotnet/aspnetcore - 0d72ad5e4c5b1394e9708f47ed81e9748e4fd819 + 9a34a6e3c7975f41300bd2550a089a85810cafd1 - + https://github.com/dotnet/aspnetcore - 0d72ad5e4c5b1394e9708f47ed81e9748e4fd819 + 9a34a6e3c7975f41300bd2550a089a85810cafd1 - + https://github.com/dotnet/aspnetcore - 0d72ad5e4c5b1394e9708f47ed81e9748e4fd819 + 9a34a6e3c7975f41300bd2550a089a85810cafd1 - + https://github.com/dotnet/aspnetcore - 0d72ad5e4c5b1394e9708f47ed81e9748e4fd819 + 9a34a6e3c7975f41300bd2550a089a85810cafd1 - + https://github.com/dotnet/aspnetcore - 0d72ad5e4c5b1394e9708f47ed81e9748e4fd819 + 9a34a6e3c7975f41300bd2550a089a85810cafd1 - + https://github.com/dotnet/aspnetcore - 0d72ad5e4c5b1394e9708f47ed81e9748e4fd819 + 9a34a6e3c7975f41300bd2550a089a85810cafd1 - + https://github.com/dotnet/aspnetcore - 0d72ad5e4c5b1394e9708f47ed81e9748e4fd819 + 9a34a6e3c7975f41300bd2550a089a85810cafd1 - + https://github.com/dotnet/aspnetcore - 0d72ad5e4c5b1394e9708f47ed81e9748e4fd819 + 9a34a6e3c7975f41300bd2550a089a85810cafd1 - + https://github.com/dotnet/razor - 4b3e4096e8b7aa647340e9f1cc21720741c35f86 + 4b1b541ed34e34b4b91202871f661879dabb4f55 - + https://github.com/dotnet/razor - 4b3e4096e8b7aa647340e9f1cc21720741c35f86 + 4b1b541ed34e34b4b91202871f661879dabb4f55 - + https://github.com/dotnet/razor - 4b3e4096e8b7aa647340e9f1cc21720741c35f86 + 4b1b541ed34e34b4b91202871f661879dabb4f55 - + https://github.com/dotnet/razor - 4b3e4096e8b7aa647340e9f1cc21720741c35f86 + 4b1b541ed34e34b4b91202871f661879dabb4f55 - + https://github.com/dotnet/aspnetcore - 0d72ad5e4c5b1394e9708f47ed81e9748e4fd819 + 9a34a6e3c7975f41300bd2550a089a85810cafd1 - + https://github.com/dotnet/aspnetcore - 0d72ad5e4c5b1394e9708f47ed81e9748e4fd819 + 9a34a6e3c7975f41300bd2550a089a85810cafd1 - + https://github.com/dotnet/aspnetcore - 0d72ad5e4c5b1394e9708f47ed81e9748e4fd819 + 9a34a6e3c7975f41300bd2550a089a85810cafd1 - + https://github.com/dotnet/aspnetcore - 0d72ad5e4c5b1394e9708f47ed81e9748e4fd819 + 9a34a6e3c7975f41300bd2550a089a85810cafd1 https://github.com/dotnet/test-templates @@ -375,47 +375,47 @@ https://github.com/dotnet/test-templates 49c9ad01f057b3c6352bbec12b117acc2224493c - + https://github.com/dotnet/test-templates - 2b8a55f3d3f380ae10d2c2f4d0974c92727ccc22 + 950cc4a13d0c3f7d84558aec0552e15a5e8559d4 - + https://github.com/dotnet/test-templates - 2b8a55f3d3f380ae10d2c2f4d0974c92727ccc22 + 950cc4a13d0c3f7d84558aec0552e15a5e8559d4 - + https://github.com/dotnet/winforms - f1676f5dcec5d4a84228406113fcad5fe31025af + 0749a97fb592ed4a5d7ddc8d13acf9f5097f68f7 - + https://github.com/dotnet/wpf - d8b93d9e0c5ca81cfae3ff691824c3cdef74f5b3 + 32546f1aeee78cb85fe80191126ea3ecbe47aeed - + https://github.com/dotnet/xdt - c2a9df9c1867454039a1223cef1c090359e33646 + 4ddd8113a29852380b7b929117bfe67f401ac320 - + https://github.com/dotnet/xdt - c2a9df9c1867454039a1223cef1c090359e33646 + 4ddd8113a29852380b7b929117bfe67f401ac320 - + https://github.com/dotnet/roslyn-analyzers - a7c74cf887abe4a38240bc4ead0b221d9d42434f + 2595aeb5e9a506f3f845c01be18d70ded045e33a - + https://github.com/dotnet/roslyn-analyzers - a7c74cf887abe4a38240bc4ead0b221d9d42434f + 2595aeb5e9a506f3f845c01be18d70ded045e33a - + https://github.com/dotnet/roslyn-analyzers - a7c74cf887abe4a38240bc4ead0b221d9d42434f + 2595aeb5e9a506f3f845c01be18d70ded045e33a @@ -441,49 +441,49 @@ - + https://github.com/dotnet/source-build-externals - e4ddbedd151b969514f2c5b756616707c31e0bfb + 4e60131607fd144eb86fe4487f1a37da940ca990 - + https://github.com/dotnet/source-build-reference-packages - 46174fbca16412ddabc1e881f6281192924e4ed3 + 6a1e86367923914589bda2244eb2321b97523c33 https://github.com/dotnet/deployment-tools 7871ee378dce87b64d930d4f33dca9c888f4034d - + https://github.com/dotnet/sourcelink - 1099a677155d9d4c1a81a612dffccbcf0d94f8e9 + 9700a2bfe76f993dbeb34439d4fe320c31182b2a - + https://github.com/dotnet/sourcelink - 1099a677155d9d4c1a81a612dffccbcf0d94f8e9 + 9700a2bfe76f993dbeb34439d4fe320c31182b2a - + https://github.com/dotnet/sourcelink - 1099a677155d9d4c1a81a612dffccbcf0d94f8e9 + 9700a2bfe76f993dbeb34439d4fe320c31182b2a - + https://github.com/dotnet/sourcelink - 1099a677155d9d4c1a81a612dffccbcf0d94f8e9 + 9700a2bfe76f993dbeb34439d4fe320c31182b2a - + https://github.com/dotnet/sourcelink - 1099a677155d9d4c1a81a612dffccbcf0d94f8e9 + 9700a2bfe76f993dbeb34439d4fe320c31182b2a - + https://github.com/dotnet/sourcelink - 1099a677155d9d4c1a81a612dffccbcf0d94f8e9 + 9700a2bfe76f993dbeb34439d4fe320c31182b2a - + https://github.com/dotnet/sourcelink - 1099a677155d9d4c1a81a612dffccbcf0d94f8e9 + 9700a2bfe76f993dbeb34439d4fe320c31182b2a @@ -499,125 +499,125 @@ - + https://github.com/dotnet/runtime - 46cfb747b4c22471242dee0d106f5c79cf9fd4c5 + 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815 - + https://github.com/dotnet/runtime - 46cfb747b4c22471242dee0d106f5c79cf9fd4c5 + 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815 - + https://github.com/dotnet/runtime - 46cfb747b4c22471242dee0d106f5c79cf9fd4c5 + 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815 - + https://github.com/dotnet/runtime - 46cfb747b4c22471242dee0d106f5c79cf9fd4c5 + 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815 - + https://github.com/dotnet/runtime - 46cfb747b4c22471242dee0d106f5c79cf9fd4c5 + 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815 - + https://github.com/dotnet/runtime - 46cfb747b4c22471242dee0d106f5c79cf9fd4c5 + 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815 - + https://github.com/dotnet/runtime - 46cfb747b4c22471242dee0d106f5c79cf9fd4c5 + 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815 - + https://github.com/dotnet/runtime - 46cfb747b4c22471242dee0d106f5c79cf9fd4c5 + 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815 - + https://github.com/dotnet/aspnetcore - 0d72ad5e4c5b1394e9708f47ed81e9748e4fd819 + 9a34a6e3c7975f41300bd2550a089a85810cafd1 - + https://github.com/dotnet/runtime - 46cfb747b4c22471242dee0d106f5c79cf9fd4c5 + 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815 - + https://github.com/dotnet/runtime - 46cfb747b4c22471242dee0d106f5c79cf9fd4c5 + 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815 - + https://github.com/dotnet/runtime - 46cfb747b4c22471242dee0d106f5c79cf9fd4c5 + 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815 - + https://github.com/dotnet/runtime - 46cfb747b4c22471242dee0d106f5c79cf9fd4c5 + 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815 - + https://github.com/dotnet/runtime - 46cfb747b4c22471242dee0d106f5c79cf9fd4c5 + 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815 - + https://github.com/dotnet/runtime - 46cfb747b4c22471242dee0d106f5c79cf9fd4c5 + 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815 - + https://github.com/dotnet/runtime - 46cfb747b4c22471242dee0d106f5c79cf9fd4c5 + 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815 - + https://github.com/dotnet/runtime - 46cfb747b4c22471242dee0d106f5c79cf9fd4c5 + 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815 - + https://github.com/dotnet/runtime - 46cfb747b4c22471242dee0d106f5c79cf9fd4c5 + 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815 - + https://github.com/dotnet/runtime - 46cfb747b4c22471242dee0d106f5c79cf9fd4c5 + 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815 - + https://github.com/dotnet/runtime - 46cfb747b4c22471242dee0d106f5c79cf9fd4c5 + 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815 - + https://github.com/dotnet/arcade - 04b9022eba9c184a8036328af513c22e6949e8b6 + 1230437de1ab7b3e15fe7cdfe7ffce2f65449959 - + https://github.com/dotnet/arcade - 04b9022eba9c184a8036328af513c22e6949e8b6 + 1230437de1ab7b3e15fe7cdfe7ffce2f65449959 - + https://github.com/dotnet/arcade - 04b9022eba9c184a8036328af513c22e6949e8b6 + 1230437de1ab7b3e15fe7cdfe7ffce2f65449959 - + https://github.com/dotnet/arcade - 04b9022eba9c184a8036328af513c22e6949e8b6 + 1230437de1ab7b3e15fe7cdfe7ffce2f65449959 - + https://github.com/dotnet/arcade - 04b9022eba9c184a8036328af513c22e6949e8b6 + 1230437de1ab7b3e15fe7cdfe7ffce2f65449959 - + https://github.com/dotnet/arcade - 04b9022eba9c184a8036328af513c22e6949e8b6 + 1230437de1ab7b3e15fe7cdfe7ffce2f65449959 - + https://github.com/dotnet/arcade - 04b9022eba9c184a8036328af513c22e6949e8b6 + 1230437de1ab7b3e15fe7cdfe7ffce2f65449959 - + https://github.com/dotnet/runtime - 46cfb747b4c22471242dee0d106f5c79cf9fd4c5 + 0d44aea3696bab80b11a12c6bdfdbf8de9c4e815 https://github.com/dotnet/arcade-services @@ -627,14 +627,14 @@ https://github.com/dotnet/arcade-services 47e3672c762970073e4282bd563233da86bcca3e - + https://github.com/dotnet/scenario-tests - d92413b87d36250859d8cb51ff69a03b5f5c4cab + 76edac963b2c6296018c7bf1e04a03c8488076d3 - + https://github.com/dotnet/scenario-tests - d92413b87d36250859d8cb51ff69a03b5f5c4cab + 76edac963b2c6296018c7bf1e04a03c8488076d3 - + https://github.com/dotnet/aspire - 75fdcff28495bdd643f6323133a7d411df71ab70 + 137e8dcae0a7b22c05f48c4e7a5d36fe3f00a8d7 - + https://github.com/dotnet/aspire - 75fdcff28495bdd643f6323133a7d411df71ab70 + 137e8dcae0a7b22c05f48c4e7a5d36fe3f00a8d7 diff --git a/eng/Versions.props b/eng/Versions.props index 71a097b631b7..e10330210c7b 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -19,10 +19,11 @@ false release - rc + rtm rtm servicing - 2 + + @@ -69,7 +70,7 @@ https://dotnetclimsrc.blob.core.windows.net/dotnet/ - 9.0.0-preview.24453.1 + 9.0.0-preview.24476.1 1.0.0-20230414.1 2.22.0 2.0.1-servicing-26011-01 @@ -111,80 +112,80 @@ - 9.0.0-rc.2.24466.1 + 9.0.0-rtm.24474.4 1.1.0-rc.24069.1 1.1.0-rc.24202.1 - 1.1.0-rc.24463.2 + 1.1.0-rc.24477.5 - 9.0.0-rc.2.24463.7 - 9.0.0-rc.2.24463.7 - 9.0.0-rc.2.24463.7 - 9.0.0-rc.2.24463.7 - 9.0.0-rc.2.24463.7 - 9.0.0-rc.2.24463.7 - 9.0.0-rc.2.24463.7 - 9.0.0-rc.2.24463.7 - 9.0.0-rc.2.24463.7 - 9.0.0-rc.2.24463.7 - 9.0.0-rc.2.24463.7 + 9.0.0-rtm.24476.4 + 9.0.0-rtm.24476.4 + 9.0.0-rtm.24476.4 + 9.0.0-rtm.24476.4 + 9.0.0-rtm.24476.4 + 9.0.0-rtm.24476.4 + 9.0.0-rtm.24476.4 + 9.0.0-rtm.24476.4 + 9.0.0-rtm.24476.4 + 9.0.0-rtm.24476.4 + 9.0.0-rtm.24476.4 8.0.0-rc.1.23414.4 - 9.0.0-rc.2.24463.7 - 9.0.0-rc.2.24463.7 - 9.0.0-rc.2.24463.7 - 9.0.0-rc.2.24463.7 - 9.0.0-rc.2.24463.7 - 9.0.0-rc.2.24463.7 + 9.0.0-rtm.24476.4 + 9.0.0-rtm.24476.4 + 9.0.0-rtm.24476.4 + 9.0.0-rtm.24476.4 + 9.0.0-rtm.24476.4 + 9.0.0-rtm.24476.4 2.1.0 - 9.0.0-rc.2.24463.7 + 9.0.0-rtm.24476.4 8.0.0 - 9.0.0-rc.2.24463.7 - 9.0.0-rc.2.24463.7 - 9.0.0-rc.2.24463.7 - 9.0.0-rc.2.24463.7 - 9.0.0-rc.2.24463.7 - 9.0.0-rc.2.24463.7 - 9.0.0-rc.2.24463.7 + 9.0.0-rtm.24476.4 + 9.0.0-rtm.24476.4 + 9.0.0-rtm.24476.4 + 9.0.0-rtm.24476.4 + 9.0.0-rtm.24476.4 + 9.0.0-rtm.24476.4 + 9.0.0-rtm.24476.4 8.0.0 - 9.0.0-rc.2.24463.7 - 9.0.0-rc.2.24463.7 - 9.0.0-rc.2.24463.7 - 9.0.0-rc.2.24463.7 - 9.0.0-rc.2.24463.7 - 9.0.0-rc.2.24463.7 - 9.0.0-rc.2.24463.7 + 9.0.0-rtm.24476.4 + 9.0.0-rtm.24476.4 + 9.0.0-rtm.24476.4 + 9.0.0-rtm.24476.4 + 9.0.0-rtm.24476.4 + 9.0.0-rtm.24476.4 + 9.0.0-rtm.24476.4 8.0.4 - 9.0.0-rc.2.24463.7 - 9.0.0-rc.2.24463.7 + 9.0.0-rtm.24476.4 + 9.0.0-rtm.24476.4 - 9.0.0-rtm.24467.8 - 9.0.0-rtm.24467.8 - 9.0.0-rtm.24467.8 - 9.0.0-rtm.24467.8 + 9.0.0-rtm.24476.4 + 9.0.0-rtm.24476.4 + 9.0.0-rtm.24476.4 + 9.0.0-rtm.24476.4 - 6.12.0-rc.108 - 6.12.0-rc.108 - 6.12.0-rc.108 - 6.12.0-rc.108 - 6.12.0-rc.108 - 6.12.0-rc.108 - 6.12.0-rc.108 - 6.12.0-rc.108 - 6.12.0-rc.108 - 6.12.0-rc.108 - 6.12.0-rc.108 - 6.12.0-rc.108 + 6.12.0-rc.120 + 6.12.0-rc.120 + 6.12.0-rc.120 + 6.12.0-rc.120 + 6.12.0-rc.120 + 6.12.0-rc.120 + 6.12.0-rc.120 + 6.12.0-rc.120 + 6.12.0-rc.120 + 6.12.0-rc.120 + 6.12.0-rc.120 + 6.12.0-rc.120 @@ -194,8 +195,8 @@ - 9.0.0-preview.24454.1 - 3.11.0-beta1.24454.1 + 9.0.0-preview.24479.1 + 3.11.0-beta1.24479.1 @@ -212,8 +213,8 @@ then use that in Directory.Packages.props. At usage sites, either we use MicrosoftBuildMinimumVersion, or MicrosoftBuildVersion in source-only modes. --> - 17.12.0-preview-24467-02 - 17.12.0-preview-24467-02 + 17.12.0 + 17.12.0-preview-24477-01 $([System.IO.File]::ReadAllText('$(RepoRoot)src\Layout\redist\minimumMSBuildVersion').Trim()) @@ -231,45 +232,45 @@ - 12.9.100-beta.24466.6 + 12.9.100-beta.24477.3 - 4.12.0-3.24463.1 - 4.12.0-3.24463.1 - 4.12.0-3.24463.1 - 4.12.0-3.24463.1 - 4.12.0-3.24463.1 - 4.12.0-3.24463.1 - 4.12.0-3.24463.1 - 4.12.0-3.24463.1 + 4.12.0-3.24479.1 + 4.12.0-3.24479.1 + 4.12.0-3.24479.1 + 4.12.0-3.24479.1 + 4.12.0-3.24479.1 + 4.12.0-3.24479.1 + 4.12.0-3.24479.1 + 4.12.0-3.24479.1 - 9.0.0-rtm.24466.12 - 9.0.0-rtm.24466.12 - 9.0.0-rtm.24466.12 - 9.0.0-rtm.24466.12 - 9.0.0-rtm.24466.12 - 9.0.0-rtm.24466.12 - 9.0.0-rtm.24466.12 - 9.0.0-rtm.24466.12 - 9.0.0-rtm.24466.12 - 9.0.0-rtm.24466.12 - 9.0.0-rtm.24466.12 - 9.0.0-rtm.24466.12 - 9.0.0-rtm.24466.12 + 9.0.0-rtm.24477.5 + 9.0.0-rtm.24477.5 + 9.0.0-rtm.24477.5 + 9.0.0-rtm.24477.5 + 9.0.0-rtm.24477.5 + 9.0.0-rtm.24477.5 + 9.0.0-rtm.24477.5 + 9.0.0-rtm.24477.5 + 9.0.0-rtm.24477.5 + 9.0.0-rtm.24477.5 + 9.0.0-rtm.24477.5 + 9.0.0-rtm.24477.5 + 9.0.0-rtm.24477.5 - 9.0.0-preview.24467.4 - 9.0.0-preview.24467.4 - 9.0.0-preview.24467.4 + 9.0.0-preview.24476.3 + 9.0.0-preview.24476.3 + 9.0.0-preview.24476.3 - 9.0.0-rtm.24467.10 - 9.0.0-rtm.24467.10 + 9.0.0-rtm.24476.2 + 9.0.0-rtm.24476.2 @@ -304,7 +305,7 @@ 2.2.0-beta.19072.10 2.0.0 - 9.0.0-preview.24372.1 + 9.0.0-preview.24475.2 @@ -313,19 +314,19 @@ - 9.0.0-beta.24466.2 - 9.0.0-beta.24466.2 - 9.0.0-beta.24466.2 - 9.0.0-beta.24466.2 + 9.0.0-beta.24473.1 + 9.0.0-beta.24473.1 + 9.0.0-beta.24473.1 + 9.0.0-beta.24473.1 - 9.0.0-beta.24466.3 - 9.0.0-beta.24466.3 - 9.0.0-beta.24466.3 - 9.0.0-beta.24466.3 - 9.0.0-beta.24466.3 - 9.0.0-beta.24466.3 + 9.0.0-beta.24474.2 + 9.0.0-beta.24474.2 + 9.0.0-beta.24474.2 + 9.0.0-beta.24474.2 + 9.0.0-beta.24474.2 + 9.0.0-beta.24474.2 @@ -351,7 +352,7 @@ 8.0.100 - 8.2.0 + 8.2.1 9.0.100-preview.6 9.0.0-preview.6.24327.7 34.99.0-preview.6.340 @@ -360,14 +361,14 @@ 14.2.9714-net9-p6 17.2.9714-net9-p6 - 9.0.0-rc.2.24455.1 + 9.0.0-rtm.24469.1 $(MicrosoftNETWorkloadEmscriptenCurrentManifest90100TransportPackageVersion) - 9.0.100$([System.Text.RegularExpressions.Regex]::Match($(EmscriptenWorkloadManifestVersion), `-[A-z]*[\.]*\d*`)) + 9.0.100$([System.Text.RegularExpressions.Regex]::Match($(EmscriptenWorkloadManifestVersion), `-(?!rtm)[A-z]*[\.]*\d*`)) $(MicrosoftNETCoreAppRefPackageVersion) - 9.0.100$([System.Text.RegularExpressions.Regex]::Match($(MonoWorkloadManifestVersion), `-[A-z]*[\.]*\d*`)) + 9.0.100$([System.Text.RegularExpressions.Regex]::Match($(MonoWorkloadManifestVersion), `-(?!rtm)[A-z]*[\.]*\d*`)) diff --git a/global.json b/global.json index c2dd7c515dd0..3581b41bfa75 100644 --- a/global.json +++ b/global.json @@ -17,8 +17,8 @@ "cmake": "latest" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "9.0.0-beta.24466.2", - "Microsoft.DotNet.Helix.Sdk": "9.0.0-beta.24466.2", + "Microsoft.DotNet.Arcade.Sdk": "9.0.0-beta.24473.1", + "Microsoft.DotNet.Helix.Sdk": "9.0.0-beta.24473.1", "Microsoft.Build.NoTargets": "3.7.0", "Microsoft.DotNet.CMake.Sdk": "9.0.0-beta.24217.1" } diff --git a/sdk.sln b/sdk.sln index c57d23959f8c..1e3a6f52e474 100644 --- a/sdk.sln +++ b/sdk.sln @@ -507,6 +507,8 @@ Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Microsoft.WebTools.AspireSe EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Net.Sdk.Compilers.Toolset", "src\Microsoft.Net.Sdk.Compilers.Toolset\Microsoft.Net.Sdk.Compilers.Toolset.csproj", "{FA579C03-2EB4-4D47-88EE-BFF339E96FAF}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.WebTools.AspireService.Tests", "test\Microsoft.WebTools.AspireService.Tests\Microsoft.WebTools.AspireService.Tests.csproj", "{1F0B4B3C-DC88-4740-B04F-1707102E9930}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -965,6 +967,10 @@ Global {FA579C03-2EB4-4D47-88EE-BFF339E96FAF}.Debug|Any CPU.Build.0 = Debug|Any CPU {FA579C03-2EB4-4D47-88EE-BFF339E96FAF}.Release|Any CPU.ActiveCfg = Release|Any CPU {FA579C03-2EB4-4D47-88EE-BFF339E96FAF}.Release|Any CPU.Build.0 = Release|Any CPU + {1F0B4B3C-DC88-4740-B04F-1707102E9930}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1F0B4B3C-DC88-4740-B04F-1707102E9930}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1F0B4B3C-DC88-4740-B04F-1707102E9930}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1F0B4B3C-DC88-4740-B04F-1707102E9930}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1143,6 +1149,7 @@ Global {19014C60-F87C-4CC7-AC0F-C41B6126EBCE} = {71A9F549-0EB6-41F9-BC16-4A6C5007FC91} {94C8526E-DCC2-442F-9868-3DD0BA2688BE} = {71A9F549-0EB6-41F9-BC16-4A6C5007FC91} {FA579C03-2EB4-4D47-88EE-BFF339E96FAF} = {22AB674F-ED91-4FBC-BFEE-8A1E82F9F05E} + {1F0B4B3C-DC88-4740-B04F-1707102E9930} = {580D1AE7-AA8F-4912-8B76-105594E00B3B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {FB8F26CE-4DE6-433F-B32A-79183020BBD6} @@ -1150,6 +1157,7 @@ Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Compatibility\ApiCompat\Microsoft.DotNet.ApiCompat.Shared\Microsoft.DotNet.ApiCompat.Shared.projitems*{03c5a84a-982b-4f38-ac73-ab832c645c4a}*SharedItemsImports = 5 src\Compatibility\ApiCompat\Microsoft.DotNet.ApiCompat.Shared\Microsoft.DotNet.ApiCompat.Shared.projitems*{0a3c9afd-f6e6-4a5d-83fb-93bf66732696}*SharedItemsImports = 5 + src\BuiltInTools\AspireService\Microsoft.WebTools.AspireService.projitems*{1f0b4b3c-dc88-4740-b04f-1707102e9930}*SharedItemsImports = 5 src\BuiltInTools\AspireService\Microsoft.WebTools.AspireService.projitems*{94c8526e-dcc2-442f-9868-3dd0ba2688be}*SharedItemsImports = 13 src\Compatibility\ApiCompat\Microsoft.DotNet.ApiCompat.Shared\Microsoft.DotNet.ApiCompat.Shared.projitems*{9d36039f-d0a1-462f-85b4-81763c6b02cb}*SharedItemsImports = 13 src\Compatibility\ApiCompat\Microsoft.DotNet.ApiCompat.Shared\Microsoft.DotNet.ApiCompat.Shared.projitems*{a9103b98-d888-4260-8a05-fa36f640698a}*SharedItemsImports = 5 diff --git a/src/BuiltInTools/AspireService/AspireServerService.cs b/src/BuiltInTools/AspireService/AspireServerService.cs index d777ce951154..3b620131f73f 100644 --- a/src/BuiltInTools/AspireService/AspireServerService.cs +++ b/src/BuiltInTools/AspireService/AspireServerService.cs @@ -1,18 +1,12 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Collections.Generic; -using System.Diagnostics; using System.Net; using System.Net.WebSockets; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; -using System.Text; using System.Text.Json; using System.Text.Json.Serialization; -using System.Threading; -using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; diff --git a/src/BuiltInTools/AspireService/Helpers/CertGenerator.cs b/src/BuiltInTools/AspireService/Helpers/CertGenerator.cs index 189a88c48ed5..a0b0f7766d48 100644 --- a/src/BuiltInTools/AspireService/Helpers/CertGenerator.cs +++ b/src/BuiltInTools/AspireService/Helpers/CertGenerator.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; @@ -35,7 +34,11 @@ public static X509Certificate2 GenerateCert() // The file will be automatically generated by the following call and disposed when the returned cert is disposed. using (cert) { +#if NET9_0_OR_GREATER + return X509CertificateLoader.LoadPkcs12(cert.Export(X509ContentType.Pfx), password: null, X509KeyStorageFlags.UserKeySet); +#else return new X509Certificate2(cert.Export(X509ContentType.Pfx), "", X509KeyStorageFlags.UserKeySet); +#endif } } else diff --git a/src/BuiltInTools/DotNetDeltaApplier/StartupHook.cs b/src/BuiltInTools/DotNetDeltaApplier/StartupHook.cs index 0941b5b89621..1968f7d21aa3 100644 --- a/src/BuiltInTools/DotNetDeltaApplier/StartupHook.cs +++ b/src/BuiltInTools/DotNetDeltaApplier/StartupHook.cs @@ -26,9 +26,11 @@ public static void Initialize() // When launching the application process dotnet-watch sets Hot Reload environment variables via CLI environment directives (dotnet [env:X=Y] run). // Currently, the CLI parser sets the env variables to the dotnet.exe process itself, rather then to the target process. // This may cause the dotnet.exe process to connect to the named pipe and break it for the target process. - if (Path.ChangeExtension(processPath, ".exe") != Path.ChangeExtension(s_targetProcessPath, ".exe")) + var processExe = Path.ChangeExtension(processPath, ".exe"); + var expectedExe = Path.ChangeExtension(s_targetProcessPath, ".exe"); + if (!string.Equals(processExe, expectedExe, Path.DirectorySeparatorChar == '\\' ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal)) { - Log($"Ignoring process '{processPath}', expecting '{s_targetProcessPath}'"); + Log($"Ignoring process '{processExe}', expecting '{expectedExe}'"); return; } diff --git a/src/BuiltInTools/dotnet-watch/DotNetWatcher.cs b/src/BuiltInTools/dotnet-watch/DotNetWatcher.cs index 785219c3879e..d2f57ce7eb34 100644 --- a/src/BuiltInTools/dotnet-watch/DotNetWatcher.cs +++ b/src/BuiltInTools/dotnet-watch/DotNetWatcher.cs @@ -27,7 +27,7 @@ public override async Task WatchAsync(CancellationToken cancellationToken) var environmentBuilder = EnvironmentVariablesBuilder.FromCurrentEnvironment(); - FileItem? changedFile = null; + ChangedFile? changedFile = null; var buildEvaluator = new BuildEvaluator(Context, RootFileSetFactory); await using var browserConnector = new BrowserConnector(Context); @@ -86,7 +86,7 @@ public override async Task WatchAsync(CancellationToken cancellationToken) var processTask = ProcessRunner.RunAsync(processSpec, Context.Reporter, isUserApplication: true, processExitedSource: null, combinedCancellationSource.Token); - Task fileSetTask; + Task fileSetTask; Task finishedTask; while (true) @@ -94,9 +94,9 @@ public override async Task WatchAsync(CancellationToken cancellationToken) fileSetTask = fileSetWatcher.GetChangedFileAsync(startedWatching: null, combinedCancellationSource.Token); finishedTask = await Task.WhenAny(processTask, fileSetTask, cancelledTaskSource.Task); - if (staticFileHandler != null && finishedTask == fileSetTask && fileSetTask.Result is FileItem fileItem) + if (staticFileHandler != null && finishedTask == fileSetTask && fileSetTask.Result.HasValue) { - if (await staticFileHandler.HandleFileChangesAsync([fileItem], combinedCancellationSource.Token)) + if (await staticFileHandler.HandleFileChangesAsync([fileSetTask.Result.Value], combinedCancellationSource.Token)) { // We're able to handle the file change event without doing a full-rebuild. continue; @@ -131,7 +131,7 @@ public override async Task WatchAsync(CancellationToken cancellationToken) Debug.Assert(finishedTask == fileSetTask); changedFile = fileSetTask.Result; Debug.Assert(changedFile != null, "ChangedFile should only be null when cancelled"); - Context.Reporter.Output($"File changed: {changedFile.Value.FilePath}"); + Context.Reporter.Output($"File changed: {changedFile.Value.Item.FilePath}"); } } } diff --git a/src/BuiltInTools/dotnet-watch/FileItem.cs b/src/BuiltInTools/dotnet-watch/FileItem.cs index 71779898168a..7fc91cd6fa1c 100644 --- a/src/BuiltInTools/dotnet-watch/FileItem.cs +++ b/src/BuiltInTools/dotnet-watch/FileItem.cs @@ -1,9 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.DotNet.Watcher.Internal; + namespace Microsoft.DotNet.Watcher { - internal readonly struct FileItem + internal readonly record struct FileItem { public string FilePath { get; init; } @@ -14,7 +16,7 @@ internal readonly struct FileItem public string? StaticWebAssetPath { get; init; } - public bool IsNewFile { get; init; } + public ChangeKind Change { get; init; } public bool IsStaticFile => StaticWebAssetPath != null; } diff --git a/src/BuiltInTools/dotnet-watch/Filters/BuildEvaluator.cs b/src/BuiltInTools/dotnet-watch/Filters/BuildEvaluator.cs index ad7599378ee2..0025fbbbcbf5 100644 --- a/src/BuiltInTools/dotnet-watch/Filters/BuildEvaluator.cs +++ b/src/BuiltInTools/dotnet-watch/Filters/BuildEvaluator.cs @@ -46,7 +46,7 @@ public IReadOnlyList GetProcessArguments(int iteration) return [context.RootProjectOptions.Command, .. context.RootProjectOptions.CommandArguments]; } - public async ValueTask EvaluateAsync(FileItem? changedFile, CancellationToken cancellationToken) + public async ValueTask EvaluateAsync(ChangedFile? changedFile, CancellationToken cancellationToken) { if (context.EnvironmentOptions.SuppressMSBuildIncrementalism) { @@ -54,7 +54,7 @@ public async ValueTask EvaluateAsync(FileItem? changedFile, Ca return _evaluationResult = await CreateEvaluationResult(cancellationToken); } - if (_evaluationResult == null || RequiresMSBuildRevaluation(changedFile)) + if (_evaluationResult == null || RequiresMSBuildRevaluation(changedFile?.Item)) { RequiresRevaluation = true; } diff --git a/src/BuiltInTools/dotnet-watch/HotReload/IncrementalMSBuildWorkspace.cs b/src/BuiltInTools/dotnet-watch/HotReload/IncrementalMSBuildWorkspace.cs index 17e4c6d27cd8..90ec68694946 100644 --- a/src/BuiltInTools/dotnet-watch/HotReload/IncrementalMSBuildWorkspace.cs +++ b/src/BuiltInTools/dotnet-watch/HotReload/IncrementalMSBuildWorkspace.cs @@ -1,20 +1,15 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.ExternalAccess.Watch.Api; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.MSBuild; -using Microsoft.Extensions.Tools.Internal; -using Microsoft.CodeAnalysis.ExternalAccess.Watch.Api; -using System.Collections.Immutable; using Microsoft.CodeAnalysis.Text; +using Microsoft.DotNet.Watcher.Internal; +using Microsoft.Extensions.Tools.Internal; namespace Microsoft.DotNet.Watcher.Tools; @@ -27,8 +22,11 @@ public IncrementalMSBuildWorkspace(IReporter reporter) { WorkspaceFailed += (_sender, diag) => { - // Errors reported here are not fatal, an exception would be thrown for fatal issues. - reporter.Verbose($"MSBuildWorkspace warning: {diag.Diagnostic}"); + // Report both Warning and Failure as warnings. + // MSBuildProjectLoader reports Failures for cases where we can safely continue loading projects + // (e.g. non-C#/VB project is ignored). + // https://github.com/dotnet/roslyn/issues/75170 + reporter.Warn($"msbuild: {diag.Diagnostic}", "⚠"); }; _reporter = reporter; @@ -106,13 +104,24 @@ ImmutableArray MapDocuments(ProjectId mappedProjectId, IReadOnlyLi }).ToImmutableArray(); } - public async ValueTask UpdateFileContentAsync(IEnumerable changedFiles, CancellationToken cancellationToken) + public async ValueTask UpdateFileContentAsync(IEnumerable changedFiles, CancellationToken cancellationToken) { var updatedSolution = CurrentSolution; - foreach (var changedFile in changedFiles) + var documentsToRemove = new List(); + + foreach (var (changedFile, change) in changedFiles) { + // when a file is added we reevaluate the project: + Debug.Assert(change != ChangeKind.Add); + var documentIds = updatedSolution.GetDocumentIdsWithFilePath(changedFile.FilePath); + if (change == ChangeKind.Delete) + { + documentsToRemove.AddRange(documentIds); + continue; + } + foreach (var documentId in documentIds) { var textDocument = updatedSolution.GetDocument(documentId) @@ -140,9 +149,17 @@ public async ValueTask UpdateFileContentAsync(IEnumerable changedFiles } } + updatedSolution = RemoveDocuments(updatedSolution, documentsToRemove); + await ReportSolutionFilesAsync(SetCurrentSolution(updatedSolution), cancellationToken); } + private static Solution RemoveDocuments(Solution solution, IEnumerable ids) + => solution + .RemoveDocuments(ids.Where(id => solution.GetDocument(id) != null).ToImmutableArray()) + .RemoveAdditionalDocuments(ids.Where(id => solution.GetAdditionalDocument(id) != null).ToImmutableArray()) + .RemoveAnalyzerConfigDocuments(ids.Where(id => solution.GetAnalyzerConfigDocument(id) != null).ToImmutableArray()); + private static async ValueTask GetSourceTextAsync(string filePath, CancellationToken cancellationToken) { var zeroLengthRetryPerformed = false; diff --git a/src/BuiltInTools/dotnet-watch/HotReload/ScopedCssFileHandler.cs b/src/BuiltInTools/dotnet-watch/HotReload/ScopedCssFileHandler.cs index dfe625075227..3a6961854987 100644 --- a/src/BuiltInTools/dotnet-watch/HotReload/ScopedCssFileHandler.cs +++ b/src/BuiltInTools/dotnet-watch/HotReload/ScopedCssFileHandler.cs @@ -5,6 +5,7 @@ using System.Collections; using System.Diagnostics; using Microsoft.Build.Graph; +using Microsoft.DotNet.Watcher.Internal; using Microsoft.Extensions.Tools.Internal; namespace Microsoft.DotNet.Watcher.Tools @@ -13,14 +14,14 @@ internal sealed class ScopedCssFileHandler(IReporter reporter, ProjectNodeMap pr { private const string BuildTargetName = "GenerateComputedBuildStaticWebAssets"; - public async ValueTask HandleFileChangesAsync(IReadOnlyList files, CancellationToken cancellationToken) + public async ValueTask HandleFileChangesAsync(IReadOnlyList files, CancellationToken cancellationToken) { var projectsToRefresh = new HashSet(); var hasApplicableFiles = false; for (int i = 0; i < files.Count; i++) { - var file = files[i]; + var file = files[i].Item; if (!file.FilePath.EndsWith(".razor.css", StringComparison.Ordinal) && !file.FilePath.EndsWith(".cshtml.css", StringComparison.Ordinal)) diff --git a/src/BuiltInTools/dotnet-watch/HotReload/StaticFileHandler.cs b/src/BuiltInTools/dotnet-watch/HotReload/StaticFileHandler.cs index f8d9b8bffd12..ce53bc8a7ce3 100644 --- a/src/BuiltInTools/dotnet-watch/HotReload/StaticFileHandler.cs +++ b/src/BuiltInTools/dotnet-watch/HotReload/StaticFileHandler.cs @@ -7,6 +7,7 @@ using System.Text.Json; using System.Text.Json.Serialization; using Microsoft.CodeAnalysis.StackTraceExplorer; +using Microsoft.DotNet.Watcher.Internal; using Microsoft.Extensions.Tools.Internal; namespace Microsoft.DotNet.Watcher.Tools @@ -18,13 +19,13 @@ internal sealed class StaticFileHandler(IReporter reporter, ProjectNodeMap proje DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }; - public async ValueTask HandleFileChangesAsync(IReadOnlyList files, CancellationToken cancellationToken) + public async ValueTask HandleFileChangesAsync(IReadOnlyList files, CancellationToken cancellationToken) { var allFilesHandled = true; var refreshRequests = new Dictionary>(); for (int i = 0; i < files.Count; i++) { - var file = files[i]; + var file = files[i].Item; if (file.StaticWebAssetPath is null) { diff --git a/src/BuiltInTools/dotnet-watch/HotReloadDotNetWatcher.cs b/src/BuiltInTools/dotnet-watch/HotReloadDotNetWatcher.cs index ee09b3c257df..24b543ebd9b3 100644 --- a/src/BuiltInTools/dotnet-watch/HotReloadDotNetWatcher.cs +++ b/src/BuiltInTools/dotnet-watch/HotReloadDotNetWatcher.cs @@ -74,7 +74,7 @@ public override async Task WatchAsync(CancellationToken shutdownCancellationToke HotReloadFileSetWatcher? fileSetWatcher = null; EvaluationResult? evaluationResult = null; RunningProject? rootRunningProject = null; - Task? fileSetWatcherTask = null; + Task? fileSetWatcherTask = null; try { @@ -178,7 +178,7 @@ public override async Task WatchAsync(CancellationToken shutdownCancellationToke // When a new file is added we need to run design-time build to find out // what kind of the file it is and which project(s) does it belong to (can be linked, web asset, etc.). // We don't need to rebuild and restart the application though. - if (changedFiles.Any(f => f.IsNewFile)) + if (changedFiles.Any(f => f.Change is ChangeKind.Add)) { Context.Reporter.Verbose("File addition triggered re-evaluation."); @@ -195,9 +195,9 @@ public override async Task WatchAsync(CancellationToken shutdownCancellationToke // update files in the change set with new evaluation info: for (int i = 0; i < changedFiles.Length; i++) { - if (evaluationResult.Files.TryGetValue(changedFiles[i].FilePath, out var evaluatedFile)) + if (evaluationResult.Files.TryGetValue(changedFiles[i].Item.FilePath, out var evaluatedFile)) { - changedFiles[i] = evaluatedFile; + changedFiles[i] = changedFiles[i] with { Item = evaluatedFile }; } } @@ -336,24 +336,43 @@ await Task.WhenAll( } } - private void ReportFileChanges(IReadOnlyList fileItems) + private void ReportFileChanges(IReadOnlyList changedFiles) { - Report(added: true); - Report(added: false); + Report(kind: ChangeKind.Add); + Report(kind: ChangeKind.Update); + Report(kind: ChangeKind.Delete); - void Report(bool added) + void Report(ChangeKind kind) { - var items = fileItems.Where(item => item.IsNewFile == added).ToArray(); + var items = changedFiles.Where(item => item.Change == kind).ToArray(); if (items is not []) { - Context.Reporter.Output(GetMessage(items, added)); + Context.Reporter.Output(GetMessage(items, kind)); } } - string GetMessage(IReadOnlyList items, bool added) - => items is [var item] - ? (added ? "File added: " : "File changed: ") + GetRelativeFilePath(item.FilePath) - : (added ? "Files added: " : "Files changed: ") + string.Join(", ", items.Select(f => GetRelativeFilePath(f.FilePath))); + string GetMessage(IReadOnlyList items, ChangeKind kind) + => items is [{Item: var item }] + ? GetSingularMessage(kind) + ": " + GetRelativeFilePath(item.FilePath) + : GetPluralMessage(kind) + ": " + string.Join(", ", items.Select(f => GetRelativeFilePath(f.Item.FilePath))); + + static string GetSingularMessage(ChangeKind kind) + => kind switch + { + ChangeKind.Update => "File updated", + ChangeKind.Add => "File added", + ChangeKind.Delete => "File deleted", + _ => throw new InvalidOperationException() + }; + + static string GetPluralMessage(ChangeKind kind) + => kind switch + { + ChangeKind.Update => "Files updated", + ChangeKind.Add => "Files added", + ChangeKind.Delete => "Files deleted", + _ => throw new InvalidOperationException() + }; } private async ValueTask EvaluateRootProjectAsync(CancellationToken cancellationToken) diff --git a/src/BuiltInTools/dotnet-watch/Internal/FileWatcher.cs b/src/BuiltInTools/dotnet-watch/Internal/FileWatcher.cs index b844f8da4fa8..3a339bf63fb1 100644 --- a/src/BuiltInTools/dotnet-watch/Internal/FileWatcher.cs +++ b/src/BuiltInTools/dotnet-watch/Internal/FileWatcher.cs @@ -10,7 +10,7 @@ internal sealed class FileWatcher(IReadOnlyDictionary fileSet, private readonly Dictionary _watchers = []; private bool _disposed; - public event Action? OnFileChange; + public event Action? OnFileChange; public void Dispose() { @@ -73,9 +73,9 @@ private void WatcherErrorHandler(object? sender, Exception error) } } - private void WatcherChangedHandler(object? sender, (string changedPath, bool newFile) args) + private void WatcherChangedHandler(object? sender, (string changedPath, ChangeKind kind) args) { - OnFileChange?.Invoke(args.changedPath, args.newFile); + OnFileChange?.Invoke(args.changedPath, args.kind); } private void DisposeWatcher(string directory) @@ -101,22 +101,22 @@ private void EnsureNotDisposed() private static string EnsureTrailingSlash(string path) => (path is [.., var last] && last != Path.DirectorySeparatorChar) ? path + Path.DirectorySeparatorChar : path; - public async Task GetChangedFileAsync(Action? startedWatching, CancellationToken cancellationToken) + public async Task GetChangedFileAsync(Action? startedWatching, CancellationToken cancellationToken) { StartWatching(); - var fileChangedSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + var fileChangedSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); cancellationToken.Register(() => fileChangedSource.TrySetResult(null)); - void FileChangedCallback(string path, bool newFile) + void FileChangedCallback(string path, ChangeKind kind) { if (fileSet.TryGetValue(path, out var fileItem)) { - fileChangedSource.TrySetResult(fileItem); + fileChangedSource.TrySetResult(new ChangedFile(fileItem, kind)); } } - FileItem? changedFile; + ChangedFile? changedFile; OnFileChange += FileChangedCallback; try diff --git a/src/BuiltInTools/dotnet-watch/Internal/FileWatcher/ChangeKind.cs b/src/BuiltInTools/dotnet-watch/Internal/FileWatcher/ChangeKind.cs new file mode 100644 index 000000000000..5fef3b698624 --- /dev/null +++ b/src/BuiltInTools/dotnet-watch/Internal/FileWatcher/ChangeKind.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.DotNet.Watcher.Internal; + +internal enum ChangeKind +{ + Update, + Add, + Delete +} + +internal readonly record struct ChangedFile(FileItem Item, ChangeKind Change); diff --git a/src/BuiltInTools/dotnet-watch/Internal/FileWatcher/DotnetFileWatcher.cs b/src/BuiltInTools/dotnet-watch/Internal/FileWatcher/DotnetFileWatcher.cs index ca622206e933..7040fe1a0763 100644 --- a/src/BuiltInTools/dotnet-watch/Internal/FileWatcher/DotnetFileWatcher.cs +++ b/src/BuiltInTools/dotnet-watch/Internal/FileWatcher/DotnetFileWatcher.cs @@ -33,7 +33,7 @@ internal DotnetFileWatcher(string watchedDirectory, Func? OnFileChange; + public event EventHandler<(string filePath, ChangeKind kind)>? OnFileChange; public event EventHandler? OnError; @@ -78,10 +78,10 @@ private void WatcherRenameHandler(object sender, RenamedEventArgs e) return; } - Logger?.Invoke("Rename"); + Logger?.Invoke($"Renamed '{e.OldFullPath}' to '{e.FullPath}'."); - NotifyChange(e.OldFullPath, newFile: false); - NotifyChange(e.FullPath, newFile: true); + NotifyChange(e.OldFullPath, ChangeKind.Delete); + NotifyChange(e.FullPath, ChangeKind.Add); if (Directory.Exists(e.FullPath)) { @@ -89,12 +89,23 @@ private void WatcherRenameHandler(object sender, RenamedEventArgs e) { // Calculated previous path of this moved item. var oldLocation = Path.Combine(e.OldFullPath, newLocation.Substring(e.FullPath.Length + 1)); - NotifyChange(oldLocation, newFile: false); - NotifyChange(newLocation, newFile: true); + NotifyChange(oldLocation, ChangeKind.Delete); + NotifyChange(newLocation, ChangeKind.Add); } } } + private void WatcherDeletedHandler(object sender, FileSystemEventArgs e) + { + if (_disposed) + { + return; + } + + Logger?.Invoke($"Deleted '{e.FullPath}'."); + NotifyChange(e.FullPath, ChangeKind.Delete); + } + private void WatcherChangeHandler(object sender, FileSystemEventArgs e) { if (_disposed) @@ -102,8 +113,8 @@ private void WatcherChangeHandler(object sender, FileSystemEventArgs e) return; } - Logger?.Invoke("Change"); - NotifyChange(e.FullPath, newFile: false); + Logger?.Invoke($"Updated '{e.FullPath}'."); + NotifyChange(e.FullPath, ChangeKind.Update); } private void WatcherAddedHandler(object sender, FileSystemEventArgs e) @@ -113,14 +124,14 @@ private void WatcherAddedHandler(object sender, FileSystemEventArgs e) return; } - Logger?.Invoke("Added"); - NotifyChange(e.FullPath, newFile: true); + Logger?.Invoke($"Added '{e.FullPath}'."); + NotifyChange(e.FullPath, ChangeKind.Add); } - private void NotifyChange(string fullPath, bool newFile) + private void NotifyChange(string fullPath, ChangeKind kind) { // Only report file changes - OnFileChange?.Invoke(this, (fullPath, newFile)); + OnFileChange?.Invoke(this, (fullPath, kind)); } private void CreateFileSystemWatcher() @@ -140,7 +151,7 @@ private void CreateFileSystemWatcher() _fileSystemWatcher.IncludeSubdirectories = true; _fileSystemWatcher.Created += WatcherAddedHandler; - _fileSystemWatcher.Deleted += WatcherChangeHandler; + _fileSystemWatcher.Deleted += WatcherDeletedHandler; _fileSystemWatcher.Changed += WatcherChangeHandler; _fileSystemWatcher.Renamed += WatcherRenameHandler; _fileSystemWatcher.Error += WatcherErrorHandler; @@ -156,7 +167,7 @@ private void DisposeInnerWatcher() _fileSystemWatcher.EnableRaisingEvents = false; _fileSystemWatcher.Created -= WatcherAddedHandler; - _fileSystemWatcher.Deleted -= WatcherChangeHandler; + _fileSystemWatcher.Deleted -= WatcherDeletedHandler; _fileSystemWatcher.Changed -= WatcherChangeHandler; _fileSystemWatcher.Renamed -= WatcherRenameHandler; _fileSystemWatcher.Error -= WatcherErrorHandler; diff --git a/src/BuiltInTools/dotnet-watch/Internal/FileWatcher/IFileSystemWatcher.cs b/src/BuiltInTools/dotnet-watch/Internal/FileWatcher/IFileSystemWatcher.cs index f75c7e3e1481..ebdef49913ff 100644 --- a/src/BuiltInTools/dotnet-watch/Internal/FileWatcher/IFileSystemWatcher.cs +++ b/src/BuiltInTools/dotnet-watch/Internal/FileWatcher/IFileSystemWatcher.cs @@ -5,7 +5,7 @@ namespace Microsoft.DotNet.Watcher.Internal { internal interface IFileSystemWatcher : IDisposable { - event EventHandler<(string filePath, bool newFile)> OnFileChange; + event EventHandler<(string filePath, ChangeKind kind)> OnFileChange; event EventHandler OnError; diff --git a/src/BuiltInTools/dotnet-watch/Internal/FileWatcher/PollingFileWatcher.cs b/src/BuiltInTools/dotnet-watch/Internal/FileWatcher/PollingFileWatcher.cs index da7630b71eb8..2df2004ee84b 100644 --- a/src/BuiltInTools/dotnet-watch/Internal/FileWatcher/PollingFileWatcher.cs +++ b/src/BuiltInTools/dotnet-watch/Internal/FileWatcher/PollingFileWatcher.cs @@ -15,7 +15,7 @@ internal class PollingFileWatcher : IFileSystemWatcher private Dictionary _knownEntities = new(); private Dictionary _tempDictionary = new(); - private Dictionary _changes = new(); + private Dictionary _changes = new(); private Thread _pollingThread; private bool _raiseEvents; @@ -40,7 +40,7 @@ public PollingFileWatcher(string watchedDirectory) _pollingThread.Start(); } - public event EventHandler<(string filePath, bool newFile)>? OnFileChange; + public event EventHandler<(string filePath, ChangeKind kind)>? OnFileChange; #pragma warning disable CS0067 // not used public event EventHandler? OnError; @@ -106,8 +106,8 @@ private void CheckForChangedFiles() if (!_knownEntities.ContainsKey(fullFilePath)) { - // New file - RecordChange(f, isNewFile: true); + // New file or directory + RecordChange(f, ChangeKind.Add); } else { @@ -115,10 +115,11 @@ private void CheckForChangedFiles() try { - if (fileMeta.FileInfo.LastWriteTime != f.LastWriteTime) + if (!fileMeta.FileInfo.Attributes.HasFlag(FileAttributes.Directory) && + fileMeta.FileInfo.LastWriteTime != f.LastWriteTime) { // File changed - RecordChange(f, isNewFile: false); + RecordChange(f, ChangeKind.Update); } _knownEntities[fullFilePath] = new FileMeta(fileMeta.FileInfo, foundAgain: true); @@ -136,8 +137,8 @@ private void CheckForChangedFiles() { if (!file.Value.FoundAgain) { - // File deleted - RecordChange(file.Value.FileInfo, isNewFile: false); + // File or directory deleted + RecordChange(file.Value.FileInfo, ChangeKind.Delete); } } @@ -148,7 +149,7 @@ private void CheckForChangedFiles() _tempDictionary.Clear(); } - private void RecordChange(FileSystemInfo fileInfo, bool isNewFile) + private void RecordChange(FileSystemInfo fileInfo, ChangeKind kind) { if (_changes.ContainsKey(fileInfo.FullName) || fileInfo.FullName.Equals(_watchedDirectory.FullName, StringComparison.Ordinal)) @@ -156,18 +157,15 @@ private void RecordChange(FileSystemInfo fileInfo, bool isNewFile) return; } - _changes.Add(fileInfo.FullName, isNewFile); + _changes.Add(fileInfo.FullName, kind); - if (fileInfo.FullName != _watchedDirectory.FullName) + if (fileInfo is FileInfo { Directory: { } directory }) { - if (fileInfo is FileInfo { Directory: { } directory }) - { - RecordChange(directory, isNewFile: false); - } - else if (fileInfo is DirectoryInfo { Parent: { } parent }) - { - RecordChange(parent, isNewFile: false); - } + RecordChange(directory, ChangeKind.Update); + } + else if (fileInfo is DirectoryInfo { Parent: { } parent }) + { + RecordChange(parent, ChangeKind.Update); } } @@ -202,14 +200,14 @@ private static void ForeachEntityInDirectory(DirectoryInfo dirInfo, Action fileSet, DateTime buildCompletionTime, IReporter reporter) : IDisposable { private static readonly TimeSpan s_debounceInterval = TimeSpan.FromMilliseconds(50); + private static readonly DateTime s_fileNotExistFileTime = DateTime.FromFileTime(0); private readonly FileWatcher _fileWatcher = new(fileSet, reporter); private readonly object _changedFilesLock = new(); - private readonly ConcurrentDictionary _changedFiles = new(StringComparer.Ordinal); + private readonly ConcurrentDictionary _changedFiles = new(StringComparer.Ordinal); - private TaskCompletionSource? _tcs; + private TaskCompletionSource? _tcs; private bool _initialized; private bool _disposed; @@ -65,7 +67,7 @@ private void EnsureInitialized() continue; } - FileItem[] changedFiles; + ChangedFile[] changedFiles; lock (_changedFilesLock) { changedFiles = _changedFiles.Values.ToArray(); @@ -82,7 +84,7 @@ private void EnsureInitialized() }, default, TaskCreationOptions.LongRunning, TaskScheduler.Default); - void FileChangedCallback(string path, bool newFile) + void FileChangedCallback(string path, ChangeKind kind) { // only handle file changes: if (Directory.Exists(path)) @@ -90,43 +92,66 @@ void FileChangedCallback(string path, bool newFile) return; } - try + if (kind != ChangeKind.Delete) { - // TODO: Deleted files will be ignored https://github.com/dotnet/sdk/issues/42535 - - // Do not report changes to files that happened during build: - var creationTime = File.GetCreationTimeUtc(path); - var writeTime = File.GetLastWriteTimeUtc(path); - if (creationTime.Ticks < buildCompletionTime.Ticks && writeTime.Ticks < buildCompletionTime.Ticks) + try + { + // Do not report changes to files that happened during build: + var creationTime = File.GetCreationTimeUtc(path); + var writeTime = File.GetLastWriteTimeUtc(path); + + if (creationTime == s_fileNotExistFileTime || writeTime == s_fileNotExistFileTime) + { + // file might have been deleted since we received the event + kind = ChangeKind.Delete; + } + else if (creationTime.Ticks < buildCompletionTime.Ticks && writeTime.Ticks < buildCompletionTime.Ticks) + { + reporter.Verbose( + $"Ignoring file change during build: {kind} '{path}' " + + $"(created {FormatTimestamp(creationTime)} and written {FormatTimestamp(writeTime)} before {FormatTimestamp(buildCompletionTime)})."); + + return; + } + else if (writeTime > creationTime) + { + reporter.Verbose($"File change: {kind} '{path}' (written {FormatTimestamp(writeTime)} after {FormatTimestamp(buildCompletionTime)})."); + } + else + { + reporter.Verbose($"File change: {kind} '{path}' (created {FormatTimestamp(creationTime)} after {FormatTimestamp(buildCompletionTime)})."); + } + } + catch (Exception e) { - reporter.Verbose($"Ignoring file updated during build: '{path}' ({FormatTimestamp(creationTime)},{FormatTimestamp(writeTime)} < {FormatTimestamp(buildCompletionTime)})."); + reporter.Verbose($"Ignoring file '{path}' due to access error: {e.Message}."); return; } } - catch (Exception e) + + if (kind == ChangeKind.Delete) { - reporter.Verbose($"Ignoring file '{path}' due to access error: {e.Message}."); - return; + reporter.Verbose($"File '{path}' deleted after {FormatTimestamp(buildCompletionTime)}."); } - if (newFile) + if (kind == ChangeKind.Add) { lock (_changedFilesLock) { - _changedFiles.TryAdd(path, new FileItem { FilePath = path, IsNewFile = newFile }); + _changedFiles.TryAdd(path, new ChangedFile(new FileItem { FilePath = path }, kind)); } } else if (fileSet.TryGetValue(path, out var fileItem)) { lock (_changedFilesLock) { - _changedFiles.TryAdd(path, fileItem); + _changedFiles.TryAdd(path, new ChangedFile(fileItem, kind)); } } } } - public Task GetChangedFilesAsync(CancellationToken cancellationToken, bool forceWaitForNewUpdate = false) + public Task GetChangedFilesAsync(CancellationToken cancellationToken, bool forceWaitForNewUpdate = false) { EnsureInitialized(); @@ -142,6 +167,6 @@ void FileChangedCallback(string path, bool newFile) } internal static string FormatTimestamp(DateTime time) - => time.ToString("yyyy-MM-dd HH:mm:ss.fffffff"); + => time.ToString("HH:mm:ss.fffffff"); } } diff --git a/src/Cli/dotnet/Telemetry/DevDeviceIDGetter.cs b/src/Cli/dotnet/Telemetry/DevDeviceIDGetter.cs index 360e530e3b49..2bb88a290745 100644 --- a/src/Cli/dotnet/Telemetry/DevDeviceIDGetter.cs +++ b/src/Cli/dotnet/Telemetry/DevDeviceIDGetter.cs @@ -38,8 +38,8 @@ private static string GetCachedDeviceId() if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - // Get device Id from Windows registry - using (var key = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Microsoft\DeveloperTools")) + // Get device Id from Windows registry matching the OS architecture + using (var key = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64).OpenSubKey(@"SOFTWARE\Microsoft\DeveloperTools")) { deviceId = key?.GetValue("deviceid") as string; } @@ -80,10 +80,16 @@ private static void CacheDeviceId(string deviceId) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - // Cache device Id in Windows registry - using (var key = Registry.CurrentUser.CreateSubKey(@"SOFTWARE\Microsoft\DeveloperTools")) + // Cache device Id in Windows registry matching the OS architecture + using (RegistryKey baseKey = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64)) { - key.SetValue("deviceid", deviceId); + using(var key = baseKey.CreateSubKey(@"SOFTWARE\Microsoft\DeveloperTools")) + { + if (key != null) + { + key.SetValue("deviceid", deviceId); + } + } } } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) diff --git a/src/Cli/dotnet/commands/dotnet-tool/update/ToolUpdateCommand.cs b/src/Cli/dotnet/commands/dotnet-tool/update/ToolUpdateCommand.cs index 1499142be4c0..42b9dd56417e 100644 --- a/src/Cli/dotnet/commands/dotnet-tool/update/ToolUpdateCommand.cs +++ b/src/Cli/dotnet/commands/dotnet-tool/update/ToolUpdateCommand.cs @@ -8,6 +8,7 @@ using Microsoft.DotNet.ToolManifest; using Microsoft.DotNet.ToolPackage; using Microsoft.DotNet.Tools.Tool.Common; +using Microsoft.Extensions.EnvironmentAbstractions; using CreateShellShimRepository = Microsoft.DotNet.Tools.Tool.Install.CreateShellShimRepository; namespace Microsoft.DotNet.Tools.Tool.Update @@ -43,16 +44,17 @@ public ToolUpdateCommand( localToolsResolverCache, reporter); + _global = result.GetValue(ToolInstallCommandParser.GlobalOption); + _toolPath = result.GetValue(ToolInstallCommandParser.ToolPathOption); + DirectoryPath? location = string.IsNullOrWhiteSpace(_toolPath) ? null : new DirectoryPath(_toolPath); _toolUpdateGlobalOrToolPathCommand = toolUpdateGlobalOrToolPathCommand ?? new ToolUpdateGlobalOrToolPathCommand( result, createToolPackageStoreDownloaderUninstaller, createShellShimRepository, - reporter); - - _global = result.GetValue(ToolInstallCommandParser.GlobalOption); - _toolPath = result.GetValue(ToolInstallCommandParser.ToolPathOption); + reporter, + ToolPackageFactory.CreateToolPackageStoreQuery(location)); } diff --git a/src/Cli/dotnet/commands/dotnet-workload/list/VisualStudioWorkloads.cs b/src/Cli/dotnet/commands/dotnet-workload/list/VisualStudioWorkloads.cs index 17258847bd3b..43553bb769e9 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/list/VisualStudioWorkloads.cs +++ b/src/Cli/dotnet/commands/dotnet-workload/list/VisualStudioWorkloads.cs @@ -4,7 +4,6 @@ using System.Runtime.Versioning; using Microsoft.Deployment.DotNet.Releases; using Microsoft.DotNet.Cli.Utils; -using Microsoft.DotNet.ToolPackage; using Microsoft.DotNet.Workloads.Workload.Install; using Microsoft.DotNet.Workloads.Workload.List; using Microsoft.NET.Sdk.WorkloadManifestReader; @@ -33,6 +32,16 @@ internal static class VisualStudioWorkloads "Microsoft.VisualStudio.Product.Enterprise", }; + /// + /// Default prefix to use for Visual Studio component and component group IDs. + /// + private static readonly string s_visualStudioComponentPrefix = "Microsoft.NET.Component"; + + /// + /// Well-known prefixes used by some workloads that can be replaced when generating component IDs. + /// + private static readonly string[] s_wellKnownWorkloadPrefixes = { "Microsoft.NET.", "Microsoft." }; + /// /// The SWIX package ID wrapping the SDK installer in Visual Studio. The ID should contain /// the SDK version as a suffix, e.g., "Microsoft.NetCore.Toolset.5.0.403". @@ -40,12 +49,44 @@ internal static class VisualStudioWorkloads private static readonly string s_visualStudioSdkPackageIdPrefix = "Microsoft.NetCore.Toolset."; /// - /// Gets a set of workload components based on the available set of workloads for the current SDK. + /// Gets a dictionary of mapping possible Visual Studio component IDs to .NET workload IDs in the current SDK. /// /// The workload resolver used to obtain available workloads. - /// A collection of Visual Studio component IDs corresponding to workload IDs. - internal static IEnumerable GetAvailableVisualStudioWorkloads(IWorkloadResolver workloadResolver) => - workloadResolver.GetAvailableWorkloads().Select(w => w.Id.ToString().Replace('-', '.')); + /// A dictionary of Visual Studio component IDs corresponding to workload IDs. + internal static Dictionary GetAvailableVisualStudioWorkloads(IWorkloadResolver workloadResolver) + { + Dictionary visualStudioComponentWorkloads = new(StringComparer.OrdinalIgnoreCase); + + // Iterate through all the available workload IDs and generate potential Visual Studio + // component IDs that map back to the original workload ID. This ensures that we + // can do reverse lookups for special cases where a workload ID contains a prefix + // corresponding with the full VS component ID prefix. For example, + // Microsoft.NET.Component.runtime.android would be a valid component ID for both + // microsoft-net-runtime-android and runtime-android. + foreach (var workload in workloadResolver.GetAvailableWorkloads()) + { + string workloadId = workload.Id.ToString(); + // Old style VS components simply replaced '-' with '.' in the workload ID. + string componentId = workload.Id.ToString().Replace('-', '.'); + + visualStudioComponentWorkloads.Add(componentId, workloadId); + + // Starting in .NET 9.0 and VS 17.12, workload components will follow the VS naming convention. + foreach (string wellKnownPrefix in s_wellKnownWorkloadPrefixes) + { + if (componentId.StartsWith(wellKnownPrefix, StringComparison.OrdinalIgnoreCase)) + { + componentId = componentId.Substring(wellKnownPrefix.Length); + break; + } + } + + componentId = s_visualStudioComponentPrefix + "." + componentId; + visualStudioComponentWorkloads.Add(componentId, workloadId); + } + + return visualStudioComponentWorkloads; + } /// /// Finds all workloads installed by all Visual Studio instances given that the @@ -59,7 +100,7 @@ internal static IEnumerable GetAvailableVisualStudioWorkloads(IWorkloadR internal static void GetInstalledWorkloads(IWorkloadResolver workloadResolver, InstalledWorkloadsCollection installedWorkloads, SdkFeatureBand? sdkFeatureBand = null) { - IEnumerable visualStudioWorkloadIds = GetAvailableVisualStudioWorkloads(workloadResolver); + Dictionary visualStudioWorkloadIds = GetAvailableVisualStudioWorkloads(workloadResolver); HashSet installedWorkloadComponents = new(); // Visual Studio instances contain a large set of packages and we have to perform a linear @@ -105,10 +146,9 @@ internal static void GetInstalledWorkloads(IWorkloadResolver workloadResolver, continue; } - if (visualStudioWorkloadIds.Contains(packageId, StringComparer.OrdinalIgnoreCase)) + if (visualStudioWorkloadIds.TryGetValue(packageId, out string workloadId)) { - // Normalize back to an SDK style workload ID. - installedWorkloadComponents.Add(packageId.Replace('.', '-')); + installedWorkloadComponents.Add(workloadId); } } diff --git a/src/Installer/finalizer/CMakeLists.txt b/src/Installer/finalizer/CMakeLists.txt index 63baa941a090..47163924e66e 100644 --- a/src/Installer/finalizer/CMakeLists.txt +++ b/src/Installer/finalizer/CMakeLists.txt @@ -42,12 +42,15 @@ add_compile_options($<$:/we4703>) # Potentially uninitia add_compile_options($<$:/we4789>) # destination of memory copy is too small add_compile_options($<$:/we4995>) # 'function': name was marked as #pragma deprecated add_compile_options($<$:/we4996>) # 'function': was declared deprecated also 'std::' +add_compile_options($<$:/guard:cf>) # Enable control flow guard add_executable(Finalizer finalizer.cpp native.rc ) +add_link_options(/guard:cf) + # These are normally part of a .vcxproj in Visual Studio, but appears to be missing when CMAKE generates a .vcxproj for arm64. target_link_libraries(Finalizer shell32.lib) target_link_libraries(Finalizer advapi32.lib) diff --git a/src/SourceBuild/content/Directory.Build.props b/src/SourceBuild/content/Directory.Build.props index 9b9e79c842f9..8ed26023229b 100644 --- a/src/SourceBuild/content/Directory.Build.props +++ b/src/SourceBuild/content/Directory.Build.props @@ -171,7 +171,8 @@ $([MSBuild]::NormalizeDirectory('$(ArtifactsDir)', 'prebuilt-report')) $([MSBuild]::NormalizeDirectory('$(PackageReportDir)', 'prebuilt-packages')) - + $([MSBuild]::NormalizeDirectory('$(PackageReportDir)', '$(MSBuildProjectName)')) + $([MSBuild]::NormalizeDirectory('$(SrcDir)', 'source-build-reference-packages', 'src')) $([MSBuild]::NormalizeDirectory('$(PrereqsPackagesDir)', 'reference')) Private.SourceBuilt.Artifacts diff --git a/src/SourceBuild/content/repo-projects/Directory.Build.targets b/src/SourceBuild/content/repo-projects/Directory.Build.targets index ccb25f46dcf8..e95aefe47f4c 100644 --- a/src/SourceBuild/content/repo-projects/Directory.Build.targets +++ b/src/SourceBuild/content/repo-projects/Directory.Build.targets @@ -511,6 +511,19 @@ Condition="'@(_InnerPackageCacheFiles)' != ''" /> + + + + + + + + + diff --git a/src/SourceBuild/content/test/Microsoft.DotNet.SourceBuild.SmokeTests/assets/LicenseScanTests/LicenseExclusions.txt b/src/SourceBuild/content/test/Microsoft.DotNet.SourceBuild.SmokeTests/assets/LicenseScanTests/LicenseExclusions.txt index 5e81d8c2fbaf..b324bfeab7a7 100644 --- a/src/SourceBuild/content/test/Microsoft.DotNet.SourceBuild.SmokeTests/assets/LicenseScanTests/LicenseExclusions.txt +++ b/src/SourceBuild/content/test/Microsoft.DotNet.SourceBuild.SmokeTests/assets/LicenseScanTests/LicenseExclusions.txt @@ -172,6 +172,7 @@ src/runtime/src/libraries/System.Text.Json/roadmap/images/higher-level-component # False positive src/sdk/THIRD-PARTY-NOTICES.TXT|unknown-license-reference +src/sdk/src/SourceBuild/patches/fsharp/0001-Fix17713-Reverting-PR-17649-Make-the-interaction-bet.patch|unknown-license-reference # Configuration, doesn't apply to source directly src/sdk/src/VirtualMonoRepo/THIRD-PARTY-NOTICES.template.txt diff --git a/src/SourceBuild/content/test/Microsoft.DotNet.SourceBuild.SmokeTests/assets/SdkContentTests/MsftToSbSdkFiles.diff b/src/SourceBuild/content/test/Microsoft.DotNet.SourceBuild.SmokeTests/assets/SdkContentTests/MsftToSbSdkFiles.diff index 8cd6f6c82125..75b77d3c30c3 100644 --- a/src/SourceBuild/content/test/Microsoft.DotNet.SourceBuild.SmokeTests/assets/SdkContentTests/MsftToSbSdkFiles.diff +++ b/src/SourceBuild/content/test/Microsoft.DotNet.SourceBuild.SmokeTests/assets/SdkContentTests/MsftToSbSdkFiles.diff @@ -45,7 +45,7 @@ index ------------ ./packs/Microsoft.NETCore.App.Ref/x.y.z/ ./packs/Microsoft.NETCore.App.Ref/x.y.z/analyzers/ @@ ------------ @@ - ./sdk-manifests/ + ./sdk-manifests/x.y.z/ ./sdk-manifests/x.y.z/ ./sdk-manifests/x.y.z/ -./sdk-manifests/x.y.z/ diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.EolTargetFrameworks.targets b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.EolTargetFrameworks.targets index d9bd74aa368f..8608d9f77a33 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.EolTargetFrameworks.targets +++ b/src/Tasks/Microsoft.NET.Build.Tasks/targets/Microsoft.NET.EolTargetFrameworks.targets @@ -21,7 +21,7 @@ Copyright (c) .NET Foundation. All rights reserved. receive servicing updates and security fixes. --> - <_EolNetCoreTargetFrameworkVersions Include="1.0;1.1;2.0;2.1;2.2;3.0;3.1;5.0" /> + <_EolNetCoreTargetFrameworkVersions Include="1.0;1.1;2.0;2.1;2.2;3.0;3.1;5.0;7.0" /> <_MinimumNonEolSupportedNetCoreTargetFramework>net6.0 diff --git a/test/HelixTasks/AssemblyScheduler.cs b/test/HelixTasks/AssemblyScheduler.cs index 65c431bad35d..428d40dff3f4 100644 --- a/test/HelixTasks/AssemblyScheduler.cs +++ b/test/HelixTasks/AssemblyScheduler.cs @@ -267,13 +267,14 @@ private static List GetTypeInfoList(MetadataReader reader) /// private static bool ShouldIncludeType(MetadataReader reader, TypeDefinition type, int testMethodCount) { - // xunit only handles public, non-abstract classes + // xunit only handles public, non-abstract, non-generic classes var isPublic = - TypeAttributes.Public == (type.Attributes & TypeAttributes.Public) || - TypeAttributes.NestedPublic == (type.Attributes & TypeAttributes.NestedPublic); + TypeAttributes.Public == (type.Attributes & TypeAttributes.VisibilityMask) || + TypeAttributes.NestedPublic == (type.Attributes & TypeAttributes.VisibilityMask); if (!isPublic || TypeAttributes.Abstract == (type.Attributes & TypeAttributes.Abstract) || - TypeAttributes.Class != (type.Attributes & TypeAttributes.Class)) + type.GetGenericParameters().Count != 0 || + TypeAttributes.Class != (type.Attributes & TypeAttributes.ClassSemanticsMask)) { return false; } diff --git a/test/Microsoft.NET.Build.Tests/GivenThatWeWantToTargetEolFrameworks.cs b/test/Microsoft.NET.Build.Tests/GivenThatWeWantToTargetEolFrameworks.cs index 9f196512336e..6e020c0df98f 100644 --- a/test/Microsoft.NET.Build.Tests/GivenThatWeWantToTargetEolFrameworks.cs +++ b/test/Microsoft.NET.Build.Tests/GivenThatWeWantToTargetEolFrameworks.cs @@ -15,6 +15,7 @@ public GivenThatWeWantToTargetEolFrameworks(ITestOutputHelper log) : base(log) [InlineData("netcoreapp3.0")] [InlineData("netcoreapp3.1")] [InlineData("net5.0")] + [InlineData("net7.0")] public void It_warns_that_framework_is_out_of_support(string targetFrameworks) { var testProject = new TestProject() diff --git a/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAnAotApp.cs b/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAnAotApp.cs index 85473ebe5cd4..52ead18ac2c2 100644 --- a/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAnAotApp.cs +++ b/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAnAotApp.cs @@ -44,7 +44,7 @@ public void NativeAot_hw_runs_with_no_warnings_when_PublishAot_is_enabled(string var publishCommand = new PublishCommand(Log, Path.Combine(testAsset.TestRoot, testProject.Name)); publishCommand - .Execute($"/p:UseCurrentRuntimeIdentifier=true", "/p:SelfContained=true") + .Execute($"/p:UseCurrentRuntimeIdentifier=true", "/p:SelfContained=true", "/p:CheckEolTargetFramework=false") .Should().Pass() .And.NotHaveStdOutContaining("IL2026") .And.NotHaveStdErrContaining("NETSDK1179") @@ -88,7 +88,7 @@ public void NativeAot_hw_runs_with_no_warnings_when_PublishAot_is_false(string t var publishCommand = new PublishCommand(testAsset); publishCommand - .Execute($"/p:RuntimeIdentifier={rid}", "/p:SelfContained=true") + .Execute($"/p:RuntimeIdentifier={rid}", "/p:SelfContained=true", "/p:CheckEolTargetFramework=false") .Should().Pass() .And.NotHaveStdOutContaining("IL2026") .And.NotHaveStdErrContaining("NETSDK1179") diff --git a/test/Microsoft.WebTools.AspireService.Tests/AspireServerServiceTests.cs b/test/Microsoft.WebTools.AspireService.Tests/AspireServerServiceTests.cs new file mode 100644 index 000000000000..69d06b8c0479 --- /dev/null +++ b/test/Microsoft.WebTools.AspireService.Tests/AspireServerServiceTests.cs @@ -0,0 +1,532 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable disable + +using System.Net; +using System.Net.Http.Headers; +using System.Net.WebSockets; +using System.Security.Cryptography.X509Certificates; +using System.Text.Json; +using Microsoft.WebTools.AspireServer.Helpers; +using Microsoft.WebTools.AspireServer.Models; + +namespace Microsoft.WebTools.AspireServer.UnitTests; + +public class AspireServerServiceTests +{ + private const string Project1Path = @"c:\test\Projects\project1.csproj"; + private const int ProcessId = 34213; + private const string DcpId = "myid"; + private const string SpecificProfileName = "SpecificProfile"; + private const string VersionedSessionUrl = $"{RunSessionRequest.Url}?{RunSessionRequest.VersionQuery}={RunSessionRequest.OurProtocolVersion}"; + + private static readonly TestRunSessionRequest Project1SessionRequest = new TestRunSessionRequest(Project1Path, debugging: false, launchProfile: null, disableLaunchProfile: false) + { + args = new List { "--project1Arg" }, + env = new List { new EnvVar { Name = "var1", Value = "value1" } } + }; + + [Fact] + public async Task SessionStarted_Test() + { + var mocks = new Mocks(); + + var server = await GetAspireServer(mocks); + + // Start listening + TaskCompletionSource connected = new(); + + TaskCompletionSource notificationTask = new(); + _ = listenForSessionUpdatesAsync(server, connected, (sn) => + { + notificationTask.SetResult((SessionChangeNotification)sn); + }); + + await connected.Task; + + await server.SessionStartedAsync(DcpId,"1", ProcessId, CancellationToken.None); + + var result = await notificationTask.Task; + + Assert.Equal(ProcessId, result.PID); + Assert.Equal("1", result.SessionId); + + await server.DisposeAsync(); + + mocks.Verify(); + } + + [Fact] + public async Task SessionEndedAsync_Test() + { + var mocks = new Mocks(); + + var server = await GetAspireServer(mocks); + + // Start listening + TaskCompletionSource connected = new(); + TaskCompletionSource sessionEndNotificationTask = new(); + _ = listenForSessionUpdatesAsync(server, connected, (sn) => + { + if (sn.NotificationType == NotificationType.SessionTerminated) + { + sessionEndNotificationTask.SetResult((SessionChangeNotification)sn); + } + }); + + await connected.Task; + + await server.SessionEndedAsync(DcpId, "1", ProcessId, 130, CancellationToken.None); + + var result = await sessionEndNotificationTask.Task; + Assert.Equal(ProcessId, result.PID); + Assert.Equal("1", result.SessionId); + Assert.Equal(130, result.ExitCode); + + await server.DisposeAsync(); + + mocks.Verify(); + } + + [Fact] + public async Task LaunchProject_Success() + { + var mocks = new Mocks(); + + mocks.GetOrCreate() + .ImplementStartProjectAsync(DcpId, "2"); + + var server = await GetAspireServer(mocks); + var tokens = await server.GetServerVariablesAsync(); + + using HttpClient client = GetHttpClient(tokens); + + HttpResponseMessage response; + response = await client.PutAsJsonAsync(VersionedSessionUrl, Project1SessionRequest); + + Assert.Equal(HttpStatusCode.Created, response.StatusCode); + Assert.Equal($"{client.BaseAddress}run_session/2", response.Headers.Location.AbsoluteUri); + + await server.DisposeAsync(); + + mocks.Verify(); + } + + [Fact] + public async Task LaunchProject_Success_ThenStopProcessRequest() + { + var mocks = new Mocks(); + + mocks.GetOrCreate() + .ImplementStartProjectAsync(DcpId, "2") + .ImplementStopSessionAsync(DcpId, "2", exists: true) + .ImplementStopSessionAsync(DcpId, "3", exists: false); + + var server = await GetAspireServer(mocks); + var tokens = await server.GetServerVariablesAsync(); + + using HttpClient client = GetHttpClient(tokens); + + var response = await client.PutAsJsonAsync(VersionedSessionUrl, Project1SessionRequest); + Assert.Equal(HttpStatusCode.Created, response.StatusCode); + + // Now send a stop session + response = await client.DeleteAsync(RunSessionRequest.Url + "/2"); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + // Validate NoContent response if session not found + response = await client.DeleteAsync(RunSessionRequest.Url + "/3"); + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + + await server.DisposeAsync(); + + mocks.Verify(); + } + + [Fact] + public async Task LaunchProject_FailedToLaunchProject() + { + var mocks = new Mocks(); + + mocks.GetOrCreate() + .ImplementStartProjectAsync(DcpId, "2", new Exception("Launch project failed")); + + var server = await GetAspireServer(mocks); + + var tokens = await server.GetServerVariablesAsync(); + using HttpClient client = GetHttpClient(tokens); + + var response = await client.PutAsJsonAsync(VersionedSessionUrl, Project1SessionRequest); + + Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); + Assert.Equal("application/json; charset=utf-8", response.Content.Headers.ContentType.ToString()); + Assert.Equal("{\"error\":{\"message\":\"Launch project failed\"}}", await response.Content.ReadAsStringAsync()); + + await server.DisposeAsync(); + mocks.Verify(); + } + + [Fact] + public async Task LaunchProject_FailNoBearerToken() + { + var mocks = new Mocks(); + + var server = await GetAspireServer(mocks); + + var tokens = await server.GetServerVariablesAsync(); + using HttpClient client = GetHttpClient(tokens); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "badToken"); + + var response = await client.PutAsJsonAsync(VersionedSessionUrl, Project1SessionRequest); + + Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); + + await server.DisposeAsync(); + mocks.Verify(); + } + + [Fact] + public async Task LaunchProject_FailWrongUrl() + { + var mocks = new Mocks(); + + var server = await GetAspireServer(mocks); + + var tokens = await server.GetServerVariablesAsync(); + using HttpClient client = GetHttpClient(tokens); + + var response = await client.PutAsJsonAsync("/run_badurl", Project1SessionRequest); + + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + + await server.DisposeAsync(); + + mocks.Verify(); + } + + [Fact] + public async Task LaunchProject_NotAPUTRequest() + { + var mocks = new Mocks(); + + var aspireServer = await GetAspireServer(mocks); + + var tokens = await aspireServer.GetServerVariablesAsync(); + using HttpClient client = GetHttpClient(tokens); + + var response = await client.PostAsJsonAsync(VersionedSessionUrl, Project1SessionRequest); + + Assert.Equal(HttpStatusCode.MethodNotAllowed, response.StatusCode); + + await aspireServer.DisposeAsync(); + + mocks.Verify(); + } + + [Fact] + public async Task StopSession_FailNoBearerToken() + { + var mocks = new Mocks(); + + var server = await GetAspireServer(mocks); + + var tokens = await server.GetServerVariablesAsync(); + using HttpClient client = GetHttpClient(tokens); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "badToken"); + + var response = await client.DeleteAsync(RunSessionRequest.Url + "/2"); + + Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); + + await server.DisposeAsync(); + mocks.Verify(); + } + + [Fact] + public async Task Info_Success() + { + var mocks = new Mocks(); + + var server = await GetAspireServer(mocks); + + var tokens = await server.GetServerVariablesAsync(); + using HttpClient client = GetHttpClient(tokens); + + var response = await client.GetAsync(InfoResponse.Url); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + await server.DisposeAsync(); + mocks.Verify(); + } + + [Fact] + public async Task Info_FailNoBearerToken() + { + var mocks = new Mocks(); + + var server = await GetAspireServer(mocks); + + var tokens = await server.GetServerVariablesAsync(); + using HttpClient client = GetHttpClient(tokens); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "badToken"); + + var response = await client.GetAsync(InfoResponse.Url); + + Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); + + await server.DisposeAsync(); + mocks.Verify(); + } + + [Fact] + public async Task SendLogMessageAsync_Test() + { + var mocks = new Mocks(); + + var aspireServer = await GetAspireServer(mocks); + + + // Start listening + TaskCompletionSource connected = new(); + TaskCompletionSource notificationTask = new(); + _ = listenForSessionUpdatesAsync(aspireServer, connected, (sn) => + { + notificationTask.SetResult((SessionLogsNotification)sn); + }); + + await connected.Task; + + await aspireServer.SendLogMessageAsync(DcpId, "1", isStdErr: false, "My Message", CancellationToken.None); + + var result = await notificationTask.Task; + + Assert.Equal("My Message", result.LogMessage); + Assert.False(result.IsStdErr); + await aspireServer.DisposeAsync(); + + mocks.Verify(); + } + + [Fact] + public async Task GetEnvironmentForOrchestrator_Tests() + { + var mocks = new Mocks(); + + var server = await GetAspireServer(mocks, waitForListening: false); + + // First time should create a key + var envVars = await server.GetServerConnectionEnvironmentAsync(CancellationToken.None); + + Assert.Equal(3, envVars.Count); + var token = envVars[1]; + Assert.NotNull(token.Value); + + // Should return the same + envVars = await server.GetServerConnectionEnvironmentAsync(CancellationToken.None); + Assert.Equal(token, envVars[1]); + + mocks.Verify(); + } + + private async Task listenForSessionUpdatesAsync(AspireServerService aspireServer, TaskCompletionSource connected, Action callback) + { + var tokens = await aspireServer.GetServerVariablesAsync(); + using var httpClient = GetHttpClient(tokens); + + using var ws = new ClientWebSocket(); + ws.Options.SetRequestHeader("Authorization", $"Bearer {tokens.bearerToken}"); + try + { + await ws.ConnectAsync(new Uri($"wss://{tokens.serverAddress}{RunSessionRequest.Url}{SessionNotificationBase.Url}"), httpClient, CancellationToken.None); + } + catch (Exception ex) + { + Assert.Fail("Could not connect to session update endpoint: " + ex.ToString()); + connected.SetResult(false); + return; + } + + connected.SetResult(true); + + while (ws.State == WebSocketState.Open) + { + try + { + var (message, messageType) = await GetSocketMsgAsync(ws); + + if (messageType == WebSocketMessageType.Close) + { + await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None); + return; + } + else + { + var notificationBase = JsonSerializer.Deserialize(message, AspireServerService.JsonSerializerOptions); + if (notificationBase is null) + { + Console.WriteLine("Unexpected null SessionNotificationBase message"); + } + else if (notificationBase.NotificationType == NotificationType.ProcessRestarted || notificationBase.NotificationType == NotificationType.SessionTerminated) + { + var scn = JsonSerializer.Deserialize(message, AspireServerService.JsonSerializerOptions); + if (scn is null) + { + Assert.Fail("Unexpected null SessionChangeNotification message"); + } + else + { + callback.Invoke(scn); + } + } + else if (notificationBase.NotificationType == NotificationType.ServiceLogs) + { + var sessionLogs = JsonSerializer.Deserialize(message, AspireServerService.JsonSerializerOptions); + if (sessionLogs is null) + { + Assert.Fail("Unexpected null SessionLogsNotification message"); + } + else + { + callback.Invoke(sessionLogs); + } + } + } + } + catch + { + // This is expected if the connection is closed + return; + } + } + } + + private static HttpClient GetHttpClient((string serverAddress, string bearerToken, string certToken) tokens) + { + HttpClient client; + var serverCert = X509CertificateLoader.LoadCertificate(Convert.FromBase64String(tokens.certToken)); + var clientHandler = new HttpClientHandler() + { + ClientCertificateOptions = ClientCertificateOption.Manual, + SslProtocols = System.Security.Authentication.SslProtocols.None, + ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => + { + return cert?.Thumbprint == serverCert.Thumbprint; + } + }; + + client = new HttpClient(clientHandler); + client.BaseAddress = new Uri($"https://{tokens.serverAddress}"); + + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokens.bearerToken); + client.DefaultRequestHeaders.Add(HttpContextExtensions.DCPInstanceIDHeader, DcpId); + + return client; + } + + private async Task<(string, WebSocketMessageType)> GetSocketMsgAsync(ClientWebSocket client) + { + var rcvBuffer = new ArraySegment(new byte[2048]); + WebSocketReceiveResult rcvResult = await client.ReceiveAsync(rcvBuffer, CancellationToken.None); + if (rcvResult.MessageType == WebSocketMessageType.Text) + { + byte[] msgBytes = rcvBuffer.Skip(rcvBuffer.Offset).Take(rcvResult.Count).ToArray(); + return (Encoding.UTF8.GetString(msgBytes), rcvResult.MessageType); + } + + return (null, rcvResult.MessageType); + } + + private async Task GetAspireServer(Mocks mocks, bool waitForListening = true) + { + var ase = mocks.GetOrCreate(); + + var aspireServer = new AspireServerService(ase.Object, displayName: "Test server", Console.WriteLine); + + if (waitForListening) + { + await aspireServer.WaitForListeningAsync(); + } + + return aspireServer; + } + +#pragma warning disable IDE1006 // Naming Styles + internal class TestRunSessionRequestP4 + { + public string project_path { get; set; } = string.Empty; + public bool debug { get; set; } + public List env { get; set; } = new List(); + public List args { get; set; } = new List(); + public string launch_profile { get; set; } + public bool disable_launch_profile { get; set; } + } + + internal class TestRunSessionRequest + { + public TestRunSessionRequest(string projectPath, bool debugging, string launchProfile, bool disableLaunchProfile) + { + launch_configurations = new TestLaunchConfiguration[] + { + new() { + project_path = projectPath, + type = RunSessionRequest.ProjectLaunchConfigurationType, + mode= debugging? RunSessionRequest.DebugLaunchMode : RunSessionRequest.NoDebugLaunchMode, + launch_profile = launchProfile, + disable_launch_profile = disableLaunchProfile + } + }; + } + public TestLaunchConfiguration[] launch_configurations { get; set; } + public List env { get; set; } = new List(); + public List args { get; set; } = new List(); + + public TestRunSessionRequestP4 ToTestRunSessionRequestP4() + { + var launchConfig = launch_configurations[0]; + return new TestRunSessionRequestP4() + { + project_path = launchConfig.project_path, + debug = string.Equals(launchConfig.mode, RunSessionRequest.DebugLaunchMode, StringComparison.OrdinalIgnoreCase), + args = args, + env = env, + launch_profile = launchConfig.launch_profile, + disable_launch_profile = launchConfig.disable_launch_profile + }; + } + } + + internal class TestLaunchConfiguration + { + public string type { get; set; } = string.Empty; + public string project_path { get; set; } = string.Empty; + public string launch_profile { get; set; } + public bool disable_launch_profile { get; set; } + public string mode { get; set; } = string.Empty; + } + + internal class TestStopSessionRequest + { + public string session_id { get; set; } = string.Empty; + } +#pragma warning restore IDE1006 // Naming Styles +} + +internal static class AspireServerServiceExtensions +{ + public static async Task WaitForListeningAsync(this AspireServerService aspireServer) + { + string serverAddress = (await aspireServer.GetServerVariablesAsync()).serverAddress; + + // We need to wait on the port being available + await Helpers.CanConnectToPortAsync(new Uri($"http://{serverAddress}"), 5000, CancellationToken.None); + + } + + public static async Task<(string serverAddress, string bearerToken, string certToken)> GetServerVariablesAsync(this AspireServerService aspireServer) + { + var enVars = await aspireServer.GetServerConnectionEnvironmentAsync(CancellationToken.None); + return (enVars[0].Value, enVars[1].Value, enVars[2].Value); + } +} diff --git a/test/Microsoft.WebTools.AspireService.Tests/Microsoft.WebTools.AspireService.Tests.csproj b/test/Microsoft.WebTools.AspireService.Tests/Microsoft.WebTools.AspireService.Tests.csproj new file mode 100644 index 000000000000..927652b1cd10 --- /dev/null +++ b/test/Microsoft.WebTools.AspireService.Tests/Microsoft.WebTools.AspireService.Tests.csproj @@ -0,0 +1,18 @@ + + + + $(SdkTargetFramework) + Microsoft.WebTools.AspireServer.UnitTests + enable + Exe + false + + + + + + + + + + diff --git a/test/Microsoft.WebTools.AspireService.Tests/Mocks/Helpers.cs b/test/Microsoft.WebTools.AspireService.Tests/Mocks/Helpers.cs new file mode 100644 index 000000000000..19f6d17f15c3 --- /dev/null +++ b/test/Microsoft.WebTools.AspireService.Tests/Mocks/Helpers.cs @@ -0,0 +1,181 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections; +using System.Net; +using System.Net.Sockets; + +namespace Microsoft.WebTools.AspireServer.UnitTests; + +public static class Helpers +{ + public static async Task CanConnectToPortAsync(Uri url, uint msToWait, CancellationToken cancelToken) + { + bool connected = false; + Socket? ipv4Socket = null; + Socket? ipv6Socket = null; + + // Create a "client" socket on any available port + try + { + TimeoutSpan timeout = new(msToWait); + if (Socket.OSSupportsIPv4) + { + try + { + ipv4Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + ipv4Socket.Bind(new IPEndPoint(IPAddress.Any, 0)); + } + catch (SocketException) + { + if (ipv4Socket != null) + { + ipv4Socket.Close(); + ipv4Socket = null; + } + } + } + if (Socket.OSSupportsIPv6) + { + try + { + ipv6Socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp); + ipv6Socket.Bind(new IPEndPoint(IPAddress.IPv6Any, 0)); + } + catch (SocketException) + { + if (ipv6Socket != null) + { + ipv6Socket.Close(); + ipv6Socket = null; + } + } + } + + // No sockets means we aren't connected + if (ipv6Socket == null && ipv4Socket == null) + { + return false; + } + + // If we have an IP address we use that otherwise assume loopback + IPEndPoint ipv4ServerEndPoint; + IPEndPoint ipv6ServerEndPoint; + if (IPAddress.TryParse(url.Host, out var ipAddress)) + { + ipv4ServerEndPoint = new IPEndPoint(ipAddress.AddressFamily == AddressFamily.InterNetwork ? ipAddress : IPAddress.Loopback, url.Port); + ipv6ServerEndPoint = new IPEndPoint(ipAddress.AddressFamily == AddressFamily.InterNetworkV6 ? ipAddress : IPAddress.IPv6Loopback, url.Port); + } + else + { + ipv4ServerEndPoint = new IPEndPoint(IPAddress.Loopback, url.Port); + ipv6ServerEndPoint = new IPEndPoint(IPAddress.IPv6Loopback, url.Port); + } + + // If a process is passed in, we bail if it has exited + while (!connected && !timeout.Expired) + { + cancelToken.ThrowIfCancellationRequested(); + if (ipv4Socket != null) + { + try + { + // Now use IOControl to set the calls non blocking + ipv4Socket.Blocking = false; + // Since we are non-blocking, the Connect should throw an error indicating + // it needs time to connect + ipv4Socket.Connect(ipv4ServerEndPoint); + } + catch (SocketException) + { + // Now ping retry and block for a millisecond timeout + ArrayList connectList = new ArrayList() {ipv4Socket}; + Socket.Select(null, connectList, null, 1000 /*microSecond -- in here, 1 milli-second*/); + if (connectList.Count == 1) + { + connected = true; + break; + } + } + finally + { + // TODO: why do we set the sockets back to blocking? + ipv4Socket.Blocking = true; + } + } + + // Now try IPV6 + if (ipv6Socket != null) + { + // Couldn't connect with IPV4, so try IPV6 + try + { + ipv6Socket.Blocking = false; + ipv6Socket.Connect(ipv6ServerEndPoint); + } + catch (SocketException) + { + // Ping retry + ArrayList connectList = new ArrayList() {ipv6Socket}; + Socket.Select(null, connectList, null, 1000 /*microSecond -- in here, 1 milli-second*/); + if (connectList.Count == 1) + { + connected = true; + break; + } + } + finally + { + // TODO: why do we set the sockets back to blocking? + ipv6Socket.Blocking = true; + } + } + + // Wait a bit and try again + await Task.Delay(20, cancelToken); + } + + } + finally + { + if (ipv4Socket != null) + { + ipv4Socket.Close(); + } + + if (ipv6Socket != null) + { + ipv6Socket.Close(); + } + } + + return connected; + } +} + +internal class TimeoutSpan +{ + private readonly long _duration; + private long _startingTickCount; + + public TimeoutSpan(long durationInMilliseconds) + { + // There are 10000 ticks in a millisecond so need to adjust accordingly + _duration = durationInMilliseconds * 10000; + Reset(); + } + + public bool Expired + { + get + { + return _duration != 0 && (DateTime.UtcNow.Ticks - _startingTickCount) > _duration; + } + } + + public void Reset() + { + // DateTime.UtcNow is way more efficient than DateTime.Now since it doesn't have to deal with locale, DST, etc + _startingTickCount = DateTime.UtcNow.Ticks; + } +} diff --git a/test/Microsoft.WebTools.AspireService.Tests/Mocks/IAspireServerEventsMock.cs b/test/Microsoft.WebTools.AspireService.Tests/Mocks/IAspireServerEventsMock.cs new file mode 100644 index 000000000000..3c805871fdf4 --- /dev/null +++ b/test/Microsoft.WebTools.AspireService.Tests/Mocks/IAspireServerEventsMock.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +using Microsoft.WebTools.AspireServer.Contracts; +using Moq; + +namespace Microsoft.WebTools.AspireServer.UnitTests; + +internal class IAspireServerEventsMock : MockFactory +{ + public IAspireServerEventsMock(Mocks mocks, MockBehavior? mockBehavior = null) + : base(mocks, mockBehavior) + { + } + + public IAspireServerEventsMock ImplementStartProjectAsync(string dcpId, string sessionId, Exception? ex = null) + { + MockObject.Setup(x => x.StartProjectAsync(dcpId, It.IsAny(), It.IsAny())) + .Returns(() => + { + if (ex is not null) + { + throw ex; + } + + return new ValueTask(sessionId); + }) + .Verifiable(); + return this; + } + + public IAspireServerEventsMock ImplementStopSessionAsync(string dcpId, string sessionId, bool exists, Exception? ex = null) + { + MockObject.Setup(x => x.StopSessionAsync(dcpId, sessionId, It.IsAny())) + .Returns(() => + { + if (ex is not null) + { + throw ex; + } + + return new ValueTask(exists); + }) + .Verifiable(); + return this; + } +} diff --git a/test/Microsoft.WebTools.AspireService.Tests/Mocks/IServiceProviderMock.cs b/test/Microsoft.WebTools.AspireService.Tests/Mocks/IServiceProviderMock.cs new file mode 100644 index 000000000000..c975028c392f --- /dev/null +++ b/test/Microsoft.WebTools.AspireService.Tests/Mocks/IServiceProviderMock.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Moq; + +namespace Microsoft.WebTools.AspireServer.UnitTests; + +internal class IServiceProviderMock : MockFactory +{ + public IServiceProviderMock(Mocks mocks, MockBehavior? mockBehavior = null) + : base(mocks, mockBehavior) + { + } + + public IServiceProviderMock ImplementService(Type type, object service) + { + MockObject.Setup(x => x.GetService(type)).Returns(service); + + return this; + } +} diff --git a/test/Microsoft.WebTools.AspireService.Tests/Mocks/MockFactory.cs b/test/Microsoft.WebTools.AspireService.Tests/Mocks/MockFactory.cs new file mode 100644 index 000000000000..ef9f4467013c --- /dev/null +++ b/test/Microsoft.WebTools.AspireService.Tests/Mocks/MockFactory.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Moq; + +namespace Microsoft.WebTools.AspireServer.UnitTests; + +public interface IMockFactory +{ + void Verify(); + object GetObject(); +} + +public class MockFactory : IMockFactory where T : class +{ + public MockFactory(Mocks mocks, MockBehavior? mockBehavior) + { + AllMocks = mocks; + MockObject = new Mock(mockBehavior ?? MockBehavior.Strict); + } + + protected Mocks AllMocks { get; } + public Mock MockObject { get; } + + public T Object => MockObject.Object; + + public virtual void Verify() + { + MockObject.VerifyAll(); + } + + public object GetObject() + { + return Object; + } +} diff --git a/test/Microsoft.WebTools.AspireService.Tests/Mocks/Mocks.cs b/test/Microsoft.WebTools.AspireService.Tests/Mocks/Mocks.cs new file mode 100644 index 000000000000..0b546ad5b20e --- /dev/null +++ b/test/Microsoft.WebTools.AspireService.Tests/Mocks/Mocks.cs @@ -0,0 +1,38 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using Moq; + +namespace Microsoft.WebTools.AspireServer.UnitTests; + +public class Mocks +{ + private readonly Dictionary _mockFactories = new(); + + public void Add(IMockFactory factory) + { + _mockFactories.Add(factory.GetType(), factory); + } + + public T GetOrCreate(MockBehavior? mockBehavior = null) where T : IMockFactory + { + if (_mockFactories.TryGetValue(typeof(T), out var factory)) + { + return (T)factory; + } + + var newMock = (IMockFactory?)Activator.CreateInstance(typeof(T), this, mockBehavior); + Debug.Assert(newMock != null); + Add(newMock); + return (T)newMock; + } + + public virtual void Verify() + { + foreach (var factory in _mockFactories) + { + factory.Value.Verify(); + } + } +} diff --git a/test/Microsoft.WebTools.AspireService.Tests/Properties/AssemblyInfo.cs b/test/Microsoft.WebTools.AspireService.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 000000000000..8b7c37cd90c4 --- /dev/null +++ b/test/Microsoft.WebTools.AspireService.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,6 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] diff --git a/test/Microsoft.WebTools.AspireService.Tests/Properties/launchSettings.json b/test/Microsoft.WebTools.AspireService.Tests/Properties/launchSettings.json new file mode 100644 index 000000000000..2d63805b76a3 --- /dev/null +++ b/test/Microsoft.WebTools.AspireService.Tests/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "Microsoft.WebTools.AspireServer.Test": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:61653;http://localhost:61654" + } + } +} \ No newline at end of file diff --git a/test/Microsoft.WebTools.AspireService.Tests/RunSessionRequestTests.cs b/test/Microsoft.WebTools.AspireService.Tests/RunSessionRequestTests.cs new file mode 100644 index 000000000000..4012d02bf10c --- /dev/null +++ b/test/Microsoft.WebTools.AspireService.Tests/RunSessionRequestTests.cs @@ -0,0 +1,46 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable disable + +using Microsoft.WebTools.AspireServer.Models; + +namespace Microsoft.WebTools.AspireServer.UnitTests; + +public class RunSessionRequestTests +{ + [Fact] + public void RunSessionRequest_ToProjectLaunchRequest() + { + var runSessionReq = new RunSessionRequest() + { + Arguments = new string[] { "--someArg" }, + Environment = new EnvVar[] + { + new EnvVar { Name = "var1", Value = "value1"}, + new EnvVar { Name = "var2", Value = "value2"}, + }, + LaunchConfigurations = new LaunchConfiguration[] + { + new() { + ProjectPath = @"c:\test\Projects\project1.csproj", + LaunchType = RunSessionRequest.ProjectLaunchConfigurationType, + LaunchMode= RunSessionRequest.DebugLaunchMode, + LaunchProfile = "specificProfileName", + DisableLaunchProfile = true + } + } + }; + + var projectReq = runSessionReq.ToProjectLaunchInformation(); + + Assert.Equal(runSessionReq.Arguments[0], projectReq.Arguments.First()); + Assert.Equal(runSessionReq.Environment.Length, projectReq.Environment.Count()); + Assert.Equal(runSessionReq.Environment[0].Name, projectReq.Environment.First().Key); + Assert.Equal(runSessionReq.Environment[0].Value, projectReq.Environment.First().Value); + Assert.Equal(runSessionReq.LaunchConfigurations[0].ProjectPath, projectReq.ProjectPath); + Assert.True(projectReq.Debug); + Assert.Equal(runSessionReq.LaunchConfigurations[0].LaunchProfile, projectReq.LaunchProfile); + Assert.Equal(runSessionReq.LaunchConfigurations[0].DisableLaunchProfile, projectReq.DisableLaunchProfile); + } +} diff --git a/test/dotnet-new.Tests/CommonTemplatesTests.cs b/test/dotnet-new.Tests/CommonTemplatesTests.cs index 6f598250a7ef..98c86dde4016 100644 --- a/test/dotnet-new.Tests/CommonTemplatesTests.cs +++ b/test/dotnet-new.Tests/CommonTemplatesTests.cs @@ -230,8 +230,8 @@ public async Task AotVariants(string name, string language) var templatesToTest = new[] { - new { Template = consoleTemplateShortname, Frameworks = new[] { null, "net6.0", "net7.0", "net8.0" } }, - new { Template = "classlib", Frameworks = new[] { null, "net6.0", "net7.0", "net8.0", "netstandard2.0", "netstandard2.1" } } + new { Template = consoleTemplateShortname, Frameworks = new[] { null, "net6.0", "net8.0" } }, + new { Template = "classlib", Frameworks = new[] { null, "net6.0", "net8.0", "netstandard2.0", "netstandard2.1" } } }; //features: top-level statements; nullables; implicit usings; filescoped namespaces @@ -240,9 +240,9 @@ public async Task AotVariants(string name, string language) //C# 12 is not supported yet - https://github.com/dotnet/sdk/issues/29195 string?[] supportedLanguageVersions = { null, "ISO-2", "2", "3", "4", "5", "6", "7", "7.1", "7.2", "7.3", "8.0", "9.0", "10.0", "11.0", "11", /*"12",*/ "latest", "latestMajor", "default", "preview" }; - string?[] nullableSupportedInFrameworkByDefault = { null, "net6.0", "net7.0", "net8.0", "netstandard2.1" }; - string?[] implicitUsingsSupportedInFramework = { null, "net6.0", "net7.0", "net8.0" }; - string?[] fileScopedNamespacesSupportedFrameworkByDefault = { null, "net6.0", "net7.0", "net8.0" }; + string?[] nullableSupportedInFrameworkByDefault = { null, "net6.0", "net8.0", "netstandard2.1" }; + string?[] implicitUsingsSupportedInFramework = { null, "net6.0", "net8.0" }; + string?[] fileScopedNamespacesSupportedFrameworkByDefault = { null, "net6.0", "net8.0" }; string?[] nullableSupportedLanguages = { "8.0", "9.0", "10.0", "11.0", "11", /*"12",*/ "latest", "latestMajor", "default", "preview" }; string?[] topLevelStatementSupportedLanguages = { null, "9.0", "10.0", "11", "11.0", /*"12",*/ "latest", "latestMajor", "default", "preview" }; diff --git a/test/dotnet-watch.Tests/FileWatcherTests.cs b/test/dotnet-watch.Tests/FileWatcherTests.cs index 03afa158fb79..eeadd08ceefd 100644 --- a/test/dotnet-watch.Tests/FileWatcherTests.cs +++ b/test/dotnet-watch.Tests/FileWatcherTests.cs @@ -1,53 +1,43 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Runtime.CompilerServices; using Microsoft.AspNetCore.Testing; using Microsoft.DotNet.Watcher.Internal; namespace Microsoft.DotNet.Watcher.Tools { - public class FileWatcherTests + public class FileWatcherTests(ITestOutputHelper output) { - public FileWatcherTests(ITestOutputHelper output) - { - _output = output; - _testAssetManager = new TestAssetsManager(output); - } - private readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(60); private readonly TimeSpan NegativeTimeout = TimeSpan.FromSeconds(5); - private readonly ITestOutputHelper _output; - private readonly TestAssetsManager _testAssetManager; + private readonly TestAssetsManager _testAssetManager = new TestAssetsManager(output); private async Task TestOperation( string dir, - (string, bool)[] expectedChanges, + (string path, ChangeKind kind)[] expectedChanges, bool usePolling, Action operation) { - // On Unix the native file watcher may surface events from - // the recent past. Delay to avoid those. - // On Unix the file write time is in 1s increments; - // if we don't wait, there's a chance that the polling - // watcher will not detect the change - await Task.Delay(1250); - using var watcher = FileWatcherFactory.CreateWatcher(dir, usePolling); if (watcher is DotnetFileWatcher dotnetWatcher) { - dotnetWatcher.Logger = m => _output.WriteLine(m); + dotnetWatcher.Logger = m => output.WriteLine(m); } var changedEv = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - var filesChanged = new HashSet<(string, bool)>(); + var filesChanged = new HashSet<(string path, ChangeKind kind)>(); - var testFileFullPath = Path.Combine(dir, "foo"); - - EventHandler<(string path, bool newFile)> handler = null; + EventHandler<(string path, ChangeKind kind)> handler = null; handler = (_, f) => { - filesChanged.Add(f); + if (filesChanged.Add(f)) + { + output.WriteLine($"Observed new {f.kind}: '{f.path}' ({filesChanged.Count} out of {expectedChanges.Length})"); + } + else + { + output.WriteLine($"Already seen {f.kind}: '{f.path}'"); + } if (filesChanged.Count == expectedChanges.Length) { @@ -88,17 +78,48 @@ await TestOperation( expectedChanges: !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && !usePolling ? new[] { - (testFileFullPath, false), - (testFileFullPath, true), + (testFileFullPath, ChangeKind.Update), + (testFileFullPath, ChangeKind.Add), } : new[] { - (testFileFullPath, true), + (testFileFullPath, ChangeKind.Add), }, usePolling, () => File.WriteAllText(testFileFullPath, string.Empty)); } + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task NewFileInNewDirectory(bool usePolling) + { + var dir = _testAssetManager.CreateTestDirectory(identifier: usePolling.ToString()).Path; + + var newDir = Path.Combine(dir, "Dir"); + var newFile = Path.Combine(newDir, "foo"); + + await TestOperation( + dir, + expectedChanges: RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && !usePolling + ? new[] + { + (newDir, ChangeKind.Add), + (newFile, ChangeKind.Update), + (newFile, ChangeKind.Add), + } + : new[] + { + (newDir, ChangeKind.Add), + }, + usePolling, + () => + { + Directory.CreateDirectory(newDir); + File.WriteAllText(newFile, string.Empty); + }); + } + [Theory] [InlineData(true)] [InlineData(false)] @@ -111,14 +132,13 @@ public async Task ChangeFile(bool usePolling) await TestOperation( dir, - expectedChanges: [(testFileFullPath, false)], + expectedChanges: [(testFileFullPath, ChangeKind.Update)], usePolling, () => File.WriteAllText(testFileFullPath, string.Empty)); } [Theory] - [InlineData(true)] - [InlineData(false)] + [CombinatorialData] public async Task MoveFile(bool usePolling) { var dir = _testAssetManager.CreateTestDirectory(identifier: usePolling.ToString()).Path; @@ -129,18 +149,19 @@ public async Task MoveFile(bool usePolling) await TestOperation( dir, - expectedChanges: RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && !usePolling - ? new[] - { - (srcFile, false), - (srcFile, true), - (dstFile, true), - } - : new[] - { - (srcFile, false), - (dstFile, true), - }, + expectedChanges: RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && !usePolling ? + [ + // On OSX events from before we started observing are reported as well. + (srcFile, ChangeKind.Update), + (srcFile, ChangeKind.Add), + (srcFile, ChangeKind.Delete), + (dstFile, ChangeKind.Add), + ] + : + [ + (srcFile, ChangeKind.Delete), + (dstFile, ChangeKind.Add), + ], usePolling, () => File.Move(srcFile, dstFile)); @@ -160,8 +181,8 @@ public async Task FileInSubdirectory() await TestOperation( dir, expectedChanges: [ - (subdir, false), - (testFileFullPath, false) + (subdir, ChangeKind.Update), + (testFileFullPath, ChangeKind.Update) ], usePolling: true, () => File.WriteAllText(testFileFullPath, string.Empty)); @@ -246,7 +267,7 @@ public async Task MultipleFiles(bool usePolling) await TestOperation( dir, - expectedChanges: [(testFileFullPath, false)], + expectedChanges: [(testFileFullPath, ChangeKind.Update)], usePolling: true, () => File.WriteAllText(testFileFullPath, string.Empty)); } @@ -274,9 +295,9 @@ private async Task AssertFileChangeRaisesEvent(string directory, IFileSystemWatc { var changedEv = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var expectedPath = Path.Combine(directory, Path.GetRandomFileName()); - EventHandler<(string, bool)> handler = (_, f) => + EventHandler<(string, ChangeKind)> handler = (_, f) => { - _output.WriteLine("File changed: " + f); + output.WriteLine("File changed: " + f); try { if (string.Equals(f.Item1, expectedPath, StringComparison.OrdinalIgnoreCase)) @@ -331,13 +352,43 @@ public async Task DeleteSubfolder(bool usePolling) await TestOperation( dir, - expectedChanges: [ - (subdir, false), - (f1, false), - (f2, false), - (f3, false), + expectedChanges: usePolling ? + [ + (subdir, ChangeKind.Delete), + (f1, ChangeKind.Delete), + (f2, ChangeKind.Delete), + (f3, ChangeKind.Delete), + ] + : RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? + [ + (subdir, ChangeKind.Add), + (subdir, ChangeKind.Delete), + (f1, ChangeKind.Update), + (f1, ChangeKind.Add), + (f1, ChangeKind.Delete), + (f2, ChangeKind.Update), + (f2, ChangeKind.Add), + (f2, ChangeKind.Delete), + (f3, ChangeKind.Update), + (f3, ChangeKind.Add), + (f3, ChangeKind.Delete), + ] + : RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? + [ + (subdir, ChangeKind.Update), + (subdir, ChangeKind.Delete), + (f1, ChangeKind.Delete), + (f2, ChangeKind.Delete), + (f3, ChangeKind.Delete), + ] + : + [ + (subdir, ChangeKind.Delete), + (f1, ChangeKind.Delete), + (f2, ChangeKind.Delete), + (f3, ChangeKind.Delete), ], - usePolling: true, + usePolling, () => Directory.Delete(subdir, recursive: true)); } } diff --git a/test/dotnet-watch.Tests/HotReload/ApplyDeltaTests.cs b/test/dotnet-watch.Tests/HotReload/ApplyDeltaTests.cs index b98819b8993c..e3e3fef94f89 100644 --- a/test/dotnet-watch.Tests/HotReload/ApplyDeltaTests.cs +++ b/test/dotnet-watch.Tests/HotReload/ApplyDeltaTests.cs @@ -121,6 +121,27 @@ public async Task BlazorWasm() //await App.AssertOutputLineStartsWith(MessageDescriptor.HotReloadSucceeded); } + [Fact] + public async Task BlazorWasm_MSBuildWarning() + { + var testAsset = TestAssets + .CopyTestAsset("WatchBlazorWasm") + .WithSource() + .WithProjectChanges(proj => + { + proj.Root.Descendants() + .Single(e => e.Name.LocalName == "ItemGroup") + .Add(XElement.Parse(""" + + """)); + }); + + App.Start(testAsset, [], testFlags: TestFlags.MockBrowser); + + await App.AssertOutputLineStartsWith("dotnet watch ⚠ msbuild: [Warning] Duplicate source file"); + await App.AssertWaitingForChanges(); + } + // Test is timing out on .NET Framework: https://github.com/dotnet/sdk/issues/41669 [CoreMSBuildOnlyFact] public async Task HandleMissingAssemblyFailure() @@ -157,5 +178,112 @@ public static void Print() await App.AssertOutputLineStartsWith("Updated types: Printer"); } + + [Theory] + [InlineData(true, Skip = "https://github.com/dotnet/sdk/issues/43320")] + [InlineData(false)] + public async Task RenameSourceFile(bool useMove) + { + Logger.WriteLine("RenameSourceFile started"); + + var testAsset = TestAssets.CopyTestAsset("WatchAppWithProjectDeps") + .WithSource(); + + var dependencyDir = Path.Combine(testAsset.Path, "Dependency"); + var oldFilePath = Path.Combine(dependencyDir, "Foo.cs"); + var newFilePath = Path.Combine(dependencyDir, "Renamed.cs"); + + var source = """ + using System; + using System.IO; + using System.Runtime.CompilerServices; + + public class Lib + { + public static void Print() => PrintFileName(); + + public static void PrintFileName([CallerFilePathAttribute] string filePath = null) + { + Console.WriteLine($"> {Path.GetFileName(filePath)}"); + } + } + """; + + File.WriteAllText(oldFilePath, source); + + App.Start(testAsset, [], "AppWithDeps"); + + await App.AssertWaitingForChanges(); + + // rename the file: + if (useMove) + { + File.Move(oldFilePath, newFilePath); + } + else + { + File.Delete(oldFilePath); + File.WriteAllText(newFilePath, source); + } + + Logger.WriteLine($"Renamed '{oldFilePath}' to '{newFilePath}'."); + + await App.AssertOutputLineStartsWith("> Renamed.cs"); + } + + [Theory] + [InlineData(true, Skip = "https://github.com/dotnet/sdk/issues/43320")] + [InlineData(false)] + public async Task RenameDirectory(bool useMove) + { + Logger.WriteLine("RenameSourceFile started"); + + var testAsset = TestAssets.CopyTestAsset("WatchAppWithProjectDeps") + .WithSource(); + + var dependencyDir = Path.Combine(testAsset.Path, "Dependency"); + var oldSubdir = Path.Combine(dependencyDir, "Subdir"); + var newSubdir = Path.Combine(dependencyDir, "NewSubdir"); + + var source = """ + using System; + using System.IO; + using System.Runtime.CompilerServices; + + public class Lib + { + public static void Print() => PrintDirectoryName(); + + public static void PrintDirectoryName([CallerFilePathAttribute] string filePath = null) + { + Console.WriteLine($"> {Path.GetFileName(Path.GetDirectoryName(filePath))}"); + } + } + """; + + File.Delete(Path.Combine(dependencyDir, "Foo.cs")); + Directory.CreateDirectory(oldSubdir); + File.WriteAllText(Path.Combine(oldSubdir, "Foo.cs"), source); + + App.Start(testAsset, [], "AppWithDeps"); + + await App.AssertWaitingForChanges(); + + // rename the directory: + if (useMove) + { + Directory.Move(oldSubdir, newSubdir); + } + else + { + Directory.Delete(oldSubdir, recursive: true); + Directory.CreateDirectory(newSubdir); + File.WriteAllText(Path.Combine(newSubdir, "Foo.cs"), source); + } + + Logger.WriteLine($"Renamed '{oldSubdir}' to '{newSubdir}'."); + + await App.AssertOutputLineStartsWith("> NewSubdir"); + } } } diff --git a/test/dotnet-watch.Tests/MSBuildEvaluationFilterTest.cs b/test/dotnet-watch.Tests/MSBuildEvaluationFilterTest.cs index 0a64ca3f0c33..6b0aa27c851a 100644 --- a/test/dotnet-watch.Tests/MSBuildEvaluationFilterTest.cs +++ b/test/dotnet-watch.Tests/MSBuildEvaluationFilterTest.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.DotNet.Watcher.Internal; using Microsoft.Extensions.Tools.Internal; namespace Microsoft.DotNet.Watcher.Tools @@ -27,7 +28,7 @@ public async Task ProcessAsync_EvaluatesFileSetIfProjFileChanges() evaluator.RequiresRevaluation = false; - await evaluator.EvaluateAsync(changedFile: new() { FilePath = "Test.csproj" }, CancellationToken.None); + await evaluator.EvaluateAsync(changedFile: new(new() { FilePath = "Test.csproj" }, ChangeKind.Update), CancellationToken.None); Assert.True(evaluator.RequiresRevaluation); } @@ -51,7 +52,7 @@ public async Task ProcessAsync_DoesNotEvaluateFileSetIfNonProjFileChanges() evaluator.RequiresRevaluation = false; - await evaluator.EvaluateAsync(changedFile: new() { FilePath = "Controller.cs" }, CancellationToken.None); + await evaluator.EvaluateAsync(changedFile: new(new() { FilePath = "Controller.cs" }, ChangeKind.Update), CancellationToken.None); Assert.False(evaluator.RequiresRevaluation); Assert.Equal(1, counter); @@ -77,7 +78,7 @@ public async Task ProcessAsync_EvaluateFileSetOnEveryChangeIfOptimizationIsSuppr evaluator.RequiresRevaluation = false; - await evaluator.EvaluateAsync(changedFile: new() { FilePath = "Controller.cs" }, CancellationToken.None); + await evaluator.EvaluateAsync(changedFile: new(new() { FilePath = "Controller.cs" }, ChangeKind.Update), CancellationToken.None); Assert.True(evaluator.RequiresRevaluation); Assert.Equal(2, counter); @@ -118,7 +119,7 @@ public async Task ProcessAsync_SetsEvaluationRequired_IfMSBuildFileChanges_ButIs evaluator.RequiresRevaluation = false; evaluator.Timestamps["Proj.csproj"] = new DateTime(1007); - await evaluator.EvaluateAsync(new() { FilePath = "Controller.cs" }, CancellationToken.None); + await evaluator.EvaluateAsync(new(new() { FilePath = "Controller.cs" }, ChangeKind.Update), CancellationToken.None); Assert.True(evaluator.RequiresRevaluation); } diff --git a/test/dotnet-watch.Tests/Watch/Utilities/WatchableApp.cs b/test/dotnet-watch.Tests/Watch/Utilities/WatchableApp.cs index e716d587d4cf..ea89c17f57bd 100644 --- a/test/dotnet-watch.Tests/Watch/Utilities/WatchableApp.cs +++ b/test/dotnet-watch.Tests/Watch/Utilities/WatchableApp.cs @@ -51,12 +51,16 @@ public async Task AssertOutputLineStartsWith(string expectedPrefix, Pred success: line => line.StartsWith(expectedPrefix, StringComparison.Ordinal), failure: failure ?? new Predicate(line => line.Contains(WatchErrorOutputEmoji, StringComparison.Ordinal))); - if (line == null && failure != null) + if (line == null) { - Assert.Fail($"Failed to find expected text: '{expectedPrefix}'"); + Assert.Fail(failure != null + ? "Encountered failure condition" + : $"Failed to find expected prefix: '{expectedPrefix}'"); + } + else + { + Assert.StartsWith(expectedPrefix, line, StringComparison.Ordinal); } - - Assert.StartsWith(expectedPrefix, line, StringComparison.Ordinal); return line.Substring(expectedPrefix.Length); } diff --git a/test/dotnet-workload-list.Tests/GivenDotnetWorkloadList.cs b/test/dotnet-workload-list.Tests/GivenDotnetWorkloadList.cs index d933851bfdb9..80216c85cde3 100644 --- a/test/dotnet-workload-list.Tests/GivenDotnetWorkloadList.cs +++ b/test/dotnet-workload-list.Tests/GivenDotnetWorkloadList.cs @@ -3,6 +3,7 @@ using System.CommandLine; using ManifestReaderTests; +using Microsoft.DotNet.Workloads.Workload; using Microsoft.DotNet.Workloads.Workload.List; using Microsoft.NET.Sdk.WorkloadManifestReader; using ListStrings = Microsoft.DotNet.Workloads.Workload.List.LocalizableStrings; @@ -37,6 +38,18 @@ public void GivenNoWorkloadsAreInstalledListIsEmpty() _reporter.Lines.Count.Should().Be(6); } + [WindowsOnlyFact] + public void GivenAvailableWorkloadsItCanComputeVisualStudioIds() + { + var workloadResolver = WorkloadResolver.CreateForTests(new MockManifestProvider(("SampleManifest", _manifestPath, "5.0.0", "6.0.100")), Directory.GetCurrentDirectory()); + +#pragma warning disable CA1416 // Validate platform compatibility + var availableWorkloads = VisualStudioWorkloads.GetAvailableVisualStudioWorkloads(workloadResolver); + availableWorkloads.Should().Contain("mock.workload.1", "mock-workload-1"); + availableWorkloads.Should().Contain("Microsoft.NET.Component.mock.workload.1", "mock-workload-1"); +#pragma warning restore CA1416 // Validate platform compatibility + } + [Fact] public void GivenNoWorkloadsAreInstalledMachineReadableListIsEmpty() { diff --git a/test/dotnet.Tests/TelemetryCommonPropertiesTests.cs b/test/dotnet.Tests/TelemetryCommonPropertiesTests.cs index 44b132f72971..575f0f6ebd0c 100644 --- a/test/dotnet.Tests/TelemetryCommonPropertiesTests.cs +++ b/test/dotnet.Tests/TelemetryCommonPropertiesTests.cs @@ -50,12 +50,16 @@ public void TelemetryCommonPropertiesShouldReturnNewGuidWhenCannotGetMacAddress( } [Fact] - public void TelemetryCommonPropertiesShouldReturnNewGuidWhenCannotDevDeviceId() + public void TelemetryCommonPropertiesShouldEnsureDevDeviceIDIsCached() { var unitUnderTest = new TelemetryCommonProperties(userLevelCacheWriter: new NothingCache()); var assignedMachineId = unitUnderTest.GetTelemetryCommonProperties()["devdeviceid"]; Guid.TryParse(assignedMachineId, out var _).Should().BeTrue("it should be a guid"); + var secondAssignedMachineId = unitUnderTest.GetTelemetryCommonProperties()["devdeviceid"]; + + Guid.TryParse(secondAssignedMachineId, out var _).Should().BeTrue("it should be a guid"); + secondAssignedMachineId.Should().Be(assignedMachineId, "it should match the previously assigned guid"); } [Fact]