1use std::path::PathBuf;
2
3use camino::Utf8PathBuf;
4use thiserror::Error;
5
6#[derive(Debug, Error)]
8pub enum BuildError {
9 #[error("target dependency cycle detected: {}", format_cycle(.0))]
10 DependencyCycle(Vec<String>),
11
12 #[error("no target named {0:?} is defined in the package graph")]
13 UnknownTargetReference(String),
14
15 #[error("target {:?} is ambiguous; use `package:target` (candidates: {})", .0, format_candidates(.1))]
16 AmbiguousTarget(String, Vec<String>),
17
18 #[error("unknown package {package:?} in target selector {selector:?}")]
19 UnknownPackageInTargetSelector { package: String, selector: String },
20
21 #[error("package {package:?} has no target {target:?}")]
22 UnknownTargetInPackage { package: String, target: String },
23
24 #[error(
32 "`deps` entry {dep:?} matches package dependency {package:?}, but that package declares no library or header-only target named {dep:?}; a bare name is shorthand for a same-named linkable target (`{dep}:{dep}`), not a default library - use a local target name or an explicit `package:target`{}",
33 format_target_suggestions(.package, .candidates)
34 )]
35 NoSameNameTargetInDependency {
36 dep: String,
37 package: String,
38 candidates: Vec<String>,
41 },
42
43 #[error(
50 "dependency {dep:?} is declared under `[dev-dependencies]` of package {package:?} but is not active here; dev dependencies link only into `test` / `example` targets, and only `cabin test` activates them for the selected packages"
51 )]
52 DevDependencyNotActive { dep: String, package: String },
53
54 #[error(
59 "target `{target}` requires the features [{}] of package {package:?}, which are not enabled; enable them with `--features {}` or add them to `[features].default` of package {package:?}",
60 format_feature_list(.missing), .missing.join(",")
61 )]
62 TargetRequiresFeatures {
63 target: String,
64 package: String,
65 missing: Vec<String>,
66 },
67
68 #[error(
73 "target `{consumer}` depends on `{dep_target}`, which requires the features [{}] of package {dep_package:?}; {}",
74 format_feature_list(.missing),
75 format_dep_feature_help(.dep_package, .missing, *.fix)
76 )]
77 TargetDepRequiresFeatures {
78 consumer: String,
79 dep_target: String,
80 dep_package: String,
81 missing: Vec<String>,
82 fix: FeatureGateFix,
84 },
85
86 #[error(
92 "every default-buildable target in the selected packages requires features that are not enabled: {}; enable them with `--features <name>`",
93 format_gated_targets(.gated)
94 )]
95 AllDefaultTargetsRequireFeatures {
96 gated: Vec<(String, Vec<String>)>,
99 },
100
101 #[error("target {0:?} has no source files; nothing to build")]
102 EmptyTargetSources(String),
103
104 #[error("source path {path} for target {target:?} is not supported: {reason}")]
105 InvalidSourcePath {
106 target: String,
107 path: Utf8PathBuf,
108 reason: String,
109 },
110
111 #[error("path {} is not valid UTF-8 and cannot be used in build commands", .0.display())]
112 NonUtf8Path(PathBuf),
113
114 #[error(
115 "selected workspace packages declare no C/C++ targets to build; pick a package with at least one library or executable"
116 )]
117 EmptySelectedPackages,
118
119 #[error(transparent)]
123 UnsupportedToolchain(#[from] cabin_core::ToolDetectionError),
124
125 #[error(
130 "selected toolchain mixes MSVC and GCC/Clang tools, which Cabin cannot drive together: {detail}"
131 )]
132 MixedToolchainDialects { detail: String },
133
134 #[error(
137 "target {target:?} has source `{path}` with an unrecognized extension; supported extensions are .c (C) and .cc / .cpp / .cxx / .c++ / .C (C++)"
138 )]
139 UnrecognizedSourceExtension { target: String, path: Utf8PathBuf },
140
141 #[error(
145 "target {target:?} has C source `{path}` but no C compiler is available; set the `CC` environment variable, pass `--cc <path>`, or add `cc = ...` under [toolchain]"
146 )]
147 MissingCCompiler { target: String, path: Utf8PathBuf },
148
149 #[error(
156 "target `{target}` compiles {language} sources but no {language} standard is declared; add `{field} = \"<standard>\"` to its `[package]` or `[target.<name>]` table, or opt into a workspace default with `{field} = {{ workspace = true }}`"
157 )]
158 MissingLanguageStandard {
159 target: String,
160 language: &'static str,
161 field: &'static str,
162 },
163
164 #[error(
172 "target `{consumer}` compiles {language} as `{consumer_standard}`, but its dependency `{dependency}` requires `{required}` for consumers of its public interface (from {requirement_source}); raise `{consumer}`'s {language} standard to at least `{required}`, or lower the dependency's interface standard if its public headers permit"
173 )]
174 IncompatibleLanguageStandard {
175 consumer: String,
176 dependency: String,
177 language: &'static str,
178 consumer_standard: &'static str,
179 required: &'static str,
180 requirement_source: &'static str,
181 },
182
183 #[error(
189 "target `{target}` requests {language} standard `{standard}`, which has no stable MSVC `/std:` flag; use a standard cl.exe supports (c11, c17, c++14, c++17, c++20) or build with a GCC/Clang toolchain"
190 )]
191 StandardUnsupportedOnMsvcDialect {
192 target: String,
193 language: &'static str,
194 standard: &'static str,
195 },
196
197 #[error(
202 "target `{target}` enables `gnu-extensions`, but this compiler has no GNU dialect mode; remove `gnu-extensions` or build with a GCC/Clang toolchain"
203 )]
204 GnuExtensionsUnsupportedOnMsvcDialect { target: String },
205
206 #[error("the manifest declares conflicting standard selections")]
212 StandardFlagConflict(#[source] Box<cabin_core::StandardFlagConflict>),
213}
214
215fn format_cycle(cycle: &[String]) -> String {
216 cycle.join(" -> ")
217}
218
219fn format_candidates(candidates: &[String]) -> String {
220 candidates.join(", ")
221}
222
223fn format_feature_list(features: &[String]) -> String {
224 features
225 .iter()
226 .map(|f| format!("{f:?}"))
227 .collect::<Vec<_>>()
228 .join(", ")
229}
230
231#[derive(Debug, Clone, Copy, PartialEq, Eq)]
237pub enum FeatureGateFix {
238 RootSelection,
241 DependencyEdge,
244 DevDependencyEdge,
248 UpstreamEdge,
253}
254
255fn format_dep_feature_help(dep_package: &str, missing: &[String], fix: FeatureGateFix) -> String {
256 let features = missing
257 .iter()
258 .map(|f| format!("{f:?}"))
259 .collect::<Vec<_>>()
260 .join(", ");
261 match fix {
262 FeatureGateFix::RootSelection => format!(
263 "enable them with `--features {}` or add them to `[features].default`",
264 missing.join(",")
265 ),
266 FeatureGateFix::DependencyEdge => format!(
267 "request them on the dependency edge, e.g. `{dep_package} = {{ version = \"...\", features = [{features}] }}` under `[dependencies]`"
268 ),
269 FeatureGateFix::DevDependencyEdge => format!(
270 "request them on the dependency edge, e.g. `{dep_package} = {{ version = \"...\", features = [{features}] }}` under `[dev-dependencies]`"
271 ),
272 FeatureGateFix::UpstreamEdge => format!(
273 "request them on the dependency edge that makes {dep_package:?} available, e.g. `{dep_package} = {{ version = \"...\", features = [{features}] }}` in the depending package's manifest"
274 ),
275 }
276}
277
278fn format_gated_targets(gated: &[(String, Vec<String>)]) -> String {
279 gated
280 .iter()
281 .map(|(target, missing)| format!("`{target}` requires [{}]", format_feature_list(missing)))
282 .collect::<Vec<_>>()
283 .join(", ")
284}
285
286fn format_target_suggestions(package: &str, candidates: &[String]) -> String {
287 if candidates.is_empty() {
288 format!(" (package {package:?} declares no library or header-only targets)")
289 } else {
290 format!(", e.g. `{}`", candidates.join("` or `"))
291 }
292}