#!/usr/bin/env bash # # Regenerates the gRPC C# files from the Communication project's .proto files. # # Background: protoc (linux/arm64) segfaults inside our Docker build container # (Grpc.Tools). As a workaround the generated C# is checked into # src/ZB.MOM.WW.ScadaBridge.Communication/ — SiteStreamGrpc/ for sitestream.proto, # CentralControlGrpc/ for central_control.proto, and SiteCommandGrpc/ for # site_command.proto — and the Protobuf ItemGroup in the .csproj is commented out, # so Docker just compiles the checked-in files. # # Run this script ON YOUR DEV MACHINE whenever a .proto changes: # # docker/regen-proto.sh [sitestream|centralcontrol|sitecommand|all] (default: all) # # 1. Injects a Protobuf ItemGroup for the selected proto(s) so Grpc.Tools runs. # 2. Deletes the stale checked-in C# so a failed regen is obvious. # 3. dotnet build (regen writes fresh files to obj/). # 4. Copies the regenerated files back into the source tree. # 5. Restores the original csproj so no active Protobuf item is left behind. # # Only the SELECTED protos get a Protobuf item. Enabling one whose generated C# # is still checked in would define every generated type twice, which is why the # per-proto selection exists. central_control.proto imports sitestream.proto, # but protoc resolves that from the project-relative path — the import needs no # Protobuf item of its own. # # Once we move to a Dockerfile base image that ships a working linux/arm64 # protoc, this script can be retired and Docker can regen the protos on every # build like every other normal .NET project. set -euo pipefail TARGET="${1:-all}" case "$TARGET" in sitestream|centralcontrol|sitecommand|all) ;; *) echo "usage: $0 [sitestream|centralcontrol|sitecommand|all]" >&2; exit 2 ;; esac SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" COMM_DIR="$REPO_ROOT/src/ZB.MOM.WW.ScadaBridge.Communication" CSPROJ="$COMM_DIR/ZB.MOM.WW.ScadaBridge.Communication.csproj" GEN="$COMM_DIR/obj/Debug/net10.0/Protos" echo "=== Regenerating gRPC files ($TARGET) ===" if [[ ! -f "$CSPROJ" ]]; then echo "ERROR: csproj not found at $CSPROJ" >&2 exit 1 fi # Backup so we can always restore the comment state on failure. Leaving the # csproj with an active Protobuf item is the one outcome that breaks Docker, so # every exit path restores this copy. BACKUP="$(mktemp)" cp "$CSPROJ" "$BACKUP" trap 'cp "$BACKUP" "$CSPROJ"; rm -f "$BACKUP"; echo "Restored csproj from backup."' ERR # 1. Inject an ItemGroup holding just the selected protos, immediately before # the closing . The documented commented-out block is left alone. python3 - "$CSPROJ" "$TARGET" <<'PY' import pathlib, sys csproj, target = pathlib.Path(sys.argv[1]), sys.argv[2] protos = [] if target in ("sitestream", "all"): protos.append("sitestream.proto") if target in ("centralcontrol", "all"): protos.append("central_control.proto") if target in ("sitecommand", "all"): protos.append("site_command.proto") items = "\n".join( f' ' for p in protos) block = f" \n{items}\n \n\n" src = csproj.read_text() if "" not in src: raise SystemExit("Couldn't find to inject the Protobuf ItemGroup before.") csproj.write_text(src.replace("", block, 1)) PY # 2. Delete the stale files so any failure to regen is obvious. if [[ "$TARGET" == "sitestream" || "$TARGET" == "all" ]]; then rm -f "$COMM_DIR/SiteStreamGrpc/Sitestream.cs" "$COMM_DIR/SiteStreamGrpc/SitestreamGrpc.cs" fi if [[ "$TARGET" == "centralcontrol" || "$TARGET" == "all" ]]; then rm -f "$COMM_DIR/CentralControlGrpc/CentralControl.cs" \ "$COMM_DIR/CentralControlGrpc/CentralControlGrpc.cs" fi if [[ "$TARGET" == "sitecommand" || "$TARGET" == "all" ]]; then rm -f "$COMM_DIR/SiteCommandGrpc/SiteCommand.cs" \ "$COMM_DIR/SiteCommandGrpc/SiteCommandGrpc.cs" fi # 3. Regenerate by building. echo "Building Communication project (regen)..." dotnet build "$CSPROJ" --nologo -v minimal | tail -5 # 4. Copy generated files back into the source tree. if [[ "$TARGET" == "sitestream" || "$TARGET" == "all" ]]; then mkdir -p "$COMM_DIR/SiteStreamGrpc" cp "$GEN/Sitestream.cs" "$GEN/SitestreamGrpc.cs" "$COMM_DIR/SiteStreamGrpc/" echo "Copied regenerated files to SiteStreamGrpc/" fi if [[ "$TARGET" == "centralcontrol" || "$TARGET" == "all" ]]; then mkdir -p "$COMM_DIR/CentralControlGrpc" cp "$GEN/CentralControl.cs" "$GEN/CentralControlGrpc.cs" "$COMM_DIR/CentralControlGrpc/" echo "Copied regenerated files to CentralControlGrpc/" fi if [[ "$TARGET" == "sitecommand" || "$TARGET" == "all" ]]; then mkdir -p "$COMM_DIR/SiteCommandGrpc" cp "$GEN/SiteCommand.cs" "$GEN/SiteCommandGrpc.cs" "$COMM_DIR/SiteCommandGrpc/" echo "Copied regenerated files to SiteCommandGrpc/" fi # 5. Restore the backed-up csproj — i.e. drop the injected ItemGroup — so Docker # builds keep working. cp "$BACKUP" "$CSPROJ" rm -f "$BACKUP" trap - ERR echo "" echo "Done. Review and commit:" echo " git diff src/ZB.MOM.WW.ScadaBridge.Communication/Protos/" echo " git diff src/ZB.MOM.WW.ScadaBridge.Communication/SiteStreamGrpc/" echo " git diff src/ZB.MOM.WW.ScadaBridge.Communication/CentralControlGrpc/" echo " git diff src/ZB.MOM.WW.ScadaBridge.Communication/SiteCommandGrpc/" echo " git diff -- src/ZB.MOM.WW.ScadaBridge.Communication/*.csproj # must be EMPTY"