Skip to content

Commit 38c71d9

Browse files
committed
Fix native build corruption in containers and suppress copy warnings
- Implement staging strategy for native builds in containers: build to local temp `C:\Temp\fw-native` and copy to host to avoid `MoveFile` atomicity issues on bind mounts (fixes CVT1107/LNK1123). - Update `Make` task to support `OutDir` and `BuildOutput` overrides. - Update `mkall.targets` to use staging paths when running in Docker. - Suppress MSB3026 warnings (copy retries) in `Directory.Build.props` to reduce build noise. - Add `Test-CvtresCompatibility` to build scripts to diagnose toolchain issues. - Fix `ParatextHelperTests` namespace ambiguity. - Fix `PicturePropertiesDialog` null license assignment. - Cleanup `App.config` files and remove obsolete `Remove-StaleNativeLibs` logic.
1 parent 724a636 commit 38c71d9

File tree

13 files changed

+165
-112
lines changed

13 files changed

+165
-112
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ _user.mak.lnx
7272
.project
7373
*.VC.db
7474
*.VC.opendb
75+
*.invalid
7576

7677
.dotest/
7778
.depend

Build/Agent/FwBuildEnvironment.psm1

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,60 @@ function Initialize-VsDevEnvironment {
9191
Write-Host " VCINSTALLDIR: $env:VCINSTALLDIR" -ForegroundColor Gray
9292
}
9393

