anodizer_core/context/populate.rs
1use super::*;
2
3impl Context {
4 /// Populate template variables from `self.git_info`.
5 ///
6 /// Must be called after `self.git_info` is set. Sets the following vars:
7 /// - `Tag`, `Version`, `RawVersion` — tag and version strings
8 /// - `Major`, `Minor`, `Patch` — semver components
9 /// - `Prerelease` — prerelease suffix (or empty)
10 /// - `BuildMetadata` — build metadata from semver tag (or empty)
11 /// - `FullCommit`, `Commit` — full commit SHA (`Commit` is alias for `FullCommit`)
12 /// - `ShortCommit` — abbreviated commit SHA
13 /// - `Branch` — current git branch
14 /// - `CommitDate` — ISO 8601 author date of HEAD commit
15 /// - `CommitTimestamp` — unix timestamp of HEAD commit
16 /// - `IsGitDirty` — "true"/"false"
17 /// - `IsGitClean` — "true"/"false" (inverse of `IsGitDirty`)
18 /// - `GitTreeState` — "clean"/"dirty"
19 /// - `GitURL` — git remote URL
20 /// - `Summary` — git describe summary
21 /// - `TagSubject` — annotated tag subject or commit subject
22 /// - `TagContents` — full annotated tag message or commit message
23 /// - `TagBody` — tag message body or commit message body
24 /// - `IsSnapshot` — from context options
25 /// - `IsNightly` — from context options
26 /// - `IsDraft` — "false" (stages may override to "true")
27 /// - `IsSingleTarget` — "true"/"false" based on single_target option
28 /// - `PreviousTag` — previous matching tag, stripped in monorepo mode (or empty)
29 /// - `PrefixedTag` — full tag with monorepo prefix, or tag_prefix-prepended (Pro addition)
30 /// - `PrefixedPreviousTag` — full previous tag with prefix (Pro addition)
31 /// - `PrefixedSummary` — full summary with prefix (Pro addition)
32 /// - `IsRelease` — "true" if not snapshot and not nightly (Pro addition)
33 /// - `IsMerging` — "true" if running with --merge flag (Pro addition)
34 ///
35 /// **Stage-scoped variables** (NOT set here; set per-artifact during stage execution):
36 /// - `Binary` — binary name, set by build stage per binary and archive stage per archive
37 /// - `ArtifactName` — output artifact filename, set by archive stage after creating each archive
38 /// - `ArtifactPath` — absolute path to artifact, set by archive stage after creating each archive
39 /// - `ArtifactExt` — artifact file extension (e.g. `.tar.gz`, `.exe`), set alongside ArtifactName
40 /// - `ArtifactID` — build config `id` field, set by build stage per build config
41 /// - `Os` — target OS, set by archive/nfpm stages per target
42 /// - `Arch` — target architecture, set by archive/nfpm stages per target
43 /// - `Target` — full target triple (e.g. `x86_64-unknown-linux-gnu`), set alongside Os/Arch
44 /// - `Checksums` — combined checksum file contents, set by checksum stage
45 pub fn populate_git_vars(&mut self) {
46 if let Some(ref info) = self.git_info {
47 // The version-derived var block (Tag/Version/RawVersion/Base/Major/
48 // Minor/Patch/Prerelease/BuildMetadata) is factored into
49 // `set_version_vars` so `render_template_for_version` can re-derive
50 // the SAME block for a promotion's target version without drift.
51 // Deriving Version/RawVersion from the parsed `SemVer` struct (not
52 // `tag.strip_prefix('v')`) handles monorepo tags like `core-v0.3.2`.
53 set_version_vars(&mut self.template_vars, &info.semver, &info.tag);
54 self.template_vars.set("FullCommit", &info.commit);
55 self.template_vars.set("Commit", &info.commit);
56 self.template_vars.set("ShortCommit", &info.short_commit);
57 self.template_vars.set("Branch", &info.branch);
58 self.template_vars.set("CommitDate", &info.commit_date);
59 self.template_vars
60 .set("CommitTimestamp", &info.commit_timestamp);
61 self.template_vars.set_bool("IsGitDirty", info.dirty);
62 self.template_vars.set_bool("IsGitClean", !info.dirty);
63 self.template_vars
64 .set("GitTreeState", if info.dirty { "dirty" } else { "clean" });
65 self.template_vars.set("GitURL", &info.remote_url);
66 self.template_vars.set("Summary", &info.summary);
67 self.template_vars.set("TagSubject", &info.tag_subject);
68 self.template_vars.set("TagContents", &info.tag_contents);
69 self.template_vars.set("TagBody", &info.tag_body);
70 self.template_vars
71 .set("PreviousTag", info.previous_tag.as_deref().unwrap_or(""));
72 self.template_vars
73 .set("FirstCommit", info.first_commit.as_deref().unwrap_or(""));
74
75 // Pro additions: PrefixedTag, PrefixedPreviousTag, PrefixedSummary
76 //
77 // When monorepo.tag_prefix is configured, the git tag already
78 // contains the prefix (e.g. "subproject1/v1.2.3"). In this case:
79 // - Tag = prefix stripped (e.g. "v1.2.3")
80 // - PrefixedTag = full tag (e.g. "subproject1/v1.2.3")
81 // - PrefixedPreviousTag = full previous tag
82 //
83 // When monorepo is NOT configured, fall back to the original
84 // behavior: prepend tag.tag_prefix to construct PrefixedTag.
85 let monorepo_prefix = self.config.monorepo_tag_prefix();
86
87 // monorepo.tag_prefix takes precedence over tag.tag_prefix for
88 // PrefixedTag / PrefixedPreviousTag / PrefixedSummary behavior.
89 // When monorepo is configured, info.tag and info.summary already
90 // contain the prefix from git, so we strip for the base vars and
91 // use the raw values for the Prefixed variants.
92 if let Some(prefix) = monorepo_prefix {
93 // Monorepo mode: the tag in git_info is the FULL prefixed tag.
94 // PrefixedTag = full tag (already has prefix).
95 self.template_vars.set("PrefixedTag", &info.tag);
96
97 // Tag = prefix stripped. Override the Tag we set above.
98 let stripped_tag = crate::git::strip_monorepo_prefix(&info.tag, prefix);
99 self.template_vars.set("Tag", stripped_tag);
100
101 // Version: derived from the parsed SemVer struct (same source as
102 // the non-monorepo path and the build stage's per-crate
103 // re-scoping) so all three stay byte-identical. `info.semver`
104 // was parsed from the full prefixed tag, so it already excludes
105 // the monorepo prefix — no separate string-strip needed.
106 //
107 // For a non-semver tag under `--skip=validate`, info.semver is
108 // the skip-validate fallback, so this yields "0.0.0" rather than
109 // the old raw prefix-stripped string.
110 let version = info.semver.version_string();
111 self.template_vars.set("Version", &version);
112
113 // PrefixedPreviousTag = full previous tag (already has prefix).
114 let prev_tag = info.previous_tag.as_deref().unwrap_or("");
115 self.template_vars.set("PrefixedPreviousTag", prev_tag);
116
117 // PreviousTag = prefix stripped, consistent with Tag being stripped.
118 let stripped_prev = crate::git::strip_monorepo_prefix(prev_tag, prefix);
119 self.template_vars.set("PreviousTag", stripped_prev);
120
121 // PrefixedSummary: info.summary from `git describe` already
122 // includes the monorepo prefix (e.g. "subproject1/v1.2.3-0-gabc123d"),
123 // so use it as-is for the prefixed variant.
124 self.template_vars.set("PrefixedSummary", &info.summary);
125 // Summary: strip the monorepo prefix for the base variant.
126 let stripped_summary = crate::git::strip_monorepo_prefix(&info.summary, prefix);
127 self.template_vars.set("Summary", stripped_summary);
128 } else {
129 // Non-monorepo: prepend tag.tag_prefix to construct PrefixedTag.
130 let tag_prefix = self
131 .config
132 .tag
133 .as_ref()
134 .and_then(|t| t.tag_prefix.as_deref())
135 .unwrap_or("");
136 self.template_vars
137 .set("PrefixedTag", &format!("{}{}", tag_prefix, info.tag));
138 let prev_tag = info.previous_tag.as_deref().unwrap_or("");
139 let prefixed_prev = if prev_tag.is_empty() {
140 String::new()
141 } else {
142 format!("{}{}", tag_prefix, prev_tag)
143 };
144 self.template_vars
145 .set("PrefixedPreviousTag", &prefixed_prev);
146 self.template_vars.set(
147 "PrefixedSummary",
148 &format!("{}{}", tag_prefix, info.summary),
149 );
150 }
151 }
152
153 // `NightlyBuild`: stateless per-base-version build counter derived
154 // from `git rev-list --count <last-tag>..HEAD`. Resets automatically
155 // when a new version tag lands (no state anodizer persists). Set
156 // unconditionally (it is just a count), but intended for nightly /
157 // snapshot `version_template`s such as
158 // `"{{ .Base }}-nightly.{{ .NightlyBuild }}+{{ .ShortCommit }}"`.
159 // Defaults to "0" outside a git repo (synthetic snapshot/scratch
160 // builds) and on any git error so templates never fail to render.
161 //
162 // The monorepo prefix constrains the last-tag lookup to the active
163 // crate's tags so per-crate workspace runs count since the right
164 // tag (not the nearest tag from another subproject).
165 let nightly_build = if self.git_info.is_some() {
166 let root = self
167 .options
168 .project_root
169 .clone()
170 .unwrap_or_else(|| PathBuf::from("."));
171 let monorepo_prefix = self.config.monorepo_tag_prefix();
172 crate::git::count_commits_since_last_tag_in(&root, monorepo_prefix).unwrap_or(0)
173 } else {
174 0
175 };
176 self.template_vars
177 .set_structured("NightlyBuild", serde_json::Value::from(nightly_build));
178
179 // Mode flags are injected as real bools (not "true"/"false" strings)
180 // so `not IsSnapshot` / `IsSnapshot == false` / bare `{% if … %}`
181 // forms all evaluate correctly; `{{ IsSnapshot }}` interpolation
182 // still renders "true"/"false".
183 self.template_vars
184 .set_bool("IsSnapshot", self.options.snapshot);
185 self.template_vars
186 .set_bool("IsNightly", self.options.nightly);
187 // Surfaced to user `if_condition:` templates so stages can
188 // selectively run inside the determinism harness even when
189 // `not IsSnapshot` would otherwise skip them.
190 self.template_vars.set_bool(
191 "IsHarness",
192 self.env_var("ANODIZER_IN_DETERMINISM_HARNESS").is_some(),
193 );
194 // Wire IsDraft from `release.draft`.
195 let is_draft = self
196 .config
197 .release
198 .as_ref()
199 .and_then(|r| r.draft)
200 .unwrap_or(false);
201 self.template_vars.set_bool("IsDraft", is_draft);
202 self.template_vars
203 .set_bool("IsSingleTarget", self.options.single_target.is_some());
204
205 // Pro addition: IsRelease — true if this is a regular release (not snapshot, not nightly).
206 let is_release = !self.options.snapshot && !self.options.nightly;
207 self.template_vars.set_bool("IsRelease", is_release);
208
209 // Pro addition: IsMerging — true if running with --merge flag.
210 self.template_vars.set_bool("IsMerging", self.options.merge);
211 }
212
213 /// Populate time-related template variables.
214 ///
215 /// Sets:
216 /// - `Date` — UTC time as RFC 3339
217 /// - `Timestamp` — unix timestamp as string
218 /// - `Now` — UTC time as RFC 3339
219 /// - `Year` — four-digit year (e.g. "2026")
220 /// - `Month` — zero-padded month (e.g. "03")
221 /// - `Day` — zero-padded day (e.g. "30")
222 /// - `Hour` — zero-padded hour (e.g. "14")
223 /// - `Minute` — zero-padded minute (e.g. "05")
224 ///
225 /// Time source resolution (first match wins):
226 ///
227 /// 1. `SOURCE_DATE_EPOCH` env var — the standard reproducibility contract
228 /// (set by the determinism harness on every child release subprocess,
229 /// and the conventional way external CI / packagers signal a fixed
230 /// epoch). This is load-bearing for byte-stability of `metadata.json`
231 /// (which embeds `Date`) and any user template that consumes `Date` /
232 /// `Timestamp` / `Now`. Without this branch, two from-clean runs of
233 /// the same commit emit metadata.json files that differ in the `date`
234 /// field, defeating release-asset idempotency.
235 /// 2. `chrono::Utc::now()` — wall-clock fallback. The
236 /// legacy semantics for runs without SDE wired in. Note that the
237 /// template docs explicitly call `.Now` "not deterministic"
238 /// — under SDE-aware reproducible builds we deviate from that
239 /// behavior intentionally.
240 pub fn populate_time_vars(&mut self) {
241 // Resolution order (SDE first, else wall-clock) is centralized in
242 // `crate::sde::resolve_now_with_env` so any caller —
243 // `populate_time_vars`, Tera built-ins, stage-srpm's `%changelog`
244 // date, nightly `date_str` — sees identical "now" semantics.
245 // Routes through the injected `env_source` so tests can inject
246 // SOURCE_DATE_EPOCH via TestContextBuilder::env() without
247 // mutating the process env.
248 let now = crate::sde::resolve_now_with_env(self.env_source());
249 self.template_vars.set("Date", &now.to_rfc3339());
250 self.template_vars
251 .set("Timestamp", &now.timestamp().to_string());
252 self.template_vars.set("Now", &now.to_rfc3339());
253 self.template_vars
254 .set("Year", &now.format("%Y").to_string());
255 self.template_vars
256 .set("Month", &now.format("%m").to_string());
257 self.template_vars.set("Day", &now.format("%d").to_string());
258 self.template_vars
259 .set("Hour", &now.format("%H").to_string());
260 self.template_vars
261 .set("Minute", &now.format("%M").to_string());
262 }
263
264 /// Populate runtime environment variables.
265 ///
266 /// Sets:
267 /// - `RuntimeGoos` — host OS in Go-compatible naming (e.g. "linux", "darwin", "windows")
268 /// - `RuntimeGoarch` — host architecture in Go-compatible naming (e.g. "amd64", "arm64")
269 /// - `Runtime_Goos` / `Runtime_Goarch` — nested aliases
270 /// - `RustcVersion` — host rustc release version (e.g. "1.96.0"), or "" when
271 /// rustc is unavailable
272 pub fn populate_runtime_vars(&mut self) {
273 let goos = map_os_to_goos(std::env::consts::OS);
274 let goarch = map_arch_to_goarch(std::env::consts::ARCH);
275 self.template_vars.set("RuntimeGoos", goos);
276 self.template_vars.set("RuntimeGoarch", goarch);
277 // Runtime.Goos / Runtime.Goarch — after preprocessing
278 // the dot becomes an underscore-separated flat key. We expose both forms.
279 self.template_vars.set("Runtime_Goos", goos);
280 self.template_vars.set("Runtime_Goarch", goarch);
281 // RustcVersion is a host-environment fact like OS/arch, so it is set in
282 // the same call — keeping it a separate populate step risks a call-site
283 // forgetting to invoke the sibling.
284 self.populate_rustc_vars();
285 }
286
287 /// Populate the `RustcVersion` built-in template variable.
288 ///
289 /// Probes `rustc -vV` and extracts the `release:` line (e.g. `"1.96.0"`).
290 /// Sets `RustcVersion` to the extracted string, or to `""` when rustc is
291 /// unavailable or the line is absent — templates that reference
292 /// `{{ .RustcVersion }}` degrade to an empty value rather than erroring.
293 fn populate_rustc_vars(&mut self) {
294 let ver = crate::partial::detect_rustc_version().unwrap_or_default();
295 self.template_vars.set("RustcVersion", &ver);
296 }
297
298 /// Populate the `ReleaseNotes` template variable from stored changelogs.
299 ///
300 /// Should be called after the changelog stage has run and populated
301 /// `self.stage_outputs.changelogs`. Uses the first crate (by crate
302 /// universe order — top-level `crates:` then every `workspaces[].crates`
303 /// entry) whose changelog is present, or an empty string if no
304 /// changelogs exist. Universe order is deterministic, unlike HashMap
305 /// iteration order.
306 pub fn populate_release_notes_var(&mut self) {
307 // Look up changelogs in universe order for determinism. The universe
308 // walk (not `config.crates`) is what lets a pure-`workspaces:` config
309 // resolve a non-empty `ReleaseNotes` — its crates carry the
310 // changelogs but never appear in the top-level list.
311 let notes = self
312 .config
313 .crate_universe()
314 .into_iter()
315 .find_map(|c| self.stage_outputs.changelogs.get(&c.name))
316 .cloned()
317 .unwrap_or_default();
318 self.template_vars.set("ReleaseNotes", ¬es);
319 }
320
321 /// Refresh the `Artifacts` structured template variable from the current
322 /// artifact registry. Should be called before rendering release body and
323 /// announce templates so they can iterate over all artifacts.
324 ///
325 /// Each artifact is serialized as a map with keys: `name`, `path`, `target`,
326 /// `kind`, `crate_name`, and `metadata`.
327 ///
328 /// **Known metadata keys** (populated by individual stages):
329 /// - `format` — archive format (e.g. `"tar.gz"`, `"zip"`), set by archive stage
330 /// - `extra_file` — `"true"` when artifact is an extra file, set by checksum stage
331 /// - `extra_name_template` — name template override for extra files, set by checksum stage
332 /// - `digest` — docker image digest (e.g. `sha256:abc123...`), set by docker stage
333 /// - `id` — artifact ID from config, set by docker and build stages
334 /// - `binary` — binary name, set by build stage
335 pub fn refresh_artifacts_var(&mut self) {
336 // CSV metadata keys we expose as JSON arrays for template iteration.
337 // Storage remains HashMap<String,String> (flat); only the
338 // template-exposed view is expanded. The
339 // ExtraBinaries / ExtraFiles list semantics.
340 const CSV_LIST_KEYS: &[&str] = &["extra_binaries", "extra_files"];
341 // JSON-encoded list metadata keys: stored as a JSON-array string in
342 // `HashMap<String,String>`, exposed as a real array on the template
343 // side so `{% for p in .Artifacts[0].metadata.Platforms %}` works.
344 // `Platforms` is the platform-list slice on
345 // `DockerImageV2` artifacts.
346 const JSON_LIST_KEYS: &[&str] = &["Platforms"];
347
348 let artifacts_value: Vec<serde_json::Value> = self
349 .artifacts
350 .all()
351 .iter()
352 .map(|a| {
353 // Rebuild metadata map converting known CSV keys into arrays.
354 let mut metadata_map = serde_json::Map::with_capacity(a.metadata.len());
355 for (k, v) in &a.metadata {
356 if CSV_LIST_KEYS.contains(&k.as_str()) {
357 let items: Vec<serde_json::Value> = if v.is_empty() {
358 Vec::new()
359 } else {
360 v.split(',')
361 .map(|s| serde_json::Value::String(s.to_string()))
362 .collect()
363 };
364 metadata_map.insert(k.clone(), serde_json::Value::Array(items));
365 } else if JSON_LIST_KEYS.contains(&k.as_str()) {
366 // Decode JSON-array string into a real Value::Array;
367 // a malformed value falls back to the raw string so
368 // custom publishers can still inspect it.
369 let parsed = serde_json::from_str::<serde_json::Value>(v)
370 .unwrap_or_else(|_| serde_json::Value::String(v.clone()));
371 metadata_map.insert(k.clone(), parsed);
372 } else {
373 metadata_map.insert(k.clone(), serde_json::Value::String(v.clone()));
374 }
375 }
376 serde_json::json!({
377 "name": a.name,
378 "path": a.path.to_string_lossy(),
379 "target": a.target.as_deref().unwrap_or(""),
380 "kind": a.kind.as_str(),
381 "crate_name": a.crate_name,
382 "metadata": serde_json::Value::Object(metadata_map),
383 })
384 })
385 .collect();
386 self.template_vars
387 .set_structured("Artifacts", serde_json::Value::Array(artifacts_value));
388 }
389
390 /// Populate the `Metadata` structured template variable from config.metadata.
391 ///
392 /// Exposes the project metadata block as a nested map with PascalCase keys
393 /// the `.Metadata.*` namespace:
394 /// `Description`, `Homepage`, `Documentation`, `License`, `Repository`,
395 /// `Maintainers`, `ModTimestamp`, `FullDescription` (resolved),
396 /// `CommitAuthor.{Name,Email}`.
397 /// Missing fields default to empty strings / empty arrays.
398 ///
399 /// `full_description` supports `Inline`, `FromFile` (template-rendered
400 /// path, read from disk), and `FromUrl` (template-rendered URL +
401 /// headers, fetched through [`crate::content_source::resolve`] which
402 /// applies retries, body caps, and CR/LF header-injection guards).
403 pub fn populate_metadata_var(&mut self) -> anyhow::Result<()> {
404 // Clone the small scalar fields so we don't hold a borrow on self.config
405 // across the render_template calls below.
406 let (
407 description,
408 homepage,
409 documentation,
410 license,
411 repository,
412 maintainers,
413 mod_timestamp,
414 full_desc_src,
415 commit_author,
416 ) = {
417 let meta = self.config.metadata.as_ref();
418 // Description / homepage / documentation / license resolve through
419 // the project-level fallback: top-level `metadata.*` wins, else the
420 // primary crate's `Cargo.toml`-derived value. This keeps
421 // `{{ Metadata.* }}` single-sourced with the per-publisher
422 // `meta_*_for` resolvers, so dropping a redundant `metadata.license`
423 // (derivable from Cargo.toml) does not silently empty the var.
424 let description = self
425 .config
426 .meta_description_project()
427 .unwrap_or("")
428 .to_string();
429 let homepage = self
430 .config
431 .meta_homepage_project()
432 .unwrap_or("")
433 .to_string();
434 let documentation = self
435 .config
436 .meta_documentation_project()
437 .unwrap_or("")
438 .to_string();
439 let license = self.config.meta_license_project().unwrap_or("").to_string();
440 let repository = self
441 .config
442 .meta_repository_project()
443 .unwrap_or("")
444 .to_string();
445 let maintainers: Vec<String> = meta
446 .and_then(|m| m.maintainers.as_ref())
447 .cloned()
448 .unwrap_or_default();
449 let mod_timestamp = meta
450 .and_then(|m| m.mod_timestamp.as_deref())
451 .unwrap_or("")
452 .to_string();
453 let full_desc_src = meta.and_then(|m| m.full_description.clone());
454 let commit_author = meta.and_then(|m| m.commit_author.clone());
455 (
456 description,
457 homepage,
458 documentation,
459 license,
460 repository,
461 maintainers,
462 mod_timestamp,
463 full_desc_src,
464 commit_author,
465 )
466 };
467
468 // Resolve full_description through the shared ContentSource resolver
469 // so Inline, FromFile (template-rendered path), and FromUrl
470 // (template-rendered URL + headers, retried HTTP fetch with
471 // body cap and CR/LF guard) all behave the same as the release
472 // header/footer fields.
473 let full_description = match full_desc_src {
474 None => String::new(),
475 Some(src) => crate::content_source::resolve(
476 &src,
477 "metadata.full_description",
478 self,
479 &self.logger("metadata"),
480 )?,
481 };
482
483 let commit_author_map = serde_json::json!({
484 "Name": commit_author.as_ref().and_then(|c| c.name.clone()).unwrap_or_default(),
485 "Email": commit_author.as_ref().and_then(|c| c.email.clone()).unwrap_or_default(),
486 });
487
488 let meta_map = serde_json::json!({
489 "Description": description,
490 "Homepage": homepage,
491 "Documentation": documentation,
492 "License": license,
493 "Repository": repository,
494 "Maintainers": maintainers,
495 "ModTimestamp": mod_timestamp,
496 "FullDescription": full_description,
497 "CommitAuthor": commit_author_map,
498 });
499 self.template_vars.set_structured("Metadata", meta_map);
500 Ok(())
501 }
502}
503
504/// Map Rust's `std::env::consts::OS` to Go-compatible GOOS naming.
505/// Templates expect Go runtime names (e.g. "darwin" not "macos").
506pub fn map_os_to_goos(os: &str) -> &str {
507 match os {
508 "macos" => "darwin",
509 other => other, // linux, windows, freebsd, etc. already match
510 }
511}
512
513/// Map Rust's `std::env::consts::ARCH` to Go-compatible GOARCH naming.
514/// Templates expect Go runtime names (e.g. "amd64" not "x86_64").
515///
516/// Delegates to the shared [`crate::target::rust_arch_to_goarch`] table so a
517/// host-derived `{{ .Runtime.Goarch }}` can never disagree with the
518/// triple-derived arch tokens in asset names. `ARCH` doesn't encode
519/// endianness, so the host's own compile-time endianness disambiguates
520/// `powerpc64`/`mips64`. Tokens outside the table (`arm` — GOARCH really is
521/// "arm" — plus exotics) pass through unchanged.
522pub fn map_arch_to_goarch(arch: &str) -> &str {
523 crate::target::rust_arch_to_goarch(arch, cfg!(target_endian = "little")).unwrap_or(arch)
524}