Skip to content

Commit 338e5db

Browse files
author
Grok Compression
committed
CI: OS specific md5 files
1 parent c393645 commit 338e5db

6 files changed

Lines changed: 231 additions & 19 deletions

File tree

.github/workflows/build.yml

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -107,22 +107,16 @@ jobs:
107107
- name: test
108108
working-directory: ${{ github.workspace }}/build
109109
shell: bash
110-
continue-on-error: true # Allow test failures, but no other failures
111110
run: |
112111
ctest --output-on-failure -C $BUILD_TYPE --output-junit test-results-${{ matrix.os }}-${{ matrix.shared_libs_flag }}.xml
113-
if [ $? -eq 0 ]; then
114-
echo "Tests passed" > test-status.txt
115-
else
116-
echo "Tests had failures (ignored due to continue-on-error)" > test-status.txt
117-
fi
118112
- name: Upload test results
119-
if: always() # Upload even if tests fail
113+
if: always()
120114
uses: actions/upload-artifact@v4
121115
with:
122-
name: test-results-${{ matrix.os }}-${{ matrix.shared_libs_flag }}
116+
name: test-results-${{ matrix.os }}-shared_${{ matrix.shared_libs_flag }}
123117
path: |
124118
${{ github.workspace }}/build/test-results-${{ matrix.os }}-${{ matrix.shared_libs_flag }}.xml
125-
${{ github.workspace }}/build/test-status.txt
119+
${{ github.workspace }}/build/Testing/Temporary/LastTest.log
126120
- name: package
127121
if: ${{ matrix.shared_libs_flag == 'ON' }}
128122
working-directory: ${{ github.workspace }}/build
@@ -132,6 +126,7 @@ jobs:
132126
cmake --build . --config $BUILD_TYPE --target package
133127
7z x grok-${{ matrix.os }}.zip
134128
- name: publish
129+
if: ${{ matrix.shared_libs_flag == 'ON' }}
135130
uses: actions/upload-artifact@v4
136131
with:
137132
name: grok-${{ matrix.os }}

bindings/swig/CMakeLists.txt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,23 @@ set(GROK_CODEC_INCLUDE_PATH "${CMAKE_SOURCE_DIR}/src/lib/codec")
1414

