Files
2026-08-27 11:12:22 +02:00

318 lines
9.4 KiB
Bash
Executable File

#!/usr/bin/env bash
set -u
SCRIPT_DIR="$(
cd "$(dirname "${BASH_SOURCE[0]}")" &&
pwd
)"
SUITE="$(cd "$SCRIPT_DIR/../.." && pwd)"
DEFAULT_SOURCE="$SUITE/configurations/generated/S02/from_patcher/smoke_s2_binary_recording_from_s01.marte"
DEFAULT_OUTPUT="$SUITE/configurations/generated/S03/from_patcher/smoke_s3_semantic_sentinels_from_s02.marte"
SOURCE_CONFIG="${1:-$DEFAULT_SOURCE}"
OUTPUT_CONFIG="${2:-$DEFAULT_OUTPUT}"
EXPECTED_SOURCE_SHA256="8ecc8ba55e46e9f6fb8f304404d4070ff4c2ea5b9c783a69113bc08b457859d1"
EXPECTED_OUTPUT_SHA256="0f8916735ff67a9be2b167b52f147687fe131a063f410a81bfbf3bf9ff65f1e4"
fail() {
code="$1"
shift
printf "S03_BUILD_STATUS=FAIL\n" >&2
printf "S03_BUILD_ERROR=%s\n" "$*" >&2
exit "$code"
}
if [ ! -f "$SOURCE_CONFIG" ]; then
fail 20 "SOURCE_NOT_FOUND:$SOURCE_CONFIG"
fi
SOURCE_SHA256="$(sha256sum -- "$SOURCE_CONFIG" | awk '{print $1}')"
printf "SOURCE_CONFIG=%s\n" "$SOURCE_CONFIG"
printf "EXPECTED_SOURCE_SHA256=%s\n" "$EXPECTED_SOURCE_SHA256"
printf "ACTUAL_SOURCE_SHA256=%s\n" "$SOURCE_SHA256"
if [ "$SOURCE_SHA256" != "$EXPECTED_SOURCE_SHA256" ]; then
fail 21 "SOURCE_HASH_MISMATCH:$SOURCE_SHA256"
fi
OUTPUT_DIR="$(dirname "$OUTPUT_CONFIG")"
if ! mkdir -p -- "$OUTPUT_DIR"; then
fail 22 "OUTPUT_DIRECTORY_CREATE_FAILED:$OUTPUT_DIR"
fi
if [ -e "$OUTPUT_CONFIG" ] && [ ! -f "$OUTPUT_CONFIG" ]; then
fail 23 "OUTPUT_EXISTS_NOT_REGULAR_FILE:$OUTPUT_CONFIG"
fi
TEMP_CONFIG="$(mktemp "$OUTPUT_DIR/.s03_candidate_XXXXXX.marte")" ||
fail 24 "TEMPORARY_FILE_CREATE_FAILED:$OUTPUT_DIR"
cleanup() {
rm -f -- "$TEMP_CONFIG"
}
trap cleanup EXIT HUP INT TERM
PYTHONHASHSEED=0 python3 - "$SOURCE_CONFIG" "$TEMP_CONFIG" <<'PY_S03_PATCHER'
import sys
from pathlib import Path
source = Path(sys.argv[1])
destination = Path(sys.argv[2])
text = source.read_text(encoding="utf-8")
def require_count(haystack, needle, expected, label):
observed = haystack.count(needle)
print(f"{label}_COUNT={observed}")
if observed != expected:
raise RuntimeError(f"{label}_COUNT_EXPECTED_{expected}_OBSERVED_{observed}")
def replace_once(haystack, old, new, label):
require_count(haystack, old, 1, label)
return haystack.replace(old, new, 1)
for forbidden in (
"+SentinelProducer =",
"SentinelScalarA",
"SentinelScalarB",
"SentinelVector",
"s03_output.bin",
):
if forbidden in text:
raise RuntimeError("SOURCE_ALREADY_CONTAINS_S03_CONTENT:" + forbidden)
constant_gam = """ +SentinelProducer = {
Class = ConstantGAM
OutputSignals = {
SentinelScalarA = {
DataSource = DDB1
Type = uint32
NumberOfElements = 1
Default = 324508639
}
SentinelScalarB = {
DataSource = DDB1
Type = uint32
NumberOfElements = 1
Default = 610839776
}
SentinelVector = {
DataSource = DDB1
Type = uint32
NumberOfDimensions = 1
NumberOfElements = 3
Default = {16909060 286397204 555885348}
}
}
}
"""
sentinel_signals = """ SentinelScalarA = {
DataSource = DDB1
Type = uint32
NumberOfElements = 1
}
SentinelScalarB = {
DataSource = DDB1
Type = uint32
NumberOfElements = 1
}
SentinelVector = {
DataSource = DDB1
Type = uint32
NumberOfDimensions = 1
NumberOfElements = 3
}
"""
filewriter_signals = """ SentinelScalarA = {
Type = uint32
NumberOfElements = 1
}
SentinelScalarB = {
Type = uint32
NumberOfElements = 1
}
SentinelVector = {
Type = uint32
NumberOfDimensions = 1
NumberOfElements = 3
}
"""
text = replace_once(
text,
" +GAMDisplay = {\n",
constant_gam + " +GAMDisplay = {\n",
"SENTINEL_PRODUCER_ANCHOR",
)
writer_start = text.index(" +IOGAM_Writer = {\n")
writer_end = text.index(" }\n +Data = {\n", writer_start)
writer = text[writer_start:writer_end]
writer = replace_once(
writer,
""" State1_Thread1_CycleTime = {
Alias = State1.Thread1_CycleTime
DataSource = Timings
Type = uint32
}
}
OutputSignals = {
""",
""" State1_Thread1_CycleTime = {
Alias = State1.Thread1_CycleTime
DataSource = Timings
Type = uint32
}
""" + sentinel_signals + """ }
OutputSignals = {
""",
"WRITER_INPUT_ANCHOR",
)
writer_output_signals = sentinel_signals.replace("DataSource = DDB1", "DataSource = FileWriter")
writer = replace_once(
writer,
""" State1_Thread1_CycleTime = {
DataSource = FileWriter
Type = uint32
}
}
}
""",
""" State1_Thread1_CycleTime = {
DataSource = FileWriter
Type = uint32
}
""" + writer_output_signals + """ }
}
""",
"WRITER_OUTPUT_ANCHOR",
)
text = text[:writer_start] + writer + text[writer_end:]
filewriter_start = text.index(" +FileWriter = {\n")
filewriter_end = text.index(" }\n +States = {\n", filewriter_start)
filewriter = text[filewriter_start:filewriter_end]
filewriter = replace_once(
filewriter,
' Filename = "__MARTE_RUN_DIR__/s02_output.bin"\n',
' Filename = "__MARTE_RUN_DIR__/s03_output.bin"\n',
"OUTPUT_FILENAME_ANCHOR",
)
filewriter = replace_once(
filewriter,
""" State1_Thread1_CycleTime = {
Type = uint32
}
}
}
""",
""" State1_Thread1_CycleTime = {
Type = uint32
}
""" + filewriter_signals + """ }
}
""",
"FILEWRITER_SIGNAL_ANCHOR",
)
text = text[:filewriter_start] + filewriter + text[filewriter_end:]
text = replace_once(
text,
"Functions = {GAMTimer GAMDisplay IOGAM_Writer}",
"Functions = {GAMTimer SentinelProducer GAMDisplay IOGAM_Writer}",
"THREAD_FUNCTION_LIST_ANCHOR",
)
# Canonicalise the historical trailing blank line so the generated candidate
# is byte-stable across copies and archive extraction tools.
text = text.rstrip("\n") + "\n"
checks = {
"SENTINEL_PRODUCER": text.count("+SentinelProducer ="),
"CONSTANT_GAM_CLASS": text.count("Class = ConstantGAM"),
"SENTINEL_SCALAR_A_DECLARATIONS": text.count("SentinelScalarA ="),
"SENTINEL_SCALAR_B_DECLARATIONS": text.count("SentinelScalarB ="),
"SENTINEL_VECTOR_DECLARATIONS": text.count("SentinelVector ="),
"VECTOR_DIMENSION_DECLARATIONS": text.count("NumberOfDimensions = 1"),
"VECTOR_CARDINALITY_DECLARATIONS": text.count("NumberOfElements = 3"),
"S03_OUTPUT_FILENAME": text.count("__MARTE_RUN_DIR__/s03_output.bin"),
"S02_OUTPUT_FILENAME": text.count("s02_output.bin"),
"RUN_DIR_PLACEHOLDER": text.count("__MARTE_RUN_DIR__"),
"FILEWRITER_CLASS": text.count("Class = FileDataSource::FileWriter"),
"THREAD_FUNCTION_LIST": text.count(
"Functions = {GAMTimer SentinelProducer GAMDisplay IOGAM_Writer}"
),
}
expected = {
"SENTINEL_PRODUCER": 1,
"CONSTANT_GAM_CLASS": 1,
"SENTINEL_SCALAR_A_DECLARATIONS": 4,
"SENTINEL_SCALAR_B_DECLARATIONS": 4,
"SENTINEL_VECTOR_DECLARATIONS": 4,
"VECTOR_DIMENSION_DECLARATIONS": 4,
"VECTOR_CARDINALITY_DECLARATIONS": 4,
"S03_OUTPUT_FILENAME": 1,
"S02_OUTPUT_FILENAME": 0,
"RUN_DIR_PLACEHOLDER": 1,
"FILEWRITER_CLASS": 1,
"THREAD_FUNCTION_LIST": 1,
}
for name in sorted(checks):
print(f"{name}_COUNT={checks[name]}")
if checks != expected:
raise RuntimeError(f"STRUCTURAL_CHECK_MISMATCH:{checks!r}")
destination.write_text(text, encoding="utf-8")
print("TRANSFORM_STATUS=PASS")
print("STRUCTURAL_CHECK_STATUS=PASS")
PY_S03_PATCHER
PYTHON_RC=$?
if [ "$PYTHON_RC" -ne 0 ]; then
fail 25 "TRANSFORM_FAILED_RC_$PYTHON_RC"
fi
GENERATED_SHA256="$(sha256sum -- "$TEMP_CONFIG" | awk '{print $1}')"
printf "GENERATED_SHA256=%s\n" "$GENERATED_SHA256"
if [ "$GENERATED_SHA256" != "$EXPECTED_OUTPUT_SHA256" ]; then
fail 26 "OUTPUT_HASH_MISMATCH:$GENERATED_SHA256"
fi
if [ -f "$OUTPUT_CONFIG" ]; then
if cmp -s -- "$TEMP_CONFIG" "$OUTPUT_CONFIG"; then
printf "S03_BUILD_STATUS=ALREADY_VALID\n"
printf "OUTPUT_CONFIG=%s\n" "$OUTPUT_CONFIG"
printf "OUTPUT_SHA256=%s\n" "$GENERATED_SHA256"
exit 0
fi
fail 27 "OUTPUT_EXISTS_BUT_DIFFERS:$OUTPUT_CONFIG"
fi
chmod 0644 "$TEMP_CONFIG" || fail 28 "OUTPUT_CHMOD_FAILED:$TEMP_CONFIG"
if ! mv -- "$TEMP_CONFIG" "$OUTPUT_CONFIG"; then
fail 29 "ATOMIC_PUBLISH_FAILED:$OUTPUT_CONFIG"
fi
trap - EXIT HUP INT TERM
printf "S03_BUILD_STATUS=CREATED\n"
printf "OUTPUT_CONFIG=%s\n" "$OUTPUT_CONFIG"
printf "OUTPUT_SHA256=%s\n" "$GENERATED_SHA256"