@@ -6,6 +6,7 @@ package builder // import "go.opentelemetry.io/collector/cmd/builder/internal/bu
6
6
import (
7
7
"errors"
8
8
"fmt"
9
+ "golang.org/x/mod/modfile"
9
10
"os"
10
11
"os/exec"
11
12
"path/filepath"
@@ -205,6 +206,12 @@ func (c *Config) ParseModules() error {
205
206
if err != nil {
206
207
return err
207
208
}
209
+
210
+ c .Replaces , err = parseReplaces (c .Replaces )
211
+ if err != nil {
212
+ return err
213
+ }
214
+
208
215
return nil
209
216
}
210
217
@@ -248,16 +255,11 @@ func parseModules(mods []Module, usedNames map[string]int) ([]Module, error) {
248
255
}
249
256
usedNames [originalModName ] = 1
250
257
251
- // Check if path is empty, otherwise filepath.Abs replaces it with current path ".".
252
258
if mod .Path != "" {
253
259
var err error
254
- mod .Path , err = filepath . Abs (mod .Path )
260
+ mod .Path , err = preparePath (mod .Path )
255
261
if err != nil {
256
- return mods , fmt .Errorf ("module has a relative \" path\" element, but we couldn't resolve the current working dir: %w" , err )
257
- }
258
- // Check if the path exists
259
- if _ , err := os .Stat (mod .Path ); os .IsNotExist (err ) {
260
- return mods , fmt .Errorf ("filepath does not exist: %s" , mod .Path )
262
+ return mods , err
261
263
}
262
264
}
263
265
@@ -266,3 +268,68 @@ func parseModules(mods []Module, usedNames map[string]int) ([]Module, error) {
266
268
267
269
return parsedModules , nil
268
270
}
271
+
272
+ // preparePath ensures paths (e.g. for `replace` directives) exist and are re-written to be absolute.
273
+ //
274
+ // This ensures the generated code path does not have be adjacent to the builder config YAML.
275
+ func preparePath (path string ) (string , error ) {
276
+ p , err := filepath .Abs (path )
277
+ if err != nil {
278
+ return "" , fmt .Errorf ("replace has a relative \" path\" element, but we couldn't resolve the current working dir: %w" , err )
279
+ }
280
+ // Check if the path exists
281
+ if _ , err := os .Stat (path ); os .IsNotExist (err ) {
282
+ return "" , fmt .Errorf ("filepath does not exist: %s" , path )
283
+ }
284
+ return p , nil
285
+ }
286
+
287
+ // parseReplaces rewrites `replace` directives for the generated `go.mod`.
288
+ //
289
+ // Currently, this consists of converting any relative paths in `ModulePath [ Version ] => FilePath` replacements
290
+ // to absolute paths. This is required because the `output_path` can be at an arbitrary location (such as in `/tmp`),
291
+ // so paths relative to the builder config will not necessarily be valid.
292
+ //
293
+ // From https://go.dev/ref/mod#go-mod-file-replace, there are two valid formats for a `ReplaceSpec`:
294
+ // - ModulePath [ Version ] "=>" FilePath
295
+ // - ModulePath [ Version ] "=>" ModulePath Version
296
+ //
297
+ // ModulePath => FilePath are 3 or 4 tokens long depending on the presence of Version, and there is
298
+ // always a single token (the path) after the `=>` divider token.
299
+ //
300
+ // ModulePath => ModulePath can also be 4 tokens long, but there are always two tokens after the `=>` divider
301
+ // token.
302
+ func parseReplaces (replaces []string ) ([]string , error ) {
303
+ // Unfortunately the modfile library does not fully parse these fragments, but it handles lexing the entries.
304
+ mf , err := modfile .ParseLax ("replace-go-mod" , []byte (strings .Join (replaces , "\n " )), nil )
305
+ if err != nil {
306
+ return replaces , err
307
+ }
308
+ var parsedReplaces []string
309
+ for i , stmt := range mf .Syntax .Stmt {
310
+ line , ok := stmt .(* modfile.Line )
311
+ if ! ok || len (line .Token ) < 3 || len (line .Token ) > 4 {
312
+ parsedReplaces = append (parsedReplaces , replaces [i ])
313
+ continue
314
+ }
315
+
316
+ dividerIndex := slices .Index (line .Token , "=>" )
317
+ if dividerIndex < 0 || dividerIndex != len (line .Token )- 2 {
318
+ // If the divider is not the second-to-last token, then this is a ModulePath => ModulePath replacement,
319
+ // so there is nothing that needs replacing.
320
+ parsedReplaces = append (parsedReplaces , replaces [i ])
321
+ continue
322
+ }
323
+
324
+ // This is a ModulePath => FilePath replacement, so ensure it exists & make it absolute.
325
+ p , err := preparePath (strings .Trim (line .Token [dividerIndex + 1 ], `"` ))
326
+ if err != nil {
327
+ return replaces , err
328
+ }
329
+ // It's possible that the absolute path needs to be quoted to be safe to use in the go.mod file.
330
+ line .Token [dividerIndex + 1 ] = modfile .AutoQuote (p )
331
+
332
+ parsedReplaces = append (parsedReplaces , strings .Join (line .Token , " " ))
333
+ }
334
+ return parsedReplaces , nil
335
+ }
0 commit comments