94+
function Get-CvtresDiagnostics {
95+
<#
96+
.SYNOPSIS
97+
Returns details about the cvtres.exe resolved in the current session.
98+
#>
99+
$result = [ordered]@{
100+
Path = $null
101+
IsVcToolset = $false
102+
IsDotNetFramework = $false
103+
}
104+
105+
$cmd = Get-Command "cvtres.exe" -ErrorAction SilentlyContinue
106+
if ($cmd) {
107+
$result.Path = $cmd.Source
108+
$lower = $result.Path.ToLowerInvariant()
109+
$result.IsVcToolset = $lower -match "[\\/]vc[\\/]tools[\\/]msvc[\\/][^\\/]+[\\/]bin[\\/]hostx64[\\/]x64[\\/]cvtres\.exe$"
110+
$result.IsDotNetFramework = $lower -match "windows[\\/]microsoft\.net[\\/]framework"
111+
return $result
112+
}
113+
114+
if ($env:VCINSTALLDIR) {
115+
$candidates = Get-ChildItem -Path (Join-Path $env:VCINSTALLDIR "Tools\MSVC\*") -Filter cvtres.exe -Recurse -ErrorAction SilentlyContinue |
116+
Sort-Object FullName -Descending
117+
if ($candidates -and $candidates.Count -gt 0) {
118+
$result.Path = $candidates[0].FullName
119+
$lower = $result.Path.ToLowerInvariant()
120+
$result.IsVcToolset = $lower -match "[\\/]vc[\\/]tools[\\/]msvc[\\/][^\\/]+[\\/]bin[\\/]hostx64[\\/]x64[\\/]cvtres\.exe$"
121+
$result.IsDotNetFramework = $lower -match "windows[\\/]microsoft\.net[\\/]framework"
122+
}
123+
}
124+
125+
return $result
126+
}
127+
128+
function Test-CvtresCompatibility {
129+
<#
130+
.SYNOPSIS
131+
Emits warnings if cvtres.exe resolves to a non-VC toolset binary.
132+
#>
133+
$diag = Get-CvtresDiagnostics
134+
135+
if (-not $diag.Path) {
136+
Write-Host "[WARN] cvtres.exe not found after VS environment setup. Toolchain may be incomplete." -ForegroundColor Yellow
137+
return
138+
}
139+
140+
if ($diag.IsDotNetFramework) {
141+
Write-Host "[WARN] cvtres.exe resolves to a .NET Framework path. Prefer the VC toolset version (Hostx64\\x64). $($diag.Path)" -ForegroundColor Yellow
142+
}
143+
elseif (-not $diag.IsVcToolset) {
144+
Write-Host "[WARN] cvtres.exe is not from the VC toolset Hostx64\\x64 folder. Confirm PATH ordering. $($diag.Path)" -ForegroundColor Yellow
145+
}
146+
}
147+
94148
# =============================================================================
95149
# MSBuild Helper Functions
96150
# =============================================================================
@@ -230,6 +284,7 @@ function Get-VSTestPath {
230284

231285
Export-ModuleMember -Function @(
232286
'Initialize-VsDevEnvironment',
287+
'Test-CvtresCompatibility',
233288
'Get-MSBuildPath',
234289
'Invoke-MSBuild',
235290
'Get-VSTestPath'

Build/Agent/FwBuildHelpers.psm1

Lines changed: 20 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -254,64 +254,34 @@ function Test-CoffArchiveHeader {
254254
return $actual -eq $expected
255255
}
256256

257-
function Remove-StaleNativeLibs {
257+
function Test-GitTrackedFile {
258258
<#
259259
.SYNOPSIS
260-
Removes/quarantines obviously corrupted native libs in Lib/<Configuration>/.
261-
.DESCRIPTION
262-
CVTRES/LNK1123 failures often come from partially written .lib files.
263-
This helper quarantines libs with bad/missing COFF headers that are
264-
clearly truncated, and always enforces COFF headers for in-tree libs
265-
we build ourselves (graphite2, generic, debugprocs, unit++).
260+
Returns $true if the path is tracked by git, $false if untracked, $null on error.
266261
#>
267262
param(
268263
[Parameter(Mandatory)][string]$RepoRoot,
269-
[Parameter(Mandatory)][string]$Configuration
264+
[Parameter(Mandatory)][string]$Path
270265
)
271266

272-
$libDir = Join-Path $RepoRoot "Lib/$Configuration"
273-
if (-not (Test-Path $libDir)) { return }
274-
275-
$libFiles = Get-ChildItem -Path $libDir -Filter '*.lib' -File -ErrorAction SilentlyContinue
276-
if (-not $libFiles) { return }
277-
278-
$enforcedHeaders = @('generic.lib','debugprocs.lib','graphite2.lib','unit++.lib')
279-
280-
$quarantined = @()
281-
foreach ($lib in $libFiles) {
282-
$length = $lib.Length
283-
$headerOk = Test-CoffArchiveHeader -Path $lib.FullName
267+
if (-not (Test-Path $RepoRoot)) { return $null }
284268

285-
if ($null -eq $headerOk) {
286-
Write-Host "[WARN] Skipping '$($lib.Name)' (could not read file to validate header)." -ForegroundColor Yellow
287-
continue
288-
}
289-
290-
$isClearlyTruncated = ($length -lt 8)
291-
$headerMismatch = (-not $headerOk)
292-
$nameLower = $lib.Name.ToLowerInvariant()
293-
$enforceHeader = $headerMismatch -and ($enforcedHeaders -contains $nameLower)
294-
295-
# Quarantine when header is bad and file is clearly tiny/truncated, or
296-
# when the header is bad for in-tree libs that should always be COFF.
297-
if ($enforceHeader -or $isClearlyTruncated -or ($headerMismatch -and $length -lt 4096)) {
298-
$backupPath = "$($lib.FullName).invalid"
299-
if (Test-Path $backupPath) {
300-
Remove-Item -Path $backupPath -Force -ErrorAction SilentlyContinue
301-
}
302-
Rename-Item -Path $lib.FullName -NewName $backupPath -Force
303-
$quarantined += $lib.Name
304-
continue
305-
}
306-
307-
if ($headerMismatch) {
308-
Write-Host "[WARN] '$($lib.Name)' is not a standard COFF archive; leaving it in place." -ForegroundColor Yellow
309-
}
269+
$relPath = $Path
270+
try {
271+
$uriRoot = New-Object System.Uri($RepoRoot + [System.IO.Path]::DirectorySeparatorChar)
272+
$uriPath = New-Object System.Uri($Path)
273+
$relPath = $uriRoot.MakeRelativeUri($uriPath).ToString().Replace('/', '\')
310274
}
275+
catch { }
311276

312-
if ($quarantined.Count -gt 0) {
313-
Write-Host "Quarantined $($quarantined.Count) invalid native lib(s) in $libDir (renamed with .invalid)." -ForegroundColor Yellow
314-
Write-Host "[OK] Invalid native libs quarantined" -ForegroundColor Green
277+
$gitExe = "git"
278+
$arguments = @('-C', $RepoRoot, 'ls-files', '--error-unmatch', $relPath)
279+
try {
280+
$p = Start-Process -FilePath $gitExe -ArgumentList $arguments -NoNewWindow -Wait -PassThru -ErrorAction Stop
281+
return $p.ExitCode -eq 0
282+
}
283+
catch {
284+
return $null
315285
}
316286
}
317287

@@ -333,10 +303,11 @@ Export-ModuleMember -Function @(
333303
'Get-MSBuildPath',
334304
'Invoke-MSBuild',
335305
'Get-VSTestPath',
306+
'Test-CvtresCompatibility',
307+
'Get-CvtresDiagnostics',
336308
# Local functions
337309
'Stop-ConflictingProcesses',
338310
'Remove-StaleObjFolders',
339-
'Remove-StaleNativeLibs',
340311
'Test-IsFileLockError',
341312
'Invoke-WithFileLockRetry'
342313
)

Build/Src/FwBuildTasks/Make.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,18 @@ public Make() { }
3232
[Required]
3333
public string BuildRoot { get; set; }
3434

35+
/// <summary>
36+
/// Optional override for build output root (BUILD_OUTPUT in makefiles).
37+
/// Used to redirect final outputs off bind mounts in containers.
38+
/// </summary>
39+
public string BuildOutput { get; set; }
40+
41+
/// <summary>
42+
/// Optional override for final output directory (OUT_DIR in makefiles).
43+
/// When provided, this takes precedence over the makefile's default OUT_DIR.
44+
/// </summary>
45+
public string OutDir { get; set; }
46+
3547
/// <summary>
3648
/// The build architecture (x86 or x64)
3749
/// </summary>
@@ -156,6 +168,8 @@ protected override string GenerateCommandLineCommands()
156168
bldr.AppendSwitchIfNotNull("BUILD_TYPE=", BuildType);
157169
bldr.AppendSwitchIfNotNull("BUILD_ROOT=", BuildRoot);
158170
bldr.AppendSwitchIfNotNull("BUILD_ARCH=", BuildArch);
171+
bldr.AppendSwitchIfNotNull("BUILD_OUTPUT=", BuildOutput);
172+
bldr.AppendSwitchIfNotNull("OUT_DIR=", OutDir);
159173
bldr.AppendSwitchIfNotNull("OBJ_DIR=", ObjDir);
160174
bldr.AppendSwitchIfNotNull("INT_DIR=", IntDir);
161175
bldr.AppendSwitchIfNotNull("-C", Path.GetDirectoryName(Makefile));
@@ -171,6 +185,8 @@ protected override string GenerateCommandLineCommands()
171185
bldr.AppendSwitchIfNotNull("BUILD_TYPE=", BuildType);
172186
bldr.AppendSwitchIfNotNull("BUILD_ROOT=", BuildRoot);
173187
bldr.AppendSwitchIfNotNull("BUILD_ARCH=", BuildArch);
188+
bldr.AppendSwitchIfNotNull("BUILD_OUTPUT=", BuildOutput);
189+
bldr.AppendSwitchIfNotNull("OUT_DIR=", OutDir);
174190
bldr.AppendSwitchIfNotNull("OBJ_DIR=", ObjDir);
175191
bldr.AppendSwitchIfNotNull("INT_DIR=", IntDir);
176192
bldr.AppendSwitchIfNotNull("/f ", Makefile);

Build/Windows.targets

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="Current">
33
<PropertyGroup>
4+
<BuildNativeTests Condition="'$(BuildNativeTests)'==''">false</BuildNativeTests>
45
<!-- Use container-local path for FwBuildTasks when running in Docker to avoid bind-mount conflicts -->
56
<FwBuildTasksAssembly Condition="'$(FwBuildTasksAssembly)'=='' AND '$(DOTNET_RUNNING_IN_CONTAINER)' != 'true'"
67
>$(MSBuildThisFileDirectory)..\BuildTools\FwBuildTasks\$(Configuration)\FwBuildTasks.dll</FwBuildTasksAssembly
@@ -75,13 +76,13 @@
7576
DependsOnTargets="clean-graphite2Windows"
7677
></Target>
7778
<ItemGroup>
78-
<UnitSources Include="$(fwrt)\Lib\src\unit++\*.*" Condition="'$(OS)'=='Windows_NT'" />
79+
<UnitSources Include="$(fwrt)\Lib\src\unit++\*.*" Condition="'$(OS)'=='Windows_NT' AND '$(BuildNativeTests)'=='true'" />
7980
</ItemGroup>
8081
<Target
8182
Name="Unit++"
8283
Inputs="@(UnitSources)"
8384
Outputs="$(fwrt)\Lib\$(config-lower)\unit++.lib"
84-
Condition="'$(OS)'=='Windows_NT'"
85+
Condition="'$(OS)'=='Windows_NT' AND '$(BuildNativeTests)'=='true'"
8586
>
8687
<MSBuild
8788
Projects="$(fwrt)\Lib\src\unit++\VS\unit++.vcxproj"

Build/mkall.targets

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,18 @@
3737
<ExcludedClsids Include="{e771361c-ff54-4120-9525-98a0b7a9accf}" /> <!-- ManagedLgIcuCollator -->
3838
<ExcludedClsids Include="{830BAF1F-6F84-46EF-B63E-3C1BFDF9E83E}" /> <!-- ViewInputManager -->
3939
</ItemGroup>
40+
<PropertyGroup Condition="'$(DOTNET_RUNNING_IN_CONTAINER)'=='true'">
41+
<NativeStageRoot>C:\Temp\fw-native</NativeStageRoot>
42+
<NativeStageConfig>$(NativeStageRoot)\$(config-capital)</NativeStageConfig>
43+
<NativeStageLib>$(NativeStageConfig)\Lib</NativeStageLib>
44+
<NativeStageOutput>$(NativeStageConfig)\Output</NativeStageOutput>
45+
<NativeMakeOutDir>$(NativeStageOutput)</NativeMakeOutDir>
46+
<NativeMakeLibOutDir>$(NativeStageLib)</NativeMakeLibOutDir>
47+
</PropertyGroup>
48+
<PropertyGroup Condition="'$(DOTNET_RUNNING_IN_CONTAINER)'!='true'">
49+
<NativeMakeOutDir></NativeMakeOutDir>
50+
<NativeMakeLibOutDir></NativeMakeLibOutDir>
51+
</PropertyGroup>
4052
<Target
4153
Name="regFreeCpp"
4254
DependsOnTargets="FwKernel;Views"
@@ -64,9 +76,20 @@
6476
Configuration="$(config-capital)"
6577
BuildRoot="$(fwrt)"
6678
BuildArch="x64"
79+
OutDir="$(NativeMakeOutDir)"
6780
ObjDir="$(dir-fwobj)"
6881
WorkingDirectory="$(fwrt)\Src\DebugProcs"
6982
/>
83+
<ItemGroup Condition="'$(DOTNET_RUNNING_IN_CONTAINER)'=='true'">
84+
<StageDebugProcsFiles Include="$(NativeStageOutput)\**\*.*" />
85+
</ItemGroup>
86+
<Copy
87+
Condition="'$(DOTNET_RUNNING_IN_CONTAINER)'=='true'"
88+
SourceFiles="@(StageDebugProcsFiles)"
89+
DestinationFiles="@(StageDebugProcsFiles->'$(dir-fwoutput)\\$(config-capital)\\%(RecursiveDir)%(Filename)%(Extension)')"
90+
SkipUnchangedFiles="true"
91+
OverwriteReadOnlyFiles="true"
92+
/>
7093
<Message Text="Finished building DebugProcs." />
7194
</Target>
7295
<Target Name="GenericLib" DependsOnTargets="DebugProcs">
@@ -76,9 +99,20 @@
7699
Configuration="$(config-capital)"
77100
BuildRoot="$(fwrt)"
78101
BuildArch="x64"
102+
OutDir="$(NativeMakeLibOutDir)"
79103
ObjDir="$(dir-fwobj)"
80104
WorkingDirectory="$(fwrt)\Src\Generic"
81105
/>
106+
<ItemGroup Condition="'$(DOTNET_RUNNING_IN_CONTAINER)'=='true'">
107+
<StageGenericLibFiles Include="$(NativeStageLib)\**\*.*" />
108+
</ItemGroup>
109+
<Copy
110+
Condition="'$(DOTNET_RUNNING_IN_CONTAINER)'=='true'"
111+
SourceFiles="@(StageGenericLibFiles)"
112+
DestinationFiles="@(StageGenericLibFiles->'$(dir-fwoutputlibBase)\\%(RecursiveDir)%(Filename)%(Extension)')"
113+
SkipUnchangedFiles="true"
114+
OverwriteReadOnlyFiles="true"
115+
/>
82116
<Message Text="Finished building GenericLib." />
83117
</Target>
84118
<Target
@@ -92,9 +126,20 @@
92126
Configuration="$(config-capital)"
93127
BuildRoot="$(fwrt)"
94128
BuildArch="x64"
129+
OutDir="$(NativeMakeOutDir)"
95130
ObjDir="$(dir-fwobj)"
96131
WorkingDirectory="$(fwrt)\Src\Kernel"
97132
/>
133+
<ItemGroup Condition="'$(DOTNET_RUNNING_IN_CONTAINER)'=='true'">
134+
<StageFwKernelFiles Include="$(NativeStageOutput)\**\*.*" />
135+
</ItemGroup>
136+
<Copy
137+
Condition="'$(DOTNET_RUNNING_IN_CONTAINER)'=='true'"
138+
SourceFiles="@(StageFwKernelFiles)"
139+
DestinationFiles="@(StageFwKernelFiles->'$(dir-fwoutput)\\$(config-capital)\\%(RecursiveDir)%(Filename)%(Extension)')"
140+
SkipUnchangedFiles="true"
141+
OverwriteReadOnlyFiles="true"
142+
/>
98143
<!-- Create stub manifest if needed -->
99144
<WriteLinesToFile
100145
File="$(dir-outputBase)\FwKernel.X.manifest"
@@ -131,9 +176,20 @@
131176
Configuration="$(config-capital)"
132177
BuildRoot="$(fwrt)"
133178
BuildArch="x64"
179+
OutDir="$(NativeMakeOutDir)"
134180
ObjDir="$(dir-fwobj)"
135181
WorkingDirectory="$(fwrt)\Src\views"
136182
/>
183+
<ItemGroup Condition="'$(DOTNET_RUNNING_IN_CONTAINER)'=='true'">
184+
<StageViewsFiles Include="$(NativeStageOutput)\**\*.*" />
185+
</ItemGroup>
186+
<Copy
187+
Condition="'$(DOTNET_RUNNING_IN_CONTAINER)'=='true'"
188+
SourceFiles="@(StageViewsFiles)"
189+
DestinationFiles="@(StageViewsFiles->'$(dir-fwoutput)\\$(config-capital)\\%(RecursiveDir)%(Filename)%(Extension)')"
190+
SkipUnchangedFiles="true"
191+
OverwriteReadOnlyFiles="true"
192+
/>
137193
<Message
138194
Text="Generating Views.X.manifest for 64-bit registration-free COM..."
139195
Importance="high"

Directory.Build.props

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@
1313
MSBuild ignores the duplicates (uses first), so this is harmless noise.
1414
-->
1515
<MSBuildWarningsAsMessages>$(MSBuildWarningsAsMessages);MSB3568</MSBuildWarningsAsMessages>
16+
<!--
17+
MSB3026: Copy task retry warnings (transient file locks)
18+
Demote to message so first-attempt copy retries don't spam warnings; final failure still errors via MSB3027.
19+
-->
20+
<MSBuildWarningsAsMessages>$(MSBuildWarningsAsMessages);MSB3026</MSBuildWarningsAsMessages>
1621
<!--
1722
Global TreatWarningsAsErrors Policy:
1823
All C# projects treat warnings as errors to maintain code quality.

Src/AppForTests.config

Lines changed: 2 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -44,37 +44,9 @@
4444
<assemblyIdentity name="TagLibSharp" publicKeyToken="db62eba44689b5b0" culture="neutral" />
4545
<bindingRedirect oldVersion="2.0.0.0-2.3.0.0" newVersion="2.2.0.0" />
4646
</dependentAssembly>
47-
<!--
48-
Comment out the following section when the ParatextData and FieldWorks versions of libpalaso converge
49-
-->
5047
<dependentAssembly>
51-
<assemblyIdentity name="SIL.Core" publicKeyToken="cab3c8c5232dfcf2" culture="neutral" />
52-
<!-- Until Chorus starts using libpalaso 13.0.0.0 -->
53-
<bindingRedirect oldVersion="0.0.0.0-17.0.0.0" newVersion="17.0.0.0" />
54-
</dependentAssembly>
55-
<dependentAssembly>
56-
<assemblyIdentity name="SIL.Lexicon" publicKeyToken="cab3c8c5232dfcf2" culture="neutral" />
57-
<!-- Until Chorus starts using libpalaso 13.0.0.0 -->
58-
<bindingRedirect oldVersion="0.0.0.0-17.0.0.0" newVersion="17.0.0.0" />
59-
</dependentAssembly>
60-
<dependentAssembly>
61-
<assemblyIdentity name="SIL.Scripture" publicKeyToken="cab3c8c5232dfcf2" culture="neutral" />
62-
<!-- Until Chorus starts using libpalaso 13.0.0.0 -->
63-
<bindingRedirect oldVersion="0.0.0.0-17.0.0.0" newVersion="17.0.0.0" />
64-
</dependentAssembly>
65-
<dependentAssembly>
66-
<assemblyIdentity name="SIL.TestUtilities" publicKeyToken="cab3c8c5232dfcf2" culture="neutral" />
67-
<!-- Until Chorus starts using libpalaso 13.0.0.0 -->
68-
<bindingRedirect oldVersion="0.0.0.0-17.0.0.0" newVersion="17.0.0.0" />
69-
</dependentAssembly>
70-
<dependentAssembly>
71-
<assemblyIdentity name="SIL.WritingSystems" publicKeyToken="cab3c8c5232dfcf2" culture="neutral" />
72-
<!-- Until Chorus starts using libpalaso 13.0.0.0 -->
73-
<bindingRedirect oldVersion="0.0.0.0-17.0.0.0" newVersion="17.0.0.0" />
74-
</dependentAssembly>
75-
<dependentAssembly>
76-
<assemblyIdentity name="Microsoft.Extensions.DependencyModel"
77-
publicKeyToken="adb9793829ddae60"
48+
<assemblyIdentity name="Microsoft.Extensions.DependencyModel"
49+
publicKeyToken="adb9793829ddae60"
7850
culture="neutral" />
7951
<bindingRedirect oldVersion="0.0.0.0-99.99.99.99" newVersion="8.0.0.0" />
8052
</dependentAssembly>

0 commit comments

Comments
 (0)