Skip to content
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
4 changes: 3 additions & 1 deletion .github/workflows/build-windows-eh.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,11 @@ jobs:
build-cell:
name: ${{ matrix.cell_name }}
needs: verify-producer
runs-on: windows-2022
runs-on: ${{ matrix.runner }}
timeout-minutes: 30
strategy:
fail-fast: false
max-parallel: 6
matrix: ${{ fromJSON(needs.verify-producer.outputs.matrix) }}
steps:
- name: Check out producer sources
Expand All @@ -78,6 +79,7 @@ jobs:
-Optimization "${{ matrix.optimization }}"
-SecurityCookie "${{ matrix.security_cookie }}"
-CxxFormat "${{ matrix.cxx_format }}"
-VsYear "${{ matrix.vs_year }}"
-OutputRoot "${{ runner.temp }}/windows-eh"

- name: Upload validated cell
Expand Down
6 changes: 6 additions & 0 deletions corpus/windows-eh/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,9 @@ workflow after every successful producer change on `main`.
Do not add, rename, or replace PE files without the matching generated entry in
`manifests/windows-eh.json`. Every artifact name must identify its toolchain,
architecture, C++ EH format, security-cookie mode, and optimization mode.

MSVC cells are built for every Visual Studio year the GitHub-hosted runner can
install (VS 2022 on `windows-2022`, VS 2026 on `windows-2025`). VS 2010–2019
and a non-existent VS 2025 product are explicit producer skips, not silent
cells. VS 2022 keeps the historical path; later years use
`corpus/windows-eh/msvc/vs<year>/...`.
18 changes: 17 additions & 1 deletion schema/windows-eh-manifest.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,19 @@
"$ref": "#/$defs/gitRevision"
},
"runner_image": {
"$ref": "#/$defs/nonEmptyString"
"oneOf": [
{
"$ref": "#/$defs/nonEmptyString"
},
{
"type": "array",
"minItems": 1,
"uniqueItems": true,
"items": {
"$ref": "#/$defs/nonEmptyString"
}
}
]
},
"runner_arch": {
"const": "x64"
Expand Down Expand Up @@ -183,6 +195,10 @@
},
"linker_flags": {
"$ref": "#/$defs/stringArray"
},
"visual_studio_year": {
"type": "integer",
"enum": [2022, 2026]
}
},
"allOf": [
Expand Down
27 changes: 25 additions & 2 deletions scripts/Build-WindowsCorpus.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ param(
[ValidateSet("native", "fh3", "fh4")]
[string] $CxxFormat,

[Parameter(Mandatory = $false)]
[ValidateSet("2022", "2026")]
[string] $VsYear = "2022",

[Parameter(Mandatory = $true)]
[string] $OutputRoot,

Expand Down Expand Up @@ -78,7 +82,16 @@ if ($CxxFormat -notin $SupportedFormats) {
}

$CookieLabel = if ($SecurityCookie -eq "on") { "gs" } else { "no-gs" }
$CellName = "$Toolchain-$Architecture-$CxxFormat-$CookieLabel-$Optimization"
$ToolchainKey = if ($Toolchain -eq "msvc" -and $VsYear -ne "2022") {
"msvc-vs$VsYear"
} else {
$Toolchain
}
$CellName = "$ToolchainKey-$Architecture-$CxxFormat-$CookieLabel-$Optimization"
$VsWhereVersion = switch ($VsYear) {
"2026" { "[18.0,19.0)" }
default { "[17.0,18.0)" }
}
$Compiler = if ($Toolchain -eq "msvc") { "cl.exe" } else { "clang-cl.exe" }
$Linker = if ($Toolchain -eq "msvc") { "link.exe" } else { "lld-link.exe" }
$OptimizationFlag = if ($Optimization -eq "o2") { "/O2" } else { "/Od" }
Expand Down Expand Up @@ -176,6 +189,8 @@ if ($ValidateConfigurationOnly) {
[ordered]@{
cell_name = $CellName
toolchain = $Toolchain
vs_year = [int]$VsYear
vswhere_version = $VsWhereVersion
architecture = $Architecture
target_triple = $Target.target_triple
vs_arch = $Target.vs_arch
Expand All @@ -200,7 +215,11 @@ $SourceRoot = Join-Path $RepositoryRoot "sources"
$OfficialSourceRoot = Join-Path $SourceRoot "windows-seh-tests/src"
$ProbeSourceRoot = Join-Path $SourceRoot "msvc-exceptions"
$OutputRoot = [IO.Path]::GetFullPath($OutputRoot)
$CellRelativeRoot = "corpus/windows-eh/$Toolchain/$Architecture/$CxxFormat/$CookieLabel/$Optimization"
$CellRelativeRoot = if ($Toolchain -eq "msvc" -and $VsYear -ne "2022") {
"corpus/windows-eh/$Toolchain/vs$VsYear/$Architecture/$CxxFormat/$CookieLabel/$Optimization"
} else {
"corpus/windows-eh/$Toolchain/$Architecture/$CxxFormat/$CookieLabel/$Optimization"
}
$CellOutputRoot = Join-Path $OutputRoot $CellRelativeRoot
$OfficialOutputRoot = Join-Path $CellOutputRoot "windows-seh-tests"
$ProbeOutputRoot = Join-Path $CellOutputRoot "abi-probe"
Expand All @@ -226,6 +245,9 @@ function Import-VisualStudioEnvironment {
"-requires", $Target.component,
"-property", "installationPath"
)
if ($Toolchain -eq "msvc" -and $VsWhereVersion) {
$VsWhereArguments += @("-version", $VsWhereVersion)
}
$Installation = (& $VsWhere @VsWhereArguments | Select-Object -First 1)
if (-not $Installation) {
throw "a Visual Studio installation with $($Target.component) was not found"
Expand Down Expand Up @@ -546,6 +568,7 @@ function New-ArtifactRecord(
execution = if ($Target.execute) { "passed" } else { "not-run-cross-target" }
compiler_flags = @($script:CommonCompilerFlags + $AdditionalCompilerFlags)
linker_flags = @($script:CommonLinkerFlags + $AdditionalLinkerFlags)
visual_studio_year = [int]$VsYear
}
evidence = Get-Evidence $Name $Kind
neverd = Get-NeverDExpectation $Name $Kind
Expand Down
2 changes: 2 additions & 0 deletions scripts/tests/test_build_windows_corpus_script.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ def _configuration(self, cell) -> dict:
cell.security_cookie,
"-CxxFormat",
cell.cxx_format,
"-VsYear",
str(cell.vs_year),
"-OutputRoot",
temp_dir,
"-ValidateConfigurationOnly",
Expand Down
154 changes: 149 additions & 5 deletions scripts/tests/test_verify_windows_corpus.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,10 +209,14 @@ def _artifact_path(
security_cookie: bool,
optimization: str,
name: str = "cxx_eh_probe",
vs_year: int = 2022,
) -> Path:
suite = "abi-probe" if name.endswith("_probe") else "windows-seh-tests"
extension = ".dll" if name == "xframe_eh_dll" else ".exe"
cookie_label = "gs" if security_cookie else "no-gs"
toolchain_dir = toolchain
if toolchain == "msvc" and vs_year != 2022:
toolchain_dir = f"{toolchain}/vs{vs_year}"
filename = (
"-".join(
(
Expand All @@ -229,7 +233,7 @@ def _artifact_path(
return (
root
/ "corpus/windows-eh"
/ toolchain
/ toolchain_dir
/ architecture
/ cxx_format
/ cookie_label
Expand All @@ -249,6 +253,9 @@ def _valid_manifest(
security_cookie: bool = False,
optimization: str = "o0",
name: str = "cxx_eh_probe",
vs_year: int = 2022,
runner_image: str = "windows-2022",
repository_revision: str = "1" * 40,
) -> dict:
payload = artifact.read_bytes()
is_x64 = architecture == "x86_64"
Expand Down Expand Up @@ -292,8 +299,8 @@ def _valid_manifest(
}
},
"producer": {
"repository_revision": "1" * 40,
"runner_image": "windows-2022",
"repository_revision": repository_revision,
"runner_image": runner_image,
"runner_arch": "x64",
},
"artifacts": [
Expand Down Expand Up @@ -323,6 +330,7 @@ def _valid_manifest(
"optimization": optimization,
"security_cookie": security_cookie,
"cxx_format": cxx_format,
"visual_studio_year": vs_year,
"execution": execution,
"compiler_flags": compiler_flags,
"linker_flags": [
Expand Down Expand Up @@ -374,6 +382,7 @@ def _complete_inventory() -> dict:
security_cookie=security_cookie,
optimization=cell.optimization,
name=name,
vs_year=cell.vs_year,
).as_posix(),
"architecture": cell.architecture,
"name": name,
Expand All @@ -382,6 +391,7 @@ def _complete_inventory() -> dict:
"optimization": cell.optimization,
"security_cookie": security_cookie,
"cxx_format": cell.cxx_format,
"visual_studio_year": cell.vs_year,
},
}
)
Expand All @@ -408,6 +418,30 @@ def test_accepts_schema_v2_x64_artifact(self) -> None:
self.assertEqual(result.artifact_count, 1)
self.assertEqual(result.total_bytes, artifact.stat().st_size)

def test_accepts_vs2026_msvc_layout(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
artifact = _artifact_path(
root,
toolchain="msvc",
architecture="x86_64",
cxx_format="fh4",
security_cookie=False,
optimization="o0",
vs_year=2026,
)
_write_minimal_pe(artifact, import_names=("__CxxFrameHandler4",))
manifest = _valid_manifest(
root, artifact, cxx_format="fh4", vs_year=2026
)
self.assertIn("/msvc/vs2026/", manifest["artifacts"][0]["path"])
self.assertNotIn("vs2026", Path(manifest["artifacts"][0]["path"]).name)
manifest_path = _write_manifest(root, manifest)

result = VERIFY.verify_manifest(manifest_path, root)

self.assertEqual(result.artifact_count, 1)

def test_accepts_all_four_pe_machine_targets(self) -> None:
combinations = (
("x86", "native", "load-only"),
Expand Down Expand Up @@ -851,13 +885,13 @@ def test_rejects_hash_mismatch(self) -> None:
with self.assertRaisesRegex(VERIFY.VerificationError, "SHA-256 mismatch"):
VERIFY.verify_manifest(manifest_path, root)

def test_complete_matrix_accepts_32_cells_and_168_capability_artifacts(
def test_complete_matrix_accepts_48_cells_and_264_capability_artifacts(
self,
) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
manifest = _complete_inventory()
self.assertEqual(len(manifest["artifacts"]), 168)
self.assertEqual(len(manifest["artifacts"]), 264)
manifest_path = _write_manifest(root, manifest)

VERIFY.verify_complete_matrix(manifest_path)
Expand Down Expand Up @@ -911,6 +945,116 @@ def test_merges_msvc_and_clang_cl_fragments(self) -> None:
{entry["build"]["toolchain"] for entry in merged["artifacts"]},
{"msvc", "clang-cl"},
)
self.assertEqual(merged["producer"]["runner_image"], "windows-2022")

def test_merges_fragments_built_on_different_runner_images(self) -> None:
"""VS 2022 cells run on windows-2022 and VS 2026 cells on windows-2025.
GitHub also does not promise two jobs of one workflow land on the same
image version. That is a fact about the pool, not a sign the fragments
came from different producer runs, so the merge has to accept it and
keep both images on record.
"""

with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
fragments = []
cells = (
(2022, "fh3", "__CxxFrameHandler3", "win22-20260802.262.1"),
(2026, "fh4", "__CxxFrameHandler4", "win25-20260917.1.1"),
)
for vs_year, cxx_format, personality, runner_image in cells:
artifact = _artifact_path(
root,
toolchain="msvc",
architecture="x86_64",
cxx_format=cxx_format,
security_cookie=False,
optimization="o0",
vs_year=vs_year,
)
_write_minimal_pe(artifact, import_names=(personality,))
manifest = _valid_manifest(
root,
artifact,
cxx_format=cxx_format,
vs_year=vs_year,
runner_image=runner_image,
)
fragment = root / "fragments" / f"msvc-vs{vs_year}.json"
fragment.parent.mkdir(parents=True, exist_ok=True)
fragment.write_text(json.dumps(manifest), encoding="utf-8")
fragments.append(fragment)

output = root / "manifests/windows-eh.json"
result = VERIFY.merge_manifests(fragments, output, root)

self.assertEqual(result.artifact_count, 2)
merged = json.loads(output.read_text(encoding="utf-8"))
self.assertEqual(
merged["producer"]["runner_image"],
["win22-20260802.262.1", "win25-20260917.1.1"],
)
self.assertEqual(
{
entry["build"]["visual_studio_year"]
for entry in merged["artifacts"]
},
{2022, 2026},
)

def test_rejects_fragments_from_different_producer_runs(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
fragments = []
for index, toolchain in enumerate(("msvc", "clang-cl")):
artifact = _artifact_path(
root,
toolchain=toolchain,
architecture="x86_64",
cxx_format="fh3",
security_cookie=False,
optimization="o0",
)
_write_minimal_pe(artifact, import_names=("__CxxFrameHandler3",))
manifest = _valid_manifest(
root,
artifact,
toolchain=toolchain,
repository_revision=str(index + 1) * 40,
)
fragment = root / "fragments" / f"{toolchain}.json"
fragment.parent.mkdir(parents=True, exist_ok=True)
fragment.write_text(json.dumps(manifest), encoding="utf-8")
fragments.append(fragment)

output = root / "manifests/windows-eh.json"
with self.assertRaisesRegex(
VERIFY.VerificationError, "inconsistent envelopes"
):
VERIFY.merge_manifests(fragments, output, root)

def test_accepts_unioned_runner_image_array(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
artifact = _artifact_path(
root,
toolchain="msvc",
architecture="x86_64",
cxx_format="fh3",
security_cookie=False,
optimization="o0",
)
_write_minimal_pe(artifact, import_names=("__CxxFrameHandler3",))
manifest = _valid_manifest(root, artifact)
manifest["producer"]["runner_image"] = [
"win22-20260802.262.1",
"win25-20260917.1.1",
]
manifest_path = _write_manifest(root, manifest)

result = VERIFY.verify_manifest(manifest_path, root)

self.assertEqual(result.artifact_count, 1)


if __name__ == "__main__":
Expand Down
Loading
Loading