anodizer_core/config/build.rs
1use std::collections::HashMap;
2
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5
6use super::{
7 Amd64Variant, AppBundleConfig, ArchiveConfig, ArchivesConfig, BinstallConfig, BlobConfig,
8 ChecksumConfig, DmgConfig, DockerDigestConfig, DockerManifestConfig, DockerV2Config,
9 FlatpakConfig, HookEntry, HooksConfig, MsiConfig, NfpmConfig, NsisConfig, PkgConfig,
10 PublishConfig, ReleaseConfig, SnapcraftConfig, StringOrBool, VersionSyncConfig,
11 deserialize_archives_config, deserialize_string_or_bool_opt,
12};
13
14// ---------------------------------------------------------------------------
15// BuildIgnore — exclude specific os/arch combos from builds
16// ---------------------------------------------------------------------------
17
18/// Exclude a specific os/arch combination from the build matrix.
19#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
20#[serde(deny_unknown_fields)]
21pub struct BuildIgnore {
22 /// Operating system to exclude (e.g., "linux", "darwin", "windows").
23 pub os: String,
24 /// Architecture to exclude (e.g., "amd64", "arm64", "386").
25 pub arch: String,
26}
27
28// ---------------------------------------------------------------------------
29// BuildOverride — per-target env, flags, features
30// ---------------------------------------------------------------------------
31
32/// Override env, flags, or features for targets matching glob patterns.
33#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
34#[serde(default, deny_unknown_fields)]
35pub struct BuildOverride {
36 /// Glob patterns to match against target triples (e.g., `["x86_64-*", "*-linux-*"]`).
37 pub targets: Vec<String>,
38 /// Extra environment variables to set for matching targets.
39 #[serde(default)]
40 pub env: Option<Vec<String>>,
41 /// Extra flags to append for matching targets, one per list entry.
42 pub flags: Option<Vec<String>>,
43 /// Extra features to enable for matching targets.
44 pub features: Option<Vec<String>>,
45}
46
47// ---------------------------------------------------------------------------
48// CrossStrategy
49// ---------------------------------------------------------------------------
50
51#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
52#[serde(rename_all = "lowercase")]
53pub enum CrossStrategy {
54 Auto,
55 Zigbuild,
56 Cross,
57 Cargo,
58}
59
60// ---------------------------------------------------------------------------
61// BuilderKind — `cargo` (compile from source) vs `prebuilt` (import binary)
62// ---------------------------------------------------------------------------
63
64/// Selects which builder a `builds[]` entry uses. `Cargo` (the default) runs
65/// `cargo build` and discovers the resulting binary under
66/// `target/<triple>/<profile>/`. `Prebuilt` skips compilation entirely and
67/// imports a binary the operator already placed on disk via the per-build
68/// `prebuilt:` block.
69#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
70#[serde(rename_all = "lowercase")]
71pub enum BuilderKind {
72 /// Build the binary by invoking `cargo build` (or `cross` / `zigbuild`
73 /// per the `cross:` strategy). Default when no `builder:` is set.
74 #[default]
75 Cargo,
76 /// Import a binary already staged on disk instead of compiling. Pairs
77 /// with the `prebuilt:` block on the same build entry.
78 Prebuilt,
79}
80
81// ---------------------------------------------------------------------------
82// PrebuiltConfig — path template for the `prebuilt` builder
83// ---------------------------------------------------------------------------
84
85/// Per-build options for `builder: prebuilt`. Required when `builder: prebuilt`
86/// is set on the same `builds[]` entry.
87#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
88#[serde(default, deny_unknown_fields)]
89pub struct PrebuiltConfig {
90 /// Template path to the imported binary on disk. Rendered once per
91 /// target with these template variables available in addition to the
92 /// project-wide globals (`Version`, `ProjectName`, ...):
93 ///
94 /// - `{{ Target }}` — the full Rust target triple
95 /// (e.g. `x86_64-unknown-linux-gnu`).
96 /// - `{{ Os }}` — the OS slug (`linux`, `darwin`,
97 /// `windows`, ...).
98 /// - `{{ Arch }}` — the architecture slug (`amd64`,
99 /// `arm64`, `armv7`, ...).
100 /// - `{{ Amd64 }}` — AMD64 micro-architecture variant
101 /// (`v1` / `v2` / `v3` / `v4`); set for `x86_64-*` triples.
102 /// - `{{ Arm64 }}` — ARM64 micro-architecture variant (`v8`); set for
103 /// `aarch64-*` triples.
104 /// - `{{ Arm }}` — ARM micro-architecture variant (`6` / `7`); set for
105 /// `armv6*` / `armv7*` triples.
106 /// - `{{ I386 }}` — i386 micro-architecture variant (`sse2`); set for
107 /// `i686-*` / `i386-*` / `i586-*` triples.
108 /// - `{{ ArtifactExt }}` — `.exe` on Windows targets, empty elsewhere.
109 /// - `{{ ArtifactID }}` — the build entry's `id:` (empty when unset).
110 ///
111 /// The rendered path is `stat()`-ed before the import, unless
112 /// `--dry-run` is active, in which case the stat is skipped and the
113 /// path is accepted as given. A missing file, a permission error, or
114 /// any other I/O failure aborts the build with a message that names
115 /// both the rendered path and the originating target triple, matching
116 /// the "build will fail" contract.
117 ///
118 /// Recommendation: place the staged binaries OUTSIDE `dist/`. The
119 /// release pipeline removes `dist/` on every run; pointing `path:` at
120 /// `dist/...` will resolve against an empty directory.
121 pub path: String,
122}
123
124// ---------------------------------------------------------------------------
125// CrateConfig
126// ---------------------------------------------------------------------------
127
128#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
129#[serde(default, deny_unknown_fields)]
130pub struct CrateConfig {
131 /// Crate name as published (must match the Cargo.toml package name).
132 pub name: String,
133 /// Relative path to the crate directory from the project root.
134 pub path: String,
135 /// Git tag template used to tag and identify releases (supports
136 /// templates). Overrides `defaults.crates.tag_template`. When both are
137 /// unset, resolves to `CrateConfig::DEFAULT_TAG_TEMPLATE` — use
138 /// `resolved_tag_template()` rather than reading this field directly.
139 pub tag_template: Option<String>,
140 /// Pinned semver version. When set, `anodizer bump --strict` refuses to
141 /// edit this crate's `Cargo.toml` to anything other than this value;
142 /// without `--strict`, the bump proceeds with a warning. Lets a release
143 /// captain freeze a crate's version while still running broad
144 /// `--workspace` bumps.
145 pub version: Option<String>,
146 /// Other crates this crate depends on; ensures release ordering.
147 pub depends_on: Option<Vec<String>>,
148 /// Build configurations for this crate. One entry per binary by default.
149 pub builds: Option<Vec<BuildConfig>>,
150 /// Cross-compilation strategy for this crate: auto, zigbuild, cross, or cargo.
151 pub cross: Option<CrossStrategy>,
152 #[serde(default, deserialize_with = "deserialize_archives_config")]
153 #[schemars(schema_with = "archives_schema")]
154 pub archives: ArchivesConfig,
155 /// Checksum configuration for this crate.
156 pub checksum: Option<ChecksumConfig>,
157 /// GitHub release configuration for this crate.
158 pub release: Option<ReleaseConfig>,
159 /// Publishing targets (Homebrew, Scoop, AUR, etc.) for this crate.
160 pub publish: Option<PublishConfig>,
161 /// Docker V2 image build configurations for this crate (canonical API:
162 /// images+tags, annotations, build_args, sbom, disable). The legacy
163 /// `docker:` block was removed; this is the only docker surface. The
164 /// `docker_v2:` spelling is still accepted via serde alias for back-compat.
165 #[serde(alias = "docker_v2")]
166 pub dockers_v2: Option<Vec<DockerV2Config>>,
167 /// Docker image digest file configuration for this crate.
168 pub docker_digest: Option<DockerDigestConfig>,
169 /// Docker multi-platform manifest configurations for this crate.
170 pub docker_manifests: Option<Vec<DockerManifestConfig>>,
171 /// Linux package (deb, rpm, apk) configurations for this crate. Renamed
172 /// from `nfpm:` (singular) for spelling parity with `Defaults.nfpms` and
173 /// the rest of the plural-name per-crate packaging lists (`dmgs`, `msis`,
174 /// `pkgs`, `nsis`, ...). The `nfpm:` spelling is still accepted via serde
175 /// alias for back-compat.
176 #[serde(alias = "nfpm")]
177 pub nfpms: Option<Vec<NfpmConfig>>,
178 /// Snapcraft package configurations for this crate.
179 pub snapcrafts: Option<Vec<SnapcraftConfig>>,
180 /// macOS DMG disk image configurations for this crate.
181 pub dmgs: Option<Vec<DmgConfig>>,
182 /// Windows MSI installer configurations for this crate.
183 pub msis: Option<Vec<MsiConfig>>,
184 /// macOS PKG installer configurations for this crate.
185 pub pkgs: Option<Vec<PkgConfig>>,
186 /// NSIS installer configurations for this crate.
187 pub nsis: Option<Vec<NsisConfig>>,
188 /// macOS app bundle configurations for this crate.
189 pub app_bundles: Option<Vec<AppBundleConfig>>,
190 /// Linux Flatpak bundle configurations for this crate.
191 pub flatpaks: Option<Vec<FlatpakConfig>>,
192 /// Cloud storage (S3/GCS/Azure) upload configurations for this crate.
193 pub blobs: Option<Vec<BlobConfig>>,
194 /// cargo-binstall metadata configuration for this crate.
195 pub binstall: Option<BinstallConfig>,
196 /// Automatic version number synchronization configuration for this crate.
197 pub version_sync: Option<VersionSyncConfig>,
198 /// Repo-committed files that embed this crate's release version outside
199 /// `Cargo.toml` (repo-root-relative path strings). At `tag` time each file
200 /// has its occurrences of the old version rewritten to the new version —
201 /// both bare and `v`-prefixed forms, word-boundary anchored — and is staged
202 /// into the same bump commit as this crate's `Cargo.toml`. Overrides the
203 /// workspace-level `defaults.version_files`.
204 pub version_files: Option<Vec<String>>,
205 /// macOS universal binary (fat binary) configurations for this crate.
206 pub universal_binaries: Option<Vec<UniversalBinaryConfig>>,
207 /// When true (or template evaluating to "true"), all build outputs are
208 /// placed in a flat `dist/` directory instead of `dist/{target}/`.
209 #[serde(default, deserialize_with = "deserialize_string_or_bool_opt")]
210 pub no_unique_dist_dir: Option<StringOrBool>,
211 /// Hooks that run inside THIS crate's scope at the start of the release,
212 /// before the build. Distinct from the top-level `before:`, which fires
213 /// ONCE around the whole release; these fire once per crate with that
214 /// crate's version/tag template vars anchored, so `cmd` / `dir` / `env` /
215 /// `if` render against the crate's own `Version` / `Tag` / `ProjectName`.
216 /// A non-zero exit aborts the release.
217 ///
218 /// Fires once per crate in EVERY multi-crate mode — workspace per-crate
219 /// AND workspace lockstep with multiple publisher crates — in both a full
220 /// `anodizer release` and `anodizer release --publish-only`, matching the
221 /// per-crate iteration of `before_publish:` and the publishers. With an
222 /// explicit `--crate` subset only the selected crates' hooks fire. No-op
223 /// in a single-crate config with no `crates:` block (use the top-level
224 /// `before:` there).
225 pub before: Option<HooksConfig>,
226 /// Hooks that run inside THIS crate's scope at the end of the release,
227 /// after the crate's publish dispatch (and post-publish verification)
228 /// completes. Per-crate counterpart of the top-level `after:` (which fires
229 /// once around the whole release). Same per-crate firing semantics across
230 /// all modes, template surface, and abort semantics as the per-crate
231 /// `before:`.
232 pub after: Option<HooksConfig>,
233 /// Hooks that run immediately before THIS crate's publishers dispatch,
234 /// once per matching artifact (the same per-artifact semantics as the
235 /// top-level `before_publish:`), scoped to the crate's own artifacts and
236 /// template vars. Honors the per-entry `ids:` / `artifacts:` filters. A
237 /// non-zero exit aborts the release before that crate publishes to any
238 /// registry. The top-level `before_publish:` still fires once over the
239 /// full artifact set; this one targets a single crate's artifacts.
240 pub before_publish: Option<HooksConfig>,
241}
242
243/// Helper schema function for archives (accepts false or array).
244pub(super) fn archives_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
245 let mut schema = generator.subschema_for::<Option<Vec<ArchiveConfig>>>();
246 schema.ensure_object().insert(
247 "description".to_owned(),
248 "Archive configurations for this crate. Set to false to disable archiving, or provide an array of archive configs.".into(),
249 );
250 schema
251}
252
253impl Default for CrateConfig {
254 fn default() -> Self {
255 CrateConfig {
256 name: String::new(),
257 path: String::new(),
258 tag_template: None,
259 version: None,
260 depends_on: None,
261 builds: None,
262 cross: None,
263 archives: ArchivesConfig::Configs(vec![]),
264 checksum: None,
265 release: None,
266 publish: None,
267 dockers_v2: None,
268 docker_digest: None,
269 docker_manifests: None,
270 nfpms: None,
271 snapcrafts: None,
272 dmgs: None,
273 msis: None,
274 pkgs: None,
275 nsis: None,
276 app_bundles: None,
277 flatpaks: None,
278 blobs: None,
279 binstall: None,
280 version_sync: None,
281 version_files: None,
282 universal_binaries: None,
283 no_unique_dist_dir: None,
284 before: None,
285 after: None,
286 before_publish: None,
287 }
288 }
289}
290
291impl CrateConfig {
292 /// Built-in fallback used when neither the crate nor
293 /// `defaults.crates.tag_template` supplies a value.
294 pub const DEFAULT_TAG_TEMPLATE: &'static str = "v{{ Version }}";
295
296 /// Resolves this crate's effective tag template: the crate's own value
297 /// if set, else the built-in default. `defaults_merge::apply_to_crate`
298 /// folds `defaults.crates.tag_template` into `self.tag_template` before
299 /// this is ever read, so this accessor only needs to know about the
300 /// crate-level field and the built-in fallback.
301 pub fn resolved_tag_template(&self) -> &str {
302 self.tag_template
303 .as_deref()
304 .unwrap_or(Self::DEFAULT_TAG_TEMPLATE)
305 }
306}
307
308// ---------------------------------------------------------------------------
309// UniversalBinaryConfig
310// ---------------------------------------------------------------------------
311
312#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
313#[serde(default, deny_unknown_fields)]
314pub struct UniversalBinaryConfig {
315 /// Unique identifier for this universal binary, propagated into the
316 /// artifact's metadata as `id`.
317 #[serde(default)]
318 pub id: Option<String>,
319 /// Output filename template for the universal binary (supports templates).
320 pub name_template: Option<String>,
321 /// When true, remove the individual arch binaries after creating the universal binary.
322 pub replace: Option<bool>,
323 /// Build IDs filter: only combine artifacts from builds whose `id` is in this list.
324 pub ids: Option<Vec<String>>,
325 /// Pre/post hooks around universal binary creation.
326 pub hooks: Option<BuildHooksConfig>,
327 /// Override the modification timestamp for reproducible universal binaries.
328 pub mod_timestamp: Option<String>,
329}
330
331// ---------------------------------------------------------------------------
332// BuildConfig
333// ---------------------------------------------------------------------------
334
335#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
336#[serde(default, deny_unknown_fields)]
337pub struct BuildConfig {
338 /// Unique identifier for this build, used to reference it from archives and other configs.
339 pub id: Option<String>,
340 /// Binary name to build (must match a Cargo binary target in the crate).
341 ///
342 /// Optional so that `defaults.builds` (a path-mirrored template that
343 /// applies to every crate) can omit `binary` — the per-crate `builds[]`
344 /// entry supplies it. When the binary is absent at the per-crate level
345 /// it falls back to the crate's `name` field.
346 pub binary: Option<String>,
347 /// When true (or template evaluating to "true"), skip this build entirely.
348 #[serde(default, deserialize_with = "deserialize_string_or_bool_opt")]
349 pub skip: Option<StringOrBool>,
350 /// Target triples to build for. When set, REPLACES `defaults.targets`
351 /// for this build (override semantics — the per-build value wins
352 /// outright, no concat). When `None`, this build inherits
353 /// `defaults.targets` verbatim. Both `cli::commands::helpers::
354 /// collect_build_targets` and `stage-build` enforce this rule.
355 pub targets: Option<Vec<String>>,
356 /// Cargo features to enable for this build.
357 pub features: Option<Vec<String>>,
358 /// When true, pass --no-default-features to cargo build.
359 pub no_default_features: Option<bool>,
360 /// Per-target environment variables keyed as {target: {KEY: VALUE}}.
361 pub env: Option<HashMap<String, HashMap<String, String>>>,
362 /// Copy the binary from another build ID instead of building it.
363 pub copy_from: Option<String>,
364 /// Extra flags passed to cargo build, one per list entry (e.g., `["--release", "--locked"]`).
365 ///
366 /// Each entry is template-rendered then passed verbatim as a single argv
367 /// token — there is no `sh -c` step and no shell tokenization, so a
368 /// rendered value containing spaces stays one argv entry (it is NOT
369 /// re-split). Use one list entry per flag, including the flag and its
370 /// value as separate entries (`["--target-dir", "/tmp/{{ Version }}"]`)
371 /// when the value itself may contain spaces.
372 pub flags: Option<Vec<String>>,
373 /// When true, enable reproducible builds by stripping timestamps.
374 pub reproducible: Option<bool>,
375 /// Per-build hooks executed before and after compilation.
376 pub hooks: Option<BuildHooksConfig>,
377 /// Exclude specific os/arch combinations from this build's target matrix.
378 /// Falls back to `defaults.builds.ignore` when not set.
379 pub ignore: Option<Vec<BuildIgnore>>,
380 /// Per-target overrides for env, flags, and features for this build.
381 /// Falls back to `defaults.builds.overrides` when not set.
382 pub overrides: Option<Vec<BuildOverride>>,
383 /// Override the cross-compilation tool binary path (e.g., a custom `cross` wrapper).
384 /// When set, this binary is used instead of cargo/cross/zigbuild.
385 pub cross_tool: Option<String>,
386 /// Override the modification timestamp of built binaries for reproducible builds.
387 /// Template string (e.g. `"{{ CommitTimestamp }}"`) or unix timestamp.
388 pub mod_timestamp: Option<String>,
389 /// Override the cargo subcommand (default: auto-detected "build" or "zigbuild").
390 /// Enables e.g. `cargo auditable build` by setting `command: "auditable build"`.
391 pub command: Option<String>,
392 /// When true (or template evaluating to "true"), place binaries in flat dist/
393 /// instead of dist/{target}/. Overrides the crate-level setting.
394 #[serde(default, deserialize_with = "deserialize_string_or_bool_opt")]
395 pub no_unique_dist_dir: Option<StringOrBool>,
396 /// Declared x86-64 micro-architecture level for this build's artifacts:
397 /// `"v2"`, `"v3"`, `"v4"`, or the `"v1"` baseline. When set, it overrides
398 /// the level anodizer detects from the resolved build env — the
399 /// config-map and inherited process environment merged under cargo's own
400 /// mutually-exclusive source order (`CARGO_ENCODED_RUSTFLAGS`, then
401 /// `RUSTFLAGS`, then `CARGO_TARGET_<TRIPLE>_RUSTFLAGS`, first present
402 /// wins) carrying `-Ctarget-cpu=x86-64-v<N>` —
403 /// for BOTH the artifact's `amd64_variant` metadata — which names a
404 /// v2/v3-tuned group's archives (`…_amd64v3.tar.gz`) — and the config-time
405 /// asset-name derivation feeding cargo-binstall `pkg_url` and the
406 /// `curl | sh` installer's case table. Declare it when the tuning value is
407 /// only resolvable at build time (e.g. `RUSTFLAGS: "{{ .Env.CI_FLAGS }}"`)
408 /// or when importing a tuned binary via `builder: prebuilt`. Ignored for
409 /// non-x86_64 targets.
410 ///
411 /// Typed as [`Amd64Variant`], so any value outside `"v1"`..`"v4"` is
412 /// rejected when the config is parsed — on every axis the field can be
413 /// set from (`crates[]`, `workspaces[].crates[]`, and `defaults.builds`).
414 pub amd64_variant: Option<Amd64Variant>,
415 /// Builder to use for this entry. `cargo` (the default when omitted)
416 /// runs `cargo build`. `prebuilt` skips compilation and imports a
417 /// binary the operator already produced via the `prebuilt:` block.
418 ///
419 /// When `builder: prebuilt`, `targets:` MUST be set explicitly — no
420 /// `defaults.targets` fallback — and the `prebuilt.path` template is
421 /// rendered per target then stat()-ed before the import.
422 pub builder: Option<BuilderKind>,
423 /// Options for the `prebuilt` builder. Required when
424 /// `builder: prebuilt`; ignored (with a config-load warning) otherwise.
425 pub prebuilt: Option<PrebuiltConfig>,
426 /// Accepted-but-ignored legacy key. anodizer always drives the cargo
427 /// toolchain (or `cross` / `zigbuild`) directly, so there is no
428 /// pluggable build binary to point at. The field is parsed only so that
429 /// configs imported from a Go-style tool keep loading instead of
430 /// hard-failing under `deny_unknown_fields`; its value is never read.
431 /// Use `command:` to override the cargo subcommand instead.
432 #[doc(hidden)]
433 #[serde(default, skip_serializing)]
434 pub gobinary: Option<String>,
435}
436
437/// Pre/post hook configuration shared across multiple stages. Despite the
438/// `Build` prefix in the name, this type is used by both the **build** stage
439/// (pre/post compilation hooks) and the **archive** stage (pre/post archiving
440/// hooks). The name is kept for backward compatibility with existing configs.
441/// **Not** to be confused with the top-level `HooksConfig` (which carries a
442/// flat `hooks: Vec<String>` list for `before`/`after` lifecycle hooks).
443#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
444#[serde(default, deny_unknown_fields)]
445pub struct BuildHooksConfig {
446 /// Commands to run before the build step.
447 pub pre: Option<Vec<HookEntry>>,
448 /// Commands to run after the build step.
449 pub post: Option<Vec<HookEntry>>,
450}
451
452/// Pre/post archive hook configuration.
453///
454/// Archive hooks use `before`/`after`; build hooks use `pre`/`post`.
455#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
456#[serde(default, deny_unknown_fields)]
457pub struct ArchiveHooksConfig {
458 /// Commands to run before the archive step.
459 pub before: Option<Vec<HookEntry>>,
460 /// Commands to run after the archive step.
461 pub after: Option<Vec<HookEntry>>,
462}
463
464#[cfg(test)]
465mod legacy_field_tests {
466 use super::*;
467
468 /// An imported config carrying the removed Go-style `gobinary:` key must
469 /// still parse (accept-and-ignore) rather than hard-fail under
470 /// `deny_unknown_fields`. The value is captured but never read.
471 #[test]
472 fn build_gobinary_is_accepted_and_ignored() {
473 let build: BuildConfig =
474 serde_yaml_ng::from_str("binary: myapp\ngobinary: /usr/local/bin/go")
475 .expect("gobinary: must parse as an accepted-but-ignored legacy field");
476 assert_eq!(build.binary.as_deref(), Some("myapp"));
477 assert_eq!(build.gobinary.as_deref(), Some("/usr/local/bin/go"));
478 }
479
480 /// `gobinary` is parse-only and must not round-trip into serialized output
481 /// (it carries no behavior).
482 #[test]
483 fn build_gobinary_is_not_serialized() {
484 let build = BuildConfig {
485 gobinary: Some("/usr/local/bin/go".to_string()),
486 ..Default::default()
487 };
488 let yaml = serde_yaml_ng::to_string(&build).unwrap();
489 assert!(
490 !yaml.contains("gobinary"),
491 "gobinary must be skipped on serialize: {yaml}"
492 );
493 }
494}