Cross-platform satellite assemblies via in-proc Csc task (fixes #82)#123
Cross-platform satellite assemblies via in-proc Csc task (fixes #82)#123paulirwin wants to merge 3 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR updates ICU4N’s build pipeline to generate ICU4N resource-only satellite assemblies in a cross-platform way by switching from the al.exe/Mono al approach to the in-process MSBuild Csc task (mirroring the .NET SDK’s satellite generation pattern), removing the need for Windows SDK tooling or Mono on non-Windows.
Changes:
- Replace the custom
al-basedLinkAssembliestask with aWriteCodeFragment+ in-procCsc-based MSBuild pipeline for neutral and per-culture satellites. - Add a small MSBuild recursion target (
_BuildOneSatelliteAssembly) to build localized satellites per culture (parallelized). - Remove the repo-level
.NET Framework SDK Fixupproperties that were only needed for the oldalpipeline.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/ICU4N/ICU4N.csproj | Reworks satellite assembly generation to use WriteCodeFragment + Csc and MSBuild recursion instead of al tooling. |
| Directory.Build.props | Removes TargetFrameworkSDKToolsDirectory fixup properties that supported the prior al/Mono pipeline. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| DependsOnTargets="_CollectSatelliteInputs" | ||
| Condition=" '$(_SatelliteCulture)' != '' and '$(TargetFramework)' == '$(SatelliteAssemblyTargetFramework)' " | ||
| Inputs="$(MSBuildAllProjects);$(ICU4JResourcesDirectory)/$(_SatelliteCulture)/*.*" | ||
| Outputs="$(ICU4NSatelliteAssemblyOutputDir)/$(_SatelliteCulture)/$(AssemblyName).resources.dll"> |
There was a problem hiding this comment.
_BuildOneSatelliteAssembly tries to be incremental via Inputs=.../*.*, but MSBuild does not expand wildcards in Inputs/Outputs when they come from a property string. As written, the input path with *.* won’t exist as a real file, so the target will be considered out-of-date and will rebuild every culture on every build. Define an item list for the culture resources and reference that item list in Inputs (e.g., @(_OneCultureResource)), so incremental checks use the actual file set.
NightOwl888
left a comment
There was a problem hiding this comment.
I left a comment yesterday, but I didn't follow through on finishing the "review" in GitHub. We definitely should address the incremental build gate that we had before. Maybe there is a way to get it to work with Inputs and Outputs that doesn't cause it to be an all or nothing proposition with generating satellite assemblies, but I know the original approach from the LinkAssemblies task worked pretty well. I am sure there is probably a way to turn that logic into a separate task that returns a boolean and then use it in a batch so it runs before every call to csc, one for each resource file.
| Deterministic="true" | ||
| TargetType="Library" | ||
| UseSharedCompilation="true" /> | ||
| </Target> |
There was a problem hiding this comment.
I ran some checks and there are many differences between the old output and the new. I cannot point to any specific line to tell you what is wrong, so I will summarize all of the differences here with detail in drill down sections.
1. Localized Satellite Assembly
Old way
✔️ Compiles with net40 target
✔️ Includes culture
✔️ Strong names the assembly with PublicKeyToken=efb17c8e4f0e291b
✔️ Assembly version is correct
✔️ Includes assembly attributes for Title, Description, Company, Product, InformationalVersion, Copyright, FileVersion, and Version
- All of these come from the root
Directory.Targets.propsfile except for the 3 version attributes which come fromNerdbank.GitVersioning
Details
using System.Reflection;
// Assembly ICU4N.resources, Version=60.0.0.0, Culture=af, PublicKeyToken=efb17c8e4f0e291b
// MVID: FA9FF38F-D866-45AB-84B8-67F2065144AB
// Assembly references:
// mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
[assembly: AssemblyTitle("ICU4N")]
[assembly: AssemblyDescription("ICU (International Components for Unicode) is a set of libraries providing Unicode and Globalization support for software applications. It provides Text-boundary analysis (RuleBasedBreakIterator) as well as easy access to all of the many Unicode character properties, Unicode Normalization, Case Folding and other fundamental operations as specified by the Unicode Standard. ICU4N is a .NET port of ICU4J.")]
[assembly: AssemblyCompany("ICU4N")]
[assembly: AssemblyProduct("ICU4N")]
[assembly: AssemblyInformationalVersion("60.1.0-alpha.443+1d36d9c978")]
[assembly: AssemblyCopyright("Copyright © 2019 - 2026 ICU4N")]
[assembly: AssemblyFileVersion("60.1.0")]
[assembly: AssemblyVersion("60.0.0.0")]New way
❌ Compiles with netstandard2.0 (not likely to load under net40)
✔️ Includes culture
✔️ Strong names the assembly with PublicKeyToken=efb17c8e4f0e291b
✔️ Assembly version is correct
❌ Doesn't include any version or package author attributes except AssemblyVersion
👉 Includes CompilationRelaxations, RuntimeCompatibility, and Debuggable attributes (probably no harm in these, but wouldn't hurt to check)
Details
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
// Assembly ICU4N.resources, Version=60.0.0.0, Culture=af, PublicKeyToken=efb17c8e4f0e291b
// MVID: 01459FD1-3937-40A7-9245-1C169CCEF272
// Assembly references:
// netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyVersion("60.0.0.0")]Note that .NET Core contains backward compatibility to load legacy assemblies, but net40 contains no support for loading netstandard2.0. But, the .NET SDK can compile net40 targets just fine, so this should be simple to fix.
For performance reasons, we don't build all satellite assemblies twice, we simply copy them into 2 different NuGet packages, ICU4N.Resources and ICU4N.Resources.NetFramework4.0.
2. Neutral Satellite Assembly
Old way
✔️ Compiles with net40 target
✔️ Culture is neutral
✔️ Strong names the assembly with PublicKeyToken=efb17c8e4f0e291b
✔️ Assembly version is correct
✔️ Includes assembly attributes for Title, Description, Company, Product, InformationalVersion, Copyright, FileVersion, and Version
- All of these come from the root
Directory.Targets.propsfile except for the 3 version attributes which come fromNerdbank.GitVersioning
Details
using System.Reflection;
// Assembly ICU4N.resources, Version=60.0.0.0, Culture=neutral, PublicKeyToken=efb17c8e4f0e291b
// MVID: 01F0572E-27A1-4FAC-A9F9-DD8503A6B45F
// Assembly references:
// mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
[assembly: AssemblyTitle("ICU4N")]
[assembly: AssemblyDescription("ICU (International Components for Unicode) is a set of libraries providing Unicode and Globalization support for software applications. It provides Text-boundary analysis (RuleBasedBreakIterator) as well as easy access to all of the many Unicode character properties, Unicode Normalization, Case Folding and other fundamental operations as specified by the Unicode Standard. ICU4N is a .NET port of ICU4J.")]
[assembly: AssemblyCompany("ICU4N")]
[assembly: AssemblyProduct("ICU4N")]
[assembly: AssemblyInformationalVersion("60.1.0-alpha.443+1d36d9c978")]
[assembly: AssemblyCopyright("Copyright © 2019 - 2026 ICU4N")]
[assembly: AssemblyFileVersion("60.1.0")]
[assembly: AssemblyVersion("60.0.0.0")]New way
❌ Compiles with netstandard2.0 (not likely to load under net40)
✔️ Culture is neutral
✔️ Strong names the assembly with PublicKeyToken=efb17c8e4f0e291b
✔️ Assembly version is correct
❌ Doesn't include any version or package author attributes except AssemblyVersion
👉 Includes CompilationRelaxations, RuntimeCompatibility, and Debuggable attributes (probably no harm in these, but wouldn't hurt to check)
Details
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
// Assembly ICU4N.resources, Version=60.0.0.0, Culture=neutral, PublicKeyToken=efb17c8e4f0e291b
// MVID: 5B27F173-4CDB-4480-89C4-FE22FBEE6051
// Assembly references:
// netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyVersion("60.0.0.0")]3. Build
Old way
✔️ Builds all satellite assemblies if missing when running Build command
✔️ Builds all satellite assemblies if missing when running Rebuild command
✔️ Builds one missing satellite assembly when running Build command
✔️ Builds one missing satellite assembly when running Rebuild command
🚀 Takes about 2 minutes to build all satellite assemblies on my machine
✔️ Reports each localized satellite assembly written in build output with minimal output
✔️ Unpacks resources from the .jar if the _artifacts/icu4j-temp folder is missing
✔️ Downloads icu4j-60.1.jar if missing
Details
Build started at 10:34 PM...
1>------ Build started: Project: ICU4JResourceConverter, Configuration: Debug Any CPU ------
1> ICU4JResourceConverter -> F:\Projects\ICU4N\src\tools\ICU4JResourceConverter\bin\Debug\net9.0\ICU4JResourceConverter.dll
2>------ Build started: Project: ICU4N, Configuration: Debug Any CPU ------
2> SkipT4Transform: false
2> UseMicrosoftT4: true
2> Calling Transform...
2> ResourcesRequireUpdate: False
2> Generating Satellite Assembly for
2> Generating Satellite Assembly for af
2> Generating Satellite Assembly for agq
2> Generating Satellite Assembly for ak
2> Generating Satellite Assembly for am
2> Generating Satellite Assembly for ar
2> Generating Satellite Assembly for ars
2> Generating Satellite Assembly for as
2> Generating Satellite Assembly for asa
2> Generating Satellite Assembly for ast
2> Generating Satellite Assembly for az
2> Generating Satellite Assembly for az-Cyrl
2> Generating Satellite Assembly for az-Latn
2> Generating Satellite Assembly for bas
2> Generating Satellite Assembly for be
2> Generating Satellite Assembly for bem
2> Generating Satellite Assembly for bez
2> Generating Satellite Assembly for bg
2> Generating Satellite Assembly for bm
2> Generating Satellite Assembly for bn
2> Generating Satellite Assembly for bo
2> Generating Satellite Assembly for br
2> Generating Satellite Assembly for brx
2> Generating Satellite Assembly for bs
2> Generating Satellite Assembly for bs-Cyrl
2> Generating Satellite Assembly for bs-Latn
2> Generating Satellite Assembly for ca
2> Generating Satellite Assembly for ccp
2> Generating Satellite Assembly for ce
2> Generating Satellite Assembly for cgg
2> Generating Satellite Assembly for chr
2> Generating Satellite Assembly for cs
2> Generating Satellite Assembly for cy
2> Generating Satellite Assembly for da
2> Generating Satellite Assembly for dav
2> Generating Satellite Assembly for de
2> Generating Satellite Assembly for dje
2> Generating Satellite Assembly for dsb
2> Generating Satellite Assembly for dua
2> Generating Satellite Assembly for dyo
2> Generating Satellite Assembly for dz
2> Generating Satellite Assembly for ebu
2> Generating Satellite Assembly for ee
2> Generating Satellite Assembly for el
2> Generating Satellite Assembly for en
2> Generating Satellite Assembly for eo
2> Generating Satellite Assembly for es
2> Generating Satellite Assembly for et
2> Generating Satellite Assembly for eu
2> Generating Satellite Assembly for ewo
2> Generating Satellite Assembly for fa
2> Generating Satellite Assembly for ff
2> Generating Satellite Assembly for fi
2> Generating Satellite Assembly for fil
2> Generating Satellite Assembly for fo
2> Generating Satellite Assembly for fr
2> Generating Satellite Assembly for fur
2> Generating Satellite Assembly for fy
2> Generating Satellite Assembly for ga
2> Generating Satellite Assembly for gd
2> Generating Satellite Assembly for gl
2> Generating Satellite Assembly for gsw
2> Generating Satellite Assembly for gu
2> Generating Satellite Assembly for guz
2> Generating Satellite Assembly for gv
2> Generating Satellite Assembly for ha
2> Generating Satellite Assembly for haw
2> Generating Satellite Assembly for he
2> Generating Satellite Assembly for hi
2> Generating Satellite Assembly for hr
2> Generating Satellite Assembly for hsb
2> Generating Satellite Assembly for hu
2> Generating Satellite Assembly for hy
2> Generating Satellite Assembly for id
2> Generating Satellite Assembly for ig
2> Generating Satellite Assembly for ii
2> Generating Satellite Assembly for in
2> Generating Satellite Assembly for is
2> Generating Satellite Assembly for it
2> Generating Satellite Assembly for iw
2> Generating Satellite Assembly for ja
2> Generating Satellite Assembly for jgo
2> Generating Satellite Assembly for jmc
2> Generating Satellite Assembly for ka
2> Generating Satellite Assembly for kab
2> Generating Satellite Assembly for kam
2> Generating Satellite Assembly for kde
2> Generating Satellite Assembly for kea
2> Generating Satellite Assembly for khq
2> Generating Satellite Assembly for ki
2> Generating Satellite Assembly for kk
2> Generating Satellite Assembly for kkj
2> Generating Satellite Assembly for kl
2> Generating Satellite Assembly for kln
2> Generating Satellite Assembly for km
2> Generating Satellite Assembly for kn
2> Generating Satellite Assembly for ko
2> Generating Satellite Assembly for kok
2> Generating Satellite Assembly for ks
2> Generating Satellite Assembly for ksb
2> Generating Satellite Assembly for ksf
2> Generating Satellite Assembly for ksh
2> Generating Satellite Assembly for ku
2> Generating Satellite Assembly for kw
2> Generating Satellite Assembly for ky
2> Generating Satellite Assembly for lag
2> Generating Satellite Assembly for lb
2> Generating Satellite Assembly for lg
2> Generating Satellite Assembly for lkt
2> Generating Satellite Assembly for ln
2> Generating Satellite Assembly for lo
2> Generating Satellite Assembly for lrc
2> Generating Satellite Assembly for lt
2> Generating Satellite Assembly for lu
2> Generating Satellite Assembly for luo
2> Generating Satellite Assembly for luy
2> Generating Satellite Assembly for lv
2> Generating Satellite Assembly for mas
2> Generating Satellite Assembly for mer
2> Generating Satellite Assembly for mfe
2> Generating Satellite Assembly for mg
2> Generating Satellite Assembly for mgh
2> Generating Satellite Assembly for mgo
2> Generating Satellite Assembly for mk
2> Generating Satellite Assembly for ml
2> Generating Satellite Assembly for mn
2> Generating Satellite Assembly for mo
2> Generating Satellite Assembly for mr
2> Generating Satellite Assembly for ms
2> Generating Satellite Assembly for mt
2> Generating Satellite Assembly for mua
2> Generating Satellite Assembly for my
2> Generating Satellite Assembly for mzn
2> Generating Satellite Assembly for naq
2> Generating Satellite Assembly for nb
2> Generating Satellite Assembly for nd
2> Generating Satellite Assembly for nds
2> Generating Satellite Assembly for ne
2> Generating Satellite Assembly for nl
2> Generating Satellite Assembly for nmg
2> Generating Satellite Assembly for nn
2> Generating Satellite Assembly for nnh
2> Generating Satellite Assembly for no
2> Generating Satellite Assembly for nus
2> Generating Satellite Assembly for nyn
2> Generating Satellite Assembly for om
2> Generating Satellite Assembly for or
2> Generating Satellite Assembly for os
2> Generating Satellite Assembly for pa
2> Generating Satellite Assembly for pa-Arab
2> Generating Satellite Assembly for pa-Guru
2> Generating Satellite Assembly for pl
2> Generating Satellite Assembly for ps
2> Generating Satellite Assembly for pt
2> Generating Satellite Assembly for quz
2> Generating Satellite Assembly for rm
2> Generating Satellite Assembly for rn
2> Generating Satellite Assembly for ro
2> Generating Satellite Assembly for rof
2> Generating Satellite Assembly for ru
2> Generating Satellite Assembly for rw
2> Generating Satellite Assembly for rwk
2> Generating Satellite Assembly for sah
2> Generating Satellite Assembly for saq
2> Generating Satellite Assembly for sbp
2> Generating Satellite Assembly for se
2> Generating Satellite Assembly for seh
2> Generating Satellite Assembly for ses
2> Generating Satellite Assembly for sg
2> Generating Satellite Assembly for sh
2> Generating Satellite Assembly for shi
2> Generating Satellite Assembly for shi-Latn
2> Generating Satellite Assembly for shi-Tfng
2> Generating Satellite Assembly for si
2> Generating Satellite Assembly for sk
2> Generating Satellite Assembly for sl
2> Generating Satellite Assembly for smn
2> Generating Satellite Assembly for sn
2> Generating Satellite Assembly for so
2> Generating Satellite Assembly for sq
2> Generating Satellite Assembly for sr
2> Generating Satellite Assembly for sr-Cyrl
2> Generating Satellite Assembly for sr-Latn
2> Generating Satellite Assembly for sv
2> Generating Satellite Assembly for sw
2> Generating Satellite Assembly for ta
2> Generating Satellite Assembly for te
2> Generating Satellite Assembly for teo
2> Generating Satellite Assembly for tg
2> Generating Satellite Assembly for th
2> Generating Satellite Assembly for ti
2> Generating Satellite Assembly for tl
2> Generating Satellite Assembly for to
2> Generating Satellite Assembly for tr
2> Generating Satellite Assembly for tt
2> Generating Satellite Assembly for twq
2> Generating Satellite Assembly for tzm
2> Generating Satellite Assembly for ug
2> Generating Satellite Assembly for uk
2> Generating Satellite Assembly for ur
2> Generating Satellite Assembly for uz
2> Generating Satellite Assembly for uz-Arab
2> Generating Satellite Assembly for uz-Cyrl
2> Generating Satellite Assembly for uz-Latn
2> Generating Satellite Assembly for vai
2> Generating Satellite Assembly for vai-Latn
2> Generating Satellite Assembly for vai-Vaii
2> Generating Satellite Assembly for vi
2> Generating Satellite Assembly for vun
2> Generating Satellite Assembly for wae
2> Generating Satellite Assembly for wo
2> Generating Satellite Assembly for xog
2> Generating Satellite Assembly for yav
2> Generating Satellite Assembly for yi
2> Generating Satellite Assembly for yo
2> Generating Satellite Assembly for zgh
2> Generating Satellite Assembly for zh
2> Generating Satellite Assembly for zh-Hans
2> Generating Satellite Assembly for zh-Hant
2> Generating Satellite Assembly for zu
2> ICU4N -> F:\Projects\ICU4N\src\ICU4N\bin\Debug\net40\ICU4N.dll
2> ICU4N -> F:\Projects\ICU4N\src\ICU4N\bin\Debug\net451\ICU4N.dll
2> ICU4N -> F:\Projects\ICU4N\src\ICU4N\bin\Debug\net462\ICU4N.dll
2> ICU4N -> F:\Projects\ICU4N\src\ICU4N\bin\Debug\net8.0\ICU4N.dll
2> ICU4N -> F:\Projects\ICU4N\src\ICU4N\bin\Debug\net9.0\ICU4N.dll
2> ICU4N -> F:\Projects\ICU4N\src\ICU4N\bin\Debug\netstandard2.0\ICU4N.dll
========== Build: 2 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========
========== Build completed at 10:36 PM and took 02:06.053 minutes ==========New way
❌ Doesn't build all satellite assemblies if missing when running Build command
✔️ Builds all satellite assemblies if missing when running Rebuild command
❌ Doesn't build one missing satellite assembly when running Build command
✔️ Builds one missing satellite assembly when running Rebuild command
👉 Takes nearly 5 minutes to build all satellite assemblies on my machine
❌ Reports no progress on satellite assembly builds with minimal build output
✔️ Unpacks resources from the .jar if the _artifacts/icu4j-temp folder is missing
✔️ Downloads icu4j-60.1.jar if missing
While the satellite assembly takes awhile, the incremental build ensures that debugging is fast. The most annoying part is there is no progress reported when the satellite assemblies are being output, so it looks like it is frozen.
Before, we bypassed the caching on Visual Studio to run the process during build. Basically, this means we could get lots of failures until we stumble upon trying the Rebuild command to try getting out of the scenario. Rebuild is what happens in CI so it will always work there.
Certainly, all of the correctness issues take priority over the speed, but it would be nice if it at least didn't force a rebuild just to restore satellite assemblies if they are missing.
Details
Rebuild started at 10:23 PM...
Restored F:\Projects\ICU4N\src\tools\ICU4JResourceConverter\ICU4JResourceConverter.csproj (in 58 ms).
Restored F:\Projects\ICU4N\src\ICU4N.Resources.NETFramework4.0\ICU4N.Resources.NETFramework4.0.csproj (in 76 ms).
Restored F:\Projects\ICU4N\src\ICU4N.Resources\ICU4N.Resources.csproj (in 74 ms).
Restored F:\Projects\ICU4N\tests\ICU4N.Tests.Transliterator\ICU4N.Tests.Transliterator.csproj (in 198 ms).
Restored F:\Projects\ICU4N\tests\ICU4N.Tests\ICU4N.Tests.csproj (in 197 ms).
Restored F:\Projects\ICU4N\tests\ICU4N.Tests.Collation\ICU4N.Tests.Collation.csproj (in 197 ms).
1>------ Rebuild All started: Project: ICU4JResourceConverter, Configuration: Debug Any CPU ------
Restored F:\Projects\ICU4N\src\ICU4N.CurrencyData\ICU4N.CurrencyData.csproj (in 151 ms).
Restored F:\Projects\ICU4N\src\ICU4N\ICU4N.csproj (in 236 ms).
Restored F:\Projects\ICU4N\src\ICU4N.LanguageData\ICU4N.LanguageData.csproj (in 237 ms).
Restored F:\Projects\ICU4N\src\ICU4N.RegionData\ICU4N.RegionData.csproj (in 237 ms).
Restored F:\Projects\ICU4N\src\ICU4N.TestFramework\ICU4N.TestFramework.csproj (in 240 ms).
Restored F:\Projects\ICU4N\src\ICU4N.Collation\ICU4N.Collation.csproj (in 237 ms).
Restored F:\Projects\ICU4N\src\ICU4N.Transliterator\ICU4N.Transliterator.csproj (in 222 ms).
1> ICU4JResourceConverter -> F:\Projects\ICU4N\src\tools\ICU4JResourceConverter\bin\Debug\net9.0\ICU4JResourceConverter.dll
2>------ Rebuild All started: Project: ICU4N, Configuration: Debug Any CPU ------
2> SkipT4Transform: false
2> UseMicrosoftT4: true
2> Calling Transform...
2> ResourcesRequireUpdate: False
2> ICU4N -> F:\Projects\ICU4N\src\ICU4N\bin\Debug\net40\ICU4N.dll
2> ICU4N -> F:\Projects\ICU4N\src\ICU4N\bin\Debug\net9.0\ICU4N.dll
2> ICU4N -> F:\Projects\ICU4N\src\ICU4N\bin\Debug\net462\ICU4N.dll
2> ICU4N -> F:\Projects\ICU4N\src\ICU4N\bin\Debug\net8.0\ICU4N.dll
2> ICU4N -> F:\Projects\ICU4N\src\ICU4N\bin\Debug\net451\ICU4N.dll
2> ICU4N -> F:\Projects\ICU4N\src\ICU4N\bin\Debug\netstandard2.0\ICU4N.dll
========== Rebuild All: 2 succeeded, 0 failed, 0 skipped ==========
========== Rebuild completed at 10:28 PM and took 04:54.743 minutes ==========Addresses review feedback on NightOwl888#123: - Satellites now reference mscorlib 4.0 (from Microsoft.NETFramework.ReferenceAssemblies.net40, which the main project already restores) instead of netstandard 2.0, so they load under .NET Framework 4.0. Csc is invoked with NoStandardLib=true to prevent netstandard.dll from being re-injected. - Full set of package attributes (Title, Description, Company, Product, Copyright, InformationalVersion, FileVersion) are now written into every satellite via WriteCodeFragment, matching the old al.exe /template output. The version attributes depend on GetBuildVersion so Nerdbank.GitVersioning values are available. - Fresh `dotnet build` now deploys satellites to bin/ and propagates them to dependent projects. The old <None Include="…/*.resources.dll" CopyToOutputDirectory=…> glob was evaluated before satellite generation ran, so on a clean build it captured zero files. Items are now injected into _NoneWithTargetPath right before _GetCopyToOutputDirectoryItemsFrom… so both local copy and transitive project-reference propagation pick them up. - Added per-culture "Generating Satellite Assembly for <culture>" messages at high importance so minimal-verbosity VS output shows progress. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
FYI - the error: ##[error]src\ICU4N\ICU4N.csproj(244,11): Error MSB4057: The target "GetBuildVersion" does not exist in the project.Usually is due to Nerdbank.GitVersioning and some build execution ordering problem. Although, I saw it happen in our custom analyzer projects, also. |
Addresses two regressions introduced in 393a63c (build #395 on PR NightOwl888#123): - CI (Azure DevOps) failed with MSB4057: "target GetBuildVersion does not exist". Nerdbank.GitVersioning is conditionally referenced in Directory.Build.targets and excluded when TF_BUILD=true, since the pipeline passes /p:AssemblyVersion, /p:FileVersion and /p:InformationalVersion directly. A static DependsOnTargets reference to GetBuildVersion therefore fails to resolve on CI. Gate the dependency on the same condition NB.GV itself uses, so the target chain runs locally (where NB.GV populates InformationalVersion from git state) but is skipped on CI (where the props are already set by the pipeline inputs). - Full solution build failed locally on .NET SDK 10 with MSB4006: "circular dependency in the target dependency graph involving target GetCopyToOutputDirectoryItems". The sub-invocation that collects transitive copy items from a ProjectReference pulls _IncludeGeneratedSatelliteAssemblies into the same subgraph as GetCopyToOutputDirectoryItems; combining DependsOnTargets= "GenerateOurSatelliteAssemblies" with BeforeTargets= "_GetCopyToOutputDirectoryItemsFromThisProject" closes a loop via the GenerateSatelliteAssemblies → ExecICU4JResourceConverter → GenerateOurSatelliteAssemblies chain. Drop the DependsOnTargets: main-build ordering already guarantees the satellite files exist before PrepareForRun → CopyFilesToOutputDirectory → GetCopyToOutputDirectoryItems runs, and for transitive queries the referenced project's Build has completed before the consumer asks for its copy items. Verified locally on macOS (SDK 10.0.201): - Full solution build: succeeds. - Full test suite (net6.0/net8.0/net9.0): all 4462 pass, 50 skipped (net4x TFMs skipped — require Mono, unrelated environment limit). - 220 satellites (1 neutral + 219 localized) propagate into consuming test projects' bin/ directories. - Simulated CI build (TF_BUILD=true, no NB.GV, /p: version inputs): succeeds and produces all 220 satellites. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…Owl888#82) Generate the ICU4N resource-only satellite assemblies with the in-process `Csc` MSBuild task (from Microsoft.Build.Tasks.CodeAnalysis, shipped with every modern dotnet SDK) — the same pattern the .NET SDK itself uses in Microsoft.NET.Sdk.targets::CoreGenerateSatelliteAssemblies. This replaces the prior `al.exe` / Mono `al` pipeline, which required the Windows SDK or Mono to build the netstandard2.0 target. Build now runs on Windows, macOS, and Linux with no extra tooling. Details: - csc has no -culture: or -version: value flags (those exist on `al`). WriteCodeFragment emits a per-culture .cs stub containing [assembly: AssemblyVersion] and [assembly: AssemblyCulture] which is passed to Csc as a source input. AssemblyCulture drives the AssemblyDef row's Culture field, which is what the CLR satellite resolver reads. - @(ReferencePath) is filtered down to the BCL anchors the attributes live on (mscorlib / netstandard / System.Runtime) — same filter the SDK's satellite target applies. - MSBuild's target- and task-level batching don't compose cleanly with WriteCodeFragment (which treats any metadata key as a named attribute argument). The escape hatch is to <MSBuild>-recurse into a helper target once per culture, passing the culture as a global property. Inside the recursion everything is scalar and Csc keeps sharing its compilation server across all 219 calls. - Removes the TargetFrameworkSDKToolsDirectory fixup in Directory.Build.props (was used to locate `al`; no longer needed). Verified: 220 satellites (1 neutral + 219 localized) build on macOS (SDK 10.0.201) and in Linux Docker (SDK 9.0.313), are strong-name signed with the ICU4N public key token, stamp version 60.0.0.0 and the correct culture (including compound forms like sr-Cyrl), and embed resources with the same data.*.res naming the old al-based task produced. Full test suites (ICU4N.Tests, ICU4N.Tests.Collation, ICU4N.Tests.Transliterator) pass on macOS for net6.0, net8.0, net9.0.
Generate the ICU4N resource-only satellite assemblies with the in-process
CscMSBuild task (from Microsoft.Build.Tasks.CodeAnalysis, shipped with every modern dotnet SDK) — the same pattern the .NET SDK itself uses in Microsoft.NET.Sdk.targets::CoreGenerateSatelliteAssemblies. This replaces the prioral.exe/ Monoalpipeline, which required the Windows SDK or Mono to build the netstandard2.0 target. Build now runs on Windows, macOS, and Linux with no extra tooling.Verified: 220 satellites (1 neutral + 219 localized) build on macOS (SDK 10.0.201) and in Linux Docker (SDK 9.0.313), are strong-name signed with the ICU4N public key token, stamp version 60.0.0.0 and the correct culture (including compound forms like sr-Cyrl), and embed resources with the same data.*.res naming the old al-based task produced.
Fixes #82
Scope note
In #122 (comment), @NightOwl888 sketched three pieces for fixing cross-platform satellite builds:
alto a csc-based MSBuild pipeline — matching what the .NET SDK does inCoreGenerateSatelliteAssemblies.This PR does #1 only. That's the narrow fix that unblocks non-Windows builds today: the satellite target now uses the in-process
Csctask (fromMicrosoft.Build.Tasks.CodeAnalysis) withWriteCodeFragmentfor the attribute stub — the same pattern the SDK uses. Verified on macOS and in a Linux Docker container; full test suite passes on net6.0/net8.0/net9.0 on macOS.#2 and #3 are deliberately out of scope. Both are substantial refactors (a new project structure, a new test-packaging model, a CI migration) that are worth doing on their own merits and reviewed as their own changes, and neither is required to close #82. Keeping this PR small and focused makes it easier to review and ship; the larger plan can layer on top without conflict since the
al-specific plumbing is already gone.AI: Co-authored with Claude Code (Opus 4.7)