Skip to content

Commit 6fb2ca4

Browse files
committed
Fix build timeout. Improve build versioning.
1 parent 7e2ad61 commit 6fb2ca4

File tree

6 files changed

+191
-101
lines changed

6 files changed

+191
-101
lines changed

pkg/plugins/builder/builder.go

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ package builder
33
import (
44
"context"
55
"embed"
6+
"fmt"
67
"log"
78
"os"
89
"path/filepath"
910
"runtime"
1011
"text/template"
11-
"time"
1212

1313
"github.com/launchrctl/launchr"
1414
)
@@ -29,6 +29,11 @@ type UsePluginInfo struct {
2929
}
3030

3131
func (p UsePluginInfo) String() string {
32+
return fmt.Sprintf("%s %s", p.Package, p.Version)
33+
}
34+
35+
// GoGetString provides a package path for a go get.
36+
func (p UsePluginInfo) GoGetString() string {
3237
dep := p.Package
3338
if p.Version != "" {
3439
dep += "@" + p.Version
@@ -39,6 +44,7 @@ func (p UsePluginInfo) String() string {
3944
// BuildOptions stores launchr build parameters.
4045
type BuildOptions struct {
4146
LaunchrVersion *launchr.AppVersion
47+
CorePkg UsePluginInfo
4248
PkgName string
4349
ModReplace map[string]string
4450
Plugins []UsePluginInfo
@@ -59,7 +65,7 @@ var tmplView = template.Must(template.ParseFS(embedTmplFs, "tmpl/*.tmpl"))
5965
type buildVars struct {
6066
PkgName string
6167
LaunchrVersion *launchr.AppVersion
62-
LaunchrPkg string
68+
CorePkg UsePluginInfo
6369
BuildVersion *launchr.AppVersion
6470
Plugins []UsePluginInfo
6571
Cwd string
@@ -80,10 +86,6 @@ func NewBuilder(opts *BuildOptions) (*Builder, error) {
8086
// Build prepares build environment, generates go files and build the binary.
8187
func (b *Builder) Build(ctx context.Context) error {
8288
log.Printf("[INFO] Start building")
83-
// Execute build.
84-
ctx, cancel := context.WithTimeout(ctx, time.Second*60)
85-
defer cancel()
86-
8789
// Prepare build environment dir and go executable.
8890
var err error
8991
b.env, err = newBuildEnvironment()
@@ -99,13 +101,25 @@ func (b *Builder) Build(ctx context.Context) error {
99101
}()
100102
log.Printf("[INFO] Temporary folder: %s", b.env.wd)
101103

104+
// Write files to dir and generate go mod.
105+
log.Printf("[INFO] Creating project files and fetching dependencies")
106+
err = b.env.CreateModFile(ctx, b.BuildOptions)
107+
if err != nil {
108+
return err
109+
}
110+
102111
// Generate app version info.
103112
buildVer := b.getBuildVersion(b.LaunchrVersion)
113+
for _, p := range b.Plugins {
114+
if p.Version == "" {
115+
p.Version = b.env.getPkgVersion(p.Package)
116+
}
117+
}
104118

105119
// Generate project files.
106120
mainVars := buildVars{
107121
LaunchrVersion: b.LaunchrVersion,
108-
LaunchrPkg: launchrPkg,
122+
CorePkg: b.CorePkg,
109123
PkgName: b.PkgName,
110124
BuildVersion: buildVer,
111125
Plugins: b.Plugins,
@@ -117,9 +131,7 @@ func (b *Builder) Build(ctx context.Context) error {
117131
{"genpkg.tmpl", nil, "gen/pkg.go"},
118132
}
119133

120-
// Write files to dir and generate go mod.
121-
log.Printf("[INFO] Creating project files and fetching dependencies")
122-
err = b.env.CreateProject(ctx, files, b.BuildOptions)
134+
err = b.env.CreateSourceFiles(ctx, files)
123135
if err != nil {
124136
return err
125137
}
@@ -188,7 +200,9 @@ func (b *Builder) goBuild(ctx context.Context) error {
188200
func (b *Builder) getBuildVersion(version *launchr.AppVersion) *launchr.AppVersion {
189201
bv := *version
190202
bv.Name = b.PkgName
191-
// @todo get version from the fetched go.mod module
203+
204+
// Get version from the fetched go.mod module
205+
bv.Version = b.env.getPkgVersion(b.CorePkg.Package)
192206

193207
bv.OS = os.Getenv("GOOS")
194208
bv.Arch = os.Getenv("GOARCH")

pkg/plugins/builder/environment.go

Lines changed: 80 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import (
1010
"path/filepath"
1111
"strings"
1212
"time"
13+
14+
"golang.org/x/mod/modfile"
1315
)
1416

1517
type envVars []string
@@ -39,8 +41,9 @@ func (a *envVars) Set(k string, v string) {
3941
}
4042

4143
type buildEnvironment struct {
42-
wd string
43-
env envVars
44+
wd string
45+
env envVars
46+
gomod *modfile.File
4447
}
4548

4649
func newBuildEnvironment() (*buildEnvironment, error) {
@@ -52,30 +55,8 @@ func newBuildEnvironment() (*buildEnvironment, error) {
5255
return &buildEnvironment{wd: tmpDir, env: envFromOs()}, nil
5356
}
5457

55-
func (env *buildEnvironment) CreateProject(ctx context.Context, files []genGoFile, opts *BuildOptions) error {
56-
// Generate project files.
57-
var buf bytes.Buffer
58+
func (env *buildEnvironment) CreateModFile(ctx context.Context, opts *BuildOptions) error {
5859
var err error
59-
for _, f := range files {
60-
buf.Reset()
61-
// Render template.
62-
err = tmplView.ExecuteTemplate(&buf, f.TmplName, f.Vars)
63-
if err != nil {
64-
return err
65-
}
66-
// Create target file with directories recursively.
67-
target := filepath.Join(env.wd, f.Filename)
68-
dir := filepath.Dir(target)
69-
err = os.MkdirAll(dir, 0700)
70-
if err != nil {
71-
return err
72-
}
73-
err = os.WriteFile(target, buf.Bytes(), 0600)
74-
if err != nil {
75-
return err
76-
}
77-
}
78-
7960
// Create go.mod.
8061
err = env.execGoMod(ctx, "init", opts.PkgName)
8162
if err != nil {
@@ -91,6 +72,11 @@ func (env *buildEnvironment) CreateProject(ctx context.Context, files []genGoFil
9172
}
9273
}
9374

75+
err = env.execGoGet(ctx, opts.CorePkg.GoGetString())
76+
if err != nil {
77+
return err
78+
}
79+
9480
// Download plugins.
9581
nextPlugin:
9682
for _, p := range opts.Plugins {
@@ -100,7 +86,39 @@ nextPlugin:
10086
continue nextPlugin
10187
}
10288
}
103-
err = env.execGoGet(ctx, p.String())
89+
err = env.execGoGet(ctx, p.GoGetString())
90+
if err != nil {
91+
return err
92+
}
93+
}
94+
95+
err = env.parseGoMod()
96+
if err != nil {
97+
return err
98+
}
99+
100+
return err
101+
}
102+
103+
func (env *buildEnvironment) CreateSourceFiles(ctx context.Context, files []genGoFile) error {
104+
// Generate project files.
105+
var buf bytes.Buffer
106+
var err error
107+
for _, f := range files {
108+
buf.Reset()
109+
// Render template.
110+
err = tmplView.ExecuteTemplate(&buf, f.TmplName, f.Vars)
111+
if err != nil {
112+
return err
113+
}
114+
// Create target file with directories recursively.
115+
target := filepath.Join(env.wd, f.Filename)
116+
dir := filepath.Dir(target)
117+
err = os.MkdirAll(dir, 0700)
118+
if err != nil {
119+
return err
120+
}
121+
err = os.WriteFile(target, buf.Bytes(), 0600)
104122
if err != nil {
105123
return err
106124
}
@@ -169,3 +187,39 @@ func (env *buildEnvironment) Go() string {
169187
func (env *buildEnvironment) Close() error {
170188
return os.RemoveAll(env.wd)
171189
}
190+
191+
func (env *buildEnvironment) parseGoMod() error {
192+
var err error
193+
modFile, err := os.ReadFile(filepath.Join(env.wd, "go.mod"))
194+
if err != nil {
195+
return err
196+
}
197+
env.gomod, err = modfile.Parse("go.mod", modFile, nil)
198+
if err != nil {
199+
return err
200+
}
201+
return nil
202+
}
203+
204+
func (env *buildEnvironment) getPkgVersion(pkg string) string {
205+
var vrep *modfile.Replace
206+
for _, rep := range env.gomod.Replace {
207+
if strings.HasPrefix(pkg, rep.Old.Path) {
208+
vrep = rep
209+
break
210+
}
211+
}
212+
213+
var vreq *modfile.Require
214+
for _, req := range env.gomod.Require {
215+
if strings.HasPrefix(pkg, req.Mod.Path) {
216+
vreq = req
217+
break
218+
}
219+
}
220+
v := vreq.Mod.Version
221+
if vrep != nil && vrep.New.Version != "" {
222+
v = vrep.New.Version
223+
}
224+
return v
225+
}

pkg/plugins/builder/plugin.go

Lines changed: 82 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
package builder
33

44
import (
5+
"context"
6+
"fmt"
7+
"strings"
8+
"time"
9+
510
"github.com/spf13/cobra"
611

712
"github.com/launchrctl/launchr"
@@ -36,6 +41,7 @@ func (p *Plugin) CobraAddCommands(rootCmd *cobra.Command) error {
3641
var (
3742
name string
3843
out string
44+
timeout int
3945
plugins []string
4046
replace []string
4147
debug bool
@@ -46,14 +52,88 @@ func (p *Plugin) CobraAddCommands(rootCmd *cobra.Command) error {
4652
RunE: func(cmd *cobra.Command, args []string) error {
4753
// Don't show usage help on a runtime error.
4854
cmd.SilenceUsage = true
49-
return Execute(name, out, plugins, replace, launchr.GetVersion(), debug)
55+
ctx := cmd.Context()
56+
if timeout != 0 {
57+
// Execute build.
58+
var cancel context.CancelFunc
59+
ctx, cancel = context.WithTimeout(ctx, time.Second*time.Duration(timeout))
60+
defer cancel()
61+
}
62+
allplugs, err := parsePlugins(plugins)
63+
if err != nil {
64+
return err
65+
}
66+
allrepl, err := parseReplace(replace)
67+
if err != nil {
68+
return err
69+
}
70+
71+
if len(out) == 0 && len(name) > 0 {
72+
out = "./" + name
73+
}
74+
75+
opts := &BuildOptions{
76+
LaunchrVersion: launchr.GetVersion(),
77+
CorePkg: UsePluginInfo{Package: launchrPkg},
78+
PkgName: name,
79+
ModReplace: allrepl,
80+
Plugins: allplugs,
81+
BuildOutput: out,
82+
Debug: debug,
83+
}
84+
85+
return Execute(ctx, opts)
5086
},
5187
}
5288
buildCmd.Flags().StringVarP(&name, "name", "n", "launchr", `Result application name`)
53-
buildCmd.Flags().StringVarP(&out, "output", "o", "", `Build output file, by default application name is used"`)
89+
buildCmd.Flags().StringVarP(&out, "output", "o", "", `Build output file, by default application name is used`)
90+
buildCmd.Flags().IntVarP(&timeout, "timeout", "t", 120, `Build timeout in seconds`)
5491
buildCmd.Flags().StringSliceVarP(&plugins, "plugin", "p", nil, `Include PLUGIN into the build with an optional version`)
5592
buildCmd.Flags().StringSliceVarP(&replace, "replace", "r", nil, `Replace go dependency, see "go mod edit -replace"`)
5693
buildCmd.Flags().BoolVarP(&debug, "debug", "d", false, `Include debug flags into the build to support go debugging with "delve". If not specified, debugging info is trimmed`)
5794
rootCmd.AddCommand(buildCmd)
5895
return nil
5996
}
97+
98+
// Execute runs launchr and executes build of launchr.
99+
func Execute(ctx context.Context, opts *BuildOptions) error {
100+
builder, err := NewBuilder(opts)
101+
if err != nil {
102+
return err
103+
}
104+
defer builder.Close()
105+
return builder.Build(ctx)
106+
}
107+
108+
func parsePlugins(plugins []string) ([]UsePluginInfo, error) {
109+
// Collect unique plugins. Include default launchr plugins.
110+
defaultPlugins := launchrPkg + "/pkg/plugins"
111+
setplugs := map[string]UsePluginInfo{
112+
defaultPlugins: {defaultPlugins, ""},
113+
}
114+
for _, pdef := range plugins {
115+
pv := strings.SplitN(pdef, "@", 2)
116+
if len(pv) == 1 {
117+
pv = append(pv, "")
118+
}
119+
setplugs[pv[0]] = UsePluginInfo{pv[0], pv[1]}
120+
}
121+
allplugs := make([]UsePluginInfo, 0, len(setplugs))
122+
for _, p := range setplugs {
123+
allplugs = append(allplugs, p)
124+
}
125+
return allplugs, nil
126+
}
127+
128+
func parseReplace(replace []string) (map[string]string, error) {
129+
// Replace module dependencies, e.g. with local paths for development or different version.
130+
allrepl := map[string]string{}
131+
for _, rdef := range replace {
132+
oldnew := strings.SplitN(rdef, "=", 2)
133+
if len(oldnew) == 1 {
134+
return nil, fmt.Errorf("incorrect replace definition: %s", rdef)
135+
}
136+
allrepl[oldnew[0]] = oldnew[1]
137+
}
138+
return allrepl, nil
139+
}

0 commit comments

Comments
 (0)