Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Localizes nuget.exe with default, embedded resource assembly lookup #4773

Merged
merged 22 commits into from
Sep 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions eng/pipelines/templates/Build_and_UnitTest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,14 @@ steps:
}

- task: MSBuild@1
displayName: "Restore for VS2019"
displayName: "Restore"
inputs:
solution: "build\\build.proj"
configuration: "$(BuildConfiguration)"
msbuildArguments: "/MaxCPUCount /ConsoleLoggerParameters:Verbosity=Minimal;Summary;ForceNoAlign /t:RestoreVS /p:BuildNumber=$(BuildNumber) /p:BuildRTM=$(BuildRTM) /v:m /p:IncludeApex=true /bl:$(Build.StagingDirectory)\\binlog\\01.Restore.binlog"

- task: MSBuild@1
displayName: "Build for VS2019"
displayName: "Build"
inputs:
solution: "build\\build.proj"
configuration: "$(BuildConfiguration)"
Expand All @@ -109,11 +109,18 @@ steps:
msbuildArguments: "/t:AfterBuild /bl:$(Build.StagingDirectory)\\binlog\\03.Localize.binlog"

- task: MSBuild@1
displayName: "Build Final NuGet.exe (via ILMerge)"
displayName: "Build NuGet.exe Localized"
inputs:
solution: "src\\NuGet.Clients\\NuGet.CommandLine\\NuGet.CommandLine.csproj"
configuration: "$(BuildConfiguration)"
msbuildArguments: "/t:ILMergeNuGetExe /p:ExpectedLocalizedArtifactCount=$(LocalizedLanguageCount) /bl:$(Build.StagingDirectory)\\binlog\\04.ILMergeNuGetExe.binlog"
msbuildArguments: "/t:Build /p:SkipILMergeOfNuGetExe=true /bl:$(Build.StagingDirectory)\\binlog\\04.BuildNuGetExe.binlog"

- task: MSBuild@1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you look at the logs for Build NuGet.exe Localized, you will see that we're now ilmerging twice.
We need to build twice, but no need to ilmerge twice.

Copy link
Contributor Author

@dominoFire dominoFire Sep 20, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, now it builds twice and ILMerge once

04.BuildNuGetExe.binlog

Build2nd

05.ILMergeNuGetExe.binlog

image

See reference build

displayName: "ILMerge NuGet.exe"
inputs:
solution: "src\\NuGet.Clients\\NuGet.CommandLine\\NuGet.CommandLine.csproj"
configuration: "$(BuildConfiguration)"
msbuildArguments: "/t:ILMergeNuGetExe /bl:$(Build.StagingDirectory)\\binlog\\05.ILMergeNuGetExe.binlog"

- task: MSBuild@1
displayName: "Publish NuGet.exe (ILMerged) into NuGet.CommandLine.Test (Mac tests use this)"
Expand Down
3 changes: 1 addition & 2 deletions eng/pipelines/templates/pipeline.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

parameters:
- name: isOfficialBuild
type: boolean
Expand Down Expand Up @@ -93,7 +94,6 @@ stages:
VsTargetChannel: $[stageDependencies.Initialize.Initialize_Build.outputs['updatebuildnumber.VsTargetChannel']]
VsTargetMajorVersion: $[stageDependencies.Initialize.Initialize_Build.outputs['updatebuildnumber.VsTargetMajorVersion']]
SDKVersionForBuild: $[stageDependencies.Initialize.Initialize_Build.outputs['getSDKVersionForBuild.SDKVersionForBuild']]
LocalizedLanguageCount: "13"
BuildRTM: "false"
SemanticVersion: $[stageDependencies.Initialize.GetSemanticVersion.outputs['setsemanticversion.SemanticVersion']]
pool:
Expand All @@ -115,7 +115,6 @@ stages:
VsTargetChannel: $[stageDependencies.Initialize.Initialize_Build.outputs['updatebuildnumber.VsTargetChannel']]
VsTargetMajorVersion: $[stageDependencies.Initialize.Initialize_Build.outputs['updatebuildnumber.VsTargetMajorVersion']]
SDKVersionForBuild: $[stageDependencies.Initialize.Initialize_Build.outputs['getSDKVersionForBuild.SDKVersionForBuild']]
LocalizedLanguageCount: "13"
BuildRTM: "true"
pool:
name: VSEngSS-MicroBuild2022-1ES
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,52 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Globalization;
using System.Resources;
using System.Threading;

