From fb1558d5b153183f0b5f882c86dd27f9afa02244 Mon Sep 17 00:00:00 2001 From: Till Schneidereit Date: Tue, 1 Apr 2025 18:19:34 +0100 Subject: [PATCH 1/2] Spin Improvement Proposal: Supporting multiple build profiles This PR adds a SIP describing changes that'd allow developers to define and use multiple build profiles for components in a `spin.toml` file Signed-off-by: Till Schneidereit --- docs/content/sips/023-build-profiles.md | 141 ++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 docs/content/sips/023-build-profiles.md diff --git a/docs/content/sips/023-build-profiles.md b/docs/content/sips/023-build-profiles.md new file mode 100644 index 0000000000..c9eae1f98b --- /dev/null +++ b/docs/content/sips/023-build-profiles.md @@ -0,0 +1,141 @@ +title = "SIP 023 - Build profiles" +template = "main" +date = "2025-03-31T12:00:00Z" + +--- + +Summary: Support defining multiple build profiles for components, and running a Spin application in with those profiles. + +Owner(s): [till@fermyon.com](mailto:till@fermyon.com) + +Created: March 31, 2025 + +# Background + +Building and running individual components or entire applications in configurations other than the default one is currently difficult to do with Spin: one has to manually edit the `[component.name.build]` tables for some or all components. This is laborious and error-prone, and can lead to accidentally committing and deploying debug/testing/profiling builds to production. + +# Proposal + +This very simple proposal contains three parts: + +1. Adding an optional `[component.name.profile.profile-name]` table to use for defining additional build profiles of a component +2. Adding a `-profile [profile-name]` CLI flag and `SPIN_PROFILE` env var to `spin {build, watch, up}` to use specific build profiles of all components (where available, with fallback to the default otherwise) +3. Adding a (repeatable) `-component-profile name=[profile-name]` CLI flag and `SPIN_COMPONENT_PROFILE="name=[profile-name],..."` env var to use specific profiles for specific components + +## Example + +The following `spin.toml` file shows how these parts are applied: + +```toml +# (Source: ) + +spin_manifest_version = 2 + +[application] +name = "sentiment-analysis" +version = "0.1.0" +authors = ["Caleb Schoepp "] +description = "A sentiment analysis API that demonstrates using LLM inference and KV stores together" + +[[trigger.http]] +route = "/api/..." +component = "sentiment-analysis" + +[component.sentiment-analysis] +source = "target/spin-http-js.wasm" +allowed_outbound_hosts = [] +exclude_files = ["**/node_modules"] +key_value_stores = ["default"] +ai_models = ["llama2-chat"] + +[component.sentiment-analysis.build] +command = "npm run build" +watch = ["src/**/*", "package.json", "package-lock.json"] + +# Debug build of the `sentiment-analysis` component: +[component.sentiment-analysis.profile.debug] +source = "target/spin-http-js.debug.wasm" + +[component.sentiment-analysis.profile.debug.build] +command = "npm run build:debug" +watch = ["src/**/*", "package.json", "package-lock.json"] + +[[trigger.http]] +route = "/..." +component = "ui" + +# The `ui` component doesn't have a debug build, so the release build will always be used. +[component.ui] +source = { url = "", digest = "sha256:650376c33a0756b1a52cad7ca670f1126391b79050df0321407da9c741d32375" } +allowed_outbound_hosts = [] +files = [{ source = "../sentiment-analysis-assets", destination = "/" }] + +[variables] +kv_explorer_user = { required = true } +kv_explorer_password = { required = true } + +[[trigger.http]] +component = "kv-explorer" +route = "/internal/kv-explorer/..." + +[component.kv-explorer] +source = { url = "", digest = "sha256:65bc286f8315746d1beecd2430e178f539fa487ebf6520099daae09a35dbce1d" } +allowed_outbound_hosts = ["redis://*:*", "mysql://*:*", "postgres://*:*"] +# add or remove stores you want to explore here +key_value_stores = ["default"] + +# Uses a pre-built debug version of the component. +[component.kv-explorer.profile.debug] +source = { url = "", digest = ".." } + +[component.kv-explorer.variables] +kv_credentials = "{{ kv_explorer_user }}:{{ kv_explorer_password }}" + +``` + +The application defined in this manifest can be run in various configurations: + +- `spin up`: uses the release/default builds for everything +- `spin up --profile debug`: uses builds of the profile named `debug` of all components that have them, default builds for the rest +- `spin up --component-profile sentiment-analysis=debug`: uses the debug build of just the `sentiment-analysis` component, default builds for everything else + +## Profile-overridable fields + +This proposal is focused on build configurations. As such, the fields that are supported in `[component.component-name.profile.profile-name]` tables are limited to: + +- `source` +- `command` +- `watch` + +This set can be expanded in changes building on this initial support, as adding additional fields should always be backwards-compatible. + +## Profiles are all-or-nothing + +When running an application with a named profile applied, components that don’t define that profile fall back to the default build configuration. The same isn’t true for individual keys in a profile: if e.g. a profile contains the `source` field but no `command` field, running `spin build` will not try to run the default configuration’s build command. + +## Local testing only + +At least for now, this proposal is focused entirely on local testing: deployment through plugins is not proposed to gain support for a `--profile` flag. + +## Backwards-compatibility + +This proposal should be fully backwards-compatible. + +## Implementation concerns + +I can't see any major implementation concerns: this should hopefully be pretty straightforward. + +## Alternatives considered + +Instead of adding generalized profiles support, we could add just a hard-coded `debug` profile. This would simplify the syntax a little bit, but at the cost of flexibility. + +We could do so by using syntax such as + +```toml +[component.sentiment-analysis.debug] +... +``` + +and changing the `--profile [profile-name]` flag to instead be `--debug`, and `--component-profile sentiment-analysis=debug` be `--debug-component sentiment-analysis`. + +I’m not proposing this alternative because the benefits seem much smaller than the downsides. From b3c8ef522f641f8c80ca7c40208cf825ae68e0d3 Mon Sep 17 00:00:00 2001 From: Till Schneidereit Date: Sat, 5 Apr 2025 13:56:47 +0100 Subject: [PATCH 2/2] Address review feedback Signed-off-by: Till Schneidereit --- docs/content/sips/023-build-profiles.md | 97 +++++++++++++------------ 1 file changed, 51 insertions(+), 46 deletions(-) diff --git a/docs/content/sips/023-build-profiles.md b/docs/content/sips/023-build-profiles.md index c9eae1f98b..9f0106bf05 100644 --- a/docs/content/sips/023-build-profiles.md +++ b/docs/content/sips/023-build-profiles.md @@ -16,37 +16,32 @@ Building and running individual components or entire applications in configurati # Proposal -This very simple proposal contains three parts: +This very simple proposal consists of four parts: 1. Adding an optional `[component.name.profile.profile-name]` table to use for defining additional build profiles of a component 2. Adding a `-profile [profile-name]` CLI flag and `SPIN_PROFILE` env var to `spin {build, watch, up}` to use specific build profiles of all components (where available, with fallback to the default otherwise) -3. Adding a (repeatable) `-component-profile name=[profile-name]` CLI flag and `SPIN_COMPONENT_PROFILE="name=[profile-name],..."` env var to use specific profiles for specific components + 1. By popular demand, the `debug` profile can be selected using the `--debug` CLI flag + 2. `release` is the default profile, but can also be explicitly named for regularity, including with the `--release` alias +3. Adding a (repeatable) `-component-profile [component-name]=[profile-name]` CLI flag and `SPIN_COMPONENT_PROFILE="[component-name]=[profile-name],..."` env var to use specific profiles for specific components +4. Any operations that publish a Spin application to a remote location (such as `spin registry push`) must implicitly run a `spin build --release` step before publishing build results ## Example The following `spin.toml` file shows how these parts are applied: ```toml -# (Source: ) +# (Source: https://github.com/fermyon/ai-examples/blob/main/sentiment-analysis-ts/spin.toml, +# with parts not relevant to profiles omitted.) spin_manifest_version = 2 [application] name = "sentiment-analysis" -version = "0.1.0" -authors = ["Caleb Schoepp "] -description = "A sentiment analysis API that demonstrates using LLM inference and KV stores together" - -[[trigger.http]] -route = "/api/..." -component = "sentiment-analysis" +... [component.sentiment-analysis] source = "target/spin-http-js.wasm" -allowed_outbound_hosts = [] -exclude_files = ["**/node_modules"] -key_value_stores = ["default"] -ai_models = ["llama2-chat"] +... [component.sentiment-analysis.build] command = "npm run build" @@ -60,45 +55,49 @@ source = "target/spin-http-js.debug.wasm" command = "npm run build:debug" watch = ["src/**/*", "package.json", "package-lock.json"] -[[trigger.http]] -route = "/..." -component = "ui" - # The `ui` component doesn't have a debug build, so the release build will always be used. [component.ui] -source = { url = "", digest = "sha256:650376c33a0756b1a52cad7ca670f1126391b79050df0321407da9c741d32375" } -allowed_outbound_hosts = [] -files = [{ source = "../sentiment-analysis-assets", destination = "/" }] - -[variables] -kv_explorer_user = { required = true } -kv_explorer_password = { required = true } - -[[trigger.http]] -component = "kv-explorer" -route = "/internal/kv-explorer/..." +source = { url = ".../spin_static_fs.wasm", digest = "..." } +... [component.kv-explorer] -source = { url = "", digest = "sha256:65bc286f8315746d1beecd2430e178f539fa487ebf6520099daae09a35dbce1d" } -allowed_outbound_hosts = ["redis://*:*", "mysql://*:*", "postgres://*:*"] -# add or remove stores you want to explore here -key_value_stores = ["default"] +source = { url = ".../spin-kv-explorer.wasm", digest = "..." } +... # Uses a pre-built debug version of the component. [component.kv-explorer.profile.debug] -source = { url = "", digest = ".." } - -[component.kv-explorer.variables] -kv_credentials = "{{ kv_explorer_user }}:{{ kv_explorer_password }}" - +source = { url = ".../spin-kv-explorer.debug.wasm", digest = "..." } ``` The application defined in this manifest can be run in various configurations: - `spin up`: uses the release/default builds for everything -- `spin up --profile debug`: uses builds of the profile named `debug` of all components that have them, default builds for the rest +- `spin up --profile debug` or `spin up --debug`: uses builds of the profile named `debug` of all components that have them, default builds for the rest - `spin up --component-profile sentiment-analysis=debug`: uses the debug build of just the `sentiment-analysis` component, default builds for everything else +## Details of profile selection + +### Application-wide profile selection + +A profile can be selected for the entire application in three ways: +1. Via the `--profile=[profile-name]` CLI flag to `spin {build, up, watch}` +2. Via the `--debug` CLI flag to `spin {build, up, watch}` as an alias for `--profile=debug` +3. Via the `SPIN_PROFILE=[profile-name]` env var + +If the `SPIN_PROFILE` env var is set and `--profile` / `--debug` are passed as CLI flags, the value of the CLI flag is used, and the env var ignored. `--profile` and `--debug` are mutually exclusive and lead `spin` to exit with an error. + +### Component-specific profile selection + +A profile can be selected for a specific component in two ways: +1. Via the `--component-profile [component-name]=[profile-name],...` CLI flag +2. Via the `SPIN_COMPONENT_PROFILE="[component-name]=[profile-name],..."` env var + +The CLI flag is repeatable, but can also support multiple comma-separated entries in a single instance. + +If both the `SPIN_COMPONENT_PROFILE` env var is set and `--component-profile` provided, they will be merged into a single list, with settings from the CLI flag taking precedence: if a profile is specified in both for the same component, the value from the CLI flag is used. + +Component-specific profile selection overrides the application-wide profile. E.g. the command `spin up --debug --component-profile foo=release` will cause all components to use the `debug` profile, except for the `foo` component, which will use the default/`release` profile. + ## Profile-overridable fields This proposal is focused on build configurations. As such, the fields that are supported in `[component.component-name.profile.profile-name]` tables are limited to: @@ -109,23 +108,29 @@ This proposal is focused on build configurations. As such, the fields that are s This set can be expanded in changes building on this initial support, as adding additional fields should always be backwards-compatible. -## Profiles are all-or-nothing +## Profiles are atomic -When running an application with a named profile applied, components that don’t define that profile fall back to the default build configuration. The same isn’t true for individual keys in a profile: if e.g. a profile contains the `source` field but no `command` field, running `spin build` will not try to run the default configuration’s build command. +When running an application with a named profile applied, components that don’t define that profile fall back to the default build configuration. The same isn’t true for individual fields in a profile: if e.g. a profile contains the `source` field but no `command` field, running `spin build` will not try to run the default configuration’s build command. That also means that if a field is required, it must be included in all profiles, and omitting it in any profiles will cause the manifest to be rejected. ## Local testing only At least for now, this proposal is focused entirely on local testing: deployment through plugins is not proposed to gain support for a `--profile` flag. -## Backwards-compatibility +## Deploy implies release build + +To ensure that deployment operations always publishes up-to-date artifacts, they must implicitly run a build of the release profile of all components before performing the publishing itself. E.g., where currently one has to run `spin build && spin registry push` to ensure that the latest source code is compiled before publishing `.wasm` components to a registry, implementing this proposal would change behavior such that `spin registry push` automatically runs `SPIN_COMPONENT_PROFILE="" spin build --release`. + +We probably can't apply this to plugin-based deployment operations automatically, but should ensure that we provide documentation to plugin authors that instructs them to do this, and ideally an easy way to do it via a well-named function in the API. + +# Backwards-compatibility -This proposal should be fully backwards-compatible. +This proposal should be fully backwards-compatible in practice, but entails a change in behavior around publishing operations, as discussed in the previous section. -## Implementation concerns +# Implementation concerns I can't see any major implementation concerns: this should hopefully be pretty straightforward. -## Alternatives considered +# Alternatives considered Instead of adding generalized profiles support, we could add just a hard-coded `debug` profile. This would simplify the syntax a little bit, but at the cost of flexibility. @@ -138,4 +143,4 @@ We could do so by using syntax such as and changing the `--profile [profile-name]` flag to instead be `--debug`, and `--component-profile sentiment-analysis=debug` be `--debug-component sentiment-analysis`. -I’m not proposing this alternative because the benefits seem much smaller than the downsides. +I’m not proposing this alternative because the benefits seem much smaller than the downsides: it's useful to be able to use profiles with properties such as "optimize, but retain information required for profiling", or "skip debug asserts, but include debug symbols". Other build tools such a Rust's `cargo` started out with just `release` and `debug` and then had to retrofit support for custom profiles later on to satisfy this kind of requirement.