Skip to content

Commit 1fb5178

Browse files
committed
Plan B
Signed-off-by: itowlson <[email protected]>
1 parent 01919e1 commit 1fb5178

File tree

8 files changed

+119
-58
lines changed

8 files changed

+119
-58
lines changed

crates/build/src/lib.rs

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ pub async fn build(
2626
target_checks: TargetChecking,
2727
cache_root: Option<PathBuf>,
2828
) -> Result<()> {
29-
let build_info = component_build_configs(manifest_file)
29+
let build_info = component_build_configs(manifest_file, profile)
3030
.await
3131
.with_context(|| {
3232
format!(
@@ -36,7 +36,7 @@ pub async fn build(
3636
})?;
3737
let app_dir = parent_dir(manifest_file)?;
3838

39-
let build_result = build_components(component_ids, build_info.components(), &app_dir, profile);
39+
let build_result = build_components(component_ids, build_info.components(), &app_dir);
4040

4141
// Emit any required warnings now, so that they don't bury any errors.
4242
if let Some(e) = build_info.load_error() {
@@ -114,7 +114,6 @@ fn build_components(
114114
component_ids: &[String],
115115
components: Vec<ComponentBuildInfo>,
116116
app_dir: &Path,
117-
profile: Option<&str>,
118117
) -> Result<(), anyhow::Error> {
119118
let components_to_build = if component_ids.is_empty() {
120119
components
@@ -144,24 +143,18 @@ fn build_components(
144143

145144
components_to_build
146145
.into_iter()
147-
.map(|c| build_component(c, app_dir, profile))
146+
.map(|c| build_component(c, app_dir))
148147
.collect::<Result<Vec<_>, _>>()?;
149148

150149
terminal::step!("Finished", "building all Spin components");
151150
Ok(())
152151
}
153152

154153
/// Run the build command of the component.
155-
fn build_component(
156-
build_info: ComponentBuildInfo,
157-
app_dir: &Path,
158-
profile: Option<&str>,
159-
) -> Result<()> {
154+
fn build_component(build_info: ComponentBuildInfo, app_dir: &Path) -> Result<()> {
160155
match build_info.build {
161156
Some(b) => {
162-
let commands = b.commands(profile);
163-
164-
let command_count = commands.len();
157+
let command_count = b.commands().len();
165158

166159
if command_count > 1 {
167160
terminal::step!(
@@ -172,7 +165,7 @@ fn build_component(
172165
);
173166
}
174167

175-
for (index, command) in commands.into_iter().enumerate() {
168+
for (index, command) in b.commands().iter().enumerate() {
176169
if command_count > 1 {
177170
terminal::step!(
178171
"Running build step",

crates/build/src/manifest.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,17 @@ impl ManifestBuildInfo {
6666
/// given (v1 or v2) manifest path. If the manifest cannot be loaded, the
6767
/// function attempts fallback: if fallback succeeds, result is Ok but the load error
6868
/// is also returned via the second part of the return value tuple.
69-
pub async fn component_build_configs(manifest_file: impl AsRef<Path>) -> Result<ManifestBuildInfo> {
69+
pub async fn component_build_configs(
70+
manifest_file: impl AsRef<Path>,
71+
profile: Option<&str>,
72+
) -> Result<ManifestBuildInfo> {
7073
let manifest = spin_manifest::manifest_from_file(&manifest_file);
7174
match manifest {
7275
Ok(mut manifest) => {
76+
manifest.ensure_profile(profile)?;
77+
7378
spin_manifest::normalize::normalize_manifest(&mut manifest);
74-
let components = build_configs_from_manifest(&manifest);
79+
let components = build_configs_from_manifest(&manifest, profile);
7580
let deployment_targets = deployment_targets_from_manifest(&manifest);
7681
Ok(ManifestBuildInfo::Loadable {
7782
components,
@@ -101,13 +106,14 @@ pub async fn component_build_configs(manifest_file: impl AsRef<Path>) -> Result<
101106

102107
fn build_configs_from_manifest(
103108
manifest: &spin_manifest::schema::v2::AppManifest,
109+
profile: Option<&str>,
104110
) -> Vec<ComponentBuildInfo> {
105111
manifest
106112
.components
107113
.iter()
108114
.map(|(id, c)| ComponentBuildInfo {
109115
id: id.to_string(),
110-
build: c.build.clone(),
116+
build: c.build_config(profile),
111117
})
112118
.collect()
113119
}

crates/doctor/src/rustlang/target.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,9 @@ impl Diagnostic for TargetDiagnostic {
1717
let manifest_str = patient.manifest_doc.to_string();
1818
let manifest = spin_manifest::manifest_from_str(&manifest_str)?;
1919
let uses_rust = manifest.components.values().any(|c| {
20-
c.build
21-
.as_ref()
22-
.map(|b| b.commands(None).iter().any(|c| c.starts_with("cargo")))
23-
.unwrap_or_default()
20+
c.build_commands(None)
21+
.iter()
22+
.any(|c| c.starts_with("cargo"))
2423
});
2524

2625
if uses_rust {

crates/loader/src/local.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ impl LocalLoader {
166166
let component_requires_service_chaining = requires_service_chaining(&component);
167167

168168
let source = self
169-
.load_component_source(id, component.source(self.profile.as_ref()))
169+
.load_component_source(id, component.source(self.profile.as_ref()).clone())
170170
.await
171171
.with_context(|| format!("Failed to load Wasm source {}", component.source))?;
172172

crates/manifest/src/schema/common.rs

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -168,11 +168,6 @@ pub struct ComponentBuildConfig {
168168
///
169169
/// Learn more: https://spinframework.dev/build#setting-up-for-spin-build
170170
pub command: Commands,
171-
/// The command or commands for building the component in non-default profiles.
172-
/// If a component has no special build instructions for a profile, the
173-
/// default build command is used.
174-
#[serde(default, skip_serializing_if = "super::v2::Map::is_empty")]
175-
pub profile: super::v2::Map<String, ComponentProfileBuildCommand>,
176171
/// The working directory for the build command. If omitted, the build working
177172
/// directory is the directory containing `spin.toml`.
178173
///
@@ -193,27 +188,9 @@ pub struct ComponentBuildConfig {
193188
pub watch: Vec<String>,
194189
}
195190

196-
/// Component build configuration
197-
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
198-
#[serde(deny_unknown_fields)]
199-
pub struct ComponentProfileBuildCommand {
200-
/// The command or commands to build the component in a named profile. If multiple commands
201-
/// are specified, they are run sequentially from left to right.
202-
///
203-
/// Example: `profile.debug.command = "cargo build"`
204-
///
205-
/// Learn more: https://spinframework.dev/build#setting-up-for-spin-build
206-
pub command: Commands,
207-
}
208-
209191
impl ComponentBuildConfig {
210192
/// The commands to execute for the build
211-
pub fn commands(&self, profile: Option<&str>) -> Vec<&String> {
212-
if let Some(profile) = profile {
213-
if let Some(profile_cmd) = self.profile.get(profile) {
214-
return profile_cmd.command.as_vec();
215-
}
216-
}
193+
pub fn commands(&self) -> Vec<&String> {
217194
self.command.as_vec()
218195
}
219196
}
@@ -236,7 +213,7 @@ pub enum Commands {
236213
}
237214

238215
impl Commands {
239-
fn as_vec(&self) -> Vec<&String> {
216+
pub(crate) fn as_vec(&self) -> Vec<&String> {
240217
match self {
241218
Self::Single(cmd) => vec![cmd],
242219
Self::Multiple(cmds) => cmds.iter().collect(),

crates/manifest/src/schema/v2.rs

Lines changed: 89 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,25 @@ impl AppManifest {
5252
}
5353
Ok(())
5454
}
55+
56+
/// Whether any component in the application defines the given profile.
57+
/// Not every component defines every profile, and components intentionally
58+
/// fall back to the anonymouse profile if they are asked for a profile
59+
/// they don't define. So this can be used to detect that a user might have
60+
/// mistyped a profile (e.g. `spin up --profile deugb`).
61+
pub fn ensure_profile(&self, profile: Option<&str>) -> anyhow::Result<()> {
62+
let Some(p) = profile else {
63+
return Ok(());
64+
};
65+
66+
let is_defined = self.components.values().any(|c| c.profile.contains_key(p));
67+
68+
if is_defined {
69+
Ok(())
70+
} else {
71+
Err(anyhow!("Profile {p} is not defined in this application"))
72+
}
73+
}
5574
}
5675

5776
/// App details
@@ -397,31 +416,89 @@ pub struct Component {
397416
pub dependencies: ComponentDependencies,
398417
/// TODO: profile docs
399418
#[serde(default, skip_serializing_if = "Map::is_empty")]
400-
pub profile: Map<String, ComponentProfileUp>,
419+
pub profile: Map<String, ComponentProfileOverride>,
401420
}
402421

403-
/// Customisations for running a Spin component in a non-default profile.
422+
/// Customisations for a Spin component in a non-default profile.
404423
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
405424
#[serde(deny_unknown_fields)]
406-
pub struct ComponentProfileUp {
425+
pub struct ComponentProfileOverride {
407426
/// The file, package, or URL containing the component Wasm binary.
408427
///
409-
/// Example: `source = "bin/cart.wasm"`
428+
/// Example: `source = "bin/debug/cart.wasm"`
410429
///
411430
/// Learn more: https://spinframework.dev/writing-apps#the-component-source
412-
pub source: ComponentSource,
431+
#[serde(default, skip_serializing_if = "Option::is_none")]
432+
pub source: Option<ComponentSource>,
433+
434+
/// The command or commands for building the component in non-default profiles.
435+
/// If a component has no special build instructions for a profile, the
436+
/// default build command is used.
437+
#[serde(default, skip_serializing_if = "Option::is_none")]
438+
pub build: Option<ComponentProfileBuildOverride>,
439+
}
440+
441+
/// Customisations for a Spin component build in a non-default profile.
442+
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
443+
#[serde(deny_unknown_fields)]
444+
pub struct ComponentProfileBuildOverride {
445+
/// The command or commands to build the component in a named profile. If multiple commands
446+
/// are specified, they are run sequentially from left to right.
447+
///
448+
/// Example: `build.command = "cargo build"`
449+
///
450+
/// Learn more: https://spinframework.dev/build#setting-up-for-spin-build
451+
pub command: super::common::Commands,
413452
}
414453

415454
impl Component {
416-
/// TODO: docs! docs! docs!
417-
pub fn source(&self, profile: Option<impl AsRef<str>>) -> ComponentSource {
418-
let Some(profile) = profile.as_ref() else {
419-
return self.source.clone();
455+
fn profile(&self, profile: Option<impl AsRef<str>>) -> Option<&ComponentProfileOverride> {
456+
profile.and_then(|p| self.profile.get(p.as_ref()))
457+
}
458+
459+
/// The commands to execute for the build
460+
pub fn build_commands(&self, profile: Option<&str>) -> Vec<&String> {
461+
let profile_build = self.profile(profile).and_then(|o| o.build.as_ref());
462+
463+
match profile_build {
464+
None => match &self.build {
465+
Some(b) => b.command.as_vec(),
466+
None => vec![],
467+
},
468+
Some(b) => b.command.as_vec(),
469+
}
470+
}
471+
472+
/// The build configuration for the component
473+
pub fn build_config(&self, profile: Option<impl AsRef<str>>) -> Option<ComponentBuildConfig> {
474+
let build_config = self.build.clone();
475+
let build_profile = self.profile(profile).and_then(|o| o.build.as_ref());
476+
477+
let Some(build_profile) = build_profile else {
478+
return build_config;
420479
};
421-
let Some(source) = self.profile.get(profile.as_ref()) else {
422-
return self.source.clone();
480+
481+
let Some(mut build_config) = build_config else {
482+
return Some(ComponentBuildConfig {
483+
command: build_profile.command.clone(),
484+
workdir: None,
485+
watch: vec![],
486+
});
423487
};
424-
source.source.clone()
488+
489+
build_config.command = build_profile.command.clone();
490+
491+
Some(build_config)
492+
}
493+
494+
/// TODO: docs! docs! docs!
495+
pub fn source(&self, profile: Option<impl AsRef<str>>) -> &ComponentSource {
496+
let profile_source = self.profile(profile).and_then(|o| o.source.as_ref());
497+
498+
match profile_source {
499+
Some(s) => s,
500+
None => &self.source,
501+
}
425502
}
426503
}
427504

src/commands/up.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,8 @@ impl UpCommand {
174174
.context("Could not canonicalize working directory")?;
175175

176176
let resolved_app_source = self.resolve_app_source(&app_source, &working_dir).await?;
177+
resolved_app_source.ensure_profile(self.profile())?;
178+
177179
if self.help {
178180
let trigger_cmds =
179181
trigger_commands_for_trigger_types(resolved_app_source.trigger_types())

src/commands/up/app_source.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,4 +126,11 @@ impl ResolvedAppSource {
126126

127127
types.into_iter().collect()
128128
}
129+
130+
pub fn ensure_profile(&self, profile: Option<&str>) -> anyhow::Result<()> {
131+
match self {
132+
Self::File { manifest, .. } => manifest.ensure_profile(profile),
133+
_ => Ok(()),
134+
}
135+
}
129136
}

0 commit comments

Comments
 (0)