Skip to content

[plugin] Refactor lambda-build into pluggable build backends + thin-layer plugin args#679

Merged
sebsto merged 2 commits into
mainfrom
fix/plugins_dedup_code
Jun 29, 2026
Merged

[plugin] Refactor lambda-build into pluggable build backends + thin-layer plugin args#679
sebsto merged 2 commits into
mainfrom
fix/plugins_dedup_code

Conversation

@sebsto

@sebsto sebsto commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator

Summary

Refactors the internals of the lambda-build build path into a pluggable backend
architecture, and tidies the plugin→helper argument plumbing. This is an internal
refactor: no user-facing flag, no --cross-compile value, no output ZIP path, and
no public Swift API changes. Behaviour is identical; the goal is to make adding new
cross-compilation backends (podman, finch, colima, swift-static-sdk, custom-sdk, …)
a localised, additive change.

1. Pluggable build backends (lambda-build)

The build logic in Builder.swift was a hard-coded native-vs-docker/container
branch with an enum (CrossCompileMethod) that carried both the parsed user choice
and the argv-building logic. It's now split along two independent axes:

  • BuildBackend protocolhow a build runs: NativeBuildBackend (on an
    Amazon Linux host) and ContainerBuildBackend (inside a container).
  • ContainerCLI protocol — the argument flavor of a container runtime:
    DockerCLI and AppleContainerCLI. A single ContainerBuildBackend serves both
    (and future runtimes) since only the argv spelling differs, not the build flow.

CrossCompileMethod keeps only the parsed value, parse(...), and a
makeBackend(configuration:) factory; its pullArguments/runArguments (and the
fatalError SDK stubs) are gone. Builder.build(...) is now select-and-delegate
(~8 lines); Builder.swift shrinks from ~700 to ~460 lines.

