Skip to content

COMP: Build and test on Windows - #82

Merged
hjmjohnson merged 2 commits into
masterfrom
ci/windows
Sep 22, 2026
Merged

hjmjohnson merged 2 commits into
masterfrom
ci/windows

Conversation

@hjmjohnson

@hjmjohnson hjmjohnson commented Sep 22, 2026 •

Copy link
Copy Markdown
Member

Adds a Windows leg, static and shared, so the WIN32 and _MSC_VER paths are compiled by CI for the first time — and fixes the one defect that exposed.

Two commits, one file each, separable:

Commit File What
a007745 .github/workflows/cmake-multi-platform.yml the Windows job
79a5a40 cifti/afni_xml.h the defect the job found
What the leg found: the cifti DLL was missing half its entry points

The cifti library is built from two sources with two public headers. afni_xml_io.h decorates its declarations with CIF_API; afni_xml.h decorated none of its own. On Windows that produced a DLL with every axml_ entry point absent, and the library's own tools could not link against it:

cifti_tool.obj    : error LNK2019: unresolved external symbol axml_free_xml_t
afni_xml_tool.obj : error LNK2019: unresolved external symbol axml_read_file
... fatal error LNK1120: 5 unresolved externals

afni_xml.h was the only undecorated public header in the project. For comparison, after the fix: nifti2_io.h 134 decorated declarations, nifti1_io.h 103, nifticdf.h 89, fslio.h 70, afni_xml.h 21, afni_xml_io.h 10, znzlib.h 13.

The CIF_API definition moves to afni_xml.h, which afni_xml_io.h includes at its top, so one definition serves both headers instead of each carrying its own copy. One macro per library is this project's existing convention — ZNZ_API, NIO_API, NCDF_API, FSL_API, NI2_API — and both these headers belong to the same library, so they must share one.

No ABI effect off Windows. CIF_API expands to __attribute__((visibility("default"))) on ELF and Mach-O, and this project sets no -fvisibility=hidden policy, so the attribute is a no-op there. Verified: the exported symbol set of the shared build is byte-identical before and after, and the exported symbol baseline job agrees.

Why a Windows leg at all

Twelve source files carry WIN32 or _MSC_VER guards and no workflow compiled them, so a change breaking the Windows path was invisible here and would surface only when a consumer reported it. This library is vendored into ITK, AFNI, FSL, dcm2niix and Slicer, several of which ship on Windows.

The immediate trigger was concrete. A review of #41 found that its new test target guards on if(NIFTI_BUILD_TESTING AND ZLIB_FOUND) while the function it tests is compiled under #if !defined (WIN32), so the target would configure and then fail to link. That was an inference from reading guards; with this leg it becomes a red check. Adding the leg first also keeps the two findings attributable — landing #41 first would have mixed its guard omission in with this DLL defect.

Build choices

Both linkages. The ZNZ_API and NIFTI_API decorations differ between them and only the shared build exercises dllexport — which is precisely how the cifti defect surfaced. Static alone would have passed and found nothing.

zlib and expat from vcpkg, which the runner image already carries. find_package(ZLIB REQUIRED) at CMakeLists.txt:109 leaves no zlib-free configuration.

The toolchain path is resolved in PowerShell, not by a GitHub expression. VCPKG_INSTALLATION_ROOT is set by the runner image rather than by the workflow, so the env expression context expands it to nothing. The step now reads it in the shell that knows it and fails with the path it tried, rather than as a CMake error several frames removed from the cause.

--no-tests=ignore on the ctest step, matching the minimal job.

Scope of the evidence

A successful link proves the symbols the in-tree tools actually use are exported — no more. A function declared in a header but called by no tool could still be undecorated and silently missing from a DLL without the build noticing. That is exactly the blind spot that let this defect survive: it surfaced only because the cifti tools happened to call the missing symbols.

Closing that gap properly would mean comparing each DLL's export table against its header's declared set — a Windows analogue of the existing exported symbol baseline job. Worth a follow-up rather than expanding this PR.

@hjmjohnson
hjmjohnson marked this pull request as ready for review September 22, 2026 14:33
Twelve source files carry WIN32 or _MSC_VER guards and no workflow has
ever compiled them, so a change that breaks the Windows path is invisible
here and surfaces only when a consumer reports it.

Static and shared, because the ZNZ_API and NIFTI_API decorations differ
between them and only the shared build exercises the dllexport path.
zlib and expat come from vcpkg, which the runner image already carries.

VCPKG_INSTALLATION_ROOT is set by the runner image rather than by the
workflow, so the toolchain path is read in PowerShell and checked before
cmake runs, which reports a missing toolchain as itself rather than as a
CMake error several lines removed from the cause.
afni_xml_io.h decorates its declarations with CIF_API and afni_xml.h
decorated none of its own, so a Windows shared build produced a DLL
missing every axml_ entry point and both cifti tools failed to link
against the library they are built with.

The macro definition moves to afni_xml.h, which afni_xml_io.h includes
at its top, so one definition now serves both headers rather than each
carrying its own. Nothing changes where the attribute expands to default
visibility: the exported set of the shared build is byte-identical.

Found by the Windows job added in the preceding commit.
@hjmjohnson hjmjohnson self-assigned this Sep 22, 2026
@hjmjohnson
hjmjohnson merged commit 63b361a into master Sep 22, 2026
25 checks passed
@hjmjohnson
hjmjohnson deleted the ci/windows branch September 22, 2026 14:52
@seanm

seanm commented Sep 22, 2026

Copy link
Copy Markdown
Collaborator

@hjmjohnson Why are you merging PRs with no one reviewing them?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants