@@ -52,6 +52,9 @@ intended for check-in.
5252
5353The output directory is set via rendered-specs-dir in the project config, or
5454via --output-dir on the command line. If neither is set, an error is returned.
55+ Within the output directory, components are organized into letter-prefixed
56+ subdirectories based on the first character of their name (e.g., specs/c/curl,
57+ specs/v/vim).
5558
5659Unlike prepare-sources, render skips downloading source tarballs from the
5760lookaside cache — only spec files, patches, scripts, and other git-tracked
@@ -179,7 +182,7 @@ func RenderComponents(env *azldev.Env, options *RenderOptions) ([]*RenderResult,
179182 mockResultMap := batchMockProcess (env , mockProcessor , stagingDir , prepared )
180183
181184 // ── Phase 3: Parallel finishing ──
182- parallelFinish (env , prepared , mockResultMap , results , stagingDir , options . OutputDir ,
185+ parallelFinish (env , prepared , mockResultMap , results , stagingDir ,
183186 options .Force )
184187
185188 // Clean up stale rendered directories when explicitly requested.
@@ -511,7 +514,6 @@ func parallelFinish(
511514 mockResultMap map [string ]* sources.ComponentMockResult ,
512515 results []* RenderResult ,
513516 stagingDir string ,
514- outputDir string ,
515517 allowOverwrite bool ,
516518) {
517519 if len (prepared ) == 0 {
@@ -540,7 +542,7 @@ func parallelFinish(
540542 go func (prep * preparedComponent ) {
541543 defer waitGroup .Done ()
542544
543- result := finishOneComponent (workerEnv , env , prep , mockResultMap , semaphore , stagingDir , outputDir , allowOverwrite )
545+ result := finishOneComponent (workerEnv , env , prep , mockResultMap , semaphore , stagingDir , allowOverwrite )
544546 resultsChan <- finishResult {index : prep .index , result : result }
545547 }(prep )
546548 }
@@ -568,7 +570,6 @@ func finishOneComponent(
568570 mockResultMap map [string ]* sources.ComponentMockResult ,
569571 semaphore chan struct {},
570572 stagingDir string ,
571- outputDir string ,
572573 allowOverwrite bool ,
573574) * RenderResult {
574575 componentName := prep .comp .GetName ()
@@ -593,7 +594,7 @@ func finishOneComponent(
593594 Status : renderStatusOK ,
594595 }
595596
596- err := finishComponentRender (env , prep , mockResultMap , stagingDir , outputDir , allowOverwrite )
597+ err := finishComponentRender (env , prep , mockResultMap , stagingDir , allowOverwrite )
597598 if err != nil {
598599 slog .Error ("Failed to finish rendering component" ,
599600 "component" , componentName , "error" , err )
@@ -624,7 +625,6 @@ func finishComponentRender(
624625 prep * preparedComponent ,
625626 mockResultMap map [string ]* sources.ComponentMockResult ,
626627 stagingDir string ,
627- baseOutputDir string ,
628628 allowOverwrite bool ,
629629) error {
630630 componentName := prep .comp .GetName ()
@@ -658,22 +658,20 @@ func finishComponentRender(
658658 }
659659
660660 // Copy rendered files to the component's output directory.
661- if copyErr := copyRenderedOutput (env , componentDir , baseOutputDir , componentName , allowOverwrite ); copyErr != nil {
661+ if copyErr := copyRenderedOutput (env , componentDir , prep . compOutputDir , allowOverwrite ); copyErr != nil {
662662 return copyErr
663663 }
664664
665665 slog .Info ("Rendered component" , "component" , componentName ,
666- "output" , filepath . Join ( baseOutputDir , componentName ) )
666+ "output" , prep . compOutputDir )
667667
668668 return nil
669669}
670670
671671// copyRenderedOutput copies the rendered files from tempDir to the component's output directory.
672672// For managed output (inside project root), existing output is removed before copying.
673673// For external output, existing directories cause an error.
674- func copyRenderedOutput (env * azldev.Env , tempDir , baseOutputDir , componentName string , allowOverwrite bool ) error {
675- componentOutputDir := filepath .Join (baseOutputDir , componentName )
676-
674+ func copyRenderedOutput (env * azldev.Env , tempDir , componentOutputDir string , allowOverwrite bool ) error {
677675 exists , existsErr := fileutils .DirExists (env .FS (), componentOutputDir )
678676 if existsErr != nil {
679677 return fmt .Errorf ("checking output directory %#q:\n %w" , componentOutputDir , existsErr )
@@ -704,7 +702,7 @@ func copyRenderedOutput(env *azldev.Env, tempDir, baseOutputDir, componentName s
704702 }
705703
706704 if copyErr := fileutils .CopyDirRecursive (env , env .FS (), tempDir , componentOutputDir , copyOptions ); copyErr != nil {
707- return fmt .Errorf ("copying rendered files for %#q:\n %w" , componentName , copyErr )
705+ return fmt .Errorf ("copying rendered files to %#q:\n %w" , componentOutputDir , copyErr )
708706 }
709707
710708 return nil
@@ -770,6 +768,8 @@ func findSpecFile(fs opctx.FS, dir, componentName string) (string, error) {
770768
771769// cleanupStaleRenders removes rendered output directories for components that
772770// no longer exist in the current configuration. Only called during full renders (-a).
771+ // The output directory uses letter-prefix subdirectories (e.g., SPECS/c/curl),
772+ // so this walks two levels: letter directories, then component directories within each.
773773func cleanupStaleRenders (fs opctx.FS , currentComponents * components.ComponentSet , outputDir string ) error {
774774 exists , existsErr := fileutils .Exists (fs , outputDir )
775775 if existsErr != nil {
@@ -780,7 +780,7 @@ func cleanupStaleRenders(fs opctx.FS, currentComponents *components.ComponentSet
780780 return nil
781781 }
782782
783- entries , err := fileutils .ReadDir (fs , outputDir )
783+ letterEntries , err := fileutils .ReadDir (fs , outputDir )
784784 if err != nil {
785785 return fmt .Errorf ("reading output directory %#q:\n %w" , outputDir , err )
786786 }
@@ -791,22 +791,34 @@ func cleanupStaleRenders(fs opctx.FS, currentComponents *components.ComponentSet
791791 currentNames [comp .GetName ()] = true
792792 }
793793
794- for _ , entry := range entries {
795- // Skip non-directories and known non-component files.
796- if ! entry .IsDir () {
794+ for _ , letterEntry := range letterEntries {
795+ if ! letterEntry .IsDir () {
797796 continue
798797 }
799798
800- if currentNames [entry .Name ()] {
801- continue
799+ letterDir := filepath .Join (outputDir , letterEntry .Name ())
800+
801+ compEntries , readErr := fileutils .ReadDir (fs , letterDir )
802+ if readErr != nil {
803+ return fmt .Errorf ("reading letter directory %#q:\n %w" , letterDir , readErr )
802804 }
803805
804- stalePath := filepath .Join (outputDir , entry .Name ())
806+ for _ , compEntry := range compEntries {
807+ if ! compEntry .IsDir () {
808+ continue
809+ }
810+
811+ if currentNames [compEntry .Name ()] {
812+ continue
813+ }
805814
806- slog . Info ( "Removing stale rendered output" , "directory" , stalePath )
815+ stalePath := filepath . Join ( letterDir , compEntry . Name () )
807816
808- if removeErr := fs .RemoveAll (stalePath ); removeErr != nil {
809- return fmt .Errorf ("removing stale directory %#q:\n %w" , stalePath , removeErr )
817+ slog .Info ("Removing stale rendered output" , "directory" , stalePath )
818+
819+ if removeErr := fs .RemoveAll (stalePath ); removeErr != nil {
820+ return fmt .Errorf ("removing stale directory %#q:\n %w" , stalePath , removeErr )
821+ }
810822 }
811823 }
812824
0 commit comments