2626 description : " True if all 5 foryc binaries built and validated."
2727 value : ${{ jobs.build-complete.outputs.ready }}
2828
29- # FIX 7: Narrowed from "v*" to "foryc-v*" to avoid triggering on Java/Rust/Go release tags
3029 push :
3130 tags : ["foryc-v*"]
3231
6059# 10 MB constraint:
6160# Each binary must remain under 10 MB after UPX compression.
6261# This is a hard gate for crates.io embedding in Phase 2.
63- # Enforced with an explicit assertion step in each build job.
6462# macOS binaries are uncompressed but remain under 10 MB because
6563# fory_compiler has zero third-party pip dependencies (pure stdlib only).
64+ #
65+ # Windows Defender note:
66+ # Set-MpPreference -DisableRealtimeMonitoring $true is required in BOTH
67+ # the build and validate jobs. Add-MpPreference -ExclusionPath is NOT
68+ # sufficient — it only excludes file system scans, not the memory-level
69+ # LoadLibrary interception that causes PYI-3104 / ERROR_NOACCESS failures
70+ # when PyInstaller extracts and maps python311.dll at runtime.
6671# ─────────────────────────────────────────────────────────────────────────────
6772jobs :
6873 build :
@@ -104,10 +109,6 @@ jobs:
104109 use_upx : false
105110 codesign : true
106111
107- # LATEST FIX: windows-2019 is deprecated and runners no longer provision.
108- # windows-latest (Server 2022) works correctly when combined with the
109- # Defender exclusion step below. The DLL issue was always caused by
110- # Defender scanning PyInstaller's extracted temp files, not the runner version.
111112 - target : windows-x86_64
112113 os : windows-latest
113114 artifact_name : foryc-windows-x86_64
@@ -117,13 +118,9 @@ jobs:
117118 codesign : false
118119
119120 steps :
120- # Full checkout required: needs compiler/foryc.spec,
121- # compiler/requirements-dev.txt, and the full compiler/ package
122- # for pip install and pyinstaller to work.
123121 - name : Checkout
124122 uses : actions/checkout@v5
125123
126- # FIX 8: Added pip cache to avoid cold installs on every run (~60-120s saved per target)
127124 - name : Set up Python ${{ env.PYTHON_VERSION }}
128125 uses : actions/setup-python@v5
129126 with :
@@ -155,19 +152,16 @@ jobs:
155152 upx --version
156153
157154 # ── Install build dependencies ──────────────────────────────────────────
158- # pyinstaller version is pinned in requirements-dev.txt.
159- # fory_compiler is installed from source so PyInstaller's import
160- # tracer can walk the actual installed package tree.
161155 - name : Install PyInstaller and fory_compiler
162156 run : |
163157 python -m pip install --upgrade pip
164158 pip install -r compiler/requirements-dev.txt
165159 pip install ./compiler
166160
167- # FIX 9: Verify all hiddenimports are importable before PyInstaller runs.
168- # PyInstaller silently ignores nonexistent hiddenimports — this step
169- # catches renamed or removed modules before they silently disappear
170- # from the binary.
161+ # ── Verify all hiddenimports are importable ─────────────────────────────
162+ # PyInstaller silently ignores nonexistent hiddenimports entries.
163+ # This step catches renamed or removed modules before they silently
164+ # disappear from the binary.
171165 - name : Verify all hiddenimports are importable
172166 working-directory : compiler
173167 run : |
@@ -198,30 +192,24 @@ jobs:
198192 print('All hiddenimports verified.')
199193 "
200194
201- # LATEST FIX (build job): Defender exclusions instead of full disable.
202- # Targets GITHUB_WORKSPACE and RUNNER_TEMP — the two paths PyInstaller
203- # writes to during build and extraction. This is sufficient and more
204- # targeted than disabling real-time monitoring globally.
205- - name : Disable Windows Defender for PyInstaller paths
195+ # ── Windows Defender (build job) ────────────────────────────────────────
196+ # Full real-time monitoring disable is required — not just path exclusions.
197+ # Add-MpPreference -ExclusionPath only covers filesystem scans.
198+ # The PYI-3104 / LoadLibrary: Invalid access to memory location error
199+ # is caused by Defender intercepting the memory-mapping of python311.dll
200+ # during PyInstaller bootloader execution. Only DisableRealtimeMonitoring
201+ # stops that interception.
202+ - name : Disable Windows Defender real-time monitoring (PyInstaller workaround)
206203 if : runner.os == 'Windows'
207204 shell : pwsh
208- run : |
209- Add-MpPreference -ExclusionPath "$env:GITHUB_WORKSPACE"
210- Add-MpPreference -ExclusionPath "$env:RUNNER_TEMP"
211- Add-MpPreference -ExclusionProcess "foryc.exe"
205+ run : Set-MpPreference -DisableRealtimeMonitoring $true
212206
213207 # ── Build ───────────────────────────────────────────────────────────────
214- # Must run from compiler/ so pathex=['.'] in foryc.spec resolves
215- # fory_compiler/__main__.py correctly.
216208 - name : Build standalone binary with PyInstaller
217209 working-directory : compiler
218210 run : pyinstaller foryc.spec
219211
220- # FIX 3 (occurrence 1/3): Use BINARY_PATH env var instead of inline matrix
221- # expression inside Python string — safe against paths with spaces or quotes.
222212 # ── Pre-compression smoke test ──────────────────────────────────────────
223- # Confirms the binary is functional before UPX touches it.
224- # A broken binary here gives a cleaner error than post-UPX.
225213 - name : Smoke test (pre-UPX)
226214 shell : bash
227215 env :
@@ -236,11 +224,7 @@ jobs:
236224 "
237225 "$BINARY_PATH" --help
238226
239- # FIX 3 (occurrence 2/3): Same env var fix.
240227 # ── UPX compression ─────────────────────────────────────────────────────
241- # --best --lzma: maximum compression, ~10% better ratio than default.
242- # UPX on PyInstaller --onefile binaries is well-supported on Linux/Windows.
243- # macOS is excluded entirely (UPX 4.x+ dropped Mach-O support).
244228 - name : Compress with UPX
245229 if : matrix.use_upx
246230 shell : bash
@@ -256,30 +240,23 @@ jobs:
256240 print(f'Size: {s:,} bytes ({s/1024/1024:.2f} MB)')
257241 "
258242
259- # FIX 2: Renamed step — UPX is disabled for macOS targets so "after UPX"
260- # was misleading. PyInstaller-built Apple Silicon binaries require a valid
261- # code signature regardless of UPX. Ad-hoc signing requires no Developer ID
262- # and is sufficient for binaries distributed via crates.io (not quarantined) .
243+ # ── macOS aarch64: ad-hoc codesign ───────────────────────────────────────
244+ # PyInstaller-built Apple Silicon binaries require valid code signatures.
245+ # UPX is disabled for macOS — this step runs regardless of UPX.
246+ # --sign - creates an ad-hoc signature; no Apple Developer ID required .
263247 - name : Ad-hoc codesign binary (macOS aarch64, required for Apple Silicon)
264248 if : matrix.codesign
265249 run : |
266250 codesign --force --deep --sign - "${{ matrix.binary_path }}"
267251 codesign --verify --verbose "${{ matrix.binary_path }}"
268252
269253 # ── Post-compression smoke test ─────────────────────────────────────────
270- # Critical test: the UPX-compressed (and re-signed) binary must execute.
271- # Failure here means UPX broke the binary on this platform.
272254 - name : Smoke test (post-UPX)
273255 shell : bash
274256 run : |
275257 "${{ matrix.binary_path }}" --help
276258
277- # FIX 3 (occurrence 3/3): Same env var fix.
278259 # ── crates.io 10 MB size gate ────────────────────────────────────────────
279- # Each per-platform foryc-bin-* crate (Phase 2) embeds exactly one binary.
280- # crates.io hard limit is 10 MB per crate.
281- # If this assertion fails: add more entries to excludes[] in foryc.spec,
282- # investigate why binary grew, or reconsider distribution strategy.
283260 - name : Assert binary is under 10 MB (crates.io hard limit)
284261 shell : bash
285262 env :
@@ -316,7 +293,7 @@ jobs:
316293 # Python is NOT set up in this job — binary must be fully self-contained.
317294 # 2. End-to-end compile of compiler/examples/demo.fdl to Rust output.
318295 # 3. End-to-end compile of compiler/examples/demo.fdl to Java output.
319- # Validates that all 5 language generators are correctly embedded in binary .
296+ # Validates that all 5 language generators are correctly embedded.
320297 # ─────────────────────────────────────────────────────────────────────────────
321298 validate :
322299 name : validate / ${{ matrix.target }}
@@ -346,12 +323,6 @@ jobs:
346323 artifact_name : foryc-macos-aarch64
347324 binary_name : foryc
348325
349- # windows-latest (Server 2022) — intentionally different from
350- # build runner (windows-2019 was deprecated). Binary built on
351- # Server 2022 and validated on Server 2022 for consistency.
352- # Defender exclusions below are required for PyInstaller onefile
353- # extraction — without them, DLL load fails as Defender quarantines
354- # files extracted to RUNNER_TEMP mid-execution.
355326 - target : windows-x86_64
356327 os : windows-latest
357328 artifact_name : foryc-windows-x86_64
@@ -366,35 +337,28 @@ jobs:
366337 sparse-checkout : compiler/examples
367338 sparse-checkout-cone-mode : true
368339
369- # FIX 1: Changed from download-artifact@v5 to @v4.
370- # upload-artifact@v4 and download-artifact@v5 use incompatible artifact
371- # storage APIs — mismatched versions cause artifact-not-found failures.
372340 - name : Download artifact
373341 uses : actions/download-artifact@v4
374342 with :
375343 name : ${{ matrix.artifact_name }}
376344 path : ./artifact
377345
378- # FIX 10 (NEW): Defender exclusions in validate job.
379- # The original PR had Defender disable only in the build job .
380- # PyInstaller onefile binaries extract their payload to RUNNER_TEMP on
381- # first run. Without Defender exclusions, real-time scanning quarantines
382- # the extracted DLLs mid-execution, causing DLL load failed errors .
383- # This was the actual root cause of the "windows DLL failure" — not the
384- # runner version .
385- - name : Disable Windows Defender for PyInstaller extraction paths
346+ # ── Windows Defender ( validate job) ────────────────────────────────────
347+ # MUST appear before the binary is executed — not just before the build.
348+ # PyInstaller onefile extracts python311.dll to RUNNER_TEMP on first run.
349+ # Defender intercepts the LoadLibrary call at the memory level, causing
350+ # PYI-3104: Invalid access to memory location .
351+ # Path exclusions (Add-MpPreference) do NOT fix this — full real-time
352+ # monitoring disable is required in this job exactly as in the build job .
353+ - name : Disable Windows Defender real-time monitoring ( PyInstaller extraction workaround)
386354 if : runner.os == 'Windows'
387355 shell : pwsh
388- run : |
389- Add-MpPreference -ExclusionPath "$env:RUNNER_TEMP"
390- Add-MpPreference -ExclusionProcess "foryc.exe"
356+ run : Set-MpPreference -DisableRealtimeMonitoring $true
391357
392358 - name : Make executable (Unix)
393359 if : runner.os != 'Windows'
394360 run : chmod +x ./artifact/${{ matrix.binary_name }}
395361
396- # FIX 4: Tightened from broad keyword grep to specific usage line check.
397- # Previous pattern matched "fory" anywhere including crash tracebacks.
398362 # ── Test 1: --help ───────────────────────────────────────────────────────
399363 - name : Validate --help output
400364 shell : bash
@@ -406,8 +370,6 @@ jobs:
406370 exit 1
407371 }
408372
409- # FIX 5: Changed from file count check to non-empty .rs file check.
410- # Previous check passed if foryc produced a 0-byte file or an error log.
411373 # ── Test 2: End-to-end FDL → Rust ────────────────────────────────────────
412374 - name : End-to-end compile demo.fdl to Rust
413375 shell : bash
@@ -430,11 +392,7 @@ jobs:
430392 fi
431393 echo "PASS: ${RS_COUNT} .rs file(s) generated"
432394
433- # FIX 6: Added Java generator validation.
434- # The foryc.spec embeds all 5 language backends (java, python, cpp, rust, go).
435- # Without this test, a silent module drop in any non-Rust generator would
436- # be invisible and the broken binary would be promoted to Phase 2 crates.
437- # ── Test 3: End-to-end FDL → Java ────────────────────────────────────────
395+ # ── Test 3: End-to-end FDL → Java ─────────────────────────────────────────
438396 - name : End-to-end compile demo.fdl to Java
439397 shell : bash
440398 run : |
0 commit comments