1515
# --- Python bindings ---
1616
if(GRK_BUILD_PYTHON_SWIG)
17+
# Python extension modules MUST use the dynamic C++ runtime even in static builds.
18+
# ucm_set_runtime(STATIC) adds -static-libstdc++ / -static-libgcc to CMAKE_CXX_FLAGS
19+
# globally. Embedding a private copy inside a .so that Python dlopen's causes
20+
# duplicate RTTI / global state alongside Python's own libstdc++ -> segfault.
21+
# Strip these flags at directory scope so all SWIG MODULE targets link dynamically.
22+
if(NOT BUILD_SHARED_LIBS AND CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND UNIX)
23+
string(REGEX REPLACE "-static-libstdc\\+\\+" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
24+
string(REGEX REPLACE "-static-libgcc" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
25+
foreach(_config ${CMAKE_CONFIGURATION_TYPES} ${CMAKE_BUILD_TYPE})
26+
if(_config)
27+
string(TOUPPER "${_config}" _CONFIG)
28+
string(REGEX REPLACE "-static-libstdc\\+\\+" "" CMAKE_CXX_FLAGS_${_CONFIG} "${CMAKE_CXX_FLAGS_${_CONFIG}}")
29+
string(REGEX REPLACE "-static-libgcc" "" CMAKE_CXX_FLAGS_${_CONFIG} "${CMAKE_CXX_FLAGS_${_CONFIG}}")
30+
endif()
31+
endforeach()
32+
endif()
33+
1734
find_package(Python3 COMPONENTS Interpreter Development REQUIRED)
1835
include_directories(${Python3_INCLUDE_DIRS})
1936

@@ -96,6 +113,7 @@ if(GRK_BUILD_PYTHON_SWIG)
96113
COMMENT "Copying grok_codec.py to bin directory"
97114
)
98115
endif()
116+
99117
endif()
100118

101119
# --- C# bindings ---

tests/nonregression/CMakeLists.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,10 +198,17 @@ function(add_decoder_test EXE_NAME ARGS INPUT_FILENAME OUTPUT_FILENAME TEST_NUM
198198
else()
199199
if(NOT IS_CORE_DEC)
200200
# MD5 Check: Verify the decoded output against registered md5 checksums
201+
# Build a platform key: e.g. "Linux", "Linux-static", "Darwin", "Windows"
202+
if(BUILD_SHARED_LIBS)
203+
set(_md5_platform "${CMAKE_SYSTEM_NAME}")
204+
else()
205+
set(_md5_platform "${CMAKE_SYSTEM_NAME}-static")
206+
endif()
201207
add_test(NAME NR-DEC-${INPUT_FILENAME_NAME}-${TEST_NUM}-decode-md5
202208
COMMAND ${CMAKE_COMMAND}
203209
-DREFFILE:STRING=${CMAKE_CURRENT_SOURCE_DIR}/md5refs.txt
204210
-DOUTFILENAME:STRING=${OUTPUT_FILENAME}
211+
-DSYSTEM_NAME:STRING=${_md5_platform}
205212
-P ${CMAKE_CURRENT_SOURCE_DIR}/checkmd5refs.cmake)
206213
set_tests_properties(NR-DEC-${INPUT_FILENAME_NAME}-${TEST_NUM}-decode-md5
207214
PROPERTIES DEPENDS NR-DEC-${INPUT_FILENAME_NAME}-${TEST_NUM}-decode)

tests/nonregression/checkmd5refs.cmake

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,28 @@
3232
# we do not really care about the exact pixel value, we simply need to known
3333
# when a code change impact output generation.
3434

35-
# This script expect two inputs
36-
# REFFILE: Path to the md5sum.txt file
37-
# OUTFILENAME: The name of the generated file we want to check The script will
38-
# check whether a PGX or a PNG file was generated in the test suite (computed
39-
# from OUTFILENAME)
35+
# This script expects:
36+
# REFFILE: Path to the canonical md5refs.txt file
37+
# OUTFILENAME: The name of the generated file to check
38+
# SYSTEM_NAME: (optional) CMAKE_SYSTEM_NAME — used to select a platform-specific
39+
# md5refs-<SYSTEM_NAME>.txt that overrides REFFILE when it exists.
40+
# This lets Linux/macOS/Windows carry different checksums for
41+
# floating-point-sensitive codecs (e.g. 9/7 wavelet) without
42+
# requiring separate branches.
43+
44+
# Resolve the active reference file: prefer platform-specific override.
45+
set(ACTIVE_REFFILE "${REFFILE}")
46+
if(SYSTEM_NAME)
47+
get_filename_component(_refdir "${REFFILE}" DIRECTORY)
48+
get_filename_component(_refname "${REFFILE}" NAME_WE)
49+
get_filename_component(_refext "${REFFILE}" EXT)
50+
set(_platform_reffile "${_refdir}/${_refname}-${SYSTEM_NAME}${_refext}")
51+
if(EXISTS "${_platform_reffile}")
52+
set(ACTIVE_REFFILE "${_platform_reffile}")
53+
message(STATUS "Using platform-specific MD5 refs: ${_platform_reffile}")
54+
endif()
55+
endif()
56+
4057
# Extract filename without extension
4158
get_filename_component(OUTFILENAME_NAME_WE ${OUTFILENAME} NAME_WE)
4259

@@ -53,8 +70,8 @@ if(NOT globfiles)
5370
message(SEND_ERROR "Could not find output PGX/PGM/PNG/BMP/TIF files: ${OUTFILENAME_NAME_WE}")
5471
endif()
5572

56-
# Read the reference file (REFFILE) content to variable
57-
file(READ ${REFFILE} ref_content)
73+
# Read the active reference file content
74+
file(READ ${ACTIVE_REFFILE} ref_content)
5875

5976
# Loop through all globbed files and compare MD5 hash
6077
foreach(file_path ${globfiles})
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Download LastTest.log artifacts from the most recent GitHub Actions CI run
4+
and update the platform-specific md5refs files.
5+
6+
Requirements: gh CLI authenticated (gh auth login).
7+
8+
Usage:
9+
python tests/nonregression/collect_ci_md5refs.py
10+
python tests/nonregression/collect_ci_md5refs.py --run-id 1234567890
11+
python tests/nonregression/collect_ci_md5refs.py --dry-run
12+
"""
13+
14+
import argparse
15+
import json
16+
import subprocess
17+
import sys
18+
import tempfile
19+
from pathlib import Path
20+
21+
REPO = "GrokImageCompression/grok"
22+
SCRIPT_DIR = Path(__file__).parent
23+
24+
# Maps artifact name pattern -> platform key for update_md5refs.py
25+
# Artifact names: test-results-<os>-shared_<ON|OFF>
26+
ARTIFACT_PLATFORM_MAP = {
27+
("macos-latest", "ON"): "Darwin",
28+
("macos-latest", "OFF"): "Darwin-static",
29+
("windows-latest","ON"): "Windows",
30+
("windows-latest","OFF"): "Windows-static",
31+
("ubuntu-latest", "ON"): None, # canonical md5refs.txt — skip
32+
("ubuntu-latest", "OFF"): "Linux-static",
33+
}
34+
35+
36+
def run(cmd, **kwargs):
37+
return subprocess.run(cmd, check=True, text=True, capture_output=True, **kwargs)
38+
39+
40+
def get_latest_run_id(repo):
41+
result = run(["gh", "run", "list", "--repo", repo, "--workflow", "build.yml",
42+
"--limit", "1", "--json", "databaseId"])
43+
runs = json.loads(result.stdout)
44+
if not runs:
45+
sys.exit("No completed runs found for build.yml")
46+
return str(runs[0]["databaseId"])
47+
48+
49+
def list_artifacts(repo, run_id):
50+
result = run(["gh", "run", "view", run_id, "--repo", repo, "--json", "jobs"])
51+
# list artifacts via api
52+
result = run(["gh", "api", f"repos/{repo}/actions/runs/{run_id}/artifacts",
53+
"--paginate"])
54+
data = json.loads(result.stdout)
55+
return data.get("artifacts", [])
56+
57+
58+
def download_artifact(repo, artifact_id, dest_dir):
59+
run(["gh", "api", f"repos/{repo}/actions/artifacts/{artifact_id}/zip",
60+
"--header", "Accept: application/vnd.github+json",
61+
"-H", "X-GitHub-Api-Version: 2022-11-28"],
62+
**{"capture_output": False}) # won't work — need gh run download
63+
64+
65+
def main():
66+
parser = argparse.ArgumentParser(description=__doc__,
67+
formatter_class=argparse.RawDescriptionHelpFormatter)
68+
parser.add_argument("--run-id", help="Specific GitHub Actions run ID (default: latest)")
69+
parser.add_argument("--repo", default=REPO)
70+
parser.add_argument("--dry-run", action="store_true",
71+
help="Show what would be done without modifying md5refs files")
72+
args = parser.parse_args()
73+
74+
# Check gh is available
75+
try:
76+
run(["gh", "auth", "status"])
77+
except (subprocess.CalledProcessError, FileNotFoundError):
78+
sys.exit("gh CLI not found or not authenticated. Run: gh auth login")
79+
80+
run_id = args.run_id or get_latest_run_id(args.repo)
81+
print(f"Using run ID: {run_id}")
82+
print(f"https://github.com/{args.repo}/actions/runs/{run_id}")
83+
84+
with tempfile.TemporaryDirectory(prefix="grk_md5_") as tmpdir:
85+
tmp = Path(tmpdir)
86+
87+
# Download all test-results artifacts for this run
88+
print("\nDownloading artifacts...")
89+
try:
90+
run(["gh", "run", "download", run_id,
91+
"--repo", args.repo,
92+
"--pattern", "test-results-*",
93+
"--dir", str(tmp)])
94+
except subprocess.CalledProcessError as e:
95+
sys.exit(f"Failed to download artifacts:\n{e.stderr}")
96+
97+
# Each artifact lands in tmp/<artifact-name>/
98+
for artifact_dir in sorted(tmp.iterdir()):
99+
if not artifact_dir.is_dir():
100+
continue
101+
name = artifact_dir.name
102+
103+
# Parse artifact name; two historical formats:
104+
# test-results-<os>-shared_<ON|OFF> (new)
105+
# test-results-<os>-<ON|OFF> (old)
106+
prefix = "test-results-"
107+
if not name.startswith(prefix):
108+
continue
109+
rest = name[len(prefix):] # e.g. "macos-latest-shared_ON" or "macos-latest-ON"
110+
111+
if "-shared_" in rest:
112+
os_name, shared_part = rest.rsplit("-shared_", 1)
113+
elif rest.endswith("-ON") or rest.endswith("-OFF"):
114+
shared_part = rest.rsplit("-", 1)[1]
115+
os_name = rest[: -(len(shared_part) + 1)]
116+
else:
117+
print(f" Skipping unrecognised artifact: {name}")
118+
continue
119+
shared_flag = shared_part # ON or OFF
120+
121+
platform_key = ARTIFACT_PLATFORM_MAP.get((os_name, shared_flag))
122+
if platform_key is None:
123+
print(f" Skipping {name} (canonical Linux refs — update md5refs.txt manually if needed)")
124+
continue
125+
126+
log_path = artifact_dir / "Testing" / "Temporary" / "LastTest.log"
127+
if not log_path.exists():
128+
print(f" WARNING: no LastTest.log in {name}")
129+
continue
130+
131+
print(f"\n--- {name} -> platform key: {platform_key} ---")
132+
cmd = [sys.executable, str(SCRIPT_DIR / "update_md5refs.py"),
133+
"--platform", platform_key, str(log_path)]
134+
if args.dry_run:
135+
print(f" [dry-run] would run: {' '.join(cmd)}")
136+
else:
137+
result = subprocess.run(cmd, text=True)
138+
if result.returncode != 0:
139+
print(f" WARNING: update_md5refs.py exited {result.returncode}")
140+
141+
if args.dry_run:
142+
print("\n[dry-run] No files were modified.")
143+
else:
144+
print("\nDone. Commit the updated md5refs-*.txt files:")
145+
for key in sorted(set(v for v in ARTIFACT_PLATFORM_MAP.values() if v)):
146+
ref = SCRIPT_DIR / f"md5refs-{key}.txt"
147+
if ref.exists():
148+
print(f" git add {ref.relative_to(Path.cwd()) if ref.is_relative_to(Path.cwd()) else ref}")
149+
150+
151+
if __name__ == "__main__":
152+
main()

tests/nonregression/update_md5refs.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,17 +99,40 @@ def update_md5refs(md5refs_path, mismatches):
9999

100100

101101
def main():
102+
import argparse
103+
104+
parser = argparse.ArgumentParser(
105+
description="Update md5refs.txt (or a platform-specific override) from ctest LastTest.log"
106+
)
107+
parser.add_argument("files", nargs="*", help="Optional: path to LastTest.log or md5refs.txt")
108+
parser.add_argument(
109+
"--platform",
110+
metavar="NAME",
111+
help=(
112+
"Write to md5refs-NAME.txt instead of md5refs.txt. "
113+
"Use the platform key set by CMake: Darwin, Windows, Linux, "
114+
"Linux-static, Darwin-static, Windows-static. "
115+
"Example: --platform Linux-static"
116+
),
117+
)
118+
args = parser.parse_args()
119+
102120
log_path = LOG_PATH
103121
md5refs_path = MD5REFS_PATH
104122

105-
# Allow overrides from command line
106-
for arg in sys.argv[1:]:
123+
for arg in args.files:
107124
p = Path(arg)
108125
if p.name == "LastTest.log":
109126
log_path = p
110-
elif p.name == "md5refs.txt":
127+
elif "md5refs" in p.name:
111128
md5refs_path = p
112129

130+
if args.platform:
131+
md5refs_path = md5refs_path.parent / f"md5refs-{args.platform}.txt"
132+
if not md5refs_path.exists():
133+
md5refs_path.write_text("")
134+
print(f"Created new file: {md5refs_path}")
135+
113136
if not log_path.exists():
114137
sys.exit(f"Log file not found: {log_path}")
115138
if not md5refs_path.exists():

0 commit comments

Comments
 (0)