Skip to content

Commit 4fe36fc

Browse files
authored
launchr - build extra steps (prebuild script) (#42)
1 parent 82c7731 commit 4fe36fc

File tree

7 files changed

+204
-16
lines changed

7 files changed

+204
-16
lines changed

app.go

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package launchr
22

33
import (
4+
"embed"
45
"errors"
56
"fmt"
7+
"io/fs"
68
"os"
79
"path/filepath"
810
"reflect"
@@ -17,6 +19,10 @@ import (
1719
_ "github.com/launchrctl/launchr/plugins" // include default plugins
1820
)
1921

22+
var (
23+
errTplAssetsNotFound = "assets not found for requested plugin %s"
24+
)
25+
2026
// ActionsGroup is a cobra command group definition
2127
var ActionsGroup = &cobra.Group{
2228
ID: "actions",
@@ -31,17 +37,23 @@ type launchrCfg struct {
3137
}
3238

3339
type appImpl struct {
34-
rootCmd *cobra.Command
35-
streams cli.Streams
36-
workDir string
37-
cfgDir string
38-
services map[ServiceInfo]Service
39-
actionMngr action.Manager
40-
pluginMngr PluginManager
41-
config Config
42-
mFS []ManagedFS
40+
rootCmd *cobra.Command
41+
streams cli.Streams
42+
workDir string
43+
cfgDir string
44+
services map[ServiceInfo]Service
45+
actionMngr action.Manager
46+
pluginMngr PluginManager
47+
config Config
48+
mFS []ManagedFS
4349
skipActions bool // skipActions to skip loading if not requested.
4450
reqCmd string // reqCmd to search for the requested cobra command.
51+
assetsStorage embed.FS
52+
}
53+
54+
// AppOptions represents the launchr application options.
55+
type AppOptions struct {
56+
AssetsFs embed.FS
4557
}
4658

4759
// getPluginByType returns specific plugins from the app.
@@ -70,8 +82,8 @@ func getPluginByType[T Plugin](app *appImpl) []T {
7082
return res
7183
}
7284

73-
func newApp() *appImpl {
74-
return &appImpl{}
85+
func newApp(options *AppOptions) *appImpl {
86+
return &appImpl{assetsStorage: options.AssetsFs}
7587
}
7688

7789
func (app *appImpl) Name() string { return name }
@@ -115,6 +127,27 @@ func (app *appImpl) GetService(v interface{}) {
115127
panic(fmt.Sprintf("service %q does not exist", stype))
116128
}
117129

130+
func (app *appImpl) GetPluginAssets(p Plugin) fs.FS {
131+
pluginsMap := app.pluginMngr.All()
132+
var packagePath string
133+
for pi, plg := range pluginsMap {
134+
if plg == p {
135+
packagePath = pi.GetPackagePath()
136+
}
137+
}
138+
139+
if packagePath == "" {
140+
panic(errors.New("trying to get assets for unknown plugin"))
141+
}
142+
143+
subFS, err := fs.Sub(app.assetsStorage, filepath.Join("assets", packagePath))
144+
if err != nil {
145+
panic(fmt.Errorf(errTplAssetsNotFound, packagePath))
146+
}
147+
148+
return subFS
149+
}
150+
118151
// init initializes application and plugins.
119152
func (app *appImpl) init() error {
120153
var err error
@@ -285,6 +318,6 @@ func (app *appImpl) Execute() int {
285318
}
286319

287320
// Run executes launchr application.
288-
func Run() int {
289-
return newApp().Execute()
321+
func Run(options *AppOptions) int {
322+
return newApp(options).Execute()
290323
}

cmd/launchr/launchr.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@ import (
1111
)
1212

1313
func main() {
14-
os.Exit(launchr.Run())
14+
os.Exit(launchr.Run(&launchr.AppOptions{}))
1515
}

gen.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ func (app *appImpl) Generate() int {
9898

9999
// Gen generates application specific build files and returns os exit code.
100100
func Gen() int {
101-
return newApp().Generate()
101+
return newApp(&AppOptions{}).Generate()
102102
}
103103

104104
type initTplVars struct {

internal/launchr/types.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ type App interface {
2626
// GetService retrieves a service of type v and assigns it to v.
2727
// Panics if a service is not found.
2828
GetService(v interface{})
29+
30+
GetPluginAssets(p Plugin) fs.FS
2931
// RegisterFS registers a File System in launchr.
3032
// It may be a FS for action discovery, see action.DiscoveryFS.
3133
RegisterFS(fs ManagedFS)
@@ -57,6 +59,11 @@ func (p PluginInfo) String() string {
5759
return p.pkgPath + "." + p.typeName
5860
}
5961

62+
// GetPackagePath returns the package path of the PluginInfo.
63+
func (p PluginInfo) GetPackagePath() string {
64+
return p.pkgPath
65+
}
66+
6067
// InitPluginInfo sets private fields for internal usage only.
6168
func InitPluginInfo(pi *PluginInfo, p Plugin) {
6269
pi.pkgPath, pi.typeName = GetTypePkgPathName(p)

plugins/builder/builder.go

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@ import (
55
"embed"
66
"fmt"
77
"os"
8+
"path"
89
"path/filepath"
910
"regexp"
1011
"strings"
1112
"text/template"
1213

14+
"golang.org/x/mod/module"
15+
1316
"github.com/launchrctl/launchr/internal/launchr"
1417
"github.com/launchrctl/launchr/pkg/cli"
1518
"github.com/launchrctl/launchr/pkg/log"
@@ -152,6 +155,13 @@ func (b *Builder) Build(ctx context.Context) error {
152155
return err
153156
}
154157

158+
// prebuild
159+
cli.Println("Executing prebuild scripts")
160+
err = b.preBuild(ctx)
161+
if err != nil {
162+
return err
163+
}
164+
155165
// Build the main go package.
156166
cli.Println("Building %s", b.PkgName)
157167
err = b.goBuild(ctx)
@@ -210,6 +220,101 @@ func (b *Builder) goBuild(ctx context.Context) error {
210220
return nil
211221
}
212222

223+
func (b *Builder) preBuild(ctx context.Context) error {
224+
output, err := b.env.execGoList(ctx)
225+
if err != nil {
226+
return err
227+
}
228+
229+
pluginVersionMap := make(map[string]string)
230+
lines := strings.Split(output, "\n")
231+
for _, line := range lines {
232+
pv := strings.Split(line, " ")
233+
for _, p := range b.BuildOptions.Plugins {
234+
if strings.Contains(pv[0], p.Path) {
235+
pluginVersionMap[p.Path] = pv[1]
236+
continue
237+
}
238+
}
239+
}
240+
241+
assetsPath := filepath.Join(b.wd, b.env.wd, "assets")
242+
buildName := filepath.Base(b.env.wd)
243+
err = os.MkdirAll(assetsPath, 0750)
244+
if err != nil {
245+
return err
246+
}
247+
248+
for pluginName, v := range pluginVersionMap {
249+
packagePath, _ := getModulePath(pluginName, v)
250+
if _, err = os.Stat(packagePath); os.IsNotExist(err) {
251+
log.Debug("you don't have this module/version installed (%s)", packagePath)
252+
continue
253+
}
254+
255+
// check if prebuild script exists.
256+
prebuildScriptPath := filepath.Join(packagePath, "scripts", "prebuild.go")
257+
if _, err = os.Stat(prebuildScriptPath); os.IsNotExist(err) {
258+
log.Debug("prebuild script does not exist for %s, skipping", pluginName)
259+
continue
260+
}
261+
262+
tmpPath := filepath.Join(os.TempDir(), buildName, filepath.Base(pluginName))
263+
264+
// clean tmp folder if it existed before.
265+
err = os.RemoveAll(tmpPath)
266+
if err != nil {
267+
return err
268+
}
269+
270+
// prepare tmp folder for assets and force prebuild script to push data there.
271+
err = os.MkdirAll(tmpPath, 0750)
272+
if err != nil {
273+
return err
274+
}
275+
276+
log.Debug("executing prebuild script for plugin %s", pluginName)
277+
cmd := b.env.NewCommand(ctx, b.env.Go(), "run", "scripts/prebuild.go", v, tmpPath)
278+
cmd.Dir = packagePath
279+
280+
err = b.env.RunCmd(ctx, cmd)
281+
if err != nil {
282+
return err
283+
}
284+
285+
// prepare plugin assets folder.
286+
pluginAssetsPath := filepath.Join(assetsPath, pluginName)
287+
err = os.MkdirAll(pluginAssetsPath, 0750)
288+
if err != nil {
289+
return err
290+
}
291+
292+
if _, err = os.Stat(pluginAssetsPath); err == nil {
293+
err = os.Remove(pluginAssetsPath)
294+
if err != nil {
295+
return err
296+
}
297+
}
298+
299+
// move assets from tmp dir to assets folder.
300+
err = os.Rename(tmpPath, pluginAssetsPath)
301+
if err != nil {
302+
return err
303+
}
304+
}
305+
306+
// create empty .info file for embed.
307+
// prevent embed error in case of 0 assets in folder.
308+
file, err := os.Create(filepath.Clean(filepath.Join(assetsPath, ".info")))
309+
if err != nil {
310+
fmt.Println(err.Error())
311+
os.Exit(2)
312+
}
313+
defer file.Close()
314+
315+
return nil
316+
}
317+
213318
func (b *Builder) runGen(ctx context.Context) error {
214319
genArgs := []string{"generate", "./..."}
215320
cmd := b.env.NewCommand(ctx, b.env.Go(), genArgs...)
@@ -221,3 +326,22 @@ func (b *Builder) runGen(ctx context.Context) error {
221326
cmd.Env = env
222327
return b.env.RunCmd(ctx, cmd)
223328
}
329+
330+
func getModulePath(name, version string) (string, error) {
331+
cache, ok := os.LookupEnv("GOMODCACHE")
332+
if !ok {
333+
cache = path.Join(os.Getenv("GOPATH"), "pkg", "mod")
334+
}
335+
336+
escapedPath, err := module.EscapePath(name)
337+
if err != nil {
338+
return "", err
339+
}
340+
341+
escapedVersion, err := module.EscapeVersion(version)
342+
if err != nil {
343+
return "", err
344+
}
345+
346+
return path.Join(cache, escapedPath+"@"+escapedVersion), nil
347+
}

plugins/builder/environment.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package builder
44
import (
55
"bytes"
66
"context"
7+
"io"
78
"os"
89
"os/exec"
910
"path/filepath"
@@ -173,6 +174,25 @@ func (env *buildEnvironment) execGoGet(ctx context.Context, args ...string) erro
173174
return env.RunCmd(ctx, cmd)
174175
}
175176

177+
func (env *buildEnvironment) execGoList(ctx context.Context, args ...string) (string, error) {
178+
cmd := env.NewCommand(ctx, env.Go(), append([]string{"list", "-m", "all"}, args...)...)
179+
cmd.Stdout = nil
180+
181+
stdout, err := cmd.StdoutPipe()
182+
if err != nil {
183+
return "", err
184+
}
185+
186+
var outputText string
187+
go func() {
188+
outputBytes, _ := io.ReadAll(stdout)
189+
outputText = string(outputBytes)
190+
}()
191+
192+
err = env.RunCmd(ctx, cmd)
193+
return outputText, err
194+
}
195+
176196
func (env *buildEnvironment) RunCmd(ctx context.Context, cmd *exec.Cmd) error {
177197
log.Debug("Executing shell: %s", cmd)
178198
log.Debug("Shell env variables: %s", cmd.Env)

plugins/builder/tmpl/main.tmpl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package main
44

55
import (
66
"os"
7+
"embed"
78

89
"{{.CorePkg.Path}}"
910
_ "{{.PkgName}}/gen"
@@ -12,6 +13,9 @@ import (
1213
{{- end}}
1314
)
1415

16+
//go:embed assets/*
17+
var assets embed.FS
18+
1519
func main() {
16-
os.Exit(launchr.Run())
20+
os.Exit(launchr.Run(&launchr.AppOptions{AssetsFs: assets}))
1721
}

0 commit comments

Comments
 (0)