namespace NuGet.CommandLine
{
/// <summary>
/// A wrapper for string resources located at NuGetResources.resx
/// </summary>
internal static class LocalizedResourceManager
{
private static readonly ResourceManager _resourceManager = new ResourceManager("NuGet.CommandLine.NuGetResources", typeof(LocalizedResourceManager).Assembly);

public static string GetString(string resourceName)
{
var culture = GetLanguageName();
return _resourceManager.GetString(resourceName + '_' + culture, CultureInfo.InvariantCulture) ??
_resourceManager.GetString(resourceName, CultureInfo.InvariantCulture);
return GetString(resourceName, _resourceManager);
}

[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "the convention is to used lower case letter for language name.")]
/// <summary>
/// Returns the 3 letter language name used to locate localized resources.
/// </summary>
/// <returns>the 3 letter language name used to locate localized resources.</returns>
public static string GetLanguageName()
internal static string GetString(string resourceName, ResourceManager resourceManager)
{
var culture = Thread.CurrentThread.CurrentUICulture;
if (string.IsNullOrEmpty(resourceName))
{
throw new ArgumentException(NuGetResources.ArgumentNullOrEmpty, nameof(resourceName));
}
if (resourceManager == null)
{
throw new ArgumentNullException(nameof(resourceManager));
}

string localizedString = resourceManager.GetString(resourceName, Thread.CurrentThread.CurrentUICulture);
if (localizedString == null) // can be empty if .resx has an empty string
{
// Fallback on existing method
CultureInfo neutralCulture = GetNeutralCulture(Thread.CurrentThread.CurrentUICulture);
string languageName = GetLanguageName(neutralCulture);
return resourceManager.GetString(resourceName + '_' + languageName, CultureInfo.InvariantCulture) ??
resourceManager.GetString(resourceName, CultureInfo.InvariantCulture);
}

return localizedString;
}

internal static CultureInfo GetNeutralCulture(CultureInfo inputCulture)
{
CultureInfo culture = inputCulture;
while (!culture.IsNeutralCulture)
{
if (culture.Parent == culture)
Expand All @@ -33,7 +57,10 @@ public static string GetLanguageName()
culture = culture.Parent;
}

return culture.ThreeLetterWindowsLanguageName.ToLowerInvariant();
return culture;
}

[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "the convention is to used lower case letter for language name.")]
private static string GetLanguageName(CultureInfo culture) => culture.ThreeLetterWindowsLanguageName.ToLowerInvariant();
}
}
11 changes: 8 additions & 3 deletions src/NuGet.Clients/NuGet.CommandLine/Common/ResourceHelper.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Globalization;
Expand All @@ -17,6 +20,10 @@ public static string GetLocalizedString(Type resourceType, string resourceNames)
{
throw new ArgumentNullException(nameof(resourceType));
}
if (string.IsNullOrEmpty(resourceNames))
{
throw new ArgumentException("Cannot be null or empty", nameof(resourceNames));
}

