1+ // Package cache provides artifact management for SIMPL+ build caching.
2+ //
3+ // The Crestron SIMPL+ compiler (SPlusCC.exe) generates output files in two locations:
4+ // 1. A shared "SPlsWork" directory adjacent to source files (for most artifacts)
5+ // 2. A .ush header file placed adjacent to the source file itself
6+ //
7+ // Multiple SIMPL+ source files in the same directory all output to the same SPlsWork
8+ // folder, making it critical to filter artifacts by source file name when caching.
9+ //
10+ // For a source file named "example.usp", the compiler generates:
11+ // - Adjacent: example.ush (header file in same directory as source)
12+ // - SPlsWork: example.dll, example.cs, example.inf
13+ // - SPlsWork: S2_example.c, S2_example.h, S2_example.elf, etc.
14+ //
15+ // This package handles selective copying/restoration of only the artifacts
16+ // belonging to a specific source file, ignoring shared libraries and other
17+ // source files' artifacts.
118package cache
219
320import (
@@ -7,14 +24,15 @@ import (
724 "path/filepath"
825)
926
10- // CopyArtifacts copies compiled outputs from source to cache
11- func CopyArtifacts (sourceDir , destDir string , outputs []string ) error {
27+ // CopyArtifacts copies compiled outputs from a base directory to cache
28+ // The outputs paths are relative to baseDir (e.g., "SPlsWork/example.dll", "example.ush")
29+ func CopyArtifacts (baseDir , destDir string , outputs []string ) error {
1230 if err := os .MkdirAll (destDir , 0o755 ); err != nil {
1331 return fmt .Errorf ("failed to create artifact directory: %w" , err )
1432 }
1533
1634 for _ , output := range outputs {
17- src := filepath .Join (sourceDir , output )
35+ src := filepath .Join (baseDir , output )
1836 dst := filepath .Join (destDir , output )
1937
2038 if err := copyFile (src , dst ); err != nil {
@@ -25,16 +43,18 @@ func CopyArtifacts(sourceDir, destDir string, outputs []string) error {
2543 return nil
2644}
2745
28- // RestoreArtifacts copies cached outputs back to the working directory
46+ // RestoreArtifacts copies cached outputs back to the base directory
47+ // The outputs paths are relative to destDir (e.g., "SPlsWork/example.dll", "example.ush")
2948func RestoreArtifacts (cacheDir , destDir string , outputs []string ) error {
30- if err := os .MkdirAll (destDir , 0o755 ); err != nil {
31- return fmt .Errorf ("failed to create output directory: %w" , err )
32- }
33-
3449 for _ , output := range outputs {
3550 src := filepath .Join (cacheDir , output )
3651 dst := filepath .Join (destDir , output )
3752
53+ // Create parent directory if needed (e.g., for SPlsWork/...)
54+ if err := os .MkdirAll (filepath .Dir (dst ), 0o755 ); err != nil {
55+ return fmt .Errorf ("failed to create output directory: %w" , err )
56+ }
57+
3858 if err := copyFile (src , dst ); err != nil {
3959 return fmt .Errorf ("failed to restore %s: %w" , output , err )
4060 }
@@ -43,33 +63,81 @@ func RestoreArtifacts(cacheDir, destDir string, outputs []string) error {
4363 return nil
4464}
4565
46- // CollectOutputs scans a directory and returns a list of compiled output files
47- func CollectOutputs (dir string ) ([]string , error ) {
66+ // CollectOutputs scans for compiled output files specific to the given source file.
67+ // It checks two locations:
68+ // 1. The source file directory for .ush header files
69+ // 2. The SPlsWork directory for other artifacts
70+ //
71+ // Returns paths relative to the source directory (e.g., "example.ush", "SPlsWork/example.dll")
72+ func CollectOutputs (sourceFile string ) ([]string , error ) {
4873 var outputs []string
4974
50- entries , err := os .ReadDir (dir )
75+ // Extract base name without extension (e.g., "example1" from "example1.usp")
76+ baseName := filepath .Base (sourceFile )
77+ baseName = baseName [:len (baseName )- len (filepath .Ext (baseName ))]
78+
79+ sourceDir := filepath .Dir (sourceFile )
80+ splsWorkDir := filepath .Join (sourceDir , "SPlsWork" )
81+
82+ // Check for .ush file adjacent to source
83+ ushFile := baseName + ".ush"
84+ ushPath := filepath .Join (sourceDir , ushFile )
85+ if _ , err := os .Stat (ushPath ); err == nil {
86+ outputs = append (outputs , ushFile )
87+ }
88+
89+ // Scan SPlsWork directory
90+ entries , err := os .ReadDir (splsWorkDir )
5191 if err != nil {
5292 if os .IsNotExist (err ) {
53- return nil , nil // No outputs yet
93+ return outputs , nil // No SPlsWork directory yet
5494 }
55- return nil , fmt .Errorf ("failed to read output directory: %w" , err )
95+ return nil , fmt .Errorf ("failed to read SPlsWork directory: %w" , err )
5696 }
5797
5898 for _ , entry := range entries {
5999 if entry .IsDir () {
60100 continue
61101 }
62102
63- // Only collect actual output files, skip metadata
64103 name := entry .Name ()
65- if name != "metadata.json" {
66- outputs = append (outputs , name )
104+
105+ // Skip metadata files
106+ if name == "metadata.json" {
107+ continue
108+ }
109+
110+ // Check if this file belongs to our source file
111+ // Match patterns: {basename}.* or S2_{basename}.*
112+ if isOutputFile (name , baseName ) {
113+ // Store with SPlsWork/ prefix for proper path handling
114+ outputs = append (outputs , filepath .Join ("SPlsWork" , name ))
67115 }
68116 }
69117
70118 return outputs , nil
71119}
72120
121+ // isOutputFile checks if a filename belongs to the given source base name
122+ func isOutputFile (filename , baseName string ) bool {
123+ fileBase := filename [:len (filename )- len (filepath .Ext (filename ))]
124+
125+ // Direct match: example1.dll, example1.cs, etc.
126+ if fileBase == baseName {
127+ return true
128+ }
129+
130+ // Target-prefixed match: S2_example1.c, S2_example1.h, etc.
131+ if len (fileBase ) > 3 && fileBase [0 ] == 'S' && fileBase [2 ] == '_' {
132+ // Extract after "S2_" prefix
133+ if fileBase [3 :] == baseName {
134+ return true
135+ }
136+ }
137+
138+ return false
139+ }
140+
73141// copyFile copies a file from src to dst
74142func copyFile (src , dst string ) error {
75143 srcFile , err := os .Open (src )
0 commit comments