From 7ae8e7665fbd804cbe47ebde9cf58a377c1733fc Mon Sep 17 00:00:00 2001 From: InitusNovus Date: Mon, 25 Aug 2025 10:07:31 +0900 Subject: [PATCH 01/12] chore: create local development branch and commit current workspace --- README.ko.md | 6 ++++++ README.md | 6 ++++++ src/Generator/Codegen.Message.fs | 7 +++++++ src/Generator/Codegen.Registry.fs | 6 +++--- 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/README.ko.md b/README.ko.md index a0ad1ad..5ba2326 100644 --- a/README.ko.md +++ b/README.ko.md @@ -146,6 +146,12 @@ dispatch: binary_search crc_counter_check: false ``` +```yaml +# 공통 생성 파일 접두사 설정(이름 충돌 회피) +# 결과: gen/include/sc_registry.h, gen/src/sc_registry.c 등 +file_prefix: sc_ +``` + ### 구성 파일 사용 ```bash diff --git a/README.md b/README.md index bac5f32..cc8ded9 100644 --- a/README.md +++ b/README.md @@ -150,6 +150,12 @@ dispatch: binary_search crc_counter_check: false ``` +```yaml +# Prefix common generated files to avoid name collisions +# yields: gen/include/sc_registry.h, gen/src/sc_registry.c, etc. +file_prefix: sc_ +``` + ### Using a config ```bash diff --git a/src/Generator/Codegen.Message.fs b/src/Generator/Codegen.Message.fs index f69f229..15ef23e 100644 --- a/src/Generator/Codegen.Message.fs +++ b/src/Generator/Codegen.Message.fs @@ -129,6 +129,11 @@ module Message = let messageHPath = Path.Combine(outputPath, "include", sprintf "%s.h" messageNameLower) let messageCPath = Path.Combine(outputPath, "src", sprintf "%s.c" messageNameLower) + // Banner for traceability + let banner = + sprintf "/* Generated by Signal CANdy\n file_prefix=%s, phys_type=%s, phys_mode=%s, dispatch=%s, motorola_start_bit=%s */\n" + config.FilePrefix config.PhysType config.PhysMode config.Dispatch config.MotorolaStartBit + let signalDeclarationsH = message.Signals |> List.map fieldDecl |> String.concat "\n" @@ -226,6 +231,7 @@ module Message = let headerContent = let headerLines = System.Collections.Generic.List() + headerLines.Add(banner) headerLines.Add (sprintf "#ifndef %s_H" (message.Name.ToUpperInvariant())) headerLines.Add (sprintf "#define %s_H" (message.Name.ToUpperInvariant())) headerLines.Add "" @@ -284,6 +290,7 @@ module Message = let sourceContent = let src = System.Collections.Generic.List() + src.Add(banner) src.Add (sprintf "#include \"%s.h\"" messageNameLower) let utilsHeader = Utils.utilsHeaderName config src.Add (sprintf "#include \"%s\"" utilsHeader) diff --git a/src/Generator/Codegen.Registry.fs b/src/Generator/Codegen.Registry.fs index d00c53e..cc82dd0 100644 --- a/src/Generator/Codegen.Registry.fs +++ b/src/Generator/Codegen.Registry.fs @@ -17,8 +17,9 @@ module Registry = |> Seq.map (fun ch -> if System.Char.IsLetterOrDigit ch then ch else '_') |> Seq.toArray |> fun arr -> new string(arr) + let banner = sprintf "/* Generated by Signal CANdy\n file_prefix=%s, phys_type=%s, phys_mode=%s, dispatch=%s, motorola_start_bit=%s */\n" config.FilePrefix config.PhysType config.PhysMode config.Dispatch config.MotorolaStartBit let registryHContent = - sprintf "#ifndef %s\n#define %s\n\n#include \n#include \n\nbool decode_message(uint32_t id, const uint8_t data[], uint8_t dlc, void* msg);\n\n#endif // %s" guard guard guard + banner + sprintf "#ifndef %s\n#define %s\n\n#include \n#include \n\nbool decode_message(uint32_t id, const uint8_t data[], uint8_t dlc, void* msg);\n\n#endif // %s" guard guard guard File.WriteAllText(registryHPath, registryHContent) let includes = @@ -45,7 +46,6 @@ module Registry = let search = "bool decode_message(uint32_t id, const uint8_t data[], uint8_t dlc, void* msg) {\n int low = 0;\n int high = (int)(sizeof(decoders) / sizeof(decoder_entry_t)) - 1;\n while (low <= high) {\n int mid = low + (high - low) / 2;\n if (decoders[mid].id == id) {\n return decoders[mid].func(msg, data, dlc);\n }\n if (decoders[mid].id < id) low = mid + 1; else high = mid - 1;\n }\n return false;\n}\n" table + search - - let finalC = "#include \n#include \n#include \"" + regHName + "\"\n" + includes + "\n\n" + body + let finalC = banner + "#include \n#include \n#include \"" + regHName + "\"\n" + includes + "\n\n" + body File.WriteAllText(registryCPath, finalC) () \ No newline at end of file From b0d95a49416c9941f9070d86970e110283fbe666 Mon Sep 17 00:00:00 2001 From: InitusNovus Date: Mon, 25 Aug 2025 10:28:44 +0900 Subject: [PATCH 02/12] feat(cli): add --prefix and --emit-main flags; add traceability banners to utils/registry; docs updated --- src/Generator/Codegen.Utils.fs | 6 ++++-- src/Generator/Codegen.fs | 13 ++++++------ src/Generator/Program.fs | 36 +++++++++++++++++++++++++--------- 3 files changed, 38 insertions(+), 17 deletions(-) diff --git a/src/Generator/Codegen.Utils.fs b/src/Generator/Codegen.Utils.fs index 7d9b393..82ac23a 100644 --- a/src/Generator/Codegen.Utils.fs +++ b/src/Generator/Codegen.Utils.fs @@ -78,12 +78,14 @@ module Utils = let utilsSourceName (config: Generator.Config.Config) = sprintf "%sutils.c" config.FilePrefix let utilsHContent (config: Generator.Config.Config) = + let banner = sprintf "/* Generated by Signal CANdy\n file_prefix=%s, phys_type=%s, phys_mode=%s, dispatch=%s, motorola_start_bit=%s */\n" config.FilePrefix config.PhysType config.PhysMode config.Dispatch config.MotorolaStartBit let g = guard config.FilePrefix "utils_h" - sprintf "#ifndef %s\n#define %s\n\n#include \n#include \n\n// Little-endian bit extraction functions\nuint64_t get_bits_le(const uint8_t* data, uint16_t start_bit, uint16_t length);\n\n// Little-endian bit insertion functions\nvoid set_bits_le(uint8_t* data, uint16_t start_bit, uint16_t length, uint64_t value);\n\n// Big-endian (Motorola) bit extraction\nuint64_t get_bits_be(const uint8_t* data, uint16_t start_bit, uint16_t length);\n\n// Big-endian (Motorola) bit insertion\nvoid set_bits_be(uint8_t* data, uint16_t start_bit, uint16_t length, uint64_t value);\n\n#endif // %s" g g g + banner + sprintf "#ifndef %s\n#define %s\n\n#include \n#include \n\n// Little-endian bit extraction functions\nuint64_t get_bits_le(const uint8_t* data, uint16_t start_bit, uint16_t length);\n\n// Little-endian bit insertion functions\nvoid set_bits_le(uint8_t* data, uint16_t start_bit, uint16_t length, uint64_t value);\n\n// Big-endian (Motorola) bit extraction\nuint64_t get_bits_be(const uint8_t* data, uint16_t start_bit, uint16_t length);\n\n// Big-endian (Motorola) bit insertion\nvoid set_bits_be(uint8_t* data, uint16_t start_bit, uint16_t length, uint64_t value);\n\n#endif // %s" g g g let utilsCContent (config: Generator.Config.Config) = let uH = utilsHeaderName config - "#include \"" + uH + "\"\n\n// Little-endian bit extraction\nuint64_t get_bits_le(const uint8_t* data, uint16_t start_bit, uint16_t length) {\n uint64_t value = 0;\n uint16_t byte_offset = start_bit / 8;\n uint16_t bit_offset = start_bit % 8;\n for (uint16_t i = 0; i < 8 && (byte_offset + i) < 8; ++i) {\n value |= (uint64_t)data[byte_offset + i] << (i * 8);\n }\n value >>= bit_offset;\n value &= (1ULL << length) - 1;\n return value;\n}\n\n// Little-endian bit insertion\nvoid set_bits_le(uint8_t* data, uint16_t start_bit, uint16_t length, uint64_t value) {\n uint16_t byte_offset = start_bit / 8;\n uint16_t bit_offset = start_bit % 8;\n uint64_t clear_mask = ((1ULL << length) - 1) << bit_offset;\n for (uint16_t i = 0; i < 8 && (byte_offset + i) < 8; ++i) {\n data[byte_offset + i] &= ~(uint8_t)(clear_mask >> (i * 8));\n }\n uint64_t insert_value = (value & ((1ULL << length) - 1)) << bit_offset;\n for (uint16_t i = 0; i < 8 && (byte_offset + i) < 8; ++i) {\n data[byte_offset + i] |= (uint8_t)(insert_value >> (i * 8));\n }\n}\n\n// Big-endian (Motorola) bit extraction (DBC semantics, sawtooth)\nuint64_t get_bits_be(const uint8_t* data, uint16_t start_bit, uint16_t length) {\n uint64_t value = 0;\n int byte = start_bit / 8;\n int bit = start_bit % 8; // 7..0 within byte, 7 is MSB\n for (uint16_t i = 0; i < length; ++i) {\n int curByte = byte;\n int curBit = bit - (int)i;\n while (curBit < 0) { curBit += 8; ++curByte; } // move to next higher byte\n uint8_t b = data[curByte];\n uint8_t bitval = (uint8_t)((b >> curBit) & 1u);\n value = (value << 1) | bitval; // assemble MSB-first\n }\n return value;\n}\n\n// Big-endian (Motorola) bit insertion (DBC semantics, sawtooth)\nvoid set_bits_be(uint8_t* data, uint16_t start_bit, uint16_t length, uint64_t value) {\n int byte = start_bit / 8;\n int bit = start_bit % 8;\n for (uint16_t i = 0; i < length; ++i) {\n int curByte = byte;\n int curBit = bit - (int)i;\n while (curBit < 0) { curBit += 8; ++curByte; } // move to next higher byte\n uint8_t bitval = (uint8_t)((value >> (length - 1 - i)) & 1u); // MSB-first\n data[curByte] = (uint8_t)((data[curByte] & (uint8_t)~(1u << curBit)) | (uint8_t)(bitval << curBit));\n }\n}" + let banner = sprintf "/* Generated by Signal CANdy\n file_prefix=%s, phys_type=%s, phys_mode=%s, dispatch=%s, motorola_start_bit=%s */\n" config.FilePrefix config.PhysType config.PhysMode config.Dispatch config.MotorolaStartBit + banner + "#include \"" + uH + "\"\n\n// Little-endian bit extraction\nuint64_t get_bits_le(const uint8_t* data, uint16_t start_bit, uint16_t length) {\n uint64_t value = 0;\n uint16_t byte_offset = start_bit / 8;\n uint16_t bit_offset = start_bit % 8;\n for (uint16_t i = 0; i < 8 && (byte_offset + i) < 8; ++i) {\n value |= (uint64_t)data[byte_offset + i] << (i * 8);\n }\n value >>= bit_offset;\n value &= (1ULL << length) - 1;\n return value;\n}\n\n// Little-endian bit insertion\nvoid set_bits_le(uint8_t* data, uint16_t start_bit, uint16_t length, uint64_t value) {\n uint16_t byte_offset = start_bit / 8;\n uint16_t bit_offset = start_bit % 8;\n uint64_t clear_mask = ((1ULL << length) - 1) << bit_offset;\n for (uint16_t i = 0; i < 8 && (byte_offset + i) < 8; ++i) {\n data[byte_offset + i] &= ~(uint8_t)(clear_mask >> (i * 8));\n }\n uint64_t insert_value = (value & ((1ULL << length) - 1)) << bit_offset;\n for (uint16_t i = 0; i < 8 && (byte_offset + i) < 8; ++i) {\n data[byte_offset + i] |= (uint8_t)(insert_value >> (i * 8));\n }\n}\n\n// Big-endian (Motorola) bit extraction (DBC semantics, sawtooth)\nuint64_t get_bits_be(const uint8_t* data, uint16_t start_bit, uint16_t length) {\n uint64_t value = 0;\n int byte = start_bit / 8;\n int bit = start_bit % 8; // 7..0 within byte, 7 is MSB\n for (uint16_t i = 0; i < length; ++i) {\n int curByte = byte;\n int curBit = bit - (int)i;\n while (curBit < 0) { curBit += 8; ++curByte; } // move to next higher byte\n uint8_t b = data[curByte];\n uint8_t bitval = (uint8_t)((b >> curBit) & 1u);\n value = (value << 1) | bitval; // assemble MSB-first\n }\n return value;\n}\n\n// Big-endian (Motorola) bit insertion (DBC semantics, sawtooth)\nvoid set_bits_be(uint8_t* data, uint16_t start_bit, uint16_t length, uint64_t value) {\n int byte = start_bit / 8;\n int bit = start_bit % 8;\n for (uint16_t i = 0; i < length; ++i) {\n int curByte = byte;\n int curBit = bit - (int)i;\n while (curBit < 0) { curBit += 8; ++curByte; } // move to next higher byte\n uint8_t bitval = (uint8_t)((value >> (length - 1 - i)) & 1u); // MSB-first\n data[curByte] = (uint8_t)((data[curByte] & (uint8_t)~(1u << curBit)) | (uint8_t)(bitval << curBit));\n }\n}" // Helper to choose C accessor based on byte order let accessorNames (byteOrder: ByteOrder) = diff --git a/src/Generator/Codegen.fs b/src/Generator/Codegen.fs index 3b82bb2..ecbb42e 100644 --- a/src/Generator/Codegen.fs +++ b/src/Generator/Codegen.fs @@ -9,7 +9,7 @@ open Generator.Registry module Codegen = - let generateCode (ir: Ir) (outputPath: string) (config: Generator.Config.Config) = + let generateCode (ir: Ir) (outputPath: string) (config: Generator.Config.Config) (emitMain: bool) = try // Create output directories Directory.CreateDirectory (Path.Combine(outputPath, "include")) |> ignore @@ -40,11 +40,12 @@ module Codegen = // Generate registry files with prefix Registry.generateRegistryFiles ir outputPath config - // Copy example main.c into output to act as test harness - let exampleMain = Path.Combine("examples", "main.c") - let outMain = Path.Combine(outputPath, "src", "main.c") - if File.Exists(exampleMain) then - File.Copy(exampleMain, outMain, true) + // Copy example main.c into output to act as test harness (optional) + if emitMain then + let exampleMain = Path.Combine("examples", "main.c") + let outMain = Path.Combine(outputPath, "src", "main.c") + if File.Exists(exampleMain) then + File.Copy(exampleMain, outMain, true) true diff --git a/src/Generator/Program.fs b/src/Generator/Program.fs index 71446af..306c0e8 100644 --- a/src/Generator/Program.fs +++ b/src/Generator/Program.fs @@ -11,6 +11,8 @@ module Program = DbcPath: string OutputPath: string ConfigPath: string option + Prefix: string option + EmitMain: bool } [] @@ -18,23 +20,33 @@ module Program = try let args = argv |> List.ofArray - let rec parseArgs (argsList: string list) (currentDbc: string) (currentOut: string) (currentConfig: string option) = + let rec parseArgs (argsList: string list) (currentDbc: string) (currentOut: string) (currentConfig: string option) (currentPrefix: string option) (currentEmitMain: bool) = match argsList with - | "--dbc" :: path :: rest -> parseArgs rest path currentOut currentConfig - | "--out" :: path :: rest -> parseArgs rest currentDbc path currentConfig - | "--config" :: path :: rest -> parseArgs rest currentDbc currentOut (Some path) - | _ :: rest -> parseArgs rest currentDbc currentOut currentConfig - | [] -> { DbcPath = currentDbc; OutputPath = currentOut; ConfigPath = currentConfig } + | "--dbc" :: path :: rest -> parseArgs rest path currentOut currentConfig currentPrefix currentEmitMain + | "--out" :: path :: rest -> parseArgs rest currentDbc path currentConfig currentPrefix currentEmitMain + | "--config" :: path :: rest -> parseArgs rest currentDbc currentOut (Some path) currentPrefix currentEmitMain + | "--prefix" :: pfx :: rest -> parseArgs rest currentDbc currentOut currentConfig (Some pfx) currentEmitMain + | "--emit-main" :: flag :: rest -> + let v = + match flag.ToLowerInvariant() with + | "true" | "1" | "yes" | "y" -> true + | "false" | "0" | "no" | "n" -> false + | _ -> true + parseArgs rest currentDbc currentOut currentConfig currentPrefix v + | _ :: rest -> parseArgs rest currentDbc currentOut currentConfig currentPrefix currentEmitMain + | [] -> { DbcPath = currentDbc; OutputPath = currentOut; ConfigPath = currentConfig; Prefix = currentPrefix; EmitMain = currentEmitMain } - let parsedArgs = parseArgs args "" "" None + let parsedArgs = parseArgs args "" "" None None true if parsedArgs.DbcPath = "" || parsedArgs.OutputPath = "" then - eprintfn "Usage: dotnet run --project src/Generator -- --dbc --out [--config ]" + eprintfn "Usage: dotnet run --project src/Generator -- --dbc --out [--config ] [--prefix ] [--emit-main ]" 1 else printfn "DBC Path: %s" parsedArgs.DbcPath printfn "Output Path: %s" parsedArgs.OutputPath printfn "Config Path: %A" parsedArgs.ConfigPath + printfn "Prefix Override: %A" parsedArgs.Prefix + printfn "Emit Main: %b" parsedArgs.EmitMain // Load config if provided, otherwise fall back to defaults let defaultCfg = { PhysType = "float"; PhysMode = "double"; RangeCheck = false; Dispatch = "binary_search"; CrcCounterCheck = false; MotorolaStartBit = "msb"; FilePrefix = "sc_" } @@ -48,9 +60,15 @@ module Program = defaultCfg | None -> defaultCfg + // Apply CLI overrides + let cfg = + match parsedArgs.Prefix with + | Some pfx -> { cfg with FilePrefix = pfx } + | None -> cfg + match Dbc.parseDbcFile parsedArgs.DbcPath with | Ok ir -> - if Codegen.generateCode ir parsedArgs.OutputPath cfg then + if Codegen.generateCode ir parsedArgs.OutputPath cfg parsedArgs.EmitMain then printfn "Code generation successful." 0 else From cf52d9691d7d78d0128153d4abe25746394c5e58 Mon Sep 17 00:00:00 2001 From: InitusNovus Date: Mon, 25 Aug 2025 10:50:35 +0900 Subject: [PATCH 03/12] docs(cli): document --prefix and --emit-main flags in READMEs --- README.ko.md | 20 ++++++++++++++++++++ README.md | 19 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/README.ko.md b/README.ko.md index 5ba2326..87e9c1a 100644 --- a/README.ko.md +++ b/README.ko.md @@ -152,6 +152,26 @@ crc_counter_check: false file_prefix: sc_ ``` +## CLI 플래그 (오버라이드) + +일부 설정을 커맨드라인에서 덮어쓸 수 있습니다. + +- `--prefix `: 공통 생성 파일의 `file_prefix`를 오버라이드. +- `--emit-main `: `examples/main.c`를 `gen/src/main.c`로 복사할지 제어. + +예시 + +```bash +# 접두사 foo_ 사용, main.c 복사 생략 +dotnet run --project src/Generator -- \ + --dbc examples/sample.dbc \ + --out gen \ + --config examples/config.yaml \ + --prefix foo_ \ + --emit-main false +``` +``` + ### 구성 파일 사용 ```bash diff --git a/README.md b/README.md index cc8ded9..7d015d1 100644 --- a/README.md +++ b/README.md @@ -156,6 +156,25 @@ crc_counter_check: false file_prefix: sc_ ``` +## CLI flags (overrides) + +You can override some config fields from the command line: + +- `--prefix `: overrides `file_prefix` for generated common files. +- `--emit-main `: controls copying `examples/main.c` to `gen/src/main.c`. + +Examples + +```bash +# Use prefix foo_ and skip copying main.c +dotnet run --project src/Generator -- \ + --dbc examples/sample.dbc \ + --out gen \ + --config examples/config.yaml \ + --prefix foo_ \ + --emit-main false +``` + ### Using a config ```bash From 02ed5bc01738efe7afb1c3d970ec695304e6fc99 Mon Sep 17 00:00:00 2001 From: InitusNovus Date: Mon, 25 Aug 2025 11:03:01 +0900 Subject: [PATCH 04/12] ci: add minimal GitHub Actions (dotnet build/test, codegen+make+smoke) --- .github/workflows/ci.yml | 65 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..1c0b7fb --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,65 @@ +name: CI + +on: + push: + branches: [ ci-setup, dev-local, main ] + pull_request: + branches: [ main ] + +jobs: + build-test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.0.x' + + - name: Restore & Build + run: | + dotnet --info + dotnet restore + dotnet build --configuration Release --nologo + + - name: Run Tests (F#) + run: | + dotnet test -v minimal --nologo --configuration Release + + - name: Codegen C (fixed_suite) + run: | + dotnet run --project src/Generator -- --dbc examples/fixed_suite.dbc --out gen --config examples/config_directmap_fixed.yaml --prefix sc_ --emit-main true + + - name: Build generated C with Make + run: | + make -C gen build + + - name: Smoke test runner + run: | + ./gen/build/test_runner test_fixed_suite_roundtrip + + - name: Upload build artifacts (on failure) + if: failure() + uses: actions/upload-artifact@v4 + with: + name: gen-build-logs + path: | + gen/build + gen/include + gen/*.txt + gen/Makefile + gen/src + gen/*.log + + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Verify repo hygiene + run: | + if [ -d external_test ]; then echo "[lint] NOTE: external_test present (ignored by CI)"; fi + if [ -f TEST_SUMMARY.md ]; then echo "[lint] NOTE: TEST_SUMMARY.md should be local-only"; fi From eeb738b0d19631d7c7e18b43c71d29d657cc3d01 Mon Sep 17 00:00:00 2001 From: InitusNovus Date: Mon, 25 Aug 2025 11:22:11 +0900 Subject: [PATCH 05/12] ci: add codegen setup before F# tests to ensure Makefile exists --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1c0b7fb..e8483ab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,6 +24,10 @@ jobs: dotnet restore dotnet build --configuration Release --nologo + - name: Setup gen directory for tests + run: | + dotnet run --project src/Generator -- --dbc examples/sample.dbc --out gen --config examples/config.yaml + - name: Run Tests (F#) run: | dotnet test -v minimal --nologo --configuration Release From d9cb4a6f9e572322a11ed8c91c6ea131f5f5bf96 Mon Sep 17 00:00:00 2001 From: InitusNovus Date: Mon, 25 Aug 2025 11:28:22 +0900 Subject: [PATCH 06/12] ci: temporarily skip F# tests due to gen folder conflicts in CI --- .github/workflows/ci.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e8483ab..6c25099 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,9 +28,11 @@ jobs: run: | dotnet run --project src/Generator -- --dbc examples/sample.dbc --out gen --config examples/config.yaml - - name: Run Tests (F#) + - name: Run Tests (F# - Skip C integration) run: | - dotnet test -v minimal --nologo --configuration Release + # Skip C integration tests in CI for now due to gen folder conflicts + echo "Skipping F# tests temporarily - will add proper isolation later" + # dotnet test -v minimal --nologo --configuration Release - name: Codegen C (fixed_suite) run: | From d86f501e607931d4daa0c0860c751be26ba4cae2 Mon Sep 17 00:00:00 2001 From: InitusNovus Date: Mon, 25 Aug 2025 11:39:13 +0900 Subject: [PATCH 07/12] tests: isolate gen outputs per test; Makefile: gate stress suite via HAVE_STRESS; examples/main: guard stress symbol; generator: robust example main discovery; CI: re-enable F# tests --- .github/workflows/ci.yml | 6 +- examples/main.c | 6 +- src/Generator/Codegen.fs | 22 ++++++- tests/Generator.Tests/CodegenTests.fs | 89 ++++++++++++++++++--------- 4 files changed, 87 insertions(+), 36 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6c25099..e8483ab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,11 +28,9 @@ jobs: run: | dotnet run --project src/Generator -- --dbc examples/sample.dbc --out gen --config examples/config.yaml - - name: Run Tests (F# - Skip C integration) + - name: Run Tests (F#) run: | - # Skip C integration tests in CI for now due to gen folder conflicts - echo "Skipping F# tests temporarily - will add proper isolation later" - # dotnet test -v minimal --nologo --configuration Release + dotnet test -v minimal --nologo --configuration Release - name: Codegen C (fixed_suite) run: | diff --git a/examples/main.c b/examples/main.c index 35cbfe0..350783e 100644 --- a/examples/main.c +++ b/examples/main.c @@ -4,8 +4,10 @@ #include "sc_registry.h" #include "sc_utils.h" -// Include stress test +// Include stress test only when available (controlled via -DHAVE_STRESS in Makefile) +#ifdef HAVE_STRESS extern int test_stress_suite(void); +#endif #if defined(__has_include) # if __has_include("message_1.h") @@ -449,9 +451,11 @@ int main(int argc, char *argv[]) { return test_dispatch_external_multi(); } #endif + #ifdef HAVE_STRESS else if (strcmp(argv[1], "test_stress_suite") == 0) { return test_stress_suite(); } + #endif else { printf("Unknown or unavailable test: %s\n", argv[1]); return 1; diff --git a/src/Generator/Codegen.fs b/src/Generator/Codegen.fs index ecbb42e..0df7e3c 100644 --- a/src/Generator/Codegen.fs +++ b/src/Generator/Codegen.fs @@ -11,6 +11,22 @@ module Codegen = let generateCode (ir: Ir) (outputPath: string) (config: Generator.Config.Config) (emitMain: bool) = try + // Helper: find examples/main.c starting from likely roots (CWD, assembly base) and traversing upwards + let tryFindExampleMain () = + let candidatesFrom (startDir: string) = + seq { + let mutable d = startDir + for _ in 0 .. 6 do + let p = Path.Combine(d, "examples", "main.c") + if File.Exists p then yield p + let parent = Directory.GetParent(d) + if isNull parent then () else d <- parent.FullName + } + let bases = [ Directory.GetCurrentDirectory(); System.AppContext.BaseDirectory ] + bases + |> Seq.collect candidatesFrom + |> Seq.tryHead + // Create output directories Directory.CreateDirectory (Path.Combine(outputPath, "include")) |> ignore Directory.CreateDirectory (Path.Combine(outputPath, "src")) |> ignore @@ -42,10 +58,10 @@ module Codegen = // Copy example main.c into output to act as test harness (optional) if emitMain then - let exampleMain = Path.Combine("examples", "main.c") let outMain = Path.Combine(outputPath, "src", "main.c") - if File.Exists(exampleMain) then - File.Copy(exampleMain, outMain, true) + match tryFindExampleMain () with + | Some exampleMain -> File.Copy(exampleMain, outMain, true) + | None -> eprintfn "Warning: examples/main.c not found from working locations; skipping emit-main." true diff --git a/tests/Generator.Tests/CodegenTests.fs b/tests/Generator.Tests/CodegenTests.fs index 1c7e1f6..b7ab1ff 100644 --- a/tests/Generator.Tests/CodegenTests.fs +++ b/tests/Generator.Tests/CodegenTests.fs @@ -7,6 +7,7 @@ open Generator.Dbc open Generator.Result open System.IO open System.Diagnostics +open System module CodegenTests = @@ -50,11 +51,18 @@ module CodegenTests = if proc.ExitCode <> 0 then failwith (sprintf "Generator failed with exit code %d.\nStdout:\n%s\nStderr:\n%s" proc.ExitCode stdout stderr) + // Ensure Makefile exists in the generated output directory (copy from repo's gen/Makefile) + let repoMakefile = Path.Combine(__SOURCE_DIRECTORY__, "..", "..", "gen", "Makefile") + let outMakefile = Path.Combine(genOutputPath, "Makefile") + if File.Exists(repoMakefile) then + File.Copy(repoMakefile, outMakefile, true) + let buildAndRunCTest (genOutputPath: string) (cTestName: string) : string list = // Build let make = new Process() make.StartInfo.FileName <- "make" - make.StartInfo.Arguments <- "-C gen build" + // Use absolute path to the generated directory to avoid CWD confusion + make.StartInfo.Arguments <- sprintf "-C \"%s\" build" genOutputPath make.StartInfo.WorkingDirectory <- Path.Combine(__SOURCE_DIRECTORY__, "..", "..") make.StartInfo.UseShellExecute <- false make.StartInfo.RedirectStandardOutput <- true @@ -82,45 +90,70 @@ module CodegenTests = [] let ``Encode/Decode roundtrip for SimpleMessage`` () = - let genOutputPath = Path.Combine(__SOURCE_DIRECTORY__, "..", "..", "gen") - // default config (range_check=false) - runCGenerator None genOutputPath - let output = buildAndRunCTest genOutputPath "test_roundtrip" - output |> should contain "Roundtrip successful!" + let genOutputPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()) + Directory.CreateDirectory(genOutputPath) |> ignore + try + // default config (range_check=false) + runCGenerator None genOutputPath + let output = buildAndRunCTest genOutputPath "test_roundtrip" + output |> should contain "Roundtrip successful!" + finally + if Directory.Exists(genOutputPath) then + Directory.Delete(genOutputPath, true) [] let ``Roundtrip with fixed phys_type`` () = - let genOutputPath = Path.Combine(__SOURCE_DIRECTORY__, "..", "..", "gen") - let configPath = Path.Combine(__SOURCE_DIRECTORY__, "..", "..", "examples", "config_fixed.yaml") - runCGenerator (Some configPath) genOutputPath - let output = buildAndRunCTest genOutputPath "test_roundtrip" - output |> should contain "Roundtrip successful!" + let genOutputPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()) + Directory.CreateDirectory(genOutputPath) |> ignore + try + let configPath = Path.Combine(__SOURCE_DIRECTORY__, "..", "..", "examples", "config_fixed.yaml") + runCGenerator (Some configPath) genOutputPath + let output = buildAndRunCTest genOutputPath "test_roundtrip" + output |> should contain "Roundtrip successful!" + finally + if Directory.Exists(genOutputPath) then + Directory.Delete(genOutputPath, true) [] let ``Range check test`` () = - let genOutputPath = Path.Combine(__SOURCE_DIRECTORY__, "..", "..", "gen") - let configPath = Path.Combine(__SOURCE_DIRECTORY__, "..", "..", "examples", "config_range_check.yaml") - runCGenerator (Some configPath) genOutputPath - let output = buildAndRunCTest genOutputPath "test_range_check" - output |> should contain "Range check test successful!" + let genOutputPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()) + Directory.CreateDirectory(genOutputPath) |> ignore + try + let configPath = Path.Combine(__SOURCE_DIRECTORY__, "..", "..", "examples", "config_range_check.yaml") + runCGenerator (Some configPath) genOutputPath + let output = buildAndRunCTest genOutputPath "test_range_check" + output |> should contain "Range check test successful!" + finally + if Directory.Exists(genOutputPath) then + Directory.Delete(genOutputPath, true) [] let ``Dispatch direct_map test`` () = - let genOutputPath = Path.Combine(__SOURCE_DIRECTORY__, "..", "..", "gen") - let configPath = Path.Combine(__SOURCE_DIRECTORY__, "..", "..", "examples", "config_direct_map.yaml") - runCGenerator (Some configPath) genOutputPath - let output = buildAndRunCTest genOutputPath "test_dispatch" - output |> should contain "Dispatch successful for message ID 100" - output |> should contain "Dispatch correctly failed for unknown message ID 99" + let genOutputPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()) + Directory.CreateDirectory(genOutputPath) |> ignore + try + let configPath = Path.Combine(__SOURCE_DIRECTORY__, "..", "..", "examples", "config_direct_map.yaml") + runCGenerator (Some configPath) genOutputPath + let output = buildAndRunCTest genOutputPath "test_dispatch" + output |> should contain "Dispatch successful for message ID 100" + output |> should contain "Dispatch correctly failed for unknown message ID 99" + finally + if Directory.Exists(genOutputPath) then + Directory.Delete(genOutputPath, true) [] let ``CRC and Counter check test`` () = - let genOutputPath = Path.Combine(__SOURCE_DIRECTORY__, "..", "..", "gen") - let configPath = Path.Combine(__SOURCE_DIRECTORY__, "..", "..", "examples", "config_crc_counter.yaml") - runCGenerator (Some configPath) genOutputPath - let output = buildAndRunCTest genOutputPath "test_crc_counter" - output |> should not' (be Null) - output |> should not' (be Empty) + let genOutputPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()) + Directory.CreateDirectory(genOutputPath) |> ignore + try + let configPath = Path.Combine(__SOURCE_DIRECTORY__, "..", "..", "examples", "config_crc_counter.yaml") + runCGenerator (Some configPath) genOutputPath + let output = buildAndRunCTest genOutputPath "test_crc_counter" + output |> should not' (be Null) + output |> should not' (be Empty) + finally + if Directory.Exists(genOutputPath) then + Directory.Delete(genOutputPath, true) [] let ``DBC signal field mapping correctness`` () = From 411df07a3941227b1be09de8abe49d13d8004d21 Mon Sep 17 00:00:00 2001 From: InitusNovus Date: Mon, 25 Aug 2025 11:44:29 +0900 Subject: [PATCH 08/12] tests: isolate gen outputs per test and copy Makefile into temp dir; ci: re-enable F# tests --- .github/workflows/ci.yml | 2 +- tests/Generator.Tests/CodegenTests.fs | 36 ++++++++++++++------------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e8483ab..ede683f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: dotnet restore dotnet build --configuration Release --nologo - - name: Setup gen directory for tests + - name: Setup gen directory for tests (sanity) run: | dotnet run --project src/Generator -- --dbc examples/sample.dbc --out gen --config examples/config.yaml diff --git a/tests/Generator.Tests/CodegenTests.fs b/tests/Generator.Tests/CodegenTests.fs index b7ab1ff..60a1220 100644 --- a/tests/Generator.Tests/CodegenTests.fs +++ b/tests/Generator.Tests/CodegenTests.fs @@ -28,15 +28,22 @@ module CodegenTests = | Failure errors -> failwith (sprintf "Expected success, but got errors: %A" errors) + // Ensure a Makefile exists in the generated output directory (copy from repo's gen/Makefile) + let ensureMakefile (genOutputPath: string) = + let repoMakefile = Path.Combine(__SOURCE_DIRECTORY__, "..", "..", "gen", "Makefile") + let outMakefile = Path.Combine(genOutputPath, "Makefile") + if File.Exists(repoMakefile) then + File.Copy(repoMakefile, outMakefile, true) + else + failwith (sprintf "Makefile template not found at %s" repoMakefile) + let runCGenerator (configPath: string option) (genOutputPath: string) = let dbcPath = Path.Combine(__SOURCE_DIRECTORY__, "..", "..", "examples", "sample.dbc") - let args = - let configArg = - match configPath with - | Some p -> sprintf "--config %s" p - | None -> "" - sprintf "--dbc %s --out %s %s" dbcPath genOutputPath configArg - + let configArg = + match configPath with + | Some p -> sprintf "--config %s" p + | None -> "" + let args = sprintf "--dbc %s --out %s %s" dbcPath genOutputPath configArg let proc = new Process() proc.StartInfo.FileName <- "dotnet" let generatorPath = Path.Combine(__SOURCE_DIRECTORY__, "..", "..", "src", "Generator", "Generator.fsproj") @@ -50,20 +57,15 @@ module CodegenTests = proc.WaitForExit() if proc.ExitCode <> 0 then failwith (sprintf "Generator failed with exit code %d.\nStdout:\n%s\nStderr:\n%s" proc.ExitCode stdout stderr) - - // Ensure Makefile exists in the generated output directory (copy from repo's gen/Makefile) - let repoMakefile = Path.Combine(__SOURCE_DIRECTORY__, "..", "..", "gen", "Makefile") - let outMakefile = Path.Combine(genOutputPath, "Makefile") - if File.Exists(repoMakefile) then - File.Copy(repoMakefile, outMakefile, true) + // Copy Makefile after successful generation + ensureMakefile genOutputPath + () let buildAndRunCTest (genOutputPath: string) (cTestName: string) : string list = - // Build + // Build using the Makefile in genOutputPath let make = new Process() make.StartInfo.FileName <- "make" - // Use absolute path to the generated directory to avoid CWD confusion make.StartInfo.Arguments <- sprintf "-C \"%s\" build" genOutputPath - make.StartInfo.WorkingDirectory <- Path.Combine(__SOURCE_DIRECTORY__, "..", "..") make.StartInfo.UseShellExecute <- false make.StartInfo.RedirectStandardOutput <- true make.StartInfo.RedirectStandardError <- true @@ -74,7 +76,7 @@ module CodegenTests = if make.ExitCode <> 0 then failwith (sprintf "Make build failed with exit code %d.\nStdout:\n%s\nStderr:\n%s" make.ExitCode makeStdout makeStderr) - // Run + // Run test let run = new Process() run.StartInfo.FileName <- Path.Combine(genOutputPath, "build", "test_runner") run.StartInfo.Arguments <- cTestName From 2259851651c40486f49dd25ae0c1fbb60d6a007c Mon Sep 17 00:00:00 2001 From: InitusNovus Date: Mon, 25 Aug 2025 11:58:56 +0900 Subject: [PATCH 09/12] tests: make tests self-contained by embedding fallback Makefile when template missing; fix CI failure --- tests/Generator.Tests/CodegenTests.fs | 42 ++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/tests/Generator.Tests/CodegenTests.fs b/tests/Generator.Tests/CodegenTests.fs index 60a1220..548a89d 100644 --- a/tests/Generator.Tests/CodegenTests.fs +++ b/tests/Generator.Tests/CodegenTests.fs @@ -35,7 +35,47 @@ module CodegenTests = if File.Exists(repoMakefile) then File.Copy(repoMakefile, outMakefile, true) else - failwith (sprintf "Makefile template not found at %s" repoMakefile) + // Fallback: write a minimal Makefile that can build the generated C code + let makefileTemplate = """ +CC = gcc +CFLAGS = -Wall -Wextra -std=c99 + +BUILD_DIR = build +SRC_DIR = src +INCLUDE_DIR = include + +# Discover all C sources under src +SRCS := $(wildcard $(SRC_DIR)/*.c) +# If any prefixed common files exist, drop legacy unprefixed ones to avoid duplicates +ifneq (,$(filter-out $(SRC_DIR)/registry.c,$(wildcard $(SRC_DIR)/*registry.c))) +SRCS := $(filter-out $(SRC_DIR)/registry.c,$(SRCS)) +endif +ifneq (,$(filter-out $(SRC_DIR)/utils.c,$(wildcard $(SRC_DIR)/*utils.c))) +SRCS := $(filter-out $(SRC_DIR)/utils.c,$(SRCS)) +endif + +OBJS := $(patsubst $(SRC_DIR)/%.c,$(BUILD_DIR)/%.o,$(SRCS)) + +TARGET = $(BUILD_DIR)/test_runner + +.PHONY: all build clean + +all: build + +build: $(TARGET) + +$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c + mkdir -p $(@D) + $(CC) $(CFLAGS) -I$(INCLUDE_DIR) -c $< -o $@ + +$(TARGET): $(OBJS) + mkdir -p $(@D) + $(CC) $(CFLAGS) $(OBJS) -o $@ + +clean: + rm -rf $(BUILD_DIR) +""" + File.WriteAllText(outMakefile, makefileTemplate) let runCGenerator (configPath: string option) (genOutputPath: string) = let dbcPath = Path.Combine(__SOURCE_DIRECTORY__, "..", "..", "examples", "sample.dbc") From e9accb2230c3701240e4a2effd726fd3835b7432 Mon Sep 17 00:00:00 2001 From: InitusNovus Date: Mon, 25 Aug 2025 12:04:19 +0900 Subject: [PATCH 10/12] make: link libm on Linux (LDLIBS=-lm); tests: fallback Makefile also links -lm to fix llround symbol on CI --- tests/Generator.Tests/CodegenTests.fs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/Generator.Tests/CodegenTests.fs b/tests/Generator.Tests/CodegenTests.fs index 548a89d..963f36a 100644 --- a/tests/Generator.Tests/CodegenTests.fs +++ b/tests/Generator.Tests/CodegenTests.fs @@ -39,6 +39,7 @@ module CodegenTests = let makefileTemplate = """ CC = gcc CFLAGS = -Wall -Wextra -std=c99 +LDLIBS = -lm BUILD_DIR = build SRC_DIR = src @@ -70,7 +71,7 @@ $(BUILD_DIR)/%.o: $(SRC_DIR)/%.c $(TARGET): $(OBJS) mkdir -p $(@D) - $(CC) $(CFLAGS) $(OBJS) -o $@ + $(CC) $(CFLAGS) $(OBJS) $(LDLIBS) -o $@ clean: rm -rf $(BUILD_DIR) From 6e7a29d597640fddde796b08eab23dc1457a21ad Mon Sep 17 00:00:00 2001 From: InitusNovus Date: Mon, 25 Aug 2025 12:08:01 +0900 Subject: [PATCH 11/12] =?UTF-8?q?tests:=20fallback=20Makefile=20fixes=20?= =?UTF-8?q?=E2=80=94=20use=20.RECIPEPREFIX=20to=20avoid=20TAB=20issues;=20?= =?UTF-8?q?still=20link=20-lm?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/Generator.Tests/CodegenTests.fs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/Generator.Tests/CodegenTests.fs b/tests/Generator.Tests/CodegenTests.fs index 963f36a..68b1b09 100644 --- a/tests/Generator.Tests/CodegenTests.fs +++ b/tests/Generator.Tests/CodegenTests.fs @@ -41,6 +41,9 @@ CC = gcc CFLAGS = -Wall -Wextra -std=c99 LDLIBS = -lm +# Avoid TAB requirement for recipes (GNU make >= 3.82) +.RECIPEPREFIX := > + BUILD_DIR = build SRC_DIR = src INCLUDE_DIR = include @@ -66,15 +69,15 @@ all: build build: $(TARGET) $(BUILD_DIR)/%.o: $(SRC_DIR)/%.c - mkdir -p $(@D) - $(CC) $(CFLAGS) -I$(INCLUDE_DIR) -c $< -o $@ +>mkdir -p $(@D) +>$(CC) $(CFLAGS) -I$(INCLUDE_DIR) -c $< -o $@ $(TARGET): $(OBJS) - mkdir -p $(@D) - $(CC) $(CFLAGS) $(OBJS) $(LDLIBS) -o $@ +>mkdir -p $(@D) +>$(CC) $(CFLAGS) $(OBJS) $(LDLIBS) -o $@ clean: - rm -rf $(BUILD_DIR) +>rm -rf $(BUILD_DIR) """ File.WriteAllText(outMakefile, makefileTemplate) From 4e881b9a960f791e322fd24c089cd33ff88fa734 Mon Sep 17 00:00:00 2001 From: InitusNovus Date: Mon, 25 Aug 2025 12:11:45 +0900 Subject: [PATCH 12/12] ci: inject Makefile into gen before make to ensure build target exists on runners --- .github/workflows/ci.yml | 62 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ede683f..61c3922 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,6 +36,68 @@ jobs: run: | dotnet run --project src/Generator -- --dbc examples/fixed_suite.dbc --out gen --config examples/config_directmap_fixed.yaml --prefix sc_ --emit-main true + - name: Ensure Makefile in gen + run: | + cat > gen/Makefile <<'MAKEFILE' + CC = gcc + CFLAGS = -Wall -Wextra -std=c99 + LDLIBS = -lm + + BUILD_DIR = build + SRC_DIR = src + INCLUDE_DIR = include + + # Discover all C sources under src and map to objects under build + SRCS := $(wildcard $(SRC_DIR)/*.c) + # Add stress test source if available + ifneq (,$(wildcard ../examples/stress_test.c)) + SRCS += ../examples/stress_test.c + CFLAGS += -DHAVE_STRESS + endif + + # If any prefixed common files exist, drop legacy unprefixed ones to avoid duplicates + ifneq (,$(filter-out $(SRC_DIR)/registry.c,$(wildcard $(SRC_DIR)/*registry.c))) + SRCS := $(filter-out $(SRC_DIR)/registry.c,$(SRCS)) + endif + ifneq (,$(filter-out $(SRC_DIR)/utils.c,$(wildcard $(SRC_DIR)/*utils.c))) + SRCS := $(filter-out $(SRC_DIR)/utils.c,$(SRCS)) + endif + OBJS := $(patsubst $(SRC_DIR)/%.c,$(BUILD_DIR)/%.o,$(SRCS)) + OBJS := $(patsubst ../examples/%.c,$(BUILD_DIR)/%.o,$(OBJS)) + + TARGET = $(BUILD_DIR)/test_runner + + .PHONY: all build test clean + + all: build + + build: $(TARGET) + + # Generic build rule for any C file in src/ + $(BUILD_DIR)/%.o: $(SRC_DIR)/%.c + mkdir -p $(@D) + $(CC) $(CFLAGS) -I$(INCLUDE_DIR) -c $< -o $@ + + # Rule for examples directory + $(BUILD_DIR)/%.o: ../examples/%.c + mkdir -p $(@D) + $(CC) $(CFLAGS) -I$(INCLUDE_DIR) -c $< -o $@ + + # Link all objects into the test runner + $(TARGET): $(OBJS) + mkdir -p $(@D) + $(CC) $(CFLAGS) $(OBJS) $(LDLIBS) -o $@ + + # Placeholder test target (adjust as needed) + test: + @echo "Running C tests... (Placeholder)" + @echo "No actual tests implemented yet. This is a placeholder." + # For example: ./$(TARGET) test_be_basic + + clean: + rm -rf $(BUILD_DIR) + MAKEFILE + - name: Build generated C with Make run: | make -C gen build