if (_cachedManagers == null)
{
Expand Down Expand Up @@ -47,9 +54,7 @@ public static string GetLocalizedString(Type resourceType, string resourceNames)
var builder = new StringBuilder();
foreach (var resource in resourceNames.Split(';'))
{
var culture = LocalizedResourceManager.GetLanguageName();
string value = resourceManager.GetString(resource + '_' + culture, CultureInfo.InvariantCulture) ??
resourceManager.GetString(resource, CultureInfo.InvariantCulture);
string value = LocalizedResourceManager.GetString(resource, resourceManager);
if (String.IsNullOrEmpty(value))
{
throw new InvalidOperationException(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@
[assembly: SuppressMessage("Build", "CA1822:Member FindDependency does not access instance data and can be marked as static (Shared in VisualBasic)", Justification = "<Pending>", Scope = "member", Target = "~M:NuGet.CommandLine.ProjectFactory.FindDependency(NuGet.Packaging.Core.PackageIdentity,System.Collections.Generic.IEnumerable{System.Tuple{NuGet.Packaging.PackageReaderBase,NuGet.Packaging.Core.PackageDependency}})~System.Boolean")]
[assembly: SuppressMessage("Build", "CA1062:In externally visible method 'string ProjectFactory.InitializeProperties(IPackageMetadata metadata)', validate parameter 'metadata' is non-null before using it. If appropriate, throw an ArgumentNullException when the argument is null or add a Code Contract precondition asserting non-null argument.", Justification = "<Pending>", Scope = "member", Target = "~M:NuGet.CommandLine.ProjectFactory.InitializeProperties(NuGet.Packaging.IPackageMetadata)~System.String")]
[assembly: SuppressMessage("Build", "CA1062:In externally visible method 'IProjectFactory ProjectFactory.ProjectCreator(PackArgs packArgs, string path)', validate parameter 'packArgs' is non-null before using it. If appropriate, throw an ArgumentNullException when the argument is null or add a Code Contract precondition asserting non-null argument.", Justification = "<Pending>", Scope = "member", Target = "~M:NuGet.CommandLine.ProjectFactory.ProjectCreator(NuGet.Commands.PackArgs,System.String)~NuGet.Commands.IProjectFactory")]
[assembly: SuppressMessage("Build", "CA1062:In externally visible method 'string ResourceHelper.GetLocalizedString(Type resourceType, string resourceNames)', validate parameter 'resourceNames' is non-null before using it. If appropriate, throw an ArgumentNullException when the argument is null or add a Code Contract precondition asserting non-null argument.", Justification = "<Pending>", Scope = "member", Target = "~M:NuGet.CommandLine.ResourceHelper.GetLocalizedString(System.Type,System.String)~System.String")]
[assembly: SuppressMessage("Build", "CA1822:Member GetPackageReferenceFile does not access instance data and can be marked as static (Shared in VisualBasic)", Justification = "<Pending>", Scope = "member", Target = "~M:NuGet.CommandLine.RestoreCommand.GetPackageReferenceFile(System.String)~System.String")]
[assembly: SuppressMessage("Build", "CA1307:The behavior of 'string.Equals(string, string)' could vary based on the current user's locale settings. Replace this call in 'NuGet.CommandLine.RestoreCommand.IsPackagesConfig(string)' with a call to 'string.Equals(string, string, System.StringComparison)'.", Justification = "<Pending>", Scope = "member", Target = "~M:NuGet.CommandLine.RestoreCommand.IsPackagesConfig(System.String)~System.Boolean")]
[assembly: SuppressMessage("Build", "CA1031:Modify 'GetNuGetVersion' to catch a more specific allowed exception type, or rethrow the exception.", Justification = "<Pending>", Scope = "member", Target = "~M:NuGet.CommandLine.SelfUpdater.GetNuGetVersion(System.Reflection.Assembly)~NuGet.Versioning.NuGetVersion")]
Expand Down
21 changes: 7 additions & 14 deletions src/NuGet.Clients/NuGet.CommandLine/NuGet.CommandLine.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project>
<Project>

<PropertyGroup>
<IsCommandLinePackage>true</IsCommandLinePackage>
Expand Down Expand Up @@ -84,6 +84,8 @@
<AutoGen>True</AutoGen>
<DependentUpon>NuGetResources.resx</DependentUpon>
</Compile>
<!-- nuget.exe localized assemblies -->
<EmbeddedResource Include="$(OutputPath)\**\$(AssemblyName).resources.dll" Exclude="@(MergeExclude)" />
</ItemGroup>

<ItemGroup>
Expand All @@ -99,9 +101,8 @@
<PathToMergedNuGetExe>$(ArtifactsDirectory)$(VsixOutputDirName)\NuGet.exe</PathToMergedNuGetExe>
</PropertyGroup>
<ItemGroup>
<!-- Dependent assemblies -->
<BuildArtifacts Include="$(OutputPath)\*.dll" Exclude="@(MergeExclude)" />
<!-- NuGet.exe needs all NuGet.Commands.resources.dll merged in -->
<LocalizedArtifacts Include="$(ArtifactsDirectory)\NuGet.Commands\**\$(NETFXTargetFramework)\**\*.resources.dll" />
</ItemGroup>
<Message Text="$(MSBuildProjectName) -&gt; $(PathToMergedNuGetExe)" Importance="High" />
</Target>
Expand All @@ -112,21 +113,13 @@
<Target Name="ILMergeNuGetExe"
AfterTargets="Build"
DependsOnTargets="DetermineILMergeNuGetExeInputsOutputs"
Inputs="$(PathToBuiltNuGetExe);@(BuildArtifacts);@(LocalizedArtifacts)"
Inputs="$(PathToBuiltNuGetExe);@(BuildArtifacts)"
Outputs="$(PathToMergedNuGetExe)"
Condition="'$(BuildingInsideVisualStudio)' != 'true' and '$(SkipILMergeOfNuGetExe)' != 'true'" >
<PropertyGroup>
<!-- when done after build, no localizedartifacts are built yet, so expected localized artifact count is 0. -->
<ExpectedLocalizedArtifactCount Condition="'$(ExpectedLocalizedArtifactCount)' == ''">0</ExpectedLocalizedArtifactCount>
</PropertyGroup>
<Error Text="Build dependencies are inconsistent with mergeinclude specified in ilmerge.props" Condition="'@(BuildArtifacts-&gt;Count())' != '@(MergeInclude-&gt;Count())'" />
<Error Text="Satellite assemblies count ILMerged into NuGet.exe should be $(ExpectedLocalizedArtifactCount), but was: @(LocalizedArtifacts-&gt;Count())"
Condition="'@(LocalizedArtifacts-&gt;Count())' != '$(ExpectedLocalizedArtifactCount)'" />
<PropertyGroup>
<IlmergeCommand>$(ILMergeExePath) /lib:$(OutputPath) /out:$(PathToMergedNuGetExe) @(MergeAllowDup -> '/allowdup:%(Identity)', ' ') /log:$(OutputPath)IlMergeLog.txt</IlmergeCommand>
<IlmergeCommand Condition="Exists($(MS_PFX_PATH))">$(IlmergeCommand) /delaysign /keyfile:$(MS_PFX_PATH)</IlmergeCommand>
<!-- LocalizedArtifacts need fullpath, since there will be duplicate file names -->
<IlmergeCommand>$(IlmergeCommand) $(PathToBuiltNuGetExe) @(BuildArtifacts->'%(filename)%(extension)', ' ') @(LocalizedArtifacts->'%(fullpath)', ' ')</IlmergeCommand>
<IlmergeCommand>$(IlmergeCommand) $(PathToBuiltNuGetExe) @(BuildArtifacts->'%(fullpath)', ' ')</IlmergeCommand>
</PropertyGroup>
<MakeDir Directories="$([System.IO.Path]::GetDirectoryName($(PathToMergedNuGetExe)))" />
<Exec Command="$(IlmergeCommand)" ContinueOnError="false" />
Expand All @@ -143,7 +136,7 @@
</Target>

<!-- Do nothing. This basically strips away the framework assemblies from the resulting nuspec.-->
<Target Name ="_GetFrameworkAssemblyReferences" DependsOnTargets="ResolveReferences"/>
<Target Name="_GetFrameworkAssemblyReferences" DependsOnTargets="ResolveReferences" />

<Target Name="GetSigningInputs" Returns="@(DllsToSign)">
<ItemGroup>
Expand Down
Loading