diff --git a/.vsts-ci.yml b/.vsts-ci.yml index 3df0da1ce832..775bc568d823 100644 --- a/.vsts-ci.yml +++ b/.vsts-ci.yml @@ -3,7 +3,7 @@ trigger: branches: include: - main - - release/8.0.1xx + - release/8.0.2xx - internal/release/* - exp/* diff --git a/Directory.Build.props b/Directory.Build.props index fc5cbc439c2f..ae3882f1df94 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -16,7 +16,7 @@ - net7.0 + net8.0 $(SdkTargetFramework) diff --git a/Directory.Packages.props b/Directory.Packages.props index 2dcf8979312d..5d37eb795d6b 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -41,6 +41,7 @@ + @@ -54,8 +55,8 @@ - + @@ -103,7 +104,7 @@ - + diff --git a/NuGet.config b/NuGet.config index 5bd5b884357a..4632f1861eb3 100644 --- a/NuGet.config +++ b/NuGet.config @@ -4,30 +4,28 @@ - - - - + - - - - + + + + - + + - + @@ -39,6 +37,7 @@ + @@ -52,13 +51,18 @@ - + + + + + + diff --git a/eng/SourceBuildPrebuiltBaseline.xml b/eng/SourceBuildPrebuiltBaseline.xml index 123bd07493ff..3dcc5ba862f5 100644 --- a/eng/SourceBuildPrebuiltBaseline.xml +++ b/eng/SourceBuildPrebuiltBaseline.xml @@ -32,5 +32,9 @@ + + + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 6669f98b9fe9..b1d4cb9ee3c4 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,204 +1,200 @@ - + https://github.com/dotnet/templating - ae1a4c208a624e5cb5e0b7c5df1bd066afc48b81 - - - https://github.com/dotnet/templating - ae1a4c208a624e5cb5e0b7c5df1bd066afc48b81 + 563930b1d9ad60c8d7dbd1975390f5870601defa + - + https://github.com/dotnet/templating - ae1a4c208a624e5cb5e0b7c5df1bd066afc48b81 - + 563930b1d9ad60c8d7dbd1975390f5870601defa - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - bf5e279d9239bfef5bb1b8d6212f1b971c434606 + 1381d5ebd2ab1f292848d5b19b80cf71ac332508 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - bf5e279d9239bfef5bb1b8d6212f1b971c434606 + 1381d5ebd2ab1f292848d5b19b80cf71ac332508 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - bf5e279d9239bfef5bb1b8d6212f1b971c434606 + 1381d5ebd2ab1f292848d5b19b80cf71ac332508 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - bf5e279d9239bfef5bb1b8d6212f1b971c434606 + 1381d5ebd2ab1f292848d5b19b80cf71ac332508 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - bf5e279d9239bfef5bb1b8d6212f1b971c434606 + 1381d5ebd2ab1f292848d5b19b80cf71ac332508 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - bf5e279d9239bfef5bb1b8d6212f1b971c434606 + 1381d5ebd2ab1f292848d5b19b80cf71ac332508 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - bf5e279d9239bfef5bb1b8d6212f1b971c434606 + 1381d5ebd2ab1f292848d5b19b80cf71ac332508 https://dev.azure.com/dnceng/internal/_git/dotnet-runtime 5535e31a712343a63f5d7d796cd874e563e5ac14 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - bf5e279d9239bfef5bb1b8d6212f1b971c434606 + 1381d5ebd2ab1f292848d5b19b80cf71ac332508 - + https://github.com/dotnet/emsdk - 201f4dae9d1a1e105d8ba86d7ece61eed1f665e0 + 2fc2ffd960930318f33fcaa690cbdbc55d72f52d - + https://github.com/dotnet/msbuild - b5265ef370a651f8c3458110b804e5cbf869eeb5 + 90725d08d9825ad5897029b47f600345c29125b7 - + https://github.com/dotnet/msbuild - b5265ef370a651f8c3458110b804e5cbf869eeb5 + 90725d08d9825ad5897029b47f600345c29125b7 - + https://github.com/dotnet/msbuild - b5265ef370a651f8c3458110b804e5cbf869eeb5 + 90725d08d9825ad5897029b47f600345c29125b7 - + https://github.com/dotnet/fsharp - 424e4b7cffb7656efd63f7a905a2498e39011104 + a7979111f86ab7332897ea617635bf3435c39bc3 - + https://github.com/dotnet/fsharp - 424e4b7cffb7656efd63f7a905a2498e39011104 + a7979111f86ab7332897ea617635bf3435c39bc3 - - https://github.com/dotnet/format - 28925c0e519d66c80328aacf973b74e40bb1d5bd + + https://dev.azure.com/dnceng/internal/_git/dotnet-format + 2651752953c0d41c8c7b8d661cf2237151af33d0 - + https://github.com/dotnet/roslyn - 82a29fc43b3e0a9c53d6cc2be950e43765682170 + 4fc721bbc2c0eac5931f588e1d14ab2a1f936646 - + https://github.com/dotnet/roslyn - 82a29fc43b3e0a9c53d6cc2be950e43765682170 + 4fc721bbc2c0eac5931f588e1d14ab2a1f936646 - + https://github.com/dotnet/roslyn - 82a29fc43b3e0a9c53d6cc2be950e43765682170 + 4fc721bbc2c0eac5931f588e1d14ab2a1f936646 - + https://github.com/dotnet/roslyn - 82a29fc43b3e0a9c53d6cc2be950e43765682170 + 4fc721bbc2c0eac5931f588e1d14ab2a1f936646 - + https://github.com/dotnet/roslyn - 82a29fc43b3e0a9c53d6cc2be950e43765682170 + 4fc721bbc2c0eac5931f588e1d14ab2a1f936646 - + https://github.com/dotnet/roslyn - 82a29fc43b3e0a9c53d6cc2be950e43765682170 + 4fc721bbc2c0eac5931f588e1d14ab2a1f936646 - + https://github.com/dotnet/roslyn - 82a29fc43b3e0a9c53d6cc2be950e43765682170 + 4fc721bbc2c0eac5931f588e1d14ab2a1f936646 - + https://dev.azure.com/dnceng/internal/_git/dotnet-aspnetcore - 8e941eb42f819adb116b881195158b3887a70a1c + da7e9894ce22ef8cc02e5acc56e95a6f8cf8f644 - + https://dev.azure.com/dnceng/internal/_git/dotnet-aspnetcore - 8e941eb42f819adb116b881195158b3887a70a1c + da7e9894ce22ef8cc02e5acc56e95a6f8cf8f644 - - https://github.com/nuget/nuget.client - 0dd5a1ea536201af94725353e4bc711d7560b246 + + https://dev.azure.com/devdiv/DevDiv/_git/NuGet-NuGet.Client-Trusted + 623fde83a3cd73cb479ec7fa03866c6116894dbf - - https://github.com/nuget/nuget.client - 0dd5a1ea536201af94725353e4bc711d7560b246 + + https://dev.azure.com/devdiv/DevDiv/_git/NuGet-NuGet.Client-Trusted + 623fde83a3cd73cb479ec7fa03866c6116894dbf - - https://github.com/nuget/nuget.client - 0dd5a1ea536201af94725353e4bc711d7560b246 + + https://dev.azure.com/devdiv/DevDiv/_git/NuGet-NuGet.Client-Trusted + 623fde83a3cd73cb479ec7fa03866c6116894dbf - - https://github.com/nuget/nuget.client - 0dd5a1ea536201af94725353e4bc711d7560b246 + + https://dev.azure.com/devdiv/DevDiv/_git/NuGet-NuGet.Client-Trusted + 623fde83a3cd73cb479ec7fa03866c6116894dbf - - https://github.com/nuget/nuget.client - 0dd5a1ea536201af94725353e4bc711d7560b246 + + https://dev.azure.com/devdiv/DevDiv/_git/NuGet-NuGet.Client-Trusted + 623fde83a3cd73cb479ec7fa03866c6116894dbf - - https://github.com/nuget/nuget.client - 0dd5a1ea536201af94725353e4bc711d7560b246 + + https://dev.azure.com/devdiv/DevDiv/_git/NuGet-NuGet.Client-Trusted + 623fde83a3cd73cb479ec7fa03866c6116894dbf - - https://github.com/nuget/nuget.client - 0dd5a1ea536201af94725353e4bc711d7560b246 + + https://dev.azure.com/devdiv/DevDiv/_git/NuGet-NuGet.Client-Trusted + 623fde83a3cd73cb479ec7fa03866c6116894dbf - - https://github.com/nuget/nuget.client - 0dd5a1ea536201af94725353e4bc711d7560b246 + + https://dev.azure.com/devdiv/DevDiv/_git/NuGet-NuGet.Client-Trusted + 623fde83a3cd73cb479ec7fa03866c6116894dbf - - https://github.com/nuget/nuget.client - 0dd5a1ea536201af94725353e4bc711d7560b246 + + https://dev.azure.com/devdiv/DevDiv/_git/NuGet-NuGet.Client-Trusted + 623fde83a3cd73cb479ec7fa03866c6116894dbf - - https://github.com/nuget/nuget.client - 0dd5a1ea536201af94725353e4bc711d7560b246 + + https://dev.azure.com/devdiv/DevDiv/_git/NuGet-NuGet.Client-Trusted + 623fde83a3cd73cb479ec7fa03866c6116894dbf - - https://github.com/nuget/nuget.client - 0dd5a1ea536201af94725353e4bc711d7560b246 + + https://dev.azure.com/devdiv/DevDiv/_git/NuGet-NuGet.Client-Trusted + 623fde83a3cd73cb479ec7fa03866c6116894dbf - - https://github.com/nuget/nuget.client - 0dd5a1ea536201af94725353e4bc711d7560b246 + + https://dev.azure.com/devdiv/DevDiv/_git/NuGet-NuGet.Client-Trusted + 623fde83a3cd73cb479ec7fa03866c6116894dbf - - https://github.com/nuget/nuget.client - 0dd5a1ea536201af94725353e4bc711d7560b246 + + https://dev.azure.com/devdiv/DevDiv/_git/NuGet-NuGet.Client-Trusted + 623fde83a3cd73cb479ec7fa03866c6116894dbf - - https://github.com/nuget/nuget.client - 0dd5a1ea536201af94725353e4bc711d7560b246 + + https://dev.azure.com/devdiv/DevDiv/_git/NuGet-NuGet.Client-Trusted + 623fde83a3cd73cb479ec7fa03866c6116894dbf - - https://github.com/nuget/nuget.client - 0dd5a1ea536201af94725353e4bc711d7560b246 + + https://dev.azure.com/devdiv/DevDiv/_git/NuGet-NuGet.Client-Trusted + 623fde83a3cd73cb479ec7fa03866c6116894dbf - - https://github.com/nuget/nuget.client - 0dd5a1ea536201af94725353e4bc711d7560b246 + + https://dev.azure.com/devdiv/DevDiv/_git/NuGet-NuGet.Client-Trusted + 623fde83a3cd73cb479ec7fa03866c6116894dbf - + https://github.com/microsoft/vstest - aa59400b11e1aeee2e8af48928dbd48748a8bef9 + 053d7114a72aac12d1382ecc2a23b2dfdd5b084b - + https://github.com/microsoft/vstest - aa59400b11e1aeee2e8af48928dbd48748a8bef9 + 053d7114a72aac12d1382ecc2a23b2dfdd5b084b - + https://github.com/microsoft/vstest - aa59400b11e1aeee2e8af48928dbd48748a8bef9 + 053d7114a72aac12d1382ecc2a23b2dfdd5b084b - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - bf5e279d9239bfef5bb1b8d6212f1b971c434606 + 1381d5ebd2ab1f292848d5b19b80cf71ac332508 https://dev.azure.com/dnceng/internal/_git/dotnet-runtime @@ -216,116 +212,116 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-runtime 5535e31a712343a63f5d7d796cd874e563e5ac14 - + https://dev.azure.com/dnceng/internal/_git/dotnet-windowsdesktop - a0e7b5d8673f28c41bcac6e2001b39ba2c8fab54 + 593444ad8328a5a933c006c6564469666f45ad2e - + https://dev.azure.com/dnceng/internal/_git/dotnet-windowsdesktop - a0e7b5d8673f28c41bcac6e2001b39ba2c8fab54 + 593444ad8328a5a933c006c6564469666f45ad2e - + https://dev.azure.com/dnceng/internal/_git/dotnet-windowsdesktop - a0e7b5d8673f28c41bcac6e2001b39ba2c8fab54 + 593444ad8328a5a933c006c6564469666f45ad2e - + https://dev.azure.com/dnceng/internal/_git/dotnet-windowsdesktop - a0e7b5d8673f28c41bcac6e2001b39ba2c8fab54 + 593444ad8328a5a933c006c6564469666f45ad2e - + https://dev.azure.com/dnceng/internal/_git/dotnet-wpf - ac40bed7a33baf164d3984ca90c2aedba996a7b2 + 472140dd926227876848e48f41cfc9acb9275492 - + https://dev.azure.com/dnceng/internal/_git/dotnet-aspnetcore - 8e941eb42f819adb116b881195158b3887a70a1c + da7e9894ce22ef8cc02e5acc56e95a6f8cf8f644 - + https://dev.azure.com/dnceng/internal/_git/dotnet-aspnetcore - 8e941eb42f819adb116b881195158b3887a70a1c + da7e9894ce22ef8cc02e5acc56e95a6f8cf8f644 - + https://dev.azure.com/dnceng/internal/_git/dotnet-aspnetcore - 8e941eb42f819adb116b881195158b3887a70a1c + da7e9894ce22ef8cc02e5acc56e95a6f8cf8f644 - + https://dev.azure.com/dnceng/internal/_git/dotnet-aspnetcore - 8e941eb42f819adb116b881195158b3887a70a1c + da7e9894ce22ef8cc02e5acc56e95a6f8cf8f644 - + https://dev.azure.com/dnceng/internal/_git/dotnet-aspnetcore - 8e941eb42f819adb116b881195158b3887a70a1c + da7e9894ce22ef8cc02e5acc56e95a6f8cf8f644 - + https://dev.azure.com/dnceng/internal/_git/dotnet-aspnetcore - 8e941eb42f819adb116b881195158b3887a70a1c + da7e9894ce22ef8cc02e5acc56e95a6f8cf8f644 - + https://dev.azure.com/dnceng/internal/_git/dotnet-aspnetcore - 8e941eb42f819adb116b881195158b3887a70a1c + da7e9894ce22ef8cc02e5acc56e95a6f8cf8f644 - + https://dev.azure.com/dnceng/internal/_git/dotnet-aspnetcore - 8e941eb42f819adb116b881195158b3887a70a1c + da7e9894ce22ef8cc02e5acc56e95a6f8cf8f644 - + https://dev.azure.com/dnceng/internal/_git/dotnet-aspnetcore - 8e941eb42f819adb116b881195158b3887a70a1c + da7e9894ce22ef8cc02e5acc56e95a6f8cf8f644 - + https://dev.azure.com/dnceng/internal/_git/dotnet-aspnetcore - 8e941eb42f819adb116b881195158b3887a70a1c + da7e9894ce22ef8cc02e5acc56e95a6f8cf8f644 - + https://dev.azure.com/dnceng/internal/_git/dotnet-aspnetcore - 8e941eb42f819adb116b881195158b3887a70a1c + da7e9894ce22ef8cc02e5acc56e95a6f8cf8f644 - + https://github.com/dotnet/razor - d135dd8d2ec1c2fbdee220e8656b308694e17a4b + df383360c34ada8889fdf18dc36d245f2938db66 - + https://github.com/dotnet/razor - d135dd8d2ec1c2fbdee220e8656b308694e17a4b + df383360c34ada8889fdf18dc36d245f2938db66 - + https://github.com/dotnet/razor - d135dd8d2ec1c2fbdee220e8656b308694e17a4b + df383360c34ada8889fdf18dc36d245f2938db66 - + https://dev.azure.com/dnceng/internal/_git/dotnet-aspnetcore - 8e941eb42f819adb116b881195158b3887a70a1c + da7e9894ce22ef8cc02e5acc56e95a6f8cf8f644 - + https://dev.azure.com/dnceng/internal/_git/dotnet-aspnetcore - 8e941eb42f819adb116b881195158b3887a70a1c + da7e9894ce22ef8cc02e5acc56e95a6f8cf8f644 - + https://dev.azure.com/dnceng/internal/_git/dotnet-aspnetcore - 8e941eb42f819adb116b881195158b3887a70a1c + da7e9894ce22ef8cc02e5acc56e95a6f8cf8f644 - + https://dev.azure.com/dnceng/internal/_git/dotnet-aspnetcore - 8e941eb42f819adb116b881195158b3887a70a1c + da7e9894ce22ef8cc02e5acc56e95a6f8cf8f644 https://github.com/dotnet/xdt 9a1c3e1b7f0c8763d4c96e593961a61a72679a7b - + https://github.com/dotnet/roslyn-analyzers - b4d9a1334d5189172977ba8fddd00bda70161e4a + abef8ced132657943b7150f01a308e2199a17d5d - + https://github.com/dotnet/roslyn-analyzers - b4d9a1334d5189172977ba8fddd00bda70161e4a + abef8ced132657943b7150f01a308e2199a17d5d - + https://github.com/dotnet/roslyn-analyzers - b4d9a1334d5189172977ba8fddd00bda70161e4a + abef8ced132657943b7150f01a308e2199a17d5d @@ -337,9 +333,9 @@ 02fe27cd6a9b001c8feb7938e6ef4b3799745759 - + https://github.com/dotnet/source-build-externals - 7134e53b6b1210a1ce8838b12b8f6071e0a3433b + 83274d94c7e2ff21081b0d75ecbec2da2241f831 @@ -413,9 +409,9 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-runtime 5535e31a712343a63f5d7d796cd874e563e5ac14 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - bf5e279d9239bfef5bb1b8d6212f1b971c434606 + 1381d5ebd2ab1f292848d5b19b80cf71ac332508 https://dev.azure.com/dnceng/internal/_git/dotnet-runtime @@ -425,9 +421,9 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-runtime 5535e31a712343a63f5d7d796cd874e563e5ac14 - + https://dev.azure.com/dnceng/internal/_git/dotnet-aspnetcore - 8e941eb42f819adb116b881195158b3887a70a1c + da7e9894ce22ef8cc02e5acc56e95a6f8cf8f644 https://dev.azure.com/dnceng/internal/_git/dotnet-runtime @@ -457,9 +453,9 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-runtime 5535e31a712343a63f5d7d796cd874e563e5ac14 - + https://dev.azure.com/dnceng/internal/_git/dotnet-winforms - e4ede9b8979b9d2b1b1d4383f30a791414f0625b + 0b4028eb507aeb222f5bd1fc421876cc5e5e3fb8 https://dev.azure.com/dnceng/internal/_git/dotnet-runtime diff --git a/eng/Versions.props b/eng/Versions.props index 83158ff93351..eb210470a04d 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -11,13 +11,13 @@ - 8.0.102 - 8.0.100 + 8.0.200 + 8.0.200 true release - rtm + preview rtm servicing @@ -48,21 +48,22 @@ - 8.0.1 - 8.0.1-servicing.23580.1 - 8.0.1 + 8.0.2 + 8.0.2-servicing.24067.11 + 8.0.2 $(MicrosoftNETCoreAppRuntimewinx64PackageVersion) 8.0.0 - 8.0.1 - 8.0.1-servicing.23580.1 + 8.0.2 + 8.0.2-servicing.24067.11 8.0.0 + $(MicrosoftExtensionsDependencyModelPackageVersion) 8.0.0 8.0.0 8.0.0 - 8.0.1 + 8.0.2 8.0.0 8.0.0 - 8.0.1 + 8.0.2 8.0.0 8.0.0 8.0.0 @@ -70,36 +71,36 @@ 8.0.0 8.0.0 8.0.0 - 8.0.0 + 8.0.1 8.0.0 8.0.0 8.0.0 8.0.0 - 8.0.1 + 8.0.2 8.0.0 - 6.8.0-rc.122 - 6.8.0-rc.122 + 6.9.1-rc.3 + 6.9.1-rc.3 6.0.0-rc.278 - 6.8.0-rc.122 - 6.8.0-rc.122 - 6.8.0-rc.122 - 6.8.0-rc.122 - 6.8.0-rc.122 - 6.8.0-rc.122 - 6.8.0-rc.122 - 6.8.0-rc.122 - 6.8.0-rc.122 + 6.9.1-rc.3 + 6.9.1-rc.3 + 6.9.1-rc.3 + 6.9.1-rc.3 + 6.9.1-rc.3 + 6.9.1-rc.3 + 6.9.1-rc.3 + 6.9.1-rc.3 + 6.9.1-rc.3 $(NuGetPackagingPackageVersion) $(NuGetProjectModelPackageVersion) - 17.8.0-release-23615-02 - 17.8.0-release-23615-02 - 17.8.0-release-23615-02 + 17.9.0-release-23627-01 + 17.9.0-release-23627-01 + 17.9.0-release-23627-01 @@ -109,16 +110,16 @@ - 8.0.456602 + 8.0.453106 - 8.0.0-preview.23525.2 - 3.11.0-beta1.23525.2 + 8.0.0-preview.23614.1 + 3.11.0-beta1.23614.1 - 17.8.5 + 17.9.4 $(MicrosoftBuildPackageVersion) - 8.0.102 + 8.0.200-preview.24060.12 $(MicrosoftTemplateEngineAbstractionsPackageVersion) $(MicrosoftTemplateEngineAbstractionsPackageVersion) $(MicrosoftTemplateEngineAbstractionsPackageVersion) $(MicrosoftTemplateEngineAbstractionsPackageVersion) - 8.0.102-servicing.24064.3 + 8.0.200-preview.24060.12 $(MicrosoftTemplateEngineMocksPackageVersion) $(MicrosoftTemplateEngineAbstractionsPackageVersion) $(MicrosoftTemplateEngineMocksPackageVersion) - 12.8.0-beta.23563.2 + 12.8.200-beta.24062.3 - 4.8.0-7.23614.11 - 4.8.0-7.23614.11 - 4.8.0-7.23614.11 - 4.8.0-7.23614.11 - 4.8.0-7.23614.11 - 4.8.0-7.23614.11 - 4.8.0-7.23614.11 + 4.9.0-3.24067.18 + 4.9.0-3.24067.18 + 4.9.0-3.24067.18 + 4.9.0-3.24067.18 + 4.9.0-3.24067.18 + 4.9.0-3.24067.18 + 4.9.0-3.24067.18 $(MicrosoftNetCompilersToolsetPackageVersion) - 8.0.1 - 8.0.1-servicing.23580.8 - 8.0.1-servicing.23580.8 - 8.0.1-servicing.23580.8 - 8.0.1-servicing.23580.8 - 8.0.1-servicing.23580.8 - 8.0.1 + 8.0.2 + 8.0.2-servicing.24068.4 + 8.0.2-servicing.24068.4 + 8.0.2-servicing.24068.4 + 8.0.2-servicing.24068.4 + 8.0.2-servicing.24068.4 + 8.0.2 - 7.0.0-preview.24052.1 - 7.0.0-preview.24052.1 - 7.0.0-preview.24052.1 + 7.0.0-preview.24053.4 + 7.0.0-preview.24053.4 + 7.0.0-preview.24053.4 - 8.0.1-servicing.23580.5 + 8.0.2-servicing.24068.6 @@ -222,7 +224,7 @@ - 8.0.1 + 8.0.2 $(MicrosoftNETWorkloadEmscriptenCurrentManifest80100PackageVersion) 8.0.100$([System.Text.RegularExpressions.Regex]::Match($(EmscriptenWorkloadManifestVersion), `-rtm|-[A-z]*\.*\d*`)) diff --git a/src/ApiCompat/Microsoft.DotNet.ApiCompat.Task/Microsoft.DotNet.ApiCompat.Task.csproj b/src/ApiCompat/Microsoft.DotNet.ApiCompat.Task/Microsoft.DotNet.ApiCompat.Task.csproj index a7a964dbc99f..6e6311533f68 100644 --- a/src/ApiCompat/Microsoft.DotNet.ApiCompat.Task/Microsoft.DotNet.ApiCompat.Task.csproj +++ b/src/ApiCompat/Microsoft.DotNet.ApiCompat.Task/Microsoft.DotNet.ApiCompat.Task.csproj @@ -43,6 +43,7 @@ + diff --git a/src/ApiCompat/Microsoft.DotNet.ApiCompatibility/Microsoft.DotNet.ApiCompatibility.csproj b/src/ApiCompat/Microsoft.DotNet.ApiCompatibility/Microsoft.DotNet.ApiCompatibility.csproj index 60dfe5a399f3..6f12ef4f6bd7 100644 --- a/src/ApiCompat/Microsoft.DotNet.ApiCompatibility/Microsoft.DotNet.ApiCompatibility.csproj +++ b/src/ApiCompat/Microsoft.DotNet.ApiCompatibility/Microsoft.DotNet.ApiCompatibility.csproj @@ -15,6 +15,8 @@ + + diff --git a/src/BuiltInTools/dotnet-watch/dotnet-watch.csproj b/src/BuiltInTools/dotnet-watch/dotnet-watch.csproj index 1a03973fcd4f..747b43b278a6 100644 --- a/src/BuiltInTools/dotnet-watch/dotnet-watch.csproj +++ b/src/BuiltInTools/dotnet-watch/dotnet-watch.csproj @@ -26,9 +26,12 @@ + + + - $(PkgMicrosoft_Build_Runtime)\contentFiles\any\net7.0\MSBuild.dll + $(PkgMicrosoft_Build_Runtime)\contentFiles\any\net8.0\MSBuild.dll $(PkgMicrosoft_Build_Runtime)\contentFiles\any\$(NetCurrent)\MSBuild.dll diff --git a/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.cs.xlf b/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.cs.xlf index 0a19b54165ac..059c3cc90ea8 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.cs.xlf +++ b/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.cs.xlf @@ -12,6 +12,11 @@ NesprávnÄ› naformátovaný text příkazu {0} + + Could not execute because the specified command or file was not found. + Nebylo možné provést, protože zadaný příkaz nebo soubor nebyl nalezen. + + Unable to locate dotnet multiplexer NepodaÅ™ilo se najít multiplexor dotnetu. @@ -28,16 +33,14 @@ - Could not execute because the specified command or file was not found. -Possible reasons for this include: + Possible reasons for this include: * You misspelled a built-in dotnet command. * You intended to execute a .NET program, but {0} does not exist. * You intended to run a global tool, but a dotnet-prefixed executable with this name could not be found on the PATH. - SpuÅ¡tÄ›ní nebylo úspěšné, protože zadaný příkaz nebo soubor se nenaÅ¡ly. -Mezi možné příÄiny patří toto: - * V integrovaném příkazu dotnet je pÅ™eklep. + Možné důvody: + * ChybnÄ› jste napsali integrovaný příkaz dotnet. * ChtÄ›li jste spustit program .NET, ale {0} neexistuje. - * ChtÄ›li jste spustit globální nástroj, ale v promÄ›nné PATH se nepovedlo najít spustitelný soubor s pÅ™edponou dotnet s tímto názvem. + * ChtÄ›li jste spustit nástroj global, ale v cestÄ› PATH nebyl nalezen spustitelný soubor s pÅ™edponou dotnet s tímto názvem. diff --git a/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.de.xlf b/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.de.xlf index 931f8681cf5a..b65426871939 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.de.xlf +++ b/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.de.xlf @@ -12,6 +12,11 @@ Fehlerhafter Befehlstext "{0}". + + Could not execute because the specified command or file was not found. + Die Ausführung war nicht möglich, da der angegebene Befehl oder die angegebene Datei nicht gefunden wurde. + + Unable to locate dotnet multiplexer Dotnetmultiplexer nicht gefunden @@ -28,16 +33,14 @@ - Could not execute because the specified command or file was not found. -Possible reasons for this include: + Possible reasons for this include: * You misspelled a built-in dotnet command. * You intended to execute a .NET program, but {0} does not exist. * You intended to run a global tool, but a dotnet-prefixed executable with this name could not be found on the PATH. - Die Ausführung war nicht möglich, weil der angegebene Befehl oder die Datei nicht gefunden wurde. -Mögliche Ursachen: - * Sie haben sich bei einem integrierten dotnet-Befehl verschrieben. - * Sie wollten ein .NET-Programm ausführen, aber "{0}" ist nicht vorhanden. - * Sie wollten ein globales Tool ausführen, aber in PATH wurde keine ausführbare Datei dieses Namens mit dotnet-Präfix gefunden. + Mögliche Gründe hierfür sind: + * Sie haben einen integrierten dotnet-Befehl falsch geschrieben. + * Sie wollten ein .NET-Programm ausführen, aber {0} ist nicht vorhanden. + * Sie wollten ein globales Tool ausführen, aber eine ausführbare Datei mit dotnet-Präfix und diesem Namen wurde in PATH nicht gefunden. diff --git a/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.es.xlf b/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.es.xlf index 6c95a5a48054..ab310ba0919e 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.es.xlf +++ b/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.es.xlf @@ -12,6 +12,11 @@ Texto de comando con formato incorrecto "{0}" + + Could not execute because the specified command or file was not found. + No se pudo ejecutar porque no se encontró el comando o archivo especificado. + + Unable to locate dotnet multiplexer No se puede ubicar el multiplexor dotnet @@ -28,16 +33,14 @@ - Could not execute because the specified command or file was not found. -Possible reasons for this include: + Possible reasons for this include: * You misspelled a built-in dotnet command. * You intended to execute a .NET program, but {0} does not exist. * You intended to run a global tool, but a dotnet-prefixed executable with this name could not be found on the PATH. - No se pudo ejecutar porque no se encontró el comando o el archivo especificado. -Algunas de las posibles causas son : - * Escribió mal un comando dotnet integrado. - * Pretendía ejecutar un programa .NET, pero {0} no existe. - * Pretendía ejecutar una herramienta global, pero no se encontró ningún ejecutable con prefijo dotnet con este nombre en PATH. + Entre las posibles razones para esto se incluyen: + * Escribió de manera incorrecta un comando dotnet integrado. + * Tenía previsto ejecutar un programa .NET, pero {0} no existe. + * Tuvo la intención de ejecutar una herramienta global, pero no se encontró un ejecutable con el prefijo dotnet con este nombre en la ruta. diff --git a/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.fr.xlf b/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.fr.xlf index ac52d261b855..83aafec69438 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.fr.xlf +++ b/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.fr.xlf @@ -12,6 +12,11 @@ Texte de commande incorrect '{0}' + + Could not execute because the specified command or file was not found. + Exécution impossible, car la commande ou le fichier spécifié est introuvable. + + Unable to locate dotnet multiplexer Le multiplexeur dotnet est introuvable @@ -28,16 +33,14 @@ - Could not execute because the specified command or file was not found. -Possible reasons for this include: + Possible reasons for this include: * You misspelled a built-in dotnet command. * You intended to execute a .NET program, but {0} does not exist. * You intended to run a global tool, but a dotnet-prefixed executable with this name could not be found on the PATH. - Impossible d'effectuer l'exécution, car la commande ou le fichier spécifié est introuvable. -Raisons possibles : + Les raisons possibles sont les suivantes : * Vous avez mal orthographié une commande dotnet intégrée. - * Vous avez voulu exécuter un programme .NET, mais {0} n'existe pas. - * Vous avez voulu exécuter un outil global, mais l'exécutable de ce nom avec le préfixe dotnet est introuvable dans le CHEMIN. + * Vous avez l’intention d’exécuter un programme .NET, mais {0} n’existe pas. + * Vous avez l’intention d’exécuter un outil global, mais un exécutable avec un préfixe dotnet portant ce nom est introuvable sur le chemin d’accès. diff --git a/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.it.xlf b/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.it.xlf index a6c286a1e24c..d14e1840c5e8 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.it.xlf +++ b/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.it.xlf @@ -12,6 +12,11 @@ Il testo del comando '{0}' non è corretto + + Could not execute because the specified command or file was not found. + Non è stato possibile eseguire perché il comando o il file specificato non è stato trovato. + + Unable to locate dotnet multiplexer Il multiplexer dotnet non è stato trovato @@ -28,16 +33,14 @@ - Could not execute because the specified command or file was not found. -Possible reasons for this include: + Possible reasons for this include: * You misspelled a built-in dotnet command. * You intended to execute a .NET program, but {0} does not exist. * You intended to run a global tool, but a dotnet-prefixed executable with this name could not be found on the PATH. - Non è stato possibile completare l'esecuzione perché il comando o il file specificato non è stato trovato. -Motivi possibili: - * Il nome di un comando dotnet predefinito non è stato digitato correttamente. + I possibili motivi includono: + * È stato digitato in modo errato un comando dotnet predefinito. * Si intendeva eseguire un programma .NET, ma {0} non esiste. - * Si intendeva eseguire uno strumento globale, ma in PATH non è stato trovato alcun eseguibile con questo nome e prefisso dotnet. + * Si intendeva eseguire uno strumento globale, ma non è stato possibile trovare un eseguibile con prefisso dotnet con questo nome in PATH. diff --git a/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.ja.xlf b/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.ja.xlf index db28e5cadd21..ac1e63ccf812 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.ja.xlf +++ b/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.ja.xlf @@ -12,6 +12,11 @@ 無効ãªå½¢å¼ã®ã‚³ãƒžãƒ³ãƒ‰ テキスト '{0}' + + Could not execute because the specified command or file was not found. + 指定ã•れãŸã‚³ãƒžãƒ³ãƒ‰ã¾ãŸã¯ãƒ•ァイルãŒè¦‹ã¤ã‹ã‚‰ãªã‹ã£ãŸãŸã‚ã€å®Ÿè¡Œã§ãã¾ã›ã‚“ã§ã—ãŸã€‚ + + Unable to locate dotnet multiplexer dotnet マルãƒãƒ—レクサーãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“ @@ -28,16 +33,14 @@ - Could not execute because the specified command or file was not found. -Possible reasons for this include: + Possible reasons for this include: * You misspelled a built-in dotnet command. * You intended to execute a .NET program, but {0} does not exist. * You intended to run a global tool, but a dotnet-prefixed executable with this name could not be found on the PATH. - 指定ã•れãŸã‚³ãƒžãƒ³ãƒ‰ã¾ãŸã¯ãƒ•ァイルãŒè¦‹ã¤ã‹ã‚‰ãªã‹ã£ãŸãŸã‚ã€å®Ÿè¡Œã§ãã¾ã›ã‚“ã§ã—ãŸã€‚ -次ã®ã‚ˆã†ãªåŽŸå› ãŒè€ƒãˆã‚‰ã‚Œã¾ã™ã€‚ - * 組ã¿è¾¼ã¿ã® dotnet コマンドã®ã‚¹ãƒšãƒ«ãŒé–“é•ã£ã¦ã„る。 - * .NET プログラムを実行ã—よã†ã¨ã—ãŸãŒã€{0} ãŒå­˜åœ¨ã—ãªã„。 - * グローãƒãƒ« ツールを実行ã—よã†ã¨ã—ãŸãŒã€ãƒ—レフィックスã¨ã—㦠dotnet ãŒä»˜ã„ãŸã“ã®åå‰ã®å®Ÿè¡Œå¯èƒ½ãªã‚‚ã®ãŒ PATH ã«è¦‹ã¤ã‹ã‚‰ãªã‹ã£ãŸã€‚ + ã“れã«ã¯ã€æ¬¡ã®ã‚ˆã†ãªç†ç”±ãŒè€ƒãˆã‚‰ã‚Œã¾ã™: + * 組ã¿è¾¼ã¿ã® dotnet コマンドã®ã‚¹ãƒšãƒ«ãŒé–“é•ã£ã¦ã„ã¾ã™ã€‚ + * .NET プログラムを実行ã—よã†ã¨ã—ã¾ã—ãŸãŒã€{0} ãŒå­˜åœ¨ã—ã¾ã›ã‚“。 + * グローãƒãƒ« ツールを実行ã—よã†ã¨ã—ã¾ã—ãŸãŒã€ã“ã®åå‰ã® dotnet プレフィックス付ã実行å¯èƒ½ãƒ•ァイル㌠PATH ã«è¦‹ã¤ã‹ã‚Šã¾ã›ã‚“ã§ã—ãŸã€‚ diff --git a/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.ko.xlf b/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.ko.xlf index dbe9f93adc8f..4f2d0561d088 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.ko.xlf +++ b/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.ko.xlf @@ -12,6 +12,11 @@ 형ì‹ì´ ìž˜ëª»ëœ ëª…ë ¹ í…스트 '{0}' + + Could not execute because the specified command or file was not found. + 지정한 명령 ë˜ëŠ” 파ì¼ì„ ì°¾ì„ ìˆ˜ 없어 실행하지 못했습니다. + + Unable to locate dotnet multiplexer dotnet multiplexer를 ì°¾ì„ ìˆ˜ ì—†ìŒ @@ -28,16 +33,14 @@ - Could not execute because the specified command or file was not found. -Possible reasons for this include: + Possible reasons for this include: * You misspelled a built-in dotnet command. * You intended to execute a .NET program, but {0} does not exist. * You intended to run a global tool, but a dotnet-prefixed executable with this name could not be found on the PATH. - ì§€ì •ëœ ëª…ë ¹ ë˜ëŠ” 파ì¼ì„ ì°¾ì„ ìˆ˜ 없으므로 실행할 수 없습니다. -가능한 ì›ì¸ì€ 다ìŒê³¼ 같습니다. - * 기본 제공 dotnet 명령 ì² ìžê°€ 잘못 ìž…ë ¥ë˜ì—ˆìŠµë‹ˆë‹¤. - * .NET í”„ë¡œê·¸ëž¨ì„ ì‹¤í–‰í•˜ë ¤ê³  했지만, {0}ì´(ê°€) 없습니다. - * ì „ì—­ ë„구를 실행하려고 했지만, ì´ ì´ë¦„ì˜ dotnet ì ‘ë‘사가 있는 실행 파ì¼ì„ PATHì—서 ì°¾ì„ ìˆ˜ 없습니다. + ì´ì— 대한 ì˜ˆìƒ ì›ì¸ì€ 다ìŒê³¼ 같습니다. + * 기본 제공 dotnet ëª…ë ¹ì˜ ì² ìžê°€ 잘못ë˜ì—ˆìŠµë‹ˆë‹¤. + * .NET í”„ë¡œê·¸ëž¨ì„ ì‹¤í–‰í•˜ë ¤ê³  했지만 {0}ì´(ê°€) 없습니다. + * ì „ì—­ ë„구를 실행하려고 했지만 PATHì—서 ì´ ì´ë¦„ì˜ dotnet ì ‘ë‘사 실행 파ì¼ì„ 찾지 못했습니다. diff --git a/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.pl.xlf b/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.pl.xlf index bb872cff6b03..f6b356e4a6eb 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.pl.xlf +++ b/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.pl.xlf @@ -12,6 +12,11 @@ NieprawidÅ‚owo sformuÅ‚owany tekst polecenia „{0}†+ + Could not execute because the specified command or file was not found. + Nie można wykonać, ponieważ nie znaleziono okreÅ›lonego polecenia lub pliku. + + Unable to locate dotnet multiplexer Nie można zlokalizować multipleksera dotnet @@ -28,16 +33,14 @@ - Could not execute because the specified command or file was not found. -Possible reasons for this include: + Possible reasons for this include: * You misspelled a built-in dotnet command. * You intended to execute a .NET program, but {0} does not exist. * You intended to run a global tool, but a dotnet-prefixed executable with this name could not be found on the PATH. - Nie można wykonać, ponieważ nie odnaleziono okreÅ›lonego polecenia lub pliku. -Możliwe przyczyny: - * Błąd pisowni wbudowanego polecenia dotnet . - * Planowano wykonanie programu platformy .NET, ale element {0} nie istnieje. - * Planowano uruchomienie narzÄ™dzia globalnego, ale nie można odnaleźć pliku wykonywalnego z prefiksem dotnet o tej nazwie w zmiennej PATH. + Możliwe przyczyny tego sÄ… nastÄ™pujÄ…ce: + * Błędnie napisano wbudowane polecenie dotnet. + * Zamierzano wykonać program .NET, ale {0} nie istnieje. + * Zamierzano uruchomić narzÄ™dzie globalne, ale nie można odnaleźć pliku wykonywalnego z prefiksem dotnet o tej nazwie w Å›cieżce PATH. diff --git a/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.pt-BR.xlf b/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.pt-BR.xlf index c3a32a20687d..b29d04bbe723 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.pt-BR.xlf +++ b/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.pt-BR.xlf @@ -12,6 +12,11 @@ Texto do comando malformado '{0}' + + Could not execute because the specified command or file was not found. + Não foi possível executar porque o comando ou arquivo especificado não foi encontrado. + + Unable to locate dotnet multiplexer Não é possível localizar o multiplexador do dotnet @@ -28,16 +33,14 @@ - Could not execute because the specified command or file was not found. -Possible reasons for this include: + Possible reasons for this include: * You misspelled a built-in dotnet command. * You intended to execute a .NET program, but {0} does not exist. * You intended to run a global tool, but a dotnet-prefixed executable with this name could not be found on the PATH. - Não foi possível executar porque o comando ou o arquivo especificado não foi encontrado. -Possíveis motivos para isso incluem: - * Você digitou incorretamente um comando de dotnet interno. + Os possíveis motivos para isso incluem: + * Você digitou incorretamente um comando dotnet interno. * Você pretendia executar um programa .NET, mas {0} não existe. - * Você pretendia executar uma ferramenta global, mas não foi possível encontrar um executável com prefixo de dotnet com esse nome no CAMINHO. + * Você pretendia executar uma ferramenta global, mas não foi possível encontrar um executável com prefixo dotnet com esse nome no PATH. diff --git a/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.ru.xlf b/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.ru.xlf index 84b4742064c3..0de8ace38958 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.ru.xlf +++ b/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.ru.xlf @@ -12,6 +12,11 @@ Ðеправильный формат текÑта команды "{0}" + + Could not execute because the specified command or file was not found. + Ðе удалоÑÑŒ выполнить, поÑкольку ÑƒÐºÐ°Ð·Ð°Ð½Ð½Ð°Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð° или файл не найдены. + + Unable to locate dotnet multiplexer Ðе удаетÑÑ Ð½Ð°Ð¹Ñ‚Ð¸ мультиплекÑор dotnet. @@ -28,16 +33,14 @@ - Could not execute because the specified command or file was not found. -Possible reasons for this include: + Possible reasons for this include: * You misspelled a built-in dotnet command. * You intended to execute a .NET program, but {0} does not exist. * You intended to run a global tool, but a dotnet-prefixed executable with this name could not be found on the PATH. - Ðе удалоÑÑŒ выполнить, так как не найдены ÑƒÐºÐ°Ð·Ð°Ð½Ð½Ð°Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð° или указанный файл. -Возможные причины: - * вы неправильно набрали вÑтроенную команду dotnet; - * вы планировали выполнить программу .NET, однако {0} не ÑущеÑтвует; - * вы хотели запуÑтить глобальное ÑредÑтво, но по указанному в PATH пути не удалоÑÑŒ найти иÑполнÑемый файл Ñ Ð¿Ñ€ÐµÑ„Ð¸ÐºÑом dotnet, имеющий такое имÑ. + Возможные причины Ñтого включают: + * Ð’Ñ‹ допуÑтили ошибку во вÑтроенной команде dotnet. + * Ð’Ñ‹ ÑобиралиÑÑŒ выполнить программу .NET, но {0} не ÑущеÑтвует. + * Ð’Ñ‹ ÑобиралиÑÑŒ запуÑтить глобальное ÑредÑтво, но в PATH не удалоÑÑŒ найти иÑполнÑемый файл Ñ Ð¿Ñ€ÐµÑ„Ð¸ÐºÑом dotnet и таким именем. diff --git a/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.tr.xlf b/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.tr.xlf index 546b74e21b8f..effa36b8a0ee 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.tr.xlf +++ b/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.tr.xlf @@ -12,6 +12,11 @@ Hatalı biçimlendirilmiÅŸ komut metni: '{0}' + + Could not execute because the specified command or file was not found. + Belirtilen komut veya dosya bulunamadığından yürütülemedi. + + Unable to locate dotnet multiplexer Dotnet çoÄŸullayıcısı bulunamadı @@ -28,16 +33,14 @@ - Could not execute because the specified command or file was not found. -Possible reasons for this include: + Possible reasons for this include: * You misspelled a built-in dotnet command. * You intended to execute a .NET program, but {0} does not exist. * You intended to run a global tool, but a dotnet-prefixed executable with this name could not be found on the PATH. - Belirtilen komut veya dosya bulunamadığından yürütülemedi. -Bunun nedeni ÅŸunlardan biri olabilir: + Bunun olası nedenleri ÅŸunlar olabilir: * YerleÅŸik bir dotnet komutunu yanlış yazdınız. - * Bir .NET programını yürütmeyi amaçladınız, ancak {0} yok. - * Genel bir aracı çalıştırmayı amaçladınız, ancak bu ada sahip dotnet ön ekli yürütülebilir dosya PATH üzerinde bulunamadı. + * Bir .NET programını yürütmeyi amaçladınız ancak {0} mevcut deÄŸil. + * Bir genel aracı çalıştırmayı amaçladınız ancak YOL dizininde bu ada sahip dotnet ön eki olan bir yürütülebilir dosya bulunamadı. diff --git a/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.zh-Hans.xlf b/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.zh-Hans.xlf index 5d2ec87f2da8..92d85374f55b 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.zh-Hans.xlf +++ b/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.zh-Hans.xlf @@ -12,6 +12,11 @@ 命令文本“{0}â€æ ¼å¼é”™è¯¯ + + Could not execute because the specified command or file was not found. + 无法执行,因为找ä¸åˆ°æŒ‡å®šçš„命令或文件。 + + Unable to locate dotnet multiplexer 找ä¸åˆ° dotnet 多路å¤ç”¨å™¨ @@ -28,16 +33,14 @@ - Could not execute because the specified command or file was not found. -Possible reasons for this include: + Possible reasons for this include: * You misspelled a built-in dotnet command. * You intended to execute a .NET program, but {0} does not exist. * You intended to run a global tool, but a dotnet-prefixed executable with this name could not be found on the PATH. - 无法执行,因为找ä¸åˆ°æŒ‡å®šçš„命令或文件。 -å¯èƒ½çš„原因包括: - *内置的 dotnet 命令拼写错误。 - *你打算执行 .NET 程åºï¼Œä½† {0} ä¸å­˜åœ¨ã€‚ - *你打算è¿è¡Œå…¨å±€å·¥å…·ï¼Œä½†åœ¨è·¯å¾„上找ä¸åˆ°å…·æœ‰æ­¤å称且å‰ç¼€ä¸º dotnet çš„å¯æ‰§è¡Œæ–‡ä»¶ã€‚ + å¯èƒ½é€ æˆæ­¤é—®é¢˜çš„原因包括: + *内置 dotnet 命令拼写错误。 + *你打算执行 .NET 程åºï¼Œä½† {0} ä¸å­˜åœ¨ã€‚ + *你打算è¿è¡Œå…¨å±€å·¥å…·ï¼Œä½†åœ¨ PATH 上找ä¸åˆ°å…·æœ‰æ­¤å称且带有 dotnet å‰ç¼€çš„坿‰§è¡Œæ–‡ä»¶ã€‚ diff --git a/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.zh-Hant.xlf b/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.zh-Hant.xlf index 829e3039ab97..2a19383fc1ce 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.zh-Hant.xlf +++ b/src/Cli/Microsoft.DotNet.Cli.Utils/xlf/LocalizableStrings.zh-Hant.xlf @@ -12,6 +12,11 @@ 命令文字 '{0}' æ ¼å¼éŒ¯èª¤ + + Could not execute because the specified command or file was not found. + 無法執行,因為找ä¸åˆ°æŒ‡å®šçš„命令或檔案。 + + Unable to locate dotnet multiplexer 找ä¸åˆ° dotnet multiplexer @@ -28,16 +33,14 @@ - Could not execute because the specified command or file was not found. -Possible reasons for this include: + Possible reasons for this include: * You misspelled a built-in dotnet command. * You intended to execute a .NET program, but {0} does not exist. * You intended to run a global tool, but a dotnet-prefixed executable with this name could not be found on the PATH. - 因為找ä¸åˆ°æŒ‡å®šçš„命令或檔案,所以無法執行。 -å¯èƒ½çš„原因包括: - * 內建 dotnet 命令拼寫錯誤。 - * 您é è¨ˆè¦åŸ·è¡Œ .NET 程å¼ï¼Œä½†ä¸å­˜åœ¨ {0}。 - * 您é è¨ˆè¦åŸ·è¡Œå…¨åŸŸå·¥å…·ï¼Œä½†åœ¨ PATH 上找ä¸åˆ°æ­¤å稱且開頭為 dotnet çš„å¯åŸ·è¡Œæª”。 + å¯èƒ½çš„原因包括: + * 您拼錯了內建 dotnet 命令。 + * 您打算執行 .NET 程å¼ï¼Œä½† {0} ä¸å­˜åœ¨ã€‚ + * 您打算執行全域工具,但在 PATH 上找ä¸åˆ°å…·æœ‰æ­¤å稱的以 dotnet 為首碼的å¯åŸ·è¡Œæª”。 diff --git a/src/Cli/Microsoft.DotNet.Configurer/xlf/LocalizableStrings.es.xlf b/src/Cli/Microsoft.DotNet.Configurer/xlf/LocalizableStrings.es.xlf index 7c393562ad06..c4a282ffbb99 100644 --- a/src/Cli/Microsoft.DotNet.Configurer/xlf/LocalizableStrings.es.xlf +++ b/src/Cli/Microsoft.DotNet.Configurer/xlf/LocalizableStrings.es.xlf @@ -39,7 +39,7 @@ Use 'dotnet --help' to see available commands or visit: https://aka.ms/dotnet-cl Escribir su primera aplicación: https://aka.ms/dotnet-hello-world Descubra las novedades: https://aka.ms/dotnet-whats-new Explore la documentación: https://aka.ms/dotnet-docs -Notificar problemas y encontrar el origen en GitHub: https://github.com/dotnet/core +Notificar problemas y encontrar el código fuente en GitHub: https://github.com/dotnet/core Use "dotnet --help" para ver los comandos disponibles o visite: https://aka.ms/dotnet-cli -------------------------------------------------------------------------------------- diff --git a/src/Cli/dotnet/CommandLineInfo.cs b/src/Cli/dotnet/CommandLineInfo.cs index 2a67d2bd2f14..b931a2cbe157 100644 --- a/src/Cli/dotnet/CommandLineInfo.cs +++ b/src/Cli/dotnet/CommandLineInfo.cs @@ -36,7 +36,7 @@ private static void PrintWorkloadsInfo() { Reporter.Output.WriteLine(); Reporter.Output.WriteLine($"{LocalizableStrings.DotnetWorkloadInfoLabel}"); - WorkloadCommandParser.ShowWorkloadsInfo(); + WorkloadCommandParser.ShowWorkloadsInfo(showVersion: false); } private static string GetDisplayRid(DotnetVersionFile versionFile) diff --git a/src/Cli/dotnet/CommonOptions.cs b/src/Cli/dotnet/CommonOptions.cs index 3547fd59ed1c..a11b13c4476c 100644 --- a/src/Cli/dotnet/CommonOptions.cs +++ b/src/Cli/dotnet/CommonOptions.cs @@ -249,9 +249,9 @@ public static string GetCurrentRuntimeId() return currentRuntimeIdentifiers[0]; // First rid is the most specific (ex win-x64) } - private static string GetOsFromRid(string rid) => rid.Substring(0, rid.LastIndexOf("-")); + private static string GetOsFromRid(string rid) => rid.Substring(0, rid.LastIndexOf("-", StringComparison.InvariantCulture)); - private static string GetArchFromRid(string rid) => rid.Substring(rid.LastIndexOf("-") + 1, rid.Length - rid.LastIndexOf("-") - 1); + private static string GetArchFromRid(string rid) => rid.Substring(rid.LastIndexOf("-", StringComparison.InvariantCulture) + 1, rid.Length - rid.LastIndexOf("-", StringComparison.InvariantCulture) - 1); private static IEnumerable ForwardSelfContainedOptions(bool isSelfContained, ParseResult parseResult) { diff --git a/src/Cli/dotnet/Installer/Windows/ISynchronizingLogger.cs b/src/Cli/dotnet/Installer/Windows/ISynchronizingLogger.cs new file mode 100644 index 000000000000..a442a7f4401a --- /dev/null +++ b/src/Cli/dotnet/Installer/Windows/ISynchronizingLogger.cs @@ -0,0 +1,17 @@ +// 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.Installer.Windows +{ + /// + /// Represents a type used to log setup operations that can manage a series of named pipes writing to it. + /// + internal interface ISynchronizingLogger : ISetupLogger + { + /// + /// Starts a new thread to listen for log requests messages from external processes. + /// + /// The name of the pipe. + void AddNamedPipe(string pipeName); + } +} diff --git a/src/Cli/dotnet/Installer/Windows/InstallClientElevationContext.cs b/src/Cli/dotnet/Installer/Windows/InstallClientElevationContext.cs index 65c24dfbe79c..045c4e0b7ea7 100644 --- a/src/Cli/dotnet/Installer/Windows/InstallClientElevationContext.cs +++ b/src/Cli/dotnet/Installer/Windows/InstallClientElevationContext.cs @@ -14,13 +14,13 @@ namespace Microsoft.DotNet.Installer.Windows [SupportedOSPlatform("windows")] internal sealed class InstallClientElevationContext : InstallElevationContextBase { - private TimestampedFileLogger _log; + private ISynchronizingLogger _log; private Process _serverProcess; public override bool IsClient => true; - public InstallClientElevationContext(TimestampedFileLogger logger) + public InstallClientElevationContext(ISynchronizingLogger logger) { _log = logger; } @@ -60,7 +60,7 @@ public override void Elevate() // Add a pipe to the logger to allow the server to send log requests. This avoids having an elevated process writing // to a less privileged location. It also simplifies troubleshooting because log events will be chronologically - // ordered in a single file. + // ordered in a single file. _log.AddNamedPipe(WindowsUtils.CreatePipeName(_serverProcess.Id, "log")); HasElevated = true; diff --git a/src/Cli/dotnet/Installer/Windows/InstallMessageDispatcher.cs b/src/Cli/dotnet/Installer/Windows/InstallMessageDispatcher.cs index 8ca9141a9343..7d446db4944d 100644 --- a/src/Cli/dotnet/Installer/Windows/InstallMessageDispatcher.cs +++ b/src/Cli/dotnet/Installer/Windows/InstallMessageDispatcher.cs @@ -146,5 +146,51 @@ public InstallResponseMessage SendWorkloadRecordRequest(InstallRequestType reque SdkFeatureBand = sdkFeatureBand.ToString(), }); } + + /// + /// Send an to delete the install state file. + /// + /// The SDK feature band of the install state file to delete. + /// + public InstallResponseMessage SendRemoveManifestsFromInstallStateFileRequest(SdkFeatureBand sdkFeatureBand) + { + return Send(new InstallRequestMessage + { + RequestType = InstallRequestType.RemoveManifestsFromInstallStateFile, + SdkFeatureBand = sdkFeatureBand.ToString(), + }); + } + + /// + /// Sends an to write the install state file. + /// + /// The SDK feature band of the install state file to write + /// A multi-line string containing the formatted JSON data to write. + /// + public InstallResponseMessage SendSaveInstallStateManifestVersions(SdkFeatureBand sdkFeatureBand, Dictionary manifestContents) + { + return Send(new InstallRequestMessage + { + RequestType = InstallRequestType.SaveInstallStateManifestVersions, + SdkFeatureBand = sdkFeatureBand.ToString(), + InstallStateManifestVersions = manifestContents + }); + } + + /// + /// Send an to adjust the mode used for installing and updating workloads + /// + /// The SDK feature band of the install state file to write + /// Whether to use workload sets or not + /// + public InstallResponseMessage SendUpdateWorkloadModeRequest(SdkFeatureBand sdkFeatureBand, bool newMode) + { + return Send(new InstallRequestMessage + { + RequestType = InstallRequestType.AdjustWorkloadMode, + SdkFeatureBand = sdkFeatureBand.ToString(), + UseWorkloadSets = newMode, + }); + } } } diff --git a/src/Cli/dotnet/Installer/Windows/InstallRequestMessage.cs b/src/Cli/dotnet/Installer/Windows/InstallRequestMessage.cs index 712534d0b666..5ab6430bcbf4 100644 --- a/src/Cli/dotnet/Installer/Windows/InstallRequestMessage.cs +++ b/src/Cli/dotnet/Installer/Windows/InstallRequestMessage.cs @@ -29,6 +29,16 @@ public string ManifestPath set; } + /// + /// The contents of the install state file. Each element corresponds to a single line of + /// the JSON file to be written. + /// + public Dictionary InstallStateManifestVersions + { + get; + set; + } + /// /// The path of the MSI log file to generate when installing, uninstalling or repairing a specific MSI. /// @@ -110,6 +120,14 @@ public string WorkloadId set; } + /// + /// The new mode to use: workloadset or loosemanifests + /// + public bool UseWorkloadSets + { + get; set; + } + /// /// Converts a deserialized array of bytes into an . /// diff --git a/src/Cli/dotnet/Installer/Windows/InstallRequestType.cs b/src/Cli/dotnet/Installer/Windows/InstallRequestType.cs index 7bb46285b29a..2d90d3f2f68f 100644 --- a/src/Cli/dotnet/Installer/Windows/InstallRequestType.cs +++ b/src/Cli/dotnet/Installer/Windows/InstallRequestType.cs @@ -53,6 +53,21 @@ public enum InstallRequestType /// /// Remove a workload installation record. /// - DeleteWorkloadInstallationRecord + DeleteWorkloadInstallationRecord, + + /// + /// Creates an install state file. + /// + SaveInstallStateManifestVersions, + + /// + /// Removes an install state file. + /// + RemoveManifestsFromInstallStateFile, + + /// + /// Changes the workload mode + /// + AdjustWorkloadMode, } } diff --git a/src/Cli/dotnet/Installer/Windows/MsiPackageCache.cs b/src/Cli/dotnet/Installer/Windows/MsiPackageCache.cs index 9b300f91af77..54f0939a4785 100644 --- a/src/Cli/dotnet/Installer/Windows/MsiPackageCache.cs +++ b/src/Cli/dotnet/Installer/Windows/MsiPackageCache.cs @@ -1,11 +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 System.IO.Pipes; using System.Runtime.Versioning; -using System.Security.AccessControl; -using System.Security.Principal; -using Microsoft.DotNet.Cli.Utils; #if !DOT_NET_BUILD_FROM_SOURCE using Microsoft.DotNet.Installer.Windows.Security; #endif @@ -25,55 +21,6 @@ internal class MsiPackageCache : InstallerBase /// private bool _allowOnlineRevocationChecks; - /// - /// Default inheritance to apply to directory ACLs. - /// - private static readonly InheritanceFlags s_DefaultInheritance = InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit; - - /// - /// SID that matches built-in administrators. - /// - private static readonly SecurityIdentifier s_AdministratorsSid = new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null); - - /// - /// SID that matches everyone. - /// - private static readonly SecurityIdentifier s_EveryoneSid = new SecurityIdentifier(WellKnownSidType.WorldSid, null); - - /// - /// Local SYSTEM SID. - /// - private static readonly SecurityIdentifier s_LocalSystemSid = new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null); - - /// - /// SID matching built-in user accounts. - /// - private static readonly SecurityIdentifier s_UsersSid = new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null); - - /// - /// ACL rule associated with the Administrators SID. - /// - private static readonly FileSystemAccessRule s_AdministratorRule = new FileSystemAccessRule(s_AdministratorsSid, FileSystemRights.FullControl, - s_DefaultInheritance, PropagationFlags.None, AccessControlType.Allow); - - /// - /// ACL rule associated with the Everyone SID. - /// - private static readonly FileSystemAccessRule s_EveryoneRule = new FileSystemAccessRule(s_EveryoneSid, FileSystemRights.ReadAndExecute, - s_DefaultInheritance, PropagationFlags.None, AccessControlType.Allow); - - /// - /// ACL rule associated with the Local SYSTEM SID. - /// - private static readonly FileSystemAccessRule s_LocalSystemRule = new FileSystemAccessRule(s_LocalSystemSid, FileSystemRights.FullControl, - s_DefaultInheritance, PropagationFlags.None, AccessControlType.Allow); - - /// - /// ACL rule associated with the built-in users SID. - /// - private static readonly FileSystemAccessRule s_UsersRule = new FileSystemAccessRule(s_UsersSid, FileSystemRights.ReadAndExecute, - s_DefaultInheritance, PropagationFlags.None, AccessControlType.Allow); - /// /// The root directory of the package cache where MSI workload packs are stored. /// @@ -88,21 +35,6 @@ public MsiPackageCache(InstallElevationContextBase elevationContext, ISetupLogge _allowOnlineRevocationChecks = SignCheck.AllowOnlineRevocationChecks(); } - /// - /// Creates the specified directory and secures it by configuring access rules (ACLs) that allow sub-directories - /// and files to inherit access control entries. - /// - /// The path of the directory to create. - public static void CreateSecureDirectory(string path) - { - if (!Directory.Exists(path)) - { - DirectorySecurity ds = new(); - SetDirectoryAccessRules(ds); - ds.CreateDirectory(path); - } - } - /// /// Moves the MSI payload described by the manifest file to the cache. /// @@ -129,7 +61,7 @@ public void CachePayload(string packageId, string packageVersion, string manifes Directory.Delete(packageDirectory, recursive: true); } - CreateSecureDirectory(packageDirectory); + SecurityUtils.CreateSecureDirectory(packageDirectory); // We cannot assume that the MSI adjacent to the manifest is the one to cache. We'll trust // the manifest to provide the MSI filename. @@ -140,8 +72,8 @@ public void CachePayload(string packageId, string packageVersion, string manifes string cachedMsiPath = Path.Combine(packageDirectory, Path.GetFileName(msiPath)); string cachedManifestPath = Path.Combine(packageDirectory, Path.GetFileName(manifestPath)); - MoveAndSecureFile(manifestPath, cachedManifestPath, Log); - MoveAndSecureFile(msiPath, cachedMsiPath, Log); + SecurityUtils.MoveAndSecureFile(manifestPath, cachedManifestPath, Log); + SecurityUtils.MoveAndSecureFile(msiPath, cachedMsiPath, Log); } else if (IsClient) { @@ -160,37 +92,6 @@ public string GetPackageDirectory(string packageId, string packageVersion) return Path.Combine(PackageCacheRoot, packageId, packageVersion); } - /// - /// Moves a file from one location to another if the destination file does not already exist and - /// configure its permissions. - /// - /// The source file to move. - /// The destination where the source file will be moved. - /// The underlying setup log to use. - public static void MoveAndSecureFile(string sourceFile, string destinationFile, ISetupLogger log = null) - { - if (!File.Exists(destinationFile)) - { - FileAccessRetrier.RetryOnMoveAccessFailure(() => - { - // Moving the file preserves the owner SID and fails to inherit the WD ACE. - File.Copy(sourceFile, destinationFile, overwrite: true); - File.Delete(sourceFile); - }); - log?.LogMessage($"Moved '{sourceFile}' to '{destinationFile}'"); - - FileInfo fi = new(destinationFile); - FileSecurity fs = new(); - - // Set the owner and group to built-in administrators (BA). All other ACE values are inherited from - // the parent directory. See https://github.com/dotnet/sdk/issues/28450. If the directory's descriptor - // is correctly configured, we should end up with an inherited ACE for Everyone: (A;ID;0x1200a9;;;WD) - fs.SetOwner(s_AdministratorsSid); - fs.SetGroup(s_AdministratorsSid); - fi.SetAccessControl(fs); - } - } - /// /// Determines if the workload pack MSI is cached and tries to retrieve its payload from the cache. /// @@ -243,22 +144,6 @@ public bool TryGetMsiPathFromPackageData(string packageDataPath, out string msiP return true; } - /// - /// Apply a standard set of access rules to the directory security descriptor. The owner and group will - /// be set to built-in Administrators. Full access is granted to built-in administators and SYSTEM with - /// read, execute, synchronize permssions for built-in users and Everyone. - /// - /// The security descriptor to update. - private static void SetDirectoryAccessRules(DirectorySecurity ds) - { - ds.SetOwner(s_AdministratorsSid); - ds.SetGroup(s_AdministratorsSid); - ds.SetAccessRule(s_AdministratorRule); - ds.SetAccessRule(s_LocalSystemRule); - ds.SetAccessRule(s_UsersRule); - ds.SetAccessRule(s_EveryoneRule); - } - /// /// Verifies that an MSI package contains an Authenticode signature that terminates in a trusted Microsoft root certificate. /// diff --git a/src/Cli/dotnet/Installer/Windows/NullInstallerLogger.cs b/src/Cli/dotnet/Installer/Windows/NullInstallerLogger.cs new file mode 100644 index 000000000000..2c61375d89a1 --- /dev/null +++ b/src/Cli/dotnet/Installer/Windows/NullInstallerLogger.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. +namespace Microsoft.DotNet.Installer.Windows; + +/// +/// A class that swallows all logging - used for code paths where logging setup operations isn't valuable, like dotnet --info. +/// +internal class NullInstallerLogger : ISynchronizingLogger +{ + public string LogPath => String.Empty; + + public void AddNamedPipe(string pipeName) + { + + } + + public void LogMessage(string message) + { + + } +} diff --git a/src/Cli/dotnet/Installer/Windows/SecurityUtils.cs b/src/Cli/dotnet/Installer/Windows/SecurityUtils.cs new file mode 100644 index 000000000000..38ca0e78499d --- /dev/null +++ b/src/Cli/dotnet/Installer/Windows/SecurityUtils.cs @@ -0,0 +1,138 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.IO.Pipes; +using System.Runtime.Versioning; +using System.Security.AccessControl; +using System.Security.Principal; +using Microsoft.DotNet.Cli.Utils; + +namespace Microsoft.DotNet.Installer.Windows +{ + /// + /// Defines some generic security related helper methods. + /// + [SupportedOSPlatform("windows")] + internal static class SecurityUtils + { + /// + /// Default inheritance to apply to directory ACLs. + /// + private static readonly InheritanceFlags s_DefaultInheritance = InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit; + + /// + /// SID that matches built-in administrators. + /// + private static readonly SecurityIdentifier s_AdministratorsSid = new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null); + + /// + /// SID that matches everyone. + /// + private static readonly SecurityIdentifier s_EveryoneSid = new SecurityIdentifier(WellKnownSidType.WorldSid, null); + + /// + /// Local SYSTEM SID. + /// + private static readonly SecurityIdentifier s_LocalSystemSid = new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null); + + /// + /// SID matching built-in user accounts. + /// + private static readonly SecurityIdentifier s_UsersSid = new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null); + + /// + /// ACL rule associated with the Administrators SID. + /// + private static readonly FileSystemAccessRule s_AdministratorRule = new FileSystemAccessRule(s_AdministratorsSid, FileSystemRights.FullControl, + s_DefaultInheritance, PropagationFlags.None, AccessControlType.Allow); + + /// + /// ACL rule associated with the Everyone SID. + /// + private static readonly FileSystemAccessRule s_EveryoneRule = new FileSystemAccessRule(s_EveryoneSid, FileSystemRights.ReadAndExecute, + s_DefaultInheritance, PropagationFlags.None, AccessControlType.Allow); + + /// + /// ACL rule associated with the Local SYSTEM SID. + /// + private static readonly FileSystemAccessRule s_LocalSystemRule = new FileSystemAccessRule(s_LocalSystemSid, FileSystemRights.FullControl, + s_DefaultInheritance, PropagationFlags.None, AccessControlType.Allow); + + /// + /// ACL rule associated with the built-in users SID. + /// + private static readonly FileSystemAccessRule s_UsersRule = new FileSystemAccessRule(s_UsersSid, FileSystemRights.ReadAndExecute, + s_DefaultInheritance, PropagationFlags.None, AccessControlType.Allow); + + /// + /// Creates the specified directory and secures it by configuring access rules (ACLs) that allow sub-directories + /// and files to inherit access control entries. + /// + /// The path of the directory to create. + public static void CreateSecureDirectory(string path) + { + if (!Directory.Exists(path)) + { + DirectorySecurity ds = new(); + SecurityUtils.SetDirectoryAccessRules(ds); + ds.CreateDirectory(path); + } + } + + /// + /// Moves a file from one location to another if the destination file does not already exist and + /// configure its permissions. + /// + /// The source file to move. + /// The destination where the source file will be moved. + /// The underlying setup log to use. + public static void MoveAndSecureFile(string sourceFile, string destinationFile, ISetupLogger log = null) + { + if (!File.Exists(destinationFile)) + { + FileAccessRetrier.RetryOnMoveAccessFailure(() => + { + // Moving the file preserves the owner SID and fails to inherit the WD ACE. + File.Copy(sourceFile, destinationFile, overwrite: true); + File.Delete(sourceFile); + }); + log?.LogMessage($"Moved '{sourceFile}' to '{destinationFile}'"); + + SecureFile(destinationFile); + } + } + + /// + /// Secures a file by setting the owner and group to built-in administrators (BA). All other ACE values are inherited from + /// the parent directory. + /// + /// The path of the file to secure. + public static void SecureFile(string path) + { + FileInfo fi = new(path); + FileSecurity fs = new(); + + // See https://github.com/dotnet/sdk/issues/28450. If the directory's descriptor + // is correctly configured, we should end up with an inherited ACE for Everyone: (A;ID;0x1200a9;;;WD) + fs.SetOwner(s_AdministratorsSid); + fs.SetGroup(s_AdministratorsSid); + fi.SetAccessControl(fs); + } + + /// + /// Apply a standard set of access rules to the directory security descriptor. The owner and group will + /// be set to built-in Administrators. Full access is granted to built-in administators and SYSTEM with + /// read, execute, synchronize permssions for built-in users and Everyone. + /// + /// The security descriptor to update. + private static void SetDirectoryAccessRules(DirectorySecurity ds) + { + ds.SetOwner(s_AdministratorsSid); + ds.SetGroup(s_AdministratorsSid); + ds.SetAccessRule(s_AdministratorRule); + ds.SetAccessRule(s_LocalSystemRule); + ds.SetAccessRule(s_UsersRule); + ds.SetAccessRule(s_EveryoneRule); + } + } +} diff --git a/src/Cli/dotnet/Installer/Windows/TimestampedFileLogger.cs b/src/Cli/dotnet/Installer/Windows/TimestampedFileLogger.cs index c606499ca841..5f0d422b938f 100644 --- a/src/Cli/dotnet/Installer/Windows/TimestampedFileLogger.cs +++ b/src/Cli/dotnet/Installer/Windows/TimestampedFileLogger.cs @@ -13,7 +13,7 @@ namespace Microsoft.DotNet.Installer.Windows /// queue messages. /// [SupportedOSPlatform("windows")] - internal class TimestampedFileLogger : SetupLoggerBase, IDisposable, ISetupLogger + internal class TimestampedFileLogger : SetupLoggerBase, IDisposable, ISynchronizingLogger { /// /// Thread safe queue use to store incoming log request messages. @@ -81,10 +81,6 @@ public TimestampedFileLogger(string path, int flushThreshold, params string[] lo LogMessage($"=== Logging started ==="); } - /// - /// Starts a new thread to listen for log requests messages from external processes. - /// - /// The name of the pipe. public void AddNamedPipe(string pipeName) { Thread logRequestThread = new Thread(ProcessLogRequests) { IsBackground = true }; diff --git a/src/Cli/dotnet/Installer/Windows/WindowsUtils.cs b/src/Cli/dotnet/Installer/Windows/WindowsUtils.cs index 463d64513bf8..99e9b8f161c6 100644 --- a/src/Cli/dotnet/Installer/Windows/WindowsUtils.cs +++ b/src/Cli/dotnet/Installer/Windows/WindowsUtils.cs @@ -64,8 +64,10 @@ public static bool RebootRequired() using RegistryKey sessionKey = localMachineKey?.OpenSubKey(@"SYSTEM\CurrentControlSet\Control\Session Manager"); string[] pendingFileRenameOperations = (string[])sessionKey?.GetValue("PendingFileRenameOperations") ?? new string[0]; + // Destination files for pending renames start with !\??\, whereas the source does not have the leading "!". + bool hasPendingFileRenames = pendingFileRenameOperations.Any(s => !string.IsNullOrWhiteSpace(s) && s.StartsWith(@"!\??\")); - return (auKey != null || cbsKey != null || pendingFileRenameOperations.Length > 0); + return (auKey != null || cbsKey != null || hasPendingFileRenames); } } } diff --git a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.cs.xlf b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.cs.xlf index c00aae5e30af..a99263b23eec 100644 --- a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.cs.xlf +++ b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.cs.xlf @@ -4,7 +4,7 @@ The requested certificate chain policy could not be checked: {0} - The requested certificate chain policy could not be checked: {0} + Požadované zásady Å™etÄ›zu certifikátů nebylo možné zkontrolovat: {0} diff --git a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.de.xlf b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.de.xlf index 108efca9275b..c618130cbb2a 100644 --- a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.de.xlf +++ b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.de.xlf @@ -4,7 +4,7 @@ The requested certificate chain policy could not be checked: {0} - The requested certificate chain policy could not be checked: {0} + Die angeforderte Zertifikatkettenrichtlinie konnte nicht überprüft werden: {0} diff --git a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.es.xlf b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.es.xlf index 196614f4c45f..71298e7b2d27 100644 --- a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.es.xlf +++ b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.es.xlf @@ -4,7 +4,7 @@ The requested certificate chain policy could not be checked: {0} - The requested certificate chain policy could not be checked: {0} + No se pudo comprobar la directiva de cadena de certificados solicitada: {0} diff --git a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.fr.xlf b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.fr.xlf index d9453fce2c36..cd58f475fcde 100644 --- a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.fr.xlf +++ b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.fr.xlf @@ -4,7 +4,7 @@ The requested certificate chain policy could not be checked: {0} - The requested certificate chain policy could not be checked: {0} + Impossible de vérifier la stratégie de chaîne de certificats demandée : {0} diff --git a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.it.xlf b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.it.xlf index 5cdcdd9e9707..24789b558ec4 100644 --- a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.it.xlf +++ b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.it.xlf @@ -4,7 +4,7 @@ The requested certificate chain policy could not be checked: {0} - The requested certificate chain policy could not be checked: {0} + Non è stato possibile controllare il criterio della catena di certificati richiesto: {0} diff --git a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.ja.xlf b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.ja.xlf index 29bca91ae045..b8da6643583e 100644 --- a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.ja.xlf +++ b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.ja.xlf @@ -4,7 +4,7 @@ The requested certificate chain policy could not be checked: {0} - The requested certificate chain policy could not be checked: {0} + è¦æ±‚ã•れãŸè¨¼æ˜Žæ›¸ãƒã‚§ãƒ¼ãƒ³ ãƒãƒªã‚·ãƒ¼ã‚’確èªã§ãã¾ã›ã‚“ã§ã—ãŸ: {0} diff --git a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.ko.xlf b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.ko.xlf index eff70df8d5b1..c5dcfc917c3d 100644 --- a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.ko.xlf +++ b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.ko.xlf @@ -4,7 +4,7 @@ The requested certificate chain policy could not be checked: {0} - The requested certificate chain policy could not be checked: {0} + 요청한 ì¸ì¦ì„œ ì²´ì¸ ì •ì±…ì„ í™•ì¸í•  수 없습니다: {0} diff --git a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.pl.xlf b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.pl.xlf index d47b4882d5bb..5db4a271b19c 100644 --- a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.pl.xlf +++ b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.pl.xlf @@ -4,7 +4,7 @@ The requested certificate chain policy could not be checked: {0} - The requested certificate chain policy could not be checked: {0} + Nie można sprawdzić żądanych zasad Å‚aÅ„cucha certyfikatów: {0} diff --git a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.pt-BR.xlf b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.pt-BR.xlf index 457c0255912b..2f5e0a444dd3 100644 --- a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.pt-BR.xlf +++ b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.pt-BR.xlf @@ -4,7 +4,7 @@ The requested certificate chain policy could not be checked: {0} - The requested certificate chain policy could not be checked: {0} + Não foi possível verificar a política de cadeia de certificados solicitada: {0} diff --git a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.ru.xlf b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.ru.xlf index d952a0d46d26..8f58abf58003 100644 --- a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.ru.xlf +++ b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.ru.xlf @@ -4,7 +4,7 @@ The requested certificate chain policy could not be checked: {0} - The requested certificate chain policy could not be checked: {0} + Ðе удалоÑÑŒ проверить запрошенную политику цепочки Ñертификатов {0} diff --git a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.tr.xlf b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.tr.xlf index 341d602fc487..a22be57d131d 100644 --- a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.tr.xlf +++ b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.tr.xlf @@ -4,7 +4,7 @@ The requested certificate chain policy could not be checked: {0} - The requested certificate chain policy could not be checked: {0} + İstenen sertifika zinciri ilkesi denetlenemedi: {0} diff --git a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.zh-Hans.xlf b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.zh-Hans.xlf index 3323173002c8..0ad42e9a1659 100644 --- a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.zh-Hans.xlf +++ b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.zh-Hans.xlf @@ -4,7 +4,7 @@ The requested certificate chain policy could not be checked: {0} - The requested certificate chain policy could not be checked: {0} + 无法检查请求的è¯ä¹¦é“¾ç­–ç•¥: {0} diff --git a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.zh-Hant.xlf b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.zh-Hant.xlf index 28ffbf5688f0..314465d1da63 100644 --- a/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.zh-Hant.xlf +++ b/src/Cli/dotnet/Installer/Windows/xlf/LocalizableStrings.zh-Hant.xlf @@ -4,7 +4,7 @@ The requested certificate chain policy could not be checked: {0} - The requested certificate chain policy could not be checked: {0} + ç„¡æ³•æª¢æŸ¥è¦æ±‚的憑證éˆçµåŽŸå‰‡: {0} diff --git a/src/Cli/dotnet/NugetPackageDownloader/LocalizableStrings.resx b/src/Cli/dotnet/NugetPackageDownloader/LocalizableStrings.resx index bfb61a0a4c16..cb0ad6727fe8 100644 --- a/src/Cli/dotnet/NugetPackageDownloader/LocalizableStrings.resx +++ b/src/Cli/dotnet/NugetPackageDownloader/LocalizableStrings.resx @@ -144,4 +144,19 @@ Package Source Mapping is enabled, but no source mapped under the specified package ID: {0}. See the documentation for Package Source Mapping at https://aka.ms/nuget-package-source-mapping for more details. + + A version of {0} of package {1} + + + Version {0} of package {1} + + + A version between {0} and {1} of package {2} + + + A version higher than {0} of package {1} + + + A version less than {0} of package {1} + \ No newline at end of file diff --git a/src/Cli/dotnet/NugetPackageDownloader/NuGetPackageDownloader.cs b/src/Cli/dotnet/NugetPackageDownloader/NuGetPackageDownloader.cs index ac176a717930..933f561d985c 100644 --- a/src/Cli/dotnet/NugetPackageDownloader/NuGetPackageDownloader.cs +++ b/src/Cli/dotnet/NugetPackageDownloader/NuGetPackageDownloader.cs @@ -1,11 +1,13 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Text.RegularExpressions; using System.Threading; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.ToolPackage; using Microsoft.DotNet.Tools; using Microsoft.Extensions.EnvironmentAbstractions; +using Microsoft.TemplateEngine.Abstractions; using NuGet.Common; using NuGet.Configuration; using NuGet.Credentials; @@ -161,8 +163,8 @@ public async Task GetPackageUrl(PackageId packageId, PackageSourceLocation packageSourceLocation = null, bool includePreview = false) { - (var source, var resolvedPackageVersion) = await GetPackageSourceAndVersion(packageId, packageVersion, packageSourceLocation, includePreview); - + (var source, var resolvedPackageVersion) = await GetPackageSourceAndVersion(packageId, packageVersion, packageSourceLocation, includePreview).ConfigureAwait(false); + SourceRepository repository = GetSourceRepository(source); if (repository.PackageSource.IsLocal) { @@ -450,15 +452,45 @@ await Task.WhenAll( throw new NuGetPackageNotFoundException( string.Format( LocalizableStrings.IsNotFoundInNuGetFeeds, - $"{packageIdentifier}::{versionRange}", + GenerateVersionRangeErrorDescription(packageIdentifier, versionRange), string.Join(", ", packageSources.Select(source => source.Source)))); } + } + + private string GenerateVersionRangeErrorDescription(string packageIdentifier, VersionRange versionRange) + { + if (!string.IsNullOrEmpty(versionRange.OriginalString) && versionRange.OriginalString == "*") + { + return $"{packageIdentifier}"; + } + else if (versionRange.HasLowerAndUpperBounds && versionRange.MinVersion == versionRange.MaxVersion) + { + return string.Format(LocalizableStrings.PackageVersionDescriptionForExactVersionMatch, + versionRange.MinVersion, packageIdentifier); + } + else if (versionRange.HasLowerAndUpperBounds) + { + return string.Format(LocalizableStrings.PackageVersionDescriptionForVersionWithLowerAndUpperBounds, + versionRange.MinVersion, versionRange.MaxVersion, packageIdentifier); + } + else if (versionRange.HasLowerBound) + { + return string.Format(LocalizableStrings.PackageVersionDescriptionForVersionWithLowerBound, + versionRange.MinVersion, packageIdentifier); + } + else if (versionRange.HasUpperBound) + { + return string.Format(LocalizableStrings.PackageVersionDescriptionForVersionWithUpperBound, + versionRange.MaxVersion, packageIdentifier); + } + // Default message if the format doesn't match any of the expected cases + return string.Format(LocalizableStrings.PackageVersionDescriptionDefault, versionRange, packageIdentifier); } - private async Task<(PackageSource, IPackageSearchMetadata)> GetLatestVersionInternalAsync( - string packageIdentifier, IEnumerable packageSources, bool includePreview, - CancellationToken cancellationToken) + private async Task<(PackageSource, IPackageSearchMetadata)> GetLatestVersionInternalAsync( + string packageIdentifier, IEnumerable packageSources, bool includePreview, + CancellationToken cancellationToken) { if (packageSources == null) { @@ -499,7 +531,7 @@ await Task.WhenAll( .SelectMany(result => result.foundPackages.Select(package => (result.source, package))); if (!accumulativeSearchResults.Any()) - { + { throw new NuGetPackageNotFoundException( string.Format( LocalizableStrings.IsNotFoundInNuGetFeeds, @@ -527,8 +559,7 @@ public async Task GetBestPackageVersionAsync(PackageId packageId, VersionRange versionRange, PackageSourceLocation packageSourceLocation = null) { - if (versionRange.MinVersion != null && !versionRange.IsFloating && - (versionRange.MaxVersion == null || versionRange.MinVersion == versionRange.MaxVersion)) + if (versionRange.MinVersion != null && versionRange.MaxVersion != null && versionRange.MinVersion == versionRange.MaxVersion) { return versionRange.MinVersion; } @@ -625,7 +656,8 @@ bool TryGetPackageMetadata( } throw new NuGetPackageNotFoundException(string.Format(LocalizableStrings.IsNotFoundInNuGetFeeds, - $"{packageIdentifier}::{packageVersion}", string.Join(";", sources.Select(s => s.Source)))); + GenerateVersionRangeErrorDescription(packageIdentifier, new VersionRange(minVersion: packageVersion, maxVersion: packageVersion, includeMaxVersion: true)), + string.Join(";", sources.Select(s => s.Source)))); } private async Task<(PackageSource source, IEnumerable foundPackages)> diff --git a/src/Cli/dotnet/NugetPackageDownloader/ToolPackageException.cs b/src/Cli/dotnet/NugetPackageDownloader/ToolPackageException.cs index b2475bdeefcd..8e087629e471 100644 --- a/src/Cli/dotnet/NugetPackageDownloader/ToolPackageException.cs +++ b/src/Cli/dotnet/NugetPackageDownloader/ToolPackageException.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.Cli.Utils; + namespace Microsoft.DotNet.Cli.NuGetPackageDownloader { - internal class NuGetPackageInstallerException : Exception + internal class NuGetPackageInstallerException : GracefulException { public NuGetPackageInstallerException() { diff --git a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.cs.xlf b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.cs.xlf index 911fd33da0d3..6668ef85e22e 100644 --- a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.cs.xlf +++ b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.cs.xlf @@ -42,6 +42,31 @@ PÅ™eskakuje se ověření podpisu balíÄku NuGet. + + A version of {0} of package {1} + Verze {0} balíÄku {1} + + + + Version {0} of package {1} + Verze {0} balíÄku {1} + + + + A version between {0} and {1} of package {2} + Verze mezi {0} a {1} balíÄku {2} + + + + A version higher than {0} of package {1} + Verze vyšší než {0} balíÄku {1} + + + + A version less than {0} of package {1} + Verze menší než {0} balíÄku {1} + + Skip NuGet package signing validation. NuGet signing validation is not available on Linux or macOS https://aka.ms/workloadskippackagevalidation . PÅ™eskoÄit ověřování podepisování balíÄku NuGet. Ověřování podepisování balíÄku NuGet není k dispozici na Linuxu nebo v macOS https://aka.ms/workloadskippackagevalidation. diff --git a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.de.xlf b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.de.xlf index 59eb4441ee9b..5dadf0d934f3 100644 --- a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.de.xlf +++ b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.de.xlf @@ -42,6 +42,31 @@ Die Überprüfung der NuGet-Paketsignatur wird übersprungen. + + A version of {0} of package {1} + Eine Version von "{0}" des Pakets "{1}" + + + + Version {0} of package {1} + Version "{0}" des Pakets "{1}" + + + + A version between {0} and {1} of package {2} + Eine Version zwischen "{0}" und "{1}" des Pakets "{2}" + + + + A version higher than {0} of package {1} + Eine Version, die höher als "{0}" des Pakets "{1}" ist. + + + + A version less than {0} of package {1} + Eine Version, die niedriger als "{0}" des Pakets "{1}" ist. + + Skip NuGet package signing validation. NuGet signing validation is not available on Linux or macOS https://aka.ms/workloadskippackagevalidation . Überprüfung der NuGet-Paketsignierung überspringen. Die Überprüfung der NuGet-Signierung ist auf Linux- oder macOS nicht verfügbar https://aka.ms/workloadskippackagevalidation. diff --git a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.es.xlf b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.es.xlf index 92ffef1b484e..cca9fc12e15c 100644 --- a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.es.xlf +++ b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.es.xlf @@ -42,6 +42,31 @@ Omitiendo la comprobación de la firma del paquete NuGet. + + A version of {0} of package {1} + Versión de {0} del paquete {1} + + + + Version {0} of package {1} + Versión {0} del paquete {1} + + + + A version between {0} and {1} of package {2} + Una versión entre {0} y {1} del paquete {2} + + + + A version higher than {0} of package {1} + Una versión superior a {0} del paquete {1} + + + + A version less than {0} of package {1} + Versión anterior a la {0} del paquete {1} + + Skip NuGet package signing validation. NuGet signing validation is not available on Linux or macOS https://aka.ms/workloadskippackagevalidation . Omitir la validación de firma del paquete NuGet. La validación de firma de NuGet no está disponible en Linux o macOS https://aka.ms/workloadskippackagevalidation. diff --git a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.fr.xlf b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.fr.xlf index 3dd83630d84a..2486bfba02c4 100644 --- a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.fr.xlf +++ b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.fr.xlf @@ -42,6 +42,31 @@ La vérification de la signature du package NuGet est ignorée. + + A version of {0} of package {1} + Une version de {0} du package {1} + + + + Version {0} of package {1} + Une version {0} du package {1} + + + + A version between {0} and {1} of package {2} + Une version comprise entre {0} et {1} du package {2} + + + + A version higher than {0} of package {1} + Une version supérieure à {0} du package {1} + + + + A version less than {0} of package {1} + Une version inférieure à {0} du package {1} + + Skip NuGet package signing validation. NuGet signing validation is not available on Linux or macOS https://aka.ms/workloadskippackagevalidation . Ignorer la validation de signature de package NuGet. La validation de signature NuGet n’est pas disponible sur Linux ou macOS https://aka.ms/workloadskippackagevalidation. diff --git a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.it.xlf b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.it.xlf index e62cebfc5365..f8cb87b2bd63 100644 --- a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.it.xlf +++ b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.it.xlf @@ -42,6 +42,31 @@ La verifica della firma del pacchetto NuGet verrà ignorata. + + A version of {0} of package {1} + Versione di {0} del pacchetto {1} + + + + Version {0} of package {1} + Versione {0} del pacchetto {1} + + + + A version between {0} and {1} of package {2} + Versione compresa tra {0} e {1} del pacchetto {2} + + + + A version higher than {0} of package {1} + Versione superiore a {0} del pacchetto {1} + + + + A version less than {0} of package {1} + Versione inferiore a {0} del pacchetto {1} + + Skip NuGet package signing validation. NuGet signing validation is not available on Linux or macOS https://aka.ms/workloadskippackagevalidation . Ignorare la convalida della firma del pacchetto NuGet. La convalida della firma NuGet non è disponibile in Linux o macOS https://aka.ms/workloadskippackagevalidation. diff --git a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.ja.xlf b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.ja.xlf index 2f19c3d49013..9c05afb0a3b4 100644 --- a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.ja.xlf +++ b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.ja.xlf @@ -42,6 +42,31 @@ NuGet パッケージ署åã®èªè¨¼ã‚’スキップã—ã¦ã„ã¾ã™ã€‚ + + A version of {0} of package {1} + パッケージ {1} ã® {0} ã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ + + + + Version {0} of package {1} + パッケージ {1} ã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ {0} + + + + A version between {0} and {1} of package {2} + パッケージ {2} ã® {0} 㨠{1} ã®é–“ã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ + + + + A version higher than {0} of package {1} + パッケージ {1} ã® {0} より上ä½ã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ + + + + A version less than {0} of package {1} + パッケージ {1} ã® {0} 未満ã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ + + Skip NuGet package signing validation. NuGet signing validation is not available on Linux or macOS https://aka.ms/workloadskippackagevalidation . NuGet パッケージ署åã®æ¤œè¨¼ã‚’スキップã—ã¾ã™ã€‚NuGet ç½²åã®æ¤œè¨¼ã¯ã€Linux ã¾ãŸã¯ macOS https://aka.ms/workloadskippackagevalidation ã§ã¯ä½¿ç”¨ã§ãã¾ã›ã‚“。 diff --git a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.ko.xlf b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.ko.xlf index 7510fae557a0..4001ca7986f8 100644 --- a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.ko.xlf +++ b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.ko.xlf @@ -42,6 +42,31 @@ NuGet 패키지 서명 확ì¸ì„ 건너뛰는 중입니다. + + A version of {0} of package {1} + {0}ì˜ ë²„ì „ì˜ íŒ¨í‚¤ì§€ {1} + + + + Version {0} of package {1} + 버전 {0}ì˜ íŒ¨í‚¤ì§€ {1} + + + + A version between {0} and {1} of package {2} + {0} ë° {1} 사ì´ì˜ ë²„ì „ì˜ íŒ¨í‚¤ì§€ {2} + + + + A version higher than {0} of package {1} + {0}보다 ë†’ì€ ë²„ì „ì˜ íŒ¨í‚¤ì§€ {1} + + + + A version less than {0} of package {1} + {0}보다 ë‚®ì€ ë²„ì „ì˜ íŒ¨í‚¤ì§€ {1} + + Skip NuGet package signing validation. NuGet signing validation is not available on Linux or macOS https://aka.ms/workloadskippackagevalidation . NuGet 패키지 서명 유효성 검사를 건너ëœë‹ˆë‹¤. NuGet 서명 유효성 검사는 Linux ë˜ëŠ” macOS https://aka.ms/workloadskippackagevalidationì—서 사용할 수 없습니다. diff --git a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.pl.xlf b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.pl.xlf index 6dc11d30cf84..c37e1a4a00e0 100644 --- a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.pl.xlf +++ b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.pl.xlf @@ -42,6 +42,31 @@ Pomijanie weryfikacji podpisu pakietu NuGet. + + A version of {0} of package {1} + Wersja {0} pakietu {1} + + + + Version {0} of package {1} + Wersja {0} pakietu {1} + + + + A version between {0} and {1} of package {2} + Wersja miÄ™dzy {0} i {1} pakietu {2} + + + + A version higher than {0} of package {1} + Wersja nowsza niż {0} pakietu {1} + + + + A version less than {0} of package {1} + Wersja mniejsza niż {0} pakietu {1} + + Skip NuGet package signing validation. NuGet signing validation is not available on Linux or macOS https://aka.ms/workloadskippackagevalidation . PomiÅ„ weryfikacjÄ™ podpisywania pakietu NuGet. Sprawdzanie podpisywania NuGet nie jest dostÄ™pne w systemie Linux ani MacOS https://aka.ms/workloadskippackagevalidation . diff --git a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.pt-BR.xlf b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.pt-BR.xlf index 4cd6fd064461..1e7289a6ecb5 100644 --- a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.pt-BR.xlf +++ b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.pt-BR.xlf @@ -42,6 +42,31 @@ Ignorando a verificação de assinatura do pacote NuGet. + + A version of {0} of package {1} + Uma versão do {0} do pacote {1} + + + + Version {0} of package {1} + Versão {0} do pacote {1} + + + + A version between {0} and {1} of package {2} + Uma versão entre {0} e {1} do pacote {2} + + + + A version higher than {0} of package {1} + Uma versão superior a {0} do pacote {1} + + + + A version less than {0} of package {1} + Uma versão anterior a {0} do pacote {1} + + Skip NuGet package signing validation. NuGet signing validation is not available on Linux or macOS https://aka.ms/workloadskippackagevalidation . Ignorar a validação da assinatura do pacote NuGet. A validação da assinatura do NuGet não está disponível no Linux ou macOS https://aka.ms/workloadskippackagevalidation . diff --git a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.ru.xlf b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.ru.xlf index 7a8e0b3d4166..b94dffd62b7a 100644 --- a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.ru.xlf +++ b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.ru.xlf @@ -42,6 +42,31 @@ ПропуÑк проверки подпиÑи пакета NuGet. + + A version of {0} of package {1} + ВерÑÐ¸Ñ {0} пакета {1} + + + + Version {0} of package {1} + ВерÑÐ¸Ñ {0} пакета {1} + + + + A version between {0} and {1} of package {2} + ВерÑÐ¸Ñ Ð¾Ñ‚ {0} до {1} пакета {2} + + + + A version higher than {0} of package {1} + ВерÑÐ¸Ñ Ð¿Ð°ÐºÐµÑ‚Ð° {1} выше {0} + + + + A version less than {0} of package {1} + ВерÑÐ¸Ñ Ð¿Ð°ÐºÐµÑ‚Ð° {1} ниже {0} + + Skip NuGet package signing validation. NuGet signing validation is not available on Linux or macOS https://aka.ms/workloadskippackagevalidation . ПропуÑтить проверку подпиÑи пакета NuGet. Проверка подпиÑи NuGet недоÑтупна в Linux или macOS https://aka.ms/workloadskippackagevalidation. diff --git a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.tr.xlf b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.tr.xlf index 449b304962f6..fd6d4ff5e9a4 100644 --- a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.tr.xlf +++ b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.tr.xlf @@ -42,6 +42,31 @@ NuGet paket imzası doÄŸrulaması atlanıyor. + + A version of {0} of package {1} + {1} paketinin bir {0} sürümü + + + + Version {0} of package {1} + {1} paketinin {0} sürümü + + + + A version between {0} and {1} of package {2} + {2} paketinin {0} ve {1} arasındaki bir sürümü + + + + A version higher than {0} of package {1} + {1} paketinin {0} sürümünden yüksek bir sürümü + + + + A version less than {0} of package {1} + {1} paketinin {0} sürümünden düşük bir sürümü + + Skip NuGet package signing validation. NuGet signing validation is not available on Linux or macOS https://aka.ms/workloadskippackagevalidation . NuGet paketi imza doÄŸrulamasını atlayın. NuGet imza doÄŸrulaması Linux veya macOS üzerinde kullanılamıyor. Daha fazla bilgi için bkz. https://aka.ms/workloadskippackagevalidation. diff --git a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.zh-Hans.xlf b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.zh-Hans.xlf index d8fa4715311c..2df9a0fe0842 100644 --- a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.zh-Hans.xlf +++ b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.zh-Hans.xlf @@ -42,6 +42,31 @@ 正在跳过 NuGet 包签å验è¯ã€‚ + + A version of {0} of package {1} + 包 {1} 的版本 {0} + + + + Version {0} of package {1} + 包 {1} 的版本 {0} + + + + A version between {0} and {1} of package {2} + 包 {2} çš„ {0} å’Œ {1} 之间的版本 + + + + A version higher than {0} of package {1} + 包 {1} 的高于 {0} 的版本 + + + + A version less than {0} of package {1} + 包 {1} 的低于 {0} 的版本 + + Skip NuGet package signing validation. NuGet signing validation is not available on Linux or macOS https://aka.ms/workloadskippackagevalidation . 跳过 NuGet 包签å验è¯ã€‚Linux 或 macOS ä¸Šä¸æä¾› NuGet ç­¾å验è¯ã€‚https://aka.ms/workloadskippackagevalidation。 diff --git a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.zh-Hant.xlf b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.zh-Hant.xlf index ddafb653bcaf..ff999afa8383 100644 --- a/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.zh-Hant.xlf +++ b/src/Cli/dotnet/NugetPackageDownloader/xlf/LocalizableStrings.zh-Hant.xlf @@ -42,6 +42,31 @@ æ­£åœ¨ç•¥éŽ NuGet 套件簽章驗證。 + + A version of {0} of package {1} + å°è£ {1} 的版本 {0} + + + + Version {0} of package {1} + å°è£ {1} 的版本 {0} + + + + A version between {0} and {1} of package {2} + å°è£ {2} çš„ {0} 與 {1} 之間的版本 + + + + A version higher than {0} of package {1} + å°è£ {1} 高於 {0} 的版本 + + + + A version less than {0} of package {1} + å°è£ {1} å°æ–¼ {0} 的版本 + + Skip NuGet package signing validation. NuGet signing validation is not available on Linux or macOS https://aka.ms/workloadskippackagevalidation . ç•¥éŽ NuGet 套件簽署驗證。NuGet 套件簽署驗證在 Linux 或 macOS 上無法使用 https://aka.ms/workloadskippackagevalidation。 diff --git a/src/Cli/dotnet/ParseResultExtensions.cs b/src/Cli/dotnet/ParseResultExtensions.cs index d7f48e207cf6..ff49d8d2bdff 100644 --- a/src/Cli/dotnet/ParseResultExtensions.cs +++ b/src/Cli/dotnet/ParseResultExtensions.cs @@ -122,16 +122,38 @@ public static string[] GetSubArguments(this string[] args) var subargs = args.ToList(); // Don't remove any arguments that are being passed to the app in dotnet run - var runArgs = subargs.Contains("--") ? subargs.GetRange(subargs.IndexOf("--"), subargs.Count() - subargs.IndexOf("--")) : new List(); - subargs = subargs.Contains("--") ? subargs.GetRange(0, subargs.IndexOf("--")) : subargs; + var dashDashIndex = subargs.IndexOf("--"); - subargs.RemoveAll(arg => DiagOption.Name.Equals(arg) || DiagOption.Aliases.Contains(arg)); - if (subargs[0].Equals("dotnet")) + var runArgs = dashDashIndex > -1 ? subargs.GetRange(dashDashIndex, subargs.Count() - dashDashIndex) : new List(0); + subargs = dashDashIndex > -1 ? subargs.GetRange(0, dashDashIndex) : subargs; + + return subargs + .SkipWhile(arg => DiagOption.Name.Equals(arg) || DiagOption.Aliases.Contains(arg) || arg.Equals("dotnet")) + .Skip(1) // remove top level command (ex build or publish) + .Concat(runArgs) + .ToArray(); + } + + public static bool DiagOptionPrecedesSubcommand(this string[] args, string subCommand) + { + if (string.IsNullOrEmpty(subCommand)) { - subargs.RemoveAt(0); + return true; } - subargs.RemoveAt(0); // remove top level command (ex build or publish) - return subargs.Concat(runArgs).ToArray(); + + for (var i = 0; i < args.Length; i++) + { + if (args[i].Equals(subCommand)) + { + return false; + } + else if (DiagOption.Name.Equals(args) || DiagOption.Aliases.Contains(args[i])) + { + return true; + } + } + + return false; } private static string GetSymbolResultValue(ParseResult parseResult, SymbolResult symbolResult) diff --git a/src/Cli/dotnet/Parser.cs b/src/Cli/dotnet/Parser.cs index 12da079c7f53..1a19ef71612c 100644 --- a/src/Cli/dotnet/Parser.cs +++ b/src/Cli/dotnet/Parser.cs @@ -38,6 +38,7 @@ public static class Parser NewCommandParser.GetCommand(), NuGetCommandParser.GetCommand(), PackCommandParser.GetCommand(), + PackageCommandParser.GetCommand(), ParseCommandParser.GetCommand(), PublishCommandParser.GetCommand(), RemoveCommandParser.GetCommand(), diff --git a/src/Cli/dotnet/Program.cs b/src/Cli/dotnet/Program.cs index 72307655b8af..3f178fda6490 100644 --- a/src/Cli/dotnet/Program.cs +++ b/src/Cli/dotnet/Program.cs @@ -142,9 +142,14 @@ internal static int ProcessArgs(string[] args, TimeSpan startupTime, ITelemetry ToolPathSentinelFileName))); if (parseResult.GetValue(Parser.DiagOption) && parseResult.IsDotnetBuiltInCommand()) { - Environment.SetEnvironmentVariable(CommandLoggingContext.Variables.Verbose, bool.TrueString); - CommandLoggingContext.SetVerbose(true); - Reporter.Reset(); + // We found --diagnostic or -d, but we still need to determine whether the option should + // be attached to the dotnet command or the subcommand. + if (args.DiagOptionPrecedesSubcommand(parseResult.RootSubCommandResult())) + { + Environment.SetEnvironmentVariable(CommandLoggingContext.Variables.Verbose, bool.TrueString); + CommandLoggingContext.SetVerbose(true); + Reporter.Reset(); + } } if (parseResult.HasOption(Parser.VersionOption) && parseResult.IsTopLevelDotnetCommand()) { @@ -237,17 +242,26 @@ internal static int ProcessArgs(string[] args, TimeSpan startupTime, ITelemetry else { PerformanceLogEventSource.Log.ExtensibleCommandResolverStart(); - var resolvedCommand = CommandFactoryUsingResolver.Create( - "dotnet-" + parseResult.GetValue(Parser.DotnetSubCommand), - args.GetSubArguments(), - FrameworkConstants.CommonFrameworks.NetStandardApp15); - PerformanceLogEventSource.Log.ExtensibleCommandResolverStop(); + try + { + var resolvedCommand = CommandFactoryUsingResolver.Create( + "dotnet-" + parseResult.GetValue(Parser.DotnetSubCommand), + args.GetSubArguments(), + FrameworkConstants.CommonFrameworks.NetStandardApp15); + PerformanceLogEventSource.Log.ExtensibleCommandResolverStop(); - PerformanceLogEventSource.Log.ExtensibleCommandStart(); - var result = resolvedCommand.Execute(); - PerformanceLogEventSource.Log.ExtensibleCommandStop(); + PerformanceLogEventSource.Log.ExtensibleCommandStart(); + var result = resolvedCommand.Execute(); + PerformanceLogEventSource.Log.ExtensibleCommandStop(); - exitCode = result.ExitCode; + exitCode = result.ExitCode; + } + catch (CommandUnknownException e) + { + Reporter.Error.WriteLine(e.Message.Red()); + Reporter.Output.WriteLine(e.InstructionMessage); + exitCode = 1; + } } PerformanceLogEventSource.Log.TelemetryClientFlushStart(); diff --git a/src/Cli/dotnet/ToolPackage/ToolPackageException.cs b/src/Cli/dotnet/ToolPackage/ToolPackageException.cs index 5617dbc72f20..0614f662482e 100644 --- a/src/Cli/dotnet/ToolPackage/ToolPackageException.cs +++ b/src/Cli/dotnet/ToolPackage/ToolPackageException.cs @@ -1,9 +1,10 @@ // 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.Cli.Utils; namespace Microsoft.DotNet.ToolPackage { - internal class ToolPackageException : Exception + internal class ToolPackageException : GracefulException { public ToolPackageException() { diff --git a/src/Cli/dotnet/commands/InstallingWorkloadCommand.cs b/src/Cli/dotnet/commands/InstallingWorkloadCommand.cs index 0d87f4f82e00..9e010dfb3632 100644 --- a/src/Cli/dotnet/commands/InstallingWorkloadCommand.cs +++ b/src/Cli/dotnet/commands/InstallingWorkloadCommand.cs @@ -3,8 +3,11 @@ using System.CommandLine; using System.IO; +using System.Linq; using System.Net.Http.Json; using System.Runtime.CompilerServices; +using System.Text.Json; +using System.Text.Json.Nodes; using Microsoft.Deployment.DotNet.Releases; using Microsoft.DotNet.Cli; using Microsoft.DotNet.Cli.NuGetPackageDownloader; @@ -88,38 +91,10 @@ public InstallingWorkloadCommand( _workloadManifestUpdaterFromConstructor = workloadManifestUpdater; } - protected internal void UpdateInstallState(bool createDefaultJson, IEnumerable manifestVersionUpdates) - { - var defaultJsonPath = Path.Combine(WorkloadInstallType.GetInstallStateFolder(_sdkFeatureBand, _dotnetPath), "default.json"); - if (createDefaultJson) - { - var jsonContents = WorkloadSet.FromManifests( + protected static Dictionary GetInstallStateContents(IEnumerable manifestVersionUpdates) => + WorkloadSet.FromManifests( manifestVersionUpdates.Select(update => new WorkloadManifestInfo(update.ManifestId.ToString(), update.NewVersion.ToString(), /* We don't actually use the directory here */ string.Empty, update.NewFeatureBand)) ).ToDictionaryForJson(); - Directory.CreateDirectory(Path.GetDirectoryName(defaultJsonPath)); - File.WriteAllLines(defaultJsonPath, ToJsonEnumerable(jsonContents)); - } - else - { - if (File.Exists(defaultJsonPath)) - { - File.Delete(defaultJsonPath); - } - } - } - - private IEnumerable ToJsonEnumerable(Dictionary dict) - { - yield return "{"; - yield return "\"manifests\": {"; - foreach (KeyValuePair line in dict) - { - yield return $"\"{line.Key}\": \"{line.Value}\","; - } - yield return "}"; - yield return "}"; - yield break; - } protected async Task> GetDownloads(IEnumerable workloadIds, bool skipManifestUpdate, bool includePreview, string downloadFolder = null) { @@ -221,6 +196,12 @@ protected IEnumerable GetInstalledWorkloads(bool fromPreviousSdk) internal static class InstallingWorkloadCommandParser { + public static readonly CliOption WorkloadSetMode = new("--mode") + { + Description = Strings.WorkloadSetMode, + Hidden = true + }; + public static readonly CliOption PrintDownloadLinkOnlyOption = new("--print-download-link-only") { Description = Strings.PrintDownloadLinkOnlyDescription, diff --git a/src/Cli/dotnet/commands/dotnet-package/PackageCommandParser.cs b/src/Cli/dotnet/commands/dotnet-package/PackageCommandParser.cs new file mode 100644 index 000000000000..63bf74e8faa3 --- /dev/null +++ b/src/Cli/dotnet/commands/dotnet-package/PackageCommandParser.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 System.CommandLine; + +namespace Microsoft.DotNet.Cli +{ + internal class PackageCommandParser + { + private const string DocsLink = "https://aka.ms/dotnet-package"; + + public static CliCommand GetCommand() + { + CliCommand command = new DocumentedCommand("package", DocsLink); + command.SetAction((parseResult) => parseResult.HandleMissingCommand()); + command.Subcommands.Add(PackageSearchCommandParser.GetCommand()); + + return command; + } + } +} diff --git a/src/Cli/dotnet/commands/dotnet-package/search/LocalizableStrings.resx b/src/Cli/dotnet/commands/dotnet-package/search/LocalizableStrings.resx new file mode 100644 index 000000000000..de09b2b0ad7f --- /dev/null +++ b/src/Cli/dotnet/commands/dotnet-package/search/LocalizableStrings.resx @@ -0,0 +1,174 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Searches one or more package sources for packages that match a search term. If no sources are specified, all sources defined in the NuGet.Config are used. + + + ConfigFile + + + The NuGet configuration file. If specified, only the settings from this file will be used. If not specified, the hierarchy of configuration files from the current directory will be used. For more information, see https://docs.microsoft.com/nuget/consume-packages/configuring-nuget-behavior + + + Require that the search term exactly match the name of the package. Causes `--take` and `--skip` options to be ignored. + + + Format + + + Format the output accordingly. Either `table`, or `json`. The default value is `table`. + + + Stop and wait for user input or action (for example to complete authentication). + + + Include prerelease packages. + + + SearchTerm + + + Search term to filter package names, descriptions, and tags. Used as a literal value. Example: `dotnet package search some.package`. See also `--exact-match`. + + + Skip + + + Number of results to skip, to allow pagination. Default 0. + + + Source + + + The package source to search. You can pass multiple `--source` options to search multiple package sources. Example: `--source https://api.nuget.org/v3/index.json`. + + + Take + + + Number of results to return. Default 20. + + + Verbosity + + + Display this amount of details in the output: `normal`, `minimal`, `detailed`. The default is `normal` + + \ No newline at end of file diff --git a/src/Cli/dotnet/commands/dotnet-package/search/PackageSearchCommand.cs b/src/Cli/dotnet/commands/dotnet-package/search/PackageSearchCommand.cs new file mode 100644 index 000000000000..f7a6d4800217 --- /dev/null +++ b/src/Cli/dotnet/commands/dotnet-package/search/PackageSearchCommand.cs @@ -0,0 +1,31 @@ +// 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.Tools.NuGet; +using System.CommandLine; + +namespace Microsoft.DotNet.Cli +{ + internal class PackageSearchCommand : CommandBase + { + public PackageSearchCommand(ParseResult parseResult) : base(parseResult) { } + + public override int Execute() + { + var args = new List + { + "package", + "search" + }; + + var searchArgument = _parseResult.GetValue(PackageSearchCommandParser.SearchTermArgument); + if (searchArgument != null) + { + args.Add(searchArgument); + } + + args.AddRange(_parseResult.OptionValuesToBeForwarded(PackageSearchCommandParser.GetCommand())); + return NuGetCommand.Run(args.ToArray()); + } + } +} diff --git a/src/Cli/dotnet/commands/dotnet-package/search/PackageSearchCommandParser.cs b/src/Cli/dotnet/commands/dotnet-package/search/PackageSearchCommandParser.cs new file mode 100644 index 000000000000..5bc951d470c4 --- /dev/null +++ b/src/Cli/dotnet/commands/dotnet-package/search/PackageSearchCommandParser.cs @@ -0,0 +1,105 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.CommandLine; +using LocalizableStrings = Microsoft.DotNet.Tools.Package.Search.LocalizableStrings; + +namespace Microsoft.DotNet.Cli +{ + internal static class PackageSearchCommandParser + { + public static readonly CliArgument SearchTermArgument = new CliArgument("SearchTerm") + { + HelpName = LocalizableStrings.SearchTermArgumentName, + Description = LocalizableStrings.SearchTermDescription, + Arity = ArgumentArity.ZeroOrOne + }; + + public static readonly CliOption Sources = new ForwardedOption>("--source") + { + Description = LocalizableStrings.SourceDescription, + HelpName = LocalizableStrings.SourceArgumentName + }.ForwardAsManyArgumentsEachPrefixedByOption("--source") + .AllowSingleArgPerToken(); + + public static readonly CliOption Take = new ForwardedOption("--take") + { + Description = LocalizableStrings.TakeDescription, + HelpName = LocalizableStrings.TakeArgumentName + }.ForwardAsSingle(o => $"--take:{o}"); + + public static readonly CliOption Skip = new ForwardedOption("--skip") + { + Description = LocalizableStrings.SkipDescription, + HelpName = LocalizableStrings.SkipArgumentName + }.ForwardAsSingle(o => $"--skip:{o}"); + + public static readonly CliOption ExactMatch = new ForwardedOption("--exact-match") + { + Description = LocalizableStrings.ExactMatchDescription + }.ForwardAs("--exact-match"); + + public static readonly CliOption Interactive = new ForwardedOption("--interactive") + { + Description = LocalizableStrings.InteractiveDescription + }.ForwardAs("--interactive"); + + public static readonly CliOption Prerelease = new ForwardedOption("--prerelease") + { + Description = LocalizableStrings.PrereleaseDescription + }.ForwardAs("--prerelease"); + + public static readonly CliOption ConfigFile = new ForwardedOption("--configfile") + { + Description = LocalizableStrings.ConfigFileDescription, + HelpName = LocalizableStrings.ConfigFileArgumentName + }.ForwardAsSingle(o => $"--configfile:{o}"); + + public static readonly CliOption Format = new ForwardedOption("--format") + { + Description = LocalizableStrings.FormatDescription, + HelpName = LocalizableStrings.FormatArgumentName + }.ForwardAsSingle(o => $"--format:{o}"); + + public static readonly CliOption Verbosity = new ForwardedOption("--verbosity") + { + Description = LocalizableStrings.VerbosityDescription, + HelpName = LocalizableStrings.VerbosityArgumentName + }.ForwardAsSingle(o => $"--verbosity:{o}"); + + private static readonly CliCommand Command = ConstructCommand(); + + public static CliCommand GetCommand() + { + return Command; + } + + private static CliCommand ConstructCommand() + { + CliCommand searchCommand = new("search", LocalizableStrings.CommandDescription); + + searchCommand.Arguments.Add(SearchTermArgument); + searchCommand.Options.Add(Sources); + searchCommand.Options.Add(Take); + searchCommand.Options.Add(Skip); + searchCommand.Options.Add(ExactMatch); + searchCommand.Options.Add(Interactive); + searchCommand.Options.Add(Prerelease); + searchCommand.Options.Add(ConfigFile); + searchCommand.Options.Add(Format); + searchCommand.Options.Add(Verbosity); + + searchCommand.SetAction((parseResult) => { + var command = new PackageSearchCommand(parseResult); + int exitCode = command.Execute(); + + if (exitCode == 1) + { + parseResult.ShowHelp(); + } + }); + + return searchCommand; + } + } +} diff --git a/src/Cli/dotnet/commands/dotnet-package/search/xlf/LocalizableStrings.cs.xlf b/src/Cli/dotnet/commands/dotnet-package/search/xlf/LocalizableStrings.cs.xlf new file mode 100644 index 000000000000..ab95f376c5ef --- /dev/null +++ b/src/Cli/dotnet/commands/dotnet-package/search/xlf/LocalizableStrings.cs.xlf @@ -0,0 +1,97 @@ + + + + + + Searches one or more package sources for packages that match a search term. If no sources are specified, all sources defined in the NuGet.Config are used. + Searches one or more package sources for packages that match a search term. If no sources are specified, all sources defined in the NuGet.Config are used. + + + + ConfigFile + ConfigFile + + + + The NuGet configuration file. If specified, only the settings from this file will be used. If not specified, the hierarchy of configuration files from the current directory will be used. For more information, see https://docs.microsoft.com/nuget/consume-packages/configuring-nuget-behavior + The NuGet configuration file. If specified, only the settings from this file will be used. If not specified, the hierarchy of configuration files from the current directory will be used. For more information, see https://docs.microsoft.com/nuget/consume-packages/configuring-nuget-behavior + + + + Require that the search term exactly match the name of the package. Causes `--take` and `--skip` options to be ignored. + Require that the search term exactly match the name of the package. Causes `--take` and `--skip` options to be ignored. + + + + Format + Format + + + + Format the output accordingly. Either `table`, or `json`. The default value is `table`. + Format the output accordingly. Either `table`, or `json`. The default value is `table`. + + + + Stop and wait for user input or action (for example to complete authentication). + Stop and wait for user input or action (for example to complete authentication). + + + + Include prerelease packages. + Include prerelease packages. + + + + SearchTerm + SearchTerm + + + + Search term to filter package names, descriptions, and tags. Used as a literal value. Example: `dotnet package search some.package`. See also `--exact-match`. + Search term to filter package names, descriptions, and tags. Used as a literal value. Example: `dotnet package search some.package`. See also `--exact-match`. + + + + Skip + Skip + + + + Number of results to skip, to allow pagination. Default 0. + Number of results to skip, to allow pagination. Default 0. + + + + Source + Source + + + + The package source to search. You can pass multiple `--source` options to search multiple package sources. Example: `--source https://api.nuget.org/v3/index.json`. + The package source to search. You can pass multiple `--source` options to search multiple package sources. Example: `--source https://api.nuget.org/v3/index.json`. + + + + Take + Take + + + + Number of results to return. Default 20. + Number of results to return. Default 20. + + + + Verbosity + Verbosity + + + + Display this amount of details in the output: `normal`, `minimal`, `detailed`. The default is `normal` + Display this amount of details in the output: `normal`, `minimal`, `detailed`. The default is `normal` + + + + + \ No newline at end of file diff --git a/src/Cli/dotnet/commands/dotnet-package/search/xlf/LocalizableStrings.de.xlf b/src/Cli/dotnet/commands/dotnet-package/search/xlf/LocalizableStrings.de.xlf new file mode 100644 index 000000000000..25c81c1bd851 --- /dev/null +++ b/src/Cli/dotnet/commands/dotnet-package/search/xlf/LocalizableStrings.de.xlf @@ -0,0 +1,97 @@ + + + + + + Searches one or more package sources for packages that match a search term. If no sources are specified, all sources defined in the NuGet.Config are used. + Searches one or more package sources for packages that match a search term. If no sources are specified, all sources defined in the NuGet.Config are used. + + + + ConfigFile + ConfigFile + + + + The NuGet configuration file. If specified, only the settings from this file will be used. If not specified, the hierarchy of configuration files from the current directory will be used. For more information, see https://docs.microsoft.com/nuget/consume-packages/configuring-nuget-behavior + The NuGet configuration file. If specified, only the settings from this file will be used. If not specified, the hierarchy of configuration files from the current directory will be used. For more information, see https://docs.microsoft.com/nuget/consume-packages/configuring-nuget-behavior + + + + Require that the search term exactly match the name of the package. Causes `--take` and `--skip` options to be ignored. + Require that the search term exactly match the name of the package. Causes `--take` and `--skip` options to be ignored. + + + + Format + Format + + + + Format the output accordingly. Either `table`, or `json`. The default value is `table`. + Format the output accordingly. Either `table`, or `json`. The default value is `table`. + + + + Stop and wait for user input or action (for example to complete authentication). + Stop and wait for user input or action (for example to complete authentication). + + + + Include prerelease packages. + Include prerelease packages. + + + + SearchTerm + SearchTerm + + + + Search term to filter package names, descriptions, and tags. Used as a literal value. Example: `dotnet package search some.package`. See also `--exact-match`. + Search term to filter package names, descriptions, and tags. Used as a literal value. Example: `dotnet package search some.package`. See also `--exact-match`. + + + + Skip + Skip + + + + Number of results to skip, to allow pagination. Default 0. + Number of results to skip, to allow pagination. Default 0. + + + + Source + Source + + + + The package source to search. You can pass multiple `--source` options to search multiple package sources. Example: `--source https://api.nuget.org/v3/index.json`. + The package source to search. You can pass multiple `--source` options to search multiple package sources. Example: `--source https://api.nuget.org/v3/index.json`. + + + + Take + Take + + + + Number of results to return. Default 20. + Number of results to return. Default 20. + + + + Verbosity + Verbosity + + + + Display this amount of details in the output: `normal`, `minimal`, `detailed`. The default is `normal` + Display this amount of details in the output: `normal`, `minimal`, `detailed`. The default is `normal` + + + + + \ No newline at end of file diff --git a/src/Cli/dotnet/commands/dotnet-package/search/xlf/LocalizableStrings.es.xlf b/src/Cli/dotnet/commands/dotnet-package/search/xlf/LocalizableStrings.es.xlf new file mode 100644 index 000000000000..e588d1659213 --- /dev/null +++ b/src/Cli/dotnet/commands/dotnet-package/search/xlf/LocalizableStrings.es.xlf @@ -0,0 +1,97 @@ + + + + + + Searches one or more package sources for packages that match a search term. If no sources are specified, all sources defined in the NuGet.Config are used. + Searches one or more package sources for packages that match a search term. If no sources are specified, all sources defined in the NuGet.Config are used. + + + + ConfigFile + ConfigFile + + + + The NuGet configuration file. If specified, only the settings from this file will be used. If not specified, the hierarchy of configuration files from the current directory will be used. For more information, see https://docs.microsoft.com/nuget/consume-packages/configuring-nuget-behavior + The NuGet configuration file. If specified, only the settings from this file will be used. If not specified, the hierarchy of configuration files from the current directory will be used. For more information, see https://docs.microsoft.com/nuget/consume-packages/configuring-nuget-behavior + + + + Require that the search term exactly match the name of the package. Causes `--take` and `--skip` options to be ignored. + Require that the search term exactly match the name of the package. Causes `--take` and `--skip` options to be ignored. + + + + Format + Format + + + + Format the output accordingly. Either `table`, or `json`. The default value is `table`. + Format the output accordingly. Either `table`, or `json`. The default value is `table`. + + + + Stop and wait for user input or action (for example to complete authentication). + Stop and wait for user input or action (for example to complete authentication). + + + + Include prerelease packages. + Include prerelease packages. + + + + SearchTerm + SearchTerm + + + + Search term to filter package names, descriptions, and tags. Used as a literal value. Example: `dotnet package search some.package`. See also `--exact-match`. + Search term to filter package names, descriptions, and tags. Used as a literal value. Example: `dotnet package search some.package`. See also `--exact-match`. + + + + Skip + Skip + + + + Number of results to skip, to allow pagination. Default 0. + Number of results to skip, to allow pagination. Default 0. + + + + Source + Source + + + + The package source to search. You can pass multiple `--source` options to search multiple package sources. Example: `--source https://api.nuget.org/v3/index.json`. + The package source to search. You can pass multiple `--source` options to search multiple package sources. Example: `--source https://api.nuget.org/v3/index.json`. + + + + Take + Take + + + + Number of results to return. Default 20. + Number of results to return. Default 20. + + + + Verbosity + Verbosity + + + + Display this amount of details in the output: `normal`, `minimal`, `detailed`. The default is `normal` + Display this amount of details in the output: `normal`, `minimal`, `detailed`. The default is `normal` + + + + + \ No newline at end of file diff --git a/src/Cli/dotnet/commands/dotnet-package/search/xlf/LocalizableStrings.fr.xlf b/src/Cli/dotnet/commands/dotnet-package/search/xlf/LocalizableStrings.fr.xlf new file mode 100644 index 000000000000..af84c66837d9 --- /dev/null +++ b/src/Cli/dotnet/commands/dotnet-package/search/xlf/LocalizableStrings.fr.xlf @@ -0,0 +1,97 @@ + + + + + + Searches one or more package sources for packages that match a search term. If no sources are specified, all sources defined in the NuGet.Config are used. + Searches one or more package sources for packages that match a search term. If no sources are specified, all sources defined in the NuGet.Config are used. + + + + ConfigFile + ConfigFile + + + + The NuGet configuration file. If specified, only the settings from this file will be used. If not specified, the hierarchy of configuration files from the current directory will be used. For more information, see https://docs.microsoft.com/nuget/consume-packages/configuring-nuget-behavior + The NuGet configuration file. If specified, only the settings from this file will be used. If not specified, the hierarchy of configuration files from the current directory will be used. For more information, see https://docs.microsoft.com/nuget/consume-packages/configuring-nuget-behavior + + + + Require that the search term exactly match the name of the package. Causes `--take` and `--skip` options to be ignored. + Require that the search term exactly match the name of the package. Causes `--take` and `--skip` options to be ignored. + + + + Format + Format + + + + Format the output accordingly. Either `table`, or `json`. The default value is `table`. + Format the output accordingly. Either `table`, or `json`. The default value is `table`. + + + + Stop and wait for user input or action (for example to complete authentication). + Stop and wait for user input or action (for example to complete authentication). + + + + Include prerelease packages. + Include prerelease packages. + + + + SearchTerm + SearchTerm + + + + Search term to filter package names, descriptions, and tags. Used as a literal value. Example: `dotnet package search some.package`. See also `--exact-match`. + Search term to filter package names, descriptions, and tags. Used as a literal value. Example: `dotnet package search some.package`. See also `--exact-match`. + + + + Skip + Skip + + + + Number of results to skip, to allow pagination. Default 0. + Number of results to skip, to allow pagination. Default 0. + + + + Source + Source + + + + The package source to search. You can pass multiple `--source` options to search multiple package sources. Example: `--source https://api.nuget.org/v3/index.json`. + The package source to search. You can pass multiple `--source` options to search multiple package sources. Example: `--source https://api.nuget.org/v3/index.json`. + + + + Take + Take + + + + Number of results to return. Default 20. + Number of results to return. Default 20. + + + + Verbosity + Verbosity + + + + Display this amount of details in the output: `normal`, `minimal`, `detailed`. The default is `normal` + Display this amount of details in the output: `normal`, `minimal`, `detailed`. The default is `normal` + + + + + \ No newline at end of file diff --git a/src/Cli/dotnet/commands/dotnet-package/search/xlf/LocalizableStrings.it.xlf b/src/Cli/dotnet/commands/dotnet-package/search/xlf/LocalizableStrings.it.xlf new file mode 100644 index 000000000000..d11f25fd0541 --- /dev/null +++ b/src/Cli/dotnet/commands/dotnet-package/search/xlf/LocalizableStrings.it.xlf @@ -0,0 +1,97 @@ + + + + + + Searches one or more package sources for packages that match a search term. If no sources are specified, all sources defined in the NuGet.Config are used. + Searches one or more package sources for packages that match a search term. If no sources are specified, all sources defined in the NuGet.Config are used. + + + + ConfigFile + ConfigFile + + + + The NuGet configuration file. If specified, only the settings from this file will be used. If not specified, the hierarchy of configuration files from the current directory will be used. For more information, see https://docs.microsoft.com/nuget/consume-packages/configuring-nuget-behavior + The NuGet configuration file. If specified, only the settings from this file will be used. If not specified, the hierarchy of configuration files from the current directory will be used. For more information, see https://docs.microsoft.com/nuget/consume-packages/configuring-nuget-behavior + + + + Require that the search term exactly match the name of the package. Causes `--take` and `--skip` options to be ignored. + Require that the search term exactly match the name of the package. Causes `--take` and `--skip` options to be ignored. + + + + Format + Format + + + + Format the output accordingly. Either `table`, or `json`. The default value is `table`. + Format the output accordingly. Either `table`, or `json`. The default value is `table`. + + + + Stop and wait for user input or action (for example to complete authentication). + Stop and wait for user input or action (for example to complete authentication). + + + + Include prerelease packages. + Include prerelease packages. + + + + SearchTerm + SearchTerm + + + + Search term to filter package names, descriptions, and tags. Used as a literal value. Example: `dotnet package search some.package`. See also `--exact-match`. + Search term to filter package names, descriptions, and tags. Used as a literal value. Example: `dotnet package search some.package`. See also `--exact-match`. + + + + Skip + Skip + + + + Number of results to skip, to allow pagination. Default 0. + Number of results to skip, to allow pagination. Default 0. + + + + Source + Source + + + + The package source to search. You can pass multiple `--source` options to search multiple package sources. Example: `--source https://api.nuget.org/v3/index.json`. + The package source to search. You can pass multiple `--source` options to search multiple package sources. Example: `--source https://api.nuget.org/v3/index.json`. + + + + Take + Take + + + + Number of results to return. Default 20. + Number of results to return. Default 20. + + + + Verbosity + Verbosity + + + + Display this amount of details in the output: `normal`, `minimal`, `detailed`. The default is `normal` + Display this amount of details in the output: `normal`, `minimal`, `detailed`. The default is `normal` + + + + + \ No newline at end of file diff --git a/src/Cli/dotnet/commands/dotnet-package/search/xlf/LocalizableStrings.ja.xlf b/src/Cli/dotnet/commands/dotnet-package/search/xlf/LocalizableStrings.ja.xlf new file mode 100644 index 000000000000..11c4cb28b221 --- /dev/null +++ b/src/Cli/dotnet/commands/dotnet-package/search/xlf/LocalizableStrings.ja.xlf @@ -0,0 +1,97 @@ + + + + + + Searches one or more package sources for packages that match a search term. If no sources are specified, all sources defined in the NuGet.Config are used. + Searches one or more package sources for packages that match a search term. If no sources are specified, all sources defined in the NuGet.Config are used. + + + + ConfigFile + ConfigFile + + + + The NuGet configuration file. If specified, only the settings from this file will be used. If not specified, the hierarchy of configuration files from the current directory will be used. For more information, see https://docs.microsoft.com/nuget/consume-packages/configuring-nuget-behavior + The NuGet configuration file. If specified, only the settings from this file will be used. If not specified, the hierarchy of configuration files from the current directory will be used. For more information, see https://docs.microsoft.com/nuget/consume-packages/configuring-nuget-behavior + + + + Require that the search term exactly match the name of the package. Causes `--take` and `--skip` options to be ignored. + Require that the search term exactly match the name of the package. Causes `--take` and `--skip` options to be ignored. + + + + Format + Format + + + + Format the output accordingly. Either `table`, or `json`. The default value is `table`. + Format the output accordingly. Either `table`, or `json`. The default value is `table`. + + + + Stop and wait for user input or action (for example to complete authentication). + Stop and wait for user input or action (for example to complete authentication). + + + + Include prerelease packages. + Include prerelease packages. + + + + SearchTerm + SearchTerm + + + + Search term to filter package names, descriptions, and tags. Used as a literal value. Example: `dotnet package search some.package`. See also `--exact-match`. + Search term to filter package names, descriptions, and tags. Used as a literal value. Example: `dotnet package search some.package`. See also `--exact-match`. + + + + Skip + Skip + + + + Number of results to skip, to allow pagination. Default 0. + Number of results to skip, to allow pagination. Default 0. + + + + Source + Source + + + + The package source to search. You can pass multiple `--source` options to search multiple package sources. Example: `--source https://api.nuget.org/v3/index.json`. + The package source to search. You can pass multiple `--source` options to search multiple package sources. Example: `--source https://api.nuget.org/v3/index.json`. + + + + Take + Take + + + + Number of results to return. Default 20. + Number of results to return. Default 20. + + + + Verbosity + Verbosity + + + + Display this amount of details in the output: `normal`, `minimal`, `detailed`. The default is `normal` + Display this amount of details in the output: `normal`, `minimal`, `detailed`. The default is `normal` + + + + + \ No newline at end of file diff --git a/src/Cli/dotnet/commands/dotnet-package/search/xlf/LocalizableStrings.ko.xlf b/src/Cli/dotnet/commands/dotnet-package/search/xlf/LocalizableStrings.ko.xlf new file mode 100644 index 000000000000..2920635a01c3 --- /dev/null +++ b/src/Cli/dotnet/commands/dotnet-package/search/xlf/LocalizableStrings.ko.xlf @@ -0,0 +1,97 @@ + + + + + + Searches one or more package sources for packages that match a search term. If no sources are specified, all sources defined in the NuGet.Config are used. + Searches one or more package sources for packages that match a search term. If no sources are specified, all sources defined in the NuGet.Config are used. + + + + ConfigFile + ConfigFile + + + + The NuGet configuration file. If specified, only the settings from this file will be used. If not specified, the hierarchy of configuration files from the current directory will be used. For more information, see https://docs.microsoft.com/nuget/consume-packages/configuring-nuget-behavior + The NuGet configuration file. If specified, only the settings from this file will be used. If not specified, the hierarchy of configuration files from the current directory will be used. For more information, see https://docs.microsoft.com/nuget/consume-packages/configuring-nuget-behavior + + + + Require that the search term exactly match the name of the package. Causes `--take` and `--skip` options to be ignored. + Require that the search term exactly match the name of the package. Causes `--take` and `--skip` options to be ignored. + + + + Format + Format + + + + Format the output accordingly. Either `table`, or `json`. The default value is `table`. + Format the output accordingly. Either `table`, or `json`. The default value is `table`. + + + + Stop and wait for user input or action (for example to complete authentication). + Stop and wait for user input or action (for example to complete authentication). + + + + Include prerelease packages. + Include prerelease packages. + + + + SearchTerm + SearchTerm + + + + Search term to filter package names, descriptions, and tags. Used as a literal value. Example: `dotnet package search some.package`. See also `--exact-match`. + Search term to filter package names, descriptions, and tags. Used as a literal value. Example: `dotnet package search some.package`. See also `--exact-match`. + + + + Skip + Skip + + + + Number of results to skip, to allow pagination. Default 0. + Number of results to skip, to allow pagination. Default 0. + + + + Source + Source + + + + The package source to search. You can pass multiple `--source` options to search multiple package sources. Example: `--source https://api.nuget.org/v3/index.json`. + The package source to search. You can pass multiple `--source` options to search multiple package sources. Example: `--source https://api.nuget.org/v3/index.json`. + + + + Take + Take + + + + Number of results to return. Default 20. + Number of results to return. Default 20. + + + + Verbosity + Verbosity + + + + Display this amount of details in the output: `normal`, `minimal`, `detailed`. The default is `normal` + Display this amount of details in the output: `normal`, `minimal`, `detailed`. The default is `normal` + + + + + \ No newline at end of file diff --git a/src/Cli/dotnet/commands/dotnet-package/search/xlf/LocalizableStrings.pl.xlf b/src/Cli/dotnet/commands/dotnet-package/search/xlf/LocalizableStrings.pl.xlf new file mode 100644 index 000000000000..a9051a7b47fc --- /dev/null +++ b/src/Cli/dotnet/commands/dotnet-package/search/xlf/LocalizableStrings.pl.xlf @@ -0,0 +1,97 @@ + + + + + + Searches one or more package sources for packages that match a search term. If no sources are specified, all sources defined in the NuGet.Config are used. + Searches one or more package sources for packages that match a search term. If no sources are specified, all sources defined in the NuGet.Config are used. + + + + ConfigFile + ConfigFile + + + + The NuGet configuration file. If specified, only the settings from this file will be used. If not specified, the hierarchy of configuration files from the current directory will be used. For more information, see https://docs.microsoft.com/nuget/consume-packages/configuring-nuget-behavior + The NuGet configuration file. If specified, only the settings from this file will be used. If not specified, the hierarchy of configuration files from the current directory will be used. For more information, see https://docs.microsoft.com/nuget/consume-packages/configuring-nuget-behavior + + + + Require that the search term exactly match the name of the package. Causes `--take` and `--skip` options to be ignored. + Require that the search term exactly match the name of the package. Causes `--take` and `--skip` options to be ignored. + + + + Format + Format + + + + Format the output accordingly. Either `table`, or `json`. The default value is `table`. + Format the output accordingly. Either `table`, or `json`. The default value is `table`. + + + + Stop and wait for user input or action (for example to complete authentication). + Stop and wait for user input or action (for example to complete authentication). + + + + Include prerelease packages. + Include prerelease packages. + + + + SearchTerm + SearchTerm + + + + Search term to filter package names, descriptions, and tags. Used as a literal value. Example: `dotnet package search some.package`. See also `--exact-match`. + Search term to filter package names, descriptions, and tags. Used as a literal value. Example: `dotnet package search some.package`. See also `--exact-match`. + + + + Skip + Skip + + + + Number of results to skip, to allow pagination. Default 0. + Number of results to skip, to allow pagination. Default 0. + + + + Source + Source + + + + The package source to search. You can pass multiple `--source` options to search multiple package sources. Example: `--source https://api.nuget.org/v3/index.json`. + The package source to search. You can pass multiple `--source` options to search multiple package sources. Example: `--source https://api.nuget.org/v3/index.json`. + + + + Take + Take + + + + Number of results to return. Default 20. + Number of results to return. Default 20. + + + + Verbosity + Verbosity + + + + Display this amount of details in the output: `normal`, `minimal`, `detailed`. The default is `normal` + Display this amount of details in the output: `normal`, `minimal`, `detailed`. The default is `normal` + + + + + \ No newline at end of file diff --git a/src/Cli/dotnet/commands/dotnet-package/search/xlf/LocalizableStrings.pt-BR.xlf b/src/Cli/dotnet/commands/dotnet-package/search/xlf/LocalizableStrings.pt-BR.xlf new file mode 100644 index 000000000000..c5c8365f6a04 --- /dev/null +++ b/src/Cli/dotnet/commands/dotnet-package/search/xlf/LocalizableStrings.pt-BR.xlf @@ -0,0 +1,97 @@ + + + + + + Searches one or more package sources for packages that match a search term. If no sources are specified, all sources defined in the NuGet.Config are used. + Searches one or more package sources for packages that match a search term. If no sources are specified, all sources defined in the NuGet.Config are used. + + + + ConfigFile + ConfigFile + + + + The NuGet configuration file. If specified, only the settings from this file will be used. If not specified, the hierarchy of configuration files from the current directory will be used. For more information, see https://docs.microsoft.com/nuget/consume-packages/configuring-nuget-behavior + The NuGet configuration file. If specified, only the settings from this file will be used. If not specified, the hierarchy of configuration files from the current directory will be used. For more information, see https://docs.microsoft.com/nuget/consume-packages/configuring-nuget-behavior + + + + Require that the search term exactly match the name of the package. Causes `--take` and `--skip` options to be ignored. + Require that the search term exactly match the name of the package. Causes `--take` and `--skip` options to be ignored. + + + + Format + Format + + + + Format the output accordingly. Either `table`, or `json`. The default value is `table`. + Format the output accordingly. Either `table`, or `json`. The default value is `table`. + + + + Stop and wait for user input or action (for example to complete authentication). + Stop and wait for user input or action (for example to complete authentication). + + + + Include prerelease packages. + Include prerelease packages. + + + + SearchTerm + SearchTerm + + + + Search term to filter package names, descriptions, and tags. Used as a literal value. Example: `dotnet package search some.package`. See also `--exact-match`. + Search term to filter package names, descriptions, and tags. Used as a literal value. Example: `dotnet package search some.package`. See also `--exact-match`. + + + + Skip + Skip + + + + Number of results to skip, to allow pagination. Default 0. + Number of results to skip, to allow pagination. Default 0. + + + + Source + Source + + + + The package source to search. You can pass multiple `--source` options to search multiple package sources. Example: `--source https://api.nuget.org/v3/index.json`. + The package source to search. You can pass multiple `--source` options to search multiple package sources. Example: `--source https://api.nuget.org/v3/index.json`. + + + + Take + Take + + + + Number of results to return. Default 20. + Number of results to return. Default 20. + + + + Verbosity + Verbosity + + + + Display this amount of details in the output: `normal`, `minimal`, `detailed`. The default is `normal` + Display this amount of details in the output: `normal`, `minimal`, `detailed`. The default is `normal` + + + + + \ No newline at end of file diff --git a/src/Cli/dotnet/commands/dotnet-package/search/xlf/LocalizableStrings.ru.xlf b/src/Cli/dotnet/commands/dotnet-package/search/xlf/LocalizableStrings.ru.xlf new file mode 100644 index 000000000000..46b1600c3a28 --- /dev/null +++ b/src/Cli/dotnet/commands/dotnet-package/search/xlf/LocalizableStrings.ru.xlf @@ -0,0 +1,97 @@ + + + + + + Searches one or more package sources for packages that match a search term. If no sources are specified, all sources defined in the NuGet.Config are used. + Searches one or more package sources for packages that match a search term. If no sources are specified, all sources defined in the NuGet.Config are used. + + + + ConfigFile + ConfigFile + + + + The NuGet configuration file. If specified, only the settings from this file will be used. If not specified, the hierarchy of configuration files from the current directory will be used. For more information, see https://docs.microsoft.com/nuget/consume-packages/configuring-nuget-behavior + The NuGet configuration file. If specified, only the settings from this file will be used. If not specified, the hierarchy of configuration files from the current directory will be used. For more information, see https://docs.microsoft.com/nuget/consume-packages/configuring-nuget-behavior + + + + Require that the search term exactly match the name of the package. Causes `--take` and `--skip` options to be ignored. + Require that the search term exactly match the name of the package. Causes `--take` and `--skip` options to be ignored. + + + + Format + Format + + + + Format the output accordingly. Either `table`, or `json`. The default value is `table`. + Format the output accordingly. Either `table`, or `json`. The default value is `table`. + + + + Stop and wait for user input or action (for example to complete authentication). + Stop and wait for user input or action (for example to complete authentication). + + + + Include prerelease packages. + Include prerelease packages. + + + + SearchTerm + SearchTerm + + + + Search term to filter package names, descriptions, and tags. Used as a literal value. Example: `dotnet package search some.package`. See also `--exact-match`. + Search term to filter package names, descriptions, and tags. Used as a literal value. Example: `dotnet package search some.package`. See also `--exact-match`. + + + + Skip + Skip + + + + Number of results to skip, to allow pagination. Default 0. + Number of results to skip, to allow pagination. Default 0. + + + + Source + Source + + + + The package source to search. You can pass multiple `--source` options to search multiple package sources. Example: `--source https://api.nuget.org/v3/index.json`. + The package source to search. You can pass multiple `--source` options to search multiple package sources. Example: `--source https://api.nuget.org/v3/index.json`. + + + + Take + Take + + + + Number of results to return. Default 20. + Number of results to return. Default 20. + + + + Verbosity + Verbosity + + + + Display this amount of details in the output: `normal`, `minimal`, `detailed`. The default is `normal` + Display this amount of details in the output: `normal`, `minimal`, `detailed`. The default is `normal` + + + + + \ No newline at end of file diff --git a/src/Cli/dotnet/commands/dotnet-package/search/xlf/LocalizableStrings.tr.xlf b/src/Cli/dotnet/commands/dotnet-package/search/xlf/LocalizableStrings.tr.xlf new file mode 100644 index 000000000000..4a383e5cda10 --- /dev/null +++ b/src/Cli/dotnet/commands/dotnet-package/search/xlf/LocalizableStrings.tr.xlf @@ -0,0 +1,97 @@ + + + + + + Searches one or more package sources for packages that match a search term. If no sources are specified, all sources defined in the NuGet.Config are used. + Searches one or more package sources for packages that match a search term. If no sources are specified, all sources defined in the NuGet.Config are used. + + + + ConfigFile + ConfigFile + + + + The NuGet configuration file. If specified, only the settings from this file will be used. If not specified, the hierarchy of configuration files from the current directory will be used. For more information, see https://docs.microsoft.com/nuget/consume-packages/configuring-nuget-behavior + The NuGet configuration file. If specified, only the settings from this file will be used. If not specified, the hierarchy of configuration files from the current directory will be used. For more information, see https://docs.microsoft.com/nuget/consume-packages/configuring-nuget-behavior + + + + Require that the search term exactly match the name of the package. Causes `--take` and `--skip` options to be ignored. + Require that the search term exactly match the name of the package. Causes `--take` and `--skip` options to be ignored. + + + + Format + Format + + + + Format the output accordingly. Either `table`, or `json`. The default value is `table`. + Format the output accordingly. Either `table`, or `json`. The default value is `table`. + + + + Stop and wait for user input or action (for example to complete authentication). + Stop and wait for user input or action (for example to complete authentication). + + + + Include prerelease packages. + Include prerelease packages. + + + + SearchTerm + SearchTerm + + + + Search term to filter package names, descriptions, and tags. Used as a literal value. Example: `dotnet package search some.package`. See also `--exact-match`. + Search term to filter package names, descriptions, and tags. Used as a literal value. Example: `dotnet package search some.package`. See also `--exact-match`. + + + + Skip + Skip + + + + Number of results to skip, to allow pagination. Default 0. + Number of results to skip, to allow pagination. Default 0. + + + + Source + Source + + + + The package source to search. You can pass multiple `--source` options to search multiple package sources. Example: `--source https://api.nuget.org/v3/index.json`. + The package source to search. You can pass multiple `--source` options to search multiple package sources. Example: `--source https://api.nuget.org/v3/index.json`. + + + + Take + Take + + + + Number of results to return. Default 20. + Number of results to return. Default 20. + + + + Verbosity + Verbosity + + + + Display this amount of details in the output: `normal`, `minimal`, `detailed`. The default is `normal` + Display this amount of details in the output: `normal`, `minimal`, `detailed`. The default is `normal` + + + + + \ No newline at end of file diff --git a/src/Cli/dotnet/commands/dotnet-package/search/xlf/LocalizableStrings.zh-Hans.xlf b/src/Cli/dotnet/commands/dotnet-package/search/xlf/LocalizableStrings.zh-Hans.xlf new file mode 100644 index 000000000000..dd4e698e80cd --- /dev/null +++ b/src/Cli/dotnet/commands/dotnet-package/search/xlf/LocalizableStrings.zh-Hans.xlf @@ -0,0 +1,97 @@ + + + + + + Searches one or more package sources for packages that match a search term. If no sources are specified, all sources defined in the NuGet.Config are used. + Searches one or more package sources for packages that match a search term. If no sources are specified, all sources defined in the NuGet.Config are used. + + + + ConfigFile + ConfigFile + + + + The NuGet configuration file. If specified, only the settings from this file will be used. If not specified, the hierarchy of configuration files from the current directory will be used. For more information, see https://docs.microsoft.com/nuget/consume-packages/configuring-nuget-behavior + The NuGet configuration file. If specified, only the settings from this file will be used. If not specified, the hierarchy of configuration files from the current directory will be used. For more information, see https://docs.microsoft.com/nuget/consume-packages/configuring-nuget-behavior + + + + Require that the search term exactly match the name of the package. Causes `--take` and `--skip` options to be ignored. + Require that the search term exactly match the name of the package. Causes `--take` and `--skip` options to be ignored. + + + + Format + Format + + + + Format the output accordingly. Either `table`, or `json`. The default value is `table`. + Format the output accordingly. Either `table`, or `json`. The default value is `table`. + + + + Stop and wait for user input or action (for example to complete authentication). + Stop and wait for user input or action (for example to complete authentication). + + + + Include prerelease packages. + Include prerelease packages. + + + + SearchTerm + SearchTerm + + + + Search term to filter package names, descriptions, and tags. Used as a literal value. Example: `dotnet package search some.package`. See also `--exact-match`. + Search term to filter package names, descriptions, and tags. Used as a literal value. Example: `dotnet package search some.package`. See also `--exact-match`. + + + + Skip + Skip + + + + Number of results to skip, to allow pagination. Default 0. + Number of results to skip, to allow pagination. Default 0. + + + + Source + Source + + + + The package source to search. You can pass multiple `--source` options to search multiple package sources. Example: `--source https://api.nuget.org/v3/index.json`. + The package source to search. You can pass multiple `--source` options to search multiple package sources. Example: `--source https://api.nuget.org/v3/index.json`. + + + + Take + Take + + + + Number of results to return. Default 20. + Number of results to return. Default 20. + + + + Verbosity + Verbosity + + + + Display this amount of details in the output: `normal`, `minimal`, `detailed`. The default is `normal` + Display this amount of details in the output: `normal`, `minimal`, `detailed`. The default is `normal` + + + + + \ No newline at end of file diff --git a/src/Cli/dotnet/commands/dotnet-package/search/xlf/LocalizableStrings.zh-Hant.xlf b/src/Cli/dotnet/commands/dotnet-package/search/xlf/LocalizableStrings.zh-Hant.xlf new file mode 100644 index 000000000000..f66a8fe3c625 --- /dev/null +++ b/src/Cli/dotnet/commands/dotnet-package/search/xlf/LocalizableStrings.zh-Hant.xlf @@ -0,0 +1,97 @@ + + + + + + Searches one or more package sources for packages that match a search term. If no sources are specified, all sources defined in the NuGet.Config are used. + Searches one or more package sources for packages that match a search term. If no sources are specified, all sources defined in the NuGet.Config are used. + + + + ConfigFile + ConfigFile + + + + The NuGet configuration file. If specified, only the settings from this file will be used. If not specified, the hierarchy of configuration files from the current directory will be used. For more information, see https://docs.microsoft.com/nuget/consume-packages/configuring-nuget-behavior + The NuGet configuration file. If specified, only the settings from this file will be used. If not specified, the hierarchy of configuration files from the current directory will be used. For more information, see https://docs.microsoft.com/nuget/consume-packages/configuring-nuget-behavior + + + + Require that the search term exactly match the name of the package. Causes `--take` and `--skip` options to be ignored. + Require that the search term exactly match the name of the package. Causes `--take` and `--skip` options to be ignored. + + + + Format + Format + + + + Format the output accordingly. Either `table`, or `json`. The default value is `table`. + Format the output accordingly. Either `table`, or `json`. The default value is `table`. + + + + Stop and wait for user input or action (for example to complete authentication). + Stop and wait for user input or action (for example to complete authentication). + + + + Include prerelease packages. + Include prerelease packages. + + + + SearchTerm + SearchTerm + + + + Search term to filter package names, descriptions, and tags. Used as a literal value. Example: `dotnet package search some.package`. See also `--exact-match`. + Search term to filter package names, descriptions, and tags. Used as a literal value. Example: `dotnet package search some.package`. See also `--exact-match`. + + + + Skip + Skip + + + + Number of results to skip, to allow pagination. Default 0. + Number of results to skip, to allow pagination. Default 0. + + + + Source + Source + + + + The package source to search. You can pass multiple `--source` options to search multiple package sources. Example: `--source https://api.nuget.org/v3/index.json`. + The package source to search. You can pass multiple `--source` options to search multiple package sources. Example: `--source https://api.nuget.org/v3/index.json`. + + + + Take + Take + + + + Number of results to return. Default 20. + Number of results to return. Default 20. + + + + Verbosity + Verbosity + + + + Display this amount of details in the output: `normal`, `minimal`, `detailed`. The default is `normal` + Display this amount of details in the output: `normal`, `minimal`, `detailed`. The default is `normal` + + + + + \ No newline at end of file diff --git a/src/Cli/dotnet/commands/dotnet-restore/RestoreCommandParser.cs b/src/Cli/dotnet/commands/dotnet-restore/RestoreCommandParser.cs index 0d08befe5c19..bce6bc18d5c7 100644 --- a/src/Cli/dotnet/commands/dotnet-restore/RestoreCommandParser.cs +++ b/src/Cli/dotnet/commands/dotnet-restore/RestoreCommandParser.cs @@ -80,9 +80,9 @@ public static void AddImplicitRestoreOptions(CliCommand command, bool showHelp = command.Options.Add(option); } } - private static string GetOsFromRid(string rid) => rid.Substring(0, rid.LastIndexOf("-")); - private static string GetArchFromRid(string rid) => rid.Substring(rid.LastIndexOf("-") + 1, rid.Length - rid.LastIndexOf("-") - 1); - public static string RestoreRuntimeArgFunc(IEnumerable rids) + private static string GetOsFromRid(string rid) => rid.Substring(0, rid.LastIndexOf("-", StringComparison.InvariantCulture)); + private static string GetArchFromRid(string rid) => rid.Substring(rid.LastIndexOf("-", StringComparison.InvariantCulture) + 1, rid.Length - rid.LastIndexOf("-", StringComparison.InvariantCulture) - 1); + public static string RestoreRuntimeArgFunc(IEnumerable rids) { List convertedRids = new(); foreach (string rid in rids) diff --git a/src/Cli/dotnet/commands/dotnet-test/TestCommandParser.cs b/src/Cli/dotnet/commands/dotnet-test/TestCommandParser.cs index 33d8e9437480..03da9ca92fb6 100644 --- a/src/Cli/dotnet/commands/dotnet-test/TestCommandParser.cs +++ b/src/Cli/dotnet/commands/dotnet-test/TestCommandParser.cs @@ -142,7 +142,7 @@ private static CliOption CreateBlameHangDumpOption() public static readonly CliOption NoLogoOption = new ForwardedOption("--nologo") { Description = LocalizableStrings.CmdNoLogo - }.ForwardAs("-property:VSTestNoLogo=nologo"); + }.ForwardAs("-property:VSTestNoLogo=true"); public static readonly CliOption NoRestoreOption = CommonOptions.NoRestoreOption; diff --git a/src/Cli/dotnet/commands/dotnet-tool/install/LocalizableStrings.resx b/src/Cli/dotnet/commands/dotnet-tool/install/LocalizableStrings.resx index cf87d1bac6c3..bacf2bd43283 100644 --- a/src/Cli/dotnet/commands/dotnet-tool/install/LocalizableStrings.resx +++ b/src/Cli/dotnet/commands/dotnet-tool/install/LocalizableStrings.resx @@ -235,4 +235,7 @@ If you would like to create a manifest, use `dotnet new tool-manifest`, usually Create a tool manifest if one isn't found during tool installation. For information on how manifests are located, see https://aka.ms/dotnet/tools/create-manifest-if-needed + + Allow package downgrade when installing a .NET tool package. + \ No newline at end of file diff --git a/src/Cli/dotnet/commands/dotnet-tool/install/ToolInstallCommandParser.cs b/src/Cli/dotnet/commands/dotnet-tool/install/ToolInstallCommandParser.cs index 4f79a5a2362e..32f93512db6d 100644 --- a/src/Cli/dotnet/commands/dotnet-tool/install/ToolInstallCommandParser.cs +++ b/src/Cli/dotnet/commands/dotnet-tool/install/ToolInstallCommandParser.cs @@ -48,6 +48,11 @@ internal static class ToolInstallCommandParser Description = LocalizableStrings.CreateManifestIfNeededOptionDescription }; + public static readonly CliOption AllowPackageDowngradeOption = new("--allow-downgrade") + { + Description = LocalizableStrings.AllowPackageDowngradeOptionDescription + }; + public static readonly CliOption VerbosityOption = CommonOptions.VerbosityOption; // Don't use the common options version as we don't want this to be a forwarded option @@ -77,6 +82,19 @@ private static CliCommand ConstructCommand() { CliCommand command = new("install", LocalizableStrings.CommandDescription); + AddCommandOptions(command); + + command.Options.Add(ArchitectureOption); + command.Options.Add(CreateManifestIfNeededOption); + command.Options.Add(AllowPackageDowngradeOption); + + command.SetAction((parseResult) => new ToolInstallCommand(parseResult).Execute()); + + return command; + } + + public static CliCommand AddCommandOptions(CliCommand command) + { command.Arguments.Add(PackageIdArgument); command.Options.Add(GlobalOption.WithHelpDescription(command, LocalizableStrings.GlobalOptionDescription)); command.Options.Add(LocalOption.WithHelpDescription(command, LocalizableStrings.LocalOptionDescription)); @@ -92,12 +110,7 @@ private static CliCommand ConstructCommand() command.Options.Add(ToolCommandRestorePassThroughOptions.NoCacheOption); command.Options.Add(ToolCommandRestorePassThroughOptions.InteractiveRestoreOption); command.Options.Add(VerbosityOption); - command.Options.Add(ArchitectureOption); - command.Options.Add(CreateManifestIfNeededOption); - - command.SetAction((parseResult) => new ToolInstallCommand(parseResult).Execute()); - return command; - } + } } } diff --git a/src/Cli/dotnet/commands/dotnet-tool/install/ToolInstallGlobalOrToolPathCommand.cs b/src/Cli/dotnet/commands/dotnet-tool/install/ToolInstallGlobalOrToolPathCommand.cs index a9cbfe8d1a63..e722b6af6c5e 100644 --- a/src/Cli/dotnet/commands/dotnet-tool/install/ToolInstallGlobalOrToolPathCommand.cs +++ b/src/Cli/dotnet/commands/dotnet-tool/install/ToolInstallGlobalOrToolPathCommand.cs @@ -11,10 +11,12 @@ using Microsoft.DotNet.ToolPackage; using Microsoft.DotNet.Tools.Tool.Common; using Microsoft.DotNet.Tools.Tool.Uninstall; +using Microsoft.DotNet.Tools.Tool.Update; using Microsoft.Extensions.EnvironmentAbstractions; using NuGet.Common; using NuGet.Frameworks; using NuGet.Versioning; +using static System.Formats.Asn1.AsnWriter; namespace Microsoft.DotNet.Tools.Tool.Install { @@ -30,7 +32,7 @@ internal class ToolInstallGlobalOrToolPathCommand : CommandBase private readonly IReporter _reporter; private readonly IReporter _errorReporter; private CreateShellShimRepository _createShellShimRepository; - private CreateToolPackageStoresAndDownloader _createToolPackageStoresAndDownloader; + private readonly CreateToolPackageStoresAndDownloaderAndUninstaller _createToolPackageStoreDownloaderUninstaller; private readonly ShellShimTemplateFinder _shellShimTemplateFinder; private readonly PackageId _packageId; @@ -43,10 +45,11 @@ internal class ToolInstallGlobalOrToolPathCommand : CommandBase private readonly string _toolPath; private readonly string _architectureOption; private IEnumerable _forwardRestoreArguments; + private readonly bool _allowPackageDowngrade; public ToolInstallGlobalOrToolPathCommand( ParseResult parseResult, - CreateToolPackageStoresAndDownloader createToolPackageStoreAndDownloader = null, + CreateToolPackageStoresAndDownloaderAndUninstaller createToolPackageStoreDownloaderUninstaller = null, CreateShellShimRepository createShellShimRepository = null, IEnvironmentPathInstruction environmentPathInstruction = null, IReporter reporter = null, @@ -63,8 +66,6 @@ public ToolInstallGlobalOrToolPathCommand( _toolPath = parseResult.GetValue(ToolAppliedOption.ToolPathOption); _architectureOption = parseResult.GetValue(ToolInstallCommandParser.ArchitectureOption); - _createToolPackageStoresAndDownloader = createToolPackageStoreAndDownloader ?? ToolPackageFactory.CreateToolPackageStoresAndDownloader; - _forwardRestoreArguments = parseResult.OptionValuesToBeForwarded(ToolInstallCommandParser.GetCommand()); _environmentPathInstruction = environmentPathInstruction @@ -80,6 +81,9 @@ public ToolInstallGlobalOrToolPathCommand( Interactive: parseResult.GetValue(ToolCommandRestorePassThroughOptions.InteractiveRestoreOption)); nugetPackageDownloader ??= new NuGetPackageDownloader(tempDir, verboseLogger: new NullLogger(), restoreActionConfig: restoreAction); _shellShimTemplateFinder = new ShellShimTemplateFinder(nugetPackageDownloader, tempDir, packageSourceLocation); + _allowPackageDowngrade = parseResult.GetValue(ToolInstallCommandParser.AllowPackageDowngradeOption); + _createToolPackageStoreDownloaderUninstaller = createToolPackageStoreDownloaderUninstaller ?? + ToolPackageFactory.CreateToolPackageStoresAndDownloaderAndUninstaller; _reporter = (reporter ?? Reporter.Output); _errorReporter = (reporter ?? Reporter.Error); @@ -87,15 +91,7 @@ public ToolInstallGlobalOrToolPathCommand( public override int Execute() { - if (!string.IsNullOrEmpty(_configFilePath) && !File.Exists(_configFilePath)) - { - throw new GracefulException( - string.Format( - LocalizableStrings.NuGetConfigurationFileDoesNotExist, - Path.GetFullPath(_configFilePath))); - } - - VersionRange versionRange = _parseResult.GetVersionRange(); + ValidateArguments(); DirectoryPath? toolPath = null; if (!string.IsNullOrEmpty(_toolPath)) @@ -103,31 +99,39 @@ public override int Execute() toolPath = new DirectoryPath(_toolPath); } - (IToolPackageStore toolPackageStore, IToolPackageStoreQuery toolPackageStoreQuery, IToolPackageDownloader toolPackageDownloader) = - _createToolPackageStoresAndDownloader(toolPath, _forwardRestoreArguments); + VersionRange versionRange = _parseResult.GetVersionRange(); - // Prevent installation if any version of the package is installed - if (toolPackageStoreQuery.EnumeratePackageVersions(_packageId).FirstOrDefault() != null) - { - _errorReporter.WriteLine(string.Format(LocalizableStrings.ToolAlreadyInstalled, _packageId).Red()); - return 1; - } + (IToolPackageStore toolPackageStore, + IToolPackageStoreQuery toolPackageStoreQuery, + IToolPackageDownloader toolPackageDownloader, + IToolPackageUninstaller toolPackageUninstaller) = _createToolPackageStoreDownloaderUninstaller(toolPath, _forwardRestoreArguments); - FilePath? configFile = null; - if (!string.IsNullOrEmpty(_configFilePath)) - { - configFile = new FilePath(_configFilePath); - } + var appHostSourceDirectory = ShellShimTemplateFinder.GetDefaultAppHostSourceDirectory(); + IShellShimRepository shellShimRepository = _createShellShimRepository(appHostSourceDirectory, toolPath); - try + IToolPackage oldPackageNullable = GetOldPackage(toolPackageStoreQuery); + + using (var scope = new TransactionScope( + TransactionScopeOption.Required, + TimeSpan.Zero)) { - IToolPackage package = null; - using (var scope = new TransactionScope( - TransactionScopeOption.Required, - TimeSpan.Zero)) + if (oldPackageNullable != null) { - package = toolPackageDownloader.InstallPackage( - new PackageLocation(nugetConfig: configFile, additionalFeeds: _source), + RunWithHandlingUninstallError(() => + { + foreach (RestoredCommand command in oldPackageNullable.Commands) + { + shellShimRepository.RemoveShim(command.Name); + } + + toolPackageUninstaller.Uninstall(oldPackageNullable.PackageDirectory); + }); + } + + RunWithHandlingInstallError(() => + { + IToolPackage newInstalledPackage = toolPackageDownloader.InstallPackage( + new PackageLocation(nugetConfig: GetConfigFile(), additionalFeeds: _source), packageId: _packageId, versionRange: versionRange, targetFramework: _framework, @@ -135,10 +139,12 @@ public override int Execute() isGlobalTool: true ); + EnsureVersionIsHigher(oldPackageNullable, newInstalledPackage, _allowPackageDowngrade); + NuGetFramework framework; - if (string.IsNullOrEmpty(_framework) && package.Frameworks.Count() > 0) + if (string.IsNullOrEmpty(_framework) && newInstalledPackage.Frameworks.Count() > 0) { - framework = package.Frameworks + framework = newInstalledPackage.Frameworks .Where(f => f.Version < (new NuGetVersion(Product.Version)).Version) .MaxBy(f => f.Version); } @@ -148,43 +154,170 @@ public override int Execute() null : NuGetFramework.Parse(_framework); } - string appHostSourceDirectory = _shellShimTemplateFinder.ResolveAppHostSourceDirectoryAsync(_architectureOption, framework, RuntimeInformation.ProcessArchitecture).Result; - IShellShimRepository shellShimRepository = _createShellShimRepository(appHostSourceDirectory, toolPath); - foreach (var command in package.Commands) + foreach (RestoredCommand command in newInstalledPackage.Commands) { - shellShimRepository.CreateShim(command.Executable, command.Name, package.PackagedShims); + shellShimRepository.CreateShim(command.Executable, command.Name, newInstalledPackage.PackagedShims); } - scope.Complete(); - } + foreach (string w in newInstalledPackage.Warnings) + { + _reporter.WriteLine(w.Yellow()); + } + if (_global) + { + _environmentPathInstruction.PrintAddPathInstructionIfPathDoesNotExist(); + } - foreach (string w in package.Warnings) - { - _reporter.WriteLine(w.Yellow()); - } + PrintSuccessMessage(oldPackageNullable, newInstalledPackage); + }); - if (_global) - { - _environmentPathInstruction.PrintAddPathInstructionIfPathDoesNotExist(); - } + scope.Complete(); + + } + return 0; + } + + private static void EnsureVersionIsHigher(IToolPackage oldPackageNullable, IToolPackage newInstalledPackage, bool allowDowngrade) + { + if (oldPackageNullable != null && (newInstalledPackage.Version < oldPackageNullable.Version && !allowDowngrade)) + { + throw new GracefulException( + new[] + { + string.Format(Update.LocalizableStrings.UpdateToLowerVersion, + newInstalledPackage.Version.ToNormalizedString(), + oldPackageNullable.Version.ToNormalizedString()) + }, + isUserError: false); + } + } - _reporter.WriteLine( + private void ValidateArguments() + { + if (!string.IsNullOrEmpty(_configFilePath) && !File.Exists(_configFilePath)) + { + throw new GracefulException( string.Format( - LocalizableStrings.InstallationSucceeded, - string.Join(", ", package.Commands.Select(c => c.Name)), - package.Id, - package.Version.ToNormalizedString()).Green()); - return 0; + LocalizableStrings.NuGetConfigurationFileDoesNotExist, + Path.GetFullPath(_configFilePath))); } - catch (Exception ex) when (InstallToolCommandLowLevelErrorConverter.ShouldConvertToUserFacingError(ex)) + } + + private void RunWithHandlingInstallError(Action installAction) + { + try + { + installAction(); + } + catch (Exception ex) + when (InstallToolCommandLowLevelErrorConverter.ShouldConvertToUserFacingError(ex)) + { + var message = new List + { + string.Format(Update.LocalizableStrings.UpdateToolFailed, _packageId) + }; + message.AddRange( + InstallToolCommandLowLevelErrorConverter.GetUserFacingMessages(ex, _packageId)); + + + throw new GracefulException( + messages: message, + verboseMessages: new[] { ex.ToString() }, + isUserError: false); + } + } + + private void RunWithHandlingUninstallError(Action uninstallAction) + { + try { + uninstallAction(); + } + catch (Exception ex) + when (ToolUninstallCommandLowLevelErrorConverter.ShouldConvertToUserFacingError(ex)) + { + var message = new List + { + string.Format(Update.LocalizableStrings.UpdateToolFailed, _packageId) + }; + message.AddRange( + ToolUninstallCommandLowLevelErrorConverter.GetUserFacingMessages(ex, _packageId)); + throw new GracefulException( - messages: InstallToolCommandLowLevelErrorConverter.GetUserFacingMessages(ex, _packageId), + messages: message, verboseMessages: new[] { ex.ToString() }, isUserError: false); } } + + private FilePath? GetConfigFile() + { + FilePath? configFile = null; + if (!string.IsNullOrEmpty(_configFilePath)) + { + configFile = new FilePath(_configFilePath); + } + + return configFile; + } + + private IToolPackage GetOldPackage(IToolPackageStoreQuery toolPackageStoreQuery) + { + IToolPackage oldPackageNullable; + try + { + oldPackageNullable = toolPackageStoreQuery.EnumeratePackageVersions(_packageId).SingleOrDefault(); + } + catch (InvalidOperationException) + { + throw new GracefulException( + messages: new[] + { + string.Format( + Update.LocalizableStrings.ToolHasMultipleVersionsInstalled, + _packageId), + }, + isUserError: false); + } + + return oldPackageNullable; + } + + private void PrintSuccessMessage(IToolPackage oldPackage, IToolPackage newInstalledPackage) + { + if (!_verbosity.IsQuiet()) + { + if (oldPackage == null) + { + _reporter.WriteLine( + string.Format( + Install.LocalizableStrings.InstallationSucceeded, + string.Join(", ", newInstalledPackage.Commands.Select(c => c.Name)), + newInstalledPackage.Id, + newInstalledPackage.Version.ToNormalizedString()).Green()); + } + else if (oldPackage.Version != newInstalledPackage.Version) + { + _reporter.WriteLine( + string.Format( + Update.LocalizableStrings.UpdateSucceeded, + newInstalledPackage.Id, + oldPackage.Version.ToNormalizedString(), + newInstalledPackage.Version.ToNormalizedString()).Green()); + } + else + { + _reporter.WriteLine( + string.Format( + ( + newInstalledPackage.Version.IsPrerelease ? + Update.LocalizableStrings.UpdateSucceededPreVersionNoChange : Update.LocalizableStrings.UpdateSucceededStableVersionNoChange + ), + newInstalledPackage.Id, newInstalledPackage.Version).Green()); + } + } + } } } diff --git a/src/Cli/dotnet/commands/dotnet-tool/install/ToolInstallLocalCommand.cs b/src/Cli/dotnet/commands/dotnet-tool/install/ToolInstallLocalCommand.cs index 9f9980ba9371..d9c5fcfaec00 100644 --- a/src/Cli/dotnet/commands/dotnet-tool/install/ToolInstallLocalCommand.cs +++ b/src/Cli/dotnet/commands/dotnet-tool/install/ToolInstallLocalCommand.cs @@ -19,6 +19,8 @@ internal class ToolInstallLocalCommand : CommandBase private readonly ILocalToolsResolverCache _localToolsResolverCache; private readonly ToolInstallLocalInstaller _toolLocalPackageInstaller; private readonly IReporter _reporter; + private readonly PackageId _packageId; + private readonly bool _allowPackageDowngrade; private readonly string _explicitManifestFile; private readonly bool _createManifestIfNeeded; @@ -44,15 +46,89 @@ public ToolInstallLocalCommand( _toolManifestEditor = toolManifestEditor ?? new ToolManifestEditor(); _localToolsResolverCache = localToolsResolverCache ?? new LocalToolsResolverCache(); _toolLocalPackageInstaller = new ToolInstallLocalInstaller(parseResult, toolPackageDownloader); + _packageId = new PackageId(parseResult.GetValue(ToolUpdateCommandParser.PackageIdArgument)); + _allowPackageDowngrade = parseResult.GetValue(ToolInstallCommandParser.AllowPackageDowngradeOption); } + public override int Execute() { FilePath manifestFile = GetManifestFilePath(); - return Install(manifestFile); + + (FilePath? manifestFileOptional, string warningMessage) = + _toolManifestFinder.ExplicitManifestOrFindManifestContainPackageId(_explicitManifestFile, _packageId); + + if (warningMessage != null) + { + _reporter.WriteLine(warningMessage.Yellow()); + } + + manifestFile = manifestFileOptional ?? GetManifestFilePath(); + var existingPackageWithPackageId = _toolManifestFinder.Find(manifestFile).Where(p => p.PackageId.Equals(_packageId)); + + if (!existingPackageWithPackageId.Any()) + { + return InstallNewTool(manifestFile); + } + + var existingPackage = existingPackageWithPackageId.Single(); + var toolDownloadedPackage = _toolLocalPackageInstaller.Install(manifestFile); + + InstallToolUpdate(existingPackage, toolDownloadedPackage, manifestFile); + + _localToolsResolverCache.SaveToolPackage( + toolDownloadedPackage, + _toolLocalPackageInstaller.TargetFrameworkToInstall); + + return 0; } - public int Install(FilePath manifestFile) + public int InstallToolUpdate(ToolManifestPackage existingPackage, IToolPackage toolDownloadedPackage, FilePath manifestFile) + { + if (existingPackage.Version > toolDownloadedPackage.Version && !_allowPackageDowngrade) + { + throw new GracefulException(new[] + { + string.Format( + Update.LocalizableStrings.UpdateLocalToolToLowerVersion, + toolDownloadedPackage.Version.ToNormalizedString(), + existingPackage.Version.ToNormalizedString(), + manifestFile.Value) + }, + isUserError: false); + } + else if (existingPackage.Version == toolDownloadedPackage.Version) + { + _reporter.WriteLine( + string.Format( + Update.LocalizableStrings.UpdateLocaToolSucceededVersionNoChange, + toolDownloadedPackage.Id, + existingPackage.Version.ToNormalizedString(), + manifestFile.Value)); + } + else + { + _toolManifestEditor.Edit( + manifestFile, + _packageId, + toolDownloadedPackage.Version, + toolDownloadedPackage.Commands.Select(c => c.Name).ToArray()); + _reporter.WriteLine( + string.Format( + Update.LocalizableStrings.UpdateLocalToolSucceeded, + toolDownloadedPackage.Id, + existingPackage.Version.ToNormalizedString(), + toolDownloadedPackage.Version.ToNormalizedString(), + manifestFile.Value).Green()); + } + + _localToolsResolverCache.SaveToolPackage( + toolDownloadedPackage, + _toolLocalPackageInstaller.TargetFrameworkToInstall); + + return 0; + } + public int InstallNewTool(FilePath manifestFile) { IToolPackage toolDownloadedPackage = _toolLocalPackageInstaller.Install(manifestFile); @@ -78,7 +154,7 @@ public int Install(FilePath manifestFile) return 0; } - private FilePath GetManifestFilePath() + public FilePath GetManifestFilePath() { try { diff --git a/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.cs.xlf b/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.cs.xlf index 1073407eb811..43ef15efbca5 100644 --- a/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.cs.xlf +++ b/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.cs.xlf @@ -2,6 +2,11 @@ + + Allow package downgrade when installing a .NET tool package. + PÅ™i instalaci balíÄku nástroje .NET povolte downgrade balíÄku. + + Create a tool manifest if one isn't found during tool installation. For information on how manifests are located, see https://aka.ms/dotnet/tools/create-manifest-if-needed VytvoÅ™te manifest nástroje, pokud se nÄ›jaký nenajde bÄ›hem instalace nástroje. Informace o tom, jak se manifesty nacházejí, najdete v tématu https://aka.ms/dotnet/tools/create-manifest-if-needed diff --git a/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.de.xlf b/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.de.xlf index 8439dc3b3908..e3ca99a38cec 100644 --- a/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.de.xlf +++ b/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.de.xlf @@ -2,6 +2,11 @@ + + Allow package downgrade when installing a .NET tool package. + Paketdowngrade beim Installieren eines .NET-Toolpakets zulassen. + + Create a tool manifest if one isn't found during tool installation. For information on how manifests are located, see https://aka.ms/dotnet/tools/create-manifest-if-needed Erstellen Sie ein Toolmanifest, wenn es während der Toolinstallation nicht gefunden wird. Informationen dazu, wie Manifeste gefunden werden, finden Sie unter https://aka.ms/dotnet/tools/create-manifest-if-needed diff --git a/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.es.xlf b/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.es.xlf index 1bea2d9d6a19..86a9057d5377 100644 --- a/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.es.xlf +++ b/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.es.xlf @@ -2,6 +2,11 @@ + + Allow package downgrade when installing a .NET tool package. + Permitir la degradación del paquete al instalar un paquete de herramientas de .NET. + + Create a tool manifest if one isn't found during tool installation. For information on how manifests are located, see https://aka.ms/dotnet/tools/create-manifest-if-needed Cree un manifiesto de herramienta si no se encuentra ninguno durante la instalación de la herramienta. Para obtener información sobre cómo se encuentran los manifiestos, consulte https://aka.ms/dotnet/tools/create-manifest-if-needed diff --git a/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.fr.xlf b/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.fr.xlf index 62b5b8ea09b7..cfdf3f4a5cfa 100644 --- a/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.fr.xlf +++ b/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.fr.xlf @@ -2,6 +2,11 @@ + + Allow package downgrade when installing a .NET tool package. + Autoriser le passage à une version antérieure du package lors de l’installation d’un package d’outils .NET. + + Create a tool manifest if one isn't found during tool installation. For information on how manifests are located, see https://aka.ms/dotnet/tools/create-manifest-if-needed Créez un manifeste d'outil si aucun n'est trouvé lors de l'installation de l'outil. Pour plus d'informations sur la localisation des manifestes, voir https://aka.ms/dotnet/tools/create-manifest-if-needed diff --git a/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.it.xlf b/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.it.xlf index 5b79eb906320..c5b0b0a52ee4 100644 --- a/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.it.xlf +++ b/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.it.xlf @@ -2,6 +2,11 @@ + + Allow package downgrade when installing a .NET tool package. + Consente il downgrade del pacchetto durante l'installazione di un pacchetto di strumenti .NET. + + Create a tool manifest if one isn't found during tool installation. For information on how manifests are located, see https://aka.ms/dotnet/tools/create-manifest-if-needed Creare un manifesto dello strumento se non ne viene trovato uno durante l'installazione dello strumento. Per informazioni sulla posizione dei manifesti, vedere https://aka.ms/dotnet/tools/create-manifest-if-needed diff --git a/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.ja.xlf b/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.ja.xlf index dc9cbe468dfa..f398d692956f 100644 --- a/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.ja.xlf +++ b/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.ja.xlf @@ -2,6 +2,11 @@ + + Allow package downgrade when installing a .NET tool package. + .NET ツール パッケージã®ã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«æ™‚ã«ãƒ‘ッケージã®ãƒ€ã‚¦ãƒ³ã‚°ãƒ¬ãƒ¼ãƒ‰ã‚’許å¯ã—ã¾ã™ã€‚ + + Create a tool manifest if one isn't found during tool installation. For information on how manifests are located, see https://aka.ms/dotnet/tools/create-manifest-if-needed ツールã®ã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«ä¸­ã«ãƒ„ール マニフェスト見ã¤ã‹ã‚‰ãªã„å ´åˆã¯ä½œæˆã—ã¾ã™ã€‚マニフェストã®å ´æ‰€ã«ã¤ã„ã¦ã¯ã€https://aka.ms/dotnet/tools/create-manifest-if-needed ã‚’å‚ç…§ã—ã¦ãã ã•ã„ diff --git a/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.ko.xlf b/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.ko.xlf index 09523ab43bdc..91df7322d886 100644 --- a/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.ko.xlf +++ b/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.ko.xlf @@ -2,6 +2,11 @@ + + Allow package downgrade when installing a .NET tool package. + .NET ë„구 패키지를 설치할 때 패키지 다운그레ì´ë“œë¥¼ 허용합니다. + + Create a tool manifest if one isn't found during tool installation. For information on how manifests are located, see https://aka.ms/dotnet/tools/create-manifest-if-needed ë„구 설치 ì¤‘ì— ë„구 매니페스트를 ì°¾ì„ ìˆ˜ 없는 경우 ë„구 매니페스트를 만드세요. ë§¤ë‹ˆíŽ˜ìŠ¤íŠ¸ì˜ ìœ„ì¹˜ëŠ” https://aka.ms/dotnet/tools/create-manifest-if-needed를 참조하세요. diff --git a/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.pl.xlf b/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.pl.xlf index 76b632ec7f10..b5a1cef3c5bf 100644 --- a/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.pl.xlf +++ b/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.pl.xlf @@ -2,6 +2,11 @@ + + Allow package downgrade when installing a .NET tool package. + Zezwalaj na obniżanie wersji pakietu podczas instalowania pakietu narzÄ™dzi .NET. + + Create a tool manifest if one isn't found during tool installation. For information on how manifests are located, see https://aka.ms/dotnet/tools/create-manifest-if-needed Utwórz manifest narzÄ™dzi, jeÅ›li nie zostanie znaleziony podczas instalacji narzÄ™dzia. Aby uzyskać informacje o lokalizacji manifestów, zobacz https://aka.ms/dotnet/tools/create-manifest-if-needed diff --git a/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.pt-BR.xlf b/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.pt-BR.xlf index 7392c799046c..21d7783b11d8 100644 --- a/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.pt-BR.xlf +++ b/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.pt-BR.xlf @@ -2,6 +2,11 @@ + + Allow package downgrade when installing a .NET tool package. + Permitir downgrade de pacote ao instalar um pacote de ferramentas do .NET. + + Create a tool manifest if one isn't found during tool installation. For information on how manifests are located, see https://aka.ms/dotnet/tools/create-manifest-if-needed Crie um manifesto de ferramenta se nenhum for encontrado durante a instalação da ferramenta. Para obter informações sobre como os manifestos estão localizados, consulte https://aka.ms/dotnet/tools/create-manifest-if-needed diff --git a/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.ru.xlf b/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.ru.xlf index 94c8bd4d3f22..ad8741fc30a3 100644 --- a/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.ru.xlf +++ b/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.ru.xlf @@ -2,6 +2,11 @@ + + Allow package downgrade when installing a .NET tool package. + Разрешить переход на иÑпользование более ранней верÑии пакета при уÑтановке пакета инÑтрументов .NET. + + Create a tool manifest if one isn't found during tool installation. For information on how manifests are located, see https://aka.ms/dotnet/tools/create-manifest-if-needed Создайте манифеÑÑ‚ инÑтрумента, еÑли он не найден во Ð²Ñ€ÐµÐ¼Ñ ÑƒÑтановки инÑтрумента. Ð¡Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¾ ÑпоÑобе Ñ€Ð°Ð·Ð¼ÐµÑ‰ÐµÐ½Ð¸Ñ Ð¼Ð°Ð½Ð¸Ñ„ÐµÑтов Ñм. на Ñтранице https://aka.ms/dotnet/tools/create-manifest-if-needed diff --git a/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.tr.xlf b/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.tr.xlf index 6977fd232503..233a159082b4 100644 --- a/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.tr.xlf +++ b/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.tr.xlf @@ -2,6 +2,11 @@ + + Allow package downgrade when installing a .NET tool package. + Bir .NET araç paketini yüklerken paketi eski sürüme düşürmeye izin verin. + + Create a tool manifest if one isn't found during tool installation. For information on how manifests are located, see https://aka.ms/dotnet/tools/create-manifest-if-needed Araç yüklemesi sırasında bir araç bildirimi bulunmazsa bir araç bildirimi oluÅŸturun. Bildirimlerin nasıl bulunduÄŸu hakkında bilgi edinmek için bkz. https://aka.ms/dotnet/tools/create-manifest-if-needed diff --git a/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.zh-Hans.xlf b/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.zh-Hans.xlf index 1687123d06da..740148454acd 100644 --- a/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.zh-Hans.xlf +++ b/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.zh-Hans.xlf @@ -2,6 +2,11 @@ + + Allow package downgrade when installing a .NET tool package. + 安装 .NET 工具包时å…许包é™çº§ã€‚ + + Create a tool manifest if one isn't found during tool installation. For information on how manifests are located, see https://aka.ms/dotnet/tools/create-manifest-if-needed 如果在工具安装期间找ä¸åˆ°å·¥å…·æ¸…å•,请创建一个工具清å•。若è¦äº†è§£å¦‚何查找清å•,请å‚阅 https://aka.ms/dotnet/tools/create-manifest-if-needed diff --git a/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.zh-Hant.xlf b/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.zh-Hant.xlf index 23b08414bf46..06cbc589d936 100644 --- a/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.zh-Hant.xlf +++ b/src/Cli/dotnet/commands/dotnet-tool/install/xlf/LocalizableStrings.zh-Hant.xlf @@ -2,6 +2,11 @@ + + Allow package downgrade when installing a .NET tool package. + å®‰è£ .NET 工具套件時å…許套件é™ç´šã€‚ + + Create a tool manifest if one isn't found during tool installation. For information on how manifests are located, see https://aka.ms/dotnet/tools/create-manifest-if-needed å¦‚æžœåœ¨å·¥å…·å®‰è£æœŸé–“找ä¸åˆ°å·¥å…·è³‡è¨Šæ¸…單,則建立工具資訊清單。如需如何尋找資訊清單的相關資訊,請åƒé–± https://aka.ms/dotnet/tools/create-manifest-if-needed diff --git a/src/Cli/dotnet/commands/dotnet-tool/update/LocalizableStrings.resx b/src/Cli/dotnet/commands/dotnet-tool/update/LocalizableStrings.resx index af3c3cf46ee8..52cbe1ac3ad2 100644 --- a/src/Cli/dotnet/commands/dotnet-tool/update/LocalizableStrings.resx +++ b/src/Cli/dotnet/commands/dotnet-tool/update/LocalizableStrings.resx @@ -183,10 +183,10 @@ and the corresponding package Ids for installed tools using the command 'dotnet tool list'. - Tool '{0}' was reinstalled with the latest prerelease version (version '{1}'). + Tool '{0}' was reinstalled with the prerelease version (version '{1}'). - Tool '{0}' was reinstalled with the latest stable version (version '{1}'). + Tool '{0}' was reinstalled with the stable version (version '{1}'). Tool '{0}' failed to update due to the following: @@ -218,10 +218,10 @@ and the corresponding package Ids for installed tools using the command Tool '{0}' was successfully updated from version '{1}' to version '{2}' (manifest file {3}). - - The requested version {0} is lower than existing version {1} (manifest file {2}). + + The requested version {0} is lower than existing version {1} (manifest file {2}). Use the --allow-downgrade option to allow this update. Tool '{0}' is up to date (version '{1}' manifest file {2}) . - + \ No newline at end of file diff --git a/src/Cli/dotnet/commands/dotnet-tool/update/ToolUpdateCommandParser.cs b/src/Cli/dotnet/commands/dotnet-tool/update/ToolUpdateCommandParser.cs index 37d1114ba0c2..4fda156ac3f7 100644 --- a/src/Cli/dotnet/commands/dotnet-tool/update/ToolUpdateCommandParser.cs +++ b/src/Cli/dotnet/commands/dotnet-tool/update/ToolUpdateCommandParser.cs @@ -32,6 +32,8 @@ internal static class ToolUpdateCommandParser public static readonly CliOption VerbosityOption = ToolInstallCommandParser.VerbosityOption; + public static readonly CliOption AllowPackageDowngradeOption = ToolInstallCommandParser.AllowPackageDowngradeOption; + private static readonly CliCommand Command = ConstructCommand(); public static CliCommand GetCommand() @@ -43,21 +45,8 @@ private static CliCommand ConstructCommand() { CliCommand command = new("update", LocalizableStrings.CommandDescription); - command.Arguments.Add(PackageIdArgument); - command.Options.Add(GlobalOption.WithHelpDescription(command, LocalizableStrings.GlobalOptionDescription)); - command.Options.Add(ToolPathOption.WithHelpDescription(command, LocalizableStrings.ToolPathOptionDescription)); - command.Options.Add(LocalOption.WithHelpDescription(command, LocalizableStrings.LocalOptionDescription)); - command.Options.Add(ConfigOption); - command.Options.Add(AddSourceOption); - command.Options.Add(FrameworkOption); - command.Options.Add(VersionOption); - command.Options.Add(ToolManifestOption.WithHelpDescription(command, LocalizableStrings.ManifestPathOptionDescription)); - command.Options.Add(PrereleaseOption); - command.Options.Add(ToolCommandRestorePassThroughOptions.DisableParallelOption); - command.Options.Add(ToolCommandRestorePassThroughOptions.IgnoreFailedSourcesOption); - command.Options.Add(ToolCommandRestorePassThroughOptions.NoCacheOption); - command.Options.Add(ToolCommandRestorePassThroughOptions.InteractiveRestoreOption); - command.Options.Add(VerbosityOption); + ToolInstallCommandParser.AddCommandOptions(command); + command.Options.Add(AllowPackageDowngradeOption); command.SetAction((parseResult) => new ToolUpdateCommand(parseResult).Execute()); diff --git a/src/Cli/dotnet/commands/dotnet-tool/update/ToolUpdateGlobalOrToolPathCommand.cs b/src/Cli/dotnet/commands/dotnet-tool/update/ToolUpdateGlobalOrToolPathCommand.cs index 86a737cb6693..74c034b509c1 100644 --- a/src/Cli/dotnet/commands/dotnet-tool/update/ToolUpdateGlobalOrToolPathCommand.cs +++ b/src/Cli/dotnet/commands/dotnet-tool/update/ToolUpdateGlobalOrToolPathCommand.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.CommandLine; -using System.Transactions; using Microsoft.DotNet.Cli; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.ShellShim; @@ -10,33 +9,20 @@ using Microsoft.DotNet.Tools.Tool.Install; using Microsoft.DotNet.Tools.Tool.Uninstall; using Microsoft.Extensions.EnvironmentAbstractions; -using NuGet.Versioning; using Microsoft.DotNet.Cli.ToolPackage; +using CreateShellShimRepository = Microsoft.DotNet.Tools.Tool.Install.CreateShellShimRepository; namespace Microsoft.DotNet.Tools.Tool.Update { - internal delegate IShellShimRepository CreateShellShimRepository(string appHostSourceDirectory, DirectoryPath? nonGlobalLocation = null); - internal delegate (IToolPackageStore, IToolPackageStoreQuery, IToolPackageDownloader, IToolPackageUninstaller) CreateToolPackageStoresAndDownloaderAndUninstaller( DirectoryPath? nonGlobalLocation = null, IEnumerable additionalRestoreArguments = null); internal class ToolUpdateGlobalOrToolPathCommand : CommandBase { - private readonly IReporter _reporter; - private readonly IReporter _errorReporter; private readonly CreateShellShimRepository _createShellShimRepository; private readonly CreateToolPackageStoresAndDownloaderAndUninstaller _createToolPackageStoreDownloaderUninstaller; - - private readonly PackageId _packageId; - private readonly string _configFilePath; - private readonly string _framework; - private readonly string[] _additionalFeeds; - private readonly bool _global; - private readonly VerbosityOptions _verbosity; - private readonly string _toolPath; - private readonly IEnumerable _forwardRestoreArguments; - private readonly string _packageVersion; + private readonly ToolInstallGlobalOrToolPathCommand _toolInstallGlobalOrToolPathCommand; public ToolUpdateGlobalOrToolPathCommand(ParseResult parseResult, CreateToolPackageStoresAndDownloaderAndUninstaller createToolPackageStoreDownloaderUninstaller = null, @@ -44,228 +30,24 @@ public ToolUpdateGlobalOrToolPathCommand(ParseResult parseResult, IReporter reporter = null) : base(parseResult) { - _packageId = new PackageId(parseResult.GetValue(ToolUninstallCommandParser.PackageIdArgument)); - _configFilePath = parseResult.GetValue(ToolUpdateCommandParser.ConfigOption); - _framework = parseResult.GetValue(ToolUpdateCommandParser.FrameworkOption); - _additionalFeeds = parseResult.GetValue(ToolUpdateCommandParser.AddSourceOption); - _packageVersion = parseResult.GetValue(ToolUpdateCommandParser.VersionOption); - _global = parseResult.GetValue(ToolUpdateCommandParser.GlobalOption); - _verbosity = parseResult.GetValue(ToolUpdateCommandParser.VerbosityOption); - _toolPath = parseResult.GetValue(ToolUpdateCommandParser.ToolPathOption); - _forwardRestoreArguments = parseResult.OptionValuesToBeForwarded(ToolUpdateCommandParser.GetCommand()); - _createToolPackageStoreDownloaderUninstaller = createToolPackageStoreDownloaderUninstaller ?? ToolPackageFactory.CreateToolPackageStoresAndDownloaderAndUninstaller; _createShellShimRepository = createShellShimRepository ?? ShellShimRepositoryFactory.CreateShellShimRepository; - _reporter = (reporter ?? Reporter.Output); - _errorReporter = (reporter ?? Reporter.Error); + _toolInstallGlobalOrToolPathCommand = new ToolInstallGlobalOrToolPathCommand( + + parseResult, + _createToolPackageStoreDownloaderUninstaller, + _createShellShimRepository, + reporter: reporter); } public override int Execute() { - ValidateArguments(); - - DirectoryPath? toolPath = null; - if (!string.IsNullOrEmpty(_toolPath)) - { - toolPath = new DirectoryPath(_toolPath); - } - - VersionRange versionRange = _parseResult.GetVersionRange(); - - (IToolPackageStore toolPackageStore, - IToolPackageStoreQuery toolPackageStoreQuery, - IToolPackageDownloader toolPackageDownloader, - IToolPackageUninstaller toolPackageUninstaller) = _createToolPackageStoreDownloaderUninstaller(toolPath, _forwardRestoreArguments); - - var appHostSourceDirectory = ShellShimTemplateFinder.GetDefaultAppHostSourceDirectory(); - IShellShimRepository shellShimRepository = _createShellShimRepository(appHostSourceDirectory, toolPath); - - IToolPackage oldPackageNullable = GetOldPackage(toolPackageStoreQuery); - - using (var scope = new TransactionScope( - TransactionScopeOption.Required, - TimeSpan.Zero)) - { - if (oldPackageNullable != null) - { - RunWithHandlingUninstallError(() => - { - foreach (RestoredCommand command in oldPackageNullable.Commands) - { - shellShimRepository.RemoveShim(command.Name); - } - - toolPackageUninstaller.Uninstall(oldPackageNullable.PackageDirectory); - }); - } - - RunWithHandlingInstallError(() => - { - IToolPackage newInstalledPackage = toolPackageDownloader.InstallPackage( - new PackageLocation(nugetConfig: GetConfigFile(), additionalFeeds: _additionalFeeds), - packageId: _packageId, - versionRange: versionRange, - targetFramework: _framework, - verbosity: _verbosity, - isGlobalTool: true - ); - - EnsureVersionIsHigher(oldPackageNullable, newInstalledPackage); - - foreach (RestoredCommand command in newInstalledPackage.Commands) - { - shellShimRepository.CreateShim(command.Executable, command.Name, newInstalledPackage.PackagedShims); - } - - PrintSuccessMessage(oldPackageNullable, newInstalledPackage); - }); - - scope.Complete(); - } - + _toolInstallGlobalOrToolPathCommand.Execute(); return 0; } - - private static void EnsureVersionIsHigher(IToolPackage oldPackageNullable, IToolPackage newInstalledPackage) - { - if (oldPackageNullable != null && (newInstalledPackage.Version < oldPackageNullable.Version)) - { - throw new GracefulException( - new[] - { - string.Format(LocalizableStrings.UpdateToLowerVersion, - newInstalledPackage.Version.ToNormalizedString(), - oldPackageNullable.Version.ToNormalizedString()) - }, - isUserError: false); - } - } - - private void ValidateArguments() - { - if (!string.IsNullOrEmpty(_configFilePath) && !File.Exists(_configFilePath)) - { - throw new GracefulException( - string.Format( - LocalizableStrings.NuGetConfigurationFileDoesNotExist, - Path.GetFullPath(_configFilePath))); - } - } - - private void RunWithHandlingInstallError(Action installAction) - { - try - { - installAction(); - } - catch (Exception ex) - when (InstallToolCommandLowLevelErrorConverter.ShouldConvertToUserFacingError(ex)) - { - var message = new List - { - string.Format(LocalizableStrings.UpdateToolFailed, _packageId) - }; - message.AddRange( - InstallToolCommandLowLevelErrorConverter.GetUserFacingMessages(ex, _packageId)); - - - throw new GracefulException( - messages: message, - verboseMessages: new[] { ex.ToString() }, - isUserError: false); - } - } - - private void RunWithHandlingUninstallError(Action uninstallAction) - { - try - { - uninstallAction(); - } - catch (Exception ex) - when (ToolUninstallCommandLowLevelErrorConverter.ShouldConvertToUserFacingError(ex)) - { - var message = new List - { - string.Format(LocalizableStrings.UpdateToolFailed, _packageId) - }; - message.AddRange( - ToolUninstallCommandLowLevelErrorConverter.GetUserFacingMessages(ex, _packageId)); - - throw new GracefulException( - messages: message, - verboseMessages: new[] { ex.ToString() }, - isUserError: false); - } - } - - private FilePath? GetConfigFile() - { - FilePath? configFile = null; - if (!string.IsNullOrEmpty(_configFilePath)) - { - configFile = new FilePath(_configFilePath); - } - - return configFile; - } - - private IToolPackage GetOldPackage(IToolPackageStoreQuery toolPackageStoreQuery) - { - IToolPackage oldPackageNullable; - try - { - oldPackageNullable = toolPackageStoreQuery.EnumeratePackageVersions(_packageId).SingleOrDefault(); - } - catch (InvalidOperationException) - { - throw new GracefulException( - messages: new[] - { - string.Format( - LocalizableStrings.ToolHasMultipleVersionsInstalled, - _packageId), - }, - isUserError: false); - } - - return oldPackageNullable; - } - - private void PrintSuccessMessage(IToolPackage oldPackage, IToolPackage newInstalledPackage) - { - if (oldPackage == null) - { - _reporter.WriteLine( - string.Format( - Install.LocalizableStrings.InstallationSucceeded, - string.Join(", ", newInstalledPackage.Commands.Select(c => c.Name)), - newInstalledPackage.Id, - newInstalledPackage.Version.ToNormalizedString()).Green()); - } - else if (oldPackage.Version != newInstalledPackage.Version) - { - _reporter.WriteLine( - string.Format( - LocalizableStrings.UpdateSucceeded, - newInstalledPackage.Id, - oldPackage.Version.ToNormalizedString(), - newInstalledPackage.Version.ToNormalizedString()).Green()); - } - else - { - _reporter.WriteLine( - string.Format( - ( - newInstalledPackage.Version.IsPrerelease ? - LocalizableStrings.UpdateSucceededPreVersionNoChange : LocalizableStrings.UpdateSucceededStableVersionNoChange - ), - newInstalledPackage.Id, newInstalledPackage.Version).Green()); - } - } } } diff --git a/src/Cli/dotnet/commands/dotnet-tool/update/ToolUpdateLocalCommand.cs b/src/Cli/dotnet/commands/dotnet-tool/update/ToolUpdateLocalCommand.cs index 8118adee5229..5a3f232865b1 100644 --- a/src/Cli/dotnet/commands/dotnet-tool/update/ToolUpdateLocalCommand.cs +++ b/src/Cli/dotnet/commands/dotnet-tool/update/ToolUpdateLocalCommand.cs @@ -19,13 +19,9 @@ internal class ToolUpdateLocalCommand : CommandBase private readonly IToolManifestEditor _toolManifestEditor; private readonly ILocalToolsResolverCache _localToolsResolverCache; private readonly IToolPackageDownloader _toolPackageDownloader; - private readonly ToolInstallLocalInstaller _toolLocalPackageInstaller; private readonly Lazy _toolInstallLocalCommand; private readonly IReporter _reporter; - private readonly PackageId _packageId; - private readonly string _explicitManifestFile; - public ToolUpdateLocalCommand( ParseResult parseResult, IToolPackageDownloader toolPackageDownloader = null, @@ -35,9 +31,6 @@ public ToolUpdateLocalCommand( IReporter reporter = null) : base(parseResult) { - _packageId = new PackageId(parseResult.GetValue(ToolUpdateCommandParser.PackageIdArgument)); - _explicitManifestFile = parseResult.GetValue(ToolUpdateCommandParser.ToolManifestOption); - _reporter = (reporter ?? Reporter.Output); if (toolPackageDownloader == null) @@ -59,7 +52,6 @@ public ToolUpdateLocalCommand( _toolManifestEditor = toolManifestEditor ?? new ToolManifestEditor(); _localToolsResolverCache = localToolsResolverCache ?? new LocalToolsResolverCache(); - _toolLocalPackageInstaller = new ToolInstallLocalInstaller(parseResult, toolPackageDownloader); _toolInstallLocalCommand = new Lazy( () => new ToolInstallLocalCommand( parseResult, @@ -72,73 +64,7 @@ public ToolUpdateLocalCommand( public override int Execute() { - (FilePath? manifestFileOptional, string warningMessage) = - _toolManifestFinder.ExplicitManifestOrFindManifestContainPackageId(_explicitManifestFile, _packageId); - - var manifestFile = manifestFileOptional ?? _toolManifestFinder.FindFirst(); - - var toolDownloadedPackage = _toolLocalPackageInstaller.Install(manifestFile); - var existingPackageWithPackageId = - _toolManifestFinder - .Find(manifestFile) - .Where(p => p.PackageId.Equals(_packageId)); - - if (!existingPackageWithPackageId.Any()) - { - return _toolInstallLocalCommand.Value.Install(manifestFile); - } - - var existingPackage = existingPackageWithPackageId.Single(); - if (existingPackage.Version > toolDownloadedPackage.Version) - { - throw new GracefulException(new[] - { - string.Format( - LocalizableStrings.UpdateLocaToolToLowerVersion, - toolDownloadedPackage.Version.ToNormalizedString(), - existingPackage.Version.ToNormalizedString(), - manifestFile.Value) - }, - isUserError: false); - } - - if (existingPackage.Version != toolDownloadedPackage.Version) - { - _toolManifestEditor.Edit( - manifestFile, - _packageId, - toolDownloadedPackage.Version, - toolDownloadedPackage.Commands.Select(c => c.Name).ToArray()); - } - - _localToolsResolverCache.SaveToolPackage( - toolDownloadedPackage, - _toolLocalPackageInstaller.TargetFrameworkToInstall); - - if (warningMessage != null) - { - _reporter.WriteLine(warningMessage.Yellow()); - } - - if (existingPackage.Version == toolDownloadedPackage.Version) - { - _reporter.WriteLine( - string.Format( - LocalizableStrings.UpdateLocaToolSucceededVersionNoChange, - toolDownloadedPackage.Id, - existingPackage.Version.ToNormalizedString(), - manifestFile.Value)); - } - else - { - _reporter.WriteLine( - string.Format( - LocalizableStrings.UpdateLocalToolSucceeded, - toolDownloadedPackage.Id, - existingPackage.Version.ToNormalizedString(), - toolDownloadedPackage.Version.ToNormalizedString(), - manifestFile.Value).Green()); - } + _toolInstallLocalCommand.Value.Execute(); return 0; } diff --git a/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.cs.xlf b/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.cs.xlf index 49017f3ead99..60cc43f41a94 100644 --- a/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.cs.xlf +++ b/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.cs.xlf @@ -42,24 +42,24 @@ Nástroj {0} je aktuální (verze {1}, soubor manifestu {2}). - - The requested version {0} is lower than existing version {1} (manifest file {2}). - Požadovaná verze {0} je nižší než stávající verze {1} (soubor manifestu {2}). - - Tool '{0}' was successfully updated from version '{1}' to version '{2}' (manifest file {3}). Nástroj {0} byl úspěšnÄ› aktualizován z verze {1} na verzi {2} (soubor manifestu {3}). + + The requested version {0} is lower than existing version {1} (manifest file {2}). Use the --allow-downgrade option to allow this update. + Požadovaná verze {0} je nižší než stávající verze {1} (soubor manifestu {2}). Pokud chcete povolit tuto aktualizaci, použijte možnost --allow-downgrade. + + - Tool '{0}' was reinstalled with the latest prerelease version (version '{1}'). - Nástroj{0}byl znovu nainstalován s nejnovÄ›jší pÅ™edběžnou verzí (verze{1}). + Tool '{0}' was reinstalled with the prerelease version (version '{1}'). + Nástroj {0} se pÅ™einstaloval na pÅ™edběžnou verzi (verze {1}). - Tool '{0}' was reinstalled with the latest stable version (version '{1}'). - Nástroj {0} byl pÅ™einstalován nejnovÄ›jší stabilní verzí (verze {1}). + Tool '{0}' was reinstalled with the stable version (version '{1}'). + Nástroj {0} se pÅ™einstaloval na stabilní verzi (verze {1}). diff --git a/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.de.xlf b/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.de.xlf index 36cf57b95ead..b93c1827fbb7 100644 --- a/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.de.xlf +++ b/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.de.xlf @@ -42,24 +42,24 @@ Das Tool "{0}" ist auf dem neuesten Stand (Version {1}, Manifestdatei "{2}"). - - The requested version {0} is lower than existing version {1} (manifest file {2}). - Die angeforderte Version {0} ist niedriger als die vorhandene Version {1} (Manifestdatei "{2}"). - - Tool '{0}' was successfully updated from version '{1}' to version '{2}' (manifest file {3}). Das Tool "{0}" wurde erfolgreich von Version {1} auf Version {2} aktualisiert (Manifestdatei "{3}"). + + The requested version {0} is lower than existing version {1} (manifest file {2}). Use the --allow-downgrade option to allow this update. + Die angeforderte Version {0} ist niedriger als die vorhandene Version {1} (Manifestdatei {2}). Verwenden Sie die Option --Downgrade zulassen, um dieses Update zuzulassen. + + - Tool '{0}' was reinstalled with the latest prerelease version (version '{1}'). - Das Tool „{0}“ wurde in der neuesten Vorabversion neu installiert (Version „{1}“). + Tool '{0}' was reinstalled with the prerelease version (version '{1}'). + Das Tool "{0}" wurde mit der Vorabversion (Version "{1}" ) neu installiert. - Tool '{0}' was reinstalled with the latest stable version (version '{1}'). - Das Tool "{0}" wurde in der neuesten stabilen Version neu installiert (Version {1}). + Tool '{0}' was reinstalled with the stable version (version '{1}'). + Das Tool "{0}" wurde mit der stabilen Version (Version "{1}" ) neu installiert. diff --git a/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.es.xlf b/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.es.xlf index e57a011b7607..f8fe5b7b2cfb 100644 --- a/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.es.xlf +++ b/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.es.xlf @@ -42,23 +42,23 @@ La herramienta "{0}" está actualizada (versión "{1}", archivo de manifiesto {2}) . - - The requested version {0} is lower than existing version {1} (manifest file {2}). - La versión solicitada {0} es anterior a la versión existente {1} (archivo de manifiesto {2}). - - Tool '{0}' was successfully updated from version '{1}' to version '{2}' (manifest file {3}). La herramienta "{0}" se actualizó correctamente de la versión "{1}" a la versión "{2}" (archivo de manifiesto {3}). + + The requested version {0} is lower than existing version {1} (manifest file {2}). Use the --allow-downgrade option to allow this update. + La versión solicitada {0} es anterior a la versión existente {1} (archivo de manifiesto {2}). Use la opción --allow-downgrade para permitir esta actualización. + + - Tool '{0}' was reinstalled with the latest prerelease version (version '{1}'). + Tool '{0}' was reinstalled with the prerelease version (version '{1}'). La herramienta "{0}" se ha reinstalado con la versión preliminar más reciente (versión "{1}"). - Tool '{0}' was reinstalled with the latest stable version (version '{1}'). + Tool '{0}' was reinstalled with the stable version (version '{1}'). La herramienta "{0}" se reinstaló con la versión estable más reciente (versión "{1}"). diff --git a/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.fr.xlf b/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.fr.xlf index 4bf16511cbb6..9b4433a9f6fb 100644 --- a/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.fr.xlf +++ b/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.fr.xlf @@ -42,24 +42,24 @@ L'outil '{0}' est à jour (version '{1}', fichier manifeste {2}). - - The requested version {0} is lower than existing version {1} (manifest file {2}). - La version demandée {0} est inférieure à la version existante {1} (fichier manifeste {2}). - - Tool '{0}' was successfully updated from version '{1}' to version '{2}' (manifest file {3}). L'outil '{0}' a été correctement mis à jour de la version '{1}' à la version '{2}' (fichier manifeste {3}). + + The requested version {0} is lower than existing version {1} (manifest file {2}). Use the --allow-downgrade option to allow this update. + La version demandée {0} est inférieure à la version existante{1} (fichier manifeste {2}). Utilisez l’option --allow-downgrade pour autoriser cette mise à jour. + + - Tool '{0}' was reinstalled with the latest prerelease version (version '{1}'). - L'outil '{0}' a été réinstallé avec la dernière version préliminaire (version '{1}'). + Tool '{0}' was reinstalled with the prerelease version (version '{1}'). + L'outil « {0} » a été réinstallé avec la version préliminaire (version « {1} »). - Tool '{0}' was reinstalled with the latest stable version (version '{1}'). - L'outil '{0}' a été réinstallé avec la dernière version stable (version '{1}'). + Tool '{0}' was reinstalled with the stable version (version '{1}'). + L'outil « {0} » a été réinstallé avec la version stable (version « {1} »). diff --git a/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.it.xlf b/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.it.xlf index 1b64464c6117..8f657409de82 100644 --- a/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.it.xlf +++ b/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.it.xlf @@ -42,24 +42,24 @@ Lo strumento '{0}' è aggiornato (file manifesto {2} versione '{1}'). - - The requested version {0} is lower than existing version {1} (manifest file {2}). - La versione richiesta {0} è inferiore a quella esistente {1} (file manifesto {2}). - - Tool '{0}' was successfully updated from version '{1}' to version '{2}' (manifest file {3}). Lo strumento '{0}' è stato aggiornato dalla versione '{1}' alla versione '{2}' (file manifesto {3}). + + The requested version {0} is lower than existing version {1} (manifest file {2}). Use the --allow-downgrade option to allow this update. + La versione richiesta {0} è inferiore a quella esistente {1} (file manifesto {2}). Usare l'opzione --allow-downgrade per consentire questo aggiornamento. + + - Tool '{0}' was reinstalled with the latest prerelease version (version '{1}'). - Lo strumento '{0}' è stato reinstallato con l'ultima versione preliminare (versione '{1}'). + Tool '{0}' was reinstalled with the prerelease version (version '{1}'). + Lo strumento '{0}' è stato reinstallato con la versione preliminare (versione '{1}'). - Tool '{0}' was reinstalled with the latest stable version (version '{1}'). - Lo strumento '{0}' è stato reinstallato con l'ultima versione stabile (versione '{1}'). + Tool '{0}' was reinstalled with the stable version (version '{1}'). + Lo strumento '{0}' è stato reinstallato con la versione stabile (versione '{1}'). diff --git a/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.ja.xlf b/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.ja.xlf index 3d7d560ea2a2..97851054ddaa 100644 --- a/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.ja.xlf +++ b/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.ja.xlf @@ -42,24 +42,24 @@ ツール '{0}' ã¯æœ€æ–°ã®çŠ¶æ…‹ã§ã™ (ãƒãƒ¼ã‚¸ãƒ§ãƒ³ '{1}' マニフェスト ファイル {2})。 - - The requested version {0} is lower than existing version {1} (manifest file {2}). - è¦æ±‚ã•れãŸãƒãƒ¼ã‚¸ãƒ§ãƒ³ {0} ã¯ã€æ—¢å­˜ã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ {1} (マニフェスト ファイル {2}) よりも低ããªã£ã¦ã„ã¾ã™ã€‚ - - Tool '{0}' was successfully updated from version '{1}' to version '{2}' (manifest file {3}). ツール '{0}' ãŒãƒãƒ¼ã‚¸ãƒ§ãƒ³ '{1}' ã‹ã‚‰ãƒãƒ¼ã‚¸ãƒ§ãƒ³ '{2}' (マニフェストファイル {3}) ã«æ­£å¸¸ã«æ›´æ–°ã•れã¾ã—ãŸã€‚ + + The requested version {0} is lower than existing version {1} (manifest file {2}). Use the --allow-downgrade option to allow this update. + è¦æ±‚ã•れãŸãƒãƒ¼ã‚¸ãƒ§ãƒ³ {0} ã¯ã€æ—¢å­˜ã®ãƒãƒ¼ã‚¸ãƒ§ãƒ³ {1} (マニフェスト ファイル {2}) よりも低ããªã£ã¦ã„ã¾ã™ã€‚ã“ã®æ›´æ–°ã‚’許å¯ã™ã‚‹ã«ã¯ã€--allow-downgrade オプションを使用ã—ã¾ã™ã€‚ + + - Tool '{0}' was reinstalled with the latest prerelease version (version '{1}'). - ツール '{0}' ã¯ã€æœ€æ–°ã®ãƒ—レリリース ãƒãƒ¼ã‚¸ãƒ§ãƒ³ (ãƒãƒ¼ã‚¸ãƒ§ãƒ³ '{1}') ã§å†ã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«ã•れã¾ã—ãŸã€‚ + Tool '{0}' was reinstalled with the prerelease version (version '{1}'). + ツール '{0}' ãŒãƒ—レリリース ãƒãƒ¼ã‚¸ãƒ§ãƒ³ (ãƒãƒ¼ã‚¸ãƒ§ãƒ³ '{1}') ã§å†ã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«ã•れã¾ã—ãŸã€‚ - Tool '{0}' was reinstalled with the latest stable version (version '{1}'). - ツール '{0}' ãŒå®‰å®šã—ãŸæœ€æ–°ãƒãƒ¼ã‚¸ãƒ§ãƒ³ (ãƒãƒ¼ã‚¸ãƒ§ãƒ³ '{1}') ã§å†ã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«ã•れã¾ã—ãŸã€‚ + Tool '{0}' was reinstalled with the stable version (version '{1}'). + ツール '{0}' ãŒå®‰å®šã—ãŸãƒãƒ¼ã‚¸ãƒ§ãƒ³ (ãƒãƒ¼ã‚¸ãƒ§ãƒ³ '{1}') ã§å†ã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«ã•れã¾ã—ãŸã€‚ diff --git a/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.ko.xlf b/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.ko.xlf index e89364423fa2..54b7678313fd 100644 --- a/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.ko.xlf +++ b/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.ko.xlf @@ -42,24 +42,24 @@ '{0}' ë„구는 최신 버전(버전 '{1}' 매니페스트 íŒŒì¼ {2})입니다. - - The requested version {0} is lower than existing version {1} (manifest file {2}). - ìš”ì²­ëœ ë²„ì „ {0}ì´(ê°€) 기존 버전 {1}(매니페스트 íŒŒì¼ {2})보다 낮습니다. - - Tool '{0}' was successfully updated from version '{1}' to version '{2}' (manifest file {3}). '{0}' ë„구가 '{1}' 버전ì—서 '{2}' 버전(매니페스트 íŒŒì¼ {3})으로 ì—…ë°ì´íЏë˜ì—ˆìŠµë‹ˆë‹¤. + + The requested version {0} is lower than existing version {1} (manifest file {2}). Use the --allow-downgrade option to allow this update. + ìš”ì²­ëœ {0} ë²„ì „ì´ ê¸°ì¡´ {1} 버전(매니페스트 íŒŒì¼ {2})보다 낮습니다. --allow-downgrade ì˜µì…˜ì„ ì‚¬ìš©í•˜ì—¬ ì´ ì—…ë°ì´íŠ¸ë¥¼ 허용하세요. + + - Tool '{0}' was reinstalled with the latest prerelease version (version '{1}'). - ë„구 '{0}'ì´(ê°€) 최신 ì‹œí—˜íŒ ë²„ì „(버전 '{1}')으로 다시 설치ë˜ì—ˆìŠµë‹ˆë‹¤. + Tool '{0}' was reinstalled with the prerelease version (version '{1}'). + '{0}' ë„구가 ì‹œí—˜íŒ ë²„ì „('{1}' 버전)으로 다시 설치ë˜ì—ˆìŠµë‹ˆë‹¤. - Tool '{0}' was reinstalled with the latest stable version (version '{1}'). - '{0}' ë„구가 안정ì ì¸ 최신 버전('{1}' 버전)으로 다시 설치ë˜ì—ˆìŠµë‹ˆë‹¤. + Tool '{0}' was reinstalled with the stable version (version '{1}'). + '{0}' ë„구가 안정화 버전('{1}' 버전)으로 다시 설치ë˜ì—ˆìŠµë‹ˆë‹¤. diff --git a/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.pl.xlf b/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.pl.xlf index d9ebce6c0772..78b6bfddfbcb 100644 --- a/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.pl.xlf +++ b/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.pl.xlf @@ -42,23 +42,23 @@ NarzÄ™dzie „{0}†jest aktualne (wersja „{1}â€, plik manifestu {2}). - - The requested version {0} is lower than existing version {1} (manifest file {2}). - Żądana wersja {0} jest niższa niż obecna wersja {1} (plik manifestu {2}). - - Tool '{0}' was successfully updated from version '{1}' to version '{2}' (manifest file {3}). NarzÄ™dzie „{0}†zostaÅ‚o pomyÅ›lnie zaktualizowane z wersji „{1}†do wersji „{2}†(plik manifestu {3}). + + The requested version {0} is lower than existing version {1} (manifest file {2}). Use the --allow-downgrade option to allow this update. + Żądana wersja {0} jest niższa niż obecna wersja {1} (plik manifestu {2}). Użyj opcji --allow-downgrade (zezwól na zmianÄ™ na starszÄ… lub mniej zaawansowanÄ… wersjÄ™), aby zezwolić na tÄ™ aktualizacjÄ™. + + - Tool '{0}' was reinstalled with the latest prerelease version (version '{1}'). - NarzÄ™dzie „{0}†zostaÅ‚o ponownie zainstalowane przy użyciu najnowszej stabilnej wersji (wersja „{1}â€). + Tool '{0}' was reinstalled with the prerelease version (version '{1}'). + NarzÄ™dzie „{0}†zostaÅ‚o ponownie zainstalowane z wersjÄ… wstÄ™pnÄ… (wersja „{1}â€). - Tool '{0}' was reinstalled with the latest stable version (version '{1}'). + Tool '{0}' was reinstalled with the stable version (version '{1}'). NarzÄ™dzie „{0}†zostaÅ‚o ponownie zainstalowane przy użyciu najnowszej stabilnej wersji (wersja „{1}â€). diff --git a/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.pt-BR.xlf b/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.pt-BR.xlf index 88f0816772ae..adc7d93e0036 100644 --- a/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.pt-BR.xlf +++ b/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.pt-BR.xlf @@ -42,24 +42,24 @@ A ferramenta '{0}' está atualizada (versão '{1}' arquivo de manifesto {2}) . - - The requested version {0} is lower than existing version {1} (manifest file {2}). - A versão solicitada {0} é inferior à versão existente {1} (arquivo de manifesto {2}). - - Tool '{0}' was successfully updated from version '{1}' to version '{2}' (manifest file {3}). A ferramenta '{0}' foi atualizada com êxito da versão '{1}' para a versão '{2}' (arquivo de manifesto {3}). + + The requested version {0} is lower than existing version {1} (manifest file {2}). Use the --allow-downgrade option to allow this update. + A versão {0} solicitada é inferior à versão {1} existente (arquivo de manifesto {2}). Use a opção --allow-downgrade para permitir essa atualização. + + - Tool '{0}' was reinstalled with the latest prerelease version (version '{1}'). - A ferramenta '{0}' foi reinstalada com a versão de pré-lançamento mais recente (versão '{1}'). + Tool '{0}' was reinstalled with the prerelease version (version '{1}'). + A ferramenta "{0}" foi reinstalada com a versão de pré-lançamento mais recente (versão "{1}"). - Tool '{0}' was reinstalled with the latest stable version (version '{1}'). - A ferramenta '{0}' foi reinstalada com a versão estável mais recente (versão '{1}'). + Tool '{0}' was reinstalled with the stable version (version '{1}'). + A ferramenta "{0}" foi reinstalada com a versão estável mais recente (versão "{1}"). diff --git a/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.ru.xlf b/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.ru.xlf index 46bf064da5cd..3360bfae0ad8 100644 --- a/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.ru.xlf +++ b/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.ru.xlf @@ -42,24 +42,24 @@ СредÑтво "{0}" обновлено (верÑÐ¸Ñ "{1}", файл манифеÑта {2}). - - The requested version {0} is lower than existing version {1} (manifest file {2}). - Ð—Ð°Ð¿Ñ€Ð¾ÑˆÐµÐ½Ð½Ð°Ñ Ð²ÐµÑ€ÑÐ¸Ñ {0} ниже ÑущеÑтвующей верÑии {1} (файл манифеÑта {2}). - - Tool '{0}' was successfully updated from version '{1}' to version '{2}' (manifest file {3}). СредÑтво "{0}" обновлено Ñ Ð²ÐµÑ€Ñии "{1}" до верÑии "{2}" (файл манифеÑта {3}). + + The requested version {0} is lower than existing version {1} (manifest file {2}). Use the --allow-downgrade option to allow this update. + Ð—Ð°Ð¿Ñ€Ð¾ÑˆÐµÐ½Ð½Ð°Ñ Ð²ÐµÑ€ÑÐ¸Ñ {0} ниже ÑущеÑтвующей верÑии {1} (файл манифеÑта {2}). Чтобы разрешить Ñто обновление, иÑпользуйте параметр --allow-downgrade. + + - Tool '{0}' was reinstalled with the latest prerelease version (version '{1}'). - ИнÑтрумент "{0}" был переуÑтановлен Ñ Ð¿Ð¾Ñледней предварительной верÑией (верÑией "{1}"). + Tool '{0}' was reinstalled with the prerelease version (version '{1}'). + ИнÑтрумент "{0}" был переуÑтановлен. УÑтановлена Ð¿Ñ€ÐµÐ´Ð²Ð°Ñ€Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð²ÐµÑ€ÑÐ¸Ñ (верÑÐ¸Ñ "{1}"). - Tool '{0}' was reinstalled with the latest stable version (version '{1}'). - ИнÑтрумент "{0}" был переуÑтановлен Ñ Ð¿Ð¾Ñледней Ñтабильной верÑией (верÑией "{1}"). + Tool '{0}' was reinstalled with the stable version (version '{1}'). + ИнÑтрумент "{0}" был переуÑтановлен. УÑтановлена ÑÑ‚Ð°Ð±Ð¸Ð»ÑŒÐ½Ð°Ñ Ð²ÐµÑ€ÑÐ¸Ñ (верÑÐ¸Ñ "{1}"). diff --git a/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.tr.xlf b/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.tr.xlf index 9ae1020925b0..75e7380a661b 100644 --- a/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.tr.xlf +++ b/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.tr.xlf @@ -42,24 +42,24 @@ '{0}' aracı güncel (sürüm '{1}' bildirim dosyası {2}). - - The requested version {0} is lower than existing version {1} (manifest file {2}). - İstenen sürüm ({0}) mevcut sürümünden ({1}) (bildirim dosyası {2}) düşük. - - Tool '{0}' was successfully updated from version '{1}' to version '{2}' (manifest file {3}). '{0}' aracı, '{1}' sürümünden '{2}' (bildirim dosyası {3}) sürümüne baÅŸarıyla güncelleÅŸtirildi. + + The requested version {0} is lower than existing version {1} (manifest file {2}). Use the --allow-downgrade option to allow this update. + İstenen {0} sürümü mevcut {1} sürümünden ({2}bildirim dosyası) düşük. Bu güncelleÅŸtirmeye izin vermek için --allow-downgrade seçeneÄŸini kullanın. + + - Tool '{0}' was reinstalled with the latest prerelease version (version '{1}'). - '{0}' aracı, en yeni ön sürüm (sürüm '{1}') ile yeniden yüklendi. + Tool '{0}' was reinstalled with the prerelease version (version '{1}'). + '{0}' aracı, ön sürüm (sürüm '{1}') ile yeniden yüklendi. - Tool '{0}' was reinstalled with the latest stable version (version '{1}'). - '{0}' aracı, en son kararlı sürüm (sürüm '{1}') ile yeniden yüklendi. + Tool '{0}' was reinstalled with the stable version (version '{1}'). + '{0}' aracı, kararlı sürüm (sürüm '{1}') ile yeniden yüklendi. diff --git a/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.zh-Hans.xlf b/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.zh-Hans.xlf index a7264b53a603..0580fb08f0b6 100644 --- a/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.zh-Hans.xlf +++ b/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.zh-Hans.xlf @@ -42,24 +42,24 @@ 工具“{0}â€æ˜¯æœ€æ–°çš„(版本“{1}â€æ¸…啿–‡ä»¶ {2})。 - - The requested version {0} is lower than existing version {1} (manifest file {2}). - 请求的版本 {0} 低于现有版本 {1} (æ¸…å•æ–‡ä»¶{2})。 - - Tool '{0}' was successfully updated from version '{1}' to version '{2}' (manifest file {3}). 工具“{0}â€å·²æˆåŠŸä»Žç‰ˆæœ¬â€œ{1}â€æ›´æ–°åˆ°ç‰ˆæœ¬â€œ{2}â€(æ¸…å•æ–‡ä»¶ {3})。 + + The requested version {0} is lower than existing version {1} (manifest file {2}). Use the --allow-downgrade option to allow this update. + 请求的版本 {0} 低于现有版本 {1} (æ¸…å•æ–‡ä»¶ {2})。使用 --allow-downgrade 选项å…许此更新。 + + - Tool '{0}' was reinstalled with the latest prerelease version (version '{1}'). - 工具“{0}â€å·²é‡æ–°å®‰è£…最新预å‘行版本(版本“{1}â€)。 + Tool '{0}' was reinstalled with the prerelease version (version '{1}'). + 工具“{0}â€å·²é‡æ–°å®‰è£…预å‘行版本(版本“{1}â€)。 - Tool '{0}' was reinstalled with the latest stable version (version '{1}'). - 工具“{0}â€å·²é‡æ–°å®‰è£…最新稳定版本(版本“{1}â€)。 + Tool '{0}' was reinstalled with the stable version (version '{1}'). + 工具“{0}â€å·²é‡æ–°å®‰è£…稳定版本(版本“{1}â€)。 diff --git a/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.zh-Hant.xlf b/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.zh-Hant.xlf index 24f4b489a06f..8deb59eb6be0 100644 --- a/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.zh-Hant.xlf +++ b/src/Cli/dotnet/commands/dotnet-tool/update/xlf/LocalizableStrings.zh-Hant.xlf @@ -42,24 +42,24 @@ 工具 '{0}' 為最新 (版本 '{1}' 資訊清單檔 {2})。 - - The requested version {0} is lower than existing version {1} (manifest file {2}). - è¦æ±‚的版本 {0} ä½Žæ–¼ç¾æœ‰ç‰ˆæœ¬ {1} (資訊清單檔 {2})。 - - Tool '{0}' was successfully updated from version '{1}' to version '{2}' (manifest file {3}). å·²æˆåŠŸå°‡å·¥å…· '{0}' 從 '{1}' 版更新為 '{2}' 版 (資訊清單檔 {3})。 + + The requested version {0} is lower than existing version {1} (manifest file {2}). Use the --allow-downgrade option to allow this update. + è¦æ±‚的版本 {0} ä½Žæ–¼ç¾æœ‰ç‰ˆæœ¬ {1} (資訊清單檔 {2})。使用 --allow-downgrade é¸é …以å…許此更新。 + + - Tool '{0}' was reinstalled with the latest prerelease version (version '{1}'). - 已使用最新æ¶é®®ç‰ˆ ('{0}' 版) 來釿–°å®‰è£å·¥å…· '{1}'。 + Tool '{0}' was reinstalled with the prerelease version (version '{1}'). + 已使用æ¶é®®ç‰ˆæœ¬ (版本 '{1}') 來釿–°å®‰è£å·¥å…· '{0}'。 - Tool '{0}' was reinstalled with the latest stable version (version '{1}'). - 已使用最新穩定版本 ('{1}' 版) 來釿–°å®‰è£å·¥å…· '{0}'。 + Tool '{0}' was reinstalled with the stable version (version '{1}'). + 已使用穩定版本 (版本 '{1}') 來釿–°å®‰è£å·¥å…· '{0}'。 diff --git a/src/Cli/dotnet/commands/dotnet-workload/InstallStateContents.cs b/src/Cli/dotnet/commands/dotnet-workload/InstallStateContents.cs new file mode 100644 index 000000000000..d6fcc23bf654 --- /dev/null +++ b/src/Cli/dotnet/commands/dotnet-workload/InstallStateContents.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Microsoft.DotNet.Workloads.Workload +{ + internal class InstallStateContents + { + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public bool? UseWorkloadSets { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public Dictionary Manifests { get; set; } + + private static readonly JsonSerializerOptions s_options = new() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + WriteIndented = true, + }; + + public static InstallStateContents FromString(string contents) + { + return JsonSerializer.Deserialize(contents, s_options); + } + + public override string ToString() + { + return JsonSerializer.Serialize(this, s_options); + } + } +} \ No newline at end of file diff --git a/src/Cli/dotnet/commands/dotnet-workload/WorkloadCommandParser.cs b/src/Cli/dotnet/commands/dotnet-workload/WorkloadCommandParser.cs index a66915b36625..58a178422aea 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/WorkloadCommandParser.cs +++ b/src/Cli/dotnet/commands/dotnet-workload/WorkloadCommandParser.cs @@ -47,7 +47,7 @@ internal static string GetWorkloadsVersion(WorkloadInfoHelper workloadInfoHelper return workloadInfoHelper.ManifestProvider.GetWorkloadVersion(); } - internal static void ShowWorkloadsInfo(ParseResult parseResult = null, WorkloadInfoHelper workloadInfoHelper = null, IReporter reporter = null, string dotnetDir = null) + internal static void ShowWorkloadsInfo(ParseResult parseResult = null, WorkloadInfoHelper workloadInfoHelper = null, IReporter reporter = null, string dotnetDir = null, bool showVersion = true) { workloadInfoHelper ??= new WorkloadInfoHelper(parseResult != null ? parseResult.HasOption(SharedOptions.InteractiveOption) : false); IEnumerable installedList = workloadInfoHelper.InstalledSdkWorkloadIds; @@ -55,7 +55,10 @@ internal static void ShowWorkloadsInfo(ParseResult parseResult = null, WorkloadI reporter ??= Cli.Utils.Reporter.Output; string dotnetPath = dotnetDir ?? Path.GetDirectoryName(Environment.ProcessPath); - reporter.WriteLine($" Workload version: {workloadInfoHelper.ManifestProvider.GetWorkloadVersion()}"); + if (showVersion) + { + reporter.WriteLine($" Workload version: {workloadInfoHelper.ManifestProvider.GetWorkloadVersion()}"); + } if (installedWorkloads.Count == 0) { diff --git a/src/Cli/dotnet/commands/dotnet-workload/WorkloadInfoHelper.cs b/src/Cli/dotnet/commands/dotnet-workload/WorkloadInfoHelper.cs index a8ee40187373..f686d494a53d 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/WorkloadInfoHelper.cs +++ b/src/Cli/dotnet/commands/dotnet-workload/WorkloadInfoHelper.cs @@ -56,7 +56,8 @@ public WorkloadInfoHelper( userProfileDir, verifySignatures ?? !SignCheck.IsDotNetSigned(), restoreActionConfig: restoreConfig, - elevationRequired: false); + elevationRequired: false, + shouldLog: false); WorkloadRecordRepo = workloadRecordRepo ?? Installer.GetWorkloadInstallationRecordRepository(); } @@ -66,8 +67,7 @@ public WorkloadInfoHelper( public IWorkloadInstallationRecordRepository WorkloadRecordRepo { get; private init; } public IWorkloadResolver WorkloadResolver { get; private init; } - public IEnumerable InstalledSdkWorkloadIds => - WorkloadRecordRepo.GetInstalledWorkloads(_currentSdkFeatureBand); + public IEnumerable InstalledSdkWorkloadIds => WorkloadRecordRepo.GetInstalledWorkloads(_currentSdkFeatureBand); public InstalledWorkloadsCollection AddInstalledVsWorkloads(IEnumerable sdkWorkloadIds) { diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/FileBasedInstaller.cs b/src/Cli/dotnet/commands/dotnet-workload/install/FileBasedInstaller.cs index 22b073f6aa80..08cd93199320 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/FileBasedInstaller.cs +++ b/src/Cli/dotnet/commands/dotnet-workload/install/FileBasedInstaller.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Text.Json; -using Microsoft.Deployment.DotNet.Releases; using Microsoft.DotNet.Cli; using Microsoft.DotNet.Cli.NuGetPackageDownloader; using Microsoft.DotNet.Cli.Utils; @@ -453,6 +452,36 @@ public void GarbageCollect(Func getResolverForWorkloa } + public void RemoveManifestsFromInstallState(SdkFeatureBand sdkFeatureBand) + { + string path = Path.Combine(WorkloadInstallType.GetInstallStateFolder(_sdkFeatureBand, _dotnetDir), "default.json"); + + if (File.Exists(path)) + { + var installStateContents = File.Exists(path) ? InstallStateContents.FromString(File.ReadAllText(path)) : new InstallStateContents(); + installStateContents.Manifests = null; + File.WriteAllText(path, installStateContents.ToString()); + } + } + + public void SaveInstallStateManifestVersions(SdkFeatureBand sdkFeatureBand, Dictionary manifestContents) + { + string path = Path.Combine(WorkloadInstallType.GetInstallStateFolder(_sdkFeatureBand, _dotnetDir), "default.json"); + Directory.CreateDirectory(Path.GetDirectoryName(path)); + var installStateContents = File.Exists(path) ? InstallStateContents.FromString(File.ReadAllText(path)) : new InstallStateContents(); + installStateContents.Manifests = manifestContents; + File.WriteAllText(path, installStateContents.ToString()); + } + + public void UpdateInstallMode(SdkFeatureBand sdkFeatureBand, bool newMode) + { + string path = Path.Combine(WorkloadInstallType.GetInstallStateFolder(sdkFeatureBand, _dotnetDir), "default.json"); + Directory.CreateDirectory(Path.GetDirectoryName(path)); + var installStateContents = File.Exists(path) ? InstallStateContents.FromString(File.ReadAllText(path)) : new InstallStateContents(); + installStateContents.UseWorkloadSets = newMode; + File.WriteAllText(path, installStateContents.ToString()); + } + /// /// Remove all workload installation records that aren't from Visual Studio. /// diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/IInstaller.cs b/src/Cli/dotnet/commands/dotnet-workload/install/IInstaller.cs index 9415fa72f1de..5d388ef8bd21 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/IInstaller.cs +++ b/src/Cli/dotnet/commands/dotnet-workload/install/IInstaller.cs @@ -33,6 +33,21 @@ internal interface IInstaller : IWorkloadManifestInstaller void ReplaceWorkloadResolver(IWorkloadResolver workloadResolver); void Shutdown(); + + /// + /// Delete the install state file at the specified path. + /// + /// The SDK feature band of the install state file. + void RemoveManifestsFromInstallState(SdkFeatureBand sdkFeatureBand); + + /// + /// Writes the specified JSON contents to the install state file. + /// + /// The SDK feature band of the install state file. + /// The JSON contents describing the install state. + void SaveInstallStateManifestVersions(SdkFeatureBand sdkFeatureBand, Dictionary manifestContents); + + void UpdateInstallMode(SdkFeatureBand sdkFeatureBand, bool newMode); } // Interface to pass to workload manifest updater diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/IWorkloadManifestUpdater.cs b/src/Cli/dotnet/commands/dotnet-workload/install/IWorkloadManifestUpdater.cs index 87957062092f..d9dec221ad29 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/IWorkloadManifestUpdater.cs +++ b/src/Cli/dotnet/commands/dotnet-workload/install/IWorkloadManifestUpdater.cs @@ -12,13 +12,9 @@ internal interface IWorkloadManifestUpdater Task BackgroundUpdateAdvertisingManifestsWhenRequiredAsync(); - IEnumerable<( - ManifestVersionUpdate manifestUpdate, - Dictionary Workloads - )> CalculateManifestUpdates(); + IEnumerable CalculateManifestUpdates(); - IEnumerable - CalculateManifestRollbacks(string rollbackDefinitionFilePath); + IEnumerable CalculateManifestRollbacks(string rollbackDefinitionFilePath); Task> GetManifestPackageDownloadsAsync(bool includePreviews, SdkFeatureBand providedSdkFeatureBand, SdkFeatureBand installedSdkFeatureBand); @@ -26,4 +22,6 @@ Dictionary Workloads void DeleteUpdatableWorkloadsFile(); } + + internal record ManifestUpdateWithWorkloads(ManifestVersionUpdate ManifestUpdate, WorkloadCollection Workloads); } diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/LocalizableStrings.resx b/src/Cli/dotnet/commands/dotnet-workload/install/LocalizableStrings.resx index 08f453f48894..b9cd5af6b5dd 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/LocalizableStrings.resx +++ b/src/Cli/dotnet/commands/dotnet-workload/install/LocalizableStrings.resx @@ -183,6 +183,9 @@ Only print the list of links to download without downloading. + + Control whether future workload operations should use workload sets or loose manifests. + Download packages needed to install a workload to a folder that can be used for offline installation. diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/MsiInstallerBase.cs b/src/Cli/dotnet/commands/dotnet-workload/install/MsiInstallerBase.cs index e33f8a56668a..60d086bb4d1a 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/MsiInstallerBase.cs +++ b/src/Cli/dotnet/commands/dotnet-workload/install/MsiInstallerBase.cs @@ -1,9 +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.Diagnostics; using System.Runtime.Versioning; using Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.Workloads.Workload; +using Microsoft.DotNet.Workloads.Workload.Install; using Microsoft.DotNet.Workloads.Workload.Install.InstallRecord; using Microsoft.NET.Sdk.WorkloadManifestReader; using Microsoft.Win32; @@ -26,6 +29,11 @@ internal abstract class MsiInstallerBase : InstallerBase /// private string _dotNetHome; + /// + /// Full path to the root directory for storing workload data. + /// + public static readonly string WorkloadDataRoot = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "dotnet", "workloads"); + /// /// Default reinstall mode (equivalent to VOMUS). /// @@ -267,6 +275,40 @@ protected uint RepairMsi(string productCode, string logFile) throw new InvalidOperationException($"Invalid configuration: elevated: {IsElevated}, client: {IsClient}"); } + /// + /// Instructs future workload operations to use workload sets or loose manifests, per newMode. + /// + /// The feature band to update + /// Where to use loose manifests or workload sets + /// Full path of the log file + /// Error code indicating the result of the operation + /// + protected void UpdateInstallMode(SdkFeatureBand sdkFeatureBand, bool newMode) + { + Elevate(); + + if (IsElevated) + { + string path = Path.Combine(WorkloadInstallType.GetInstallStateFolder(sdkFeatureBand, DotNetHome), "default.json"); + // Create the parent folder for the state file and set up all required ACLs + SecurityUtils.CreateSecureDirectory(Path.GetDirectoryName(path)); + var installStateContents = File.Exists(path) ? InstallStateContents.FromString(File.ReadAllText(path)) : new InstallStateContents(); + installStateContents.UseWorkloadSets = newMode; + File.WriteAllText(path, installStateContents.ToString()); + + SecurityUtils.SecureFile(path); + } + else if (IsClient) + { + InstallResponseMessage response = Dispatcher.SendUpdateWorkloadModeRequest(sdkFeatureBand, newMode); + ExitOnFailure(response, "Failed to update install mode."); + } + else + { + throw new InvalidOperationException($"Invalid configuration: elevated: {IsElevated}, client: {IsClient}"); + } + } + /// /// Installs the specified MSI. /// @@ -487,5 +529,65 @@ protected void UpdateDependent(InstallRequestType requestType, string providerKe ExitOnFailure(response, $"Failed to update dependent, providerKey: {providerKeyName}, dependent: {dependent}."); } } + + /// + /// Deletes manifests from the install state file for the specified feature band. + /// + /// The feature band of the install state file. + public void RemoveManifestsFromInstallState(SdkFeatureBand sdkFeatureBand) + { + string path = Path.Combine(WorkloadInstallType.GetInstallStateFolder(sdkFeatureBand, DotNetHome), "default.json"); + + if (!File.Exists(path)) + { + Log?.LogMessage($"Install state file does not exist: {path}"); + return; + } + + Elevate(); + + if (IsElevated) + { + if (File.Exists(path)) + { + var installStateContents = InstallStateContents.FromString(File.ReadAllText(path)); + installStateContents.Manifests = null; + File.WriteAllText(path, installStateContents.ToString()); + } + } + else if (IsClient) + { + InstallResponseMessage response = Dispatcher.SendRemoveManifestsFromInstallStateFileRequest(sdkFeatureBand); + ExitOnFailure(response, $"Failed to remove install state file: {path}"); + } + } + + /// + /// Writes the contents of the install state JSON file. + /// + /// The path of the isntall state file to write. + /// The contents of the JSON file, formatted as a single line. + public void SaveInstallStateManifestVersions(SdkFeatureBand sdkFeatureBand, Dictionary manifestContents) + { + string path = Path.Combine(WorkloadInstallType.GetInstallStateFolder(sdkFeatureBand, DotNetHome), "default.json"); + Elevate(); + + if (IsElevated) + { + // Create the parent folder for the state file and set up all required ACLs + SecurityUtils.CreateSecureDirectory(Path.GetDirectoryName(path)); + + var installStateContents = File.Exists(path) ? InstallStateContents.FromString(File.ReadAllText(path)) : new InstallStateContents(); + installStateContents.Manifests = manifestContents; + File.WriteAllText(path, installStateContents.ToString()); + + SecurityUtils.SecureFile(path); + } + else if (IsClient) + { + InstallResponseMessage respone = Dispatcher.SendSaveInstallStateManifestVersions(sdkFeatureBand, manifestContents); + ExitOnFailure(respone, $"Failed to write install state file: {path}"); + } + } } } diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/NetSdkMsiInstallerClient.cs b/src/Cli/dotnet/commands/dotnet-workload/install/NetSdkMsiInstallerClient.cs index a7629da7ac62..6ff3b44635d5 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/NetSdkMsiInstallerClient.cs +++ b/src/Cli/dotnet/commands/dotnet-workload/install/NetSdkMsiInstallerClient.cs @@ -1019,9 +1019,12 @@ public static NetSdkMsiInstallerClient Create( PackageSourceLocation packageSourceLocation = null, IReporter reporter = null, string tempDirPath = null, - RestoreActionConfig restoreActionConfig = null) + RestoreActionConfig restoreActionConfig = null, + bool shouldLog = true) { - TimestampedFileLogger logger = new(Path.Combine(Path.GetTempPath(), $"Microsoft.NET.Workload_{Environment.ProcessId}_{DateTime.Now:yyyyMMdd_HHmmss_fff}.log")); + ISynchronizingLogger logger = + shouldLog ? new TimestampedFileLogger(Path.Combine(Path.GetTempPath(), $"Microsoft.NET.Workload_{Environment.ProcessId}_{DateTime.Now:yyyyMMdd_HHmmss_fff}.log")) + : new NullInstallerLogger(); InstallClientElevationContext elevationContext = new(logger); if (nugetPackageDownloader == null) @@ -1064,9 +1067,14 @@ private void OnProcessExit(object sender, EventArgs e) } finally { - ((TimestampedFileLogger)Log).Dispose(); + if (Log is IDisposable tfl) + { + tfl.Dispose(); + } } } } + + void IInstaller.UpdateInstallMode(SdkFeatureBand sdkFeatureBand, bool newMode) => UpdateInstallMode(sdkFeatureBand, newMode); } } diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/NetSdkMsiInstallerServer.cs b/src/Cli/dotnet/commands/dotnet-workload/install/NetSdkMsiInstallerServer.cs index 06e55b87c783..c62dd4b36096 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/NetSdkMsiInstallerServer.cs +++ b/src/Cli/dotnet/commands/dotnet-workload/install/NetSdkMsiInstallerServer.cs @@ -93,6 +93,22 @@ public void Run() Dispatcher.ReplySuccess($"Updated dependent '{request.Dependent}' for provider key '{request.ProviderKeyName}'"); break; + case InstallRequestType.SaveInstallStateManifestVersions: + SaveInstallStateManifestVersions(new SdkFeatureBand(request.SdkFeatureBand), request.InstallStateManifestVersions); + Dispatcher.ReplySuccess($"Created install state file for {request.SdkFeatureBand}."); + break; + + case InstallRequestType.RemoveManifestsFromInstallStateFile: + RemoveManifestsFromInstallState(new SdkFeatureBand(request.SdkFeatureBand)); + Dispatcher.ReplySuccess($"Deleted install state file for {request.SdkFeatureBand}."); + break; + + case InstallRequestType.AdjustWorkloadMode: + UpdateInstallMode(new SdkFeatureBand(request.SdkFeatureBand), request.UseWorkloadSets); + string newMode = request.ProductCode.Equals("true", StringComparison.OrdinalIgnoreCase) ? "workload sets" : "loose manifests"; + Dispatcher.ReplySuccess($"Updated install mode to use {newMode}."); + break; + default: throw new InvalidOperationException($"Unknown message request: {(int)request.RequestType}"); } diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/WorkloadInstallCommand.cs b/src/Cli/dotnet/commands/dotnet-workload/install/WorkloadInstallCommand.cs index 11689ddcc858..97a63aea9d47 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/WorkloadInstallCommand.cs +++ b/src/Cli/dotnet/commands/dotnet-workload/install/WorkloadInstallCommand.cs @@ -40,7 +40,7 @@ public WorkloadInstallCommand( _workloadResolver, Verbosity, _userProfileDir, VerifySignatures, PackageDownloader, _dotnetPath, TempDirectoryPath, _packageSourceLocation, RestoreActionConfiguration, elevationRequired: !_printDownloadLinkOnly && string.IsNullOrWhiteSpace(_downloadToCacheOption)); - _workloadManifestUpdater = _workloadManifestUpdaterFromConstructor ?? new WorkloadManifestUpdater(Reporter, _workloadResolver, PackageDownloader, _userProfileDir, TempDirectoryPath, + _workloadManifestUpdater = _workloadManifestUpdaterFromConstructor ?? new WorkloadManifestUpdater(Reporter, _workloadResolver, PackageDownloader, _userProfileDir, _workloadInstaller.GetWorkloadInstallationRecordRepository(), _workloadInstaller, _packageSourceLocation, displayManifestUpdates: Verbosity.IsDetailedOrDiagnostic()); ValidateWorkloadIdsInput(); @@ -99,7 +99,7 @@ public override int Execute() } else if (_skipManifestUpdate && usedRollback) { - throw new GracefulException(string.Format(LocalizableStrings.CannotCombineSkipManifestAndRollback, + throw new GracefulException(string.Format(LocalizableStrings.CannotCombineSkipManifestAndRollback, WorkloadInstallCommandParser.SkipManifestUpdateOption.Name, InstallingWorkloadCommandParser.FromRollbackFileOption.Name, WorkloadInstallCommandParser.SkipManifestUpdateOption.Name, InstallingWorkloadCommandParser.FromRollbackFileOption.Name), isUserError: true); } @@ -128,13 +128,13 @@ public void InstallWorkloads(IEnumerable workloadIds, bool skipManif { Reporter.WriteLine(); - var manifestsToUpdate = Enumerable.Empty (); + var manifestsToUpdate = Enumerable.Empty(); var useRollback = false; if (!skipManifestUpdate) { var installStateFilePath = Path.Combine(WorkloadInstallType.GetInstallStateFolder(_sdkFeatureBand, _dotnetPath), "default.json"); - if (File.Exists(installStateFilePath)) + if (string.IsNullOrWhiteSpace(_fromRollbackDefinition) && File.Exists(installStateFilePath) && InstallStateContents.FromString(File.ReadAllText(installStateFilePath)).Manifests is not null) { // If there is a rollback state file, then we don't want to automatically update workloads when a workload is installed // To update to a new version, the user would need to run "dotnet workload update" @@ -162,7 +162,7 @@ public void InstallWorkloads(IEnumerable workloadIds, bool skipManif _workloadManifestUpdater.UpdateAdvertisingManifestsAsync(includePreviews, offlineCache).Wait(); manifestsToUpdate = useRollback ? _workloadManifestUpdater.CalculateManifestRollbacks(_fromRollbackDefinition) : - _workloadManifestUpdater.CalculateManifestUpdates().Select(m => m.manifestUpdate); + _workloadManifestUpdater.CalculateManifestUpdates().Select(m => m.ManifestUpdate); } InstallWorkloadsWithInstallRecord(_workloadInstaller, workloadIds, _sdkFeatureBand, manifestsToUpdate, offlineCache, useRollback); @@ -229,7 +229,7 @@ private void InstallWorkloadsWithInstallRecord( if (usingRollback) { - UpdateInstallState(true, manifestsToUpdate); + installer.SaveInstallStateManifestVersions(sdkFeatureBand, GetInstallStateContents(manifestsToUpdate)); } }, rollback: () => @@ -249,7 +249,7 @@ private void InstallWorkloadsWithInstallRecord( private async Task> GetPackageDownloadUrlsAsync(IEnumerable workloadIds, bool skipManifestUpdate, bool includePreview) { var downloads = await GetDownloads(workloadIds, skipManifestUpdate, includePreview); - + var urls = new List(); foreach (var download in downloads) { diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/WorkloadInstallerFactory.cs b/src/Cli/dotnet/commands/dotnet-workload/install/WorkloadInstallerFactory.cs index aa1ae48eb1cc..ceb8fece65e8 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/WorkloadInstallerFactory.cs +++ b/src/Cli/dotnet/commands/dotnet-workload/install/WorkloadInstallerFactory.cs @@ -23,7 +23,8 @@ public static IInstaller GetWorkloadInstaller( string tempDirPath = null, PackageSourceLocation packageSourceLocation = null, RestoreActionConfig restoreActionConfig = null, - bool elevationRequired = true) + bool elevationRequired = true, + bool shouldLog = true) { dotnetDir = string.IsNullOrWhiteSpace(dotnetDir) ? Path.GetDirectoryName(Environment.ProcessPath) : dotnetDir; var installType = WorkloadInstallType.GetWorkloadInstallType(sdkFeatureBand, dotnetDir); @@ -34,9 +35,9 @@ public static IInstaller GetWorkloadInstaller( { throw new InvalidOperationException(LocalizableStrings.OSDoesNotSupportMsi); } - + // TODO: should restoreActionConfig be flowed through to the client here as well like it is for the FileBasedInstaller below? return NetSdkMsiInstallerClient.Create(verifySignatures, sdkFeatureBand, workloadResolver, - nugetPackageDownloader, verbosity, packageSourceLocation, reporter, tempDirPath); + nugetPackageDownloader, verbosity, packageSourceLocation, reporter, tempDirPath, shouldLog: shouldLog); } if (elevationRequired && !WorkloadFileBasedInstall.IsUserLocal(dotnetDir, sdkFeatureBand.ToString()) && !CanWriteToDotnetRoot(dotnetDir)) diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/WorkloadManifestUpdater.cs b/src/Cli/dotnet/commands/dotnet-workload/install/WorkloadManifestUpdater.cs index da18d53e4e6d..a7fe27c39f50 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/WorkloadManifestUpdater.cs +++ b/src/Cli/dotnet/commands/dotnet-workload/install/WorkloadManifestUpdater.cs @@ -22,9 +22,8 @@ internal class WorkloadManifestUpdater : IWorkloadManifestUpdater private readonly INuGetPackageDownloader _nugetPackageDownloader; private readonly SdkFeatureBand _sdkFeatureBand; private readonly string _userProfileDir; - private readonly string _tempDirPath; private readonly PackageSourceLocation _packageSourceLocation; - Func _getEnvironmentVariable; + private readonly Func _getEnvironmentVariable; private readonly IWorkloadInstallationRecordRepository _workloadRecordRepo; private readonly IWorkloadManifestInstaller _workloadManifestInstaller; private readonly bool _displayManifestUpdates; @@ -33,7 +32,6 @@ public WorkloadManifestUpdater(IReporter reporter, IWorkloadResolver workloadResolver, INuGetPackageDownloader nugetPackageDownloader, string userProfileDir, - string tempDirPath, IWorkloadInstallationRecordRepository workloadRecordRepo, IWorkloadManifestInstaller workloadManifestInstaller, PackageSourceLocation packageSourceLocation = null, @@ -44,7 +42,6 @@ public WorkloadManifestUpdater(IReporter reporter, _reporter = reporter; _workloadResolver = workloadResolver; _userProfileDir = userProfileDir; - _tempDirPath = tempDirPath; _nugetPackageDownloader = nugetPackageDownloader; _sdkFeatureBand = sdkFeatureBand ?? new SdkFeatureBand(_workloadResolver.GetSdkFeatureBand()); _packageSourceLocation = packageSourceLocation; @@ -72,15 +69,14 @@ private static WorkloadManifestUpdater GetInstance(string userProfileDir) workloadResolver, VerbosityOptions.normal, userProfileDir, verifySignatures: false); var workloadRecordRepo = installer.GetWorkloadInstallationRecordRepository(); - return new WorkloadManifestUpdater(reporter, workloadResolver, nugetPackageDownloader, userProfileDir, tempPackagesDir.Value, workloadRecordRepo, installer); + return new WorkloadManifestUpdater(reporter, workloadResolver, nugetPackageDownloader, userProfileDir, workloadRecordRepo, installer); } public async Task UpdateAdvertisingManifestsAsync(bool includePreviews, DirectoryPath? offlineCache = null) { // this updates all the manifests var manifests = _workloadResolver.GetInstalledManifests(); - await Task.WhenAll(manifests.Select(manifest => UpdateAdvertisingManifestAsync(manifest, includePreviews, offlineCache))) - .ConfigureAwait(false); + await Task.WhenAll(manifests.Select(manifest => UpdateAdvertisingManifestAsync(manifest, includePreviews, offlineCache))).ConfigureAwait(false); WriteUpdatableWorkloadsFile(); } @@ -161,37 +157,26 @@ public static void AdvertiseWorkloadUpdates() } } - public IEnumerable<( - ManifestVersionUpdate manifestUpdate, - Dictionary Workloads - )> - CalculateManifestUpdates() + public IEnumerable CalculateManifestUpdates() { - var manifestUpdates = - new List<(ManifestVersionUpdate manifestUpdate, - Dictionary Workloads)>(); var currentManifestIds = GetInstalledManifestIds(); foreach (var manifestId in currentManifestIds) { - var currentManifestVersion = GetInstalledManifestVersion(manifestId); - var advertisingManifestVersionAndWorkloads = GetAdvertisingManifestVersionAndWorkloads(manifestId); - if (advertisingManifestVersionAndWorkloads == null) + var advertisingInfo = GetAdvertisingManifestVersionAndWorkloads(manifestId); + if (advertisingInfo == null) { continue; } - if (advertisingManifestVersionAndWorkloads != null && - ((advertisingManifestVersionAndWorkloads.Value.ManifestVersion.CompareTo(currentManifestVersion.manifestVersion) > 0 - && advertisingManifestVersionAndWorkloads.Value.ManifestFeatureBand.Equals(currentManifestVersion.sdkFeatureBand)) || - advertisingManifestVersionAndWorkloads.Value.ManifestFeatureBand.CompareTo(currentManifestVersion.sdkFeatureBand) > 0)) + var (installedVersion, installedBand) = GetInstalledManifestVersion(manifestId); + var ((adVersion, adBand), adWorkloads) = advertisingInfo.Value; + if ((adVersion.CompareTo(installedVersion) > 0 && adBand.Equals(installedBand)) || + adBand.CompareTo(installedBand) > 0) { - manifestUpdates.Add((new ManifestVersionUpdate(manifestId, currentManifestVersion.manifestVersion, currentManifestVersion.sdkFeatureBand.ToString(), - advertisingManifestVersionAndWorkloads.Value.ManifestVersion, advertisingManifestVersionAndWorkloads.Value.ManifestFeatureBand.ToString()), - advertisingManifestVersionAndWorkloads.Value.Workloads)); + var update = new ManifestVersionUpdate(manifestId, installedVersion, installedBand.ToString(), adVersion, adBand.ToString()); + yield return new(update, adWorkloads); } } - - return manifestUpdates; } public IEnumerable GetUpdatableWorkloadsToAdvertise(IEnumerable installedWorkloads) @@ -213,25 +198,23 @@ public IEnumerable CalculateManifestRollbacks(string roll var currentManifestIds = GetInstalledManifestIds(); var manifestRollbacks = ParseRollbackDefinitionFile(rollbackDefinitionFilePath); - var unrecognizedManifestIds = manifestRollbacks.Where(rollbackManifest => !currentManifestIds.Contains(rollbackManifest.Item1)); + var unrecognizedManifestIds = manifestRollbacks.Where(rollbackManifest => !currentManifestIds.Contains(rollbackManifest.Id)); if (unrecognizedManifestIds.Any()) { _reporter.WriteLine(string.Format(LocalizableStrings.RollbackDefinitionContainsExtraneousManifestIds, rollbackDefinitionFilePath, string.Join(" ", unrecognizedManifestIds)).Yellow()); - manifestRollbacks = manifestRollbacks.Where(rollbackManifest => currentManifestIds.Contains(rollbackManifest.Item1)); + manifestRollbacks = manifestRollbacks.Where(rollbackManifest => currentManifestIds.Contains(rollbackManifest.Id)); } - var manifestUpdates = manifestRollbacks - .Select(manifest => - { - var installedManifestInfo = GetInstalledManifestVersion(manifest.id); - return new ManifestVersionUpdate(manifest.id, installedManifestInfo.manifestVersion, installedManifestInfo.sdkFeatureBand.ToString(), - manifest.version, manifest.featureBand.ToString()); - }); + var manifestUpdates = manifestRollbacks.Select(manifest => + { + var (id, (version, band)) = manifest; + var (installedVersion, installedBand) = GetInstalledManifestVersion(id); + return new ManifestVersionUpdate(id, installedVersion, installedBand.ToString(), version, band.ToString()); + }); return manifestUpdates; } - public async Task> GetManifestPackageDownloadsAsync(bool includePreviews, SdkFeatureBand providedSdkFeatureBand, SdkFeatureBand installedSdkFeatureBand) { var downloads = new List(); @@ -239,97 +222,103 @@ public async Task> GetManifestPackageDownloadsAsyn { try { - var packageId = _workloadManifestInstaller.GetManifestPackageId(new ManifestId(manifest.Id), providedSdkFeatureBand); - - bool success; - // After checking the --sdk-version, check the current sdk band, and then the manifest band in that order - (success, var latestVersion) = await GetPackageVersion(packageId, packageSourceLocation: _packageSourceLocation, includePreview: includePreviews); - if (success) - { - downloads.Add(new WorkloadDownload(manifest.Id, packageId.ToString(), latestVersion.ToString())); - } - - if (!success && !installedSdkFeatureBand.Equals(providedSdkFeatureBand)) + PackageId? providedPackageId = null; + var fallbackFeatureBand = new SdkFeatureBand(manifest.ManifestFeatureBand); + // The bands should be checked in the order defined here. + SdkFeatureBand[] bands = [providedSdkFeatureBand, installedSdkFeatureBand, fallbackFeatureBand]; + var success = false; + // Use Distinct to eliminate bands that are the same. + foreach (var band in bands.Distinct()) { - var newPackageId = _workloadManifestInstaller.GetManifestPackageId(new ManifestId(manifest.Id), installedSdkFeatureBand); - - (success, latestVersion) = await GetPackageVersion(newPackageId, packageSourceLocation: _packageSourceLocation, includePreview: includePreviews); + var packageId = _workloadManifestInstaller.GetManifestPackageId(new ManifestId(manifest.Id), band); + providedPackageId ??= packageId; - if (success) + try { - downloads.Add(new WorkloadDownload(manifest.Id, newPackageId.ToString(), latestVersion.ToString())); + var latestVersion = await _nugetPackageDownloader.GetLatestPackageVersion(packageId, _packageSourceLocation, includePreviews); + success = true; + downloads.Add(new WorkloadDownload(manifest.Id, packageId.ToString(), latestVersion.ToString())); + break; } - } - var fallbackFeatureBand = new SdkFeatureBand(manifest.ManifestFeatureBand); - if (!success && !fallbackFeatureBand.Equals(installedSdkFeatureBand)) - { - var newPackageId = _workloadManifestInstaller.GetManifestPackageId(new ManifestId(manifest.Id), fallbackFeatureBand); - - (success, latestVersion) = await GetPackageVersion(newPackageId, packageSourceLocation: _packageSourceLocation, includePreview: includePreviews); - - if (success) + catch (NuGetPackageNotFoundException) { - downloads.Add(new WorkloadDownload(manifest.Id, newPackageId.ToString(), latestVersion.ToString())); } } + if (!success) { - _reporter.WriteLine(string.Format(LocalizableStrings.ManifestPackageUrlNotResolved, packageId)); + _reporter.WriteLine(LocalizableStrings.ManifestPackageUrlNotResolved, providedPackageId); } } catch { - _reporter.WriteLine(string.Format(LocalizableStrings.ManifestPackageUrlNotResolved, manifest.Id)); + _reporter.WriteLine(LocalizableStrings.ManifestPackageUrlNotResolved, manifest.Id); } } + return downloads; } - private IEnumerable GetInstalledManifestIds() - { - return _workloadResolver.GetInstalledManifests().Select(manifest => new ManifestId(manifest.Id)); - } + private IEnumerable GetInstalledManifestIds() => _workloadResolver.GetInstalledManifests().Select(manifest => new ManifestId(manifest.Id)); private async Task UpdateAdvertisingManifestAsync(WorkloadManifestInfo manifest, bool includePreviews, DirectoryPath? offlineCache = null) { string packagePath = null; var manifestId = new ManifestId(manifest.Id); - string currentFeatureBand = _sdkFeatureBand.ToString(); - try { - var adManifestPath = GetAdvertisingManifestPath(_sdkFeatureBand, manifestId); - - bool success; - (success, packagePath) = await GetManifestPackageUpdate(_sdkFeatureBand, manifestId, includePreviews, offlineCache); - if (!success) + SdkFeatureBand? currentFeatureBand = null; + var fallbackFeatureBand = new SdkFeatureBand(manifest.ManifestFeatureBand); + // The bands should be checked in the order defined here. + SdkFeatureBand[] bands = [_sdkFeatureBand, fallbackFeatureBand]; + var success = false; + // Use Distinct to eliminate bands that are the same. + foreach (var band in bands.Distinct()) { - if (!(manifest.ManifestFeatureBand).Equals(_sdkFeatureBand)) + var manifestPackageId = _workloadManifestInstaller.GetManifestPackageId(manifestId, band); + currentFeatureBand = band; + + try + { + // If an offline cache is present, use that. Otherwise, try to acquire the package online. + packagePath = offlineCache != null ? + Directory.GetFiles(offlineCache.Value.Value) + .Where(path => path.EndsWith(".nupkg") && Path.GetFileName(path).StartsWith(manifestPackageId.ToString(), StringComparison.OrdinalIgnoreCase)) + .Max() : + await _nugetPackageDownloader.DownloadPackageAsync(manifestPackageId, packageSourceLocation: _packageSourceLocation, includePreview: includePreviews); + + if (packagePath != null) + { + success = true; + break; + } + } + catch (NuGetPackageNotFoundException) { - (success, packagePath) = await GetManifestPackageUpdate(new SdkFeatureBand(manifest.ManifestFeatureBand), manifestId, includePreviews, offlineCache); - currentFeatureBand = manifest.ManifestFeatureBand.ToString(); } } + if (!success) { - _reporter.WriteLine(string.Format(LocalizableStrings.AdManifestPackageDoesNotExist, manifestId)); + _reporter.WriteLine(LocalizableStrings.AdManifestPackageDoesNotExist, manifestId); return; } + var adManifestPath = GetAdvertisingManifestPath(_sdkFeatureBand, manifestId); await _workloadManifestInstaller.ExtractManifestAsync(packagePath, adManifestPath); - // add file that contains the advertisted manifest feature band so GetAdvertisingManifestVersionAndWorkloads will use correct feature band, regardless of if rollback occurred or not - File.WriteAllText(Path.Combine(adManifestPath, "AdvertisedManifestFeatureBand.txt"), currentFeatureBand); + // add file that contains the advertised manifest feature band so GetAdvertisingManifestVersionAndWorkloads will use correct feature band, regardless of if rollback occurred or not + File.WriteAllText(Path.Combine(adManifestPath, "AdvertisedManifestFeatureBand.txt"), currentFeatureBand.ToString()); if (_displayManifestUpdates) { - _reporter.WriteLine(string.Format(LocalizableStrings.AdManifestUpdated, manifestId)); + _reporter.WriteLine(LocalizableStrings.AdManifestUpdated, manifestId); } } catch (Exception e) { - _reporter.WriteLine(string.Format(LocalizableStrings.FailedAdManifestUpdate, manifestId, e.Message)); + _reporter.WriteLine(LocalizableStrings.FailedAdManifestUpdate, manifestId, e.Message); } finally { @@ -354,51 +343,45 @@ private async Task UpdateAdvertisingManifestAsync(WorkloadManifestInfo manifest, } } - private (ManifestVersion ManifestVersion, SdkFeatureBand ManifestFeatureBand, Dictionary Workloads)? - GetAdvertisingManifestVersionAndWorkloads(ManifestId manifestId) + private (ManifestVersionWithBand ManifestWithBand, WorkloadCollection Workloads)? GetAdvertisingManifestVersionAndWorkloads(ManifestId manifestId) { - var manifestPath = Path.Combine(GetAdvertisingManifestPath(_sdkFeatureBand, manifestId), - "WorkloadManifest.json"); + var manifestPath = Path.Combine(GetAdvertisingManifestPath(_sdkFeatureBand, manifestId), "WorkloadManifest.json"); if (!File.Exists(manifestPath)) { return null; } - using (FileStream fsSource = new FileStream(manifestPath, FileMode.Open, FileAccess.Read)) - { - var manifest = WorkloadManifestReader.ReadWorkloadManifest(manifestId.ToString(), fsSource, manifestPath); - // we need to know the feature band of the advertised manifest (read it from the AdvertisedManifestFeatureBand.txt file) - // if we don't find the file then use the current feature band - var adManifestFeatureBandPath = Path.Combine(GetAdvertisingManifestPath(_sdkFeatureBand, manifestId), "AdvertisedManifestFeatureBand.txt"); - - SdkFeatureBand adManifestFeatureBand = _sdkFeatureBand; - if (File.Exists(adManifestFeatureBandPath)) - { - adManifestFeatureBand = new SdkFeatureBand(File.ReadAllText(adManifestFeatureBandPath)); - } - + using FileStream fsSource = new(manifestPath, FileMode.Open, FileAccess.Read); + var manifest = WorkloadManifestReader.ReadWorkloadManifest(manifestId.ToString(), fsSource, manifestPath); + // we need to know the feature band of the advertised manifest (read it from the AdvertisedManifestFeatureBand.txt file) + // if we don't find the file then use the current feature band + var adManifestFeatureBandPath = Path.Combine(GetAdvertisingManifestPath(_sdkFeatureBand, manifestId), "AdvertisedManifestFeatureBand.txt"); - return (new ManifestVersion(manifest.Version), adManifestFeatureBand, manifest.Workloads.Values.OfType().ToDictionary(w => w.Id)); + SdkFeatureBand adManifestFeatureBand = _sdkFeatureBand; + if (File.Exists(adManifestFeatureBandPath)) + { + adManifestFeatureBand = new SdkFeatureBand(File.ReadAllText(adManifestFeatureBandPath)); } + + ManifestVersionWithBand manifestWithBand = new(new ManifestVersion(manifest.Version), adManifestFeatureBand); + var workloads = manifest.Workloads.Values.OfType().ToDictionary(w => w.Id); + return (manifestWithBand, workloads); } - private (ManifestVersion manifestVersion, SdkFeatureBand sdkFeatureBand) GetInstalledManifestVersion(ManifestId manifestId) + private ManifestVersionWithBand GetInstalledManifestVersion(ManifestId manifestId) { - - var manifest = _workloadResolver.GetInstalledManifests() - .FirstOrDefault(manifest => manifest.Id.ToLowerInvariant().Equals(manifestId.ToString())); + var manifest = _workloadResolver.GetInstalledManifests().FirstOrDefault(manifest => manifest.Id.ToLowerInvariant().Equals(manifestId.ToString())); if (manifest == null) { throw new Exception(string.Format(LocalizableStrings.ManifestDoesNotExist, manifestId.ToString())); } - return (new ManifestVersion(manifest.Version), new SdkFeatureBand(manifest.ManifestFeatureBand)); + return new(new ManifestVersion(manifest.Version), new SdkFeatureBand(manifest.ManifestFeatureBand)); } private bool AdManifestSentinelIsDueForUpdate() { var sentinelPath = GetAdvertisingManifestSentinelPath(_sdkFeatureBand); - int updateIntervalHours; - if (!int.TryParse(_getEnvironmentVariable(EnvironmentVariableNames.WORKLOAD_UPDATE_NOTIFY_INTERVAL_HOURS), out updateIntervalHours)) + if (!int.TryParse(_getEnvironmentVariable(EnvironmentVariableNames.WORKLOAD_UPDATE_NOTIFY_INTERVAL_HOURS), out int updateIntervalHours)) { updateIntervalHours = 24; } @@ -418,8 +401,7 @@ private bool AdManifestSentinelIsDueForUpdate() private async Task UpdatedAdManifestPackagesExistAsync() { var manifests = GetInstalledManifestIds(); - var availableUpdates = await Task.WhenAll(manifests.Select(manifest => NewerManifestPackageExists(manifest))) - .ConfigureAwait(false); + var availableUpdates = await Task.WhenAll(manifests.Select(manifest => NewerManifestPackageExists(manifest))).ConfigureAwait(false); return availableUpdates.Any(); } @@ -437,7 +419,7 @@ private async Task NewerManifestPackageExists(ManifestId manifest) } } - private IEnumerable<(ManifestId id, ManifestVersion version, SdkFeatureBand featureBand)> ParseRollbackDefinitionFile(string rollbackDefinitionFilePath) + private IEnumerable<(ManifestId Id, ManifestVersionWithBand ManifestWithBand)> ParseRollbackDefinitionFile(string rollbackDefinitionFilePath) { string fileContent; @@ -457,12 +439,11 @@ private async Task NewerManifestPackageExists(ManifestId manifest) } } - return WorkloadSet.FromJson(fileContent, _sdkFeatureBand).ManifestVersions - .Select(kvp => (kvp.Key, kvp.Value.Version, kvp.Value.FeatureBand)); + var versions = WorkloadSet.FromJson(fileContent, _sdkFeatureBand).ManifestVersions; + return versions.Select(kvp => (kvp.Key, new ManifestVersionWithBand(kvp.Value.Version, kvp.Value.FeatureBand))); } - private bool BackgroundUpdatesAreDisabled() => - bool.TryParse(_getEnvironmentVariable(EnvironmentVariableNames.WORKLOAD_UPDATE_NOTIFY_DISABLE), out var disableEnvVar) && disableEnvVar; + private bool BackgroundUpdatesAreDisabled() => bool.TryParse(_getEnvironmentVariable(EnvironmentVariableNames.WORKLOAD_UPDATE_NOTIFY_DISABLE), out var disableEnvVar) && disableEnvVar; private string GetAdvertisingManifestSentinelPath(SdkFeatureBand featureBand) => Path.Combine(_userProfileDir, $".workloadAdvertisingManifestSentinel{featureBand}"); @@ -470,67 +451,8 @@ private bool BackgroundUpdatesAreDisabled() => private static string GetAdvertisingWorkloadsFilePath(string userProfileDir, SdkFeatureBand featureBand) => Path.Combine(userProfileDir, $".workloadAdvertisingUpdates{featureBand}"); - private async Task GetOnlinePackagePath(SdkFeatureBand sdkFeatureBand, ManifestId manifestId, bool includePreviews) - { - string packagePath = await _nugetPackageDownloader.DownloadPackageAsync( - _workloadManifestInstaller.GetManifestPackageId(manifestId, sdkFeatureBand), - packageSourceLocation: _packageSourceLocation, - includePreview: includePreviews); - - return packagePath; - } - - private string GetOfflinePackagePath(SdkFeatureBand sdkFeatureBand, ManifestId manifestId, DirectoryPath? offlineCache = null) - { - string packagePath = Directory.GetFiles(offlineCache.Value.Value) - .Where(path => path.EndsWith(".nupkg")) - .Where(path => - { - var manifestPackageId = _workloadManifestInstaller.GetManifestPackageId(manifestId, sdkFeatureBand).ToString(); - return Path.GetFileName(path).StartsWith(manifestPackageId, StringComparison.OrdinalIgnoreCase); - }) - .Max(); - - return packagePath; - } - - private async Task<(bool, string)> GetManifestPackageUpdate(SdkFeatureBand sdkFeatureBand, ManifestId manifestId, bool includePreviews, DirectoryPath? offlineCache = null) - { - if (offlineCache == null || !offlineCache.HasValue) - { - try - { - string packagePath = await GetOnlinePackagePath(sdkFeatureBand, manifestId, includePreviews); - return (true, packagePath); - } - catch (NuGetPackageNotFoundException) - { - return (false, null); - } - } - else - { - string packagePath = GetOfflinePackagePath(sdkFeatureBand, manifestId, offlineCache); - return (packagePath != null, packagePath); - } - } - - private async Task<(bool, NuGetVersion)> GetPackageVersion(PackageId packageId, PackageSourceLocation packageSourceLocation = null, bool includePreview = false) - { - try - { - var latestVersion = await _nugetPackageDownloader.GetLatestPackageVersion(packageId, packageSourceLocation: _packageSourceLocation, includePreview: includePreview); - return (true, latestVersion); - } - catch (NuGetPackageNotFoundException) - { - return (false, null); - } - - } - + private string GetAdvertisingManifestPath(SdkFeatureBand featureBand, ManifestId manifestId) => Path.Combine(_userProfileDir, "sdk-advertising", featureBand.ToString(), manifestId.ToString()); - private string GetAdvertisingManifestPath(SdkFeatureBand featureBand, ManifestId manifestId) => - Path.Combine(_userProfileDir, "sdk-advertising", featureBand.ToString(), manifestId.ToString()); + private record ManifestVersionWithBand(ManifestVersion Version, SdkFeatureBand Band); } } diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.cs.xlf b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.cs.xlf index 9f8d6f8e2699..720a2aefe836 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.cs.xlf +++ b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.cs.xlf @@ -362,6 +362,11 @@ CESTA + + Control whether future workload operations should use workload sets or loose manifests. + UrÄete, jestli by budoucí operace úloh mÄ›ly používat sady úloh nebo volné manifesty. + + Workload updates are available. Run `dotnet workload list` for more information. Jsou k dispozici aktualizace úloh. Pokud chcete získat další informace, spusÅ¥te `dotnet workload list`. diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.de.xlf b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.de.xlf index 23d61efe36f7..f412e4e21584 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.de.xlf +++ b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.de.xlf @@ -362,6 +362,11 @@ PFAD + + Control whether future workload operations should use workload sets or loose manifests. + Hiermit wird gesteuert, ob zukünftige Workloadvorgänge Workloadsätze oder lose Manifeste verwenden sollen. + + Workload updates are available. Run `dotnet workload list` for more information. Es sind Workloadupdates verfügbar. Um weitere Informationen zu erhalten, führen Sie `dotnet workload list` aus. diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.es.xlf b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.es.xlf index 056fb8cbeeaf..661379832a29 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.es.xlf +++ b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.es.xlf @@ -362,6 +362,11 @@ RUTA DE ACCESO + + Control whether future workload operations should use workload sets or loose manifests. + Controle si las operaciones de carga de trabajo futuras deben usar conjuntos de cargas de trabajo o manifiestos flexibles. + + Workload updates are available. Run `dotnet workload list` for more information. Hay actualizaciones de carga de trabajo disponibles. Ejecute "dotnet workload list" para obtener más información. diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.fr.xlf b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.fr.xlf index 879b2659c9fd..1462fa793077 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.fr.xlf +++ b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.fr.xlf @@ -362,6 +362,11 @@ PATH + + Control whether future workload operations should use workload sets or loose manifests. + Contrôlez si les futures opérations de charge de travail doivent utiliser des ensembles de charges de travail ou des manifestes lâches. + + Workload updates are available. Run `dotnet workload list` for more information. Des mises à jour de la charge de travail sont disponibles. Exécutez `dotnet workload list` pour plus d’informations. diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.it.xlf b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.it.xlf index b7ba22c5001c..2f9f807b9fa6 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.it.xlf +++ b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.it.xlf @@ -362,6 +362,11 @@ PERCORSO + + Control whether future workload operations should use workload sets or loose manifests. + Controllare se le operazioni future del carico di lavoro devono usare set di carichi di lavoro o manifesti separati. + + Workload updates are available. Run `dotnet workload list` for more information. Sono disponibili aggiornamenti del carico di lavoro. Per altre informazioni, eseguire `dotnet workload list`. diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.ja.xlf b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.ja.xlf index ed50daa56103..93aa4172e61b 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.ja.xlf +++ b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.ja.xlf @@ -362,6 +362,11 @@ パス + + Control whether future workload operations should use workload sets or loose manifests. + å°†æ¥ã®ãƒ¯ãƒ¼ã‚¯ãƒ­ãƒ¼ãƒ‰æ“作ã§ãƒ¯ãƒ¼ã‚¯ãƒ­ãƒ¼ãƒ‰ セットを使用ã™ã‚‹ã‹ã€ãƒ«ãƒ¼ã‚º マニフェストを使用ã™ã‚‹ã‹ã‚’制御ã—ã¾ã™ã€‚ + + Workload updates are available. Run `dotnet workload list` for more information. ãƒ¯ãƒ¼ã‚¯ãƒ­ãƒ¼ãƒ‰ã®æ›´æ–°ãŒåˆ©ç”¨å¯èƒ½ã§ã™ã€‚詳細ã«ã¤ã„ã¦ã¯ã€`dotnet workload list` を実行ã—ã¦ãã ã•ã„。 diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.ko.xlf b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.ko.xlf index a46019514be2..dcfa49035630 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.ko.xlf +++ b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.ko.xlf @@ -362,6 +362,11 @@ 경로 + + Control whether future workload operations should use workload sets or loose manifests. + 향후 워í¬ë¡œë“œ 작업ì—서 워í¬ë¡œë“œ ì§‘í•©ì„ ì‚¬ìš©í• ì§€, 매니페스트를 완화할지를 제어합니다. + + Workload updates are available. Run `dotnet workload list` for more information. 워í¬ë¡œë“œ ì—…ë°ì´íŠ¸ë¥¼ 사용할 수 있습니다. ìžì„¸í•œ ë‚´ìš©ì„ ë³´ë ¤ë©´ `dotnet workload list`ì„ ì‹¤í–‰í•˜ì„¸ìš”. diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.pl.xlf b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.pl.xlf index 94697cd0040d..6325acdc4b93 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.pl.xlf +++ b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.pl.xlf @@ -362,6 +362,11 @@ ÅšCIEÅ»KA + + Control whether future workload operations should use workload sets or loose manifests. + OkreÅ›l, czy przyszÅ‚e operacje zwiÄ…zane z obciążeniami powinny wykorzystywać zestawy obciążeÅ„, czy luźne manifesty. + + Workload updates are available. Run `dotnet workload list` for more information. DostÄ™pne sÄ… aktualizacje obciążenia. Uruchom polecenie `dotnet workload list`, aby uzyskać wiÄ™cej informacji. diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.pt-BR.xlf b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.pt-BR.xlf index a3f860528e7e..8c03cca867d1 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.pt-BR.xlf +++ b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.pt-BR.xlf @@ -362,6 +362,11 @@ CAMINHO + + Control whether future workload operations should use workload sets or loose manifests. + Controle se as operações de carga de trabalho futuras devem usar conjuntos de carga de trabalho ou manifestos flexíveis. + + Workload updates are available. Run `dotnet workload list` for more information. As atualizações de carga de trabalho estão disponíveis. Execute `dotnet workload list` para obter mais informações. diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.ru.xlf b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.ru.xlf index bc3ec05f6d96..b4940992ccc1 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.ru.xlf +++ b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.ru.xlf @@ -362,6 +362,11 @@ PATH + + Control whether future workload operations should use workload sets or loose manifests. + Укажите, должны ли будущие операции рабочей нагрузки иÑпользовать наборы рабочей нагрузки или Ñвободные манифеÑты. + + Workload updates are available. Run `dotnet workload list` for more information. ДоÑтупны Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ Ñ€Ð°Ð±Ð¾Ñ‡ÐµÐ¹ нагрузки. Ð”Ð»Ñ Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð´Ð¾Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ñ‹Ñ… Ñведений запуÑтите `dotnet workload list`. diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.tr.xlf b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.tr.xlf index c2cd403c021b..3691d8eb871c 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.tr.xlf +++ b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.tr.xlf @@ -362,6 +362,11 @@ YOL + + Control whether future workload operations should use workload sets or loose manifests. + Gelecekteki iÅŸ yükü iÅŸlemlerinin iÅŸ yükü kümelerini mi yoksa gevÅŸek bildirimleri mi kullanması gerektiÄŸini kontrol edin. + + Workload updates are available. Run `dotnet workload list` for more information. İş yükü güncelleÅŸtirmeleri var. Daha fazla bilgi için `dotnet workload list` çalıştırın. diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.zh-Hans.xlf b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.zh-Hans.xlf index 58ce764a255c..60dce47d2925 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.zh-Hans.xlf +++ b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.zh-Hans.xlf @@ -362,6 +362,11 @@ 路径 + + Control whether future workload operations should use workload sets or loose manifests. + 控制未æ¥çš„工作负载æ“ä½œåº”è¯¥ä½¿ç”¨å·¥ä½œè´Ÿè½½é›†è¿˜æ˜¯æ¾æ•£æ¸…å•。 + + Workload updates are available. Run `dotnet workload list` for more information. 有å¯ç”¨çš„工作负载更新。有关详细信æ¯ï¼Œè¯·è¿è¡Œ `dotnet workload list`。 diff --git a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.zh-Hant.xlf b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.zh-Hant.xlf index d1e4bc9f6655..4921d99e1aab 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.zh-Hant.xlf +++ b/src/Cli/dotnet/commands/dotnet-workload/install/xlf/LocalizableStrings.zh-Hant.xlf @@ -362,6 +362,11 @@ 路徑 + + Control whether future workload operations should use workload sets or loose manifests. + 控制未來的工作負載作業應該使用工作負載集åˆé‚„是鬆散資訊清單。 + + Workload updates are available. Run `dotnet workload list` for more information. 有å¯ç”¨çš„工作負載更新。如需詳細資訊,請執行 `dotnet workload list`。 diff --git a/src/Cli/dotnet/commands/dotnet-workload/list/WorkloadListCommand.cs b/src/Cli/dotnet/commands/dotnet-workload/list/WorkloadListCommand.cs index 42fb65445dd7..2a3d861b517e 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/list/WorkloadListCommand.cs +++ b/src/Cli/dotnet/commands/dotnet-workload/list/WorkloadListCommand.cs @@ -54,7 +54,7 @@ public WorkloadListCommand( string userProfileDir1 = userProfileDir ?? CliFolderPathCalculator.DotnetUserProfileFolderPath; _workloadManifestUpdater = workloadManifestUpdater ?? new WorkloadManifestUpdater(Reporter, - _workloadListHelper.WorkloadResolver, PackageDownloader, userProfileDir1, TempDirectoryPath, _workloadListHelper.WorkloadRecordRepo, _workloadListHelper.Installer); + _workloadListHelper.WorkloadResolver, PackageDownloader, userProfileDir1, _workloadListHelper.WorkloadRecordRepo, _workloadListHelper.Installer); } public override int Execute() @@ -65,9 +65,9 @@ public override int Execute() { _workloadListHelper.CheckTargetSdkVersionIsValid(); - UpdateAvailableEntry[] updateAvailable = GetUpdateAvailable(installedList); - ListOutput listOutput = new(installedList.Select(id => id.ToString()).ToArray(), - updateAvailable); + var updateAvailable = GetUpdateAvailable(installedList); + var installed = installedList.Select(id => id.ToString()).ToArray(); + ListOutput listOutput = new(installed, updateAvailable.ToArray()); Reporter.WriteLine("==workloadListJsonOutputStart=="); Reporter.WriteLine( @@ -108,29 +108,23 @@ public override int Execute() return 0; } - internal UpdateAvailableEntry[] GetUpdateAvailable(IEnumerable installedList) + internal IEnumerable GetUpdateAvailable(IEnumerable installedList) { - HashSet installedWorkloads = installedList.ToHashSet(); _workloadManifestUpdater.UpdateAdvertisingManifestsAsync(_includePreviews).Wait(); - var manifestsToUpdate = - _workloadManifestUpdater.CalculateManifestUpdates(); + var manifestsToUpdate = _workloadManifestUpdater.CalculateManifestUpdates(); - List updateList = new(); - foreach ((ManifestVersionUpdate manifestUpdate, Dictionary workloads) in manifestsToUpdate) + foreach ((ManifestVersionUpdate manifestUpdate, WorkloadCollection workloads) in manifestsToUpdate) { - foreach ((WorkloadId WorkloadId, WorkloadDefinition workloadDefinition) in - workloads) + foreach ((WorkloadId workloadId, WorkloadDefinition workloadDefinition) in workloads) { - if (installedWorkloads.Contains(new WorkloadId(WorkloadId.ToString()))) + if (installedList.Contains(workloadId)) { - updateList.Add(new UpdateAvailableEntry(manifestUpdate.ExistingVersion.ToString(), + yield return new UpdateAvailableEntry(manifestUpdate.ExistingVersion.ToString(), manifestUpdate.NewVersion.ToString(), - workloadDefinition.Description, WorkloadId.ToString())); + workloadDefinition.Description, workloadId.ToString()); } } } - - return updateList.ToArray(); } internal record ListOutput(string[] Installed, UpdateAvailableEntry[] UpdateAvailable); diff --git a/src/Cli/dotnet/commands/dotnet-workload/update/LocalizableStrings.resx b/src/Cli/dotnet/commands/dotnet-workload/update/LocalizableStrings.resx index 41d8c0db27ee..77709f82d798 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/update/LocalizableStrings.resx +++ b/src/Cli/dotnet/commands/dotnet-workload/update/LocalizableStrings.resx @@ -138,6 +138,9 @@ Workload update failed: {0} + + Invalid argument "{0}" to the --mode argument for dotnet workload update. Only supported modes are "workloadset", "loosemanifest", and "auto". + Include workloads installed with earlier SDK versions in update. diff --git a/src/Cli/dotnet/commands/dotnet-workload/update/WorkloadUpdateCommand.cs b/src/Cli/dotnet/commands/dotnet-workload/update/WorkloadUpdateCommand.cs index 03930eb5c5b0..a14ad7e4d00b 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/update/WorkloadUpdateCommand.cs +++ b/src/Cli/dotnet/commands/dotnet-workload/update/WorkloadUpdateCommand.cs @@ -19,6 +19,7 @@ internal class WorkloadUpdateCommand : InstallingWorkloadCommand private readonly bool _adManifestOnlyOption; private readonly bool _printRollbackDefinitionOnly; private readonly bool _fromPreviousSdk; + private readonly string _workloadSetMode; public WorkloadUpdateCommand( ParseResult parseResult, @@ -36,13 +37,14 @@ public WorkloadUpdateCommand( _fromPreviousSdk = parseResult.GetValue(WorkloadUpdateCommandParser.FromPreviousSdkOption); _adManifestOnlyOption = parseResult.GetValue(WorkloadUpdateCommandParser.AdManifestOnlyOption); _printRollbackDefinitionOnly = parseResult.GetValue(WorkloadUpdateCommandParser.PrintRollbackOption); + _workloadSetMode = parseResult.GetValue(InstallingWorkloadCommandParser.WorkloadSetMode); _workloadInstaller = _workloadInstallerFromConstructor ?? WorkloadInstallerFactory.GetWorkloadInstaller(Reporter, _sdkFeatureBand, _workloadResolver, Verbosity, _userProfileDir, VerifySignatures, PackageDownloader, _dotnetPath, TempDirectoryPath, packageSourceLocation: _packageSourceLocation, RestoreActionConfiguration, elevationRequired: !_printDownloadLinkOnly && !_printRollbackDefinitionOnly && string.IsNullOrWhiteSpace(_downloadToCacheOption)); - _workloadManifestUpdater = _workloadManifestUpdaterFromConstructor ?? new WorkloadManifestUpdater(Reporter, _workloadResolver, PackageDownloader, _userProfileDir, TempDirectoryPath, + _workloadManifestUpdater = _workloadManifestUpdaterFromConstructor ?? new WorkloadManifestUpdater(Reporter, _workloadResolver, PackageDownloader, _userProfileDir, _workloadInstaller.GetWorkloadInstallationRecordRepository(), _workloadInstaller, _packageSourceLocation, sdkFeatureBand: _sdkFeatureBand); } @@ -80,6 +82,22 @@ public override int Execute() Reporter.WriteLine(workloadSet.ToJson()); Reporter.WriteLine("==workloadRollbackDefinitionJsonOutputEnd=="); } + else if (!string.IsNullOrWhiteSpace(_workloadSetMode)) + { + if (_workloadSetMode.Equals("workloadset", StringComparison.OrdinalIgnoreCase)) + { + _workloadInstaller.UpdateInstallMode(_sdkFeatureBand, true); + } + else if (_workloadSetMode.Equals("loosemanifest", StringComparison.OrdinalIgnoreCase) || + _workloadSetMode.Equals("auto", StringComparison.OrdinalIgnoreCase)) + { + _workloadInstaller.UpdateInstallMode(_sdkFeatureBand, false); + } + else + { + throw new GracefulException(string.Format(LocalizableStrings.WorkloadSetModeTakesWorkloadSetLooseManifestOrAuto, _workloadSetMode), isUserError: true); + } + } else { try @@ -108,7 +126,7 @@ public void UpdateWorkloads(bool includePreviews = false, DirectoryPath? offline var manifestsToUpdate = useRollback ? _workloadManifestUpdater.CalculateManifestRollbacks(_fromRollbackDefinition) : - _workloadManifestUpdater.CalculateManifestUpdates().Select(m => m.manifestUpdate); + _workloadManifestUpdater.CalculateManifestUpdates().Select(m => m.ManifestUpdate); UpdateWorkloadsWithInstallRecord(_sdkFeatureBand, manifestsToUpdate, useRollback, offlineCache); @@ -143,11 +161,9 @@ private void UpdateWorkloadsWithInstallRecord( transaction.Run( action: context => { - bool rollback = !string.IsNullOrWhiteSpace(_fromRollbackDefinition); - foreach (var manifestUpdate in manifestsToUpdate) { - _workloadInstaller.InstallWorkloadManifest(manifestUpdate, context, offlineCache, rollback); + _workloadInstaller.InstallWorkloadManifest(manifestUpdate, context, offlineCache, useRollback); } _workloadResolver.RefreshWorkloadManifests(); @@ -156,7 +172,14 @@ private void UpdateWorkloadsWithInstallRecord( _workloadInstaller.InstallWorkloads(workloads, sdkFeatureBand, context, offlineCache); - UpdateInstallState(useRollback, manifestsToUpdate); + if (useRollback) + { + _workloadInstaller.SaveInstallStateManifestVersions(sdkFeatureBand, GetInstallStateContents(manifestsToUpdate)); + } + else + { + _workloadInstaller.RemoveManifestsFromInstallState(sdkFeatureBand); + } }, rollback: () => { diff --git a/src/Cli/dotnet/commands/dotnet-workload/update/WorkloadUpdateCommandParser.cs b/src/Cli/dotnet/commands/dotnet-workload/update/WorkloadUpdateCommandParser.cs index 496c49697e59..fe068fc74b70 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/update/WorkloadUpdateCommandParser.cs +++ b/src/Cli/dotnet/commands/dotnet-workload/update/WorkloadUpdateCommandParser.cs @@ -47,6 +47,7 @@ private static CliCommand ConstructCommand() command.Options.Add(CommonOptions.VerbosityOption); command.Options.Add(PrintRollbackOption); command.Options.Add(WorkloadInstallCommandParser.SkipSignCheckOption); + command.Options.Add(InstallingWorkloadCommandParser.WorkloadSetMode); command.SetAction((parseResult) => new WorkloadUpdateCommand(parseResult).Execute()); diff --git a/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.cs.xlf b/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.cs.xlf index 11b89e60bc76..0d57937d6b65 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.cs.xlf +++ b/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.cs.xlf @@ -52,6 +52,11 @@ Nepovedlo se stáhnout balíÄky aktualizace úlohy do mezipamÄ›ti: {0}. + + Invalid argument "{0}" to the --mode argument for dotnet workload update. Only supported modes are "workloadset", "loosemanifest", and "auto". + Neplatný argument „{0}“ argumentu --mode pro aktualizaci úlohy dotnet. Jediné podporované režimy jsou workloadset, loosemanifest a auto. + + Successfully updated advertising manifests. Manifesty reklamy se úspěšnÄ› aktualizovaly. diff --git a/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.de.xlf b/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.de.xlf index d2a0c6c462bf..7fe3755912f5 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.de.xlf +++ b/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.de.xlf @@ -52,6 +52,11 @@ Fehler beim Herunterladen von Paketen zur Workloadaktualisierung in den Cache: {0} + + Invalid argument "{0}" to the --mode argument for dotnet workload update. Only supported modes are "workloadset", "loosemanifest", and "auto". + Ungültiges Argument "{0}" zum Argument --mode für das Dotnet Workload-Update. Es werden nur die Modi "workloadset", "loosemanifest" und "auto" unterstützt. + + Successfully updated advertising manifests. Werbemanifeste wurden erfolgreich aktualisiert. diff --git a/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.es.xlf b/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.es.xlf index 8662a5c2a22b..61eacfcb2be0 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.es.xlf +++ b/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.es.xlf @@ -52,6 +52,11 @@ No se pudieron descargar los paquetes de actualización de la carga de trabajo en caché: {0} + + Invalid argument "{0}" to the --mode argument for dotnet workload update. Only supported modes are "workloadset", "loosemanifest", and "auto". + Argumento "{0}" no válido para el argumento --mode para la actualización de la carga de trabajo de dotnet. Solo los modos admitidos son "workloadset", "loosemanifest" y "auto". + + Successfully updated advertising manifests. Los manifiestos de publicidad se han actualizado correctamente. diff --git a/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.fr.xlf b/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.fr.xlf index b5a33d8a6244..ffa504cef235 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.fr.xlf +++ b/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.fr.xlf @@ -52,6 +52,11 @@ Échec du téléchargement des packages de mise à jour de charge de travail dans le cache : {0} + + Invalid argument "{0}" to the --mode argument for dotnet workload update. Only supported modes are "workloadset", "loosemanifest", and "auto". + Argument «{0}» non valide à l’argument --mode pour la mise à jour de charge de travail dotnet. Seuls les modes pris en charge sont « workloadset », « loosemanifest » et « auto ». + + Successfully updated advertising manifests. Les manifestes de publicité ont été mis à jour. diff --git a/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.it.xlf b/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.it.xlf index 72fc4b08f96d..0d9403d54fe4 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.it.xlf +++ b/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.it.xlf @@ -52,6 +52,11 @@ Non è stato possibile scaricare i pacchetti di aggiornamento del carico di lavoro nella cache: {0} + + Invalid argument "{0}" to the --mode argument for dotnet workload update. Only supported modes are "workloadset", "loosemanifest", and "auto". + Argomento non valido "{0}" per l'argomento --mode per l'aggiornamento del carico di lavoro dotnet. Le uniche modalità supportate sono "workloadset", "loosemanifest" e "auto". + + Successfully updated advertising manifests. I manifesti pubblicitari sono stati aggiornati. diff --git a/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.ja.xlf b/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.ja.xlf index 0fd3558e55b9..a3555167d604 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.ja.xlf +++ b/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.ja.xlf @@ -52,6 +52,11 @@ ワークロード更新パッケージをキャッシュã«ãƒ€ã‚¦ãƒ³ãƒ­ãƒ¼ãƒ‰ã§ãã¾ã›ã‚“ã§ã—ãŸ: {0} + + Invalid argument "{0}" to the --mode argument for dotnet workload update. Only supported modes are "workloadset", "loosemanifest", and "auto". + .NET ワークロード更新㮠--mode 引数ã«å¯¾ã™ã‚‹å¼•æ•° "{0}" ãŒç„¡åйã§ã™ã€‚サãƒãƒ¼ãƒˆã•れã¦ã„るモードã¯ã€"workloadset"ã€"loosemanifest"ã€ãŠã‚ˆã³ "auto" ã®ã¿ã§ã™ã€‚ + + Successfully updated advertising manifests. åºƒå‘Šãƒžãƒ‹ãƒ•ã‚§ã‚¹ãƒˆã‚’æ­£å¸¸ã«æ›´æ–°ã—ã¾ã—ãŸã€‚ diff --git a/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.ko.xlf b/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.ko.xlf index 5496e044b69e..1f9d499812c4 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.ko.xlf +++ b/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.ko.xlf @@ -52,6 +52,11 @@ ìºì‹œí•  워í¬ë¡œë“œ ì—…ë°ì´íЏ 패키지를 다운로드하지 못했습니다. {0} + + Invalid argument "{0}" to the --mode argument for dotnet workload update. Only supported modes are "workloadset", "loosemanifest", and "auto". + dotnet 워í¬ë¡œë“œ ì—…ë°ì´íŠ¸ì˜ --mode ì¸ìˆ˜ì— 대한 "{0}" ì¸ìˆ˜ê°€ 잘못ë˜ì—ˆìŠµë‹ˆë‹¤. "workloadset", "loosemanifest", "auto" 모드만 ì§€ì›ë©ë‹ˆë‹¤. + + Successfully updated advertising manifests. 알림 매니페스트를 ì—…ë°ì´íŠ¸í–ˆìŠµë‹ˆë‹¤. diff --git a/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.pl.xlf b/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.pl.xlf index b1248dedd4f5..222d233a4c10 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.pl.xlf +++ b/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.pl.xlf @@ -52,6 +52,11 @@ Nie można pobrać pakietów aktualizacji pakietów roboczych do pamiÄ™ci podrÄ™cznej: {0} + + Invalid argument "{0}" to the --mode argument for dotnet workload update. Only supported modes are "workloadset", "loosemanifest", and "auto". + NieprawidÅ‚owy argument „{0}†argumentu --mode dla aktualizacji obciążenia dotnet. ObsÅ‚ugiwane tryby to „workloadsetâ€, „loosemanifest†i „autoâ€. + + Successfully updated advertising manifests. PomyÅ›lnie zaktualizowano manifesty reklam. diff --git a/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.pt-BR.xlf b/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.pt-BR.xlf index 5b743a470c8e..c5db7d56ba3e 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.pt-BR.xlf +++ b/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.pt-BR.xlf @@ -52,6 +52,11 @@ Falha ao baixar pacotes de atualização de carga de trabalho para o cache: {0} + + Invalid argument "{0}" to the --mode argument for dotnet workload update. Only supported modes are "workloadset", "loosemanifest", and "auto". + Argumento "{0}" inválido para o argumento --mode para atualização de carga de trabalho dotnet. Os únicos modos com suporte são "workloadset", "loosemanifest" e "auto". + + Successfully updated advertising manifests. Manifestos de anúncio atualizados com êxito. diff --git a/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.ru.xlf b/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.ru.xlf index 6049ae860032..f65de634659b 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.ru.xlf +++ b/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.ru.xlf @@ -52,6 +52,11 @@ Ðе удалоÑÑŒ Ñкачать пакеты Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ Ñ€Ð°Ð±Ð¾Ñ‡ÐµÐ¹ нагрузки в кÑш: {0} + + Invalid argument "{0}" to the --mode argument for dotnet workload update. Only supported modes are "workloadset", "loosemanifest", and "auto". + ÐедопуÑтимый аргумент "{0}" Ð´Ð»Ñ Ð°Ñ€Ð³ÑƒÐ¼ÐµÐ½Ñ‚Ð° --mode Ð´Ð»Ñ Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ Ñ€Ð°Ð±Ð¾Ñ‡ÐµÐ¹ нагрузки dotnet. ПоддерживаютÑÑ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ режимы "workloadset", "loosemanifest" и "auto". + + Successfully updated advertising manifests. МанифеÑты рекламы уÑпешно обновлены. diff --git a/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.tr.xlf b/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.tr.xlf index fe08c6c5f1b8..6c7f77da9566 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.tr.xlf +++ b/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.tr.xlf @@ -52,6 +52,11 @@ İş yükü güncelleÅŸtirme paketleri önbelleÄŸe yüklenemedi: {0} + + Invalid argument "{0}" to the --mode argument for dotnet workload update. Only supported modes are "workloadset", "loosemanifest", and "auto". + Dotnet iÅŸ yükü güncelleÅŸtirmesi için --mod bağımsız deÄŸiÅŸkeninde geçersiz "{0}" bağımsız deÄŸiÅŸkeni. Yalnızca "workloadset", "loosemanifest" ve "auto" modları desteklenir. + + Successfully updated advertising manifests. Reklam bildirimleri baÅŸarıyla güncelleÅŸtirildi. diff --git a/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.zh-Hans.xlf b/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.zh-Hans.xlf index 844cad9451c3..3c981c1c23c4 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.zh-Hans.xlf +++ b/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.zh-Hans.xlf @@ -52,6 +52,11 @@ 未能将工作负载更新程åºåŒ…下载到缓存: {0} + + Invalid argument "{0}" to the --mode argument for dotnet workload update. Only supported modes are "workloadset", "loosemanifest", and "auto". + dotnet 工作负载更新的 --mode 傿•°çš„傿•°â€œ{0}â€æ— æ•ˆã€‚仅支æŒâ€œworkloadsetâ€ã€â€œloosemanifestâ€å’Œâ€œautoâ€æ¨¡å¼ã€‚ + + Successfully updated advertising manifests. æˆåŠŸæ›´æ–°å¹¿å‘Šæ¸…å•。 diff --git a/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.zh-Hant.xlf b/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.zh-Hant.xlf index cbde1607649d..20dab178f25e 100644 --- a/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.zh-Hant.xlf +++ b/src/Cli/dotnet/commands/dotnet-workload/update/xlf/LocalizableStrings.zh-Hant.xlf @@ -52,6 +52,11 @@ 無法將工作負載更新套件下載到快å–: {0} + + Invalid argument "{0}" to the --mode argument for dotnet workload update. Only supported modes are "workloadset", "loosemanifest", and "auto". + dotnet 工作負載更新的 --mode 引數之引數 "{0}" ç„¡æ•ˆã€‚åƒ…æ”¯æ´ "workloadset"ã€"loosemanifest" å’Œ "auto" 模å¼ã€‚ + + Successfully updated advertising manifests. å·²æˆåŠŸæ›´æ–°å»£å‘Šè³‡è¨Šæ¸…å–®ã€‚ diff --git a/src/Cli/dotnet/dotnet.csproj b/src/Cli/dotnet/dotnet.csproj index 7410ec42262f..de27955804a3 100644 --- a/src/Cli/dotnet/dotnet.csproj +++ b/src/Cli/dotnet/dotnet.csproj @@ -44,6 +44,7 @@ + @@ -124,4 +125,13 @@ + + + + + + + + + diff --git a/src/Containers/Microsoft.NET.Build.Containers/AuthHandshakeMessageHandler.cs b/src/Containers/Microsoft.NET.Build.Containers/AuthHandshakeMessageHandler.cs index 94488fa74339..10ff6c46a554 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/AuthHandshakeMessageHandler.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/AuthHandshakeMessageHandler.cs @@ -31,6 +31,8 @@ internal sealed partial class AuthHandshakeMessageHandler : DelegatingHandler /// Valid characters for this clientID are in the unicode range 20-7E /// private const string ClientID = "netsdkcontainers"; + private const string BasicAuthScheme = "Basic"; + private const string BearerAuthScheme = "Bearer"; private sealed record AuthInfo(string Realm, string? Service, string? Scope); @@ -62,22 +64,33 @@ private static bool TryParseAuthenticationInfo(HttpResponseMessage msg, [NotNull } AuthenticationHeaderValue header = authenticateHeader.First(); - if (header is { Scheme: "Bearer" or "Basic", Parameter: string bearerArgs }) + + if (header.Scheme is not null) { scheme = header.Scheme; - var keyValues = ParseBearerArgs(bearerArgs); + var keyValues = ParseBearerArgs(header.Parameter); + if (keyValues is null) + { + return false; + } - var result = scheme switch + if (header.Scheme.Equals(BasicAuthScheme, StringComparison.OrdinalIgnoreCase)) { - "Bearer" => TryParseBearerAuthInfo(keyValues, out bearerAuthInfo), - "Basic" => TryParseBasicAuthInfo(keyValues, msg.RequestMessage!.RequestUri!, out bearerAuthInfo), - _ => false - }; - return result; + return TryParseBasicAuthInfo(keyValues, msg.RequestMessage!.RequestUri!, out bearerAuthInfo); + } + else if (header.Scheme.Equals(BearerAuthScheme, StringComparison.OrdinalIgnoreCase)) + { + return TryParseBearerAuthInfo(keyValues, out bearerAuthInfo); + } + else + { + return false; + } } return false; - static bool TryParseBearerAuthInfo(Dictionary authValues, [NotNullWhen(true)] out AuthInfo? authInfo) { + static bool TryParseBearerAuthInfo(Dictionary authValues, [NotNullWhen(true)] out AuthInfo? authInfo) + { if (authValues.TryGetValue("realm", out string? realm)) { string? service = null; @@ -87,19 +100,25 @@ static bool TryParseBearerAuthInfo(Dictionary authValues, [NotNu authInfo = new AuthInfo(realm, service, scope); return true; } - else { + else + { authInfo = null; return false; } } - static bool TryParseBasicAuthInfo(Dictionary authValues, Uri requestUri, out AuthInfo? authInfo) { + static bool TryParseBasicAuthInfo(Dictionary authValues, Uri requestUri, out AuthInfo? authInfo) + { authInfo = null; return true; } - static Dictionary ParseBearerArgs(string bearerHeaderArgs) + static Dictionary? ParseBearerArgs(string? bearerHeaderArgs) { + if (bearerHeaderArgs is null) + { + return null; + } Dictionary keyValues = new(); foreach (Match match in BearerParameterSplitter().Matches(bearerHeaderArgs)) { @@ -118,8 +137,10 @@ static Dictionary ParseBearerArgs(string bearerHeaderArgs) private sealed record TokenResponse(string? token, string? access_token, int? expires_in, DateTimeOffset? issued_at) { public string ResolvedToken => token ?? access_token ?? throw new ArgumentException(Resource.GetString(nameof(Strings.InvalidTokenResponse))); - public DateTimeOffset ResolvedExpiration { - get { + public DateTimeOffset ResolvedExpiration + { + get + { var issueTime = this.issued_at ?? DateTimeOffset.UtcNow; // per spec, if no issued_at use the current time var validityDuration = this.expires_in ?? 60; // per spec, if no expires_in use 60 seconds var expirationTime = issueTime.AddSeconds(validityDuration); @@ -151,12 +172,12 @@ public DateTimeOffset ResolvedExpiration { privateRepoCreds = await GetLoginCredentials(registry).ConfigureAwait(false); } - if (scheme is "Basic") + if (scheme.Equals(BasicAuthScheme, StringComparison.OrdinalIgnoreCase)) { - var authValue = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes($"{privateRepoCreds.Username}:{privateRepoCreds.Password}"))); - return new (authValue, DateTimeOffset.MaxValue); + var authValue = new AuthenticationHeaderValue(BasicAuthScheme, Convert.ToBase64String(Encoding.ASCII.GetBytes($"{privateRepoCreds.Username}:{privateRepoCreds.Password}"))); + return new(authValue, DateTimeOffset.MaxValue); } - else if (scheme is "Bearer") + else if (scheme.Equals(BearerAuthScheme, StringComparison.OrdinalIgnoreCase)) { Debug.Assert(bearerAuthInfo is not null); @@ -223,7 +244,7 @@ public DateTimeOffset ResolvedExpiration { TokenResponse? tokenResponse = JsonSerializer.Deserialize(postResponse.Content.ReadAsStream(cancellationToken)); if (tokenResponse is { } tokenEnvelope) { - var authValue = new AuthenticationHeaderValue("Bearer", tokenResponse.ResolvedToken); + var authValue = new AuthenticationHeaderValue(BearerAuthScheme, tokenResponse.ResolvedToken); return (authValue, tokenResponse.ResolvedExpiration); } else @@ -239,39 +260,39 @@ public DateTimeOffset ResolvedExpiration { /// private async Task<(AuthenticationHeaderValue, DateTimeOffset)?> TryTokenGetAsync(DockerCredentials privateRepoCreds, AuthInfo bearerAuthInfo, CancellationToken cancellationToken) { - // this doesn't seem to be called out in the spec, but actual username/password auth information should be converted into Basic auth here, - // even though the overall Scheme we're authenticating for is Bearer - var header = privateRepoCreds.Username == "" - ? new AuthenticationHeaderValue("Bearer", privateRepoCreds.Password) - : new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes($"{privateRepoCreds.Username}:{privateRepoCreds.Password}"))); - var builder = new UriBuilder(new Uri(bearerAuthInfo.Realm)); - - _logger.LogTrace("Attempting to authenticate on {uri} using GET.", bearerAuthInfo.Realm); - var queryDict = System.Web.HttpUtility.ParseQueryString(""); - if (bearerAuthInfo.Service is string svc) - { - queryDict["service"] = svc; - } - if (bearerAuthInfo.Scope is string s) - { - queryDict["scope"] = s; - } - builder.Query = queryDict.ToString(); - var message = new HttpRequestMessage(HttpMethod.Get, builder.ToString()); - message.Headers.Authorization = header; + // this doesn't seem to be called out in the spec, but actual username/password auth information should be converted into Basic auth here, + // even though the overall Scheme we're authenticating for is Bearer + var header = privateRepoCreds.Username == "" + ? new AuthenticationHeaderValue(BearerAuthScheme, privateRepoCreds.Password) + : new AuthenticationHeaderValue(BasicAuthScheme, Convert.ToBase64String(Encoding.ASCII.GetBytes($"{privateRepoCreds.Username}:{privateRepoCreds.Password}"))); + var builder = new UriBuilder(new Uri(bearerAuthInfo.Realm)); + + _logger.LogTrace("Attempting to authenticate on {uri} using GET.", bearerAuthInfo.Realm); + var queryDict = System.Web.HttpUtility.ParseQueryString(""); + if (bearerAuthInfo.Service is string svc) + { + queryDict["service"] = svc; + } + if (bearerAuthInfo.Scope is string s) + { + queryDict["scope"] = s; + } + builder.Query = queryDict.ToString(); + var message = new HttpRequestMessage(HttpMethod.Get, builder.ToString()); + message.Headers.Authorization = header; - using var tokenResponse = await base.SendAsync(message, cancellationToken).ConfigureAwait(false); - if (!tokenResponse.IsSuccessStatusCode) - { - throw new UnableToAccessRepositoryException(_registryName); - } + using var tokenResponse = await base.SendAsync(message, cancellationToken).ConfigureAwait(false); + if (!tokenResponse.IsSuccessStatusCode) + { + throw new UnableToAccessRepositoryException(_registryName); + } - TokenResponse? token = JsonSerializer.Deserialize(tokenResponse.Content.ReadAsStream(cancellationToken)); - if (token is null) - { - throw new ArgumentException(Resource.GetString(nameof(Strings.CouldntDeserializeJsonToken))); - } - return (new AuthenticationHeaderValue("Bearer", token.ResolvedToken), token.ResolvedExpiration); + TokenResponse? token = JsonSerializer.Deserialize(tokenResponse.Content.ReadAsStream(cancellationToken)); + if (token is null) + { + throw new ArgumentException(Resource.GetString(nameof(Strings.CouldntDeserializeJsonToken))); + } + return (new AuthenticationHeaderValue(BearerAuthScheme, token.ResolvedToken), token.ResolvedExpiration); } private static async Task GetLoginCredentials(string registry) diff --git a/src/Containers/Microsoft.NET.Build.Containers/ContainerBuilder.cs b/src/Containers/Microsoft.NET.Build.Containers/ContainerBuilder.cs index ec546487ed62..4a0ae6848596 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/ContainerBuilder.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/ContainerBuilder.cs @@ -59,11 +59,12 @@ public static async Task ContainerizeAsync( { try { + var ridGraphPicker = new RidGraphManifestPicker(ridGraphPath); imageBuilder = await registry.GetImageManifestAsync( baseImageName, baseImageTag, containerRuntimeIdentifier, - ridGraphPath, + ridGraphPicker, cancellationToken).ConfigureAwait(false); } catch (RepositoryNotFoundException) diff --git a/src/Containers/Microsoft.NET.Build.Containers/ImageBuilder.cs b/src/Containers/Microsoft.NET.Build.Containers/ImageBuilder.cs index fc670b38519b..ce76c4060eb2 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/ImageBuilder.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/ImageBuilder.cs @@ -237,13 +237,32 @@ internal void AssignUserFromEnvironment() /// internal void AssignPortsFromEnvironment() { - // asp.net images control port bindings via three environment variables. we should check for those variables and ensure that ports are created for them + // asp.net images control port bindings via three environment variables. we should check for those variables and ensure that ports are created for them. + // precendence is captured at https://github.com/dotnet/aspnetcore/blob/f49c1c7f7467c184ffb630086afac447772096c6/src/Hosting/Hosting/src/GenericHost/GenericWebHostService.cs#L68-L119 + // ASPNETCORE_URLS is the most specific and is the only one used if present, followed by ASPNETCORE_HTTPS_PORT and ASPNETCORE_HTTP_PORT together + // https://learn.microsoft.com//aspnet/core/fundamentals/host/web-host?view=aspnetcore-8.0#server-urls - the format of ASPNETCORE_URLS has been stable for many years now + if (_baseImageConfig.EnvironmentVariables.TryGetValue(EnvironmentVariables.ASPNETCORE_URLS, out string? urls)) + { + foreach (var url in Split(urls)) + { + _logger.LogTrace("Setting ports from ASPNETCORE_URLS environment variable"); + var match = aspnetPortRegex.Match(url); + if (match.Success && int.TryParse(match.Groups["port"].Value, out int port)) + { + _logger.LogTrace("Added port {port}", port); + ExposePort(port, PortType.tcp); + } + } + return; // we're done here - ASPNETCORE_URLS is the most specific and overrides the other two + } + + // port-specific // https://learn.microsoft.com/aspnet/core/fundamentals/servers/kestrel/endpoints?view=aspnetcore-8.0#specify-ports-only - new for .NET 8 - allows just changing port(s) easily if (_baseImageConfig.EnvironmentVariables.TryGetValue(EnvironmentVariables.ASPNETCORE_HTTP_PORTS, out string? httpPorts)) { _logger.LogTrace("Setting ports from ASPNETCORE_HTTP_PORTS environment variable"); - foreach(var port in Split(httpPorts)) + foreach (var port in Split(httpPorts)) { if (int.TryParse(port, out int parsedPort)) { @@ -260,7 +279,7 @@ internal void AssignPortsFromEnvironment() if (_baseImageConfig.EnvironmentVariables.TryGetValue(EnvironmentVariables.ASPNETCORE_HTTPS_PORTS, out string? httpsPorts)) { _logger.LogTrace("Setting ports from ASPNETCORE_HTTPS_PORTS environment variable"); - foreach(var port in Split(httpsPorts)) + foreach (var port in Split(httpsPorts)) { if (int.TryParse(port, out int parsedPort)) { @@ -274,21 +293,6 @@ internal void AssignPortsFromEnvironment() } } - // https://learn.microsoft.com//aspnet/core/fundamentals/host/web-host?view=aspnetcore-8.0#server-urls - the format of ASPNETCORE_URLS has been stable for many years now - if (_baseImageConfig.EnvironmentVariables.TryGetValue(EnvironmentVariables.ASPNETCORE_URLS, out string? urls)) - { - foreach(var url in Split(urls)) - { - _logger.LogTrace("Setting ports from ASPNETCORE_URLS environment variable"); - var match = aspnetPortRegex.Match(url); - if (match.Success && int.TryParse(match.Groups["port"].Value, out int port)) - { - _logger.LogTrace("Added port {port}", port); - ExposePort(port, PortType.tcp); - } - } - } - static string[] Split(string input) { return input.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); diff --git a/src/Containers/Microsoft.NET.Build.Containers/KnownStrings.cs b/src/Containers/Microsoft.NET.Build.Containers/KnownStrings.cs index 36fd920dd96e..be334ac7d271 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/KnownStrings.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/KnownStrings.cs @@ -25,7 +25,6 @@ public static class Properties public static readonly string ContainerEnvironmentVariable = nameof(ContainerEnvironmentVariable); public static readonly string ComputeContainerBaseImage = nameof(ComputeContainerBaseImage); - public static readonly string _ComputeContainerBaseImageTag = nameof(_ComputeContainerBaseImageTag); public static readonly string ComputeContainerConfig = nameof(ComputeContainerConfig); public static readonly string AssemblyName = nameof(AssemblyName); public static readonly string ContainerBaseRegistry = nameof(ContainerBaseRegistry); @@ -35,6 +34,15 @@ public static class Properties public static readonly string ContainerGenerateLabels = nameof(ContainerGenerateLabels); public static readonly string ContainerRuntimeIdentifier = nameof(ContainerRuntimeIdentifier); + public static readonly string RuntimeIdentifier = nameof(RuntimeIdentifier); + public static readonly string PublishAot = nameof(PublishAot); + public static readonly string PublishSelfContained = nameof(PublishSelfContained); + public static readonly string InvariantGlobalization = nameof(InvariantGlobalization); + } + + public static class Items + { + public static readonly string FrameworkReference = nameof(FrameworkReference); } public static class ErrorCodes diff --git a/src/Containers/Microsoft.NET.Build.Containers/Microsoft.NET.Build.Containers.csproj b/src/Containers/Microsoft.NET.Build.Containers/Microsoft.NET.Build.Containers.csproj index 3142bdd1eaf1..6d7041c4c4f5 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Microsoft.NET.Build.Containers.csproj +++ b/src/Containers/Microsoft.NET.Build.Containers/Microsoft.NET.Build.Containers.csproj @@ -47,7 +47,7 @@ - + diff --git a/src/Containers/Microsoft.NET.Build.Containers/PublicAPI/net472/PublicAPI.Unshipped.txt b/src/Containers/Microsoft.NET.Build.Containers/PublicAPI/net472/PublicAPI.Unshipped.txt index 49ce9afe58c0..89caa7583181 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/PublicAPI/net472/PublicAPI.Unshipped.txt +++ b/src/Containers/Microsoft.NET.Build.Containers/PublicAPI/net472/PublicAPI.Unshipped.txt @@ -21,15 +21,6 @@ override Microsoft.NET.Build.Containers.Port.GetHashCode() -> int Microsoft.NET.Build.Containers.PortType Microsoft.NET.Build.Containers.PortType.tcp = 0 -> Microsoft.NET.Build.Containers.PortType Microsoft.NET.Build.Containers.PortType.udp = 1 -> Microsoft.NET.Build.Containers.PortType -Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageTag -Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageTag.ComputedBaseImageTag.get -> string? -Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageTag.ComputeDotnetBaseImageTag() -> void -Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageTag.ContainerFamily.get -> string! -Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageTag.ContainerFamily.set -> void -Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageTag.SdkVersion.get -> string! -Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageTag.SdkVersion.set -> void -Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageTag.TargetFrameworkVersion.get -> string! -Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageTag.TargetFrameworkVersion.set -> void Microsoft.NET.Build.Containers.Tasks.CreateNewImage Microsoft.NET.Build.Containers.Tasks.CreateNewImage.BaseImageName.get -> string! Microsoft.NET.Build.Containers.Tasks.CreateNewImage.BaseImageName.set -> void @@ -86,7 +77,6 @@ Microsoft.NET.Build.Containers.Tasks.CreateNewImage.RuntimeIdentifierGraphPath.g Microsoft.NET.Build.Containers.Tasks.CreateNewImage.RuntimeIdentifierGraphPath.set -> void Microsoft.NET.Build.Containers.Tasks.CreateNewImage.WorkingDirectory.get -> string! Microsoft.NET.Build.Containers.Tasks.CreateNewImage.WorkingDirectory.set -> void -override Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageTag.Execute() -> bool override Microsoft.NET.Build.Containers.Tasks.CreateNewImage.ToolName.get -> string! override Microsoft.NET.Build.Containers.Tasks.CreateNewImage.GenerateCommandLineCommands() -> string! override Microsoft.NET.Build.Containers.Tasks.CreateNewImage.GenerateFullPathToTool() -> string! @@ -115,3 +105,23 @@ Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ParsedContainerTag override Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.Execute() -> bool static Microsoft.NET.Build.Containers.ContainerHelpers.TryParsePort(string! input, out Microsoft.NET.Build.Containers.Port? port, out Microsoft.NET.Build.Containers.ContainerHelpers.ParsePortError? error) -> bool static Microsoft.NET.Build.Containers.ContainerHelpers.TryParsePort(string? portNumber, string? portType, out Microsoft.NET.Build.Containers.Port? port, out Microsoft.NET.Build.Containers.ContainerHelpers.ParsePortError? error) -> bool +Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.ComputedContainerBaseImage.get -> string? +Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.FrameworkReferences.get -> Microsoft.Build.Framework.ITaskItem![]! +Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.FrameworkReferences.set -> void +Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.IsAotPublished.get -> bool +Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.IsAotPublished.set -> void +Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.IsSelfContained.get -> bool +Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.IsSelfContained.set -> void +Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.TargetRuntimeIdentifier.get -> string! +Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.TargetRuntimeIdentifier.set -> void +Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.UsesInvariantGlobalization.get -> bool +Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.UsesInvariantGlobalization.set -> void +Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag +Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.ComputeDotnetBaseImageAndTag() -> void +Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.ContainerFamily.get -> string! +Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.ContainerFamily.set -> void +Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.SdkVersion.get -> string! +Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.SdkVersion.set -> void +Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.TargetFrameworkVersion.get -> string! +Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.TargetFrameworkVersion.set -> void +override Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.Execute() -> bool \ No newline at end of file diff --git a/src/Containers/Microsoft.NET.Build.Containers/PublicAPI/net8.0/PublicAPI.Unshipped.txt b/src/Containers/Microsoft.NET.Build.Containers/PublicAPI/net8.0/PublicAPI.Unshipped.txt index 9b5a7e701d2d..3b6d27ca2c1e 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/PublicAPI/net8.0/PublicAPI.Unshipped.txt +++ b/src/Containers/Microsoft.NET.Build.Containers/PublicAPI/net8.0/PublicAPI.Unshipped.txt @@ -2,7 +2,17 @@ const Microsoft.NET.Build.Containers.KnownLocalRegistryTypes.Podman = "Podman" -> string! Microsoft.NET.Build.Containers.BaseImageNotFoundException Microsoft.NET.Build.Containers.Constants -Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageTag.ComputeDotnetBaseImageTag() -> void +Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.ComputedContainerBaseImage.get -> string? +Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.FrameworkReferences.get -> Microsoft.Build.Framework.ITaskItem![]! +Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.FrameworkReferences.set -> void +Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.IsAotPublished.get -> bool +Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.IsAotPublished.set -> void +Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.IsSelfContained.get -> bool +Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.IsSelfContained.set -> void +Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.TargetRuntimeIdentifier.get -> string! +Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.TargetRuntimeIdentifier.set -> void +Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.UsesInvariantGlobalization.get -> bool +Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.UsesInvariantGlobalization.set -> void static Microsoft.NET.Build.Containers.ContainerBuilder.ContainerizeAsync(System.IO.DirectoryInfo! publishDirectory, string! workingDir, string! baseRegistry, string! baseImageName, string! baseImageTag, string![]! entrypoint, string![]! entrypointArgs, string![]! defaultArgs, string![]! appCommand, string![]! appCommandArgs, string! appCommandInstruction, string! imageName, string![]! imageTags, string? outputRegistry, System.Collections.Generic.Dictionary! labels, Microsoft.NET.Build.Containers.Port[]? exposedPorts, System.Collections.Generic.Dictionary! envVars, string! containerRuntimeIdentifier, string! ridGraphPath, string! localRegistry, string? containerUser, string? archiveOutputPath, Microsoft.Extensions.Logging.ILoggerFactory! loggerFactory, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! static readonly Microsoft.NET.Build.Containers.Constants.Version -> string! Microsoft.NET.Build.Containers.ContainerBuilder @@ -111,14 +121,15 @@ override Microsoft.NET.Build.Containers.Port.GetHashCode() -> int Microsoft.NET.Build.Containers.PortType Microsoft.NET.Build.Containers.PortType.tcp = 0 -> Microsoft.NET.Build.Containers.PortType Microsoft.NET.Build.Containers.PortType.udp = 1 -> Microsoft.NET.Build.Containers.PortType -Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageTag -Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageTag.ContainerFamily.get -> string! -Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageTag.ContainerFamily.set -> void -Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageTag.SdkVersion.get -> string! -Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageTag.SdkVersion.set -> void -Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageTag.TargetFrameworkVersion.get -> string! -Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageTag.TargetFrameworkVersion.set -> void -Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageTag.ComputedBaseImageTag.get -> string? +Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag +Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.ComputeDotnetBaseImageAndTag() -> void +Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.ContainerFamily.get -> string! +Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.ContainerFamily.set -> void +Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.SdkVersion.get -> string! +Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.SdkVersion.set -> void +Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.TargetFrameworkVersion.get -> string! +Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.TargetFrameworkVersion.set -> void +override Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageAndTag.Execute() -> bool Microsoft.NET.Build.Containers.Tasks.CreateNewImage Microsoft.NET.Build.Containers.Tasks.CreateNewImage.BaseImageName.get -> string! Microsoft.NET.Build.Containers.Tasks.CreateNewImage.BaseImageName.set -> void @@ -202,7 +213,6 @@ Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ParseContainerProp Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ParsedContainerImage.get -> string! Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ParsedContainerRegistry.get -> string! Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.ParsedContainerTag.get -> string! -override Microsoft.NET.Build.Containers.Tasks.ComputeDotnetBaseImageTag.Execute() -> bool override Microsoft.NET.Build.Containers.Tasks.CreateNewImage.Execute() -> bool override Microsoft.NET.Build.Containers.Tasks.ParseContainerProperties.Execute() -> bool static Microsoft.NET.Build.Containers.ContainerHelpers.TryParsePort(string! input, out Microsoft.NET.Build.Containers.Port? port, out Microsoft.NET.Build.Containers.ContainerHelpers.ParsePortError? error) -> bool diff --git a/src/Containers/Microsoft.NET.Build.Containers/Registry/DefaultBlobOperations.cs b/src/Containers/Microsoft.NET.Build.Containers/Registry/DefaultBlobOperations.cs index fd5898fedd59..d258ebfc0886 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Registry/DefaultBlobOperations.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Registry/DefaultBlobOperations.cs @@ -36,7 +36,7 @@ public async Task ExistsAsync(string repositoryName, string digest, Cancel { HttpStatusCode.OK => true, HttpStatusCode.NotFound => false, - HttpStatusCode.Unauthorized => throw new UnableToAccessRepositoryException(_registryName, repositoryName), + HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden => throw new UnableToAccessRepositoryException(_registryName, repositoryName), _ => await LogAndThrowContainerHttpException(response, cancellationToken).ConfigureAwait(false) }; } @@ -68,7 +68,7 @@ private async Task GetAsync(string repositoryName, string d return response.StatusCode switch { HttpStatusCode.OK => response, - HttpStatusCode.Unauthorized => throw new UnableToAccessRepositoryException(_registryName, repositoryName), + HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden => throw new UnableToAccessRepositoryException(_registryName, repositoryName), _ => await LogAndThrowContainerHttpException(response, cancellationToken).ConfigureAwait(false) }; } diff --git a/src/Containers/Microsoft.NET.Build.Containers/Registry/DefaultManifestOperations.cs b/src/Containers/Microsoft.NET.Build.Containers/Registry/DefaultManifestOperations.cs index 55cf8bf93b6f..c34426d10af3 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Registry/DefaultManifestOperations.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Registry/DefaultManifestOperations.cs @@ -33,7 +33,7 @@ public async Task GetAsync(string repositoryName, string re { HttpStatusCode.OK => response, HttpStatusCode.NotFound => throw new RepositoryNotFoundException(_registryName, repositoryName, reference), - HttpStatusCode.Unauthorized => throw new UnableToAccessRepositoryException(_registryName, repositoryName), + HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden => throw new UnableToAccessRepositoryException(_registryName, repositoryName), _ => await LogAndThrowContainerHttpException(response, cancellationToken).ConfigureAwait(false) }; } diff --git a/src/Containers/Microsoft.NET.Build.Containers/Registry/DefaultRegistryAPI.cs b/src/Containers/Microsoft.NET.Build.Containers/Registry/DefaultRegistryAPI.cs index e83a4da029b3..22d4d333c4bf 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Registry/DefaultRegistryAPI.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Registry/DefaultRegistryAPI.cs @@ -30,7 +30,10 @@ internal DefaultRegistryAPI(string registryName, Uri baseUri, ILogger logger) private static HttpClient CreateClient(string registryName, Uri baseUri, ILogger logger, bool isAmazonECRRegistry = false) { - var innerHandler = new SocketsHttpHandler(); + var innerHandler = new SocketsHttpHandler() + { + UseCookies = false, + }; // Ignore certificate for https localhost repository. if (baseUri.Host == "localhost" && baseUri.Scheme == "https") diff --git a/src/Containers/Microsoft.NET.Build.Containers/Registry/Registry.cs b/src/Containers/Microsoft.NET.Build.Containers/Registry/Registry.cs index d65dd905d9c5..68456fa71bfa 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Registry/Registry.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Registry/Registry.cs @@ -7,9 +7,49 @@ using System.Diagnostics; using System.Net.Http.Json; using System.Text.Json.Nodes; +using System.Text.RegularExpressions; namespace Microsoft.NET.Build.Containers; +internal interface IManifestPicker { + public PlatformSpecificManifest? PickBestManifestForRid(IReadOnlyDictionary manifestList, string runtimeIdentifier); +} + +internal sealed class RidGraphManifestPicker : IManifestPicker +{ + private readonly RuntimeGraph _runtimeGraph; + + public RidGraphManifestPicker(string runtimeIdentifierGraphPath) + { + _runtimeGraph = GetRuntimeGraphForDotNet(runtimeIdentifierGraphPath); + } + public PlatformSpecificManifest? PickBestManifestForRid(IReadOnlyDictionary ridManifestDict, string runtimeIdentifier) + { + var bestManifestRid = GetBestMatchingRid(_runtimeGraph, runtimeIdentifier, ridManifestDict.Keys); + if (bestManifestRid is null) { + return null; + } + return ridManifestDict[bestManifestRid]; + } + + private static string? GetBestMatchingRid(RuntimeGraph runtimeGraph, string runtimeIdentifier, IEnumerable availableRuntimeIdentifiers) + { + HashSet availableRids = new HashSet(availableRuntimeIdentifiers, StringComparer.Ordinal); + foreach (var candidateRuntimeIdentifier in runtimeGraph.ExpandRuntime(runtimeIdentifier)) + { + if (availableRids.Contains(candidateRuntimeIdentifier)) + { + return candidateRuntimeIdentifier; + } + } + + return null; + } + + private static RuntimeGraph GetRuntimeGraphForDotNet(string ridGraphPath) => JsonRuntimeFormat.ReadRuntimeGraph(ridGraphPath); + +} + internal sealed class Registry { private const string DockerHubRegistry1 = "registry-1.docker.io"; @@ -102,7 +142,7 @@ public bool IsGoogleArtifactRegistry { /// private bool SupportsParallelUploads => !IsAmazonECRRegistry && _settings.ParallelUploadEnabled; - public async Task GetImageManifestAsync(string repositoryName, string reference, string runtimeIdentifier, string runtimeIdentifierGraphPath, CancellationToken cancellationToken) + public async Task GetImageManifestAsync(string repositoryName, string reference, string runtimeIdentifier, IManifestPicker manifestPicker, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); using HttpResponseMessage initialManifestResponse = await _registryAPI.Manifest.GetAsync(repositoryName, reference, cancellationToken).ConfigureAwait(false); @@ -118,7 +158,7 @@ await initialManifestResponse.Content.ReadFromJsonAsync(cancellation reference, await initialManifestResponse.Content.ReadFromJsonAsync(cancellationToken: cancellationToken).ConfigureAwait(false), runtimeIdentifier, - runtimeIdentifierGraphPath, + manifestPicker, cancellationToken).ConfigureAwait(false), var unknownMediaType => throw new NotImplementedException(Resource.FormatString( nameof(Strings.UnknownMediaType), @@ -129,6 +169,18 @@ await initialManifestResponse.Content.ReadFromJsonAsync(cancella }; } + internal async Task GetManifestListAsync(string repositoryName, string reference, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + using HttpResponseMessage initialManifestResponse = await _registryAPI.Manifest.GetAsync(repositoryName, reference, cancellationToken).ConfigureAwait(false); + + return initialManifestResponse.Content.Headers.ContentType?.MediaType switch + { + SchemaTypes.DockerManifestListV2 => await initialManifestResponse.Content.ReadFromJsonAsync(cancellationToken: cancellationToken).ConfigureAwait(false), + _ => null + }; + } + private async Task ReadSingleImageAsync(string repositoryName, ManifestV2 manifest, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -141,33 +193,8 @@ private async Task ReadSingleImageAsync(string repositoryName, Man return new ImageBuilder(manifest, new ImageConfig(configDoc), _logger); } - private async Task PickBestImageFromManifestListAsync( - string repositoryName, - string reference, - ManifestListV2 manifestList, - string runtimeIdentifier, - string runtimeIdentifierGraphPath, - CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - var runtimeGraph = GetRuntimeGraphForDotNet(runtimeIdentifierGraphPath); - var ridManifestDict = GetManifestsByRid(manifestList); - var bestManifestRid = GetBestMatchingRid(runtimeGraph, runtimeIdentifier, ridManifestDict.Keys); - if (bestManifestRid is null) { - throw new BaseImageNotFoundException(runtimeIdentifier, repositoryName, reference, ridManifestDict.Keys); - } - PlatformSpecificManifest matchingManifest = ridManifestDict[bestManifestRid]; - using HttpResponseMessage manifestResponse = await _registryAPI.Manifest.GetAsync(repositoryName, matchingManifest.digest, cancellationToken).ConfigureAwait(false); - - cancellationToken.ThrowIfCancellationRequested(); - - return await ReadSingleImageAsync( - repositoryName, - await manifestResponse.Content.ReadFromJsonAsync(cancellationToken: cancellationToken).ConfigureAwait(false), - cancellationToken).ConfigureAwait(false); - } - - IReadOnlyDictionary GetManifestsByRid(ManifestListV2 manifestList) + + private static IReadOnlyDictionary GetManifestsByRid(ManifestListV2 manifestList) { var ridDict = new Dictionary(); foreach (var manifest in manifestList.manifests) { @@ -179,21 +206,7 @@ IReadOnlyDictionary GetManifestsByRid(Manifest return ridDict; } - - private static string? GetBestMatchingRid(RuntimeGraph runtimeGraph, string runtimeIdentifier, IEnumerable availableRuntimeIdentifiers) - { - HashSet availableRids = new HashSet(availableRuntimeIdentifiers, StringComparer.Ordinal); - foreach (var candidateRuntimeIdentifier in runtimeGraph.ExpandRuntime(runtimeIdentifier)) - { - if (availableRids.Contains(candidateRuntimeIdentifier)) - { - return candidateRuntimeIdentifier; - } - } - - return null; - } - + private static string? CreateRidForPlatform(PlatformInformation platform) { // we only support linux and windows containers explicitly, so anything else we should skip past. @@ -225,7 +238,32 @@ IReadOnlyDictionary GetManifestsByRid(Manifest return $"{osPart}{versionPart ?? ""}-{platformPart}"; } - private static RuntimeGraph GetRuntimeGraphForDotNet(string ridGraphPath) => JsonRuntimeFormat.ReadRuntimeGraph(ridGraphPath); + + private async Task PickBestImageFromManifestListAsync( + string repositoryName, + string reference, + ManifestListV2 manifestList, + string runtimeIdentifier, + IManifestPicker manifestPicker, + CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + var ridManifestDict = GetManifestsByRid(manifestList); + if (manifestPicker.PickBestManifestForRid(ridManifestDict, runtimeIdentifier) is PlatformSpecificManifest matchingManifest) + { + using HttpResponseMessage manifestResponse = await _registryAPI.Manifest.GetAsync(repositoryName, matchingManifest.digest, cancellationToken).ConfigureAwait(false); + + cancellationToken.ThrowIfCancellationRequested(); + + return await ReadSingleImageAsync( + repositoryName, + await manifestResponse.Content.ReadFromJsonAsync(cancellationToken: cancellationToken).ConfigureAwait(false), + cancellationToken).ConfigureAwait(false); + } else + { + throw new BaseImageNotFoundException(runtimeIdentifier, repositoryName, reference, ridManifestDict.Keys); + } + } /// /// Ensure a blob associated with from the registry is available locally. diff --git a/src/Containers/Microsoft.NET.Build.Containers/Tasks/ComputeDotnetBaseImageAndTag.cs b/src/Containers/Microsoft.NET.Build.Containers/Tasks/ComputeDotnetBaseImageAndTag.cs new file mode 100644 index 000000000000..f4eab184e1e8 --- /dev/null +++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/ComputeDotnetBaseImageAndTag.cs @@ -0,0 +1,248 @@ +// 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.CodeAnalysis; +using System.Security.Cryptography; +using Microsoft.Build.Framework; +using NuGet.Versioning; +#if NETFRAMEWORK +using System.Linq; +#endif + +namespace Microsoft.NET.Build.Containers.Tasks; + +/// +/// Computes the base image and Tag for a Microsoft-authored container image based on the project properties and tagging scheme from various SDK versions. +/// +public sealed class ComputeDotnetBaseImageAndTag : Microsoft.Build.Utilities.Task +{ + // starting in .NET 8, the container tagging scheme started incorporating the + // 'channel' (rc/preview) and the channel increment (the numeric value after the channel name) + // into the container tags. + private const int FirstVersionWithNewTaggingScheme = 8; + + /// + /// When in preview, this influences which preview image tag is used, since previews can have compatibility problems across versions. + /// + [Required] + public string SdkVersion { get; set; } + + /// + /// Used to determine which `tag` of an image should be used by default. + /// + [Required] + public string TargetFrameworkVersion { get; set; } + + /// + /// Used to inspect the project to see if it references ASP.Net Core, which causes a change in base image to dotnet/aspnet. + /// + [Required] + public ITaskItem[] FrameworkReferences { get; set; } + + /// + /// If this is set to linux-ARCH then we use jammy-chiseled for the AOT/Extra/etc decisions. + /// If this is set to linux-musl-ARCH then we need to use `alpine` for all containers, and tag on `aot` or `extra` as necessary. + /// + [Required] + public string TargetRuntimeIdentifier { get; set; } + + + /// + /// If a project is self-contained then it includes a runtime, and so the runtime-deps image should be used. + /// + public bool IsSelfContained { get; set; } + + /// + /// If a project is AOT-published then not only is it self-contained, but it can also remove some other deps - we can use the dotnet/nightly/runtime-deps variant here aot + /// + public bool IsAotPublished { get; set; } + + /// + /// If the project is AOT'd the aot image variant doesn't contain ICU and TZData, so we use this flag to see if we need to use the `-extra` variant that does contain those packages. + /// + public bool UsesInvariantGlobalization { get; set; } + + /// + /// If set, this expresses a preference for a variant of the container image that we infer for a project. + /// e.g. 'alpine', or 'jammy-chiseled' + /// + public string ContainerFamily { get; set; } + + /// + /// The final base image computed from the inputs (or explicitly set by the user if IsUsingMicrosoftDefaultImages is true) + /// + [Output] + public string? ComputedContainerBaseImage { get; private set; } + + private bool IsAspNetCoreProject => + FrameworkReferences.Length > 0 + && FrameworkReferences.Any(x => x.ItemSpec.Equals("Microsoft.AspNetCore.App", StringComparison.OrdinalIgnoreCase)); + + private bool IsMuslRid => TargetRuntimeIdentifier.StartsWith("linux-musl", StringComparison.Ordinal); + private bool IsBundledRuntime => IsSelfContained; + private bool NeedsNightlyImages => IsAotPublished; + private bool AllowsExperimentalTagInference => String.IsNullOrEmpty(ContainerFamily); + + public ComputeDotnetBaseImageAndTag() + { + SdkVersion = ""; + TargetFrameworkVersion = ""; + ContainerFamily = ""; + FrameworkReferences = []; + TargetRuntimeIdentifier = ""; + } + + public override bool Execute() + { + var defaultRegistry = "mcr.microsoft.com"; + if (ComputeRepositoryAndTag(out var repository, out var tag)) + { + ComputedContainerBaseImage = $"{defaultRegistry}/{repository}:{tag}"; + } + return !Log.HasLoggedErrors; + } + + private bool ComputeRepositoryAndTag([NotNullWhen(true)] out string? repository, [NotNullWhen(true)] out string? tag) + { + if (ComputeVersionPart() is (string baseVersionPart, bool versionAllowsUsingAOTAndExtrasImages)) + { + Log.LogMessage("Computed base version tag of {0} from TFM {1} and SDK {2}", baseVersionPart, TargetFrameworkVersion, SdkVersion); + if (baseVersionPart is null) + { + repository = null; + tag = null; + return false; + } + + var detectedRepository = (NeedsNightlyImages, IsSelfContained, IsAspNetCoreProject) switch + { + (true, true, _) when AllowsExperimentalTagInference && versionAllowsUsingAOTAndExtrasImages => "dotnet/nightly/runtime-deps", + (_, true, _) => "dotnet/runtime-deps", + (_, _, true) => "dotnet/aspnet", + (_, _, false) => "dotnet/runtime" + }; + Log.LogMessage("Chose base image repository {0}", detectedRepository); + repository = detectedRepository; + tag = baseVersionPart; + + if (!string.IsNullOrWhiteSpace(ContainerFamily)) + { + // for the inferred image tags, 'family' aka 'flavor' comes after the 'version' portion (including any preview/rc segments). + // so it's safe to just append here + tag += $"-{ContainerFamily}"; + return true; + } + else + { + if (!versionAllowsUsingAOTAndExtrasImages) + { + tag += IsMuslRid switch + { + true => "-alpine", + false => "" // TODO: should we default here to chiseled iamges for < 8 apps? + }; + Log.LogMessage("Selected base image tag {0}", tag); + return true; + } + else + { + // chose the base OS + tag += IsMuslRid switch + { + true => "-alpine", + // default to chiseled for AOT, non-musl Apps + false when IsAotPublished => "-jammy-chiseled", // TODO: should we default here to jammy-chiseled for non-musl RIDs? + // default to jammy for non-AOT, non-musl Apps + false => "" + }; + + // now choose the variant, if any - if globalization then -extra, else -aot + tag += (IsAotPublished, UsesInvariantGlobalization) switch + { + (true, false) => "-extra", + (true, true) => "-aot", + _ => "" + }; + Log.LogMessage("Selected base image tag {0}", tag); + return true; + } + } + } + else + { + repository = null; + tag = null; + return false; + } + } + + private (string, bool)? ComputeVersionPart() + { + if (SemanticVersion.TryParse(TargetFrameworkVersion, out var tfm) && tfm.Major < FirstVersionWithNewTaggingScheme) + { + // < 8 TFMs don't support the -aot and -extras images + return ($"{tfm.Major}.{tfm.Minor}", false); + } + else if (SemanticVersion.TryParse(SdkVersion, out var version)) + { + if (ComputeVersionInternal(version, tfm) is string majMinor) + { + return (majMinor, true); + } + else + { + return null; + } + } + else + { + Log.LogError(Resources.Strings.InvalidSdkVersion, SdkVersion); + return null; + } + } + + private string? ComputeVersionInternal(SemanticVersion version, SemanticVersion? tfm) + { + if (tfm != null && (tfm.Major < version.Major || tfm.Minor < version.Minor)) + { + // in this case the TFM is earlier, so we are assumed to be in a stable scenario + return $"{tfm.Major}.{tfm.Minor}"; + } + // otherwise if we're in a scenario where we're using the TFM for the given SDK version, + // and that SDK version may be a prerelease, so we need to handle + var baseImageTag = version switch + { + // all stable versions or prereleases with majors before the switch get major/minor tags + { IsPrerelease: false } or { Major: < FirstVersionWithNewTaggingScheme } => $"{version.Major}.{version.Minor}", + // prereleases after the switch for the first SDK version get major/minor-channel.bump tags + { IsPrerelease: true, Major: >= FirstVersionWithNewTaggingScheme, Patch: 100 } => DetermineLabelBasedOnChannel(version.Major, version.Minor, version.ReleaseLabels.ToArray()), + // prereleases of subsequent SDK versions still get to use the stable tags + { IsPrerelease: true, Major: >= FirstVersionWithNewTaggingScheme } => $"{version.Major}.{version.Minor}", + }; + return baseImageTag; + } + + private string? DetermineLabelBasedOnChannel(int major, int minor, string[] releaseLabels) + { + var channel = releaseLabels.Length > 0 ? releaseLabels[0] : null; + switch (channel) + { + case null or "rtm" or "servicing": + return $"{major}.{minor}"; + case "rc" or "preview": + if (releaseLabels.Length > 1) + { + // Per the dotnet-docker team, the major.minor preview tag format is a fluke and the major.minor.0 form + // should be used for all previews going forward. + return $"{major}.{minor}.0-{channel}.{releaseLabels[1]}"; + } + Log.LogError(Resources.Strings.InvalidSdkPrereleaseVersion, channel); + return null; + case "alpha" or "dev" or "ci": + return $"{major}.{minor}-preview"; + default: + Log.LogError(Resources.Strings.InvalidSdkPrereleaseVersion, channel); + return null; + }; + } +} diff --git a/src/Containers/Microsoft.NET.Build.Containers/Tasks/ComputeDotnetBaseImageTag.cs b/src/Containers/Microsoft.NET.Build.Containers/Tasks/ComputeDotnetBaseImageTag.cs deleted file mode 100644 index 04719cf0f22f..000000000000 --- a/src/Containers/Microsoft.NET.Build.Containers/Tasks/ComputeDotnetBaseImageTag.cs +++ /dev/null @@ -1,109 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.Build.Framework; -using NuGet.Versioning; -#if NETFRAMEWORK -using System.Linq; -#endif - -namespace Microsoft.NET.Build.Containers.Tasks; - -/// -/// Computes the base image Tag for a Microsoft-authored container image based on the tagging scheme from various SDK versions. -/// -public sealed class ComputeDotnetBaseImageTag : Microsoft.Build.Utilities.Task -{ - // starting in .NET 8, the container tagging scheme started incorporating the - // 'channel' (rc/preview) and the channel increment (the numeric value after the channel name) - // into the container tags. - private const int FirstVersionWithNewTaggingScheme = 8; - - [Required] - public string SdkVersion { get; set; } - - [Required] - public string TargetFrameworkVersion { get; set; } - - public string ContainerFamily { get; set; } - - [Output] - public string? ComputedBaseImageTag { get; private set; } - - public ComputeDotnetBaseImageTag() - { - SdkVersion = ""; - TargetFrameworkVersion = ""; - ContainerFamily = ""; - } - - public override bool Execute() - { - if (SemanticVersion.TryParse(TargetFrameworkVersion, out var tfm) && tfm.Major < FirstVersionWithNewTaggingScheme) - { - ComputedBaseImageTag = $"{tfm.Major}.{tfm.Minor}"; - } - else if (SemanticVersion.TryParse(SdkVersion, out var version)) - { - ComputedBaseImageTag = ComputeVersionInternal(version, tfm); - } - else - { - Log.LogError(Resources.Strings.InvalidSdkVersion, SdkVersion); - return !Log.HasLoggedErrors; - } - - if (!string.IsNullOrWhiteSpace(ContainerFamily)) - { - // for the inferred image tags, 'family' aka 'flavor' comes after the 'version' portion (including any preview/rc segments). - // so it's safe to just append here - ComputedBaseImageTag += $"-{ContainerFamily}"; - } - return true; - } - - - private string? ComputeVersionInternal(SemanticVersion version, SemanticVersion? tfm) - { - if (tfm != null && (tfm.Major < version.Major || tfm.Minor < version.Minor)) - { - // in this case the TFM is earlier, so we are assumed to be in a stable scenario - return $"{tfm.Major}.{tfm.Minor}"; - } - // otherwise if we're in a scenario where we're using the TFM for the given SDK version, - // and that SDK version may be a prerelease, so we need to handle - var baseImageTag = (version) switch - { - // all stable versions or prereleases with majors before the switch get major/minor tags - { IsPrerelease: false } or { Major: < FirstVersionWithNewTaggingScheme } => $"{version.Major}.{version.Minor}", - // prereleases after the switch for the first SDK version get major/minor-channel.bump tags - { IsPrerelease: true, Major: >= FirstVersionWithNewTaggingScheme, Patch: 100 } => DetermineLabelBasedOnChannel(version.Major, version.Minor, version.ReleaseLabels.ToArray()), - // prereleases of subsequent SDK versions still get to use the stable tags - { IsPrerelease: true, Major: >= FirstVersionWithNewTaggingScheme } => $"{version.Major}.{version.Minor}", - }; - return baseImageTag; - } - - private string? DetermineLabelBasedOnChannel(int major, int minor, string[] releaseLabels) { - var channel = releaseLabels.Length > 0 ? releaseLabels[0] : null; - switch (channel) - { - case null or "rtm" or "servicing": - return $"{major}.{minor}"; - case "rc" or "preview": - if (releaseLabels.Length > 1) - { - // Per the dotnet-docker team, the major.minor preview tag format is a fluke and the major.minor.0 form - // should be used for all previews going forward. - return $"{major}.{minor}.0-{channel}.{releaseLabels[1]}"; - } - Log.LogError(Resources.Strings.InvalidSdkPrereleaseVersion, channel); - return null; - case "alpha" or "dev" or "ci": - return $"{major}.{minor}-preview"; - default: - Log.LogError(Resources.Strings.InvalidSdkPrereleaseVersion, channel); - return null; - }; - } -} diff --git a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs index 669edba9590e..3ede1d5be098 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImage.cs @@ -31,7 +31,19 @@ public sealed partial class CreateNewImage : Microsoft.Build.Utilities.Task, ICa public override bool Execute() { - return Task.Run(() => ExecuteAsync(_cancellationTokenSource.Token)).GetAwaiter().GetResult(); + try + { + Task.Run(() => ExecuteAsync(_cancellationTokenSource.Token)).GetAwaiter().GetResult(); + } + catch (TaskCanceledException ex) + { + Log.LogWarningFromException(ex); + } + catch (OperationCanceledException ex) + { + Log.LogWarningFromException(ex); + } + return !Log.HasLoggedErrors; } internal async Task ExecuteAsync(CancellationToken cancellationToken) @@ -64,11 +76,12 @@ internal async Task ExecuteAsync(CancellationToken cancellationToken) { try { + var picker = new RidGraphManifestPicker(RuntimeIdentifierGraphPath); imageBuilder = await registry.GetImageManifestAsync( BaseImageName, BaseImageTag, ContainerRuntimeIdentifier, - RuntimeIdentifierGraphPath, + picker, cancellationToken).ConfigureAwait(false); } catch (RepositoryNotFoundException) diff --git a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImageToolTask.cs b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImageToolTask.cs index d68c6011b18d..6189d3d74d4e 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImageToolTask.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/CreateNewImageToolTask.cs @@ -187,6 +187,11 @@ internal string GenerateCommandLineCommandsInt() builder.AppendSwitchIfNotNull("--container-user ", ContainerUser); } + if (!string.IsNullOrWhiteSpace(ArchiveOutputPath)) + { + builder.AppendSwitchIfNotNull("--archiveoutputpath ", ArchiveOutputPath); + } + return builder.ToString(); void AppendSwitchIfNotNullSantized(CommandLineBuilder builder, string commandArgName, string propertyName, ITaskItem[] value) diff --git a/src/Containers/Microsoft.NET.Build.Containers/Tasks/ParseContainerProperties.cs b/src/Containers/Microsoft.NET.Build.Containers/Tasks/ParseContainerProperties.cs index f7bc4c755ceb..91486be048f7 100644 --- a/src/Containers/Microsoft.NET.Build.Containers/Tasks/ParseContainerProperties.cs +++ b/src/Containers/Microsoft.NET.Build.Containers/Tasks/ParseContainerProperties.cs @@ -100,12 +100,12 @@ public override bool Execute() Log.LogErrorWithCodeFromResources(nameof(Strings.InvalidTag), nameof(ContainerImageTag), ContainerImageTag); } } - else if (ContainerImageTags.Length != 0 && TryValidateTags(ContainerImageTags, out var valids, out var invalids)) + else if (ContainerImageTags.Length != 0) { - validTags = valids; - if (invalids.Any()) + (validTags, var invalidTags) = TryValidateTags(ContainerImageTags); + if (invalidTags.Any()) { - Log.LogErrorWithCodeFromResources(nameof(Strings.InvalidTags), nameof(ContainerImageTags), String.Join(",", invalids)); + Log.LogErrorWithCodeFromResources(nameof(Strings.InvalidTags), nameof(ContainerImageTags), String.Join(",", invalidTags)); return !Log.HasLoggedErrors; } } @@ -200,7 +200,7 @@ private void ValidateEnvironmentVariables() } } - private static bool TryValidateTags(string[] inputTags, out string[] validTags, out string[] invalidTags) + private static (string[] validTags, string[] invalidTags) TryValidateTags(string[] inputTags) { var v = new List(); var i = new List(); @@ -215,8 +215,6 @@ private static bool TryValidateTags(string[] inputTags, out string[] validTags, i.Add(tag); } } - validTags = v.ToArray(); - invalidTags = i.ToArray(); - return invalidTags.Length == 0; + return (v.ToArray(), i.ToArray()); } } diff --git a/src/Containers/packaging/build/Microsoft.NET.Build.Containers.props b/src/Containers/packaging/build/Microsoft.NET.Build.Containers.props index 669e742dc309..c44684e057f1 100644 --- a/src/Containers/packaging/build/Microsoft.NET.Build.Containers.props +++ b/src/Containers/packaging/build/Microsoft.NET.Build.Containers.props @@ -16,5 +16,5 @@ - + diff --git a/src/Containers/packaging/build/Microsoft.NET.Build.Containers.targets b/src/Containers/packaging/build/Microsoft.NET.Build.Containers.targets index e0dc4c70ac02..ff0b034dba94 100644 --- a/src/Containers/packaging/build/Microsoft.NET.Build.Containers.targets +++ b/src/Containers/packaging/build/Microsoft.NET.Build.Containers.targets @@ -13,6 +13,7 @@ )">true <_ContainerIsTargetingNet8TFM>false <_ContainerIsTargetingNet8TFM Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp' And $([MSBuild]::VersionGreaterThanOrEquals($(_TargetFrameworkVersionWithoutV), '8.0'))">true + <_ContainerIsSelfContained>false <_ContainerIsSelfContained Condition="'$(SelfContained)' == 'true' or '$(PublishSelfContained)' == 'true'">true @@ -30,33 +31,29 @@ - - - - - - + Returns="$(ContainerBaseImage)"> - <_IsAspNet Condition="@(FrameworkReference->Count()) > 0 and @(FrameworkReference->AnyHaveMetadataValue('Identity', 'Microsoft.AspnetCore.App'))">true - - - <_ContainerBaseRegistry>mcr.microsoft.com - <_ContainerBaseImageName Condition="'$(_ContainerIsSelfContained)' == 'true'">dotnet/runtime-deps - <_ContainerBaseImageName Condition="'$(_ContainerBaseImageName)' == '' and '$(_IsAspNet)' == 'true'">dotnet/aspnet - <_ContainerBaseImageName Condition="'$(_ContainerBaseImageName)' == ''">dotnet/runtime - - - <_ContainerIsUsingMicrosoftDefaultImages Condition="'$(ContainerBaseImage)' != ''">false + + $(RuntimeIdentifier) + linux-$(NETCoreSdkPortableRuntimeIdentifier.Split('-')[1]) <_ContainerIsUsingMicrosoftDefaultImages Condition="'$(ContainerBaseImage)' == ''">true - - $(_ContainerBaseRegistry)/$(_ContainerBaseImageName):$(_ContainerBaseImageTag) + <_ContainerIsUsingMicrosoftDefaultImages Condition="'$(ContainerBaseImage)' != ''">false + + + + @@ -91,8 +88,6 @@ - $(RuntimeIdentifier) - linux-$(NETCoreSdkPortableRuntimeIdentifier.Split('-')[1]) <_ContainerIsTargetingWindows>false <_ContainerIsTargetingWindows Condition="$(ContainerRuntimeIdentifier.StartsWith('win'))">true @@ -184,14 +179,27 @@ _ContainerVerifySDKVersion; - ComputeContainerConfig + ComputeContainerConfig; + _CheckContainersPackage - + + + Microsoft.NET.Build.Containers + + + + + + + true + + + + Condition="'$(IsPublishable)' == 'true' AND '$(EnableSdkContainerSupport)' == 'true'"> $(NetCoreRoot) diff --git a/src/Layout/redist/minimumMSBuildVersion b/src/Layout/redist/minimumMSBuildVersion index 2d573323cace..616f65ff5653 100644 --- a/src/Layout/redist/minimumMSBuildVersion +++ b/src/Layout/redist/minimumMSBuildVersion @@ -1 +1 @@ -17.7.0 +17.8.3 diff --git a/src/Layout/redist/redist.csproj b/src/Layout/redist/redist.csproj index 843561432c6e..ebdfd9e7ef94 100644 --- a/src/Layout/redist/redist.csproj +++ b/src/Layout/redist/redist.csproj @@ -51,15 +51,20 @@ + - + + - - + + diff --git a/src/Layout/redist/targets/PublishDotnetWatch.targets b/src/Layout/redist/targets/PublishDotnetWatch.targets index 71c633552f01..be9cf2b38d6f 100644 --- a/src/Layout/redist/targets/PublishDotnetWatch.targets +++ b/src/Layout/redist/targets/PublishDotnetWatch.targets @@ -14,12 +14,17 @@ To reduce the size of the SDK, we use the compiler dependencies that are located in the `Roslyn/bincore` location instead of shipping our own copies in the dotnet-watch tool. These assemblies will be resolved by path in the dotnet-watch executable. + + We make an exception for the Microsoft.CodeAnalysis binaries deployed with the MSBuildWorkspace BuildHosts, since those don't + have any logic to pick up Roslyn from another location. Those can be addressed a different way which tracked in + https://github.com/dotnet/roslyn/issues/70945. --> <_DotnetWatchInputFile Include="@(_DotnetWatchBuildOutput)" - Condition="'%(Filename)' != 'Microsoft.CodeAnalysis' and - '%(Filename)' != 'Microsoft.CodeAnalysis.resources' and - '%(Filename)' != 'Microsoft.CodeAnalysis.CSharp' and - '%(Filename)' != 'Microsoft.CodeAnalysis.CSharp.resources'"/> + Condition="('%(Filename)' != 'Microsoft.CodeAnalysis' and + '%(Filename)' != 'Microsoft.CodeAnalysis.resources' and + '%(Filename)' != 'Microsoft.CodeAnalysis.CSharp' and + '%(Filename)' != 'Microsoft.CodeAnalysis.CSharp.resources') or + $([MSBuild]::ValueOrDefault('%(FullPath)', '').Contains('BuildHost'))" /> diff --git a/src/Layout/tool_msbuild/tool_msbuild.csproj b/src/Layout/tool_msbuild/tool_msbuild.csproj index ce1500ce853a..799d057f115c 100644 --- a/src/Layout/tool_msbuild/tool_msbuild.csproj +++ b/src/Layout/tool_msbuild/tool_msbuild.csproj @@ -13,6 +13,7 @@ + diff --git a/src/Microsoft.DotNet.ApiSymbolExtensions/Microsoft.DotNet.ApiSymbolExtensions.csproj b/src/Microsoft.DotNet.ApiSymbolExtensions/Microsoft.DotNet.ApiSymbolExtensions.csproj index a014b2499ad7..3f1c9f3ddfb8 100644 --- a/src/Microsoft.DotNet.ApiSymbolExtensions/Microsoft.DotNet.ApiSymbolExtensions.csproj +++ b/src/Microsoft.DotNet.ApiSymbolExtensions/Microsoft.DotNet.ApiSymbolExtensions.csproj @@ -14,7 +14,7 @@ - + diff --git a/src/RazorSdk/Targets/Microsoft.NET.Sdk.Razor.Configuration.targets b/src/RazorSdk/Targets/Microsoft.NET.Sdk.Razor.Configuration.targets index 162ba3f8416b..8f0fa59ba967 100644 --- a/src/RazorSdk/Targets/Microsoft.NET.Sdk.Razor.Configuration.targets +++ b/src/RazorSdk/Targets/Microsoft.NET.Sdk.Razor.Configuration.targets @@ -93,8 +93,8 @@ Copyright (c) .NET Foundation. All rights reserved. - Microsoft.AspNetCore.Mvc.Razor.Extensions - $(RazorSdkDirectoryRoot)tools\Microsoft.AspNetCore.Mvc.Razor.Extensions.dll + Microsoft.CodeAnalysis.Razor.Compiler.Mvc + $(RazorSdkDirectoryRoot)tools\Microsoft.CodeAnalysis.Razor.Compiler.Mvc.dll diff --git a/src/RazorSdk/Tool/DiscoverCommand.cs b/src/RazorSdk/Tool/DiscoverCommand.cs index 26ffb93d20e8..fd67bdabc4bb 100644 --- a/src/RazorSdk/Tool/DiscoverCommand.cs +++ b/src/RazorSdk/Tool/DiscoverCommand.cs @@ -109,8 +109,8 @@ internal static void PatchExtensions(CommandOption extensionNames, CommandOption var extensionName = extensionNames.Values[i]; var replacementFileName = extensionName switch { - "MVC-1.0" or "MVC-1.1" => "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X.dll", - "MVC-2.0" or "MVC-2.1" => "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version2_X.dll", + "MVC-1.0" or "MVC-1.1" => "Microsoft.CodeAnalysis.Razor.Compiler.Mvc.Version1_X.dll", + "MVC-2.0" or "MVC-2.1" => "Microsoft.CodeAnalysis.Razor.Compiler.Mvc.Version2_X.dll", _ => null, }; diff --git a/src/RazorSdk/Tool/JsonReaderExtensions.cs b/src/RazorSdk/Tool/JsonReaderExtensions.cs index b27e3e009d09..af6e17715cfc 100644 --- a/src/RazorSdk/Tool/JsonReaderExtensions.cs +++ b/src/RazorSdk/Tool/JsonReaderExtensions.cs @@ -1,56 +1,57 @@ // 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 Newtonsoft.Json; -namespace Microsoft.CodeAnalysis.Razor.Serialization +namespace Microsoft.CodeAnalysis.Razor.Serialization; + +internal static class JsonReaderExtensions { - internal static class JsonReaderExtensions + public static bool ReadTokenAndAdvance(this JsonReader reader, JsonToken expectedTokenType, out object value) { - public static bool ReadTokenAndAdvance(this JsonReader reader, JsonToken expectedTokenType, out object value) - { - value = reader.Value; - return reader.TokenType == expectedTokenType && reader.Read(); - } + value = reader.Value; + return reader.TokenType == expectedTokenType && reader.Read(); + } - public static void ReadProperties(this JsonReader reader, Action onProperty) + public static void ReadProperties(this JsonReader reader, Action onProperty) + { + while (reader.Read()) { - while (reader.Read()) + switch (reader.TokenType) { - switch (reader.TokenType) - { - case JsonToken.PropertyName: - var propertyName = reader.Value.ToString(); - onProperty(propertyName); - break; - case JsonToken.EndObject: - return; - } + case JsonToken.PropertyName: + var propertyName = reader.Value.ToString(); + onProperty(propertyName); + break; + case JsonToken.EndObject: + return; } } + } - public static string ReadNextStringProperty(this JsonReader reader, string propertyName) + public static string ReadNextStringProperty(this JsonReader reader, string propertyName) + { + while (reader.Read()) { - while (reader.Read()) + switch (reader.TokenType) { - switch (reader.TokenType) - { - case JsonToken.PropertyName: - Debug.Assert(reader.Value.ToString() == propertyName); - if (reader.Read()) - { - var value = (string)reader.Value; - return value; - } - else - { - return null; - } - } + case JsonToken.PropertyName: + Debug.Assert(reader.Value.ToString() == propertyName); + if (reader.Read()) + { + var value = (string)reader.Value; + return value; + } + else + { + return null; + } } - - throw new JsonSerializationException($"Could not find string property '{propertyName}'."); } + + throw new JsonSerializationException($"Could not find string property '{propertyName}'."); } } diff --git a/src/RazorSdk/Tool/RazorDiagnosticJsonConverter.cs b/src/RazorSdk/Tool/RazorDiagnosticJsonConverter.cs index a475dd589c7c..e185dc75c617 100644 --- a/src/RazorSdk/Tool/RazorDiagnosticJsonConverter.cs +++ b/src/RazorSdk/Tool/RazorDiagnosticJsonConverter.cs @@ -1,73 +1,73 @@ // 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.Globalization; using Microsoft.AspNetCore.Razor.Language; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -namespace Microsoft.CodeAnalysis.Razor.Serialization +namespace Microsoft.CodeAnalysis.Razor.Serialization; + +internal class RazorDiagnosticJsonConverter : JsonConverter { - internal class RazorDiagnosticJsonConverter : JsonConverter + public static readonly RazorDiagnosticJsonConverter Instance = new RazorDiagnosticJsonConverter(); + private const string RazorDiagnosticMessageKey = "Message"; + + public override bool CanConvert(Type objectType) { - public static readonly RazorDiagnosticJsonConverter Instance = new RazorDiagnosticJsonConverter(); - private const string RazorDiagnosticMessageKey = "Message"; + return typeof(RazorDiagnostic).IsAssignableFrom(objectType); + } - public override bool CanConvert(Type objectType) + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType != JsonToken.StartObject) { - return typeof(RazorDiagnostic).IsAssignableFrom(objectType); + return null; } - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - if (reader.TokenType != JsonToken.StartObject) - { - return null; - } - - var diagnostic = JObject.Load(reader); - var id = diagnostic[nameof(RazorDiagnostic.Id)].Value(); - var severity = diagnostic[nameof(RazorDiagnostic.Severity)].Value(); - var message = diagnostic[RazorDiagnosticMessageKey].Value(); + var diagnostic = JObject.Load(reader); + var id = diagnostic[nameof(RazorDiagnostic.Id)].Value(); + var severity = diagnostic[nameof(RazorDiagnostic.Severity)].Value(); + var message = diagnostic[RazorDiagnosticMessageKey].Value(); - var span = diagnostic[nameof(RazorDiagnostic.Span)].Value(); - var filePath = span[nameof(SourceSpan.FilePath)].Value(); - var absoluteIndex = span[nameof(SourceSpan.AbsoluteIndex)].Value(); - var lineIndex = span[nameof(SourceSpan.LineIndex)].Value(); - var characterIndex = span[nameof(SourceSpan.CharacterIndex)].Value(); - var length = span[nameof(SourceSpan.Length)].Value(); + var span = diagnostic[nameof(RazorDiagnostic.Span)].Value(); + var filePath = span[nameof(SourceSpan.FilePath)].Value(); + var absoluteIndex = span[nameof(SourceSpan.AbsoluteIndex)].Value(); + var lineIndex = span[nameof(SourceSpan.LineIndex)].Value(); + var characterIndex = span[nameof(SourceSpan.CharacterIndex)].Value(); + var length = span[nameof(SourceSpan.Length)].Value(); - var descriptor = new RazorDiagnosticDescriptor(id, () => message, (RazorDiagnosticSeverity)severity); - var sourceSpan = new SourceSpan(filePath, absoluteIndex, lineIndex, characterIndex, length); + var descriptor = new RazorDiagnosticDescriptor(id, message, (RazorDiagnosticSeverity)severity); + var sourceSpan = new SourceSpan(filePath, absoluteIndex, lineIndex, characterIndex, length); - return RazorDiagnostic.Create(descriptor, sourceSpan); - } + return RazorDiagnostic.Create(descriptor, sourceSpan); + } - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - var diagnostic = (RazorDiagnostic)value; + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var diagnostic = (RazorDiagnostic)value; - writer.WriteStartObject(); - WriteProperty(writer, nameof(RazorDiagnostic.Id), diagnostic.Id); - WriteProperty(writer, nameof(RazorDiagnostic.Severity), (int)diagnostic.Severity); - WriteProperty(writer, RazorDiagnosticMessageKey, diagnostic.GetMessage(CultureInfo.CurrentCulture)); + writer.WriteStartObject(); + WriteProperty(writer, nameof(RazorDiagnostic.Id), diagnostic.Id); + WriteProperty(writer, nameof(RazorDiagnostic.Severity), (int)diagnostic.Severity); + WriteProperty(writer, RazorDiagnosticMessageKey, diagnostic.GetMessage(CultureInfo.CurrentCulture)); - writer.WritePropertyName(nameof(RazorDiagnostic.Span)); - writer.WriteStartObject(); - WriteProperty(writer, nameof(SourceSpan.FilePath), diagnostic.Span.FilePath); - WriteProperty(writer, nameof(SourceSpan.AbsoluteIndex), diagnostic.Span.AbsoluteIndex); - WriteProperty(writer, nameof(SourceSpan.LineIndex), diagnostic.Span.LineIndex); - WriteProperty(writer, nameof(SourceSpan.CharacterIndex), diagnostic.Span.CharacterIndex); - WriteProperty(writer, nameof(SourceSpan.Length), diagnostic.Span.Length); - writer.WriteEndObject(); + writer.WritePropertyName(nameof(RazorDiagnostic.Span)); + writer.WriteStartObject(); + WriteProperty(writer, nameof(SourceSpan.FilePath), diagnostic.Span.FilePath); + WriteProperty(writer, nameof(SourceSpan.AbsoluteIndex), diagnostic.Span.AbsoluteIndex); + WriteProperty(writer, nameof(SourceSpan.LineIndex), diagnostic.Span.LineIndex); + WriteProperty(writer, nameof(SourceSpan.CharacterIndex), diagnostic.Span.CharacterIndex); + WriteProperty(writer, nameof(SourceSpan.Length), diagnostic.Span.Length); + writer.WriteEndObject(); - writer.WriteEndObject(); - } + writer.WriteEndObject(); + } - private void WriteProperty(JsonWriter writer, string key, T value) - { - writer.WritePropertyName(key); - writer.WriteValue(value); - } + private void WriteProperty(JsonWriter writer, string key, T value) + { + writer.WritePropertyName(key); + writer.WriteValue(value); } } diff --git a/src/RazorSdk/Tool/TagHelperDescriptorJsonConverter.cs b/src/RazorSdk/Tool/TagHelperDescriptorJsonConverter.cs index f87bdf6683e7..ae0f7acc76e8 100644 --- a/src/RazorSdk/Tool/TagHelperDescriptorJsonConverter.cs +++ b/src/RazorSdk/Tool/TagHelperDescriptorJsonConverter.cs @@ -1,850 +1,865 @@ // 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 Microsoft.AspNetCore.Razor.Language; using Newtonsoft.Json; -namespace Microsoft.CodeAnalysis.Razor.Serialization +namespace Microsoft.CodeAnalysis.Razor.Serialization; + +internal class TagHelperDescriptorJsonConverter : JsonConverter { - internal class TagHelperDescriptorJsonConverter : JsonConverter + public static readonly TagHelperDescriptorJsonConverter Instance = new(); + + public override bool CanConvert(Type objectType) { - public static readonly TagHelperDescriptorJsonConverter Instance = new TagHelperDescriptorJsonConverter(); + return typeof(TagHelperDescriptor).IsAssignableFrom(objectType); + } - public override bool CanConvert(Type objectType) + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType != JsonToken.StartObject) { - return typeof(TagHelperDescriptor).IsAssignableFrom(objectType); + return null; } - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - if (reader.TokenType != JsonToken.StartObject) - { - return null; - } - - // Required tokens (order matters) - var descriptorKind = reader.ReadNextStringProperty(nameof(TagHelperDescriptor.Kind)); - var typeName = reader.ReadNextStringProperty(nameof(TagHelperDescriptor.Name)); - var assemblyName = reader.ReadNextStringProperty(nameof(TagHelperDescriptor.AssemblyName)); - var builder = TagHelperDescriptorBuilder.Create(descriptorKind, typeName, assemblyName); + // Required tokens (order matters) + var descriptorKind = reader.ReadNextStringProperty(nameof(TagHelperDescriptor.Kind)); + var typeName = reader.ReadNextStringProperty(nameof(TagHelperDescriptor.Name)); + var assemblyName = reader.ReadNextStringProperty(nameof(TagHelperDescriptor.AssemblyName)); + using var _ = TagHelperDescriptorBuilder.GetPooledInstance(descriptorKind, typeName, assemblyName, out var builder); - reader.ReadProperties(propertyName => + reader.ReadProperties(propertyName => + { + switch (propertyName) { - switch (propertyName) - { - case nameof(TagHelperDescriptor.Documentation): - if (reader.Read()) - { - var documentation = (string)reader.Value; - builder.Documentation = documentation; - } - break; - case nameof(TagHelperDescriptor.TagOutputHint): - if (reader.Read()) - { - var tagOutputHint = (string)reader.Value; - builder.TagOutputHint = tagOutputHint; - } - break; - case nameof(TagHelperDescriptor.CaseSensitive): - if (reader.Read()) - { - var caseSensitive = (bool)reader.Value; - builder.CaseSensitive = caseSensitive; - } - break; - case nameof(TagHelperDescriptor.TagMatchingRules): - ReadTagMatchingRules(reader, builder); - break; - case nameof(TagHelperDescriptor.BoundAttributes): - ReadBoundAttributes(reader, builder); - break; - case nameof(TagHelperDescriptor.AllowedChildTags): - ReadAllowedChildTags(reader, builder); - break; - case nameof(TagHelperDescriptor.Diagnostics): - ReadDiagnostics(reader, builder.Diagnostics); - break; - case nameof(TagHelperDescriptor.Metadata): - ReadMetadata(reader, builder.Metadata); - break; - } - }); + case nameof(TagHelperDescriptor.Documentation): + if (reader.Read()) + { + var documentation = (string)reader.Value; + builder.SetDocumentation(documentation); + } + break; + case nameof(TagHelperDescriptor.TagOutputHint): + if (reader.Read()) + { + var tagOutputHint = (string)reader.Value; + builder.TagOutputHint = tagOutputHint; + } + break; + case nameof(TagHelperDescriptor.CaseSensitive): + if (reader.Read()) + { + var caseSensitive = (bool)reader.Value; + builder.CaseSensitive = caseSensitive; + } + break; + case nameof(TagHelperDescriptor.TagMatchingRules): + ReadTagMatchingRules(reader, builder); + break; + case nameof(TagHelperDescriptor.BoundAttributes): + ReadBoundAttributes(reader, builder); + break; + case nameof(TagHelperDescriptor.AllowedChildTags): + ReadAllowedChildTags(reader, builder); + break; + case nameof(TagHelperDescriptor.Diagnostics): + ReadDiagnostics(reader, builder.Diagnostics); + break; + case nameof(TagHelperDescriptor.Metadata): + ReadMetadata(reader, builder.Metadata); + break; + } + }); + + return builder.Build(); + } - return builder.Build(); - } + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var tagHelper = (TagHelperDescriptor)value; - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - var tagHelper = (TagHelperDescriptor)value; + writer.WriteStartObject(); - writer.WriteStartObject(); + writer.WritePropertyName(nameof(TagHelperDescriptor.Kind)); + writer.WriteValue(tagHelper.Kind); - writer.WritePropertyName(nameof(TagHelperDescriptor.Kind)); - writer.WriteValue(tagHelper.Kind); + writer.WritePropertyName(nameof(TagHelperDescriptor.Name)); + writer.WriteValue(tagHelper.Name); - writer.WritePropertyName(nameof(TagHelperDescriptor.Name)); - writer.WriteValue(tagHelper.Name); + writer.WritePropertyName(nameof(TagHelperDescriptor.AssemblyName)); + writer.WriteValue(tagHelper.AssemblyName); - writer.WritePropertyName(nameof(TagHelperDescriptor.AssemblyName)); - writer.WriteValue(tagHelper.AssemblyName); + if (tagHelper.Documentation != null) + { + writer.WritePropertyName(nameof(TagHelperDescriptor.Documentation)); + writer.WriteValue(tagHelper.Documentation); + } - if (tagHelper.Documentation != null) - { - writer.WritePropertyName(nameof(TagHelperDescriptor.Documentation)); - writer.WriteValue(tagHelper.Documentation); - } + if (tagHelper.TagOutputHint != null) + { + writer.WritePropertyName(nameof(TagHelperDescriptor.TagOutputHint)); + writer.WriteValue(tagHelper.TagOutputHint); + } - if (tagHelper.TagOutputHint != null) - { - writer.WritePropertyName(nameof(TagHelperDescriptor.TagOutputHint)); - writer.WriteValue(tagHelper.TagOutputHint); - } + writer.WritePropertyName(nameof(TagHelperDescriptor.CaseSensitive)); + writer.WriteValue(tagHelper.CaseSensitive); - writer.WritePropertyName(nameof(TagHelperDescriptor.CaseSensitive)); - writer.WriteValue(tagHelper.CaseSensitive); + writer.WritePropertyName(nameof(TagHelperDescriptor.TagMatchingRules)); + writer.WriteStartArray(); + foreach (var ruleDescriptor in tagHelper.TagMatchingRules) + { + WriteTagMatchingRule(writer, ruleDescriptor, serializer); + } + writer.WriteEndArray(); - writer.WritePropertyName(nameof(TagHelperDescriptor.TagMatchingRules)); + if (tagHelper.BoundAttributes != null && tagHelper.BoundAttributes.Length > 0) + { + writer.WritePropertyName(nameof(TagHelperDescriptor.BoundAttributes)); writer.WriteStartArray(); - foreach (var ruleDescriptor in tagHelper.TagMatchingRules) + foreach (var boundAttribute in tagHelper.BoundAttributes) { - WriteTagMatchingRule(writer, ruleDescriptor, serializer); + WriteBoundAttribute(writer, boundAttribute, serializer); } writer.WriteEndArray(); + } - if (tagHelper.BoundAttributes != null && tagHelper.BoundAttributes.Count > 0) - { - writer.WritePropertyName(nameof(TagHelperDescriptor.BoundAttributes)); - writer.WriteStartArray(); - foreach (var boundAttribute in tagHelper.BoundAttributes) - { - WriteBoundAttribute(writer, boundAttribute, serializer); - } - writer.WriteEndArray(); - } - - if (tagHelper.AllowedChildTags != null && tagHelper.AllowedChildTags.Count > 0) - { - writer.WritePropertyName(nameof(TagHelperDescriptor.AllowedChildTags)); - writer.WriteStartArray(); - foreach (var allowedChildTag in tagHelper.AllowedChildTags) - { - WriteAllowedChildTags(writer, allowedChildTag, serializer); - } - writer.WriteEndArray(); - } - - if (tagHelper.Diagnostics != null && tagHelper.Diagnostics.Count > 0) + if (tagHelper.AllowedChildTags != null && tagHelper.AllowedChildTags.Length > 0) + { + writer.WritePropertyName(nameof(TagHelperDescriptor.AllowedChildTags)); + writer.WriteStartArray(); + foreach (var allowedChildTag in tagHelper.AllowedChildTags) { - writer.WritePropertyName(nameof(TagHelperDescriptor.Diagnostics)); - serializer.Serialize(writer, tagHelper.Diagnostics); + WriteAllowedChildTags(writer, allowedChildTag, serializer); } - - writer.WritePropertyName(nameof(TagHelperDescriptor.Metadata)); - WriteMetadata(writer, tagHelper.Metadata); - - writer.WriteEndObject(); + writer.WriteEndArray(); } - private static void WriteAllowedChildTags(JsonWriter writer, AllowedChildTagDescriptor allowedChildTag, JsonSerializer serializer) + if (tagHelper.Diagnostics != null && tagHelper.Diagnostics.Length > 0) { - writer.WriteStartObject(); - - writer.WritePropertyName(nameof(AllowedChildTagDescriptor.Name)); - writer.WriteValue(allowedChildTag.Name); + writer.WritePropertyName(nameof(TagHelperDescriptor.Diagnostics)); + serializer.Serialize(writer, tagHelper.Diagnostics); + } - writer.WritePropertyName(nameof(AllowedChildTagDescriptor.DisplayName)); - writer.WriteValue(allowedChildTag.DisplayName); + writer.WritePropertyName(nameof(TagHelperDescriptor.Metadata)); + WriteMetadata(writer, tagHelper.Metadata); - writer.WritePropertyName(nameof(AllowedChildTagDescriptor.Diagnostics)); - serializer.Serialize(writer, allowedChildTag.Diagnostics); + writer.WriteEndObject(); + } - writer.WriteEndObject(); - } + private static void WriteAllowedChildTags(JsonWriter writer, AllowedChildTagDescriptor allowedChildTag, JsonSerializer serializer) + { + writer.WriteStartObject(); - private static void WriteBoundAttribute(JsonWriter writer, BoundAttributeDescriptor boundAttribute, JsonSerializer serializer) - { - writer.WriteStartObject(); + writer.WritePropertyName(nameof(AllowedChildTagDescriptor.Name)); + writer.WriteValue(allowedChildTag.Name); - writer.WritePropertyName(nameof(BoundAttributeDescriptor.Kind)); - writer.WriteValue(boundAttribute.Kind); + writer.WritePropertyName(nameof(AllowedChildTagDescriptor.DisplayName)); + writer.WriteValue(allowedChildTag.DisplayName); - writer.WritePropertyName(nameof(BoundAttributeDescriptor.Name)); - writer.WriteValue(boundAttribute.Name); + writer.WritePropertyName(nameof(AllowedChildTagDescriptor.Diagnostics)); + serializer.Serialize(writer, allowedChildTag.Diagnostics); - writer.WritePropertyName(nameof(BoundAttributeDescriptor.TypeName)); - writer.WriteValue(boundAttribute.TypeName); + writer.WriteEndObject(); + } - if (boundAttribute.IsEnum) - { - writer.WritePropertyName(nameof(BoundAttributeDescriptor.IsEnum)); - writer.WriteValue(boundAttribute.IsEnum); - } + private static void WriteBoundAttribute(JsonWriter writer, BoundAttributeDescriptor boundAttribute, JsonSerializer serializer) + { + writer.WriteStartObject(); - if (boundAttribute.IndexerNamePrefix != null) - { - writer.WritePropertyName(nameof(BoundAttributeDescriptor.IndexerNamePrefix)); - writer.WriteValue(boundAttribute.IndexerNamePrefix); - } + writer.WritePropertyName(nameof(BoundAttributeDescriptor.Kind)); + writer.WriteValue(boundAttribute.Kind); - if (boundAttribute.IndexerTypeName != null) - { - writer.WritePropertyName(nameof(BoundAttributeDescriptor.IndexerTypeName)); - writer.WriteValue(boundAttribute.IndexerTypeName); - } + writer.WritePropertyName(nameof(BoundAttributeDescriptor.Name)); + writer.WriteValue(boundAttribute.Name); - if (boundAttribute.Documentation != null) - { - writer.WritePropertyName(nameof(BoundAttributeDescriptor.Documentation)); - writer.WriteValue(boundAttribute.Documentation); - } + writer.WritePropertyName(nameof(BoundAttributeDescriptor.TypeName)); + writer.WriteValue(boundAttribute.TypeName); - if (boundAttribute.Diagnostics != null && boundAttribute.Diagnostics.Count > 0) - { - writer.WritePropertyName(nameof(BoundAttributeDescriptor.Diagnostics)); - serializer.Serialize(writer, boundAttribute.Diagnostics); - } + if (boundAttribute.IsEnum) + { + writer.WritePropertyName(nameof(BoundAttributeDescriptor.IsEnum)); + writer.WriteValue(boundAttribute.IsEnum); + } - writer.WritePropertyName(nameof(BoundAttributeDescriptor.Metadata)); - WriteMetadata(writer, boundAttribute.Metadata); + if (boundAttribute.IndexerNamePrefix != null) + { + writer.WritePropertyName(nameof(BoundAttributeDescriptor.IndexerNamePrefix)); + writer.WriteValue(boundAttribute.IndexerNamePrefix); + } - if (boundAttribute.BoundAttributeParameters != null && boundAttribute.BoundAttributeParameters.Count > 0) - { - writer.WritePropertyName(nameof(BoundAttributeDescriptor.BoundAttributeParameters)); - writer.WriteStartArray(); - foreach (var boundAttributeParameter in boundAttribute.BoundAttributeParameters) - { - WriteBoundAttributeParameter(writer, boundAttributeParameter, serializer); - } - writer.WriteEndArray(); - } + if (boundAttribute.IsEditorRequired) + { + writer.WritePropertyName(nameof(BoundAttributeDescriptor.IsEditorRequired)); + writer.WriteValue(boundAttribute.IsEditorRequired); + } - writer.WriteEndObject(); + if (boundAttribute.IndexerTypeName != null) + { + writer.WritePropertyName(nameof(BoundAttributeDescriptor.IndexerTypeName)); + writer.WriteValue(boundAttribute.IndexerTypeName); } - private static void WriteBoundAttributeParameter(JsonWriter writer, BoundAttributeParameterDescriptor boundAttributeParameter, JsonSerializer serializer) + if (boundAttribute.Documentation != null) { - writer.WriteStartObject(); + writer.WritePropertyName(nameof(BoundAttributeDescriptor.Documentation)); + writer.WriteValue(boundAttribute.Documentation); + } - writer.WritePropertyName(nameof(BoundAttributeParameterDescriptor.Name)); - writer.WriteValue(boundAttributeParameter.Name); + if (boundAttribute.Diagnostics != null && boundAttribute.Diagnostics.Length > 0) + { + writer.WritePropertyName(nameof(BoundAttributeDescriptor.Diagnostics)); + serializer.Serialize(writer, boundAttribute.Diagnostics); + } - writer.WritePropertyName(nameof(BoundAttributeParameterDescriptor.TypeName)); - writer.WriteValue(boundAttributeParameter.TypeName); + writer.WritePropertyName(nameof(BoundAttributeDescriptor.Metadata)); + WriteMetadata(writer, boundAttribute.Metadata); - if (boundAttributeParameter.IsEnum != default) + if (boundAttribute.Parameters != null && boundAttribute.Parameters.Length > 0) + { + writer.WritePropertyName(nameof(BoundAttributeDescriptor.Parameters)); + writer.WriteStartArray(); + foreach (var boundAttributeParameter in boundAttribute.Parameters) { - writer.WritePropertyName(nameof(BoundAttributeParameterDescriptor.IsEnum)); - writer.WriteValue(boundAttributeParameter.IsEnum); + WriteBoundAttributeParameter(writer, boundAttributeParameter, serializer); } + writer.WriteEndArray(); + } - if (boundAttributeParameter.Documentation != null) - { - writer.WritePropertyName(nameof(BoundAttributeParameterDescriptor.Documentation)); - writer.WriteValue(boundAttributeParameter.Documentation); - } + writer.WriteEndObject(); + } - if (boundAttributeParameter.Diagnostics != null && boundAttributeParameter.Diagnostics.Count > 0) - { - writer.WritePropertyName(nameof(BoundAttributeParameterDescriptor.Diagnostics)); - serializer.Serialize(writer, boundAttributeParameter.Diagnostics); - } + private static void WriteBoundAttributeParameter(JsonWriter writer, BoundAttributeParameterDescriptor boundAttributeParameter, JsonSerializer serializer) + { + writer.WriteStartObject(); + + writer.WritePropertyName(nameof(BoundAttributeParameterDescriptor.Name)); + writer.WriteValue(boundAttributeParameter.Name); - writer.WritePropertyName(nameof(BoundAttributeParameterDescriptor.Metadata)); - WriteMetadata(writer, boundAttributeParameter.Metadata); + writer.WritePropertyName(nameof(BoundAttributeParameterDescriptor.TypeName)); + writer.WriteValue(boundAttributeParameter.TypeName); - writer.WriteEndObject(); + if (boundAttributeParameter.IsEnum != default) + { + writer.WritePropertyName(nameof(BoundAttributeParameterDescriptor.IsEnum)); + writer.WriteValue(boundAttributeParameter.IsEnum); } - private static void WriteMetadata(JsonWriter writer, IReadOnlyDictionary metadata) + if (boundAttributeParameter.Documentation != null) { - writer.WriteStartObject(); - foreach (var kvp in metadata) - { - writer.WritePropertyName(kvp.Key); - writer.WriteValue(kvp.Value); - } - writer.WriteEndObject(); + writer.WritePropertyName(nameof(BoundAttributeParameterDescriptor.Documentation)); + writer.WriteValue(boundAttributeParameter.Documentation); } - private static void WriteTagMatchingRule(JsonWriter writer, TagMatchingRuleDescriptor ruleDescriptor, JsonSerializer serializer) + if (boundAttributeParameter.Diagnostics != null && boundAttributeParameter.Diagnostics.Length > 0) { - writer.WriteStartObject(); + writer.WritePropertyName(nameof(BoundAttributeParameterDescriptor.Diagnostics)); + serializer.Serialize(writer, boundAttributeParameter.Diagnostics); + } - writer.WritePropertyName(nameof(TagMatchingRuleDescriptor.TagName)); - writer.WriteValue(ruleDescriptor.TagName); + writer.WritePropertyName(nameof(BoundAttributeParameterDescriptor.Metadata)); + WriteMetadata(writer, boundAttributeParameter.Metadata); - if (ruleDescriptor.ParentTag != null) - { - writer.WritePropertyName(nameof(TagMatchingRuleDescriptor.ParentTag)); - writer.WriteValue(ruleDescriptor.ParentTag); - } + writer.WriteEndObject(); + } - if (ruleDescriptor.TagStructure != default) - { - writer.WritePropertyName(nameof(TagMatchingRuleDescriptor.TagStructure)); - writer.WriteValue(ruleDescriptor.TagStructure); - } + private static void WriteMetadata(JsonWriter writer, MetadataCollection metadata) + { + writer.WriteStartObject(); + foreach (var kvp in metadata) + { + writer.WritePropertyName(kvp.Key); + writer.WriteValue(kvp.Value); + } + writer.WriteEndObject(); + } - if (ruleDescriptor.Attributes != null && ruleDescriptor.Attributes.Count > 0) - { - writer.WritePropertyName(nameof(TagMatchingRuleDescriptor.Attributes)); - writer.WriteStartArray(); - foreach (var requiredAttribute in ruleDescriptor.Attributes) - { - WriteRequiredAttribute(writer, requiredAttribute, serializer); - } - writer.WriteEndArray(); - } + private static void WriteTagMatchingRule(JsonWriter writer, TagMatchingRuleDescriptor ruleDescriptor, JsonSerializer serializer) + { + writer.WriteStartObject(); - if (ruleDescriptor.Diagnostics != null && ruleDescriptor.Diagnostics.Count > 0) - { - writer.WritePropertyName(nameof(TagMatchingRuleDescriptor.Diagnostics)); - serializer.Serialize(writer, ruleDescriptor.Diagnostics); - } + writer.WritePropertyName(nameof(TagMatchingRuleDescriptor.TagName)); + writer.WriteValue(ruleDescriptor.TagName); - writer.WriteEndObject(); + if (ruleDescriptor.ParentTag != null) + { + writer.WritePropertyName(nameof(TagMatchingRuleDescriptor.ParentTag)); + writer.WriteValue(ruleDescriptor.ParentTag); } - private static void WriteRequiredAttribute(JsonWriter writer, RequiredAttributeDescriptor requiredAttribute, JsonSerializer serializer) + if (ruleDescriptor.TagStructure != default) { - writer.WriteStartObject(); - - writer.WritePropertyName(nameof(RequiredAttributeDescriptor.Name)); - writer.WriteValue(requiredAttribute.Name); + writer.WritePropertyName(nameof(TagMatchingRuleDescriptor.TagStructure)); + writer.WriteValue(ruleDescriptor.TagStructure); + } - if (requiredAttribute.NameComparison != default) + if (ruleDescriptor.Attributes != null && ruleDescriptor.Attributes.Length > 0) + { + writer.WritePropertyName(nameof(TagMatchingRuleDescriptor.Attributes)); + writer.WriteStartArray(); + foreach (var requiredAttribute in ruleDescriptor.Attributes) { - writer.WritePropertyName(nameof(RequiredAttributeDescriptor.NameComparison)); - writer.WriteValue(requiredAttribute.NameComparison); + WriteRequiredAttribute(writer, requiredAttribute, serializer); } + writer.WriteEndArray(); + } - if (requiredAttribute.Value != null) - { - writer.WritePropertyName(nameof(RequiredAttributeDescriptor.Value)); - writer.WriteValue(requiredAttribute.Value); - } + if (ruleDescriptor.Diagnostics != null && ruleDescriptor.Diagnostics.Length > 0) + { + writer.WritePropertyName(nameof(TagMatchingRuleDescriptor.Diagnostics)); + serializer.Serialize(writer, ruleDescriptor.Diagnostics); + } - if (requiredAttribute.ValueComparison != default) - { - writer.WritePropertyName(nameof(RequiredAttributeDescriptor.ValueComparison)); - writer.WriteValue(requiredAttribute.ValueComparison); - } + writer.WriteEndObject(); + } - if (requiredAttribute.Diagnostics != null && requiredAttribute.Diagnostics.Count > 0) - { - writer.WritePropertyName(nameof(RequiredAttributeDescriptor.Diagnostics)); - serializer.Serialize(writer, requiredAttribute.Diagnostics); - } + private static void WriteRequiredAttribute(JsonWriter writer, RequiredAttributeDescriptor requiredAttribute, JsonSerializer serializer) + { + writer.WriteStartObject(); - if (requiredAttribute.Metadata != null && requiredAttribute.Metadata.Count > 0) - { - writer.WritePropertyName(nameof(RequiredAttributeDescriptor.Metadata)); - WriteMetadata(writer, requiredAttribute.Metadata); - } + writer.WritePropertyName(nameof(RequiredAttributeDescriptor.Name)); + writer.WriteValue(requiredAttribute.Name); - writer.WriteEndObject(); + if (requiredAttribute.NameComparison != default) + { + writer.WritePropertyName(nameof(RequiredAttributeDescriptor.NameComparison)); + writer.WriteValue(requiredAttribute.NameComparison); } - private static void ReadBoundAttributes(JsonReader reader, TagHelperDescriptorBuilder builder) + if (requiredAttribute.Value != null) { - if (!reader.Read()) - { - return; - } - - if (reader.TokenType != JsonToken.StartArray) - { - return; - } - - do - { - ReadBoundAttribute(reader, builder); - } while (reader.TokenType != JsonToken.EndArray); + writer.WritePropertyName(nameof(RequiredAttributeDescriptor.Value)); + writer.WriteValue(requiredAttribute.Value); } - private static void ReadBoundAttribute(JsonReader reader, TagHelperDescriptorBuilder builder) + if (requiredAttribute.ValueComparison != default) { - if (!reader.Read()) - { - return; - } - - if (reader.TokenType != JsonToken.StartObject) - { - return; - } - - builder.BindAttribute(attribute => - { - reader.ReadProperties(propertyName => - { - switch (propertyName) - { - case nameof(BoundAttributeDescriptor.Name): - if (reader.Read()) - { - var name = (string)reader.Value; - attribute.Name = name; - } - break; - case nameof(BoundAttributeDescriptor.TypeName): - if (reader.Read()) - { - var typeName = (string)reader.Value; - attribute.TypeName = typeName; - } - break; - case nameof(BoundAttributeDescriptor.Documentation): - if (reader.Read()) - { - var documentation = (string)reader.Value; - attribute.Documentation = documentation; - } - break; - case nameof(BoundAttributeDescriptor.IndexerNamePrefix): - if (reader.Read()) - { - var indexerNamePrefix = (string)reader.Value; - if (indexerNamePrefix != null) - { - attribute.IsDictionary = true; - attribute.IndexerAttributeNamePrefix = indexerNamePrefix; - } - } - break; - case nameof(BoundAttributeDescriptor.IndexerTypeName): - if (reader.Read()) - { - var indexerTypeName = (string)reader.Value; - if (indexerTypeName != null) - { - attribute.IsDictionary = true; - attribute.IndexerValueTypeName = indexerTypeName; - } - } - break; - case nameof(BoundAttributeDescriptor.IsEnum): - if (reader.Read()) - { - var isEnum = (bool)reader.Value; - attribute.IsEnum = isEnum; - } - break; - case nameof(BoundAttributeDescriptor.BoundAttributeParameters): - ReadBoundAttributeParameters(reader, attribute); - break; - case nameof(BoundAttributeDescriptor.Diagnostics): - ReadDiagnostics(reader, attribute.Diagnostics); - break; - case nameof(BoundAttributeDescriptor.Metadata): - ReadMetadata(reader, attribute.Metadata); - break; - } - }); - }); + writer.WritePropertyName(nameof(RequiredAttributeDescriptor.ValueComparison)); + writer.WriteValue(requiredAttribute.ValueComparison); } - private static void ReadBoundAttributeParameters(JsonReader reader, BoundAttributeDescriptorBuilder builder) + if (requiredAttribute.Diagnostics != null && requiredAttribute.Diagnostics.Length > 0) { - if (!reader.Read()) - { - return; - } - - if (reader.TokenType != JsonToken.StartArray) - { - return; - } - - do - { - ReadBoundAttributeParameter(reader, builder); - } while (reader.TokenType != JsonToken.EndArray); + writer.WritePropertyName(nameof(RequiredAttributeDescriptor.Diagnostics)); + serializer.Serialize(writer, requiredAttribute.Diagnostics); } - private static void ReadBoundAttributeParameter(JsonReader reader, BoundAttributeDescriptorBuilder builder) + if (requiredAttribute.Metadata != null && requiredAttribute.Metadata.Count > 0) { - if (!reader.Read()) - { - return; - } + writer.WritePropertyName(nameof(RequiredAttributeDescriptor.Metadata)); + WriteMetadata(writer, requiredAttribute.Metadata); + } - if (reader.TokenType != JsonToken.StartObject) - { - return; - } + writer.WriteEndObject(); + } - builder.BindAttributeParameter(parameter => - { - reader.ReadProperties(propertyName => - { - switch (propertyName) - { - case nameof(BoundAttributeParameterDescriptor.Name): - if (reader.Read()) - { - var name = (string)reader.Value; - parameter.Name = name; - } - break; - case nameof(BoundAttributeParameterDescriptor.TypeName): - if (reader.Read()) - { - var typeName = (string)reader.Value; - parameter.TypeName = typeName; - } - break; - case nameof(BoundAttributeParameterDescriptor.IsEnum): - if (reader.Read()) - { - var isEnum = (bool)reader.Value; - parameter.IsEnum = isEnum; - } - break; - case nameof(BoundAttributeParameterDescriptor.Documentation): - if (reader.Read()) - { - var documentation = (string)reader.Value; - parameter.Documentation = documentation; - } - break; - case nameof(BoundAttributeParameterDescriptor.Metadata): - ReadMetadata(reader, parameter.Metadata); - break; - case nameof(BoundAttributeParameterDescriptor.Diagnostics): - ReadDiagnostics(reader, parameter.Diagnostics); - break; - } - }); - }); + private static void ReadBoundAttributes(JsonReader reader, TagHelperDescriptorBuilder builder) + { + if (!reader.Read()) + { + return; } - private static void ReadTagMatchingRules(JsonReader reader, TagHelperDescriptorBuilder builder) + if (reader.TokenType != JsonToken.StartArray) { - if (!reader.Read()) - { - return; - } + return; + } - if (reader.TokenType != JsonToken.StartArray) - { - return; - } + do + { + ReadBoundAttribute(reader, builder); + } while (reader.TokenType != JsonToken.EndArray); + } - do - { - ReadTagMatchingRule(reader, builder); - } while (reader.TokenType != JsonToken.EndArray); + private static void ReadBoundAttribute(JsonReader reader, TagHelperDescriptorBuilder builder) + { + if (!reader.Read()) + { + return; } - private static void ReadTagMatchingRule(JsonReader reader, TagHelperDescriptorBuilder builder) + if (reader.TokenType != JsonToken.StartObject) { - if (!reader.Read()) - { - return; - } - - if (reader.TokenType != JsonToken.StartObject) - { - return; - } + return; + } - builder.TagMatchingRule(rule => + builder.BindAttribute(attribute => + { + reader.ReadProperties(propertyName => { - reader.ReadProperties(propertyName => + switch (propertyName) { - switch (propertyName) - { - case nameof(TagMatchingRuleDescriptor.TagName): - if (reader.Read()) + case nameof(BoundAttributeDescriptor.Name): + if (reader.Read()) + { + var name = (string)reader.Value; + attribute.Name = name; + } + break; + case nameof(BoundAttributeDescriptor.TypeName): + if (reader.Read()) + { + var typeName = (string)reader.Value; + attribute.TypeName = typeName; + } + break; + case nameof(BoundAttributeDescriptor.Documentation): + if (reader.Read()) + { + var documentation = (string)reader.Value; + attribute.SetDocumentation(documentation); + } + break; + case nameof(BoundAttributeDescriptor.IndexerNamePrefix): + if (reader.Read()) + { + var indexerNamePrefix = (string)reader.Value; + if (indexerNamePrefix != null) { - var tagName = (string)reader.Value; - rule.TagName = tagName; + attribute.IsDictionary = true; + attribute.IndexerAttributeNamePrefix = indexerNamePrefix; } - break; - case nameof(TagMatchingRuleDescriptor.ParentTag): - if (reader.Read()) + } + break; + case nameof(BoundAttributeDescriptor.IndexerTypeName): + if (reader.Read()) + { + var indexerTypeName = (string)reader.Value; + if (indexerTypeName != null) { - var parentTag = (string)reader.Value; - rule.ParentTag = parentTag; + attribute.IsDictionary = true; + attribute.IndexerValueTypeName = indexerTypeName; } - break; - case nameof(TagMatchingRuleDescriptor.TagStructure): - rule.TagStructure = (TagStructure)reader.ReadAsInt32(); - break; - case nameof(TagMatchingRuleDescriptor.Attributes): - ReadRequiredAttributeValues(reader, rule); - break; - case nameof(TagMatchingRuleDescriptor.Diagnostics): - ReadDiagnostics(reader, rule.Diagnostics); - break; - } - }); + } + break; + case nameof(BoundAttributeDescriptor.IsEnum): + if (reader.Read()) + { + var isEnum = (bool)reader.Value; + attribute.IsEnum = isEnum; + } + break; + case nameof(BoundAttributeDescriptor.IsEditorRequired): + if (reader.Read()) + { + var value = (bool)reader.Value; + attribute.IsEditorRequired = value; + } + break; + case nameof(BoundAttributeDescriptor.Parameters): + ReadBoundAttributeParameters(reader, attribute); + break; + case nameof(BoundAttributeDescriptor.Diagnostics): + ReadDiagnostics(reader, attribute.Diagnostics); + break; + case nameof(BoundAttributeDescriptor.Metadata): + ReadMetadata(reader, attribute.Metadata); + break; + } }); + }); + } + + private static void ReadBoundAttributeParameters(JsonReader reader, BoundAttributeDescriptorBuilder builder) + { + if (!reader.Read()) + { + return; } - private static void ReadRequiredAttributeValues(JsonReader reader, TagMatchingRuleDescriptorBuilder builder) + if (reader.TokenType != JsonToken.StartArray) { - if (!reader.Read()) - { - return; - } + return; + } - if (reader.TokenType != JsonToken.StartArray) - { - return; - } + do + { + ReadBoundAttributeParameter(reader, builder); + } while (reader.TokenType != JsonToken.EndArray); + } - do - { - ReadRequiredAttribute(reader, builder); - } while (reader.TokenType != JsonToken.EndArray); + private static void ReadBoundAttributeParameter(JsonReader reader, BoundAttributeDescriptorBuilder builder) + { + if (!reader.Read()) + { + return; } - private static void ReadRequiredAttribute(JsonReader reader, TagMatchingRuleDescriptorBuilder builder) + if (reader.TokenType != JsonToken.StartObject) { - if (!reader.Read()) - { - return; - } - - if (reader.TokenType != JsonToken.StartObject) - { - return; - } + return; + } - builder.Attribute(attribute => + builder.BindAttributeParameter(parameter => + { + reader.ReadProperties(propertyName => { - reader.ReadProperties(propertyName => + switch (propertyName) { - switch (propertyName) - { - case nameof(RequiredAttributeDescriptor.Name): - if (reader.Read()) - { - var name = (string)reader.Value; - attribute.Name = name; - } - break; - case nameof(RequiredAttributeDescriptor.NameComparison): - var nameComparison = (RequiredAttributeDescriptor.NameComparisonMode)reader.ReadAsInt32(); - attribute.NameComparisonMode = nameComparison; - break; - case nameof(RequiredAttributeDescriptor.Value): - if (reader.Read()) - { - var value = (string)reader.Value; - attribute.Value = value; - } - break; - case nameof(RequiredAttributeDescriptor.ValueComparison): - var valueComparison = (RequiredAttributeDescriptor.ValueComparisonMode)reader.ReadAsInt32(); - attribute.ValueComparisonMode = valueComparison; - break; - case nameof(RequiredAttributeDescriptor.Diagnostics): - ReadDiagnostics(reader, attribute.Diagnostics); - break; - case nameof(RequiredAttributeDescriptor.Metadata): - ReadMetadata(reader, attribute.Metadata); - break; - } - }); + case nameof(BoundAttributeParameterDescriptor.Name): + if (reader.Read()) + { + var name = (string)reader.Value; + parameter.Name = name; + } + break; + case nameof(BoundAttributeParameterDescriptor.TypeName): + if (reader.Read()) + { + var typeName = (string)reader.Value; + parameter.TypeName = typeName; + } + break; + case nameof(BoundAttributeParameterDescriptor.IsEnum): + if (reader.Read()) + { + var isEnum = (bool)reader.Value; + parameter.IsEnum = isEnum; + } + break; + case nameof(BoundAttributeParameterDescriptor.Documentation): + if (reader.Read()) + { + var documentation = (string)reader.Value; + parameter.SetDocumentation(documentation); + } + break; + case nameof(BoundAttributeParameterDescriptor.Metadata): + ReadMetadata(reader, parameter.Metadata); + break; + case nameof(BoundAttributeParameterDescriptor.Diagnostics): + ReadDiagnostics(reader, parameter.Diagnostics); + break; + } }); - } + }); + } - private static void ReadAllowedChildTags(JsonReader reader, TagHelperDescriptorBuilder builder) + private static void ReadTagMatchingRules(JsonReader reader, TagHelperDescriptorBuilder builder) + { + if (!reader.Read()) { - if (!reader.Read()) - { - return; - } - - if (reader.TokenType != JsonToken.StartArray) - { - return; - } - - do - { - ReadAllowedChildTag(reader, builder); - } while (reader.TokenType != JsonToken.EndArray); + return; } - private static void ReadAllowedChildTag(JsonReader reader, TagHelperDescriptorBuilder builder) + if (reader.TokenType != JsonToken.StartArray) { - if (!reader.Read()) - { - return; - } + return; + } - if (reader.TokenType != JsonToken.StartObject) - { - return; - } + do + { + ReadTagMatchingRule(reader, builder); + } while (reader.TokenType != JsonToken.EndArray); + } - builder.AllowChildTag(childTag => - { - reader.ReadProperties(propertyName => - { - switch (propertyName) - { - case nameof(AllowedChildTagDescriptor.Name): - if (reader.Read()) - { - var name = (string)reader.Value; - childTag.Name = name; - } - break; - case nameof(AllowedChildTagDescriptor.DisplayName): - if (reader.Read()) - { - var displayName = (string)reader.Value; - childTag.DisplayName = displayName; - } - break; - case nameof(AllowedChildTagDescriptor.Diagnostics): - ReadDiagnostics(reader, childTag.Diagnostics); - break; - } - }); - }); + private static void ReadTagMatchingRule(JsonReader reader, TagHelperDescriptorBuilder builder) + { + if (!reader.Read()) + { + return; } - private static void ReadMetadata(JsonReader reader, IDictionary metadata) + if (reader.TokenType != JsonToken.StartObject) { - if (!reader.Read()) - { - return; - } - - if (reader.TokenType != JsonToken.StartObject) - { - return; - } + return; + } + builder.TagMatchingRule(rule => + { reader.ReadProperties(propertyName => { - if (reader.Read()) + switch (propertyName) { - var value = (string)reader.Value; - metadata[propertyName] = value; + case nameof(TagMatchingRuleDescriptor.TagName): + if (reader.Read()) + { + var tagName = (string)reader.Value; + rule.TagName = tagName; + } + break; + case nameof(TagMatchingRuleDescriptor.ParentTag): + if (reader.Read()) + { + var parentTag = (string)reader.Value; + rule.ParentTag = parentTag; + } + break; + case nameof(TagMatchingRuleDescriptor.TagStructure): + rule.TagStructure = (TagStructure)reader.ReadAsInt32(); + break; + case nameof(TagMatchingRuleDescriptor.Attributes): + ReadRequiredAttributeValues(reader, rule); + break; + case nameof(TagMatchingRuleDescriptor.Diagnostics): + ReadDiagnostics(reader, rule.Diagnostics); + break; } }); - } + }); + } - private static void ReadDiagnostics(JsonReader reader, RazorDiagnosticCollection diagnostics) + private static void ReadRequiredAttributeValues(JsonReader reader, TagMatchingRuleDescriptorBuilder builder) + { + if (!reader.Read()) { - if (!reader.Read()) - { - return; - } - - if (reader.TokenType != JsonToken.StartArray) - { - return; - } + return; + } - do - { - ReadDiagnostic(reader, diagnostics); - } while (reader.TokenType != JsonToken.EndArray); + if (reader.TokenType != JsonToken.StartArray) + { + return; } - private static void ReadDiagnostic(JsonReader reader, RazorDiagnosticCollection diagnostics) + do { - if (!reader.Read()) - { - return; - } + ReadRequiredAttribute(reader, builder); + } while (reader.TokenType != JsonToken.EndArray); + } - if (reader.TokenType != JsonToken.StartObject) - { - return; - } + private static void ReadRequiredAttribute(JsonReader reader, TagMatchingRuleDescriptorBuilder builder) + { + if (!reader.Read()) + { + return; + } - string id = default; - int severity = default; - string message = default; - SourceSpan sourceSpan = default; + if (reader.TokenType != JsonToken.StartObject) + { + return; + } + builder.Attribute(attribute => + { reader.ReadProperties(propertyName => { switch (propertyName) { - case nameof(RazorDiagnostic.Id): + case nameof(RequiredAttributeDescriptor.Name): if (reader.Read()) { - id = (string)reader.Value; + var name = (string)reader.Value; + attribute.Name = name; } break; - case nameof(RazorDiagnostic.Severity): - severity = reader.ReadAsInt32().Value; + case nameof(RequiredAttributeDescriptor.NameComparison): + var nameComparison = (RequiredAttributeDescriptor.NameComparisonMode)reader.ReadAsInt32(); + attribute.NameComparisonMode = nameComparison; break; - case "Message": + case nameof(RequiredAttributeDescriptor.Value): if (reader.Read()) { - message = (string)reader.Value; + var value = (string)reader.Value; + attribute.Value = value; } break; - case nameof(RazorDiagnostic.Span): - sourceSpan = ReadSourceSpan(reader); + case nameof(RequiredAttributeDescriptor.ValueComparison): + var valueComparison = (RequiredAttributeDescriptor.ValueComparisonMode)reader.ReadAsInt32(); + attribute.ValueComparisonMode = valueComparison; + break; + case nameof(RequiredAttributeDescriptor.Diagnostics): + ReadDiagnostics(reader, attribute.Diagnostics); + break; + case nameof(RequiredAttributeDescriptor.Metadata): + ReadMetadata(reader, attribute.Metadata); break; } }); + }); + } - var descriptor = new RazorDiagnosticDescriptor(id, () => message, (RazorDiagnosticSeverity)severity); + private static void ReadAllowedChildTags(JsonReader reader, TagHelperDescriptorBuilder builder) + { + if (!reader.Read()) + { + return; + } - var diagnostic = RazorDiagnostic.Create(descriptor, sourceSpan); - diagnostics.Add(diagnostic); + if (reader.TokenType != JsonToken.StartArray) + { + return; } - private static SourceSpan ReadSourceSpan(JsonReader reader) + do { - if (!reader.Read()) - { - return SourceSpan.Undefined; - } + ReadAllowedChildTag(reader, builder); + } while (reader.TokenType != JsonToken.EndArray); + } - if (reader.TokenType != JsonToken.StartObject) - { - return SourceSpan.Undefined; - } + private static void ReadAllowedChildTag(JsonReader reader, TagHelperDescriptorBuilder builder) + { + if (!reader.Read()) + { + return; + } - string filePath = default; - int absoluteIndex = default; - int lineIndex = default; - int characterIndex = default; - int length = default; + if (reader.TokenType != JsonToken.StartObject) + { + return; + } + builder.AllowChildTag(childTag => + { reader.ReadProperties(propertyName => { switch (propertyName) { - case nameof(SourceSpan.FilePath): + case nameof(AllowedChildTagDescriptor.Name): if (reader.Read()) { - filePath = (string)reader.Value; + var name = (string)reader.Value; + childTag.Name = name; } break; - case nameof(SourceSpan.AbsoluteIndex): - absoluteIndex = reader.ReadAsInt32().Value; - break; - case nameof(SourceSpan.LineIndex): - lineIndex = reader.ReadAsInt32().Value; - break; - case nameof(SourceSpan.CharacterIndex): - characterIndex = reader.ReadAsInt32().Value; + case nameof(AllowedChildTagDescriptor.DisplayName): + if (reader.Read()) + { + var displayName = (string)reader.Value; + childTag.DisplayName = displayName; + } break; - case nameof(SourceSpan.Length): - length = reader.ReadAsInt32().Value; + case nameof(AllowedChildTagDescriptor.Diagnostics): + ReadDiagnostics(reader, childTag.Diagnostics); break; } }); + }); + } + + private static void ReadMetadata(JsonReader reader, IDictionary metadata) + { + if (!reader.Read()) + { + return; + } - var sourceSpan = new SourceSpan(filePath, absoluteIndex, lineIndex, characterIndex, length); - return sourceSpan; + if (reader.TokenType != JsonToken.StartObject) + { + return; + } + + reader.ReadProperties(propertyName => + { + if (reader.Read()) + { + var value = (string)reader.Value; + metadata[propertyName] = value; + } + }); + } + + private static void ReadDiagnostics(JsonReader reader, IList diagnostics) + { + if (!reader.Read()) + { + return; + } + + if (reader.TokenType != JsonToken.StartArray) + { + return; + } + + do + { + ReadDiagnostic(reader, diagnostics); + } + while (reader.TokenType != JsonToken.EndArray); + } + + private static void ReadDiagnostic(JsonReader reader, IList diagnostics) + { + if (!reader.Read()) + { + return; } + + if (reader.TokenType != JsonToken.StartObject) + { + return; + } + + string id = default; + int severity = default; + string message = default; + SourceSpan sourceSpan = default; + + reader.ReadProperties(propertyName => + { + switch (propertyName) + { + case nameof(RazorDiagnostic.Id): + if (reader.Read()) + { + id = (string)reader.Value; + } + break; + case nameof(RazorDiagnostic.Severity): + severity = reader.ReadAsInt32().Value; + break; + case "Message": + if (reader.Read()) + { + message = (string)reader.Value; + } + break; + case nameof(RazorDiagnostic.Span): + sourceSpan = ReadSourceSpan(reader); + break; + } + }); + + var descriptor = new RazorDiagnosticDescriptor(id, message, (RazorDiagnosticSeverity)severity); + + var diagnostic = RazorDiagnostic.Create(descriptor, sourceSpan); + diagnostics.Add(diagnostic); + } + + private static SourceSpan ReadSourceSpan(JsonReader reader) + { + if (!reader.Read()) + { + return SourceSpan.Undefined; + } + + if (reader.TokenType != JsonToken.StartObject) + { + return SourceSpan.Undefined; + } + + string filePath = default; + int absoluteIndex = default; + int lineIndex = default; + int characterIndex = default; + int length = default; + + reader.ReadProperties(propertyName => + { + switch (propertyName) + { + case nameof(SourceSpan.FilePath): + if (reader.Read()) + { + filePath = (string)reader.Value; + } + break; + case nameof(SourceSpan.AbsoluteIndex): + absoluteIndex = reader.ReadAsInt32().Value; + break; + case nameof(SourceSpan.LineIndex): + lineIndex = reader.ReadAsInt32().Value; + break; + case nameof(SourceSpan.CharacterIndex): + characterIndex = reader.ReadAsInt32().Value; + break; + case nameof(SourceSpan.Length): + length = reader.ReadAsInt32().Value; + break; + } + }); + + var sourceSpan = new SourceSpan(filePath, absoluteIndex, lineIndex, characterIndex, length); + return sourceSpan; } } diff --git a/src/Resolvers/Microsoft.DotNet.MSBuildSdkResolver/Microsoft.DotNet.MSBuildSdkResolver.csproj b/src/Resolvers/Microsoft.DotNet.MSBuildSdkResolver/Microsoft.DotNet.MSBuildSdkResolver.csproj index 3a4e5841b010..77a9cf79660b 100644 --- a/src/Resolvers/Microsoft.DotNet.MSBuildSdkResolver/Microsoft.DotNet.MSBuildSdkResolver.csproj +++ b/src/Resolvers/Microsoft.DotNet.MSBuildSdkResolver/Microsoft.DotNet.MSBuildSdkResolver.csproj @@ -1,6 +1,8 @@  + + 8.0.100.0 $(ResolverTargetFramework);net472 $(ResolverTargetFramework) AnyCPU diff --git a/src/Resolvers/Microsoft.DotNet.NativeWrapper/EnvironmentProvider.cs b/src/Resolvers/Microsoft.DotNet.NativeWrapper/EnvironmentProvider.cs index 9b70750cced6..757f738ec7a9 100644 --- a/src/Resolvers/Microsoft.DotNet.NativeWrapper/EnvironmentProvider.cs +++ b/src/Resolvers/Microsoft.DotNet.NativeWrapper/EnvironmentProvider.cs @@ -12,6 +12,8 @@ namespace Microsoft.DotNet.NativeWrapper { public class EnvironmentProvider { + private static readonly char[] s_invalidPathChars = Path.GetInvalidPathChars(); + private IEnumerable _searchPaths; private readonly Func _getEnvironmentVariable; @@ -31,17 +33,12 @@ private IEnumerable SearchPaths { get { - if (_searchPaths == null) - { - var searchPaths = new List(); - - searchPaths.AddRange( - _getEnvironmentVariable(Constants.PATH) - .Split(new char[] { Path.PathSeparator }, options: StringSplitOptions.RemoveEmptyEntries) - .Select(p => p.Trim('"'))); - - _searchPaths = searchPaths; - } + _searchPaths ??= + _getEnvironmentVariable(Constants.PATH) + .Split(new char[] { Path.PathSeparator }, options: StringSplitOptions.RemoveEmptyEntries) + .Select(p => p.Trim('"')) + .Where(p => p.IndexOfAny(s_invalidPathChars) == -1) + .ToList(); return _searchPaths; } @@ -51,7 +48,6 @@ public string GetCommandPath(string commandName) { var commandNameWithExtension = commandName + Constants.ExeSuffix; var commandPath = SearchPaths - .Where(p => !Path.GetInvalidPathChars().Any(c => p.Contains(c))) .Select(p => Path.Combine(p, commandNameWithExtension)) .FirstOrDefault(File.Exists); @@ -67,10 +63,15 @@ public string GetDotnetExeDirectory(Action log = null) return environmentOverride; } - string dotnetExe = _getCurrentProcessPath(); + string dotnetExe; +#if NETCOREAPP + // The dotnet executable is loading only the .NET version of this code so there is no point checking + // the current process path on .NET Framework. We are expected to find dotnet on PATH. + dotnetExe = _getCurrentProcessPath(); if (string.IsNullOrEmpty(dotnetExe) || !Path.GetFileNameWithoutExtension(dotnetExe) .Equals(Constants.DotNet, StringComparison.InvariantCultureIgnoreCase)) +#endif { string dotnetExeFromPath = GetCommandPath(Constants.DotNet); @@ -87,6 +88,13 @@ public string GetDotnetExeDirectory(Action log = null) } else { log?.Invoke($"GetDotnetExeDirectory: dotnet command path not found. Using current process"); log?.Invoke($"GetDotnetExeDirectory: Path variable: {_getEnvironmentVariable(Constants.PATH)}"); + +#if !NETCOREAPP + // If we failed to find dotnet on PATH, we revert to the old behavior of returning the current process + // path. This is really an error state but we're keeping the contract of always returning a non-empty + // path for backward compatibility. + dotnetExe = _getCurrentProcessPath(); +#endif } } diff --git a/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/WorkloadId.cs b/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/WorkloadId.cs index 948b8ec928ae..c6adcff13fbc 100644 --- a/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/WorkloadId.cs +++ b/src/Resolvers/Microsoft.NET.Sdk.WorkloadManifestReader/WorkloadId.cs @@ -4,7 +4,7 @@ namespace Microsoft.NET.Sdk.WorkloadManifestReader { /// - /// Wraps a workload definition id string to help ensure consistency of behaviour/semantics. + /// Wraps a workload definition id string to help ensure consistency of behavior/semantics. /// Comparisons are case insensitive but ToString() will return the original string for display purposes. /// public readonly struct WorkloadId : IComparable, IEquatable diff --git a/src/Tasks/Common/Resources/xlf/Strings.cs.xlf b/src/Tasks/Common/Resources/xlf/Strings.cs.xlf index b881ae6ebecf..b2b7abf5bc73 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.cs.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.cs.xlf @@ -572,14 +572,14 @@ The following are names of parameters or literal values and should not be transl NETSDK1210: IsAotCompatible and EnableAotAnalyzer are not supported for the target framework. Consider multi-targeting to a supported framework to enable ahead-of-time compilation analysis, and set IsAotCompatible only for the supported frameworks. For example: <IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</IsAotCompatible> - NETSDK1210: IsAotCompatible and EnableAotAnalyzer are not supported for the target framework. Consider multi-targeting to a supported framework to enable ahead-of-time compilation analysis, and set IsAotCompatible only for the supported frameworks. For example: + NETSDK1210: IsAotCompatible a EnableAotAnalyzer nejsou pro cílovou architekturu podporovány. Zvažte vícenásobné cílení na podporovanou architekturu, abyste umožnili analýzu kompilace pÅ™edem, a nastavte IsAotCompatible pouze pro podporované architektury. Například: <IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</IsAotCompatible> {StrBegin="NETSDK1210: "} NETSDK1212: IsTrimmable and EnableTrimAnalyzer are not supported for the target framework. Consider multi-targeting to a supported framework to enable trimming, and set IsTrimmable only for the supported frameworks. For example: <IsTrimmable Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</IsTrimmable> - NETSDK1212: IsTrimmable and EnableTrimAnalyzer are not supported for the target framework. Consider multi-targeting to a supported framework to enable trimming, and set IsTrimmable only for the supported frameworks. For example: + NETSDK1212: IsTrimmable a EnableTrimAnalyzer nejsou pro cílovou architekturu podporovány. Zvažte vícenásobné cílení na podporovanou architekturu, která umožňuje oříznutí, a nastavte IsTrimmable pouze pro podporované architektury. Například: <IsTrimmable Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</IsTrimmable> {StrBegin="NETSDK1212: "} diff --git a/src/Tasks/Common/Resources/xlf/Strings.es.xlf b/src/Tasks/Common/Resources/xlf/Strings.es.xlf index abb936f7669b..bfeab68a8dda 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.es.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.es.xlf @@ -402,7 +402,7 @@ NETSDK1211: EnableSingleFileAnalyzer is not supported for the target framework. Consider multi-targeting to a supported framework to enable single-file analysis, and set EnableSingleFileAnalyzer only for the supported frameworks. For example: <EnableSingleFileAnalyzer Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</EnableSingleFileAnalyzer> - NETSDK1211: EnableSingleFileAnalyzer is not supported for the target framework. Consider multi-targeting to a supported framework to enable single-file analysis, and set EnableSingleFileAnalyzer only for the supported frameworks. For example: + NETSDK1211: EnableSingleFileAnalyzer no se admite para la plataforma de destino. Considere la posibilidad de usar varios destinos en un marco compatible para habilitar el análisis de archivos únicos y establezca EnableSingleFileAnalyzer solo para los marcos admitidos. Por ejemplo: <EnableSingleFileAnalyzer Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</EnableSingleFileAnalyzer> {StrBegin="NETSDK1211: "} @@ -572,14 +572,14 @@ The following are names of parameters or literal values and should not be transl NETSDK1210: IsAotCompatible and EnableAotAnalyzer are not supported for the target framework. Consider multi-targeting to a supported framework to enable ahead-of-time compilation analysis, and set IsAotCompatible only for the supported frameworks. For example: <IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</IsAotCompatible> - NETSDK1210: IsAotCompatible and EnableAotAnalyzer are not supported for the target framework. Consider multi-targeting to a supported framework to enable ahead-of-time compilation analysis, and set IsAotCompatible only for the supported frameworks. For example: + NETSDK1210: IsAotCompatible y EnableAotAnalyzer no se admite para la plataforma de destino. Considere la posibilidad de usar varios destinos en un marco compatible para habilitar el análisis de compilación con antelación y establezca IsAotCompatible solo para los marcos admitidos. Por ejemplo: <IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</IsAotCompatible> {StrBegin="NETSDK1210: "} NETSDK1212: IsTrimmable and EnableTrimAnalyzer are not supported for the target framework. Consider multi-targeting to a supported framework to enable trimming, and set IsTrimmable only for the supported frameworks. For example: <IsTrimmable Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</IsTrimmable> - NETSDK1212: IsTrimmable and EnableTrimAnalyzer are not supported for the target framework. Consider multi-targeting to a supported framework to enable trimming, and set IsTrimmable only for the supported frameworks. For example: + NETSDK1212: IsTrimmable y EnableTrimAnalyzer no se admite para la plataforma de destino. Considere la posibilidad de usar varios destinos en un marco compatible para habilitar el recorte y establezca IsTrimmable solo para los marcos admitidos. Por ejemplo: <IsTrimmable Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</IsTrimmable> {StrBegin="NETSDK1212: "} diff --git a/src/Tasks/Common/Resources/xlf/Strings.it.xlf b/src/Tasks/Common/Resources/xlf/Strings.it.xlf index 37c8148e6367..86e1d2d8342f 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.it.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.it.xlf @@ -402,7 +402,7 @@ NETSDK1211: EnableSingleFileAnalyzer is not supported for the target framework. Consider multi-targeting to a supported framework to enable single-file analysis, and set EnableSingleFileAnalyzer only for the supported frameworks. For example: <EnableSingleFileAnalyzer Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</EnableSingleFileAnalyzer> - NETSDK1211: EnableSingleFileAnalyzer is not supported for the target framework. Consider multi-targeting to a supported framework to enable single-file analysis, and set EnableSingleFileAnalyzer only for the supported frameworks. For example: + NETSDK1211: EnableSingleFileAnalyzer non è supportato per il framework di destinazione. Prendere in considerazione la multitargeting per un framework supportato per abilitare l'analisi a file singolo e impostare EnableSingleFileAnalyzer solo per i framework supportati. Ad esempio: <EnableSingleFileAnalyzer Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</EnableSingleFileAnalyzer> {StrBegin="NETSDK1211: "} @@ -572,14 +572,14 @@ The following are names of parameters or literal values and should not be transl NETSDK1210: IsAotCompatible and EnableAotAnalyzer are not supported for the target framework. Consider multi-targeting to a supported framework to enable ahead-of-time compilation analysis, and set IsAotCompatible only for the supported frameworks. For example: <IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</IsAotCompatible> - NETSDK1210: IsAotCompatible and EnableAotAnalyzer are not supported for the target framework. Consider multi-targeting to a supported framework to enable ahead-of-time compilation analysis, and set IsAotCompatible only for the supported frameworks. For example: + NETSDK1210: IsAotCompatible e EnableAotAnalyzer non sono supportati per il framework di destinazione. Prendere in considerazione la multitargeting per un framework supportato per abilitare l'analisi della compilazione in anticipo e impostare IsAotCompatible solo per i framework supportati. Ad esempio: <IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</IsAotCompatible> {StrBegin="NETSDK1210: "} NETSDK1212: IsTrimmable and EnableTrimAnalyzer are not supported for the target framework. Consider multi-targeting to a supported framework to enable trimming, and set IsTrimmable only for the supported frameworks. For example: <IsTrimmable Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</IsTrimmable> - NETSDK1212: IsTrimmable and EnableTrimAnalyzer are not supported for the target framework. Consider multi-targeting to a supported framework to enable trimming, and set IsTrimmable only for the supported frameworks. For example: + NETSDK1212: IsTrimmable e EnableTrimAnalyzer non sono supportati per il framework di destinazione. Prendere in considerazione la multitargeting per un framework supportato per abilitare il taglio e impostare IsTrimmable solo per i framework supportati. Ad esempio: <IsTrimmable Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</IsTrimmable> {StrBegin="NETSDK1212: "} diff --git a/src/Tasks/Common/Resources/xlf/Strings.ja.xlf b/src/Tasks/Common/Resources/xlf/Strings.ja.xlf index a8155edaf937..58d017d5ca72 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.ja.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.ja.xlf @@ -572,14 +572,14 @@ The following are names of parameters or literal values and should not be transl NETSDK1210: IsAotCompatible and EnableAotAnalyzer are not supported for the target framework. Consider multi-targeting to a supported framework to enable ahead-of-time compilation analysis, and set IsAotCompatible only for the supported frameworks. For example: <IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</IsAotCompatible> - NETSDK1210: IsAotCompatible and EnableAotAnalyzer are not supported for the target framework. Consider multi-targeting to a supported framework to enable ahead-of-time compilation analysis, and set IsAotCompatible only for the supported frameworks. For example: + NETSDK1210: IsAotCompatible 㨠EnableAotAnalyzer ã¯ã‚¿ãƒ¼ã‚²ãƒƒãƒˆ フレームワークã§ã¯ã‚µãƒãƒ¼ãƒˆã•れã¦ã„ã¾ã›ã‚“。Ahead of Time コンパイル分æžã‚’有効ã«ã™ã‚‹ã«ã¯ã€ã‚µãƒãƒ¼ãƒˆã•れã¦ã„るフレームワークã«å¯¾ã™ã‚‹ãƒžãƒ«ãƒã‚¿ãƒ¼ã‚²ãƒƒãƒˆã‚’検討ã—ã€ã‚µãƒãƒ¼ãƒˆã•れã¦ã„るフレームワークã«é™ã‚Š IsAotCompatible を設定ã—ã¦ãã ã•ã„。例: <IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</IsAotCompatible> {StrBegin="NETSDK1210: "} NETSDK1212: IsTrimmable and EnableTrimAnalyzer are not supported for the target framework. Consider multi-targeting to a supported framework to enable trimming, and set IsTrimmable only for the supported frameworks. For example: <IsTrimmable Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</IsTrimmable> - NETSDK1212: IsTrimmable and EnableTrimAnalyzer are not supported for the target framework. Consider multi-targeting to a supported framework to enable trimming, and set IsTrimmable only for the supported frameworks. For example: + NETSDK1212: IsTrimmable 㨠EnableTrimAnalyzer ã¯ã‚¿ãƒ¼ã‚²ãƒƒãƒˆ フレームワークã§ã¯ã‚µãƒãƒ¼ãƒˆã•れã¦ã„ã¾ã›ã‚“。トリミングを有効ã«ã™ã‚‹ã«ã¯ã€ã‚µãƒãƒ¼ãƒˆã•れã¦ã„るフレームワークã«å¯¾ã™ã‚‹ãƒžãƒ«ãƒã‚¿ãƒ¼ã‚²ãƒƒãƒˆã‚’検討ã—ã€ã‚µãƒãƒ¼ãƒˆã•れã¦ã„るフレームワークã«é™ã‚Š IsTrimmable を設定ã—ã¦ãã ã•ã„。例: <IsTrimmable Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</IsTrimmable> {StrBegin="NETSDK1212: "} diff --git a/src/Tasks/Common/Resources/xlf/Strings.pl.xlf b/src/Tasks/Common/Resources/xlf/Strings.pl.xlf index f14df578d581..d24efca06c11 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.pl.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.pl.xlf @@ -402,8 +402,8 @@ NETSDK1211: EnableSingleFileAnalyzer is not supported for the target framework. Consider multi-targeting to a supported framework to enable single-file analysis, and set EnableSingleFileAnalyzer only for the supported frameworks. For example: <EnableSingleFileAnalyzer Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</EnableSingleFileAnalyzer> - NETSDK1211: EnableSingleFileAnalyzer is not supported for the target framework. Consider multi-targeting to a supported framework to enable single-file analysis, and set EnableSingleFileAnalyzer only for the supported frameworks. For example: -<EnableSingleFileAnalyzer Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</EnableSingleFileAnalyzer> + NETSDK1211: EnableSingleFileAnalyzer nie jest obsÅ‚ugiwany dla struktury docelowej. Rozważ zastosowanie wielu elementów docelowych do obsÅ‚ugiwanej struktury, aby umożliwić analizÄ™ pojedynczych plików, i ustaw parametr EnableSingleFileAnalyzer tylko dla obsÅ‚ugiwanych struktur. Na przykÅ‚ad: +<EnableSingleFileAnalyzer Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', „{0}â€))">wartość prawda</EnableSingleFileAnalyzer> {StrBegin="NETSDK1211: "} @@ -572,15 +572,15 @@ The following are names of parameters or literal values and should not be transl NETSDK1210: IsAotCompatible and EnableAotAnalyzer are not supported for the target framework. Consider multi-targeting to a supported framework to enable ahead-of-time compilation analysis, and set IsAotCompatible only for the supported frameworks. For example: <IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</IsAotCompatible> - NETSDK1210: IsAotCompatible and EnableAotAnalyzer are not supported for the target framework. Consider multi-targeting to a supported framework to enable ahead-of-time compilation analysis, and set IsAotCompatible only for the supported frameworks. For example: -<IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</IsAotCompatible> + NETSDK1210: Elementy IsAotCompatible i EnableAotAnalyzer nie sÄ… obsÅ‚ugiwane dla struktury docelowej. Rozważ zastosowanie wielu elementów docelowych do obsÅ‚ugiwanej struktury, aby umożliwić analizÄ™ kompilacji z wyprzedzeniem, i ustaw wartość IsAotCompatible tylko dla obsÅ‚ugiwanych struktur. Na przykÅ‚ad: +<IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)„, â€{0}'))">wartość prawda</IsAotCompatible> {StrBegin="NETSDK1210: "} NETSDK1212: IsTrimmable and EnableTrimAnalyzer are not supported for the target framework. Consider multi-targeting to a supported framework to enable trimming, and set IsTrimmable only for the supported frameworks. For example: <IsTrimmable Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</IsTrimmable> - NETSDK1212: IsTrimmable and EnableTrimAnalyzer are not supported for the target framework. Consider multi-targeting to a supported framework to enable trimming, and set IsTrimmable only for the supported frameworks. For example: -<IsTrimmable Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</IsTrimmable> + NETSDK1212: Elementy IsTrimmable i EnableTrimAnalyzer nie sÄ… obsÅ‚ugiwane dla struktury docelowej. Rozważ zastosowanie wielu elementów docelowych do obsÅ‚ugiwanej struktury, aby włączyć przycinanie, i ustaw wÅ‚aÅ›ciwość IsTrimmable tylko dla obsÅ‚ugiwanych struktur. Na przykÅ‚ad: +<IsTrimmable Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', „{0}â€))">wartość prawda</IsTrimmable> {StrBegin="NETSDK1212: "} diff --git a/src/Tasks/Common/Resources/xlf/Strings.pt-BR.xlf b/src/Tasks/Common/Resources/xlf/Strings.pt-BR.xlf index 39eb2af70807..9f0cd6842b73 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.pt-BR.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.pt-BR.xlf @@ -402,8 +402,8 @@ NETSDK1211: EnableSingleFileAnalyzer is not supported for the target framework. Consider multi-targeting to a supported framework to enable single-file analysis, and set EnableSingleFileAnalyzer only for the supported frameworks. For example: <EnableSingleFileAnalyzer Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</EnableSingleFileAnalyzer> - NETSDK1211: EnableSingleFileAnalyzer is not supported for the target framework. Consider multi-targeting to a supported framework to enable single-file analysis, and set EnableSingleFileAnalyzer only for the supported frameworks. For example: -<EnableSingleFileAnalyzer Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</EnableSingleFileAnalyzer> + NETSDK1211: EnableSingleFileAnalyzer não é compatível com a estrutura de destino. Considere o direcionamento múltiplo para uma estrutura com suporte para habilitar a análise de arquivo único e defina EnableSingleFileAnalyzer somente para as estruturas com suporte. Por exemplo: +<EnableSingleFileAnalyzer Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">verdadeiro</EnableSingleFileAnalyzer> {StrBegin="NETSDK1211: "} @@ -572,15 +572,15 @@ The following are names of parameters or literal values and should not be transl NETSDK1210: IsAotCompatible and EnableAotAnalyzer are not supported for the target framework. Consider multi-targeting to a supported framework to enable ahead-of-time compilation analysis, and set IsAotCompatible only for the supported frameworks. For example: <IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</IsAotCompatible> - NETSDK1210: IsAotCompatible and EnableAotAnalyzer are not supported for the target framework. Consider multi-targeting to a supported framework to enable ahead-of-time compilation analysis, and set IsAotCompatible only for the supported frameworks. For example: -<IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</IsAotCompatible> + NETSDK1210: IsAotCompatible e EnableAotAnalyzer não são compatíveis com a estrutura de destino. Considere o direcionamento múltiplo para uma estrutura com suporte para permitir a análise de compilação antecipada e defina IsAotCompatible apenas para as estruturas com suporte. Por exemplo: +<IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">verdadeiro</IsAotCompatible> {StrBegin="NETSDK1210: "} NETSDK1212: IsTrimmable and EnableTrimAnalyzer are not supported for the target framework. Consider multi-targeting to a supported framework to enable trimming, and set IsTrimmable only for the supported frameworks. For example: <IsTrimmable Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</IsTrimmable> - NETSDK1212: IsTrimmable and EnableTrimAnalyzer are not supported for the target framework. Consider multi-targeting to a supported framework to enable trimming, and set IsTrimmable only for the supported frameworks. For example: -<IsTrimmable Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</IsTrimmable> + NETSDK1212: IsTrimmable e EnableTrimAnalyzer não são compatíveis com a estrutura de destino. Considere o direcionamento múltiplo para uma estrutura com suporte para habilitar o corte e defina IsTrimmable somente para as estruturas com suporte. Por exemplo: +<IsTrimmable Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">verdadeiro</IsTrimmable> {StrBegin="NETSDK1212: "} diff --git a/src/Tasks/Common/Resources/xlf/Strings.ru.xlf b/src/Tasks/Common/Resources/xlf/Strings.ru.xlf index eb934bfde849..561e733e1634 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.ru.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.ru.xlf @@ -402,7 +402,7 @@ NETSDK1211: EnableSingleFileAnalyzer is not supported for the target framework. Consider multi-targeting to a supported framework to enable single-file analysis, and set EnableSingleFileAnalyzer only for the supported frameworks. For example: <EnableSingleFileAnalyzer Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</EnableSingleFileAnalyzer> - NETSDK1211: EnableSingleFileAnalyzer is not supported for the target framework. Consider multi-targeting to a supported framework to enable single-file analysis, and set EnableSingleFileAnalyzer only for the supported frameworks. For example: + NETSDK1211: EnableSingleFileAnalyzer не поддерживаетÑÑ Ñ†ÐµÐ»ÐµÐ²Ð¾Ð¹ платформой. РаÑÑмотрите возможноÑть иÑпользовать неÑколько целевых верÑий Ð´Ð»Ñ Ð¿Ð¾Ð´Ð´ÐµÑ€Ð¶Ð¸Ð²Ð°ÐµÐ¼Ð¾Ð¹ платформы, чтобы включить анализ одного файла, и наÑтройте EnableSingleFileAnalyzer только Ð´Ð»Ñ Ð¿Ð¾Ð´Ð´ÐµÑ€Ð¶Ð¸Ð²Ð°ÐµÐ¼Ñ‹Ñ… платформ. Ðапример: <EnableSingleFileAnalyzer Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</EnableSingleFileAnalyzer> {StrBegin="NETSDK1211: "} @@ -572,14 +572,14 @@ The following are names of parameters or literal values and should not be transl NETSDK1210: IsAotCompatible and EnableAotAnalyzer are not supported for the target framework. Consider multi-targeting to a supported framework to enable ahead-of-time compilation analysis, and set IsAotCompatible only for the supported frameworks. For example: <IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</IsAotCompatible> - NETSDK1210: IsAotCompatible and EnableAotAnalyzer are not supported for the target framework. Consider multi-targeting to a supported framework to enable ahead-of-time compilation analysis, and set IsAotCompatible only for the supported frameworks. For example: + NETSDK1210: IsAotCompatible и EnableAotAnalyzer не поддерживаютÑÑ Ð´Ð»Ñ Ñ†ÐµÐ»ÐµÐ²Ð¾Ð¹ платформы. РаÑÑмотрите возможноÑть иÑпользовать неÑколько целевых верÑий Ð´Ð»Ñ Ð¿Ð¾Ð´Ð´ÐµÑ€Ð¶Ð¸Ð²Ð°ÐµÐ¼Ð¾Ð¹ платформы, чтобы включить анализ компилÑции AOT, и задайте параметр IsAotCompatible только Ð´Ð»Ñ Ð¿Ð¾Ð´Ð´ÐµÑ€Ð¶Ð¸Ð²Ð°ÐµÐ¼Ñ‹Ñ… платформ. Пример: <IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</IsAotCompatible> {StrBegin="NETSDK1210: "} NETSDK1212: IsTrimmable and EnableTrimAnalyzer are not supported for the target framework. Consider multi-targeting to a supported framework to enable trimming, and set IsTrimmable only for the supported frameworks. For example: <IsTrimmable Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</IsTrimmable> - NETSDK1212: IsTrimmable and EnableTrimAnalyzer are not supported for the target framework. Consider multi-targeting to a supported framework to enable trimming, and set IsTrimmable only for the supported frameworks. For example: + NETSDK1212: IsTrimmable и EnableTrimAnalyzer не поддерживаютÑÑ Ð´Ð»Ñ Ñ†ÐµÐ»ÐµÐ²Ð¾Ð¹ платформы. РаÑÑмотрите возможноÑть иÑпользовать неÑколько целевых верÑий Ð´Ð»Ñ Ð¿Ð¾Ð´Ð´ÐµÑ€Ð¶Ð¸Ð²Ð°ÐµÐ¼Ð¾Ð¹ платформы, чтобы включить обрезку, и задайте параметр IsTrimmable только Ð´Ð»Ñ Ð¿Ð¾Ð´Ð´ÐµÑ€Ð¶Ð¸Ð²Ð°ÐµÐ¼Ñ‹Ñ… платформ. Пример: <IsTrimmable Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</IsTrimmable> {StrBegin="NETSDK1212: "} diff --git a/src/Tasks/Common/Resources/xlf/Strings.tr.xlf b/src/Tasks/Common/Resources/xlf/Strings.tr.xlf index dd845ad008bf..08d35b0d704b 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.tr.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.tr.xlf @@ -402,7 +402,7 @@ NETSDK1211: EnableSingleFileAnalyzer is not supported for the target framework. Consider multi-targeting to a supported framework to enable single-file analysis, and set EnableSingleFileAnalyzer only for the supported frameworks. For example: <EnableSingleFileAnalyzer Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</EnableSingleFileAnalyzer> - NETSDK1211: EnableSingleFileAnalyzer is not supported for the target framework. Consider multi-targeting to a supported framework to enable single-file analysis, and set EnableSingleFileAnalyzer only for the supported frameworks. For example: + NETSDK1211: EnableSingleFileAnalyzer hedef altyapı için desteklenmiyor. Tek dosya analizini etkinleÅŸtirmek için desteklenen bir altyapıya çoklu hedeflemeyi kullanabilir ve EnableSingleFileAnalyzer ayarını yalnızca desteklenen altyapılar için yapabilirsiniz. ÖrneÄŸin: <EnableSingleFileAnalyzer Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</EnableSingleFileAnalyzer> {StrBegin="NETSDK1211: "} @@ -572,14 +572,14 @@ The following are names of parameters or literal values and should not be transl NETSDK1210: IsAotCompatible and EnableAotAnalyzer are not supported for the target framework. Consider multi-targeting to a supported framework to enable ahead-of-time compilation analysis, and set IsAotCompatible only for the supported frameworks. For example: <IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</IsAotCompatible> - NETSDK1210: IsAotCompatible and EnableAotAnalyzer are not supported for the target framework. Consider multi-targeting to a supported framework to enable ahead-of-time compilation analysis, and set IsAotCompatible only for the supported frameworks. For example: + NETSDK1210: IsAotCompatible ve EnableAotAnalyzer hedef altyapı için desteklenmiyor. Önceden derleme analizini etkinleÅŸtirmek için desteklenen bir altyapıya çoklu hedefleme uygulayabilir ve IsAotCompatible ayarını yalnızca desteklenen altyapılar için yapabilirsiniz. ÖrneÄŸin: <IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</IsAotCompatible> {StrBegin="NETSDK1210: "} NETSDK1212: IsTrimmable and EnableTrimAnalyzer are not supported for the target framework. Consider multi-targeting to a supported framework to enable trimming, and set IsTrimmable only for the supported frameworks. For example: <IsTrimmable Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</IsTrimmable> - NETSDK1212: IsTrimmable and EnableTrimAnalyzer are not supported for the target framework. Consider multi-targeting to a supported framework to enable trimming, and set IsTrimmable only for the supported frameworks. For example: + NETSDK1212: IsTrimmable ve EnableTrimAnalyzer hedef altyapı için desteklenmiyor. Kırpmayı etkinleÅŸtirmek için desteklenen bir altyapıya çoklu hedefleme uygulayabilir ve IsTrimmable ayarını yalnızca desteklenen altyapılar için yapabilirsiniz. ÖrneÄŸin: <IsTrimmable Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</IsTrimmable> {StrBegin="NETSDK1212: "} diff --git a/src/Tasks/Common/Resources/xlf/Strings.zh-Hans.xlf b/src/Tasks/Common/Resources/xlf/Strings.zh-Hans.xlf index 1abcb43d4d43..5dd16b79df86 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.zh-Hans.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.zh-Hans.xlf @@ -402,7 +402,7 @@ NETSDK1211: EnableSingleFileAnalyzer is not supported for the target framework. Consider multi-targeting to a supported framework to enable single-file analysis, and set EnableSingleFileAnalyzer only for the supported frameworks. For example: <EnableSingleFileAnalyzer Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</EnableSingleFileAnalyzer> - NETSDK1211: EnableSingleFileAnalyzer is not supported for the target framework. Consider multi-targeting to a supported framework to enable single-file analysis, and set EnableSingleFileAnalyzer only for the supported frameworks. For example: + NETSDK1211: ç›®æ ‡æ¡†æž¶ä¸æ”¯æŒ EnableSingleFileAnalyzerã€‚è¯·è€ƒè™‘å¯¹å—æ”¯æŒçš„æ¡†æž¶è¿›è¡Œå¤šç›®æ ‡è®¾å®šæ¥å¯ç”¨å•文件分æžï¼Œå¹¶ä¸”ä»…ä¸ºå—æ”¯æŒçš„æ¡†æž¶è®¾ç½® EnableSingleFileAnalyzer。例如: <EnableSingleFileAnalyzer Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</EnableSingleFileAnalyzer> {StrBegin="NETSDK1211: "} @@ -572,14 +572,14 @@ The following are names of parameters or literal values and should not be transl NETSDK1210: IsAotCompatible and EnableAotAnalyzer are not supported for the target framework. Consider multi-targeting to a supported framework to enable ahead-of-time compilation analysis, and set IsAotCompatible only for the supported frameworks. For example: <IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</IsAotCompatible> - NETSDK1210: IsAotCompatible and EnableAotAnalyzer are not supported for the target framework. Consider multi-targeting to a supported framework to enable ahead-of-time compilation analysis, and set IsAotCompatible only for the supported frameworks. For example: + NETSDK1210: ç›®æ ‡æ¡†æž¶ä¸æ”¯æŒ IsAotCompatible å’Œ EnableAotAnalyzerã€‚è¯·è€ƒè™‘å¯¹å—æ”¯æŒçš„æ¡†æž¶è¿›è¡Œå¤šç›®æ ‡è®¾å®šæ¥å¯ç”¨æå‰ç¼–译分æžï¼Œå¹¶ä¸”ä»…ä¸ºå—æ”¯æŒçš„æ¡†æž¶è®¾ç½® IsAotCompatible。例如: <IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</IsAotCompatible> {StrBegin="NETSDK1210: "} NETSDK1212: IsTrimmable and EnableTrimAnalyzer are not supported for the target framework. Consider multi-targeting to a supported framework to enable trimming, and set IsTrimmable only for the supported frameworks. For example: <IsTrimmable Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</IsTrimmable> - NETSDK1212: IsTrimmable and EnableTrimAnalyzer are not supported for the target framework. Consider multi-targeting to a supported framework to enable trimming, and set IsTrimmable only for the supported frameworks. For example: + NETSDK1212: ç›®æ ‡æ¡†æž¶ä¸æ”¯æŒ IsTrimmable å’Œ EnableTrimAnalyzerã€‚è¯·è€ƒè™‘å¯¹å—æ”¯æŒçš„æ¡†æž¶è¿›è¡Œå¤šç›®æ ‡è®¾å®šä»¥å¯ç”¨å‰ªè£ï¼Œå¹¶ä¸”ä»…ä¸ºå—æ”¯æŒçš„æ¡†æž¶è®¾ç½® IsTrimmable。例如: <IsTrimmable Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</IsTrimmable> {StrBegin="NETSDK1212: "} diff --git a/src/Tasks/Common/Resources/xlf/Strings.zh-Hant.xlf b/src/Tasks/Common/Resources/xlf/Strings.zh-Hant.xlf index 697f7689090e..72dfb5bfac80 100644 --- a/src/Tasks/Common/Resources/xlf/Strings.zh-Hant.xlf +++ b/src/Tasks/Common/Resources/xlf/Strings.zh-Hant.xlf @@ -402,7 +402,7 @@ NETSDK1211: EnableSingleFileAnalyzer is not supported for the target framework. Consider multi-targeting to a supported framework to enable single-file analysis, and set EnableSingleFileAnalyzer only for the supported frameworks. For example: <EnableSingleFileAnalyzer Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</EnableSingleFileAnalyzer> - NETSDK1211: EnableSingleFileAnalyzer is not supported for the target framework. Consider multi-targeting to a supported framework to enable single-file analysis, and set EnableSingleFileAnalyzer only for the supported frameworks. For example: + NETSDK1211: ç›®æ¨™æž¶æ§‹ä¸æ”¯æ´ EnableSingleFileAnalyzerã€‚è€ƒæ…®å°æ”¯æ´çš„æž¶æ§‹è¨­å®šå¤šé‡ç›®æ¨™ï¼Œä»¥å•Ÿç”¨å–®ä¸€æª”案分æžï¼Œä¸¦åƒ…é‡å°æ”¯æ´çš„æž¶æ§‹è¨­å®š EnableSingleFileAnalyzer。例如: <EnableSingleFileAnalyzer Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</EnableSingleFileAnalyzer> {StrBegin="NETSDK1211: "} @@ -572,14 +572,14 @@ The following are names of parameters or literal values and should not be transl NETSDK1210: IsAotCompatible and EnableAotAnalyzer are not supported for the target framework. Consider multi-targeting to a supported framework to enable ahead-of-time compilation analysis, and set IsAotCompatible only for the supported frameworks. For example: <IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</IsAotCompatible> - NETSDK1210: IsAotCompatible and EnableAotAnalyzer are not supported for the target framework. Consider multi-targeting to a supported framework to enable ahead-of-time compilation analysis, and set IsAotCompatible only for the supported frameworks. For example: + NETSDK1210: ç›®æ¨™æž¶æ§‹ä¸æ”¯æ´ IsAotCompatible å’Œ EnableAotAnalyzerã€‚è€ƒæ…®å°æ”¯æ´çš„æž¶æ§‹è¨­å®šå¤šé‡ç›®æ¨™ï¼Œä»¥å•Ÿç”¨æå‰ç·¨è­¯åˆ†æžï¼Œä¸¦åƒ…é‡å°æ”¯æ´çš„æž¶æ§‹è¨­å®š IsAotCompatible。例如: <IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</IsAotCompatible> {StrBegin="NETSDK1210: "} NETSDK1212: IsTrimmable and EnableTrimAnalyzer are not supported for the target framework. Consider multi-targeting to a supported framework to enable trimming, and set IsTrimmable only for the supported frameworks. For example: <IsTrimmable Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</IsTrimmable> - NETSDK1212: IsTrimmable and EnableTrimAnalyzer are not supported for the target framework. Consider multi-targeting to a supported framework to enable trimming, and set IsTrimmable only for the supported frameworks. For example: + NETSDK1212: ç›®æ¨™æž¶æ§‹ä¸æ”¯æ´ IsTrimmable å’Œ EnableTrimAnalyzerã€‚è€ƒæ…®å°æ”¯æ´çš„æž¶æ§‹è¨­å®šå¤šé‡ç›®æ¨™ï¼Œä»¥å•Ÿç”¨ä¿®å‰ªï¼Œä¸¦åƒ…é‡å°æ”¯æ´çš„æž¶æ§‹è¨­å®š IsTrimmable。例如: <IsTrimmable Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '{0}'))">true</IsTrimmable> {StrBegin="NETSDK1212: "} diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/sdk/Sdk.props b/src/Tasks/Microsoft.NET.Build.Tasks/sdk/Sdk.props index 404a3d71f4d2..00c466b9e31d 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/sdk/Sdk.props +++ b/src/Tasks/Microsoft.NET.Build.Tasks/sdk/Sdk.props @@ -11,6 +11,13 @@ Copyright (c) .NET Foundation. All rights reserved. --> + + <_AfterSdkPublishDependsOn Condition="'$(_IsAspNetCoreProject)' == 'true'">AfterPublish + <_AfterSdkPublishDependsOn Condition="'$(_IsAspNetCoreProject)' != 'true'">Publish + + + + true - - - - - - - true - - - <_DirectoryBuildPropsFile Condition="'$(_DirectoryBuildPropsFile)' == ''">Directory.Build.props - <_DirectoryBuildPropsBasePath Condition="'$(_DirectoryBuildPropsBasePath)' == ''">$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), '$(_DirectoryBuildPropsFile)')) - $([System.IO.Path]::Combine('$(_DirectoryBuildPropsBasePath)', '$(_DirectoryBuildPropsFile)')) - - - - - - false - - - - - - - true - $(MSBuildProjectName) - - - - $(ArtifactsPath)\obj\$(ArtifactsProjectName)\ - $(ArtifactsPath)\obj\ - - - - <_ArtifactsPathSetEarly>true + $(CustomAfterDirectoryBuildProps);$(MSBuildThisFileDirectory)UseArtifactsOutputPath.props diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/sdk/Sdk.targets b/src/Tasks/Microsoft.NET.Build.Tasks/sdk/Sdk.targets index 136d5d2cb673..a506081a2017 100644 --- a/src/Tasks/Microsoft.NET.Build.Tasks/sdk/Sdk.targets +++ b/src/Tasks/Microsoft.NET.Build.Tasks/sdk/Sdk.targets @@ -52,4 +52,15 @@ Copyright (c) .NET Foundation. All rights reserved. + + + + <_ContainersTargetsDir Condition=" '$(_ContainersTargetsDir)'=='' ">$(MSBuildThisFileDirectory)..\..\..\Containers\build\ + + + + + diff --git a/src/Tasks/Microsoft.NET.Build.Tasks/sdk/UseArtifactsOutputPath.props b/src/Tasks/Microsoft.NET.Build.Tasks/sdk/UseArtifactsOutputPath.props new file mode 100644 index 000000000000..1dd11c8d49ad --- /dev/null +++ b/src/Tasks/Microsoft.NET.Build.Tasks/sdk/UseArtifactsOutputPath.props @@ -0,0 +1,35 @@ + + + + + + + true + $(MSBuildProjectName) + + + + $(ArtifactsPath)\obj\$(ArtifactsProjectName)\ + $(ArtifactsPath)\obj\ + + + + + <_ArtifactsPathSetEarly>true + + diff --git a/src/Tests/Microsoft.DotNet.PackageInstall.Tests/ToolPackageInstallerNugetCacheTests.cs b/src/Tests/Microsoft.DotNet.PackageInstall.Tests/ToolPackageInstallerNugetCacheTests.cs index 3aedbf9a3a98..263b8d3bba6e 100644 --- a/src/Tests/Microsoft.DotNet.PackageInstall.Tests/ToolPackageInstallerNugetCacheTests.cs +++ b/src/Tests/Microsoft.DotNet.PackageInstall.Tests/ToolPackageInstallerNugetCacheTests.cs @@ -18,7 +18,7 @@ public ToolPackageInstallToManagedLocationInstaller(ITestOutputHelper log) : bas { } - [Theory] + [WindowsOnlyTheory] [InlineData(false)] [InlineData(true)] public void GivenNugetConfigInstallSucceeds(bool testMockBehaviorIsInSync) @@ -61,7 +61,7 @@ public void GivenNugetConfigInstallSucceeds(bool testMockBehaviorIsInSync) } } - [Theory] + [WindowsOnlyTheory] [InlineData(false)] [InlineData(true)] public void GivenNugetConfigVersionRangeInstallSucceeds(bool testMockBehaviorIsInSync) diff --git a/src/Tests/Microsoft.DotNet.PackageInstall.Tests/ToolPackageUninstallerTests.cs b/src/Tests/Microsoft.DotNet.PackageInstall.Tests/ToolPackageUninstallerTests.cs index 4e5750f81d61..21c0f99477e8 100644 --- a/src/Tests/Microsoft.DotNet.PackageInstall.Tests/ToolPackageUninstallerTests.cs +++ b/src/Tests/Microsoft.DotNet.PackageInstall.Tests/ToolPackageUninstallerTests.cs @@ -15,7 +15,7 @@ namespace Microsoft.DotNet.PackageInstall.Tests { public class ToolPackageUninstallerTests : SdkTest { - [Theory] + [WindowsOnlyTheory] [InlineData(false)] [InlineData(true)] public void GivenAnInstalledPackageUninstallRemovesThePackage(bool testMockBehaviorIsInSync) diff --git a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/CreateNewImageTests.cs b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/CreateNewImageTests.cs index 0d3ee20642d5..e9fe7898dff5 100644 --- a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/CreateNewImageTests.cs +++ b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/CreateNewImageTests.cs @@ -152,7 +152,7 @@ public void Tasks_EndToEnd_With_EnvironmentVariable_Validation() File.WriteAllText(Path.Combine(newProjectDir.FullName, "Program.cs"), $"Console.Write(Environment.GetEnvironmentVariable(\"GoodEnvVar\"));"); - new DotnetCommand(_testOutput, "build", "--configuration", "release", "/p:runtimeidentifier=linux-x64", $"/p:RuntimeFrameworkVersion=8.0.0-preview.3.23174.8") + new DotnetCommand(_testOutput, "build", "--configuration", "release", "/p:runtimeidentifier=linux-x64") .WithWorkingDirectory(newProjectDir.FullName) .Execute() .Should().Pass(); @@ -161,7 +161,7 @@ public void Tasks_EndToEnd_With_EnvironmentVariable_Validation() (IBuildEngine buildEngine, List errors) = SetupBuildEngine(); pcp.BuildEngine = buildEngine; - pcp.FullyQualifiedBaseImageName = $"mcr.microsoft.com/{DockerRegistryManager.RuntimeBaseImage}:{DockerRegistryManager.Net8PreviewImageTag}"; + pcp.FullyQualifiedBaseImageName = $"mcr.microsoft.com/{DockerRegistryManager.RuntimeBaseImage}:{DockerRegistryManager.Net8ImageTag}"; pcp.ContainerRegistry = ""; pcp.ContainerRepository = "dotnet/envvarvalidation"; pcp.ContainerImageTag = "latest"; @@ -174,7 +174,7 @@ public void Tasks_EndToEnd_With_EnvironmentVariable_Validation() Assert.True(pcp.Execute(), FormatBuildMessages(errors)); Assert.Equal("mcr.microsoft.com", pcp.ParsedContainerRegistry); Assert.Equal("dotnet/runtime", pcp.ParsedContainerImage); - Assert.Equal(DockerRegistryManager.Net8PreviewImageTag, pcp.ParsedContainerTag); + Assert.Equal(DockerRegistryManager.Net8ImageTag, pcp.ParsedContainerTag); Assert.Single(pcp.NewContainerEnvironmentVariables); Assert.Equal("Foo", pcp.NewContainerEnvironmentVariables[0].GetMetadata("Value")); @@ -229,9 +229,9 @@ public async System.Threading.Tasks.Task CreateNewImage_RootlessBaseImage() ImageBuilder imageBuilder = await registry.GetImageManifestAsync( DockerRegistryManager.RuntimeBaseImage, - DockerRegistryManager.Net8PreviewImageTag, + DockerRegistryManager.Net8ImageTag, "linux-x64", - ToolsetUtils.GetRuntimeGraphFilePath(), + ToolsetUtils.RidGraphManifestPicker, cancellationToken: default).ConfigureAwait(false); Assert.NotNull(imageBuilder); @@ -239,7 +239,7 @@ public async System.Threading.Tasks.Task CreateNewImage_RootlessBaseImage() BuiltImage builtImage = imageBuilder.Build(); - var sourceReference = new SourceImageReference(registry, DockerRegistryManager.RuntimeBaseImage, DockerRegistryManager.Net8PreviewImageTag); + var sourceReference = new SourceImageReference(registry, DockerRegistryManager.RuntimeBaseImage, DockerRegistryManager.Net8ImageTag); var destinationReference = new DestinationImageReference(registry, RootlessBase, new[] { "latest" }); await registry.PushAsync(builtImage, sourceReference, destinationReference, cancellationToken: default).ConfigureAwait(false); @@ -289,7 +289,7 @@ public async System.Threading.Tasks.Task CreateNewImage_RootlessBaseImage() AppImage, "latest", "linux-x64", - ToolsetUtils.GetRuntimeGraphFilePath(), + ToolsetUtils.RidGraphManifestPicker, cancellationToken: default).ConfigureAwait(false); Assert.Equal(RootlessUser, imageBuilder.BaseImageConfig.GetUser()); diff --git a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/DockerRegistryManager.cs b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/DockerRegistryManager.cs index 53954b10427b..534a4a78056d 100644 --- a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/DockerRegistryManager.cs +++ b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/DockerRegistryManager.cs @@ -3,6 +3,7 @@ using Microsoft.DotNet.Cli.Utils; using Microsoft.Extensions.Logging; +using Xunit.Sdk; namespace Microsoft.NET.Build.Containers.IntegrationTests; @@ -13,14 +14,22 @@ public class DockerRegistryManager public const string BaseImageSource = "mcr.microsoft.com"; public const string Net6ImageTag = "6.0"; public const string Net7ImageTag = "7.0"; - public const string Net8PreviewImageTag = "8.0-preview"; - public const string Net8PreviewWindowsSpecificImageTag = $"{Net8PreviewImageTag}-nanoserver-ltsc2022"; + public const string Net8ImageTag = "8.0"; + public const string Net8PreviewWindowsSpecificImageTag = $"{Net8ImageTag}-nanoserver-ltsc2022"; public const string LocalRegistry = "localhost:5010"; - public const string FullyQualifiedBaseImageDefault = $"{BaseImageSource}/{RuntimeBaseImage}:{Net8PreviewImageTag}"; - public const string FullyQualifiedBaseImageAspNet = $"{BaseImageSource}/{AspNetBaseImage}:{Net8PreviewImageTag}"; + public const string FullyQualifiedBaseImageDefault = $"{BaseImageSource}/{RuntimeBaseImage}:{Net8ImageTag}"; + public const string FullyQualifiedBaseImageAspNet = $"{BaseImageSource}/{AspNetBaseImage}:{Net8ImageTag}"; private static string? s_registryContainerId; - public static void StartAndPopulateDockerRegistry(ITestOutputHelper testOutput) + internal class SameArchManifestPicker : IManifestPicker + { + public PlatformSpecificManifest? PickBestManifestForRid(IReadOnlyDictionary manifestList, string runtimeIdentifier) + { + return manifestList.Values.SingleOrDefault(m => m.platform.os == "linux" && m.platform.architecture == "amd64"); + } + } + + public static async Task StartAndPopulateDockerRegistry(ITestOutputHelper testOutput) { using TestLoggerFactory loggerFactory = new(testOutput); @@ -34,6 +43,9 @@ public static void StartAndPopulateDockerRegistry(ITestOutputHelper testOutput) int spawnRegistryDelay = 1000; //ms StringBuilder failureReasons = new(); + var pullRegistry = new Registry(BaseImageSource, logger); + var pushRegistry = new Registry(LocalRegistry, logger); + for (int spawnRegistryAttempt = 1; spawnRegistryAttempt <= spawnRegistryMaxRetry; spawnRegistryAttempt++) { try @@ -52,22 +64,18 @@ public static void StartAndPopulateDockerRegistry(ITestOutputHelper testOutput) EnsureRegistryLoaded(LocalRegistry, s_registryContainerId, logger, testOutput); - foreach (string? tag in new[] { Net6ImageTag, Net7ImageTag, Net8PreviewImageTag }) + foreach (string? tag in new[] { Net6ImageTag, Net7ImageTag, Net8ImageTag }) { logger.LogInformation("Pulling image '{repo}/{image}:{tag}'.", BaseImageSource, RuntimeBaseImage, tag); - ContainerCli.PullCommand(testOutput, $"{BaseImageSource}/{RuntimeBaseImage}:{tag}") - .Execute() - .Should().Pass(); - - logger.LogInformation("Tagging image '{sourceRepo}/{sourceImage}:{sourceTag}' as '{targetRepo}/{targetImage}:{targetTag}'.",BaseImageSource, RuntimeBaseImage, tag, LocalRegistry, RuntimeBaseImage, tag); - ContainerCli.TagCommand(testOutput, $"{BaseImageSource}/{RuntimeBaseImage}:{tag}", $"{LocalRegistry}/{RuntimeBaseImage}:{tag}") - .Execute() - .Should().Pass(); - - logger.LogInformation("Pushing image '{repo}/{image}:{tag}'.", LocalRegistry, RuntimeBaseImage, tag); - ContainerCli.PushCommand(testOutput, $"{LocalRegistry}/{RuntimeBaseImage}:{tag}") - .Execute() - .Should().Pass(); + string dotnetdll = System.Reflection.Assembly.GetExecutingAssembly().Location; + var ridjson = Path.Combine(Path.GetDirectoryName(dotnetdll)!, "RuntimeIdentifierGraph.json"); + + var image = await pullRegistry.GetImageManifestAsync(RuntimeBaseImage, tag, "linux-x64", new SameArchManifestPicker(), CancellationToken.None); + var source = new SourceImageReference(pullRegistry, RuntimeBaseImage, tag); + var dest = new DestinationImageReference(pushRegistry, RuntimeBaseImage, [tag]); + logger.LogInformation($"Pushing image for {BaseImageSource}/{RuntimeBaseImage}:{tag}"); + await pushRegistry.PushAsync(image.Build(), source, dest, CancellationToken.None); + logger.LogInformation($"Pushed image for {BaseImageSource}/{RuntimeBaseImage}:{tag}"); } return; } diff --git a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/DockerRegistryTests.cs b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/DockerRegistryTests.cs index 4d4f5df614ef..18638ee17b84 100644 --- a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/DockerRegistryTests.cs +++ b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/DockerRegistryTests.cs @@ -31,7 +31,7 @@ public async Task GetFromRegistry() DockerRegistryManager.RuntimeBaseImage, DockerRegistryManager.Net6ImageTag, "linux-x64", - ridgraphfile, + ToolsetUtils.RidGraphManifestPicker, cancellationToken: default).ConfigureAwait(false); Assert.NotNull(downloadedImage); @@ -83,7 +83,7 @@ public async Task WriteToPrivateBasicRegistry() DockerRegistryManager.RuntimeBaseImage, DockerRegistryManager.Net6ImageTag, "linux-x64", - ridgraphfile, + ToolsetUtils.RidGraphManifestPicker, cancellationToken: default).ConfigureAwait(false); var image = downloadedImage.Build(); await localAuthed.PushAsync(image, sourceImage, destinationImage, CancellationToken.None); diff --git a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/DockerTestsFixture.cs b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/DockerTestsFixture.cs index 847c22b7d956..c417e23c6184 100644 --- a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/DockerTestsFixture.cs +++ b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/DockerTestsFixture.cs @@ -12,7 +12,7 @@ public DockerTestsFixture(IMessageSink messageSink) _diagnosticOutput = new SharedTestOutputHelper(messageSink); try { - DockerRegistryManager.StartAndPopulateDockerRegistry(_diagnosticOutput); + DockerRegistryManager.StartAndPopulateDockerRegistry(_diagnosticOutput).GetAwaiter().GetResult(); } catch { diff --git a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs index c012fbd153f3..c0050a9ee053 100644 --- a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs +++ b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/EndToEndTests.cs @@ -50,9 +50,9 @@ public async Task ApiEndToEndWithRegistryPushAndPull() ImageBuilder imageBuilder = await registry.GetImageManifestAsync( DockerRegistryManager.RuntimeBaseImage, - DockerRegistryManager.Net8PreviewImageTag, + DockerRegistryManager.Net8ImageTag, "linux-x64", - ToolsetUtils.GetRuntimeGraphFilePath(), + ToolsetUtils.RidGraphManifestPicker, cancellationToken: default).ConfigureAwait(false); Assert.NotNull(imageBuilder); @@ -66,7 +66,7 @@ public async Task ApiEndToEndWithRegistryPushAndPull() BuiltImage builtImage = imageBuilder.Build(); // Push the image back to the local registry - var sourceReference = new SourceImageReference(registry, DockerRegistryManager.RuntimeBaseImage, DockerRegistryManager.Net8PreviewImageTag); + var sourceReference = new SourceImageReference(registry, DockerRegistryManager.RuntimeBaseImage, DockerRegistryManager.Net8ImageTag); var destinationReference = new DestinationImageReference(registry, NewImageName(), new[] { "latest", "1.0" }); await registry.PushAsync(builtImage, sourceReference, destinationReference, cancellationToken: default).ConfigureAwait(false); @@ -97,9 +97,9 @@ public async Task ApiEndToEndWithLocalLoad() ImageBuilder imageBuilder = await registry.GetImageManifestAsync( DockerRegistryManager.RuntimeBaseImage, - DockerRegistryManager.Net8PreviewImageTag, + DockerRegistryManager.Net8ImageTag, "linux-x64", - ToolsetUtils.GetRuntimeGraphFilePath(), + ToolsetUtils.RidGraphManifestPicker, cancellationToken: default).ConfigureAwait(false); Assert.NotNull(imageBuilder); @@ -138,9 +138,9 @@ public async Task ApiEndToEndWithArchiveWritingAndLoad() ImageBuilder imageBuilder = await registry.GetImageManifestAsync( DockerRegistryManager.RuntimeBaseImage, - DockerRegistryManager.Net8PreviewImageTag, + DockerRegistryManager.Net8ImageTag, "linux-x64", - ToolsetUtils.GetRuntimeGraphFilePath(), + ToolsetUtils.RidGraphManifestPicker, cancellationToken: default).ConfigureAwait(false); Assert.NotNull(imageBuilder); @@ -197,11 +197,6 @@ private string BuildLocalApp([CallerMemberName] string testName = "TestName", st new DotnetCommand(_testOutput, "publish", "-bl", "MinimalTestApp", "-r", rid, "-f", tfm, "-c", "Debug") .WithWorkingDirectory(workingDirectory); - if (tfm == ToolsetInfo.CurrentTargetFramework) - { - publishCommand.Arguments.AddRange(new[] { "-p", $"RuntimeFrameworkVersion=8.0.0-preview.3.23174.8" }); - } - publishCommand.Execute() .Should().Pass(); @@ -209,6 +204,87 @@ private string BuildLocalApp([CallerMemberName] string testName = "TestName", st return publishDirectory; } + [DockerAvailableFact] + public async Task EndToEnd_MultiProjectSolution() + { + ILogger logger = _loggerFactory.CreateLogger(nameof(EndToEnd_MultiProjectSolution)); + DirectoryInfo newSolutionDir = new(Path.Combine(TestSettings.TestArtifactsDirectory, $"CreateNewImageTest_EndToEnd_MultiProjectSolution")); + + if (newSolutionDir.Exists) + { + newSolutionDir.Delete(recursive: true); + } + + newSolutionDir.Create(); + + // Create solution with projects + new DotnetNewCommand(_testOutput, "sln", "-n", nameof(EndToEnd_MultiProjectSolution)) + .WithVirtualHive() + .WithWorkingDirectory(newSolutionDir.FullName) + .Execute() + .Should().Pass(); + + new DotnetNewCommand(_testOutput, "console", "-n", "ConsoleApp") + .WithVirtualHive() + .WithWorkingDirectory(newSolutionDir.FullName) + .Execute() + .Should().Pass(); + + new DotnetCommand(_testOutput, "sln", "add", Path.Combine("ConsoleApp", "ConsoleApp.csproj")) + .WithWorkingDirectory(newSolutionDir.FullName) + .Execute() + .Should().Pass(); + + new DotnetNewCommand(_testOutput, "web", "-n", "WebApp") + .WithVirtualHive() + .WithWorkingDirectory(newSolutionDir.FullName) + .Execute() + .Should().Pass(); + + new DotnetCommand(_testOutput, "sln", "add", Path.Combine("WebApp", "WebApp.csproj")) + .WithWorkingDirectory(newSolutionDir.FullName) + .Execute() + .Should().Pass(); + + // Add 'EnableSdkContainerSupport' property to the ConsoleApp and set TFM + using (FileStream stream = File.Open(Path.Join(newSolutionDir.FullName, "ConsoleApp", "ConsoleApp.csproj"), FileMode.Open, FileAccess.ReadWrite)) + { + XDocument document = await XDocument.LoadAsync(stream, LoadOptions.None, CancellationToken.None); + document + .Descendants() + .First(e => e.Name.LocalName == "PropertyGroup")? + .Add(new XElement("EnableSdkContainerSupport", "true")); + document + .Descendants() + .First(e => e.Name.LocalName == "TargetFramework") + .Value = ToolsetInfo.CurrentTargetFramework; + + stream.SetLength(0); + await document.SaveAsync(stream, SaveOptions.None, CancellationToken.None); + } + + // Set TFM for WebApp + using (FileStream stream = File.Open(Path.Join(newSolutionDir.FullName, "WebApp", "WebApp.csproj"), FileMode.Open, FileAccess.ReadWrite)) + { + XDocument document = await XDocument.LoadAsync(stream, LoadOptions.None, CancellationToken.None); + document + .Descendants() + .First(e => e.Name.LocalName == "TargetFramework") + .Value = ToolsetInfo.CurrentTargetFramework; + + stream.SetLength(0); + await document.SaveAsync(stream, SaveOptions.None, CancellationToken.None); + } + + // Publish + CommandResult commandResult = new DotnetCommand(_testOutput, "publish", "/t:PublishContainer") + .WithWorkingDirectory(newSolutionDir.FullName) + .Execute(); + + commandResult.Should().Pass(); + commandResult.Should().HaveStdOutContaining("Pushed image 'webapp:latest'"); + commandResult.Should().HaveStdOutContaining("Pushed image 'consoleapp:latest'"); + } [DockerAvailableTheory()] [InlineData("webapi", false)] @@ -284,7 +360,7 @@ public async Task EndToEnd_NoAPI_ProjectType(string projectType, bool addPackage $"/p:ContainerRegistry={DockerRegistryManager.LocalRegistry}", $"/p:ContainerRepository={imageName}", $"/p:ContainerImageTag={imageTag}", - $"/p:RuntimeFrameworkVersion=8.0.0-preview.3.23174.8") + "/p:UseRazorSourceGenerator=false") .WithEnvironmentVariable("NUGET_PACKAGES", privateNuGetAssets.FullName) .WithWorkingDirectory(newProjectDir.FullName) .Execute(); @@ -442,7 +518,6 @@ public void EndToEnd_NoAPI_Console() $"/p:ContainerBaseImage={DockerRegistryManager.FullyQualifiedBaseImageAspNet}", $"/p:ContainerRegistry={DockerRegistryManager.LocalRegistry}", $"/p:ContainerRepository={imageName}", - $"/p:RuntimeFrameworkVersion=8.0.0-preview.3.23174.8", $"/p:ContainerImageTag={imageTag}") .WithEnvironmentVariable("NUGET_PACKAGES", privateNuGetAssets.FullName) .WithWorkingDirectory(newProjectDir.FullName) @@ -483,9 +558,9 @@ public async Task CanPackageForAllSupportedContainerRIDs(string dockerPlatform, var isWin = rid.StartsWith("win"); ImageBuilder? imageBuilder = await registry.GetImageManifestAsync( DockerRegistryManager.RuntimeBaseImage, - isWin ? DockerRegistryManager.Net8PreviewWindowsSpecificImageTag : DockerRegistryManager.Net8PreviewImageTag, + isWin ? DockerRegistryManager.Net8PreviewWindowsSpecificImageTag : DockerRegistryManager.Net8ImageTag, rid, - ToolsetUtils.GetRuntimeGraphFilePath(), + ToolsetUtils.RidGraphManifestPicker, cancellationToken: default).ConfigureAwait(false); Assert.NotNull(imageBuilder); @@ -500,7 +575,7 @@ public async Task CanPackageForAllSupportedContainerRIDs(string dockerPlatform, BuiltImage builtImage = imageBuilder.Build(); // Load the image into the local registry - var sourceReference = new SourceImageReference(registry, DockerRegistryManager.RuntimeBaseImage, DockerRegistryManager.Net7ImageTag); + var sourceReference = new SourceImageReference(registry, DockerRegistryManager.RuntimeBaseImage, DockerRegistryManager.Net8ImageTag); var destinationReference = new DestinationImageReference(registry, NewImageName(), new[] { rid }); await new DockerCli(_loggerFactory).LoadAsync(builtImage, sourceReference, destinationReference, default).ConfigureAwait(false); diff --git a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/ParseContainerPropertiesTests.cs b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/ParseContainerPropertiesTests.cs index 0a3dbb179aa3..2259ba90ca14 100644 --- a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/ParseContainerPropertiesTests.cs +++ b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/ParseContainerPropertiesTests.cs @@ -8,13 +8,13 @@ namespace Microsoft.NET.Build.Containers.Tasks.IntegrationTests; -[Collection("Docker tests")] public class ParseContainerPropertiesTests { - [DockerAvailableFact] + [Fact] public void Baseline() { - var (project, logs, d) = ProjectInitializer.InitProject(new () { + var (project, logs, d) = ProjectInitializer.InitProject(new() + { [ContainerBaseImage] = "mcr.microsoft.com/dotnet/runtime:7.0", [ContainerRegistry] = "localhost:5010", [ContainerRepository] = "dotnet/testimage", @@ -22,7 +22,7 @@ public void Baseline() }); using var _ = d; var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None); - Assert.True(instance.Build(new[]{ComputeContainerConfig}, new [] { logs }, null, out var outputs)); + Assert.True(instance.Build(new[] { ComputeContainerConfig }, new[] { logs }, null, out var outputs)); Assert.Equal("mcr.microsoft.com", instance.GetPropertyValue(ContainerBaseRegistry)); Assert.Equal("dotnet/runtime", instance.GetPropertyValue(ContainerBaseName)); @@ -33,26 +33,28 @@ public void Baseline() instance.GetItems("ProjectCapability").Select(i => i.EvaluatedInclude).ToArray().Should().BeEquivalentTo(new[] { "NetSdkOCIImageBuild" }); } - [DockerAvailableFact] + [Fact] public void SpacesGetReplacedWithDashes() { - var (project, logs, d) = ProjectInitializer.InitProject(new () { + var (project, logs, d) = ProjectInitializer.InitProject(new() + { [ContainerBaseImage] = "mcr.microsoft.com/dotnet runtime:7.0", [ContainerRegistry] = "localhost:5010" }); using var _ = d; var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None); - Assert.True(instance.Build(new[]{ComputeContainerConfig}, new [] { logs }, null, out var outputs)); + Assert.True(instance.Build(new[] { ComputeContainerConfig }, new[] { logs }, null, out var outputs)); - Assert.Equal("mcr.microsoft.com",instance.GetPropertyValue(ContainerBaseRegistry)); + Assert.Equal("mcr.microsoft.com", instance.GetPropertyValue(ContainerBaseRegistry)); Assert.Equal("dotnet-runtime", instance.GetPropertyValue(ContainerBaseName)); Assert.Equal("7.0", instance.GetPropertyValue(ContainerBaseTag)); } - [DockerAvailableFact] + [Fact] public void RegexCatchesInvalidContainerNames() { - var (project, logs, d) = ProjectInitializer.InitProject(new () { + var (project, logs, d) = ProjectInitializer.InitProject(new() + { [ContainerBaseImage] = "mcr.microsoft.com/dotnet/runtime:7.0", [ContainerRegistry] = "localhost:5010", [ContainerRepository] = "dotnet testimage", @@ -60,14 +62,15 @@ public void RegexCatchesInvalidContainerNames() }); using var _ = d; var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None); - Assert.True(instance.Build(new[]{ComputeContainerConfig}, new [] { logs }, null, out var outputs)); + Assert.True(instance.Build(new[] { ComputeContainerConfig }, new[] { logs }, null, out var outputs)); Assert.Contains(logs.Messages, m => m.Message?.Contains("'dotnet testimage' was not a valid container image name, it was normalized to 'dotnet-testimage'") == true); } - [DockerAvailableFact] + [Fact] public void RegexCatchesInvalidContainerTags() { - var (project, logs, d) = ProjectInitializer.InitProject(new () { + var (project, logs, d) = ProjectInitializer.InitProject(new() + { [ContainerBaseImage] = "mcr.microsoft.com/dotnet/runtime:7.0", [ContainerRegistry] = "localhost:5010", [ContainerRepository] = "dotnet/testimage", @@ -75,16 +78,17 @@ public void RegexCatchesInvalidContainerTags() }); using var _ = d; var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None); - Assert.False(instance.Build(new[]{ComputeContainerConfig}, new [] { logs }, null, out var outputs)); + Assert.False(instance.Build(new[] { ComputeContainerConfig }, new[] { logs }, null, out var outputs)); Assert.True(logs.Errors.Count > 0); Assert.Equal(logs.Errors[0].Code, ErrorCodes.CONTAINER2007); } - [DockerAvailableFact] + [Fact] public void CanOnlySupplyOneOfTagAndTags() { - var (project, logs, d) = ProjectInitializer.InitProject(new () { + var (project, logs, d) = ProjectInitializer.InitProject(new() + { [ContainerBaseImage] = "mcr.microsoft.com/dotnet/runtime:7.0", [ContainerRegistry] = "localhost:5010", [ContainerRepository] = "dotnet/testimage", @@ -93,16 +97,34 @@ public void CanOnlySupplyOneOfTagAndTags() }); using var _ = d; var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None); - Assert.False(instance.Build(new[]{ComputeContainerConfig}, new [] { logs }, null, out var outputs)); + Assert.False(instance.Build(new[] { ComputeContainerConfig }, new[] { logs }, null, out var outputs)); Assert.True(logs.Errors.Count > 0); Assert.Equal(logs.Errors[0].Code, ErrorCodes.CONTAINER2008); } - [DockerAvailableFact] + [Fact] + public void InvalidTagsThrowError() + { + var (project, logs, d) = ProjectInitializer.InitProject(new() + { + [ContainerBaseImage] = "mcr.microsoft.com/dotnet/aspnet:8.0", + [ContainerRepository] = "dotnet/testimage", + [ContainerImageTags] = "'latest;oldest'" + }); + using var _ = d; + var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None); + Assert.False(instance.Build(new[] { ComputeContainerConfig }, new[] { logs }, null, out var outputs)); + + Assert.True(logs.Errors.Count > 0); + Assert.Equal(logs.Errors[0].Code, ErrorCodes.CONTAINER2010); + } + + [Fact] public void FailsOnCompletelyInvalidRepositoryNames() { - var (project, logs, d) = ProjectInitializer.InitProject(new () { + var (project, logs, d) = ProjectInitializer.InitProject(new() + { [ContainerBaseImage] = "mcr.microsoft.com/dotnet/runtime:7.0", [ContainerRegistry] = "localhost:5010", [ContainerImageName] = "㓳㓴㓵㓶㓷㓹㓺㓻", @@ -110,16 +132,17 @@ public void FailsOnCompletelyInvalidRepositoryNames() }); using var _ = d; var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None); - Assert.False(instance.Build(new[]{ComputeContainerConfig}, new [] { logs }, null, out var outputs)); + Assert.False(instance.Build(new[] { ComputeContainerConfig }, new[] { logs }, null, out var outputs)); Assert.True(logs.Errors.Count > 0); Assert.Equal(logs.Errors[0].Code, ErrorCodes.CONTAINER2005); } - [DockerAvailableFact] + [Fact] public void FailsWhenFirstCharIsAUnicodeLetterButNonLatin() { - var (project, logs, d) = ProjectInitializer.InitProject(new () { + var (project, logs, d) = ProjectInitializer.InitProject(new() + { [ContainerBaseImage] = "mcr.microsoft.com/dotnet/runtime:7.0", [ContainerRegistry] = "localhost:5010", [ContainerImageName] = "㓳but-otherwise-valid", @@ -127,7 +150,7 @@ public void FailsWhenFirstCharIsAUnicodeLetterButNonLatin() }); using var _ = d; var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None); - Assert.False(instance.Build(new[]{ComputeContainerConfig}, new [] { logs }, null, out var outputs)); + Assert.False(instance.Build(new[] { ComputeContainerConfig }, new[] { logs }, null, out var outputs)); Assert.True(logs.Errors.Count > 0); Assert.Equal(logs.Errors[0].Code, ErrorCodes.CONTAINER2005); diff --git a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/ProjectInitializer.cs b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/ProjectInitializer.cs index d373bb05bb8c..1e19bbb4f7e0 100644 --- a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/ProjectInitializer.cs +++ b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/ProjectInitializer.cs @@ -34,7 +34,7 @@ private static string CombineFiles(string propsFile, string targetsFile) return tempTargetLocation; } - public static (Project, CapturingLogger, IDisposable) InitProject(Dictionary bonusProps, [CallerMemberName]string projectName = "") + public static (Project, CapturingLogger, IDisposable) InitProject(Dictionary bonusProps, Dictionary? bonusItems = null, [CallerMemberName] string projectName = "") { var props = new Dictionary(); // required parameters @@ -52,6 +52,7 @@ public static (Project, CapturingLogger, IDisposable) InitProject(Dictionary { @@ -68,6 +69,32 @@ public static (Project, CapturingLogger, IDisposable) InitProject(Dictionary ni, + [var ni, ..] => ni, + [] => null + }; + if (newItem is not null) + { + // we don't want to copy the MSBuild-reserved metadata, if any, + // so only use the custom metadata + var customMetadata = item.CloneCustomMetadata(); + foreach (var key in customMetadata) + { + newItem.SetMetadataValue((string)key, customMetadata[key] as string); + } + } + } + } + } + return (project, logs, collection); } } diff --git a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/RegistryTests.cs b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/RegistryTests.cs index 92bede0342c3..0f58b21e98ec 100644 --- a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/RegistryTests.cs +++ b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/RegistryTests.cs @@ -47,7 +47,7 @@ public async Task CanReadManifestFromRegistry(string fullyQualifiedContainerName containerName, containerTag, "linux-x64", - ridgraphfile, + ToolsetUtils.RidGraphManifestPicker, cancellationToken: default).ConfigureAwait(false); Assert.NotNull(downloadedImage); diff --git a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/TargetsTests.cs b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/TargetsTests.cs index 41bd32f8129a..88aa4498356d 100644 --- a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/TargetsTests.cs +++ b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/TargetsTests.cs @@ -30,7 +30,8 @@ public void CanDeferEntrypoint(string selfContainedPropertyName, bool selfContai } [Fact] - public void CanDeferToContainerImageNameWhenPresent() { + public void CanDeferToContainerImageNameWhenPresent() + { var customImageName = "my-container-app"; var (project, logger, d) = ProjectInitializer.InitProject(new() { @@ -38,7 +39,7 @@ public void CanDeferToContainerImageNameWhenPresent() { }); using var _ = d; var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None); - instance.Build(new[] { ComputeContainerConfig }, new []{ logger }); + instance.Build(new[] { ComputeContainerConfig }, new[] { logger }); logger.Warnings.Should().HaveCount(1, "a warning for the use of the old ContainerImageName property should have been created"); logger.Warnings[0].Code.Should().Be(KnownStrings.ErrorCodes.CONTAINER003); Assert.Equal(customImageName, instance.GetPropertyValue(ContainerRepository)); @@ -58,7 +59,7 @@ public void CanNormalizeInputContainerNames(string projectName, string expectedC }, projectName: $"{nameof(CanNormalizeInputContainerNames)}_{projectName}_{expectedContainerImageName}_{shouldPass}"); using var _ = d; var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None); - instance.Build(new[] { ComputeContainerConfig }, new[]{ logger }, null, out var outputs).Should().Be(shouldPass, String.Join(Environment.NewLine, logger.AllMessages)); + instance.Build(new[] { ComputeContainerConfig }, new[] { logger }, null, out var outputs).Should().Be(shouldPass, String.Join(Environment.NewLine, logger.AllMessages)); Assert.Equal(expectedContainerImageName, instance.GetPropertyValue(ContainerRepository)); } @@ -78,7 +79,7 @@ public void CanWarnOnInvalidSDKVersions(string sdkVersion, bool isAllowed) }, projectName: $"{nameof(CanWarnOnInvalidSDKVersions)}_{sdkVersion}_{isAllowed}"); using var _ = d; var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None); - instance.Build(new[]{"_ContainerVerifySDKVersion"}, new[] { logger }, null, out var outputs).Should().Be(isAllowed); + instance.Build(new[] { "_ContainerVerifySDKVersion" }, new[] { logger }, null, out var outputs).Should().Be(isAllowed); var derivedIsAllowed = Boolean.Parse(project.GetProperty("_IsSDKContainerAllowedVersion").EvaluatedValue); if (isAllowed) { @@ -103,7 +104,8 @@ public void GetsConventionalLabelsByDefault(bool shouldEvaluateLabels) }, projectName: $"{nameof(GetsConventionalLabelsByDefault)}_{shouldEvaluateLabels}"); using var _ = d; var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None); - instance.Build(new[] { ComputeContainerConfig }, new [] { logger }, null, out var outputs).Should().BeTrue("Build should have succeeded"); + var success = instance.Build(new[] { ComputeContainerConfig }, new[] { logger }, null, out var outputs); + success.Should().BeTrue("Build should have succeeded"); if (shouldEvaluateLabels) { instance.GetItems(ContainerLabel).Should().NotBeEmpty("Should have evaluated some labels by default"); @@ -132,7 +134,7 @@ public void ShouldNotIncludeSourceControlLabelsUnlessUserOptsIn(bool includeSour }, projectName: $"{nameof(ShouldNotIncludeSourceControlLabelsUnlessUserOptsIn)}_{includeSourceControl}"); using var _ = d; var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None); - instance.Build(new[] { ComputeContainerConfig }, new [] { logger }, null, out var outputs).Should().BeTrue("Build should have succeeded but failed due to {0}", String.Join("\n", logger.AllMessages)); + instance.Build(new[] { ComputeContainerConfig }, new[] { logger }, null, out var outputs).Should().BeTrue("Build should have succeeded but failed due to {0}", String.Join("\n", logger.AllMessages)); var labels = instance.GetItems(ContainerLabel); if (includeSourceControl) { @@ -180,12 +182,13 @@ public void CanComputeTagsForSupportedSDKVersions(string sdkVersion, string tfm, }, projectName: $"{nameof(CanComputeTagsForSupportedSDKVersions)}_{sdkVersion}_{tfm}_{expectedTag}"); using var _ = d; var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None); - instance.Build(new[]{"_ComputeContainerBaseImageTag"}, new [] { logger }, null, out var outputs).Should().BeTrue(String.Join(Environment.NewLine, logger.Errors)); - var computedTag = instance.GetProperty("_ContainerBaseImageTag").EvaluatedValue; - computedTag.Should().Be(expectedTag); + instance.Build(new[] { ComputeContainerBaseImage }, new[] { logger }, null, out var outputs).Should().BeTrue(String.Join(Environment.NewLine, logger.Errors)); + var computedTag = instance.GetProperty(ContainerBaseImage).EvaluatedValue; + computedTag.Should().EndWith(expectedTag); } [InlineData("v8.0", "linux-x64", null)] + [InlineData("v8.0", "linux-musl-x64", null)] [InlineData("v8.0", "win-x64", "ContainerUser")] [InlineData("v7.0", "linux-x64", null)] [InlineData("v7.0", "win-x64", null)] @@ -203,7 +206,7 @@ public void CanComputeContainerUser(string tfm, string rid, string expectedUser) }, projectName: $"{nameof(CanComputeContainerUser)}_{tfm}_{rid}_{expectedUser}"); using var _ = d; var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None); - instance.Build(new[]{ComputeContainerConfig}, new [] { logger }, null, out var outputs).Should().BeTrue(String.Join(Environment.NewLine, logger.Errors)); + instance.Build(new[] { ComputeContainerConfig }, new[] { logger }, null, out var outputs).Should().BeTrue(String.Join(Environment.NewLine, logger.Errors)); var computedTag = instance.GetProperty("ContainerUser")?.EvaluatedValue; computedTag.Should().Be(expectedUser); } @@ -222,7 +225,7 @@ public void WindowsUsersGetLinuxContainers(string sdkPortableRid, string expecte }, projectName: $"{nameof(WindowsUsersGetLinuxContainers)}_{sdkPortableRid}_{expectedRid}"); using var _ = d; var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None); - instance.Build(new[]{ComputeContainerConfig}, null, null, out var outputs).Should().BeTrue(String.Join(Environment.NewLine, logger.Errors)); + instance.Build(new[] { ComputeContainerConfig }, null, null, out var outputs).Should().BeTrue(String.Join(Environment.NewLine, logger.Errors)); var computedRid = instance.GetProperty(KnownStrings.Properties.ContainerRuntimeIdentifier)?.EvaluatedValue; computedRid.Should().Be(expectedRid); } @@ -244,8 +247,140 @@ public void CanTakeContainerBaseFamilyIntoAccount(string sdkVersion, string tfmM }, projectName: $"{nameof(CanTakeContainerBaseFamilyIntoAccount)}_{sdkVersion}_{tfmMajMin}_{containerFamily}_{expectedTag}"); using var _ = d; var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None); - instance.Build(new[]{ _ComputeContainerBaseImageTag }, null, null, out var outputs).Should().BeTrue(String.Join(Environment.NewLine, logger.Errors)); - var computedBaseImageTag = instance.GetProperty(KnownStrings.Properties._ContainerBaseImageTag)?.EvaluatedValue; - computedBaseImageTag.Should().Be(expectedTag); + instance.Build(new[] { ComputeContainerBaseImage }, null, null, out var outputs).Should().BeTrue(String.Join(Environment.NewLine, logger.Errors)); + var computedBaseImageTag = instance.GetProperty(ContainerBaseImage)?.EvaluatedValue; + computedBaseImageTag.Should().EndWith(expectedTag); + } + + [InlineData("v6.0", "linux-musl-x64", "mcr.microsoft.com/dotnet/runtime:6.0-alpine")] + [InlineData("v6.0", "linux-x64", "mcr.microsoft.com/dotnet/runtime:6.0")] + [InlineData("v7.0", "linux-musl-x64", "mcr.microsoft.com/dotnet/runtime:7.0-alpine")] + [InlineData("v7.0", "linux-x64", "mcr.microsoft.com/dotnet/runtime:7.0")] + [InlineData("v8.0", "linux-musl-x64", "mcr.microsoft.com/dotnet/runtime:8.0-alpine")] + [InlineData("v8.0", "linux-x64", "mcr.microsoft.com/dotnet/runtime:8.0")] + [Theory] + public void MuslRidsGetAlpineContainers(string tfm, string rid, string expectedImage) + { + var (project, logger, d) = ProjectInitializer.InitProject(new() + { + ["NetCoreSdkVersion"] = "8.0.100", + ["TargetFrameworkVersion"] = tfm, + [KnownStrings.Properties.ContainerRuntimeIdentifier] = rid, + }, projectName: $"{nameof(MuslRidsGetAlpineContainers)}_{tfm}_{rid}_{expectedImage}"); + using var _ = d; + var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None); + instance.Build(new[] { ComputeContainerBaseImage }, null, null, out var outputs).Should().BeTrue(String.Join(Environment.NewLine, logger.Errors)); + var computedBaseImageTag = instance.GetProperty(ContainerBaseImage)?.EvaluatedValue; + computedBaseImageTag.Should().BeEquivalentTo(expectedImage); + } + + [InlineData("linux-musl-x64", "mcr.microsoft.com/dotnet/nightly/runtime-deps:8.0-alpine-aot")] + [InlineData("linux-x64", "mcr.microsoft.com/dotnet/nightly/runtime-deps:8.0-jammy-chiseled-aot")] + [Theory] + public void AOTAppsGetAOTImages(string rid, string expectedImage) + { + var (project, logger, d) = ProjectInitializer.InitProject(new() + { + ["NetCoreSdkVersion"] = "8.0.100", + ["TargetFrameworkVersion"] = "v8.0", + [KnownStrings.Properties.ContainerRuntimeIdentifier] = rid, + [KnownStrings.Properties.PublishSelfContained] = true.ToString(), + [KnownStrings.Properties.PublishAot] = true.ToString(), + [KnownStrings.Properties.InvariantGlobalization] = true.ToString(), + }, projectName: $"{nameof(AOTAppsGetAOTImages)}_{rid}_{expectedImage}"); + using var _ = d; + var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None); + instance.Build(new[] { ComputeContainerBaseImage }, null, null, out var outputs).Should().BeTrue(String.Join(Environment.NewLine, logger.Errors)); + var computedBaseImageTag = instance.GetProperty(ContainerBaseImage)?.EvaluatedValue; + computedBaseImageTag.Should().BeEquivalentTo(expectedImage); + } + + [InlineData("linux-musl-x64", "mcr.microsoft.com/dotnet/nightly/runtime-deps:8.0-alpine-extra")] + [InlineData("linux-x64", "mcr.microsoft.com/dotnet/nightly/runtime-deps:8.0-jammy-chiseled-extra")] + [Theory] + public void AOTAppsWithCulturesGetExtraImages(string rid, string expectedImage) + { + var (project, logger, d) = ProjectInitializer.InitProject(new() + { + ["NetCoreSdkVersion"] = "8.0.100", + ["TargetFrameworkVersion"] = "v8.0", + [KnownStrings.Properties.ContainerRuntimeIdentifier] = rid, + [KnownStrings.Properties.PublishSelfContained] = true.ToString(), + [KnownStrings.Properties.PublishAot] = true.ToString(), + [KnownStrings.Properties.InvariantGlobalization] = false.ToString() + }, projectName: $"{nameof(AOTAppsWithCulturesGetExtraImages)}_{rid}_{expectedImage}"); + using var _ = d; + var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None); + instance.Build(new[] { ComputeContainerBaseImage }, null, null, out var outputs).Should().BeTrue(String.Join(Environment.NewLine, logger.Errors)); + var computedBaseImageTag = instance.GetProperty(ContainerBaseImage)?.EvaluatedValue; + computedBaseImageTag.Should().BeEquivalentTo(expectedImage); + } + + [InlineData("linux-musl-x64", "mcr.microsoft.com/dotnet/runtime-deps:7.0-alpine")] + [InlineData("linux-x64", "mcr.microsoft.com/dotnet/runtime-deps:7.0")] + [Theory] + public void AOTAppsLessThan8DoNotGetAOTImages(string rid, string expectedImage) + { + var (project, logger, d) = ProjectInitializer.InitProject(new() + { + ["NetCoreSdkVersion"] = "8.0.100", + ["TargetFrameworkVersion"] = "v7.0", + [KnownStrings.Properties.ContainerRuntimeIdentifier] = rid, + [KnownStrings.Properties.PublishSelfContained] = true.ToString(), + [KnownStrings.Properties.PublishAot] = true.ToString(), + [KnownStrings.Properties.InvariantGlobalization] = true.ToString(), + }, projectName: $"{nameof(AOTAppsLessThan8DoNotGetAOTImages)}_{rid}_{expectedImage}"); + using var _ = d; + var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None); + instance.Build(new[] { ComputeContainerBaseImage }, null, null, out var outputs).Should().BeTrue(String.Join(Environment.NewLine, logger.Errors)); + var computedBaseImageTag = instance.GetProperty(ContainerBaseImage)?.EvaluatedValue; + computedBaseImageTag.Should().BeEquivalentTo(expectedImage); + } + + [InlineData("linux-musl-x64", "mcr.microsoft.com/dotnet/runtime-deps:7.0-alpine")] + [InlineData("linux-x64", "mcr.microsoft.com/dotnet/runtime-deps:7.0")] + [Theory] + public void AOTAppsLessThan8WithCulturesDoNotGetExtraImages(string rid, string expectedImage) + { + var (project, logger, d) = ProjectInitializer.InitProject(new() + { + ["NetCoreSdkVersion"] = "8.0.100", + ["TargetFrameworkVersion"] = "v7.0", + [KnownStrings.Properties.ContainerRuntimeIdentifier] = rid, + [KnownStrings.Properties.PublishSelfContained] = true.ToString(), + [KnownStrings.Properties.PublishAot] = true.ToString(), + [KnownStrings.Properties.InvariantGlobalization] = false.ToString() + }, projectName: $"{nameof(AOTAppsWithCulturesGetExtraImages)}_{rid}_{expectedImage}"); + using var _ = d; + var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None); + instance.Build(new[] { ComputeContainerBaseImage }, null, null, out var outputs).Should().BeTrue(String.Join(Environment.NewLine, logger.Errors)); + var computedBaseImageTag = instance.GetProperty(ContainerBaseImage)?.EvaluatedValue; + computedBaseImageTag.Should().BeEquivalentTo(expectedImage); + } + + [Fact] + public void AspNetFDDAppsGetAspNetBaseImage() + { + var expectedImage = "mcr.microsoft.com/dotnet/aspnet:8.0"; + var (project, logger, d) = ProjectInitializer.InitProject(new() + { + ["NetCoreSdkVersion"] = "8.0.200", + ["TargetFrameworkVersion"] = "v8.0", + [KnownStrings.Properties.ContainerRuntimeIdentifier] = "linux-x64", + }, bonusItems: new() + { + [KnownStrings.Items.FrameworkReference] = KnownFrameworkReferences.WebApp + }, projectName: $"{nameof(AspNetFDDAppsGetAspNetBaseImage)}"); + using var _ = d; + var instance = project.CreateProjectInstance(global::Microsoft.Build.Execution.ProjectInstanceSettings.None); + instance.Build(new[] { ComputeContainerBaseImage }, null, null, out var outputs).Should().BeTrue(String.Join(Environment.NewLine, logger.Errors)); + var computedBaseImageTag = instance.GetProperty(ContainerBaseImage)?.EvaluatedValue; + computedBaseImageTag.Should().BeEquivalentTo(expectedImage); + } + + private static class KnownFrameworkReferences + { + public static Microsoft.Build.Framework.ITaskItem[] ConsoleApp { get; } = [new Microsoft.Build.Utilities.TaskItem("Microsoft.NETCore.App")]; + public static Microsoft.Build.Framework.ITaskItem[] WebApp { get; } = [.. ConsoleApp, new Microsoft.Build.Utilities.TaskItem("Microsoft.AspNetCore.App")]; } } diff --git a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/ToolsetUtils.cs b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/ToolsetUtils.cs index 35cf4fb4ddd0..599d95280157 100644 --- a/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/ToolsetUtils.cs +++ b/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests/ToolsetUtils.cs @@ -20,6 +20,8 @@ internal static string GetRuntimeGraphFilePath() return lastWrittenSdk.GetFiles("RuntimeIdentifierGraph.json").Single().FullName; } + internal static IManifestPicker RidGraphManifestPicker { get; } = new RidGraphManifestPicker(GetRuntimeGraphFilePath()); + /// /// Gets path to built Microsoft.NET.Build.Containers.*.nupkg prepared for tests. /// diff --git a/src/Tests/Microsoft.NET.Build.Containers.UnitTests/ImageBuilderTests.cs b/src/Tests/Microsoft.NET.Build.Containers.UnitTests/ImageBuilderTests.cs index 29c4b35fc6ef..559bd7d33c86 100644 --- a/src/Tests/Microsoft.NET.Build.Containers.UnitTests/ImageBuilderTests.cs +++ b/src/Tests/Microsoft.NET.Build.Containers.UnitTests/ImageBuilderTests.cs @@ -221,7 +221,7 @@ public void CanAddPortsToImage() Assert.Equal(2, resultPorts.Count); Assert.NotNull(resultPorts["6000/tcp"] as JsonObject); - Assert.NotNull( resultPorts["6010/udp"] as JsonObject); + Assert.NotNull(resultPorts["6010/udp"] as JsonObject); } [Fact] @@ -602,12 +602,56 @@ public void CanSetContainerUserAndOverrideAppUID() config!["config"]?["User"]?.GetValue().Should().Be(expected: userId, because: "The precedence of SetUser should override inferred user ids"); } + [Fact] + public void WhenMultipleUrlSourcesAreSetOnlyAspnetcoreUrlsIsUsed() + { + var builder = FromBaseImageConfig($$""" + { + "architecture": "amd64", + "config": { + "Hostname": "", + "Domainname": "", + "User": "", + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + ], + "Cmd": ["bash"], + "Image": "sha256:d772d27ebeec80393349a4770dc37f977be2c776a01c88b624d43f93fa369d69", + "WorkingDir": "" + }, + "created": "2023-02-04T08:14:52.000901321Z", + "os": "linux", + "rootfs": { + "type": "layers", + "diff_ids": [ + "sha256:bd2fe8b74db65d82ea10db97368d35b92998d4ea0e7e7dc819481fe4a68f64cf", + "sha256:94100d1041b650c6f7d7848c550cd98c25d0bdc193d30692e5ea5474d7b3b085", + "sha256:53c2a75a33c8f971b4b5036d34764373e134f91ee01d8053b4c3573c42e1cf5d", + "sha256:49a61320e585180286535a2545be5722b09e40ad44c7c190b20ec96c9e42e4a3", + "sha256:8a379cce2ac272aa71aa029a7bbba85c852ba81711d9f90afaefd3bf5036dc48" + ] + } + } + """); + + builder.AddEnvironmentVariable(ImageBuilder.EnvironmentVariables.ASPNETCORE_URLS, "https://*:12345"); + builder.AddEnvironmentVariable(ImageBuilder.EnvironmentVariables.ASPNETCORE_HTTPS_PORTS, "456"); + var builtImage = builder.Build(); + JsonNode? result = JsonNode.Parse(builtImage.Config); + Assert.NotNull(result); + var portsObject = result["config"]?["ExposedPorts"]?.AsObject(); + var assignedPorts = portsObject?.AsEnumerable().Select(portString => int.Parse(portString.Key.Split('/')[0])).ToArray(); + Assert.Equal([12345], assignedPorts); + } + private ImageBuilder FromBaseImageConfig(string baseImageConfig, [CallerMemberName] string testName = "") { - var manifest = new ManifestV2() { + var manifest = new ManifestV2() + { SchemaVersion = 2, MediaType = SchemaTypes.DockerManifestV2, - Config = new ManifestConfig() { + Config = new ManifestConfig() + { mediaType = "", size = 0, digest = "sha256:0" diff --git a/src/Tests/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildANetCoreApp.cs b/src/Tests/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildANetCoreApp.cs index 551f60fe656e..2701aa314dd1 100644 --- a/src/Tests/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildANetCoreApp.cs +++ b/src/Tests/Microsoft.NET.Build.Tests/GivenThatWeWantToBuildANetCoreApp.cs @@ -254,8 +254,8 @@ public void It_restores_only_ridless_tfm() } [Theory] - [InlineData("netcoreapp2.0")] - [InlineData("netcoreapp2.1")] + [InlineData("net6.0")] + [InlineData("net7.0")] [InlineData(ToolsetInfo.CurrentTargetFramework)] public void It_runs_the_app_from_the_output_folder(string targetFramework) { @@ -263,41 +263,34 @@ public void It_runs_the_app_from_the_output_folder(string targetFramework) } [Theory] - [InlineData("netcoreapp2.1")] + [InlineData("net6.0")] + [InlineData("net7.0")] [InlineData(ToolsetInfo.CurrentTargetFramework)] public void It_runs_a_rid_specific_app_from_the_output_folder(string targetFramework) - { + { RunAppFromOutputFolder("RunFromOutputFolderWithRID_" + targetFramework, true, false, targetFramework); } [Theory] - [InlineData("netcoreapp2.0")] + [InlineData("net6.0")] + [InlineData("net7.0")] [InlineData(ToolsetInfo.CurrentTargetFramework)] public void It_runs_the_app_with_conflicts_from_the_output_folder(string targetFramework) { - if (!EnvironmentInfo.SupportsTargetFramework(targetFramework)) - { - return; - } - RunAppFromOutputFolder("RunFromOutputFolderConflicts_" + targetFramework, false, true, targetFramework); } [Theory] - [InlineData("netcoreapp2.0")] + [InlineData("net6.0")] + [InlineData("net7.0")] [InlineData(ToolsetInfo.CurrentTargetFramework)] public void It_runs_a_rid_specific_app_with_conflicts_from_the_output_folder(string targetFramework) { - if (!EnvironmentInfo.SupportsTargetFramework(targetFramework)) - { - return; - } - RunAppFromOutputFolder("RunFromOutputFolderWithRIDConflicts_" + targetFramework, true, true, targetFramework); } private void RunAppFromOutputFolder(string testName, bool useRid, bool includeConflicts, - string targetFramework = "netcoreapp2.0") + string targetFramework = ToolsetInfo.CurrentTargetFramework) { var runtimeIdentifier = useRid ? EnvironmentInfo.GetCompatibleRid(targetFramework) : null; diff --git a/src/Tests/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAFrameworkDependentApp.cs b/src/Tests/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAFrameworkDependentApp.cs index ff31fa3e626b..c85eef8641f2 100644 --- a/src/Tests/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAFrameworkDependentApp.cs +++ b/src/Tests/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAFrameworkDependentApp.cs @@ -15,12 +15,12 @@ public GivenThatWeWantToPublishAFrameworkDependentApp(ITestOutputHelper log) : b } [Theory] - [InlineData(null, "netcoreapp2.1")] - [InlineData("true", "netcoreapp2.1")] - [InlineData("false", "netcoreapp2.1")] - [InlineData(null, "netcoreapp2.2")] - [InlineData("true", "netcoreapp2.2")] - [InlineData("false", "netcoreapp2.2")] + [InlineData(null, "net6.0")] + [InlineData("true", "net6.0")] + [InlineData("false", "net6.0")] + [InlineData(null, "net7.0")] + [InlineData("true", "net7.0")] + [InlineData("false", "net7.0")] [InlineData(null, ToolsetInfo.CurrentTargetFramework)] [InlineData("true", ToolsetInfo.CurrentTargetFramework)] [InlineData("false", ToolsetInfo.CurrentTargetFramework)] @@ -46,14 +46,6 @@ public void It_publishes_with_or_without_apphost(string useAppHost, string targe msbuildArgs.Add($"/p:UseAppHost={useAppHost}"); } - if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.OSX) && - targetFramework == "netcoreapp2.1") - { - // .NET Core 2.1.0 packages don't support latest versions of OS X, so roll forward to the - // latest patch which does - msbuildArgs.Add("/p:TargetLatestRuntimePatch=true"); - } - var publishCommand = new PublishCommand(testAsset); publishCommand .Execute(msbuildArgs.ToArray()) @@ -112,7 +104,7 @@ public void It_errors_when_using_app_host_with_older_target_framework() .Should() .Fail() .And - .HaveStdOutContaining(Strings.FrameworkDependentAppHostRequiresVersion21.Replace("“", "\"").Replace("”", "\"")); + .HaveStdOutContaining(Strings.FrameworkDependentAppHostRequiresVersion21.Replace("�", "\"").Replace("�", "\"")); } } } diff --git a/src/Tests/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishASingleFileApp.cs b/src/Tests/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishASingleFileApp.cs index 21a1c24b5ed2..57c50ae45b85 100644 --- a/src/Tests/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishASingleFileApp.cs +++ b/src/Tests/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishASingleFileApp.cs @@ -150,6 +150,25 @@ public void It_generates_publishing_single_file_with_win7() .Pass(); } + [Fact] + public void Target_after_AfterSdkPublish_executes() + { + var projectChanges = (XDocument doc) => + { + var ns = doc.Root.Name.Namespace; + var target = new XElement("Target"); + target.ReplaceAttributes(new XAttribute[] { new XAttribute("Name", "AfterAfterSdkPublish"), new XAttribute("AfterTargets", "AfterSdkPublish") }); + var message = new XElement("Message"); + message.ReplaceAttributes(new XAttribute[] { new XAttribute("Importance", "High"), new XAttribute("Text", "Executed AfterAfterSdkPublish") }); + target.Add(message); + doc.Root.Add(target); + }; + + var publishResults = GetPublishCommand(projectChanges: projectChanges).Execute(); + publishResults.Should().Pass(); + publishResults.Should().HaveStdOutContaining("Executed AfterAfterSdkPublish"); + } + [Fact] public void It_errors_when_publishing_single_file_lib() { diff --git a/src/Tests/Microsoft.NET.Sdk.Razor.Tests/ScopedCssIntegrationTests.cs b/src/Tests/Microsoft.NET.Sdk.Razor.Tests/ScopedCssIntegrationTests.cs index 1bd8241383e0..8469b0a9f7d5 100644 --- a/src/Tests/Microsoft.NET.Sdk.Razor.Tests/ScopedCssIntegrationTests.cs +++ b/src/Tests/Microsoft.NET.Sdk.Razor.Tests/ScopedCssIntegrationTests.cs @@ -90,7 +90,7 @@ public void CanOverrideScopeIdentifiers() var scoped = Path.Combine(intermediateOutputPath, "scopedcss", "Styles", "Pages", "Counter.rz.scp.css"); new FileInfo(scoped).Should().Exist(); new FileInfo(scoped).Should().Contain("b-overriden"); - var generated = Path.Combine(intermediateOutputPath, "generated", "Microsoft.NET.Sdk.Razor.SourceGenerators", "Microsoft.NET.Sdk.Razor.SourceGenerators.RazorSourceGenerator", "Components_Pages_Counter_razor.g.cs"); + var generated = Path.Combine(intermediateOutputPath, "generated", "Microsoft.CodeAnalysis.Razor.Compiler.SourceGenerators", "Microsoft.NET.Sdk.Razor.SourceGenerators.RazorSourceGenerator", "Components_Pages_Counter_razor.g.cs"); new FileInfo(generated).Should().Exist(); new FileInfo(generated).Should().Contain("b-overriden"); new FileInfo(Path.Combine(intermediateOutputPath, "scopedcss", "Components", "Pages", "Index.razor.rz.scp.css")).Should().NotExist(); @@ -319,7 +319,7 @@ public void Build_RemovingScopedCssAndBuilding_UpdatesGeneratedCodeAndBundle() new FileInfo(generatedBundle).Should().Exist(); var generatedProjectBundle = Path.Combine(intermediateOutputPath, "scopedcss", "projectbundle", "ComponentApp.bundle.scp.css"); new FileInfo(generatedProjectBundle).Should().Exist(); - var generatedCounter = Path.Combine(intermediateOutputPath, "generated", "Microsoft.NET.Sdk.Razor.SourceGenerators", "Microsoft.NET.Sdk.Razor.SourceGenerators.RazorSourceGenerator", "Components_Pages_Counter_razor.g.cs"); + var generatedCounter = Path.Combine(intermediateOutputPath, "generated", "Microsoft.CodeAnalysis.Razor.Compiler.SourceGenerators", "Microsoft.NET.Sdk.Razor.SourceGenerators.RazorSourceGenerator", "Components_Pages_Counter_razor.g.cs"); new FileInfo(generatedCounter).Should().Exist(); var componentThumbprint = FileThumbPrint.Create(generatedCounter); diff --git a/src/Tests/SDDLTests/Program.cs b/src/Tests/SDDLTests/Program.cs index eed29c834100..f6290d24535d 100644 --- a/src/Tests/SDDLTests/Program.cs +++ b/src/Tests/SDDLTests/Program.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Net.NetworkInformation; using System.Reflection; using System.Runtime.Versioning; using System.Security.AccessControl; @@ -22,6 +23,21 @@ public class SDDLTests /// private static readonly string s_programData = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData); + /// + /// Directory under ProgramData to store the install state file. + /// + private static readonly string s_installStateDirectory = Path.Combine(s_programData, "SDDLTest", "workloads", "8.0.100", "InstallState"); + + /// + /// The filename and extension of the install state file. + /// + private static readonly string s_installStateFile = "default.json"; + + /// + /// The full path of the install state file. + /// + private static readonly string s_installStateFileAssetPath = Path.Combine(s_installStateDirectory, s_installStateFile); + /// /// Directory under the user's %temp% directory where the test asset will be created. /// @@ -151,8 +167,8 @@ private static string CreateTestAsset() /// private static void RelocateAndSecureAsset() { - MsiPackageCache.CreateSecureDirectory(s_workloadPackCacheDirectory); - MsiPackageCache.MoveAndSecureFile(s_userTestAssetPath, s_cachedTestAssetPath); + SecurityUtils.CreateSecureDirectory(s_workloadPackCacheDirectory); + SecurityUtils.MoveAndSecureFile(s_userTestAssetPath, s_cachedTestAssetPath); } /// @@ -230,6 +246,25 @@ private static void VerifyDescriptors() VerifyFileSecurityDescriptor(s_cachedTestAssetPath, "BA", "BA", 4, "A;ID;0x1200a9;;;WD", "A;ID;FA;;;SY", "A;ID;FA;;;BA", "A;ID;0x1200a9;;;BU"); } + private static void CreateInstallStateAsset() + { + SecurityUtils.CreateSecureDirectory(s_installStateDirectory); + File.WriteAllLines(s_installStateFileAssetPath, new[] { "line1", "line2" }); + SecurityUtils.SecureFile(s_installStateFileAssetPath); + } + + private static void VerifyInstallStateDescriptors() + { + // Dump the descriptor of ProgramData since it's useful for analyzing. + DirectorySecurity ds = new DirectorySecurity(s_programData, s_accessControlSections); + string descriptor = ds.GetSecurityDescriptorSddlForm(s_accessControlSections); + Console.WriteLine($" Directory: {s_programData}"); + Console.WriteLine($"Descriptor: {descriptor}"); + + VerifyDirectorySecurityDescriptor(s_installStateDirectory, "BA", "BA", 4, "A;OICIID;0x1200a9;;;WD", "A;OICIID;FA;;;SY", "A;OICIID;FA;;;BA", "A;OICIID;0x1200a9;;;BU"); + VerifyFileSecurityDescriptor(s_installStateFileAssetPath, "BA", "BA", 4, "A;ID;0x1200a9;;;WD", "A;ID;FA;;;SY", "A;ID;FA;;;BA", "A;ID;0x1200a9;;;BU"); + } + static void Main(string[] args) { if (!OperatingSystem.IsWindows()) @@ -251,6 +286,8 @@ static void Main(string[] args) try { RelocateAndSecureAsset(); + + CreateInstallStateAsset(); } catch { @@ -268,6 +305,9 @@ static void Main(string[] args) CreateTestAsset(); RelocateAndSecureAsset(); VerifyDescriptors(); + + CreateInstallStateAsset(); + VerifyInstallStateDescriptors(); } catch (Exception e) { @@ -317,6 +357,7 @@ static void Main(string[] args) } VerifyDescriptors(); + VerifyInstallStateDescriptors(); } else { diff --git a/src/Tests/dotnet-nuget.UnitTests/GivenANuGetCommand.cs b/src/Tests/dotnet-nuget.UnitTests/GivenANuGetCommand.cs index 6806a7d4e8ee..b405aa74e257 100644 --- a/src/Tests/dotnet-nuget.UnitTests/GivenANuGetCommand.cs +++ b/src/Tests/dotnet-nuget.UnitTests/GivenANuGetCommand.cs @@ -65,6 +65,17 @@ public GivenANuGetCommand(ITestOutputHelper log) : base(log) "--certificate-store-location", "CurrentUser", "--certificate-subject-name", "CE40881FF5F0AD3E58965DA20A9F57", "--certificate-password", "PlaceholderPassword"}, 0)] + [InlineData(new[] { "package", "search", "nuget"}, 0)] + [InlineData(new[] { "package", "search", "nuget", + "--source", "https://api.nuget.org/v3/index.json", + "--take", "10", + "--skip", "5", + "--prerelease", + "--exact-match", + "--interactive", + "--verbosity", "detailed", + "--format", "json"}, 0)] + public void ItPassesCommandIfSupported(string[] inputArgs, int result) { // Arrange diff --git a/src/Tests/dotnet-workload-install.Tests/GivenDotnetWorkloadInstall.cs b/src/Tests/dotnet-workload-install.Tests/GivenDotnetWorkloadInstall.cs index b619e67e1e3e..476a5f49894c 100644 --- a/src/Tests/dotnet-workload-install.Tests/GivenDotnetWorkloadInstall.cs +++ b/src/Tests/dotnet-workload-install.Tests/GivenDotnetWorkloadInstall.cs @@ -104,7 +104,7 @@ public void GivenWorkloadInstallOnFailingRollbackItDisplaysTopLevelError() var mockWorkloadIds = new WorkloadId[] { new WorkloadId("xamarin-android"), new WorkloadId("xamarin-android-build") }; var testDirectory = _testAssetsManager.CreateTestDirectory().Path; var dotnetRoot = Path.Combine(testDirectory, "dotnet"); - var installer = new MockPackWorkloadInstaller(failingWorkload: "xamarin-android-build", failingRollback: true); + var installer = new MockPackWorkloadInstaller(dotnetRoot, failingWorkload: "xamarin-android-build", failingRollback: true); var workloadResolver = WorkloadResolver.CreateForTests(new MockManifestProvider(new[] { _manifestPath }), dotnetRoot); var parseResult = Parser.Instance.Parse(new string[] { "dotnet", "workload", "install", "xamarin-android", "xamarin-android-build", "--skip-manifest-update" }); var workloadResolverFactory = new MockWorkloadResolverFactory(dotnetRoot, "6.0.100", workloadResolver); @@ -140,7 +140,7 @@ public void GivenWorkloadInstallItWarnsOnGarbageCollectionFailure() var mockWorkloadIds = new WorkloadId[] { new WorkloadId("xamarin-android"), new WorkloadId("xamarin-android-build") }; var testDirectory = _testAssetsManager.CreateTestDirectory().Path; var dotnetRoot = Path.Combine(testDirectory, "dotnet"); - var installer = new MockPackWorkloadInstaller(failingGarbageCollection: true); + var installer = new MockPackWorkloadInstaller(dotnetRoot, failingGarbageCollection: true); var workloadResolver = WorkloadResolver.CreateForTests(new MockManifestProvider(new[] { _manifestPath }), dotnetRoot); var parseResult = Parser.Instance.Parse(new string[] { "dotnet", "workload", "install", "xamarin-android", "xamarin-android-build", "--skip-manifest-update" }); var workloadResolverFactory = new MockWorkloadResolverFactory(dotnetRoot, "6.0.100", workloadResolver); @@ -169,10 +169,8 @@ public void GivenNoWorkloadsInstalledInfoOptionRemarksOnThat() // However, we can test a setup where no workloads are installed and --info is provided. _reporter.Clear(); - var mockWorkloadIds = new WorkloadId[] { new WorkloadId("xamarin-android"), new WorkloadId("xamarin-android-build") }; var testDirectory = _testAssetsManager.CreateTestDirectory().Path; var dotnetRoot = Path.Combine(testDirectory, "dotnet"); - var installer = new MockPackWorkloadInstaller(); var workloadResolver = WorkloadResolver.CreateForTests(new MockManifestProvider(new[] { _manifestPath }), dotnetRoot); var parseResult = Parser.Instance.Parse(new string[] { "dotnet", "workload", "install", "xamarin-android"}); @@ -207,18 +205,17 @@ public void GivenWorkloadInstallItCanUpdateInstalledManifests(bool userLocal, st Parser.Instance.Parse(new string[] {"dotnet", "workload", "install", "xamarin-android"}); var featureBand = new SdkFeatureBand(sdkVersion); var manifestsToUpdate = - new (ManifestVersionUpdate manifestUpdate, Dictionary Workloads)[] + new ManifestUpdateWithWorkloads[] { - (new ManifestVersionUpdate(new ManifestId("mock-manifest"), new ManifestVersion("1.0.0"), featureBand.ToString(), new ManifestVersion("2.0.0"), featureBand.ToString()), - null), + new(new ManifestVersionUpdate(new ManifestId("mock-manifest"), new ManifestVersion("1.0.0"), featureBand.ToString(), new ManifestVersion("2.0.0"), featureBand.ToString()), null), }; (_, var installManager, var installer, _, _, _) = GetTestInstallers(parseResult, userLocal, sdkVersion, manifestUpdates: manifestsToUpdate, installedFeatureBand: sdkVersion); installManager.InstallWorkloads(new List(), false); // Don't actually do any installs, just update manifests - installer.InstalledManifests[0].manifestUpdate.ManifestId.Should().Be(manifestsToUpdate[0].manifestUpdate.ManifestId); - installer.InstalledManifests[0].manifestUpdate.NewVersion.Should().Be(manifestsToUpdate[0].manifestUpdate.NewVersion); + installer.InstalledManifests[0].manifestUpdate.ManifestId.Should().Be(manifestsToUpdate[0].ManifestUpdate.ManifestId); + installer.InstalledManifests[0].manifestUpdate.NewVersion.Should().Be(manifestsToUpdate[0].ManifestUpdate.NewVersion); installer.InstalledManifests[0].manifestUpdate.NewFeatureBand.Should().Be(new SdkFeatureBand(sdkVersion).ToString()); installer.InstalledManifests[0].offlineCache.Should().Be(null); } @@ -232,11 +229,9 @@ public void GivenWorkloadInstallFromCacheItInstallsCachedManifest(bool userLocal { var featureBand = new SdkFeatureBand(sdkVersion); var manifestsToUpdate = - new (ManifestVersionUpdate manifestUpdate, Dictionary - Workloads)[] + new ManifestUpdateWithWorkloads[] { - (new ManifestVersionUpdate(new ManifestId("mock-manifest"), new ManifestVersion("1.0.0"), featureBand.ToString(), new ManifestVersion("2.0.0"), featureBand.ToString()), - null) + new(new ManifestVersionUpdate(new ManifestId("mock-manifest"), new ManifestVersion("1.0.0"), featureBand.ToString(), new ManifestVersion("2.0.0"), featureBand.ToString()), null) }; var cachePath = Path.Combine(_testAssetsManager.CreateTestDirectory(identifier: AppendForUserLocal("mockCache_", userLocal) + sdkVersion).Path, "mockCachePath"); @@ -249,8 +244,8 @@ public void GivenWorkloadInstallFromCacheItInstallsCachedManifest(bool userLocal installManager.Execute(); - installer.InstalledManifests[0].manifestUpdate.ManifestId.Should().Be(manifestsToUpdate[0].manifestUpdate.ManifestId); - installer.InstalledManifests[0].manifestUpdate.NewVersion.Should().Be(manifestsToUpdate[0].manifestUpdate.NewVersion); + installer.InstalledManifests[0].manifestUpdate.ManifestId.Should().Be(manifestsToUpdate[0].ManifestUpdate.ManifestId); + installer.InstalledManifests[0].manifestUpdate.NewVersion.Should().Be(manifestsToUpdate[0].ManifestUpdate.NewVersion); installer.InstalledManifests[0].manifestUpdate.NewFeatureBand.Should().Be(new SdkFeatureBand(sdkVersion).ToString()); installer.InstalledManifests[0].offlineCache.Should().Be(new DirectoryPath(cachePath)); } @@ -264,7 +259,7 @@ public void GivenWorkloadInstallItCanDownloadToOfflineCache(bool userLocal, stri { var cachePath = Path.Combine(_testAssetsManager.CreateTestDirectory(identifier: AppendForUserLocal("mockCache_", userLocal) + sdkVersion).Path, "mockCachePath"); var parseResult = Parser.Instance.Parse(new string[] { "dotnet", "workload", "install", "xamarin-android", "--download-to-cache", cachePath }); - (_, var installManager, var installer, _, var manifestUpdater, var packageDownloader) = GetTestInstallers(parseResult, userLocal, sdkVersion, tempDirManifestPath: _manifestPath, installedFeatureBand: sdkVersion); + (_, var installManager, _, _, var manifestUpdater, var packageDownloader) = GetTestInstallers(parseResult, userLocal, sdkVersion, tempDirManifestPath: _manifestPath, installedFeatureBand: sdkVersion); installManager.Execute(); @@ -324,7 +319,7 @@ public void GivenWorkloadInstallItErrorsOnUnsupportedPlatform() var manifestPath = Path.Combine(_testAssetsManager.GetAndValidateTestProjectDirectory("SampleManifest"), "UnsupportedPlatform.json"); var testDirectory = _testAssetsManager.CreateTestDirectory().Path; var dotnetRoot = Path.Combine(testDirectory, "dotnet"); - var installer = new MockPackWorkloadInstaller(); + var installer = new MockPackWorkloadInstaller(dotnetRoot); var workloadResolver = WorkloadResolver.CreateForTests(new MockManifestProvider(new[] { manifestPath }), dotnetRoot); var nugetDownloader = new MockNuGetPackageDownloader(dotnetRoot); var manifestUpdater = new MockWorkloadManifestUpdater(); @@ -457,7 +452,7 @@ static void CreateFile(string path) string sdkVersion, [CallerMemberName] string testName = "", string failingWorkload = null, - IEnumerable<(ManifestVersionUpdate manifestUpdate, Dictionary Workloads)> manifestUpdates = null, + IEnumerable manifestUpdates = null, string tempDirManifestPath = null, string installedFeatureBand = null) { @@ -466,13 +461,13 @@ static void CreateFile(string path) var dotnetRoot = Path.Combine(testDirectory, "dotnet"); var userProfileDir = Path.Combine(testDirectory, "user-profile"); var workloadResolver = WorkloadResolver.CreateForTests(new MockManifestProvider(new[] { _manifestPath }), dotnetRoot); - var installer = new MockPackWorkloadInstaller(failingWorkload) + var installer = new MockPackWorkloadInstaller(dotnetRoot, failingWorkload) { WorkloadResolver = workloadResolver }; var nugetDownloader = new MockNuGetPackageDownloader(dotnetRoot); - var manifestUpdater = new MockWorkloadManifestUpdater(manifestUpdates, tempDirManifestPath); + var manifestUpdater = new MockWorkloadManifestUpdater(manifestUpdates); if (userLocal) { WorkloadFileBasedInstall.SetUserLocal(dotnetRoot, sdkVersion); @@ -593,13 +588,11 @@ public void ShowManifestUpdatesWhenVerbosityIsDetailedOrDiagnostic(string verbos var parseResult = Parser.Instance.Parse(new string[] { "dotnet", "workload", "install", verbosityFlag, "xamarin-android" }); var manifestsToUpdate = - new (ManifestVersionUpdate manifestUpdate, Dictionary - Workloads)[] + new ManifestUpdateWithWorkloads[] { - (new ManifestVersionUpdate(new ManifestId("mock-manifest"), new ManifestVersion("1.0.0"), sdkFeatureBand, new ManifestVersion("2.0.0"), sdkFeatureBand), - null), + new(new ManifestVersionUpdate(new ManifestId("mock-manifest"), new ManifestVersion("1.0.0"), sdkFeatureBand, new ManifestVersion("2.0.0"), sdkFeatureBand), null), }; - (_, var installManager, var installer, _, _, _) = + (_, var installManager, _, _, _, _) = GetTestInstallers(parseResult, true, sdkFeatureBand, manifestUpdates: manifestsToUpdate); installManager.InstallWorkloads(new List(), false); // Don't actually do any installs, just update manifests diff --git a/src/Tests/dotnet-workload-install.Tests/GivenFileBasedWorkloadInstall.cs b/src/Tests/dotnet-workload-install.Tests/GivenFileBasedWorkloadInstall.cs index 3a98644e69ee..d352cf05da03 100644 --- a/src/Tests/dotnet-workload-install.Tests/GivenFileBasedWorkloadInstall.cs +++ b/src/Tests/dotnet-workload-install.Tests/GivenFileBasedWorkloadInstall.cs @@ -12,6 +12,7 @@ using Microsoft.Extensions.EnvironmentAbstractions; using System.Text.Json; using Microsoft.TemplateEngine.Edge.Constraints; +using Microsoft.DotNet.Workloads.Workload; namespace Microsoft.DotNet.Cli.Workload.Install.Tests { @@ -26,6 +27,42 @@ public GivenFileBasedWorkloadInstall(ITestOutputHelper log) : base(log) _manifestPath = Path.Combine(_testAssetsManager.GetAndValidateTestProjectDirectory("SampleManifest"), "Sample2.json"); } + [Fact] + public void InstallStateUpdatesWorkProperly() + { + (string dotnetRoot, FileBasedInstaller installer, _, _) = GetTestInstaller(); + var stringFeatureBand = "6.0.300"; // This is hard-coded in the test installer, so if that changes, update this, too. + var sdkFeatureBand = new SdkFeatureBand(stringFeatureBand); + var path = Path.Combine(dotnetRoot, "metadata", "workloads", stringFeatureBand, "InstallState", "default.json"); + + installer.UpdateInstallMode(sdkFeatureBand, true); + var installState = InstallStateContents.FromString(File.ReadAllText(path)); + installState.Manifests.Should().BeNull(); + installState.UseWorkloadSets.Should().BeTrue(); + + installer.SaveInstallStateManifestVersions(sdkFeatureBand, new Dictionary() + { + { "first", "second" }, + { "third", "fourth" }, + }); + + installState = InstallStateContents.FromString(File.ReadAllText(path)); + installState.Manifests.Count.Should().Be(2); + installState.Manifests["first"].Should().Be("second"); + installState.Manifests["third"].Should().Be("fourth"); + installState.UseWorkloadSets.Should().BeTrue(); + + installer.UpdateInstallMode(sdkFeatureBand, false); + installState = InstallStateContents.FromString(File.ReadAllText(path)); + installState.UseWorkloadSets.Should().BeFalse(); + installState.Manifests.Count.Should().Be(2); + + installer.RemoveManifestsFromInstallState(sdkFeatureBand); + installState = InstallStateContents.FromString(File.ReadAllText(path)); + installState.Manifests.Should().BeNull(); + installState.UseWorkloadSets.Should().BeFalse(); + } + [Fact] public void GivenManagedInstallItCanGetFeatureBandsWhenFilesArePresent() { diff --git a/src/Tests/dotnet-workload-install.Tests/GivenWorkloadManifestUpdater.cs b/src/Tests/dotnet-workload-install.Tests/GivenWorkloadManifestUpdater.cs index b9b9b8eb969d..029d28fd9ee6 100644 --- a/src/Tests/dotnet-workload-install.Tests/GivenWorkloadManifestUpdater.cs +++ b/src/Tests/dotnet-workload-install.Tests/GivenWorkloadManifestUpdater.cs @@ -124,9 +124,9 @@ public void GivenWorkloadManifestUpdateItCanCalculateUpdates() var nugetDownloader = new MockNuGetPackageDownloader(dotnetRoot); var workloadResolver = WorkloadResolver.CreateForTests(workloadManifestProvider, dotnetRoot); var installationRepo = new MockInstallationRecordRepository(); - var manifestUpdater = new WorkloadManifestUpdater(_reporter, workloadResolver, nugetDownloader, userProfileDir: Path.Combine(testDir, ".dotnet"), testDir, installationRepo, new MockPackWorkloadInstaller()); + var manifestUpdater = new WorkloadManifestUpdater(_reporter, workloadResolver, nugetDownloader, userProfileDir: Path.Combine(testDir, ".dotnet"), installationRepo, new MockPackWorkloadInstaller(dotnetRoot)); - var manifestUpdates = manifestUpdater.CalculateManifestUpdates().Select( m => m.manifestUpdate); + var manifestUpdates = manifestUpdater.CalculateManifestUpdates().Select(m => m.ManifestUpdate); manifestUpdates.Should().BeEquivalentTo(expectedManifestUpdates); } @@ -191,9 +191,9 @@ public void GivenAdvertisedManifestsItCalculatesCorrectUpdates() var nugetDownloader = new MockNuGetPackageDownloader(dotnetRoot); var workloadResolver = WorkloadResolver.CreateForTests(workloadManifestProvider, dotnetRoot); var installationRepo = new MockInstallationRecordRepository(); - var manifestUpdater = new WorkloadManifestUpdater(_reporter, workloadResolver, nugetDownloader, userProfileDir: Path.Combine(testDir, ".dotnet"), testDir, installationRepo, new MockPackWorkloadInstaller()); + var manifestUpdater = new WorkloadManifestUpdater(_reporter, workloadResolver, nugetDownloader, userProfileDir: Path.Combine(testDir, ".dotnet"), installationRepo, new MockPackWorkloadInstaller(dotnetRoot)); - var manifestUpdates = manifestUpdater.CalculateManifestUpdates().Select(m => m.manifestUpdate); + var manifestUpdates = manifestUpdater.CalculateManifestUpdates().Select(m => m.ManifestUpdate); manifestUpdates.Should().BeEquivalentTo(expectedManifestUpdates); } @@ -233,7 +233,7 @@ public void ItCanFallbackAndAdvertiseCorrectUpdate(bool useOfflineCache) var nugetDownloader = new MockNuGetPackageDownloader(dotnetRoot); nugetDownloader.PackageIdsToNotFind.Add($"{testManifestName}.Manifest-6.0.300"); var installationRepo = new MockInstallationRecordRepository(); - var manifestUpdater = new WorkloadManifestUpdater(_reporter, workloadResolver, nugetDownloader, Path.Combine(testDir, ".dotnet"), testDir, installationRepo, new MockPackWorkloadInstaller()); + var manifestUpdater = new WorkloadManifestUpdater(_reporter, workloadResolver, nugetDownloader, Path.Combine(testDir, ".dotnet"), installationRepo, new MockPackWorkloadInstaller(dotnetRoot)); var offlineCacheDir = ""; if (useOfflineCache) @@ -310,7 +310,7 @@ public void ItCanFallbackWithNoUpdates(bool useOfflineCache) var workloadResolver = WorkloadResolver.CreateForTests(workloadManifestProvider, dotnetRoot); var nugetDownloader = new MockNuGetPackageDownloader(dotnetRoot); var installationRepo = new MockInstallationRecordRepository(); - var manifestUpdater = new WorkloadManifestUpdater(_reporter, workloadResolver, nugetDownloader, Path.Combine(testDir, ".dotnet"), testDir, installationRepo, new MockPackWorkloadInstaller()); + var manifestUpdater = new WorkloadManifestUpdater(_reporter, workloadResolver, nugetDownloader, Path.Combine(testDir, ".dotnet"), installationRepo, new MockPackWorkloadInstaller(dotnetRoot)); var offlineCacheDir = ""; if (useOfflineCache) @@ -377,7 +377,7 @@ public void GivenNoUpdatesAreAvailableAndNoRollbackItGivesAppropriateMessage(boo var workloadResolver = WorkloadResolver.CreateForTests(workloadManifestProvider, dotnetRoot); var nugetDownloader = new MockNuGetPackageDownloader(dotnetRoot); var installationRepo = new MockInstallationRecordRepository(); - var manifestUpdater = new WorkloadManifestUpdater(_reporter, workloadResolver, nugetDownloader, Path.Combine(testDir, ".dotnet"), testDir, installationRepo, new MockPackWorkloadInstaller()); + var manifestUpdater = new WorkloadManifestUpdater(_reporter, workloadResolver, nugetDownloader, Path.Combine(testDir, ".dotnet"), installationRepo, new MockPackWorkloadInstaller(dotnetRoot)); var offlineCacheDir = ""; if (useOfflineCache) @@ -440,7 +440,7 @@ public void GivenWorkloadManifestRollbackItCanCalculateUpdates() var nugetDownloader = new MockNuGetPackageDownloader(dotnetRoot); var workloadResolver = WorkloadResolver.CreateForTests(workloadManifestProvider, dotnetRoot); var installationRepo = new MockInstallationRecordRepository(); - var manifestUpdater = new WorkloadManifestUpdater(_reporter, workloadResolver, nugetDownloader, testDir, testDir, installationRepo, new MockPackWorkloadInstaller()); + var manifestUpdater = new WorkloadManifestUpdater(_reporter, workloadResolver, nugetDownloader, testDir, installationRepo, new MockPackWorkloadInstaller(dotnetRoot)); var manifestUpdates = manifestUpdater.CalculateManifestRollbacks(rollbackDefPath); manifestUpdates.Should().BeEquivalentTo(expectedManifestUpdates); @@ -483,7 +483,7 @@ public void GivenFromRollbackDefinitionItErrorsOnInstalledExtraneousManifestId() var nugetDownloader = new MockNuGetPackageDownloader(dotnetRoot); var workloadResolver = WorkloadResolver.CreateForTests(new MockManifestProvider(Array.Empty()), dotnetRoot); var installationRepo = new MockInstallationRecordRepository(); - var manifestUpdater = new WorkloadManifestUpdater(_reporter, workloadResolver, nugetDownloader, testDir, testDir, installationRepo, new MockPackWorkloadInstaller()); + var manifestUpdater = new WorkloadManifestUpdater(_reporter, workloadResolver, nugetDownloader, testDir, installationRepo, new MockPackWorkloadInstaller(dotnetRoot)); manifestUpdater.CalculateManifestRollbacks(rollbackDefPath); string.Join(" ", _reporter.Lines).Should().Contain(rollbackDefPath); @@ -525,7 +525,7 @@ public void GivenFromRollbackDefinitionItErrorsOnExtraneousManifestIdInRollbackD var nugetDownloader = new MockNuGetPackageDownloader(dotnetRoot); var workloadResolver = WorkloadResolver.CreateForTests(new MockManifestProvider(Array.Empty()), dotnetRoot); var installationRepo = new MockInstallationRecordRepository(); - var manifestUpdater = new WorkloadManifestUpdater(_reporter, workloadResolver, nugetDownloader, testDir, testDir, installationRepo, new MockPackWorkloadInstaller()); + var manifestUpdater = new WorkloadManifestUpdater(_reporter, workloadResolver, nugetDownloader, testDir, installationRepo, new MockPackWorkloadInstaller(dotnetRoot)); manifestUpdater.CalculateManifestRollbacks(rollbackDefPath); string.Join(" ", _reporter.Lines).Should().Contain(rollbackDefPath); @@ -556,8 +556,8 @@ public void GivenWorkloadManifestUpdateItChoosesHighestManifestVersionInCache() var nugetDownloader = new MockNuGetPackageDownloader(dotnetRoot); var workloadResolver = WorkloadResolver.CreateForTests(workloadManifestProvider, dotnetRoot); var installationRepo = new MockInstallationRecordRepository(); - var installer = new MockPackWorkloadInstaller(); - var manifestUpdater = new WorkloadManifestUpdater(_reporter, workloadResolver, nugetDownloader, testDir, testDir, installationRepo, installer); + var installer = new MockPackWorkloadInstaller(dotnetRoot); + var manifestUpdater = new WorkloadManifestUpdater(_reporter, workloadResolver, nugetDownloader, testDir, installationRepo, installer); manifestUpdater.UpdateAdvertisingManifestsAsync(false, new DirectoryPath(offlineCache)).Wait(); // We should have chosen the higher version manifest package to install/ extract @@ -709,7 +709,7 @@ public void TestSideBySideUpdateChecks() var workloadResolver = WorkloadResolver.CreateForTests(workloadManifestProvider, dotnetRoot); var nugetDownloader = new MockNuGetPackageDownloader(dotnetRoot); var installationRepo = new MockInstallationRecordRepository(); - var manifestUpdater = new WorkloadManifestUpdater(_reporter, workloadResolver, nugetDownloader, testDir, testDir, installationRepo, new MockPackWorkloadInstaller(), getEnvironmentVariable: getEnvironmentVariable); + var manifestUpdater = new WorkloadManifestUpdater(_reporter, workloadResolver, nugetDownloader, testDir, installationRepo, new MockPackWorkloadInstaller(dotnetRoot), getEnvironmentVariable: getEnvironmentVariable); var sentinelPath = Path.Combine(testDir, _manifestSentinelFileName + featureBand); return (manifestUpdater, nugetDownloader, sentinelPath); diff --git a/src/Tests/dotnet-workload-install.Tests/MockPackWorkloadInstaller.cs b/src/Tests/dotnet-workload-install.Tests/MockPackWorkloadInstaller.cs index f5863d1e98dd..61d6dc6f24d8 100644 --- a/src/Tests/dotnet-workload-install.Tests/MockPackWorkloadInstaller.cs +++ b/src/Tests/dotnet-workload-install.Tests/MockPackWorkloadInstaller.cs @@ -7,6 +7,7 @@ using Microsoft.Extensions.EnvironmentAbstractions; using Microsoft.NET.Sdk.WorkloadManifestReader; using Microsoft.DotNet.ToolPackage; +using Microsoft.DotNet.Workloads.Workload; namespace Microsoft.DotNet.Cli.Workload.Install.Tests { @@ -22,12 +23,13 @@ internal class MockPackWorkloadInstaller : IInstaller public bool FailingRollback; public bool FailingGarbageCollection; private readonly string FailingPack; + private readonly string _dotnetDir; public IWorkloadResolver WorkloadResolver { get; set; } public int ExitCode => 0; - public MockPackWorkloadInstaller(string failingWorkload = null, string failingPack = null, bool failingRollback = false, IList installedWorkloads = null, + public MockPackWorkloadInstaller(string dotnetDir, string failingWorkload = null, string failingPack = null, bool failingRollback = false, IList installedWorkloads = null, IList installedPacks = null, bool failingGarbageCollection = false) { InstallationRecordRepository = new MockInstallationRecordRepository(failingWorkload, installedWorkloads); @@ -35,6 +37,7 @@ public MockPackWorkloadInstaller(string failingWorkload = null, string failingPa InstalledPacks = installedPacks ?? new List(); FailingPack = failingPack; FailingGarbageCollection = failingGarbageCollection; + _dotnetDir = dotnetDir; } IEnumerable GetPacksForWorkloads(IEnumerable workloadIds) @@ -53,6 +56,11 @@ IEnumerable GetPacksForWorkloads(IEnumerable workloadIds) } } + public void UpdateInstallMode(SdkFeatureBand sdkFeatureBand, bool newMode) + { + throw new NotImplementedException(); + } + public void InstallWorkloads(IEnumerable workloadIds, SdkFeatureBand sdkFeatureBand, ITransactionContext transactionContext, DirectoryPath? offlineCache = null) { List packs = new List(); @@ -156,6 +164,26 @@ public void ReplaceWorkloadResolver(IWorkloadResolver workloadResolver) { WorkloadResolver = workloadResolver; } + + public void RemoveManifestsFromInstallState(SdkFeatureBand sdkFeatureBand) + { + string path = Path.Combine(WorkloadInstallType.GetInstallStateFolder(sdkFeatureBand, _dotnetDir), "default.json"); + if (File.Exists(path)) + { + var installStateContents = File.Exists(path) ? InstallStateContents.FromString(File.ReadAllText(path)) : new InstallStateContents(); + installStateContents.Manifests = null; + File.WriteAllText(path, installStateContents.ToString()); + } + } + + public void SaveInstallStateManifestVersions(SdkFeatureBand sdkFeatureBand, Dictionary manifestContents) + { + string path = Path.Combine(WorkloadInstallType.GetInstallStateFolder(sdkFeatureBand, _dotnetDir), "default.json"); + Directory.CreateDirectory(Path.GetDirectoryName(path)); + var installStateContents = File.Exists(path) ? InstallStateContents.FromString(File.ReadAllText(path)) : new InstallStateContents(); + installStateContents.Manifests = manifestContents; + File.WriteAllText(path, installStateContents.ToString()); + } } internal class MockInstallationRecordRepository : IWorkloadInstallationRecordRepository diff --git a/src/Tests/dotnet-workload-install.Tests/MockWorkloadManifestUpdater.cs b/src/Tests/dotnet-workload-install.Tests/MockWorkloadManifestUpdater.cs index d3e2bf8a3b6f..6a3d90073c64 100644 --- a/src/Tests/dotnet-workload-install.Tests/MockWorkloadManifestUpdater.cs +++ b/src/Tests/dotnet-workload-install.Tests/MockWorkloadManifestUpdater.cs @@ -12,16 +12,11 @@ internal class MockWorkloadManifestUpdater : IWorkloadManifestUpdater public int UpdateAdvertisingManifestsCallCount = 0; public int CalculateManifestUpdatesCallCount = 0; public int GetManifestPackageDownloadsCallCount = 0; - private IEnumerable<(ManifestVersionUpdate manifestUpdate, - Dictionary Workloads)> _manifestUpdates; - private string _tempDirManifestPath; + private readonly IEnumerable _manifestUpdates; - public MockWorkloadManifestUpdater(IEnumerable<(ManifestVersionUpdate manifestUpdate, - Dictionary Workloads)> manifestUpdates = null, string tempDirManifestPath = null) + public MockWorkloadManifestUpdater(IEnumerable manifestUpdates = null) { - _manifestUpdates = manifestUpdates ?? new List<(ManifestVersionUpdate manifestUpdate, - Dictionary Workloads)>(); - _tempDirManifestPath = tempDirManifestPath; + _manifestUpdates = manifestUpdates ?? new List(); } public Task UpdateAdvertisingManifestsAsync(bool includePreview, DirectoryPath? cachePath = null) @@ -30,9 +25,7 @@ public Task UpdateAdvertisingManifestsAsync(bool includePreview, DirectoryPath? return Task.CompletedTask; } - public IEnumerable<( - ManifestVersionUpdate manifestUpdate, - Dictionary Workloads)> CalculateManifestUpdates() + public IEnumerable CalculateManifestUpdates() { CalculateManifestUpdatesCallCount++; return _manifestUpdates; @@ -49,11 +42,11 @@ public Task> GetManifestPackageDownloadsAsync(bool public IEnumerable CalculateManifestRollbacks(string rollbackDefinitionFilePath) { - return _manifestUpdates.Select(t => t.manifestUpdate); + return _manifestUpdates.Select(t => t.ManifestUpdate); } - public Task BackgroundUpdateAdvertisingManifestsWhenRequiredAsync() => throw new System.NotImplementedException(); - public IEnumerable GetUpdatableWorkloadsToAdvertise(IEnumerable installedWorkloads) => throw new System.NotImplementedException(); + public Task BackgroundUpdateAdvertisingManifestsWhenRequiredAsync() => throw new NotImplementedException(); + public IEnumerable GetUpdatableWorkloadsToAdvertise(IEnumerable installedWorkloads) => throw new NotImplementedException(); public void DeleteUpdatableWorkloadsFile() { } } } diff --git a/src/Tests/dotnet-workload-list.Tests/GivenWorkloadInstallerAndWorkloadsInstalled.cs b/src/Tests/dotnet-workload-list.Tests/GivenWorkloadInstallerAndWorkloadsInstalled.cs index 98e467085595..29d942429114 100644 --- a/src/Tests/dotnet-workload-list.Tests/GivenWorkloadInstallerAndWorkloadsInstalled.cs +++ b/src/Tests/dotnet-workload-list.Tests/GivenWorkloadInstallerAndWorkloadsInstalled.cs @@ -6,6 +6,7 @@ using Microsoft.DotNet.Cli.NuGetPackageDownloader; using Microsoft.DotNet.Cli.Workload.Install.Tests; using Microsoft.DotNet.Workloads.Workload; +using Microsoft.DotNet.Workloads.Workload.Install; using Microsoft.DotNet.Workloads.Workload.Install.InstallRecord; using Microsoft.DotNet.Workloads.Workload.List; using Microsoft.NET.Sdk.WorkloadManifestReader; @@ -22,7 +23,7 @@ public class GivenInstalledWorkloadAndManifestUpdater : SdkTest private WorkloadListCommand _workloadListCommand; private string _testDirectory; - private List<(ManifestVersionUpdate manifestUpdate, Dictionary Workloads)> _mockManifestUpdates; + private List _mockManifestUpdates; private MockNuGetPackageDownloader _nugetDownloader; private string _dotnetRoot; @@ -40,14 +41,14 @@ private void Setup(string identifier) _mockManifestUpdates = new() { - ( + new( new ManifestVersionUpdate( new ManifestId("manifest1"), new ManifestVersion(CurrentSdkVersion), currentSdkFeatureBand.ToString(), new ManifestVersion(UpdateAvailableVersion), currentSdkFeatureBand.ToString()), - new Dictionary + new WorkloadCollection { [new WorkloadId(InstallingWorkload)] = new( new WorkloadId(InstallingWorkload), false, XamarinAndroidDescription, @@ -56,28 +57,28 @@ private void Setup(string identifier) new WorkloadId("other"), false, "other description", WorkloadDefinitionKind.Dev, null, null, null) }), - ( + new( new ManifestVersionUpdate( new ManifestId("manifest-other"), new ManifestVersion(CurrentSdkVersion), currentSdkFeatureBand.ToString(), new ManifestVersion("7.0.101"), currentSdkFeatureBand.ToString()), - new Dictionary + new WorkloadCollection { [new WorkloadId("other-manifest-workload")] = new( new WorkloadId("other-manifest-workload"), false, "other-manifest-workload description", WorkloadDefinitionKind.Dev, null, null, null) }), - ( + new( new ManifestVersionUpdate( new ManifestId("manifest-older-version"), new ManifestVersion(CurrentSdkVersion), currentSdkFeatureBand.ToString(), new ManifestVersion("6.0.100"), currentSdkFeatureBand.ToString()), - new Dictionary + new WorkloadCollection { [new WorkloadId("other-manifest-workload")] = new( new WorkloadId("other-manifest-workload"), false, @@ -107,7 +108,7 @@ public void ItShouldGetAvailableUpdate() { Setup(nameof(ItShouldGetAvailableUpdate)); WorkloadListCommand.UpdateAvailableEntry[] result = - _workloadListCommand.GetUpdateAvailable(new List {new("xamarin-android")}); + _workloadListCommand.GetUpdateAvailable(new List { new("xamarin-android") }).ToArray(); result.Should().NotBeEmpty(); result[0].WorkloadId.Should().Be(InstallingWorkload, "Only should installed workload"); diff --git a/src/Tests/dotnet-workload-update.Tests/GivenDotnetWorkloadUpdate.cs b/src/Tests/dotnet-workload-update.Tests/GivenDotnetWorkloadUpdate.cs index f9d4738eed6f..896569702333 100644 --- a/src/Tests/dotnet-workload-update.Tests/GivenDotnetWorkloadUpdate.cs +++ b/src/Tests/dotnet-workload-update.Tests/GivenDotnetWorkloadUpdate.cs @@ -214,7 +214,7 @@ public void GivenWorkloadUpdateItCanDownloadToOfflineCache() var mockWorkloadIds = new WorkloadId[] { new WorkloadId("xamarin-android") }; var cachePath = Path.Combine(_testAssetsManager.CreateTestDirectory(identifier: "cachePath").Path, "mockCachePath"); var parseResult = Parser.Instance.Parse(new string[] { "dotnet", "workload", "update", "--download-to-cache", cachePath }); - (_, var command, var installer, _, var manifestUpdater, var packageDownloader) = GetTestInstallers(parseResult, installedWorkloads: mockWorkloadIds, includeInstalledPacks: true, installedFeatureBand: "6.0.100"); + (_, var command, _, _, var manifestUpdater, var packageDownloader) = GetTestInstallers(parseResult, installedWorkloads: mockWorkloadIds, includeInstalledPacks: true, installedFeatureBand: "6.0.100"); command.Execute(); @@ -326,54 +326,46 @@ public void ApplyRollbackAcrossFeatureBand(string existingSdkFeatureBand, string var parseResult = Parser.Instance.Parse(new string[] { "dotnet", "workload", "update", "--from-rollback-file", "rollback.json" }); var manifestsToUpdate = - new (ManifestVersionUpdate manifestUpdate, - Dictionary Workloads)[] + new ManifestUpdateWithWorkloads[] { - (new ManifestVersionUpdate(new ManifestId("mock-manifest"), new ManifestVersion("1.0.0"), existingSdkFeatureBand, new ManifestVersion("2.0.0"), newSdkFeatureBand), - null), + new(new ManifestVersionUpdate(new ManifestId("mock-manifest"), new ManifestVersion("1.0.0"), existingSdkFeatureBand, new ManifestVersion("2.0.0"), newSdkFeatureBand), null), }; - - (var dotnetPath, var updateCommand, var packInstaller, var workloadResolver, var workloadManifestUpdater, var nuGetPackageDownloader) = GetTestInstallers(parseResult, manifestUpdates: manifestsToUpdate, sdkVersion: "6.0.300", identifier: existingSdkFeatureBand + newSdkFeatureBand, installedFeatureBand: existingSdkFeatureBand); + (var dotnetPath, var updateCommand, var packInstaller, _, _, _) = GetTestInstallers(parseResult, manifestUpdates: manifestsToUpdate, sdkVersion: "6.0.300", identifier: existingSdkFeatureBand + newSdkFeatureBand, installedFeatureBand: existingSdkFeatureBand); updateCommand.UpdateWorkloads(); - packInstaller.InstalledManifests[0].manifestUpdate.ManifestId.Should().Be(manifestsToUpdate[0].manifestUpdate.ManifestId); - packInstaller.InstalledManifests[0].manifestUpdate.NewVersion.Should().Be(manifestsToUpdate[0].manifestUpdate.NewVersion); - packInstaller.InstalledManifests[0].manifestUpdate.NewFeatureBand.Should().Be(manifestsToUpdate[0].manifestUpdate.NewFeatureBand); - packInstaller.InstalledManifests[0].manifestUpdate.ExistingVersion.Should().Be(manifestsToUpdate[0].manifestUpdate.ExistingVersion); - packInstaller.InstalledManifests[0].manifestUpdate.ExistingFeatureBand.Should().Be(manifestsToUpdate[0].manifestUpdate.ExistingFeatureBand); + packInstaller.InstalledManifests[0].manifestUpdate.ManifestId.Should().Be(manifestsToUpdate[0].ManifestUpdate.ManifestId); + packInstaller.InstalledManifests[0].manifestUpdate.NewVersion.Should().Be(manifestsToUpdate[0].ManifestUpdate.NewVersion); + packInstaller.InstalledManifests[0].manifestUpdate.NewFeatureBand.Should().Be(manifestsToUpdate[0].ManifestUpdate.NewFeatureBand); + packInstaller.InstalledManifests[0].manifestUpdate.ExistingVersion.Should().Be(manifestsToUpdate[0].ManifestUpdate.ExistingVersion); + packInstaller.InstalledManifests[0].manifestUpdate.ExistingFeatureBand.Should().Be(manifestsToUpdate[0].ManifestUpdate.ExistingFeatureBand); packInstaller.InstalledManifests[0].offlineCache.Should().Be(null); - var defaultJsonPath = Path.Combine(dotnetPath, "dotnet", "metadata", "workloads", "6.0.300", "InstallState", "default.json"); + var defaultJsonPath = Path.Combine(dotnetPath, "metadata", "workloads", "6.0.300", "InstallState", "default.json"); File.Exists(defaultJsonPath).Should().BeTrue(); var json = JsonDocument.Parse(new FileStream(defaultJsonPath, FileMode.Open, FileAccess.Read), new JsonDocumentOptions() { AllowTrailingCommas = true, CommentHandling = JsonCommentHandling.Skip }); json.RootElement.Should().NotBeNull(); json.RootElement.GetProperty("manifests").GetProperty("mock-manifest").GetString().Should().Be("2.0.0/" + newSdkFeatureBand); } - [Fact] + [Fact] public void ApplyRollbackWithMultipleManifestsAcrossFeatureBand() { var parseResult = Parser.Instance.Parse(new string[] { "dotnet", "workload", "update", "--from-rollback-file", "rollback.json" }); var manifestsToUpdate = - new (ManifestVersionUpdate manifestUpdate, - Dictionary Workloads)[] + new ManifestUpdateWithWorkloads[] { - (new ManifestVersionUpdate(new ManifestId("mock-manifest-1"), new ManifestVersion("1.0.0"), "6.0.300", new ManifestVersion("2.0.0"), "6.0.100"), - null), - (new ManifestVersionUpdate(new ManifestId("mock-manifest-2"), new ManifestVersion("1.0.0"), "6.0.100", new ManifestVersion("2.0.0"), "6.0.300"), - null), - (new ManifestVersionUpdate(new ManifestId("mock-manifest-3"), new ManifestVersion("1.0.0"), "5.0.100", new ManifestVersion("2.0.0"), "6.0.100"), - null), + new(new ManifestVersionUpdate(new ManifestId("mock-manifest-1"), new ManifestVersion("1.0.0"), "6.0.300", new ManifestVersion("2.0.0"), "6.0.100"), null), + new(new ManifestVersionUpdate(new ManifestId("mock-manifest-2"), new ManifestVersion("1.0.0"), "6.0.100", new ManifestVersion("2.0.0"), "6.0.300"), null), + new(new ManifestVersionUpdate(new ManifestId("mock-manifest-3"), new ManifestVersion("1.0.0"), "5.0.100", new ManifestVersion("2.0.0"), "6.0.100"), null), }; - - (_, var updateCommand, var packInstaller, var workloadResolver, var workloadManifestUpdater, var nuGetPackageDownloader) = GetTestInstallers(parseResult, manifestUpdates: manifestsToUpdate, sdkVersion: "6.0.300", installedFeatureBand: "6.0.300"); + (_, var updateCommand, var packInstaller, _, _, _) = GetTestInstallers(parseResult, manifestUpdates: manifestsToUpdate, sdkVersion: "6.0.300", installedFeatureBand: "6.0.300"); updateCommand.UpdateWorkloads(); - packInstaller.InstalledManifests[0].manifestUpdate.ManifestId.Should().Be(manifestsToUpdate[0].manifestUpdate.ManifestId); - packInstaller.InstalledManifests[0].manifestUpdate.NewVersion.Should().Be(manifestsToUpdate[0].manifestUpdate.NewVersion); + packInstaller.InstalledManifests[0].manifestUpdate.ManifestId.Should().Be(manifestsToUpdate[0].ManifestUpdate.ManifestId); + packInstaller.InstalledManifests[0].manifestUpdate.NewVersion.Should().Be(manifestsToUpdate[0].ManifestUpdate.NewVersion); packInstaller.InstalledManifests[0].manifestUpdate.NewFeatureBand.Should().Be("6.0.100"); packInstaller.InstalledManifests[1].manifestUpdate.NewFeatureBand.Should().Be("6.0.300"); packInstaller.InstalledManifests[2].manifestUpdate.NewFeatureBand.Should().Be("6.0.100"); @@ -418,7 +410,7 @@ public void GivenInvalidVersionInRollbackFileItErrors() [CallerMemberName] string testName = "", string failingWorkload = null, string failingPack = null, - IEnumerable<(ManifestVersionUpdate manifestUpdate, Dictionary Workloads)> manifestUpdates = null, + IEnumerable manifestUpdates = null, IList installedWorkloads = null, bool includeInstalledPacks = false, string sdkVersion = "6.0.100", @@ -433,8 +425,8 @@ public void GivenInvalidVersionInRollbackFileItErrors() CreatePackInfo("Xamarin.Android.Framework", "8.2.0", WorkloadPackKind.Framework, Path.Combine(dotnetRoot, "packs", "Xamarin.Android.Framework", "8.2.0"), "Xamarin.Android.Framework") }; var installer = includeInstalledPacks ? - new MockPackWorkloadInstaller(failingWorkload, failingPack, installedWorkloads: installedWorkloads, installedPacks: installedPacks) : - new MockPackWorkloadInstaller(failingWorkload, failingPack, installedWorkloads: installedWorkloads); + new MockPackWorkloadInstaller(dotnetRoot, failingWorkload, failingPack, installedWorkloads: installedWorkloads, installedPacks: installedPacks) : + new MockPackWorkloadInstaller(dotnetRoot, failingWorkload, failingPack, installedWorkloads: installedWorkloads); var copiedManifestFolder = Path.Combine(dotnetRoot, "sdk-manifests", new SdkFeatureBand(sdkVersion).ToString(), "SampleManifest"); Directory.CreateDirectory(copiedManifestFolder); @@ -444,7 +436,7 @@ public void GivenInvalidVersionInRollbackFileItErrors() var workloadResolver = WorkloadResolver.CreateForTests(new MockManifestProvider(new[] { copiedManifestFile }), dotnetRoot); installer.WorkloadResolver = workloadResolver; var nugetDownloader = new MockNuGetPackageDownloader(dotnetRoot); - var manifestUpdater = new MockWorkloadManifestUpdater(manifestUpdates, _manifestPath); + var manifestUpdater = new MockWorkloadManifestUpdater(manifestUpdates); var workloadResolverFactory = new MockWorkloadResolverFactory(dotnetRoot, sdkVersion, workloadResolver, userProfileDir: testDirectory); @@ -456,7 +448,7 @@ public void GivenInvalidVersionInRollbackFileItErrors() nugetPackageDownloader: nugetDownloader, workloadManifestUpdater: manifestUpdater); - return (testDirectory, installManager, installer, workloadResolver, manifestUpdater, nugetDownloader); + return (dotnetRoot, installManager, installer, workloadResolver, manifestUpdater, nugetDownloader); } } } diff --git a/src/Tests/dotnet.Tests/CommandTests/CompleteCommandTests.cs b/src/Tests/dotnet.Tests/CommandTests/CompleteCommandTests.cs index b4b2160fb370..a895374c1802 100644 --- a/src/Tests/dotnet.Tests/CommandTests/CompleteCommandTests.cs +++ b/src/Tests/dotnet.Tests/CommandTests/CompleteCommandTests.cs @@ -39,6 +39,7 @@ public void GivenOnlyDotnetItSuggestsTopLevelCommandsAndOptions() "new", "nuget", "pack", + "package", "publish", "remove", "restore", diff --git a/src/Tests/dotnet.Tests/CommandTests/ToolInstallGlobalOrToolPathCommandTests.cs b/src/Tests/dotnet.Tests/CommandTests/ToolInstallGlobalOrToolPathCommandTests.cs index e0c4e247d4fb..0d15084cb1f3 100644 --- a/src/Tests/dotnet.Tests/CommandTests/ToolInstallGlobalOrToolPathCommandTests.cs +++ b/src/Tests/dotnet.Tests/CommandTests/ToolInstallGlobalOrToolPathCommandTests.cs @@ -1,21 +1,23 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.CommandLine; +using System.Text.Json; +using Microsoft.DotNet.Cli.NuGetPackageDownloader; +using Microsoft.DotNet.Cli.ToolPackage; using Microsoft.DotNet.Cli.Utils; -using Microsoft.DotNet.Tools; +using Microsoft.DotNet.ShellShim; using Microsoft.DotNet.ToolPackage; -using Microsoft.DotNet.Tools.Tool.Install; +using Microsoft.DotNet.Tools; using Microsoft.DotNet.Tools.Tests.ComponentMocks; -using Microsoft.DotNet.ShellShim; +using Microsoft.DotNet.Tools.Tool.Install; +using Microsoft.DotNet.Tools.Tool.Update; using Microsoft.Extensions.DependencyModel.Tests; using Microsoft.Extensions.EnvironmentAbstractions; +using CreateShellShimRepository = Microsoft.DotNet.Tools.Tool.Install.CreateShellShimRepository; using LocalizableStrings = Microsoft.DotNet.Tools.Tool.Install.LocalizableStrings; -using System.Text.Json; -using System.CommandLine; using Parser = Microsoft.DotNet.Cli.Parser; -using Microsoft.DotNet.Cli.NuGetPackageDownloader; -using Microsoft.NET.TestFramework; -using Microsoft.DotNet.Cli.ToolPackage; + namespace Microsoft.DotNet.Tests.Commands.Tool { @@ -24,8 +26,10 @@ public class ToolInstallGlobalOrToolPathCommandTests: SdkTest private readonly IFileSystem _fileSystem; private readonly IToolPackageStore _toolPackageStore; private readonly IToolPackageStoreQuery _toolPackageStoreQuery; + private readonly ToolPackageUninstallerMock _toolPackageUninstallerMock; private readonly CreateShellShimRepository _createShellShimRepository; - private readonly CreateToolPackageStoresAndDownloader _createToolPackageStoresAndDownloader; + private readonly CreateToolPackageStoresAndDownloaderAndUninstaller _createToolPackageStoreDownloaderUninstaller; + private readonly string _toolsDirectory; private readonly EnvironmentPathInstructionMock _environmentPathInstructionMock; private readonly ParseResult _parseResult; private readonly BufferedReporter _reporter; @@ -34,6 +38,8 @@ public class ToolInstallGlobalOrToolPathCommandTests: SdkTest private readonly string _pathToPlacePackages; private const string PackageId = "global.tool.console.demo"; private const string PackageVersion = "1.0.4"; + private const string HigherPackageVersion = "2.0.0"; + private const string LowerPackageVersion = "1.0.0"; private const string ToolCommandName = "SimulatorCommand"; private readonly string UnlistedPackageId = "elemental.sysinfotool"; @@ -42,6 +48,7 @@ public ToolInstallGlobalOrToolPathCommandTests(ITestOutputHelper log): base(log) _reporter = new BufferedReporter(); _fileSystem = new FileSystemMockBuilder().UseCurrentSystemTemporaryDirectory().Build(); _temporaryDirectory = _fileSystem.Directory.CreateTemporaryDirectory().DirectoryPath; + _toolsDirectory = Path.Combine(_temporaryDirectory, "tools"); _pathToPlaceShim = Path.Combine(_temporaryDirectory, "pathToPlace"); _fileSystem.Directory.CreateDirectory(_pathToPlaceShim); _pathToPlacePackages = _pathToPlaceShim + "Packages"; @@ -57,10 +64,14 @@ public ToolInstallGlobalOrToolPathCommandTests(ITestOutputHelper log): base(log) filePermissionSetter: new NoOpFilePermissionSetter()); _environmentPathInstructionMock = new EnvironmentPathInstructionMock(_reporter, _pathToPlaceShim); - _createToolPackageStoresAndDownloader = (location, forwardArguments) => (_toolPackageStore, _toolPackageStoreQuery, CreateToolPackageDownloader()); + var store = new ToolPackageStoreMock( + new DirectoryPath(_toolsDirectory), + _fileSystem); + _toolPackageUninstallerMock = new ToolPackageUninstallerMock(_fileSystem, store); + _createToolPackageStoreDownloaderUninstaller = (location, forwardArguments) => (_toolPackageStore, _toolPackageStoreQuery, CreateToolPackageDownloader(), _toolPackageUninstallerMock); - _parseResult = Parser.Instance.Parse($"dotnet tool install -g {PackageId}"); + _parseResult = Parser.Instance.Parse($"dotnet tool install -g {PackageId} --verbosity minimal"); } [Fact] @@ -68,7 +79,7 @@ public void WhenRunWithPackageIdItShouldCreateValidShim() { var toolInstallGlobalOrToolPathCommand = new ToolInstallGlobalOrToolPathCommand( _parseResult, - _createToolPackageStoresAndDownloader, + _createToolPackageStoreDownloaderUninstaller, _createShellShimRepository, _environmentPathInstructionMock, _reporter); @@ -88,7 +99,7 @@ public void WhenRunFromToolInstallRedirectCommandWithPackageIdItShouldCreateVali { var toolInstallGlobalOrToolPathCommand = new ToolInstallGlobalOrToolPathCommand( _parseResult, - _createToolPackageStoresAndDownloader, + _createToolPackageStoreDownloaderUninstaller, _createShellShimRepository, _environmentPathInstructionMock, _reporter); @@ -128,7 +139,7 @@ public void WhenRunWithPackageIdWithSourceItShouldCreateValidShim() var toolInstallGlobalOrToolPathCommand = new ToolInstallGlobalOrToolPathCommand( result, - (location, forwardArguments) => (_toolPackageStore, _toolPackageStoreQuery, toolToolPackageDownloader), + (location, forwardArguments) => (_toolPackageStore, _toolPackageStoreQuery, toolToolPackageDownloader, _toolPackageUninstallerMock), _createShellShimRepository, _environmentPathInstructionMock, _reporter); @@ -149,7 +160,7 @@ public void WhenRunWithPackageIdItShouldShowPathInstruction() { var toolInstallGlobalOrToolPathCommand = new ToolInstallGlobalOrToolPathCommand( _parseResult, - _createToolPackageStoresAndDownloader, + _createToolPackageStoreDownloaderUninstaller, _createShellShimRepository, _environmentPathInstructionMock, _reporter); @@ -175,7 +186,7 @@ public void WhenRunWithPackageIdPackageFormatIsNotFullySupportedItShouldShowPath var toolInstallGlobalOrToolPathCommand = new ToolInstallGlobalOrToolPathCommand( _parseResult, - (location, forwardArguments) => (_toolPackageStore, _toolPackageStoreQuery, toolPackageDownloader), + (location, forwardArguments) => (_toolPackageStore, _toolPackageStoreQuery, toolPackageDownloader, _toolPackageUninstallerMock), _createShellShimRepository, _environmentPathInstructionMock, _reporter); @@ -197,7 +208,7 @@ public void GivenFailedPackageInstallWhenRunWithPackageIdItShouldFail() var toolInstallGlobalOrToolPathCommand = new ToolInstallGlobalOrToolPathCommand( _parseResult, - (location, forwardArguments) => (_toolPackageStore, _toolPackageStoreQuery, toolPackageDownloader), + (location, forwardArguments) => (_toolPackageStore, _toolPackageStoreQuery, toolPackageDownloader, _toolPackageUninstallerMock), _createShellShimRepository, _environmentPathInstructionMock, _reporter); @@ -220,7 +231,7 @@ public void GivenCreateShimItShouldHaveNoBrokenFolderOnDisk() var toolInstallGlobalOrToolPathCommand = new ToolInstallGlobalOrToolPathCommand( _parseResult, - _createToolPackageStoresAndDownloader, + _createToolPackageStoreDownloaderUninstaller, _createShellShimRepository, _environmentPathInstructionMock, _reporter); @@ -244,7 +255,7 @@ public void GivenInCorrectToolConfigurationWhenRunWithPackageIdItShouldFail() var toolInstallGlobalOrToolPathCommand = new ToolInstallGlobalOrToolPathCommand( _parseResult, - (location, forwardArguments) => (_toolPackageStore, _toolPackageStoreQuery, toolPackageDownloader), + (location, forwardArguments) => (_toolPackageStore, _toolPackageStoreQuery, toolPackageDownloader, _toolPackageUninstallerMock), _createShellShimRepository, _environmentPathInstructionMock, _reporter); @@ -265,7 +276,7 @@ public void WhenRunWithPackageIdItShouldShowSuccessMessage() { var toolInstallGlobalOrToolPathCommand = new ToolInstallGlobalOrToolPathCommand( _parseResult, - _createToolPackageStoresAndDownloader, + _createToolPackageStoreDownloaderUninstaller, _createShellShimRepository, new EnvironmentPathInstructionMock(_reporter, _pathToPlaceShim, true), _reporter); @@ -282,6 +293,29 @@ public void WhenRunWithPackageIdItShouldShowSuccessMessage() PackageVersion).Green()); } + [Fact] + public void WhenRunWithPackageIdWithQuietItShouldShowNoSuccessMessage() + { + var parseResultQuiet = Parser.Instance.Parse($"dotnet tool install -g {PackageId} --verbosity quiet"); + var toolInstallGlobalOrToolPathCommand = new ToolInstallGlobalOrToolPathCommand( + parseResultQuiet, + _createToolPackageStoreDownloaderUninstaller, + _createShellShimRepository, + new EnvironmentPathInstructionMock(_reporter, _pathToPlaceShim, true), + _reporter); + + toolInstallGlobalOrToolPathCommand.Execute().Should().Be(0); + + _reporter + .Lines + .Should() + .NotContain(string.Format( + LocalizableStrings.InstallationSucceeded, + ToolCommandName, + PackageId, + PackageVersion).Green()); + } + [Fact] public void WhenRunWithInvalidVersionItShouldThrow() { @@ -290,7 +324,7 @@ public void WhenRunWithInvalidVersionItShouldThrow() var toolInstallGlobalOrToolPathCommand = new ToolInstallGlobalOrToolPathCommand( result, - _createToolPackageStoresAndDownloader, + _createToolPackageStoreDownloaderUninstaller, _createShellShimRepository, new EnvironmentPathInstructionMock(_reporter, _pathToPlaceShim, true), _reporter); @@ -307,11 +341,11 @@ public void WhenRunWithInvalidVersionItShouldThrow() [Fact] public void WhenRunWithExactVersionItShouldSucceed() { - ParseResult result = Parser.Instance.Parse($"dotnet tool install -g {PackageId} --version {PackageVersion}"); + ParseResult result = Parser.Instance.Parse($"dotnet tool install -g {PackageId} --version {PackageVersion} --verbosity minimal"); var toolInstallGlobalOrToolPathCommand = new ToolInstallGlobalOrToolPathCommand( result, - _createToolPackageStoresAndDownloader, + _createToolPackageStoreDownloaderUninstaller, _createShellShimRepository, new EnvironmentPathInstructionMock(_reporter, _pathToPlaceShim, true), _reporter); @@ -328,14 +362,179 @@ public void WhenRunWithExactVersionItShouldSucceed() PackageVersion).Green()); } + [Fact] + public void WhenInstallTheSameVersionTwiceItShouldSucceed() + { + ParseResult result = Parser.Instance.Parse($"dotnet tool install -g {PackageId} --version {PackageVersion} --verbosity minimal"); + + var toolInstallGlobalOrToolPathCommand = new ToolInstallGlobalOrToolPathCommand( + result, + _createToolPackageStoreDownloaderUninstaller, + _createShellShimRepository, + new EnvironmentPathInstructionMock(_reporter, _pathToPlaceShim, true), + _reporter); + + toolInstallGlobalOrToolPathCommand.Execute().Should().Be(0); + + _reporter + .Lines + .Should() + .Equal(string.Format( + LocalizableStrings.InstallationSucceeded, + ToolCommandName, + PackageId, + PackageVersion).Green()); + _reporter.Clear(); + + toolInstallGlobalOrToolPathCommand.Execute().Should().Be(0); + + _reporter + .Lines + .Should() + .Equal(string.Format( + Microsoft.DotNet.Tools.Tool.Update.LocalizableStrings.UpdateSucceededStableVersionNoChange, + PackageId, + PackageVersion).Green()); + } + + [Fact] + public void WhenInstallWithHigherVersionItShouldUpdate() + { + IToolPackageDownloader toolToolPackageDownloader = GetToolPackageDownloaderWithHigherVersionInFeed(); + ParseResult result = Parser.Instance.Parse($"dotnet tool install -g {PackageId} --version {PackageVersion} --verbosity minimal"); + + var toolInstallGlobalOrToolPathCommand = new ToolInstallGlobalOrToolPathCommand( + result, + (location, forwardArguments) => (_toolPackageStore, _toolPackageStoreQuery, toolToolPackageDownloader, _toolPackageUninstallerMock), + _createShellShimRepository, + new EnvironmentPathInstructionMock(_reporter, _pathToPlaceShim, true), + _reporter); + + toolInstallGlobalOrToolPathCommand.Execute().Should().Be(0); + + _reporter + .Lines + .Should() + .Equal(string.Format( + LocalizableStrings.InstallationSucceeded, + ToolCommandName, + PackageId, + PackageVersion).Green()); + _reporter.Clear(); + + ParseResult result2 = Parser.Instance.Parse($"dotnet tool install -g {PackageId} --version {HigherPackageVersion} --verbosity minimal"); + + var toolInstallGlobalOrToolPathCommand2 = new ToolInstallGlobalOrToolPathCommand( + result2, + (location, forwardArguments) => (_toolPackageStore, _toolPackageStoreQuery, toolToolPackageDownloader, _toolPackageUninstallerMock), + _createShellShimRepository, + new EnvironmentPathInstructionMock(_reporter, _pathToPlaceShim, true), + _reporter); + + toolInstallGlobalOrToolPathCommand2.Execute().Should().Be(0); + + _reporter + .Lines + .Should() + .Equal(string.Format( + Microsoft.DotNet.Tools.Tool.Update.LocalizableStrings.UpdateSucceeded, + PackageId, + PackageVersion, + HigherPackageVersion).Green()); + } + + [Fact] + public void WhenInstallWithLowerVersionWithAllowDowngradeOptionItShouldDowngrade() + { + IToolPackageDownloader toolToolPackageDownloader = GetToolPackageDownloaderWithLowerVersionInFeed(); + ParseResult result = Parser.Instance.Parse($"dotnet tool install -g {PackageId} --version {PackageVersion} --verbosity minimal"); + + var toolInstallGlobalOrToolPathCommand = new ToolInstallGlobalOrToolPathCommand( + result, + (location, forwardArguments) => (_toolPackageStore, _toolPackageStoreQuery, toolToolPackageDownloader, _toolPackageUninstallerMock), + _createShellShimRepository, + new EnvironmentPathInstructionMock(_reporter, _pathToPlaceShim, true), + _reporter); + + toolInstallGlobalOrToolPathCommand.Execute().Should().Be(0); + + _reporter + .Lines + .Should() + .Equal(string.Format( + LocalizableStrings.InstallationSucceeded, + ToolCommandName, + PackageId, + PackageVersion).Green()); + _reporter.Clear(); + + ParseResult result2 = Parser.Instance.Parse($"dotnet tool install -g {PackageId} --version {LowerPackageVersion} --verbosity minimal --allow-downgrade"); + + var toolInstallGlobalOrToolPathCommand2 = new ToolInstallGlobalOrToolPathCommand( + result2, + (location, forwardArguments) => (_toolPackageStore, _toolPackageStoreQuery, toolToolPackageDownloader, _toolPackageUninstallerMock), + _createShellShimRepository, + new EnvironmentPathInstructionMock(_reporter, _pathToPlaceShim, true), + _reporter); + + toolInstallGlobalOrToolPathCommand2.Execute().Should().Be(0); + + _reporter + .Lines + .Should() + .Equal(string.Format( + Microsoft.DotNet.Tools.Tool.Update.LocalizableStrings.UpdateSucceeded, + PackageId, + PackageVersion, + LowerPackageVersion).Green()); + } + + [Fact] + public void WhenInstallWithLowerVersionItShouldFail() + { + IToolPackageDownloader toolToolPackageDownloader = GetToolPackageDownloaderWithLowerVersionInFeed(); + ParseResult result = Parser.Instance.Parse($"dotnet tool install -g {PackageId} --version {PackageVersion} --verbosity minimal"); + + var toolInstallGlobalOrToolPathCommand = new ToolInstallGlobalOrToolPathCommand( + result, + (location, forwardArguments) => (_toolPackageStore, _toolPackageStoreQuery, toolToolPackageDownloader, _toolPackageUninstallerMock), + _createShellShimRepository, + new EnvironmentPathInstructionMock(_reporter, _pathToPlaceShim, true), + _reporter); + + toolInstallGlobalOrToolPathCommand.Execute().Should().Be(0); + + _reporter + .Lines + .Should() + .Equal(string.Format( + LocalizableStrings.InstallationSucceeded, + ToolCommandName, + PackageId, + PackageVersion).Green()); + _reporter.Clear(); + + ParseResult result2 = Parser.Instance.Parse($"dotnet tool install -g {PackageId} --version {LowerPackageVersion} --verbosity minimal"); + + var toolInstallGlobalOrToolPathCommand2 = new ToolInstallGlobalOrToolPathCommand( + result2, + (location, forwardArguments) => (_toolPackageStore, _toolPackageStoreQuery, toolToolPackageDownloader, _toolPackageUninstallerMock), + _createShellShimRepository, + new EnvironmentPathInstructionMock(_reporter, _pathToPlaceShim, true), + _reporter); + + Action a = () => toolInstallGlobalOrToolPathCommand2.Execute(); + a.Should().Throw(); + } + [Fact] public void WhenRunWithValidVersionRangeItShouldSucceed() { - ParseResult result = Parser.Instance.Parse($"dotnet tool install -g {PackageId} --version [1.0,2.0]"); + ParseResult result = Parser.Instance.Parse($"dotnet tool install -g {PackageId} --version [1.0,2.0] --verbosity minimal"); var toolInstallGlobalOrToolPathCommand = new ToolInstallGlobalOrToolPathCommand( result, - _createToolPackageStoresAndDownloader, + _createToolPackageStoreDownloaderUninstaller, _createShellShimRepository, new EnvironmentPathInstructionMock(_reporter, _pathToPlaceShim, true), _reporter); @@ -404,11 +603,11 @@ public void WhenRunWithPrereleaseItShouldSucceed() { IToolPackageDownloader toolToolPackageDownloader = GetToolToolPackageDownloaderWithPreviewInFeed(); - ParseResult result = Parser.Instance.Parse($"dotnet tool install -g {PackageId} --prerelease"); + ParseResult result = Parser.Instance.Parse($"dotnet tool install -g {PackageId} --prerelease --verbosity minimal"); var toolInstallGlobalOrToolPathCommand = new ToolInstallGlobalOrToolPathCommand( result, - (location, forwardArguments) => (_toolPackageStore, _toolPackageStoreQuery, toolToolPackageDownloader), + (location, forwardArguments) => (_toolPackageStore, _toolPackageStoreQuery, toolToolPackageDownloader, _toolPackageUninstallerMock), _createShellShimRepository, _environmentPathInstructionMock, _reporter); @@ -434,7 +633,7 @@ public void WhenRunWithPrereleaseAndPackageVersionItShouldThrow() var toolInstallGlobalOrToolPathCommand = new ToolInstallGlobalOrToolPathCommand( result, - (location, forwardArguments) => (_toolPackageStore, _toolPackageStoreQuery, toolToolPackageDownloader), + (location, forwardArguments) => (_toolPackageStore, _toolPackageStoreQuery, toolToolPackageDownloader, _toolPackageUninstallerMock), _createShellShimRepository, _environmentPathInstructionMock, _reporter); @@ -471,6 +670,62 @@ private IToolPackageDownloader GetToolToolPackageDownloaderWithPreviewInFeed() return toolToolPackageDownloader; } + private IToolPackageDownloader GetToolPackageDownloaderWithLowerVersionInFeed() + { + var toolToolPackageDownloader = CreateToolPackageDownloader( + feeds: new List + { + new MockFeed + { + Type = MockFeedType.ImplicitAdditionalFeed, + Packages = new List + { + new MockFeedPackage + { + PackageId = PackageId, + Version = "1.0.4", + ToolCommandName = "SimulatorCommand" + }, + new MockFeedPackage + { + PackageId = PackageId, + Version = "1.0.0", + ToolCommandName = "SimulatorCommand" + } + } + } + }); + return toolToolPackageDownloader; + } + + private IToolPackageDownloader GetToolPackageDownloaderWithHigherVersionInFeed() + { + var toolToolPackageDownloader = CreateToolPackageDownloader( + feeds: new List + { + new MockFeed + { + Type = MockFeedType.ImplicitAdditionalFeed, + Packages = new List + { + new MockFeedPackage + { + PackageId = PackageId, + Version = "1.0.4", + ToolCommandName = "SimulatorCommand" + }, + new MockFeedPackage + { + PackageId = PackageId, + Version = "2.0.0", + ToolCommandName = "SimulatorCommand" + } + } + } + }); + return toolToolPackageDownloader; + } + [Fact] public void WhenRunWithoutAMatchingRangeItShouldFail() { @@ -478,7 +733,7 @@ public void WhenRunWithoutAMatchingRangeItShouldFail() var toolInstallGlobalOrToolPathCommand = new ToolInstallGlobalOrToolPathCommand( result, - _createToolPackageStoresAndDownloader, + _createToolPackageStoreDownloaderUninstaller, _createShellShimRepository, new EnvironmentPathInstructionMock(_reporter, _pathToPlaceShim, true), _reporter); @@ -496,11 +751,11 @@ public void WhenRunWithoutAMatchingRangeItShouldFail() [Fact] public void WhenRunWithValidVersionWildcardItShouldSucceed() { - ParseResult result = Parser.Instance.Parse($"dotnet tool install -g {PackageId} --version 1.0.*"); + ParseResult result = Parser.Instance.Parse($"dotnet tool install -g {PackageId} --version 1.0.* --verbosity minimal"); var toolInstallGlobalOrToolPathCommand = new ToolInstallGlobalOrToolPathCommand( result, - _createToolPackageStoresAndDownloader, + _createToolPackageStoreDownloaderUninstaller, _createShellShimRepository, new EnvironmentPathInstructionMock(_reporter, _pathToPlaceShim, true), _reporter); @@ -524,7 +779,7 @@ public void WhenRunWithPackageIdAndBinPathItShouldNoteHaveEnvironmentPathInstruc var toolInstallGlobalOrToolPathCommand = new ToolInstallGlobalOrToolPathCommand( result, - _createToolPackageStoresAndDownloader, + _createToolPackageStoreDownloaderUninstaller, _createShellShimRepository, new EnvironmentPathInstructionMock(_reporter, _pathToPlaceShim), _reporter); @@ -555,7 +810,7 @@ public void AndPackagedShimIsProvidedWhenRunWithPackageIdItCreateShimUsingPackag fileSystem: _fileSystem, store: _toolPackageStore, packagedShimsMap: packagedShimsMap, - reporter: _reporter)), + reporter: _reporter), _toolPackageUninstallerMock), _createShellShimRepository, new EnvironmentPathInstructionMock(_reporter, _pathToPlaceShim), _reporter); @@ -573,7 +828,7 @@ public void WhenRunWithArchOptionItErrorsOnInvalidRids() var parseResult = Parser.Instance.Parse($"dotnet tool install -g {PackageId} -a invalid"); var toolInstallGlobalOrToolPathCommand = new ToolInstallGlobalOrToolPathCommand( parseResult, - _createToolPackageStoresAndDownloader, + _createToolPackageStoreDownloaderUninstaller, _createShellShimRepository, _environmentPathInstructionMock, _reporter); @@ -589,7 +844,7 @@ public void WhenRunWithArchOptionItDownloadsAppHostTemplate() var parseResult = Parser.Instance.Parse($"dotnet tool install -g {PackageId} -a arm64"); var toolInstallGlobalOrToolPathCommand = new ToolInstallGlobalOrToolPathCommand( parseResult, - _createToolPackageStoresAndDownloader, + _createToolPackageStoreDownloaderUninstaller, _createShellShimRepository, _environmentPathInstructionMock, _reporter, diff --git a/src/Tests/dotnet.Tests/CommandTests/ToolInstallLocalCommandTests.cs b/src/Tests/dotnet.Tests/CommandTests/ToolInstallLocalCommandTests.cs index 898223f16c62..c4f0f2f9751f 100644 --- a/src/Tests/dotnet.Tests/CommandTests/ToolInstallLocalCommandTests.cs +++ b/src/Tests/dotnet.Tests/CommandTests/ToolInstallLocalCommandTests.cs @@ -1,11 +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.Collections.Generic; -using System.IO; -using System.Linq; -using FluentAssertions; using System.CommandLine; using Microsoft.DotNet.Cli; using Microsoft.DotNet.Cli.ToolPackage; @@ -16,12 +11,9 @@ using Microsoft.DotNet.Tools.Tool.Install; using Microsoft.Extensions.DependencyModel.Tests; using Microsoft.Extensions.EnvironmentAbstractions; -using Xunit; +using NuGet.Frameworks; using NuGet.Versioning; using LocalizableStrings = Microsoft.DotNet.Tools.Tool.Install.LocalizableStrings; -using Microsoft.NET.TestFramework.Utilities; -using System.CommandLine.Parsing; -using NuGet.Frameworks; using Parser = Microsoft.DotNet.Cli.Parser; namespace Microsoft.DotNet.Tests.Commands.Tool @@ -39,6 +31,7 @@ public class ToolInstallLocalCommandTests:SdkTest private readonly string _manifestFilePath; private readonly PackageId _packageIdA = new PackageId("local.tool.console.a"); private readonly NuGetVersion _packageVersionA; + private readonly NuGetVersion _packageNewVersionA; private readonly ToolCommandName _toolCommandNameA = new ToolCommandName("a"); private readonly ToolManifestFinder _toolManifestFinder; private readonly ToolManifestEditor _toolManifestEditor; @@ -46,6 +39,7 @@ public class ToolInstallLocalCommandTests:SdkTest public ToolInstallLocalCommandTests(ITestOutputHelper log):base(log) { _packageVersionA = NuGetVersion.Parse("1.0.4"); + _packageNewVersionA = NuGetVersion.Parse("2.0.0"); _reporter = new BufferedReporter(); _fileSystem = new FileSystemMockBuilder().UseCurrentSystemTemporaryDirectory().Build(); diff --git a/src/Tests/dotnet.Tests/CommandTests/ToolUninstallGlobalOrToolPathCommandTests.cs b/src/Tests/dotnet.Tests/CommandTests/ToolUninstallGlobalOrToolPathCommandTests.cs index 0f6842dc5751..59592ed8b5fb 100644 --- a/src/Tests/dotnet.Tests/CommandTests/ToolUninstallGlobalOrToolPathCommandTests.cs +++ b/src/Tests/dotnet.Tests/CommandTests/ToolUninstallGlobalOrToolPathCommandTests.cs @@ -56,7 +56,7 @@ public void GivenANonExistentPackageItErrors() [Fact] public void GivenAPackageItUninstalls() { - CreateInstallCommand($"-g {PackageId}").Execute().Should().Be(0); + CreateInstallCommand($"-g {PackageId} --verbosity minimal").Execute().Should().Be(0); _reporter .Lines @@ -98,7 +98,7 @@ public void GivenAPackageItUninstalls() [Fact] public void GivenAPackageWhenCallFromUninstallRedirectCommandItUninstalls() { - CreateInstallCommand($"-g {PackageId}").Execute().Should().Be(0); + CreateInstallCommand($"-g {PackageId} --verbosity minimal").Execute().Should().Be(0); _reporter .Lines @@ -168,7 +168,7 @@ var uninstallCommand [Fact] public void GivenAFailureToUninstallItLeavesItInstalled() { - CreateInstallCommand($"-g {PackageId}").Execute().Should().Be(0); + CreateInstallCommand($"-g {PackageId} --verbosity minimal").Execute().Should().Be(0); _reporter .Lines @@ -235,10 +235,11 @@ private ToolInstallGlobalOrToolPathCommand CreateInstallCommand(string options) fileSystem: _fileSystem, _reporter ); + var toolPackageDownloaderMock = new ToolPackageUninstallerMock(_fileSystem, store); return new ToolInstallGlobalOrToolPathCommand( result, - (location, forwardArguments) => (store, store, packageDownloaderMock), + (location, forwardArguments) => (store, store, packageDownloaderMock, toolPackageDownloaderMock), (_, _) => new ShellShimRepository( new DirectoryPath(_shimsDirectory), string.Empty, diff --git a/src/Tests/dotnet.Tests/CommandTests/ToolUpdateGlobalOrToolPathCommandTests.cs b/src/Tests/dotnet.Tests/CommandTests/ToolUpdateGlobalOrToolPathCommandTests.cs index b5eb57b1ea78..9b1f7e3bdcd8 100644 --- a/src/Tests/dotnet.Tests/CommandTests/ToolUpdateGlobalOrToolPathCommandTests.cs +++ b/src/Tests/dotnet.Tests/CommandTests/ToolUpdateGlobalOrToolPathCommandTests.cs @@ -143,7 +143,7 @@ public void GivenAnExistedLowerversionInstallationWhenCallItCanPrintSuccessMessa CreateInstallCommand($"-g {_packageId} --version {LowerPackageVersion}").Execute(); _reporter.Lines.Clear(); - var command = CreateUpdateCommand($"-g {_packageId}"); + var command = CreateUpdateCommand($"-g {_packageId} --verbosity minimal"); command.Execute(); @@ -152,13 +152,43 @@ public void GivenAnExistedLowerversionInstallationWhenCallItCanPrintSuccessMessa _packageId, LowerPackageVersion, HigherPackageVersion)); } + [Fact] + public void GivenAnExistedHigherversionInstallationWhenUpdateToLowerVersionItErrors() + { + CreateInstallCommand($"-g {_packageId} --version {HigherPackageVersion}").Execute(); + _reporter.Lines.Clear(); + + var command = CreateUpdateCommand($"-g {_packageId} --version {LowerPackageVersion} --verbosity minimal"); + + Action a = () => command.Execute(); + + a.Should().Throw().And.Message + .Should().Contain( + string.Format(LocalizableStrings.UpdateToLowerVersion, LowerPackageVersion, HigherPackageVersion)); + } + + [Fact] + public void GivenAnExistedHigherversionInstallationWithDowngradeFlagWhenUpdateToLowerVersionItSucceeds() + { + CreateInstallCommand($"-g {_packageId} --version {HigherPackageVersion}").Execute(); + _reporter.Lines.Clear(); + + var command = CreateUpdateCommand($"-g {_packageId} --version {LowerPackageVersion} --verbosity minimal --allow-downgrade"); + + command.Execute(); + + _reporter.Lines.First().Should().Contain(string.Format( + LocalizableStrings.UpdateSucceeded, + _packageId, HigherPackageVersion, LowerPackageVersion)); + } + [Fact] public void GivenAnExistedLowerversionInstallationWhenCallWithWildCardVersionItCanPrintSuccessMessage() { CreateInstallCommand($"-g {_packageId} --version {LowerPackageVersion}").Execute(); _reporter.Lines.Clear(); - var command = CreateUpdateCommand($"-g {_packageId} --version 1.0.5-*"); + var command = CreateUpdateCommand($"-g {_packageId} --version 1.0.5-* --verbosity minimal"); command.Execute(); @@ -173,7 +203,7 @@ public void GivenAnExistedLowerversionInstallationWhenCallWithPrereleaseVersionI CreateInstallCommand($"-g {_packageId} --version {LowerPackageVersion}").Execute(); _reporter.Lines.Clear(); - var command = CreateUpdateCommand($"-g {_packageId} --prerelease"); + var command = CreateUpdateCommand($"-g {_packageId} --prerelease --verbosity minimal"); command.Execute(); @@ -208,7 +238,7 @@ public void GivenAnExistedSameVersionInstallationWhenCallItCanPrintSuccessMessag CreateInstallCommand($"-g {_packageId} --version {HigherPackageVersion}").Execute(); _reporter.Lines.Clear(); - var command = CreateUpdateCommand($"-g {_packageId}"); + var command = CreateUpdateCommand($"-g {_packageId} --verbosity minimal"); command.Execute(); @@ -223,7 +253,7 @@ public void GivenAnExistedSameVersionInstallationWhenCallWithPrereleaseItUsesAPr CreateInstallCommand($"-g {_packageId} --version {HigherPreviewPackageVersion}").Execute(); _reporter.Lines.Clear(); - var command = CreateUpdateCommand($"-g {_packageId} --version {HigherPreviewPackageVersion}"); + var command = CreateUpdateCommand($"-g {_packageId} --version {HigherPreviewPackageVersion} --verbosity minimal"); command.Execute(); @@ -333,6 +363,9 @@ string ExpectedCommandPath() private ToolInstallGlobalOrToolPathCommand CreateInstallCommand(string options) { ParseResult result = Parser.Instance.Parse("dotnet tool install " + options); + var store = new ToolPackageStoreMock( + new DirectoryPath(_toolsDirectory), + _fileSystem); return new ToolInstallGlobalOrToolPathCommand( result, @@ -341,7 +374,7 @@ private ToolInstallGlobalOrToolPathCommand CreateInstallCommand(string options) fileSystem: _fileSystem, _reporter, _mockFeeds - )), + ), new ToolPackageUninstallerMock(_fileSystem, store)), (_, _) => GetMockedShellShimRepository(), _environmentPathInstructionMock, _reporter); diff --git a/src/Tests/dotnet.Tests/CommandTests/ToolUpdateLocalCommandTests.cs b/src/Tests/dotnet.Tests/CommandTests/ToolUpdateLocalCommandTests.cs index 78c4958e1c18..d6742c26f443 100644 --- a/src/Tests/dotnet.Tests/CommandTests/ToolUpdateLocalCommandTests.cs +++ b/src/Tests/dotnet.Tests/CommandTests/ToolUpdateLocalCommandTests.cs @@ -305,12 +305,35 @@ public void GivenFeedVersionIsLowerRunPackageIdItShouldThrow() _reporter.Clear(); Action a = () => _defaultToolUpdateLocalCommand.Execute(); a.Should().Throw().And.Message.Should().Contain(string.Format( - LocalizableStrings.UpdateLocaToolToLowerVersion, + LocalizableStrings.UpdateLocalToolToLowerVersion, "0.9.0", _packageOriginalVersionA.ToNormalizedString(), _manifestFilePath)); } + [Fact] + public void GivenFeedVersionIsLowerWithDowngradeFlagRunPackageIdItShouldSucceeds() + { + _reporter.Clear(); + + ParseResult parseResult + = Parser.Instance.Parse( + $"dotnet tool update {_packageIdA.ToString()} --version 0.9.0 --allow-downgrade"); + + _toolRestoreCommand.Execute(); + _mockFeed.Packages.Single().Version = "0.9.0"; + + ToolUpdateLocalCommand toolUpdateLocalCommand = new ToolUpdateLocalCommand( + parseResult, + _toolPackageDownloaderMock, + _toolManifestFinder, + _toolManifestEditor, + _localToolsResolverCache, + _reporter); + + toolUpdateLocalCommand.Execute().Should().Be(0); + } + private void AssertUpdateSuccess(FilePath? manifestFile = null, NuGetVersion packageVersion = null) { packageVersion ??= _packageNewVersionA; diff --git a/src/Tests/dotnet.Tests/GivenThatDotNetRunsCommands.cs b/src/Tests/dotnet.Tests/GivenThatDotNetRunsCommands.cs index f7115379540a..27289c31db96 100644 --- a/src/Tests/dotnet.Tests/GivenThatDotNetRunsCommands.cs +++ b/src/Tests/dotnet.Tests/GivenThatDotNetRunsCommands.cs @@ -27,7 +27,8 @@ public void UnresolvedPlatformReferencesFailAsExpected() .WithWorkingDirectory(testInstance.Path) .Execute("crash") .Should().Fail() - .And.HaveStdErrContaining(string.Format(LocalizableStrings.NoExecutableFoundMatchingCommand, "dotnet-crash")); + .And.HaveStdErrContaining(LocalizableStrings.NoExecutableFoundMatchingCommandErrorMessage) + .And.HaveStdOutContaining(string.Format(LocalizableStrings.NoExecutableFoundMatchingCommand, "dotnet-crash")); } [Theory] diff --git a/src/Tests/dotnet.Tests/PackagedCommandTests.cs b/src/Tests/dotnet.Tests/PackagedCommandTests.cs index 96f4539b6915..771cad76eca0 100644 --- a/src/Tests/dotnet.Tests/PackagedCommandTests.cs +++ b/src/Tests/dotnet.Tests/PackagedCommandTests.cs @@ -157,7 +157,9 @@ public void ItShowsErrorWhenToolIsNotRestored() .WithWorkingDirectory(testInstance.Path) .Execute("nonexistingtool") .Should().Fail() - .And.HaveStdErrContaining(string.Format(LocalizableStrings.NoExecutableFoundMatchingCommand, "dotnet-nonexistingtool")); + .And.HaveStdErrContaining(LocalizableStrings.NoExecutableFoundMatchingCommandErrorMessage) + .And.HaveStdOutContaining( + string.Format(LocalizableStrings.NoExecutableFoundMatchingCommand, "dotnet-nonexistingtool")); } [Fact] @@ -234,9 +236,10 @@ public void TestProjectDependencyIsNotAvailableThroughDriver() .WithWorkingDirectory(testInstance.Path) .Execute(); - result.StdErr.Should().Contain(string.Format(LocalizableStrings.NoExecutableFoundMatchingCommand, "dotnet-hello")); - - result.Should().Fail(); + result.StdErr.Should().Contain(LocalizableStrings.NoExecutableFoundMatchingCommandErrorMessage); + result.StdOut.Should().Contain(string.Format(LocalizableStrings.NoExecutableFoundMatchingCommand, "dotnet-hello")); + + result.Should().Fail(); } private void SetGeneratedPackageName(FileInfo project, string packageName) diff --git a/src/Tests/dotnet.Tests/ParserTests/ParseResultExtensionsTests.cs b/src/Tests/dotnet.Tests/ParserTests/ParseResultExtensionsTests.cs index d768e5a31d62..be639fd0b2a6 100644 --- a/src/Tests/dotnet.Tests/ParserTests/ParseResultExtensionsTests.cs +++ b/src/Tests/dotnet.Tests/ParserTests/ParseResultExtensionsTests.cs @@ -33,7 +33,7 @@ public void RootSubCommandResultReturnsCorrectSubCommand(string input, string ex [Theory] [InlineData(new string[] { "dotnet", "build" }, new string[] { })] [InlineData(new string[] { "build" }, new string[] { })] - [InlineData(new string[] { "dotnet", "test", "-d" }, new string[] { })] + [InlineData(new string[] { "dotnet", "test", "-d" }, new string[] { "-d" })] [InlineData(new string[] { "dotnet", "publish", "-o", "foo" }, new string[] { "-o", "foo" })] [InlineData(new string[] { "publish", "-o", "foo" }, new string[] { "-o", "foo" })] [InlineData(new string[] { "dotnet", "add", "package", "-h" }, new string[] { "package", "-h" })] diff --git a/src/Tests/dotnet.Tests/dotnet-msbuild/GivenDotnetOsArchOptions.cs b/src/Tests/dotnet.Tests/dotnet-msbuild/GivenDotnetOsArchOptions.cs index ed1ea7afefc3..658a5590ab25 100644 --- a/src/Tests/dotnet.Tests/dotnet-msbuild/GivenDotnetOsArchOptions.cs +++ b/src/Tests/dotnet.Tests/dotnet-msbuild/GivenDotnetOsArchOptions.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 System.Globalization; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.Tools; using BuildCommand = Microsoft.DotNet.Tools.Build.BuildCommand; @@ -148,5 +149,53 @@ public void ArchOptionsAMD64toX64() .StartWith($"{ExpectedPrefix} -restore -consoleloggerparameters:Summary -property:RuntimeIdentifier=os-x64"); }); } + + [Fact] + public void ArchOptionIsResolvedFromRidUnderDifferentCulture() + { + CultureInfo currentCultureBefore = CultureInfo.CurrentCulture; + try + { + CultureInfo.CurrentCulture = new CultureInfo("th"); + CommandDirectoryContext.PerformActionWithBasePath(WorkingDirectory, () => + { + var msbuildPath = ""; + var command = BuildCommand.FromArgs(new string[] { "--os", "os" }, msbuildPath); + var expectedArch = RuntimeInformation.ProcessArchitecture.Equals(Architecture.Arm64) ? "arm64" : Environment.Is64BitOperatingSystem ? "x64" : "x86"; + command.GetArgumentsToMSBuild() + .Should() + .StartWith($"{ExpectedPrefix} -restore -consoleloggerparameters:Summary -property:RuntimeIdentifier=os-{expectedArch}"); + }); + } + finally { CultureInfo.CurrentCulture = currentCultureBefore; } + } + + [Fact] + public void OsOptionIsResolvedFromRidUnderDifferentCulture() + { + CultureInfo currentCultureBefore = CultureInfo.CurrentCulture; + try + { + CultureInfo.CurrentCulture = new CultureInfo("th"); + CommandDirectoryContext.PerformActionWithBasePath(WorkingDirectory, () => + { + var msbuildPath = ""; + var command = BuildCommand.FromArgs(new string[] { "--arch", "arch" }, msbuildPath); + var expectedOs = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "win" : + RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "linux" : + RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "osx" : + null; + if (expectedOs == null) + { + // Not a supported OS for running test + return; + } + command.GetArgumentsToMSBuild() + .Should() + .StartWith($"{ExpectedPrefix} -restore -consoleloggerparameters:Summary -property:RuntimeIdentifier={expectedOs}-arch"); + }); + } + finally { CultureInfo.CurrentCulture = currentCultureBefore;} + } } } diff --git a/src/Tests/dotnet.Tests/dotnet.Tests.csproj b/src/Tests/dotnet.Tests/dotnet.Tests.csproj index 9753c7b25f8d..0179861656c2 100644 --- a/src/Tests/dotnet.Tests/dotnet.Tests.csproj +++ b/src/Tests/dotnet.Tests/dotnet.Tests.csproj @@ -170,5 +170,13 @@ + + + + + + + + diff --git a/src/WebSdk/Publish/Targets/Microsoft.NET.Sdk.Publish.props b/src/WebSdk/Publish/Targets/Microsoft.NET.Sdk.Publish.props index ac284f1689d5..862cdafc5d89 100644 --- a/src/WebSdk/Publish/Targets/Microsoft.NET.Sdk.Publish.props +++ b/src/WebSdk/Publish/Targets/Microsoft.NET.Sdk.Publish.props @@ -15,7 +15,4 @@ Copyright (c) .NET Foundation. All rights reserved. true - - diff --git a/src/WebSdk/Publish/Targets/Microsoft.NET.Sdk.Publish.targets b/src/WebSdk/Publish/Targets/Microsoft.NET.Sdk.Publish.targets index f6779faa84a6..be1190acfbf4 100644 --- a/src/WebSdk/Publish/Targets/Microsoft.NET.Sdk.Publish.targets +++ b/src/WebSdk/Publish/Targets/Microsoft.NET.Sdk.Publish.targets @@ -29,7 +29,6 @@ Copyright (C) Microsoft Corporation. All rights reserved. <_PublishTargetsDir Condition=" '$(_PublishTargetsDir)'=='' ">$(MSBuildThisFileDirectory)PublishTargets\ <_DotNetCLIToolTargetsDir Condition=" '$(_DotNetCLIToolTargetsDir)'=='' ">$(MSBuildThisFileDirectory)DotNetCLIToolTargets\ <_PublishProfilesDir Condition=" '$(_PublishProfilesDir)'=='' ">$(MSBuildThisFileDirectory)PublishProfiles\ - <_ContainersTargetsDir Condition=" '$(_ContainersTargetsDir)'=='' ">$(MSBuildThisFileDirectory)..\..\..\Containers\build\ - - - -