No shared argv helper between CLIs — by design. Each ContainerCLI builds its
complete argument vector independently (no base struct, no default implementation,
no shared closure). docker and Apple container argv may diverge in future CLI
versions, and future runtimes may not be compatible at all; a shared helper is
exactly the coupling that caused the earlier docker/container mix-up (#678). Each
CLI is covered by its own golden-argv tests with no shared fixtures.

Adding a backend later is now additive: a new *CLI.swift + a CrossCompileMethod
case + one line in makeBackend (container runtimes), or a new BuildBackend type
(SDK methods). No edits to Builder or existing CLIs.

File layout:

lambda-build/
  Builder.swift                 # build() select+delegate, package(), BuilderConfiguration, BuilderErrors
  CrossCompileMethod.swift      # enum + parse + makeBackend factory
  Backends/
    BuildBackend.swift          # protocol
    NativeBuildBackend.swift
    ContainerBuildBackend.swift
    ContainerCLI.swift          # protocol
    DockerCLI.swift
    AppleContainerCLI.swift

2. Thin-layer plugin argument plumbing

The plugins are meant to be the thinnest possible layer: resolve only the values
that need PluginContext / the package graph, then forward everything else to the
helper. lambda-build and the deprecated archive (Swift 6.4 passthrough) were
re-appending the original arguments after extracting some, so options like
--products reached the helper twice (and, for --products, caused the product to
be built twice). Both now forward argumentExtractor.remainingArguments, so a
consumed option is never forwarded again.

The same one-line bug (+ arguments+ argumentExtractor.remainingArguments) is
fixed in lambda-deploy, which had it too.

The internal plugin→helper flag --docker-tool-path is renamed to
--cross-compile-tool-path (and the config field dockerToolPath
crossCompileToolPath) so it no longer implies docker — it can carry any
cross-compile tool. This flag is internal (plugin→helper), not user-facing.

Note: the deprecated pre-6.4 archive implementation (Plugin.swift, behind
#if swift(<6.4)) is intentionally left untouched.

Testing

  • New BuildBackendTests.swift: separate golden-argv tests for DockerCLI and
    AppleContainerCLI (no shared fixtures), plus makeBackend factory tests
    (docker → DockerCLI, container → AppleContainerCLI, SDK methods throw).
  • All AWSLambdaPluginHelperTests pass (84 tests); swift build and
    swift format lint --strict clean.
  • End-to-end against a sample project: lambda-build default (docker) and
    --cross-compile container emit byte-for-byte identical argv to before the
    refactor; --products is no longer doubled.

@sebsto sebsto added the 🆕 semver/minor Adds new public API. label Jun 29, 2026
@sebsto sebsto self-assigned this Jun 29, 2026
@sebsto sebsto merged commit b369ed1 into main Jun 29, 2026
103 of 104 checks passed
@sebsto sebsto deleted the fix/plugins_dedup_code branch June 29, 2026 10:55
sebsto added a commit that referenced this pull request Jun 29, 2026
…andler deprecation warning (#680)

Two small follow-ups on top of the build-backend refactor (#679).

### 1. Move the cross-compile backend factory onto
`BuilderConfiguration`

The call site read circularly:

```swift
backend = try configuration.crossCompileMethod.makeBackend(configuration: configuration)
```

— reaching *through* the configuration to get the method, then handing
the
configuration straight back. The factory needs three values that the
configuration already owns (`crossCompileToolPath`, `baseDockerImage`,
`disableDockerImageUpdate`) plus the method, so it belongs on the
configuration.

- `CrossCompileMethod` is now purely the parsed user choice (`parse` +
cases +
  `description`); its `makeBackend(...)` is gone.
- `BuilderConfiguration` gains `makeCrossCompileBackend() -> any
BuildBackend`,
  which reads everything it needs from `self`.
- The call site becomes `backend = try
configuration.makeCrossCompileBackend()`.

No behaviour change. Also adopts explicit `any` (`any BuildBackend`,
`any ContainerCLI`).

The backend-selection tests now drive through
`BuilderConfiguration(...).makeCrossCompileBackend()` (passing
`--cross-compile`).
The old "unsupported methods throw" test on the factory was dropped:
unsupported
methods (`swift-static-sdk`, `custom-sdk`) are now rejected earlier, at
`BuilderConfiguration.init` via `CrossCompileMethod.parse`, and that
behaviour is
already covered by `UnsupportedCrossCompileMethodsPropertyTests`.

### 2. Fix a deprecation warning in the test log handler

swift-log 1.13 added a `log(event:)` requirement to `LogHandler` and
deprecated the
old `log(level:message:source:...)` method.
`CollectEverythingLogHandler` (test
helper) still implemented the deprecated method, so it fell back to the
deprecated
default implementation of `log(event:)`, producing a build warning. It
now
implements `log(event:)` directly. (`JSONLogHandler` in the library
already did.)

### Verification

- Clean build of products **and** tests: no compiler warnings originate
from our
sources (`Sources/`, `Tests/`); remaining output is only unrelated `ld:`
macOS
dylib-version notes and the pre-existing Docs.docc "unhandled file"
note.
- All tests pass: 140 `AWSLambdaRuntimeTests`, 83
`AWSLambdaPluginHelperTests`.
- `swift format lint --strict` clean.
@sebsto sebsto changed the title Refactor lambda-build into pluggable build backends + thin-layer plugin args [plugin] Refactor lambda-build into pluggable build backends + thin-layer plugin args Jun 29, 2026
sebsto added a commit that referenced this pull request Jun 29, 2026
…end (ZIP today, OCI-ready) (#681)

### Summary

Introduces a pluggable **archive backend** abstraction for
`lambda-build`, mirroring
the existing build-backend design (#679). Packaging the built binaries
into a
deployable artifact was hard-coded to ZIP inside `Builder.swift`; it is
now behind
an `ArchiveBackend` protocol with one implementation today (ZIP, the
existing code
moved verbatim) and a stubbed seam for OCI images in the future.

Internal refactor: the default behaviour is unchanged (ZIP), no public
Swift API
changes, and the output ZIP layout / path is identical.

### What changed

- **`ArchiveBackend` protocol** (`ArchiveBackends/ArchiveBackend.swift`)
— owns *what
kind of artifact* is produced from the built binaries. It's the
packaging
counterpart to `BuildBackend` (which owns *how the binaries are built*);
`Builder`
  selects and sequences the two.
- **`ZipArchiveBackend`** (`ArchiveBackends/ZipArchiveBackend.swift`) —
the existing
`Builder.package(...)` body, moved unchanged, with `zipToolPath`
injected via init.
- **`ArchiveFormat` enum** (`ArchiveFormat.swift`) — the parsed
`--archive-format`
value (`zip` | `oci`), mirroring `CrossCompileMethod`: `parse(...)`
(defaults to
  `zip`, case-insensitive), `isSupported` (`oci` → false), and a new
  `BuilderErrors.unsupportedArchiveFormat` case.
- **`--archive-format zip|oci` flag** — parsed into
`BuilderConfiguration`, with a
`makeArchiveBackend()` factory (following the same on-configuration
factory
convention as `makeCrossCompileBackend()`). `oci` is recognised but
throws a
"not yet supported" error. Help text and the verbose config dump
updated.
- **`Builder.build(...)`** now ends with
`configuration.makeArchiveBackend().archive(...)`
  instead of the inline `package(...)` method (which is removed).
- **Directory rename**: `Backends/` → `BuildBackends/`, so the two
families read as
  parallel siblings: `BuildBackends/` and `ArchiveBackends/`.

### Adding OCI later

Add `OCIArchiveBackend: ArchiveBackend` under `ArchiveBackends/`, then
flip the `oci`
case in `ArchiveFormat.isSupported` and `makeArchiveBackend()`. No other
changes.

### Testing

- New `ArchiveBackendTests.swift`: `ArchiveFormat.parse`
(default/case-insensitive/
oci-unsupported/unknown), `makeArchiveBackend()` selection (zip →
`ZipArchiveBackend`),
and `ZipArchiveBackend.archive` run against a temp build dir asserting
the `.zip` and
  relocated `bootstrap` are produced.
- All `AWSLambdaPluginHelperTests` pass (90 tests); `swift build` and
  `swift format lint --strict` clean.
- End-to-end: `--archive-format oci` fails fast with the stub error;
zip/default
  produce a byte-for-byte identical artifact to before.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🆕 semver/minor Adds new public API.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant