@@ -38,12 +38,14 @@ import (
38
38
39
39
// buildDependency represents a build dependency like a tool used to build or develop the project.
40
40
type buildDependency struct {
41
- // The path of the binary executable.
41
+ // BinaryExecPath is the path of the binary executable.
42
42
BinaryExecPath string
43
- // The name of the binary.
43
+ // BinaryName is the name of the binary.
44
44
BinaryName string
45
- // The name of the package.
46
- PackageName string
45
+ // ModuleName is the name of the module.
46
+ ModuleName string
47
+ // ModuleVersion is the version of the module including prefixes like "v" if any.
48
+ ModuleVersion string
47
49
}
48
50
49
51
const (
@@ -83,14 +85,33 @@ var (
83
85
// See https://github.com/mitchellh/gox for more details.
84
86
crossCompileTool = & buildDependency {
85
87
BinaryName : "gox" ,
86
- PackageName :
"github.com/mitchellh/[email protected] " ,
88
+ ModuleName : "github.com/mitchellh/gox" ,
89
+ ModuleVersion : "v1.0.1" ,
90
+ }
91
+
92
+ // devToolManager is the tool to install and run all used project tools and applications with Go's module mode.
93
+ // This is necessary because the Go toolchain currently doesn't support the handling of local or global project tool
94
+ // dependencies in module mode without "polluting" the project's Go module file (go.mod).
95
+ //
96
+ // See the FAQ/documentations of "gobin" as well as issue references for more details about the tool and its purpose:
97
+ // https://github.com/myitcv/gobin/wiki/FAQ
98
+ //
99
+ // For more details about the status of proposed official Go toolchain solutions and workarounds see the following
100
+ // references:
101
+ // - https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module
102
+ // - https://github.com/golang/go/issues/27653
103
+ // - https://github.com/golang/go/issues/25922
104
+ devToolManager = & buildDependency {
105
+ BinaryName : "gobin" ,
106
+ ModuleName : "github.com/myitcv/gobin" ,
107
+ ModuleVersion : "v0.0.13" ,
87
108
}
88
109
89
110
// The tool used to format all Go source files.
90
111
// See https://godoc.org/golang.org/x/tools/cmd/goimports for more details.
91
112
formatTool = & buildDependency {
92
- PackageName : "golang.org/x/tools/cmd/goimports" ,
93
113
BinaryName : "goimports" ,
114
+ ModuleName : "golang.org/x/tools/cmd/goimports" ,
94
115
}
95
116
96
117
// Arguments for the `-gcflags` flag to pass on each `go tool compile` invocation.
@@ -110,8 +131,9 @@ var (
110
131
// This is the same tool used by the https://golangci.com service that is also integrated in snowsaw's CI/CD pipeline.
111
132
// See https://github.com/golangci/golangci-lint for more details.
112
133
lintTool = & buildDependency {
113
- PackageName :
"github.com/golangci/golangci-lint/cmd/[email protected] " ,
114
134
BinaryName : "golangci-lint" ,
135
+ ModuleName : "github.com/golangci/golangci-lint/cmd/golangci-lint" ,
136
+ ModuleVersion : "v1.19.1" ,
115
137
}
116
138
117
139
// The output directory for reports like test coverage.
@@ -149,9 +171,14 @@ func init() {
149
171
goPath = value
150
172
}
151
173
174
+ // Bootstrap bootstraps the local development environment by installing the required tools and build dependencies.
175
+ func Bootstrap () {
176
+ mg .SerialDeps (bootstrap )
177
+ }
178
+
152
179
// Build compiles the project in development mode for the current OS and architecture type.
153
180
func Build () {
154
- mg .SerialDeps (Clean , compile )
181
+ mg .SerialDeps (clean , compile )
155
182
}
156
183
157
184
// Clean removes previous development and distribution builds from the project root.
@@ -164,42 +191,42 @@ func Clean() {
164
191
// version information via LDFLAGS.
165
192
// Run `strings <PATH_TO_BINARY> | grep "$PWD"` to verify that all paths have been successfully stripped.
166
193
func Dist () {
167
- mg .SerialDeps (Clean , validateBuildDependencies , compileProd )
194
+ mg .SerialDeps (validateDevTools , clean , compileProd )
168
195
}
169
196
170
197
// DistCrossPlatform builds the project in production mode for cross-platform distribution.
171
198
// This includes all steps from the current platform distribution/production task `Dist`,
172
199
// but instead builds for all configured OS/architecture types.
173
200
func DistCrossPlatform () {
174
- mg .SerialDeps (Clean , validateBuildDependencies , compileProdCross )
201
+ mg .SerialDeps (validateDevTools , clean , compileProdCross )
175
202
}
176
203
177
204
// DistCrossPlatformOpt builds the project in production mode for cross-platform distribution with optimizations.
178
205
// This includes all steps from the cross-platform distribution task `DistCrossPlatform` and additionally removes all
179
206
// debug metadata to shrink the memory overhead and file size as well as reducing the chance for possible security
180
207
// related problems due to enabled development features and leaked debug information.
181
208
func DistCrossPlatformOpt () {
182
- mg .SerialDeps (Clean , validateBuildDependencies , compileProdCrossOpt )
209
+ mg .SerialDeps (validateDevTools , clean , compileProdCrossOpt )
183
210
}
184
211
185
212
// DistOpt builds the project in production mode with optimizations like minification and debug symbol stripping.
186
213
// This includes all steps from the production build task `Dist` and additionally removes all debug metadata to shrink
187
214
// the memory overhead and file size as well as reducing the chance for possible security related problems due to
188
215
// enabled development features and leaked debug information.
189
216
func DistOpt () {
190
- mg .SerialDeps (Clean , validateBuildDependencies , compileProdOpt )
217
+ mg .SerialDeps (validateDevTools , clean , compileProdOpt )
191
218
}
192
219
193
220
// Format searches all project Go source files and formats them according to the Go code styleguide.
194
221
func Format () {
195
- mg .SerialDeps (validateBuildDependencies , runGoImports )
222
+ mg .SerialDeps (validateDevTools , runGoImports )
196
223
}
197
224
198
225
// Lint runs all linters configured and executed through `golangci-lint`.
199
226
// See the `.golangci.yml` configuration file and official GolangCI documentations at https://golangci.com
200
227
// and https://github.com/golangci/golangci-lint for more details.
201
228
func Lint () {
202
- mg .SerialDeps (validateBuildDependencies , runGolangCILint )
229
+ mg .SerialDeps (validateDevTools , runGolangCILint )
203
230
}
204
231
205
232
// Test runs all unit tests with enabled race detection.
@@ -209,7 +236,7 @@ func Test() {
209
236
210
237
// TestCover runs all unit tests with with coverage reports and enabled race detection.
211
238
func TestCover () {
212
- mg .SerialDeps (Clean )
239
+ mg .SerialDeps (clean )
213
240
// Ensure the required directory structure exists, `go test` doesn't create it automatically.
214
241
createDirectoryStructure (reportsDir )
215
242
testCoverageProfileFlag = fmt .Sprintf ("-coverprofile=%s" , filepath .Join (reportsDir , testCoverageOutputFileName ))
@@ -221,6 +248,55 @@ func TestIntegration() {
221
248
mg .SerialDeps (integrationTests )
222
249
}
223
250
251
+ func bootstrap () {
252
+ prt .Infof ("Bootstrapping development tool/dependency manager %s" ,
253
+ color .CyanString ("%s@%s" , devToolManager .ModuleName , devToolManager .ModuleVersion ))
254
+ cmdInstallGobin := exec .Command (goExec , "get" , "-u" ,
255
+ fmt .Sprintf ("%s@%s" , devToolManager .ModuleName , devToolManager .ModuleVersion ))
256
+ // Run the installation outside of the project root directory to prevent the pollution of the project's Go module
257
+ // file.
258
+ // This is a necessary workaround until the Go toolchain is able to install packages globally without
259
+ // updating the module file when the "go get" command is run from within the project root directory.
260
+ // See https://github.com/golang/go/issues/30515 for more details or more details and proposed solutions
261
+ // that might be added to Go's build tools in future versions.
262
+ cmdInstallGobin .Dir = os .TempDir ()
263
+ cmdInstallGobin .Env = os .Environ ()
264
+ // Explicitly enable "module" mode when installing the dev tool manager to allow to use pinned module version.
265
+ cmdInstallGobin .Env = append (cmdInstallGobin .Env , "GO111MODULE=on" )
266
+ if gobinInstallErr := cmdInstallGobin .Run (); gobinInstallErr != nil {
267
+ prt .Errorf ("Failed to install required development tool/dependency manager %s:\n %s" ,
268
+ color .CyanString ("%s@%s" , devToolManager .ModuleName , devToolManager .ModuleVersion ),
269
+ color .RedString ("%s" , gobinInstallErr ))
270
+ os .Exit (1 )
271
+ }
272
+
273
+ prt .Infof ("Bootstrapping required development tools/dependencies:" )
274
+ for _ , bd := range []* buildDependency {crossCompileTool , formatTool , lintTool } {
275
+ modulePath := bd .ModuleName
276
+ // If the non-module dependency is not installed yet, install it normally into the $GOBIN path,...
277
+ if bd .ModuleVersion == "" {
278
+ fmt .Println (color .CyanString (" %s" , modulePath ))
279
+ if installErr := sh .Run (devToolManager .BinaryName , "-u" , modulePath ); installErr != nil {
280
+ prt .Errorf ("Failed to install required development tool/dependency %s:\n %s" ,
281
+ color .CyanString (modulePath ), color .RedString ("%s" , installErr ))
282
+ os .Exit (1 )
283
+ }
284
+ continue
285
+ }
286
+
287
+ // ...otherwise install into "gobin" binary cache.
288
+ modulePath = fmt .Sprintf ("%s@%s" , bd .ModuleName , bd .ModuleVersion )
289
+ fmt .Println (color .CyanString (" %s" , modulePath ))
290
+ if installErr := sh .Run (devToolManager .BinaryName , "-u" , modulePath ); installErr != nil {
291
+ prt .Errorf ("Failed to install required development tool/dependency %s:\n %s" ,
292
+ color .CyanString (modulePath ), color .RedString ("%s" , installErr ))
293
+ os .Exit (1 )
294
+ }
295
+ }
296
+
297
+ prt .Successf ("Successfully bootstrapped required development tools/dependencies" )
298
+ }
299
+
224
300
func clean () {
225
301
if err := os .RemoveAll (buildDir ); err != nil {
226
302
prt .Errorf ("Failed to clean up project directory: %v" , err )
@@ -326,17 +402,17 @@ func getEnvFlags() map[string]string {
326
402
"Injecting %s:\n " +
327
403
" Build Date: %s\n " +
328
404
" Version: %s" ,
329
- color .CyanString ("LDFLAGS" ), color .CyanString (buildDate ), color .CyanString (strings .Join (version , "-" )))
405
+ color .BlueString ("LDFLAGS" ), color .CyanString (buildDate ), color .CyanString (strings .Join (version , "-" )))
330
406
331
407
prt .Infof (
332
408
"Injecting %s:\n " +
333
409
" -trimpath: %s" ,
334
- color .CyanString ("ASMFLAGS" ), color .CyanString (pwd ))
410
+ color .BlueString ("ASMFLAGS" ), color .CyanString (pwd ))
335
411
336
412
prt .Infof (
337
413
"Injecting %s:\n " +
338
414
" -trimpath: %s" ,
339
- color .CyanString ("GCFLAGS" ), color .CyanString (pwd ))
415
+ color .BlueString ("GCFLAGS" ), color .CyanString (pwd ))
340
416
341
417
return map [string ]string {
342
418
"BUILD_DATE_TIME" : buildDate ,
@@ -345,6 +421,15 @@ func getEnvFlags() map[string]string {
345
421
"VERSION" : strings .Join (version , "-" )}
346
422
}
347
423
424
+ // getExecutablePath returns the path to the executable for the given package/module.
425
+ // When the "resolveWithGobin" parameter is set to true, the path will be resolved from the "gobin" binary cache.
426
+ func getExecutablePath (name string , resolveWithGobin bool ) (string , error ) {
427
+ if resolveWithGobin {
428
+ return sh .Output (devToolManager .BinaryName , "-p" , "-nonet" , name )
429
+ }
430
+ return exec .LookPath (name )
431
+ }
432
+
348
433
// prepareBuildTags reads custom build tags defined by the user through the `SNOWSAW_BUILD_TAGS` environment
349
434
// variable and appends them together with all additionally passed tags to the global `tags` slice.
350
435
// Returns `true` if custom build tags have been loaded, `false` otherwise.
@@ -461,50 +546,37 @@ func runGox(envFlags map[string]string, buildFlags ...string) {
461
546
prt .Successf ("Cross compilation completed successfully with output to %s directory" , color .GreenString (buildDir ))
462
547
}
463
548
464
- // validateBuildDependencies checks if all required build dependencies are installed, the binaries are available in
465
- // PATH and will try to install them if not passing the checks.
466
- func validateBuildDependencies () {
549
+ // validateDevTools validates that all required development tool/dependency executables are bootstrapped and
550
+ // available in PATH or "gobin" binary cache.
551
+ func validateDevTools () {
552
+ prt .Infof ("Verifying development tools/dependencies" )
553
+ handleError := func (name string , err error ) {
554
+ prt .Errorf ("Failed do determine development tool/dependency %s:\n %s" ,
555
+ color .CyanString (name ), color .RedString (" %s" , err ))
556
+ prt .Warnf ("Run the %s task to install all required tools/dependencies!" , color .YellowString ("bootstrap" ))
557
+ os .Exit (1 )
558
+ }
559
+
560
+ gobinPath , checkGobinPathErr := getExecutablePath (devToolManager .BinaryName , false )
561
+ if checkGobinPathErr != nil {
562
+ handleError (fmt .Sprintf ("%s@%s" , devToolManager .ModuleName , devToolManager .ModuleVersion ), checkGobinPathErr )
563
+ }
564
+ devToolManager .BinaryExecPath = gobinPath
565
+
467
566
for _ , bd := range []* buildDependency {crossCompileTool , formatTool , lintTool } {
468
- binPath , err := exec . LookPath ( bd . BinaryName )
469
- if err == nil {
470
- bd . BinaryExecPath = binPath
471
- prt . Infof ( "Required build dependency %s already installed: %s" ,
472
- color . CyanString ( bd . PackageName ),
473
- color . BlueString ( bd .BinaryExecPath ))
567
+ if bd . ModuleVersion == "" {
568
+ p , e := getExecutablePath ( bd . BinaryName , false )
569
+ if e != nil {
570
+ handleError ( bd . ModuleName , e )
571
+ }
572
+ bd .BinaryExecPath = p
474
573
continue
475
574
}
476
575
477
- prt .Infof ("Installing required build dependency: %s" , color .CyanString (bd .PackageName ))
478
- c := exec .Command (goExec , "get" , "-u" , bd .PackageName )
479
- // Run installations outside of the project root directory to prevent the pollution of the project's Go module
480
- // file.
481
- // This is a necessary workaround until the Go toolchain is able to install packages globally without
482
- // updating the module file when the "go get" command is run from within the project root directory.
483
- // See https://github.com/golang/go/issues/30515 for more details or more details and proposed solutions
484
- // that might be added to Go's build tools in future versions.
485
- c .Dir = os .TempDir ()
486
- c .Env = os .Environ ()
487
- // Explicitly enable "module" mode to install development dependencies to allow to use pinned module versions.
488
- env := map [string ]string {"GO111MODULE" : "on" }
489
- for k , v := range env {
490
- c .Env = append (c .Env , k + "=" + v )
491
- }
492
- if err = c .Run (); err != nil {
493
- prt .Errorf ("Failed to install required build dependency %s: %v" , color .CyanString (bd .PackageName ), err )
494
- prt .Warnf ("Please install manually: %s" , color .CyanString ("go get -u %s" , bd .PackageName ))
495
- os .Exit (1 )
496
- }
497
-
498
- binPath , err = exec .LookPath (bd .BinaryName )
499
- if err != nil {
500
- bd .BinaryExecPath = binPath
501
- prt .Errorf ("Failed to find executable path of required build dependency %s after installation: %v" ,
502
- color .CyanString (bd .PackageName ), err )
503
- os .Exit (1 )
576
+ p , e := getExecutablePath (fmt .Sprintf ("%s@%s" , bd .ModuleName , bd .ModuleVersion ), true )
577
+ if e != nil {
578
+ handleError (fmt .Sprintf ("%s@%s" , bd .ModuleName , bd .ModuleVersion ), e )
504
579
}
505
- bd .BinaryExecPath = binPath
506
- prt .Infof ("Using executable %s of installed build dependency %s" ,
507
- color .CyanString (bd .BinaryExecPath ),
508
- color .BlueString (bd .PackageName ))
580
+ bd .BinaryExecPath = p
509
581
}
510
582
}
0 commit comments