154 lines
5.2 KiB
Go
154 lines
5.2 KiB
Go
// Copyright The OpenTelemetry Authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package builder // import "go.opentelemetry.io/collector/cmd/builder/internal/builder"
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"text/template"
|
|
"time"
|
|
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
var (
|
|
// ErrGoNotFound is returned when a Go binary hasn't been found
|
|
ErrGoNotFound = errors.New("go binary not found")
|
|
)
|
|
|
|
// GenerateAndCompile will generate the source files based on the given configuration, update go mod, and will compile into a binary
|
|
func GenerateAndCompile(cfg Config) error {
|
|
if err := Generate(cfg); err != nil {
|
|
return err
|
|
}
|
|
|
|
// run go get to update go.mod and go.sum files
|
|
if err := GetModules(cfg); err != nil {
|
|
return err
|
|
}
|
|
|
|
return Compile(cfg)
|
|
}
|
|
|
|
// Generate assembles a new distribution based on the given configuration
|
|
func Generate(cfg Config) error {
|
|
// create a warning message for non-aligned builder and collector base
|
|
if cfg.Distribution.OtelColVersion != defaultOtelColVersion {
|
|
cfg.Logger.Info("You're building a distribution with non-aligned version of the builder. Compilation may fail due to API changes. Please upgrade your builder or API", zap.String("builder-version", defaultOtelColVersion))
|
|
}
|
|
// if the file does not exist, try to create it
|
|
if _, err := os.Stat(cfg.Distribution.OutputPath); os.IsNotExist(err) {
|
|
if err = os.Mkdir(cfg.Distribution.OutputPath, 0750); err != nil {
|
|
return fmt.Errorf("failed to create output path: %w", err)
|
|
}
|
|
} else if err != nil {
|
|
return fmt.Errorf("failed to create output path: %w", err)
|
|
}
|
|
|
|
for _, tmpl := range []*template.Template{
|
|
mainTemplate,
|
|
mainOthersTemplate,
|
|
mainWindowsTemplate,
|
|
componentsTemplate,
|
|
componentsTestTemplate,
|
|
goModTemplate,
|
|
} {
|
|
if err := processAndWrite(cfg, tmpl, tmpl.Name(), cfg); err != nil {
|
|
return fmt.Errorf("failed to generate source file %q: %w", tmpl.Name(), err)
|
|
}
|
|
}
|
|
|
|
cfg.Logger.Info("Sources created", zap.String("path", cfg.Distribution.OutputPath))
|
|
return nil
|
|
}
|
|
|
|
// Compile generates a binary from the sources based on the configuration
|
|
func Compile(cfg Config) error {
|
|
if cfg.SkipCompilation {
|
|
cfg.Logger.Info("Generating source codes only, the distribution will not be compiled.")
|
|
return nil
|
|
}
|
|
|
|
cfg.Logger.Info("Compiling")
|
|
|
|
var ldflags = "-s -w"
|
|
|
|
args := []string{"build", "-trimpath", "-o", cfg.Distribution.Name}
|
|
if cfg.Distribution.DebugCompilation {
|
|
cfg.Logger.Info("Debug compilation is enabled, the debug symbols will be left on the resulting binary")
|
|
ldflags = ""
|
|
args = append(args, "-gcflags=all=-N -l")
|
|
}
|
|
args = append(args, "-ldflags="+ldflags)
|
|
if cfg.Distribution.BuildTags != "" {
|
|
args = append(args, "-tags", cfg.Distribution.BuildTags)
|
|
}
|
|
// #nosec G204 -- cfg.Distribution.Go is trusted to be a safe path and the caller is assumed to have carried out necessary input validation
|
|
cmd := exec.Command(cfg.Distribution.Go, args...)
|
|
cmd.Dir = cfg.Distribution.OutputPath
|
|
if out, err := cmd.CombinedOutput(); err != nil {
|
|
return fmt.Errorf("failed to compile the OpenTelemetry Collector distribution: %w. Output:\n%s", err, out)
|
|
}
|
|
cfg.Logger.Info("Compiled", zap.String("binary", fmt.Sprintf("%s/%s", cfg.Distribution.OutputPath, cfg.Distribution.Name)))
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetModules retrieves the go modules, updating go.mod and go.sum in the process
|
|
func GetModules(cfg Config) error {
|
|
if cfg.SkipGetModules {
|
|
cfg.Logger.Info("Generating source codes only, will not update go.mod and retrieve Go modules.")
|
|
return nil
|
|
}
|
|
|
|
// #nosec G204 -- cfg.Distribution.Go is trusted to be a safe path
|
|
cmd := exec.Command(cfg.Distribution.Go, "mod", "tidy", "-compat=1.18")
|
|
cmd.Dir = cfg.Distribution.OutputPath
|
|
if out, err := cmd.CombinedOutput(); err != nil {
|
|
return fmt.Errorf("failed to update go.mod: %w. Output:\n%s", err, out)
|
|
}
|
|
|
|
cfg.Logger.Info("Getting go modules")
|
|
// basic retry if error from go mod command (in case of transient network error). This could be improved
|
|
// retry 3 times with 5 second spacing interval
|
|
retries := 3
|
|
failReason := "unknown"
|
|
for i := 1; i <= retries; i++ {
|
|
// #nosec G204
|
|
cmd := exec.Command(cfg.Distribution.Go, "mod", "download")
|
|
cmd.Dir = cfg.Distribution.OutputPath
|
|
if out, err := cmd.CombinedOutput(); err != nil {
|
|
failReason = fmt.Sprintf("%s. Output:\n%s", err, out)
|
|
cfg.Logger.Info("Failed modules download", zap.String("retry", fmt.Sprintf("%d/%d", i, retries)))
|
|
time.Sleep(5 * time.Second)
|
|
continue
|
|
}
|
|
return nil
|
|
}
|
|
return fmt.Errorf("failed to download go modules: %s", failReason)
|
|
}
|
|
|
|
func processAndWrite(cfg Config, tmpl *template.Template, outFile string, tmplParams any) error {
|
|
out, err := os.Create(filepath.Clean(filepath.Join(cfg.Distribution.OutputPath, outFile)))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return tmpl.Execute(out, tmplParams)
|
|
}
|