Skip to content

Commit

Permalink
[builder] Support for --skip-new-go-module
Browse files Browse the repository at this point in the history
  • Loading branch information
kristina.pathak committed May 7, 2024
1 parent 7855bf2 commit fb6829d
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 14 deletions.
20 changes: 20 additions & 0 deletions cmd/builder/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,26 @@ ocb --skip-generate --skip-get-modules --config=config.yaml
```
to only execute the compilation step.

### Avoiding the use of a new go.mod file

There is an additional option that controls one aspect of the build
process, which specifically allows skipping the use of a new `go.mod`
file. When the `--skip-new-go-module` command-line flag is supplied,
the build process issues a `go get` command for each component,
relying on the Go toolchain to update the enclosing Go module
appropriately.

This command will avoid downgrading a dependency in the enclosing
module, according to
[`semver.Compare()`](https://pkg.go.dev/golang.org/x/mod/semver#Compare),
and will instead issue a log indicating that the component was not
upgraded.

This mode cannot be used in conjunction with several features that
control the generated `go.mod` file, including `replaces`, `excludes`,
and the component `path` override. For each of these features, users
are expected to modify the enclosing `go.mod` directly.

### Strict versioning checks

The builder checks the relevant `go.mod`
Expand Down
34 changes: 25 additions & 9 deletions cmd/builder/internal/builder/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,12 @@ import (

const defaultOtelColVersion = "0.100.0"

// ErrInvalidGoMod indicates an invalid gomod
var ErrInvalidGoMod = errors.New("invalid gomod specification for module")
var (
// ErrInvalidGoMod indicates an invalid gomod
ErrInvalidGoMod = errors.New("invalid gomod specification for module")
// ErrIncompatibleConfigurationValues indicates that there is configuration that cannot be combined
ErrIncompatibleConfigurationValues = errors.New("cannot combine configuration values")
)

// Config holds the builder's configuration
type Config struct {
Expand All @@ -29,6 +33,7 @@ type Config struct {
SkipGenerate bool `mapstructure:"-"`
SkipCompilation bool `mapstructure:"-"`
SkipGetModules bool `mapstructure:"-"`
SkipNewGoModule bool `mapstructure:"-"`
SkipStrictVersioning bool `mapstructure:"-"`
LDFlags string `mapstructure:"-"`
Verbose bool `mapstructure:"-"`
Expand Down Expand Up @@ -106,14 +111,15 @@ func NewDefaultConfig() Config {
func (c *Config) Validate() error {
var providersError error
if c.Providers != nil {
providersError = validateModules(*c.Providers)
providersError = c.validateModules(*c.Providers)
}
return multierr.Combine(
validateModules(c.Extensions),
validateModules(c.Receivers),
validateModules(c.Exporters),
validateModules(c.Processors),
validateModules(c.Connectors),
c.validateModules(c.Extensions),
c.validateModules(c.Receivers),
c.validateModules(c.Exporters),
c.validateModules(c.Processors),
c.validateModules(c.Connectors),
c.validateFlags(),
providersError,
)
}
Expand Down Expand Up @@ -226,11 +232,21 @@ func (c *Config) ParseModules() error {
return nil
}

func validateModules(mods []Module) error {
func (c *Config) validateFlags() error {
if c.SkipNewGoModule && (len(c.Replaces) != 0 || len(c.Excludes) != 0) {
return fmt.Errorf("%w excludes or replaces with --skip-new-go-module; please modify the enclosing go.mod file directly", ErrIncompatibleConfigurationValues)
}
return nil
}

func (c *Config) validateModules(mods []Module) error {
for _, mod := range mods {
if mod.GoMod == "" {
return fmt.Errorf("module %q: %w", mod.GoMod, ErrInvalidGoMod)
}
if mod.Path != "" && c.SkipNewGoModule {
return fmt.Errorf("%w cannot modify mod.path \"%v\" combined with --skip-new-go-module; please modify the enclosing go.mod file directly", ErrIncompatibleConfigurationValues, mod.Path)
}
}
return nil
}
Expand Down
30 changes: 28 additions & 2 deletions cmd/builder/internal/builder/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
package builder

import (
"errors"
"os"
"strings"
"testing"
Expand Down Expand Up @@ -122,10 +121,37 @@ func TestInvalidModule(t *testing.T) {
},
err: ErrInvalidGoMod,
},
{
cfg: Config{
Logger: zap.NewNop(),
SkipNewGoModule: true,
Extensions: []Module{{
GoMod: "some-module",
Path: "invalid",
}},
},
err: ErrIncompatibleConfigurationValues,
},
{
cfg: Config{
Logger: zap.NewNop(),
SkipNewGoModule: true,
Replaces: []string{"", ""},
},
err: ErrIncompatibleConfigurationValues,
},
{
cfg: Config{
Logger: zap.NewNop(),
SkipNewGoModule: true,
Excludes: []string{"", ""},
},
err: ErrIncompatibleConfigurationValues,
},
}

for _, test := range configurations {
assert.True(t, errors.Is(test.cfg.Validate(), test.err))
assert.ErrorIs(t, test.cfg.Validate(), test.err)
}
}

Expand Down
76 changes: 73 additions & 3 deletions cmd/builder/internal/builder/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,18 +85,30 @@ func Generate(cfg Config) error {
return fmt.Errorf("failed to create output path: %w", err)
}

for _, tmpl := range []*template.Template{
allTemplates := []*template.Template{
mainTemplate,
mainOthersTemplate,
mainWindowsTemplate,
componentsTemplate,
goModTemplate,
} {
}

// Add the go.mod template unless that file is skipped.
if !cfg.SkipNewGoModule {
allTemplates = append(allTemplates, goModTemplate)
}

for _, tmpl := range allTemplates {
if err := processAndWrite(cfg, tmpl, tmpl.Name(), cfg); err != nil {
return fmt.Errorf("failed to generate source file %q: %w", tmpl.Name(), err)
}
}

// when not creating a new go.mod file, update modules one-by-one in the
// enclosing go module.
if err := cfg.updateModules(); err != nil {
return err
}

cfg.Logger.Info("Sources created", zap.String("path", cfg.Distribution.OutputPath))
return nil
}
Expand Down Expand Up @@ -230,6 +242,64 @@ func (c *Config) allComponents() []Module {
*c.Providers...)...)...)...)...)
}

func (c *Config) updateModules() error {
if !c.SkipNewGoModule {
return nil
}

// Build the main service dependency
coremod, corever := c.coreModuleAndVersion()
corespec := coremod + " " + corever

if err := c.updateGoModule(corespec); err != nil {
return err
}

for _, comp := range c.allComponents() {
if err := c.updateGoModule(comp.GoMod); err != nil {
return err
}
}
return nil
}

func (c *Config) updateGoModule(modspec string) error {
// Re-parse the go.mod file on each iteration, since it can
// change each time.
modulePath, dependencyVersions, err := c.readGoModFile()
if err != nil {
return err
}

mod, ver, _ := strings.Cut(modspec, " ")
if mod == modulePath {
// this component is part of the same module, nothing to update.
return nil
}

// check for exact match
hasVer, ok := dependencyVersions[mod]
if ok && hasVer == ver {
c.Logger.Info("Component version match", zap.String("module", mod), zap.String("version", ver))
return nil
}

scomp := semver.Compare(hasVer, ver)
if scomp > 0 {
// version in enclosing module is newer, do not change
c.Logger.Info("Not upgrading component, enclosing module is newer.", zap.String("module", mod), zap.String("existing", hasVer), zap.String("config_version", ver))
return nil
}

// upgrading or changing version
updatespec := mod + "@" + ver

if _, err := runGoCommand(*c, "get", updatespec); err != nil {
return err
}
return nil
}

func (c *Config) readGoModFile() (string, map[string]string, error) {
var modPath string
stdout, err := runGoCommand(*c, "mod", "edit", "-print")
Expand Down
5 changes: 5 additions & 0 deletions cmd/builder/internal/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const (
skipGenerateFlag = "skip-generate"
skipCompilationFlag = "skip-compilation"
skipGetModulesFlag = "skip-get-modules"
skipNewGoModuleFlag = "skip-new-go-module"
skipStrictVersioningFlag = "skip-strict-versioning"
ldflagsFlag = "ldflags"
distributionNameFlag = "name"
Expand Down Expand Up @@ -84,6 +85,7 @@ configuration is provided, ocb will generate a default Collector.
cmd.Flags().BoolVar(&cfg.SkipGenerate, skipGenerateFlag, false, "Whether builder should skip generating go code (default false)")
cmd.Flags().BoolVar(&cfg.SkipCompilation, skipCompilationFlag, false, "Whether builder should only generate go code with no compile of the collector (default false)")
cmd.Flags().BoolVar(&cfg.SkipGetModules, skipGetModulesFlag, false, "Whether builder should skip updating go.mod and retrieve Go module list (default false)")
cmd.Flags().BoolVar(&cfg.SkipNewGoModule, skipNewGoModuleFlag, false, "Whether builder should skip generating a new go.mod file, use enclosing Go module instead (default false)")
cmd.Flags().BoolVar(&cfg.SkipStrictVersioning, skipStrictVersioningFlag, false, "Whether builder should skip strictly checking the calculated versions following dependency resolution")
cmd.Flags().BoolVar(&cfg.Verbose, verboseFlag, false, "Whether builder should print verbose output (default false)")
cmd.Flags().StringVar(&cfg.LDFlags, ldflagsFlag, "", `ldflags to include in the "go build" command`)
Expand Down Expand Up @@ -183,6 +185,9 @@ func applyCfgFromFile(flags *flag.FlagSet, cfgFromFile builder.Config) {
if !flags.Changed(skipGetModulesFlag) && cfgFromFile.SkipGetModules {
cfg.SkipGetModules = cfgFromFile.SkipGetModules
}
if !flags.Changed(skipNewGoModuleFlag) && cfgFromFile.SkipNewGoModule {
cfg.SkipNewGoModule = cfgFromFile.SkipNewGoModule
}
if !flags.Changed(skipStrictVersioningFlag) && cfgFromFile.SkipStrictVersioning {
cfg.SkipStrictVersioning = cfgFromFile.SkipStrictVersioning
}
Expand Down

0 comments on commit fb6829d

Please sign in to comment.