@@ -19,8 +19,8 @@ mod sources;
1919
2020use build:: { build_bundle_at, run_fused_entity_ids, write_cdn_manifest, BuildCounters } ;
2121use sources:: {
22- fetch_ids_into_store, from_collection_urn, from_entity_ids , from_live_reference ,
23- from_reference , manifest_from_ids,
22+ fetch_ids_into_store, from_collection_urn, from_live_reference , from_reference ,
23+ manifest_from_ids,
2424} ;
2525
2626const DEFAULT_LAMBDAS_URL : & str = "http://localhost:5141/lambdas" ;
@@ -83,7 +83,7 @@ pub(crate) struct EffectiveToggles {
8383
8484 /// Canonical `{hash}_{depsdigest}_{platform}` naming for glb/gltf
8585 /// bundles (upstream asset-reuse parity). Default ON (ab-cdn runs
86- /// asset-reuse since v49); ABGEN_ASSET_REUSE =0 opts out for parity runs
86+ /// asset-reuse since v49); ABGEN_DEPS_DIGEST =0 opts out for parity runs
8787 /// against pre-v49 reference trees.
8888 pub ( crate ) asset_reuse : bool ,
8989}
@@ -103,6 +103,13 @@ fn usage_text() -> &'static str {
103103 abgen-corpus --entity-ids <ids.txt> <out-dir> \\ \n \
104104 [--platform windows|mac] [--content-dir <dir>] [--cdn-layout] \\ \n \
105105 [--fetch-missing] [--content-server-url <url>] [-j JOBS]\n \
106+ abgen-corpus --pointer <x,y | urn | entityId> <out-dir> \\ \n \
107+ [--platform windows|mac] [--content-dir <dir>] [--cdn-layout] \\ \n \
108+ [--fetch-missing] [--no-deps-digest] [-j JOBS]\n \
109+ (single-target convenience: resolves the pointer via the content\n \
110+ server, then behaves like --entity-ids with that one entity;\n \
111+ with --cdn-layout also copies manifests to the production CDN\n \
112+ naming <out>/manifest/<entity>_<platform>.json)\n \
106113 abgen-corpus --collection-urn <urn> <out-dir> \\ \n \
107114 [--lambdas-url <url>] [--platform windows|mac] [--content-dir <dir>] \\ \n \
108115 [--fetch-missing] [-j JOBS]\n \
@@ -209,6 +216,11 @@ fn usage_text() -> &'static str {
209216 default rebuilds/overwrites every bundle (golden/determinism workflows\n \
210217 rely on that).\n \
211218 \n \
219+ --no-deps-digest: legacy {hash}_{platform} glb names instead of the\n \
220+ canonical {hash}_{depsdigest}_{platform} (for consumers that request\n \
221+ bundles by bare content hash, or pre-v49 parity). Equivalent to\n \
222+ ABGEN_DEPS_DIGEST=0.\n \
223+ \n \
212224 --gpu: enable the GPU BC7/BC5 encode path (needs a binary built with\n \
213225 --features gpu; exits 2 otherwise).\n \
214226 \n \
@@ -279,6 +291,8 @@ fn run() -> Result<()> {
279291 let mut skip_existing = false ;
280292 let mut force = false ;
281293 let mut fetch_missing = false ;
294+ let mut no_deps_digest = false ;
295+ let mut pointer_target: Option < String > = None ;
282296 let mut i = 0 ;
283297 while i < argv. len ( ) {
284298 match argv[ i] . as_str ( ) {
@@ -363,6 +377,10 @@ fn run() -> Result<()> {
363377 i += 1 ;
364378 entity_ids_path = Some ( argv. get ( i) . cloned ( ) . unwrap_or_else ( || usage ( ) ) ) ;
365379 }
380+ "--pointer" => {
381+ i += 1 ;
382+ pointer_target = Some ( argv. get ( i) . cloned ( ) . unwrap_or_else ( || usage ( ) ) ) ;
383+ }
366384 "--worlds-url" => {
367385 i += 1 ;
368386 worlds_url_flag = Some ( argv. get ( i) . cloned ( ) . unwrap_or_else ( || usage ( ) ) ) ;
@@ -383,6 +401,9 @@ fn run() -> Result<()> {
383401 "--skip-existing" => {
384402 skip_existing = true ;
385403 }
404+ "--no-deps-digest" => {
405+ no_deps_digest = true ;
406+ }
386407 "--fetch-missing" => {
387408 fetch_missing = true ;
388409 }
@@ -423,13 +444,17 @@ fn run() -> Result<()> {
423444 eprintln ! ( "error: --cdn-layout is incompatible with --flat / --collection-urn" ) ;
424445 usage ( ) ;
425446 }
426- if cdn_layout && entity_ids_path. is_none ( ) && worlds. is_empty ( ) {
427- eprintln ! ( "error: --cdn-layout currently requires --entity-ids or --world" ) ;
447+ if cdn_layout && entity_ids_path. is_none ( ) && pointer_target . is_none ( ) && worlds. is_empty ( ) {
448+ eprintln ! ( "error: --cdn-layout currently requires --entity-ids, --pointer or --world" ) ;
428449 usage ( ) ;
429450 }
430- if fetch_missing && entity_ids_path. is_none ( ) && collection_urn. is_none ( ) {
451+ if fetch_missing
452+ && entity_ids_path. is_none ( )
453+ && pointer_target. is_none ( )
454+ && collection_urn. is_none ( )
455+ {
431456 eprintln ! (
432- "error: --fetch-missing requires --entity-ids or --collection-urn \
457+ "error: --fetch-missing requires --entity-ids, --pointer or --collection-urn \
433458 (--world always fetches)"
434459 ) ;
435460 usage ( ) ;
@@ -456,10 +481,10 @@ fn run() -> Result<()> {
456481 ) ;
457482 usage ( ) ;
458483 }
459- if !( cdn_layout && entity_ids_path. is_some ( ) ) {
484+ if !( cdn_layout && ( entity_ids_path. is_some ( ) || pointer_target . is_some ( ) ) ) {
460485 eprintln ! (
461- "error: --platform with a comma list requires --entity-ids --cdn-layout \
462- (the fused encode-once pass)"
486+ "error: --platform with a comma list requires --entity-ids/--pointer \
487+ --cdn-layout (the fused encode-once pass)"
463488 ) ;
464489 usage ( ) ;
465490 }
@@ -470,6 +495,16 @@ fn run() -> Result<()> {
470495 eprintln ! ( "error: --world conflicts with --entity-ids/--from-reference/--collection-urn" ) ;
471496 usage ( ) ;
472497 }
498+ if pointer_target. is_some ( )
499+ && ( entity_ids_path. is_some ( )
500+ || !worlds. is_empty ( )
501+ || from_ref. is_some ( )
502+ || collection_urn. is_some ( )
503+ || live_mode. is_some ( ) )
504+ {
505+ eprintln ! ( "error: --pointer conflicts with the other entity-source modes" ) ;
506+ usage ( ) ;
507+ }
473508 if cdn_layout {
474509 let valid_version = ab_version. len ( ) >= 2
475510 && ab_version. as_bytes ( ) . first ( ) == Some ( & b'v' )
@@ -501,7 +536,8 @@ fn run() -> Result<()> {
501536 v38_compat : set_v38 || ( !parity_mode && BuildOpts :: env_v38_compat ( ) ) ,
502537 v38_timestamp : BuildOpts :: env_v38_timestamp ( ) ,
503538 magenta_missing : BuildOpts :: env_magenta_missing ( ) ,
504- asset_reuse : abgen:: clihelp:: env_bool ( "ABGEN_ASSET_REUSE" , true ) ,
539+ asset_reuse : !no_deps_digest
540+ && abgen:: clihelp:: env_bool ( "ABGEN_DEPS_DIGEST" , true ) ,
505541 }
506542 }
507543 Err ( msg) => {
@@ -537,23 +573,32 @@ fn run() -> Result<()> {
537573 from_live_reference ( Path :: new ( live_ref) , & cdir, & platform, per_vintage, toggles) ?;
538574 live_sample_summary = Some ( summary) ;
539575 ( m, out_root)
540- } else if let Some ( ids_path ) = entity_ids_path {
576+ } else if entity_ids_path . is_some ( ) || pointer_target . is_some ( ) {
541577 if positional. len ( ) != 1 {
542578 usage ( ) ;
543579 }
544580 let out_root = PathBuf :: from ( & positional[ 0 ] ) ;
545581 let cdir = content_dir
546582 . or_else ( || std:: env:: var ( ABGEN_CONTENT_ROOT_ENV ) . ok ( ) )
547583 . unwrap_or_else ( || DEFAULT_CONTENT_ROOT . to_string ( ) ) ;
548- if cdn_layout {
584+ let ids : Vec < String > = if let Some ( ids_path ) = & entity_ids_path {
549585 let raw =
550- std:: fs:: read_to_string ( & ids_path) . with_context ( || format ! ( "read {ids_path}" ) ) ?;
551- let ids: Vec < String > = raw
552- . lines ( )
586+ std:: fs:: read_to_string ( ids_path) . with_context ( || format ! ( "read {ids_path}" ) ) ?;
587+ raw. lines ( )
553588 . map ( |l| l. trim ( ) )
554589 . filter ( |l| !l. is_empty ( ) && !l. starts_with ( '#' ) )
555590 . map ( |l| l. to_string ( ) )
556- . collect ( ) ;
591+ . collect ( )
592+ } else {
593+ let target = pointer_target. as_deref ( ) . unwrap ( ) ;
594+ let client = abgen:: catalyst:: CatalystClient :: from_args ( & content_server_url, None ) ;
595+ let scene = client
596+ . resolve_scene ( target)
597+ . with_context ( || format ! ( "resolve --pointer {target:?}" ) ) ?;
598+ eprintln ! ( "pointer {target:?} -> entity {}" , scene. entity_id) ;
599+ vec ! [ scene. entity_id]
600+ } ;
601+ if cdn_layout {
557602 let store = LocalContentStore :: new ( & cdir) ;
558603 if fetch_missing {
559604 fetch_ids_into_store ( & store, & content_server_url, & ids) ;
@@ -583,6 +628,23 @@ fn run() -> Result<()> {
583628 "reconcile: divergent={} rebuilt={} relinked={} errs={}" ,
584629 o. reconcile. divergent, o. reconcile. rebuilt, o. reconcile. relinked, o. reconcile. errs
585630 ) ;
631+ // Single-target convenience: also emit the manifests under the
632+ // production CDN naming (manifest/<entity>_<platform>.json).
633+ if pointer_target. is_some ( ) {
634+ let man_dir = out_root. join ( "manifest" ) ;
635+ std:: fs:: create_dir_all ( & man_dir) ?;
636+ for id in & ids {
637+ for plat in & platforms {
638+ let src = out_root. join ( id) . join ( format ! ( "{plat}.manifest.json" ) ) ;
639+ if src. is_file ( ) {
640+ let dst = man_dir. join ( format ! ( "{id}_{plat}.json" ) ) ;
641+ std:: fs:: copy ( & src, & dst) ?;
642+ eprintln ! ( "manifest: {}" , dst. display( ) ) ;
643+ eprintln ! ( "bundles: {}" , out_root. join( id) . join( plat) . display( ) ) ;
644+ }
645+ }
646+ }
647+ }
586648 let n_errs = o. errs + o. manifest_errs + o. reconcile . errs ;
587649 let total = o. built + o. skipped + o. errs ;
588650 println ! (
@@ -594,14 +656,11 @@ fn run() -> Result<()> {
594656 }
595657 return Ok ( ( ) ) ;
596658 }
597- let m = from_entity_ids (
598- & ids_path,
599- & cdir,
600- & platform,
601- cdn_layout,
602- fetch_missing. then_some ( content_server_url. as_str ( ) ) ,
603- toggles,
604- ) ?;
659+ let store = LocalContentStore :: new ( & cdir) ;
660+ if fetch_missing {
661+ fetch_ids_into_store ( & store, & content_server_url, & ids) ;
662+ }
663+ let m = manifest_from_ids ( & ids, & cdir, & platform, cdn_layout, toggles) ?;
605664 ( m, out_root)
606665 } else if !worlds. is_empty ( ) {
607666 if positional. len ( ) != 1 {
0 commit comments