Skip to content

Commit 148446b

Browse files
authored
fix(ci): stop double-nesting framework inside Linux .deb/.rpm packages (#2776)
* fix(ci): stop double-nesting framework inside Linux .deb/.rpm packages The nfpm contents rule pointed `src` at `./build/framework/` for the framework staging step. `wheels-core-VER.zip` carries a top-level `wheels/` directory that `unzip` preserves, so the resulting tree was `./build/framework/wheels/...`. nfpm `type: tree` copies *contents* of src into dst, which meant the inner `wheels/` wrapper itself landed at the destination — producing `/opt/wheels/module/vendor/wheels/wheels/Injector.cfc` instead of `/opt/wheels/module/vendor/wheels/Injector.cfc`. After the user-side wrapper sync (`/opt/wheels/module/*` → `~/.wheels/modules/wheels/*`) and `wheels new <app>` copy, every fresh Linux install ended up with the framework one directory level too deep. Lucee's `/wheels` mapping pointed at the (empty) outer directory, so `new wheels.Injector("wheels.Bindings")` in the generated `public/Application.cfc` threw `could not find component or class with name [wheels.Injector]` on the first request. The existing onError handler then dereferenced `application.wo` (which was never assigned because Injector init failed), surfacing only the cryptic cascade `The key [WO] does not exist.` — issue #2773. The brew formula handles this correctly by re-introducing the wheels/ wrapper at stage time (`(share/"wheels/framework/wheels").install Dir["*"]`). Both Linux nfpm configs now pin `src` at `./build/framework/wheels/` so the contents flatten into `/opt/wheels/module/vendor/wheels/` as intended. The published 4.0.1 .deb / .rpm artifacts ship the broken layout (1 .deb download, 0 .rpm at time of fix). A re-released 4.0.2 will be needed to deliver the fix to users — the change here is to the build config only, not to any framework or CLI code. Tests: `vendor/wheels/tests/specs/cli/LinuxPackageStagingSpec.cfc` gains a per-channel `it()` that asserts `src: ./build/framework/wheels/` + `dst: /opt/wheels/module/vendor/wheels/` are paired in each nfpm yaml. Structural assertion follows the existing #2700 pattern (the file already pins four other packaging invariants the same way). Note on local verification: the structural spec was sanity-checked via equivalent grep / perl POSIX patterns over the YAMLs (positive match for the fixed form, zero matches for the buggy form). Running the spec through the CFML runner locally was blocked by a port-8081 collision with two stale wheels server processes from prior dev sessions — CI compat-matrix will run the spec across every engine × DB on this PR. Closes #2773 Signed-off-by: Peter Amiri <peter@alurium.com> * test(ci): add negative guard for buggy framework src in nfpm yamls Reviewer A on PR #2776 (wheels-bot) flagged that the new framework-src spec only asserted the *fixed* form was present, without a matching `toBeFalse` for the buggy `./build/framework/` form. The file's existing wrapper-routing checks (lines 60-68 / 81-106) already use a dual- assertion pattern; the new spec was a one-sided outlier. Add the negative guard: if a future copy-paste leaves both the bare `src: ./build/framework/` and the fixed `src: ./build/framework/wheels/` in the same yaml, nfpm would stage both — the bare one reintroduces the double-nesting and breaks every fresh Linux install. The spec now fails loudly in that scenario instead of silently passing on the presence of the fixed entry. The two regexes are mutually exclusive by construction: the positive matches `framework/wheels/` followed by whitespace + `dst:`; the negative matches `framework/` followed *immediately* by whitespace + `dst:`. Since `wheels` isn't whitespace, `[[:space:]]+` can't bridge across it, so the negative regex cannot false-positive on the fixed form. Confirmed via perl POSIX equivalent against both nfpm yamls plus a synthetic buggy fixture. Also adds an inline comment to the positive assertion documenting why `[[:space:]]+` works across the YAML line break (POSIX `[[:space:]]` resolves to Java's `\s` in both Lucee and Adobe CF, which includes `\n`) — addresses Reviewer A's Nit 2 observation that the cross-line match hadn't been locally verified. Signed-off-by: Peter Amiri <peter@alurium.com> --------- Signed-off-by: Peter Amiri <peter@alurium.com>
1 parent 1f4d729 commit 148446b

4 files changed

Lines changed: 71 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ All historical references to "CFWheels" in this changelog have been preserved fo
2222

2323
### Fixed
2424

25+
- Linux `.deb` / `.rpm` packages double-nested the framework at `/opt/wheels/module/vendor/wheels/wheels/` instead of `/opt/wheels/module/vendor/wheels/`. `wheels-core-VER.zip` carries a top-level `wheels/` directory that `unzip` preserves; the nfpm `type: tree` rule then copied the entire `build/framework/` tree (wrapper and all) into the destination, leaving `Injector.cfc` one level too deep. Every fresh `wheels new` install on Ubuntu/Fedora then crashed on first request with `could not find component or class with name [wheels.Injector]`, cascading into the cryptic `The key [WO] does not exist.` error in `onError`. The brew formula handles this correctly via `(share/"wheels/framework/wheels").install Dir["*"]`; the Linux nfpm configs now pin `src` at `./build/framework/wheels/` to match. Regression spec at `vendor/wheels/tests/specs/cli/LinuxPackageStagingSpec.cfc` (#2773)
2526
- `onError` in the generated app template and demo `public/Application.cfc` now guards `application.wo` with `StructKeyExists(application, "wo")` after the recovery try/catch. When `new wheels.Injector(...)` fails during `onApplicationStart` (e.g. a stale `/wheels` mapping under Lucee Express 7), the original error is preserved via a minimal HTML fallback instead of cascading into the cryptic "The key [WO] does not exist" exception that hit "Your First 15 Minutes" tutorial users on fresh installs
2627

2728
----

tools/distribution-drafts/linux-packages/nfpm-wheels-be.yaml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,14 @@ contents:
3434
- src: ./build/module/
3535
dst: /opt/wheels/module/
3636
type: tree
37-
- src: ./build/framework/
37+
# Framework source — see nfpm-wheels.yaml for the full explainer of why src
38+
# MUST be ./build/framework/wheels/ (with trailing /wheels/), not bare
39+
# ./build/framework/. Short version: nfpm `type: tree` copies src contents
40+
# into dst, and the wheels-core-VER.zip preserves a top-level wheels/ dir,
41+
# so without the inner /wheels/ we get a double-nested
42+
# /opt/wheels/module/vendor/wheels/wheels/ that breaks runtime resolution
43+
# of `wheels.Injector`. See issue #2773.
44+
- src: ./build/framework/wheels/
3845
dst: /opt/wheels/module/vendor/wheels/
3946
type: tree
4047
- src: ./build/sqlite-jdbc.jar

tools/distribution-drafts/linux-packages/nfpm-wheels.yaml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,16 @@ contents:
3838
dst: /opt/wheels/module/
3939
type: tree
4040
# Framework source — staged into vendor/wheels/ in scaffolded apps.
41-
- src: ./build/framework/
41+
# IMPORTANT: src points at ./build/framework/wheels/ (the inner directory),
42+
# NOT ./build/framework/. wheels-core-VER.zip has a top-level wheels/ dir
43+
# that `unzip` preserves, leaving the framework at ./build/framework/wheels/.
44+
# nfpm's `type: tree` copies the *contents* of src into dst, so without the
45+
# trailing /wheels/ on src, the framework double-nests as
46+
# /opt/wheels/module/vendor/wheels/wheels/Injector.cfc and Lucee can't
47+
# resolve `wheels.Injector` at app startup. See issue #2773. The brew
48+
# formula handles this the equivalent way — see homebrew-wheels
49+
# Formula/wheels.rb's `(share/"wheels/framework/wheels").install Dir["*"]`.
50+
- src: ./build/framework/wheels/
4251
dst: /opt/wheels/module/vendor/wheels/
4352
type: tree
4453
# SQLite JDBC — required at first run by the wrapper script (cliff fix from

vendor/wheels/tests/specs/cli/LinuxPackageStagingSpec.cfc

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,58 @@ component extends="wheels.WheelsTest" {
164164
);
165165
});
166166

167+
it("stages framework src from ./build/framework/wheels/ so contents flatten under vendor/wheels/", () => {
168+
var src = fileRead(t.path);
169+
// wheels-core-VER.zip has a top-level `wheels/` directory inside it
170+
// (the smoke test asserts this at tools/ci/smoke-test-module.sh:112).
171+
// nfpm `type: tree` copies the *contents* of src into dst, so if src
172+
// points at ./build/framework/ (one level above the inner wheels/),
173+
// the entire wheels/ subdirectory itself lands at dst — producing
174+
// /opt/wheels/module/vendor/wheels/wheels/Injector.cfc instead of
175+
// /opt/wheels/module/vendor/wheels/Injector.cfc. The framework then
176+
// never loads at runtime ("could not find component or class with
177+
// name [wheels.Injector]" — see issue ##2773).
178+
//
179+
// The brew formula handles this by explicitly re-introducing the
180+
// wheels/ wrapper at stage time — see homebrew-wheels Formula/wheels.rb:62
181+
// — (share/"wheels/framework/wheels").install Dir["*"]. The .deb/.rpm
182+
// equivalent is to point src at the inner wheels/ directory directly.
183+
//
184+
// `[[:space:]]+` matches across the YAML line break between the src
185+
// value and `dst:` — POSIX `[[:space:]]` resolves to Java's `\s` in
186+
// both Lucee and Adobe CF, which includes `\n`.
187+
var hasFixedPair = reFindNoCase(
188+
"src:[[:space:]]+\./build/framework/wheels/[[:space:]]+dst:[[:space:]]+/opt/wheels/module/vendor/wheels/",
189+
src
190+
) > 0;
191+
expect(hasFixedPair).toBeTrue(
192+
t.label & " must declare `src: ./build/framework/wheels/` (with the "
193+
& "trailing /wheels/) for the framework contents entry. Without the "
194+
& "inner /wheels/ segment, nfpm's `type: tree` double-nests the "
195+
& "framework at /opt/wheels/module/vendor/wheels/wheels/, and Lucee "
196+
& "fails to resolve `wheels.Injector` at app startup. See issue ##2773."
197+
);
198+
199+
// Negative guard: the buggy bare-framework form must not coexist
200+
// with the fixed form. A future copy-paste could leave both entries
201+
// in the file, and nfpm would happily stage both — the bare one
202+
// reintroduces the double-nesting. Pairs with the toBeTrue above
203+
// per the dual-assertion pattern already used by the wrapper-routing
204+
// checks at lines 60-68 / 81-106.
205+
var hasBuggyPair = reFindNoCase(
206+
"src:[[:space:]]+\./build/framework/[[:space:]]+dst:[[:space:]]+/opt/wheels/module/vendor/wheels/",
207+
src
208+
) > 0;
209+
expect(hasBuggyPair).toBeFalse(
210+
t.label & " must NOT declare `src: ./build/framework/` (without "
211+
& "the trailing /wheels/) for any contents entry targeting "
212+
& "/opt/wheels/module/vendor/wheels/. If both the bare and the "
213+
& "/wheels/-suffixed entries coexist, nfpm stages the inner "
214+
& "wheels/ wrapper as a subdirectory and the framework "
215+
& "double-nests. See issue ##2773."
216+
);
217+
});
218+
167219
});
168220
})(target);
169221
}

0 commit comments

Comments
 (0)