diff --git a/pkg/executors/golang/codegen/mod_file.go b/pkg/executors/golang/codegen/mod_file.go new file mode 100644 index 0000000..ba905e7 --- /dev/null +++ b/pkg/executors/golang/codegen/mod_file.go @@ -0,0 +1,68 @@ +package codegen + +import ( + "fmt" + "io/fs" + "path" + "strings" + + "golang.org/x/exp/slices" +) + +type actionsModFile struct { + info fs.FileInfo + content []string + path string + + writeFileFunc writeFileFunc +} + +func (a *actionsModFile) containsModule(moduleName string) bool { + return slices.ContainsFunc(a.content, func(s string) bool { + return strings.Contains(s, moduleName) + }) +} + +func (a *actionsModFile) replaceModulePath(rootDir string, module module) { + relativeToActionsModulePath := path.Join(strings.Repeat("../", a.segmentsTo(rootDir)), module.path) + + foundReplace := false + for i, line := range a.content { + lineTrim := strings.TrimSpace(line) + + if strings.Contains(lineTrim, fmt.Sprintf("replace %s", module.name)) { + a.content[i] = fmt.Sprintf("replace %s => %s", module.name, relativeToActionsModulePath) + foundReplace = true + break + } + + } + + if !foundReplace { + a.content = append( + a.content, + fmt.Sprintf("\nreplace %s => %s", module.name, relativeToActionsModulePath), + ) + + } +} + +func (a *actionsModFile) segmentsTo(dirPath string) int { + relativeActionsModFilePath := strings.TrimPrefix( + strings.TrimPrefix( + a.path, + dirPath, + ), + "/", + ) + + return strings.Count(relativeActionsModFilePath, "/") +} + +func (a *actionsModFile) commit() error { + return a.writeFileFunc( + a.path, + []byte(strings.Join(a.content, "\n")), + a.info.Mode(), + ) +} diff --git a/pkg/executors/golang/codegen/mod_file_test.go b/pkg/executors/golang/codegen/mod_file_test.go new file mode 100644 index 0000000..250d6d1 --- /dev/null +++ b/pkg/executors/golang/codegen/mod_file_test.go @@ -0,0 +1,247 @@ +package codegen + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestContainsModule(t *testing.T) { + t.Parallel() + + sut := actionsModFile{ + content: []string{ + "someModuleName", + "someOtherModuleName", + " someSpacedModule", + "package somePackaged", + "module someModModule", + "require someRequireModule", + }, + } + + t.Run("contains a module name", func(t *testing.T) { + actual := sut.containsModule("someModuleName") + + assert.True(t, actual) + }) + + t.Run("does not contain module", func(t *testing.T) { + actual := sut.containsModule("someNonExistingModule") + + assert.False(t, actual) + }) + + t.Run("spaced matches", func(t *testing.T) { + actual := sut.containsModule("someSpacedModule") + + assert.True(t, actual) + }) + + t.Run("packaged matches", func(t *testing.T) { + actual := sut.containsModule("somePackaged") + + assert.True(t, actual) + }) + + t.Run("modded module matches", func(t *testing.T) { + actual := sut.containsModule("someModModule") + + assert.True(t, actual) + }) + + t.Run("required module matches", func(t *testing.T) { + actual := sut.containsModule("someRequireModule") + + assert.True(t, actual) + }) + + t.Run("case sensitive doesn't match", func(t *testing.T) { + actual := sut.containsModule("SOMEMODULENAME") + + assert.False(t, actual) + }) +} + +func TestReplaceModulePath(t *testing.T) { + t.Parallel() + + createSut := func() actionsModFile { + + modFileContent := `module actions + +require ( + root_workspace v0.0.0 + subpackage v0.0.0 + othersubpackage v0.0.0 +) + +go 1.21.4 + +replace othersubpackage => ../../../../othersubpackage` + + return actionsModFile{ + path: "/some-path/some-other-path", + content: strings.Split(modFileContent, "\n"), + } + + } + + t.Run("module matches, not replaced already", func(t *testing.T) { + sut := createSut() + + expected := `module actions + +require ( + root_workspace v0.0.0 + subpackage v0.0.0 + othersubpackage v0.0.0 +) + +go 1.21.4 + +replace othersubpackage => ../../../../othersubpackage + +replace subpackage => ../subpackage` + + sut.replaceModulePath("some-other-path/newpath", module{ + name: "subpackage", + path: "subpackage", + }) + + assert.Equal(t, expected, strings.Join(sut.content, "\n")) + }) + + t.Run("module matches, not replaced already, deeper nesting", func(t *testing.T) { + sut := createSut() + + expected := `module actions + +require ( + root_workspace v0.0.0 + subpackage v0.0.0 + othersubpackage v0.0.0 +) + +go 1.21.4 + +replace othersubpackage => ../../../../othersubpackage + +replace subpackage => subpackage` + + sut.replaceModulePath("/some-path", module{ + name: "subpackage", + path: "subpackage", + }) + + assert.Equal(t, expected, strings.Join(sut.content, "\n")) + }) + + t.Run("module matches, already replaced already", func(t *testing.T) { + sut := createSut() + + expected := `module actions + +require ( + root_workspace v0.0.0 + subpackage v0.0.0 + othersubpackage v0.0.0 +) + +go 1.21.4 + +replace othersubpackage => ../othersubpackage` + + sut.replaceModulePath("some-other-path/newpath", module{ + name: "othersubpackage", + path: "othersubpackage", + }) + + assert.Equal(t, expected, strings.Join(sut.content, "\n")) + }) + + t.Run("module matches, already replaced already deeper nesting", func(t *testing.T) { + sut := createSut() + + expected := `module actions + +require ( + root_workspace v0.0.0 + subpackage v0.0.0 + othersubpackage v0.0.0 +) + +go 1.21.4 + +replace othersubpackage => othersubpackage` + + sut.replaceModulePath("/some-path", module{ + name: "othersubpackage", + path: "othersubpackage", + }) + + assert.Equal(t, expected, strings.Join(sut.content, "\n")) + }) +} + +func TestSegmentsTo(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + path string + rootDir string + expected int + }{ + { + name: "empty", + path: "", + rootDir: "", + expected: 0, + }, + { + name: "current dir", + path: "/some-dir/", + rootDir: "/some-dir/", + expected: 0, + }, + { + name: "one level", + path: "/some-dir/some-other-dir/", + rootDir: "/some-dir", + expected: 1, + }, + { + name: "2 level", + path: "/some-dir/some-other-dir/some-third-dir/", + rootDir: "/some-dir", + expected: 2, + }, + { + name: "1 level", + path: "/some-dir/some-other-dir/some-third-dir/", + rootDir: "/some-dir/some-other-dir", + expected: 1, + }, + { + name: "without trailing", + path: "/some-dir/some-third-dir", + rootDir: "/some-dir", + expected: 0, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + sut := actionsModFile{ + path: testCase.path, + } + + actual := sut.segmentsTo(testCase.rootDir) + + assert.Equal(t, testCase.expected, actual) + }) + } + +} diff --git a/pkg/executors/golang/codegen/module.go b/pkg/executors/golang/codegen/module.go new file mode 100644 index 0000000..2650905 --- /dev/null +++ b/pkg/executors/golang/codegen/module.go @@ -0,0 +1,27 @@ +package codegen + +import ( + "strings" + + "golang.org/x/exp/slices" +) + +type module struct { + name string + path string +} + +func modulesFromMap(packages map[string]string) []module { + modules := make([]module, 0, len(packages)) + for moduleName, modulePath := range packages { + modules = append(modules, module{ + name: moduleName, + path: modulePath, + }) + } + slices.SortFunc(modules, func(a, b module) int { + return strings.Compare(a.name, b.name) + }) + + return modules +} diff --git a/pkg/executors/golang/codegen/patch.go b/pkg/executors/golang/codegen/patch.go new file mode 100644 index 0000000..d4c8720 --- /dev/null +++ b/pkg/executors/golang/codegen/patch.go @@ -0,0 +1,38 @@ +package codegen + +import ( + "context" + "io/fs" + "os" +) + +type writeFileFunc = func(name string, contents []byte, permissions fs.FileMode) error + +type Patcher struct { + patchFinder *chainedPackageFinder + patcher *goModPatcher +} + +func NewPatcher() *Patcher { + return &Patcher{ + patchFinder: newChainedPatchFinder( + newWorkspaceFinder(), + newGoModuleFinder(), + newDefaultFinder(), + ), + patcher: newGoModPatcher(os.WriteFile), + } +} + +func (p *Patcher) Patch(ctx context.Context, rootDir string, shuttleLocalDir string) error { + packages, err := p.patchFinder.findPackages(ctx, rootDir) + if err != nil { + return err + } + + if err := p.patcher.patch(rootDir, shuttleLocalDir, packages); err != nil { + return err + } + + return nil +} diff --git a/pkg/executors/golang/codegen/patch_default.go b/pkg/executors/golang/codegen/patch_default.go new file mode 100644 index 0000000..b7f49ad --- /dev/null +++ b/pkg/executors/golang/codegen/patch_default.go @@ -0,0 +1,14 @@ +package codegen + +import "context" + +type defaultFinder struct{} + +func newDefaultFinder() *defaultFinder { + return &defaultFinder{} +} + +func (s *defaultFinder) Find(ctx context.Context, _ string) (packages map[string]string, ok bool, err error) { + // We return true, as this should be placed last in the chain + return make(map[string]string, 0), true, nil +} diff --git a/pkg/executors/golang/codegen/patch_finder.go b/pkg/executors/golang/codegen/patch_finder.go new file mode 100644 index 0000000..16b5317 --- /dev/null +++ b/pkg/executors/golang/codegen/patch_finder.go @@ -0,0 +1,38 @@ +package codegen + +import ( + "context" + "errors" +) + +// packageFinder exists to find whatever patches are required for a given shuttle golang action to function +type packageFinder interface { + // Find should return how many packages are required to function + Find(ctx context.Context, rootDir string) (packages map[string]string, ok bool, err error) +} + +type chainedPackageFinder struct { + finders []packageFinder +} + +func newChainedPatchFinder(finders ...packageFinder) *chainedPackageFinder { + return &chainedPackageFinder{ + finders: finders, + } +} + +// FindPackages is setup as a chain of responsibility, which means that from most significant it will attempt to find packages +// to be used. However, each finder needs to return how many packages it needs to function, as returning ok means that the finder has exclusive access to the packages +func (p *chainedPackageFinder) findPackages(ctx context.Context, rootDir string) (packages map[string]string, err error) { + for _, finder := range p.finders { + packages, ok, err := finder.Find(ctx, rootDir) + if err != nil { + return nil, err + } + if ok { + return packages, nil + } + } + + return nil, errors.New("failed to find a valid patcher") +} diff --git a/pkg/executors/golang/codegen/patch_gomodule.go b/pkg/executors/golang/codegen/patch_gomodule.go new file mode 100644 index 0000000..b312ef5 --- /dev/null +++ b/pkg/executors/golang/codegen/patch_gomodule.go @@ -0,0 +1,78 @@ +package codegen + +import ( + "context" + "errors" + "fmt" + "os" + "path" + "strings" +) + +type goModuleFinder struct{} + +func newGoModuleFinder() *goModuleFinder { + return &goModuleFinder{} +} + +func (s *goModuleFinder) Find(ctx context.Context, rootDir string) (packages map[string]string, ok bool, err error) { + contents, ok, err := s.getGoModFile(rootDir) + if err != nil { + return nil, true, err + } + if !ok { + return nil, false, nil + } + + moduleName, err := s.getModuleFromModFile(contents) + if err != nil { + return nil, true, fmt.Errorf("failed to parse go.mod in root of project: %w", err) + } + + packages = make(map[string]string, 0) + packages[moduleName] = "" + + return packages, true, nil +} + +func (g *goModuleFinder) getGoModFile(rootDir string) (contents []string, ok bool, err error) { + goMod := path.Join(rootDir, "go.mod") + if _, err := os.Stat(goMod); err != nil { + if errors.Is(err, os.ErrNotExist) { + return nil, false, nil + } + + return nil, true, err + } + + modFile, err := os.ReadFile(path.Join(rootDir, "go.mod")) + if err != nil { + return nil, true, err + } + + lines := strings.Split(string(modFile), "\n") + + if len(lines) == 0 { + return nil, true, errors.New("go mod is empty") + } + + return lines, true, nil +} + +func (g *goModuleFinder) getModuleFromModFile(contents []string) (moduleName string, err error) { + for _, line := range contents { + modFileLine := strings.TrimSpace(line) + if strings.HasPrefix(modFileLine, "module") { + sections := strings.Split(modFileLine, " ") + if len(sections) < 2 { + return "", fmt.Errorf("invalid module line: %s", modFileLine) + } + + moduleName := sections[1] + + return moduleName, nil + } + } + + return "", errors.New("failed to find a valid go.mod file") +} diff --git a/pkg/executors/golang/codegen/patch_patcher.go b/pkg/executors/golang/codegen/patch_patcher.go new file mode 100644 index 0000000..1f40b89 --- /dev/null +++ b/pkg/executors/golang/codegen/patch_patcher.go @@ -0,0 +1,55 @@ +package codegen + +import ( + "os" + "path" + "strings" +) + +type goModPatcher struct { + writeFileFunc writeFileFunc +} + +func newGoModPatcher(writeFileFunc writeFileFunc) *goModPatcher { + return &goModPatcher{writeFileFunc: writeFileFunc} +} + +func (p *goModPatcher) patch(rootDir string, shuttleLocalDir string, packages map[string]string) error { + actionsModFile, err := p.readActionsMod(shuttleLocalDir) + if err != nil { + return err + } + + for _, module := range modulesFromMap(packages) { + if !actionsModFile.containsModule(module.name) { + continue + } + + actionsModFile.replaceModulePath(rootDir, module) + } + + return actionsModFile.commit() +} + +func (g *goModPatcher) readActionsMod(shuttleLocalDir string) (*actionsModFile, error) { + path := path.Join(shuttleLocalDir, "tmp/go.mod") + + content, err := os.ReadFile(path) + if err != nil { + return nil, err + } + info, err := os.Stat(path) + if err != nil { + return nil, err + } + + lines := strings.Split(string(content), "\n") + + return &actionsModFile{ + info: info, + content: lines, + path: path, + + writeFileFunc: g.writeFileFunc, + }, nil +} diff --git a/pkg/executors/golang/codegen/patch_test.go b/pkg/executors/golang/codegen/patch_test.go new file mode 100644 index 0000000..4172ba3 --- /dev/null +++ b/pkg/executors/golang/codegen/patch_test.go @@ -0,0 +1,86 @@ +package codegen + +import ( + "context" + "io/fs" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestPatchGoMod(t *testing.T) { + t.Parallel() + + t.Run("finds root module adds to actions plan", func(t *testing.T) { + sut := NewPatcher() + sut.patcher = newGoModPatcher(func(name string, contents []byte, permissions fs.FileMode) error { + assert.Equal(t, "testdata/patch/root_module/.shuttle/actions/tmp/go.mod", name) + assert.Equal(t, `module actions + +require ( + root_module +) + +go 1.21.4 + + +replace root_module => ../../..`, string(contents)) + + return nil + }) + + err := sut.Patch(context.Background(), "testdata/patch/root_module/", "testdata/patch/root_module/.shuttle/actions") + require.NoError(t, err) + }) + + t.Run("finds root module replaces existing", func(t *testing.T) { + sut := NewPatcher() + sut.patcher = newGoModPatcher(func(name string, contents []byte, permissions fs.FileMode) error { + assert.Equal(t, "testdata/patch/replace_existing/.shuttle/actions/tmp/go.mod", name) + assert.Equal(t, `module actions + +require ( + replace_existing v0.0.0 +) + +go 1.21.4 + +replace replace_existing => ../../.. +`, string(contents)) + + return nil + }) + + err := sut.Patch(context.Background(), "testdata/patch/replace_existing/", "testdata/patch/replace_existing/.shuttle/actions") + require.NoError(t, err) + }) + + t.Run("finds root workspace adds entries", func(t *testing.T) { + sut := NewPatcher() + sut.patcher = newGoModPatcher(func(name string, contents []byte, permissions fs.FileMode) error { + assert.Equal(t, "testdata/patch/root_workspace/.shuttle/actions/tmp/go.mod", name) + assert.Equal(t, `module actions + +require ( + root_workspace v0.0.0 + subpackage v0.0.0 + othersubpackage v0.0.0 +) + +go 1.21.4 + + +replace othersubpackage => ../../../other/subpackage + +replace root_workspace => ../../.. + +replace subpackage => ../../../subpackage`, string(contents)) + + return nil + }) + + err := sut.Patch(context.Background(), "testdata/patch/root_workspace/", "testdata/patch/root_workspace/.shuttle/actions") + require.NoError(t, err) + }) +} diff --git a/pkg/executors/golang/codegen/patch_workspace.go b/pkg/executors/golang/codegen/patch_workspace.go new file mode 100644 index 0000000..bb0de16 --- /dev/null +++ b/pkg/executors/golang/codegen/patch_workspace.go @@ -0,0 +1,117 @@ +package codegen + +import ( + "context" + "errors" + "fmt" + "os" + "path" + "strings" +) + +type workspaceFinder struct{} + +func newWorkspaceFinder() *workspaceFinder { + return &workspaceFinder{} +} + +func (w *workspaceFinder) rootWorkspaceExists(rootDir string) bool { + goWork := path.Join(rootDir, "go.work") + if _, err := os.Stat(goWork); errors.Is(err, os.ErrNotExist) { + return false + } + + return true +} + +func (s *workspaceFinder) Find(ctx context.Context, rootDir string) (packages map[string]string, ok bool, err error) { + if !s.rootWorkspaceExists(rootDir) { + return nil, false, nil + } + + modules, err := s.getWorkspaceModules(rootDir) + if err != nil { + return nil, true, err + } + + packages = make(map[string]string, 0) + for _, module := range modules { + moduleName, modulePath, err := s.getWorkspaceModule(rootDir, module) + if err != nil { + return nil, true, err + } + packages[moduleName] = modulePath + } + + return packages, true, nil +} + +func (w *workspaceFinder) getWorkspaceModules(rootDir string) (modules []string, err error) { + workFile, err := os.ReadFile(path.Join(rootDir, "go.work")) + if err != nil { + return nil, err + } + + workFileContent := string(workFile) + lines := strings.Split(workFileContent, "\n") + if len(lines) == 0 { + return nil, errors.New("go work is empty") + } + + modules = make([]string, 0) + for _, line := range lines { + modFileLine := strings.Trim(strings.TrimSpace(line), "\t") + if strings.HasPrefix(modFileLine, ".") && modFileLine != "./actions" { + modules = append( + modules, + strings.TrimPrefix( + strings.TrimPrefix(modFileLine, "."), + "/", + ), + ) + } + } + + return modules, nil +} + +func (w *workspaceFinder) getWorkspaceModule(rootDir string, absoluteModulePath string) (moduleName string, modulePath string, err error) { + modFile, err := os.ReadFile(path.Join(rootDir, absoluteModulePath, "go.mod")) + if err != nil { + return "", "", fmt.Errorf("failed to find go.mod at: %s: %w", absoluteModulePath, err) + } + + modFileContent := string(modFile) + lines := strings.Split(modFileContent, "\n") + if len(lines) == 0 { + return "", "", errors.New("go mod is empty") + } + + for _, line := range lines { + modFileLine := strings.TrimSpace(line) + if strings.HasPrefix(modFileLine, "module") { + sections := strings.Split(modFileLine, " ") + if len(sections) < 2 { + return "", "", fmt.Errorf("invalid module line: %s", modFileLine) + } + + moduleName := sections[1] + modulePath = strings.TrimPrefix(absoluteModulePath, rootDir) + + return moduleName, modulePath, nil + } else if strings.HasPrefix(modFileLine, "use") && strings.Contains(modFileLine, ".") { + sections := strings.Split(modFileLine, " ") + if len(sections) == 2 { + return "", "", fmt.Errorf("invalid module line: %s", modFileLine) + } + + moduleName := sections[1] + modulePath = strings.TrimPrefix(absoluteModulePath, rootDir) + + return moduleName, modulePath, nil + + } + } + + return "", "", errors.New("failed to find a valid go.mod file") +} diff --git a/pkg/executors/golang/codegen/testdata/patch/replace_existing/.shuttle/actions/tmp/go.mod b/pkg/executors/golang/codegen/testdata/patch/replace_existing/.shuttle/actions/tmp/go.mod new file mode 100644 index 0000000..35779f4 --- /dev/null +++ b/pkg/executors/golang/codegen/testdata/patch/replace_existing/.shuttle/actions/tmp/go.mod @@ -0,0 +1,9 @@ +module actions + +require ( + replace_existing v0.0.0 +) + +go 1.21.4 + +replace replace_existing => ../bogus diff --git a/pkg/executors/golang/codegen/testdata/patch/replace_existing/go.mod b/pkg/executors/golang/codegen/testdata/patch/replace_existing/go.mod new file mode 100644 index 0000000..4dbd3f8 --- /dev/null +++ b/pkg/executors/golang/codegen/testdata/patch/replace_existing/go.mod @@ -0,0 +1,3 @@ +module replace_existing + +go 1.21.4 diff --git a/pkg/executors/golang/codegen/testdata/patch/root_module/.shuttle/actions/tmp/go.mod b/pkg/executors/golang/codegen/testdata/patch/root_module/.shuttle/actions/tmp/go.mod new file mode 100644 index 0000000..c63a4d8 --- /dev/null +++ b/pkg/executors/golang/codegen/testdata/patch/root_module/.shuttle/actions/tmp/go.mod @@ -0,0 +1,7 @@ +module actions + +require ( + root_module +) + +go 1.21.4 diff --git a/pkg/executors/golang/codegen/testdata/patch/root_module/go.mod b/pkg/executors/golang/codegen/testdata/patch/root_module/go.mod new file mode 100644 index 0000000..0db8771 --- /dev/null +++ b/pkg/executors/golang/codegen/testdata/patch/root_module/go.mod @@ -0,0 +1,3 @@ +module root_module + +go 1.21.4 diff --git a/pkg/executors/golang/codegen/testdata/patch/root_workspace/.shuttle/actions/tmp/go.mod b/pkg/executors/golang/codegen/testdata/patch/root_workspace/.shuttle/actions/tmp/go.mod new file mode 100644 index 0000000..b215f6e --- /dev/null +++ b/pkg/executors/golang/codegen/testdata/patch/root_workspace/.shuttle/actions/tmp/go.mod @@ -0,0 +1,9 @@ +module actions + +require ( + root_workspace v0.0.0 + subpackage v0.0.0 + othersubpackage v0.0.0 +) + +go 1.21.4 diff --git a/pkg/executors/golang/codegen/testdata/patch/root_workspace/go.mod b/pkg/executors/golang/codegen/testdata/patch/root_workspace/go.mod new file mode 100644 index 0000000..6f27c4c --- /dev/null +++ b/pkg/executors/golang/codegen/testdata/patch/root_workspace/go.mod @@ -0,0 +1,3 @@ +module root_workspace + +go 1.21.4 diff --git a/pkg/executors/golang/codegen/testdata/patch/root_workspace/go.work b/pkg/executors/golang/codegen/testdata/patch/root_workspace/go.work new file mode 100644 index 0000000..d9f95d8 --- /dev/null +++ b/pkg/executors/golang/codegen/testdata/patch/root_workspace/go.work @@ -0,0 +1,8 @@ +go 1.21.4 + +use ( + . + ./subpackage + ./other/subpackage + ignored +) diff --git a/pkg/executors/golang/codegen/testdata/patch/root_workspace/other/subpackage/go.mod b/pkg/executors/golang/codegen/testdata/patch/root_workspace/other/subpackage/go.mod new file mode 100644 index 0000000..0e20b67 --- /dev/null +++ b/pkg/executors/golang/codegen/testdata/patch/root_workspace/other/subpackage/go.mod @@ -0,0 +1,3 @@ +module othersubpackage + +go 1.21.4 diff --git a/pkg/executors/golang/codegen/testdata/patch/root_workspace/subpackage/go.mod b/pkg/executors/golang/codegen/testdata/patch/root_workspace/subpackage/go.mod new file mode 100644 index 0000000..d1edf00 --- /dev/null +++ b/pkg/executors/golang/codegen/testdata/patch/root_workspace/subpackage/go.mod @@ -0,0 +1,3 @@ +module subpackage + +go 1.21.4 diff --git a/pkg/executors/golang/compile/compile.go b/pkg/executors/golang/compile/compile.go index 3d38513..c8ff062 100644 --- a/pkg/executors/golang/compile/compile.go +++ b/pkg/executors/golang/compile/compile.go @@ -48,6 +48,8 @@ func Compile(ctx context.Context, ui *ui.UI, discovered *discover.Discovered) (* binaries := &Binaries{} if discovered.Local != nil { egrp.Go(func() error { + ui.Verboseln("compiling golang actions binary for: %s", discovered.Local.DirPath) + path, err := compile(ctx, ui, discovered.Local) if err != nil { return err @@ -59,6 +61,8 @@ func Compile(ctx context.Context, ui *ui.UI, discovered *discover.Discovered) (* } if discovered.Plan != nil { egrp.Go(func() error { + ui.Verboseln("compiling golang actions binary for: %s", discovered.Plan.DirPath) + path, err := compile(ctx, ui, discovered.Plan) if err != nil { return err @@ -113,14 +117,19 @@ func compile(ctx context.Context, ui *ui.UI, actions *discover.ActionsDiscovered var binarypath string + if err := codegen.NewPatcher().Patch(ctx, actions.ParentDir, shuttlelocaldir); err != nil { + return "", fmt.Errorf("failed to patch generated go.mod: %w", err) + } + if goInstalled() { + if err = codegen.ModTidy(ctx, ui, shuttlelocaldir); err != nil { + return "", fmt.Errorf("go mod tidy failed: %w", err) + } + if err = codegen.Format(ctx, ui, shuttlelocaldir); err != nil { return "", fmt.Errorf("go fmt failed: %w", err) } - if err = codegen.ModTidy(ctx, ui, shuttlelocaldir); err != nil { - return "", fmt.Errorf("go mod tidy failed: %w", err) - } binarypath, err = codegen.CompileBinary(ctx, ui, shuttlelocaldir) if err != nil { return "", fmt.Errorf("go build failed: %w", err) diff --git a/pkg/executors/golang/compile/matcher/matcher.go b/pkg/executors/golang/compile/matcher/matcher.go index 9974b0d..61a1e39 100644 --- a/pkg/executors/golang/compile/matcher/matcher.go +++ b/pkg/executors/golang/compile/matcher/matcher.go @@ -12,6 +12,7 @@ import ( "github.com/lunarway/shuttle/pkg/executors/golang/discover" "github.com/lunarway/shuttle/pkg/ui" + "golang.org/x/exp/slices" "golang.org/x/mod/sumdb/dirhash" ) @@ -66,6 +67,7 @@ func GetHash(ctx context.Context, actions *discover.ActionsDiscovered) (string, return io.NopCloser(bytes.NewReader(b)), nil } + slices.Sort(entries) hash, err := dirhash.Hash1(entries, open) if err != nil { return "", err diff --git a/pkg/executors/golang/executer/prepare.go b/pkg/executors/golang/executer/prepare.go index 7a342e3..6c0710e 100644 --- a/pkg/executors/golang/executer/prepare.go +++ b/pkg/executors/golang/executer/prepare.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "log" + "time" "github.com/lunarway/shuttle/pkg/config" "github.com/lunarway/shuttle/pkg/executors/golang/compile" @@ -17,6 +18,8 @@ func prepare( path string, c *config.ShuttleProjectContext, ) (*compile.Binaries, error) { + ui.Verboseln("preparing shuttle golang actions") + start := time.Now() log.SetFlags(log.LstdFlags | log.Lshortfile) disc, err := discover.Discover(ctx, path, c) @@ -29,5 +32,8 @@ func prepare( return nil, fmt.Errorf("failed to compile binaries: %v", err) } + elapsed := time.Since(start) + ui.Verboseln("preparing shuttle golang actions took: %d ms", elapsed.Milliseconds()) + return binaries, nil } diff --git a/pkg/executors/golang/executer/run.go b/pkg/executors/golang/executer/run.go index fdce12c..9435c16 100644 --- a/pkg/executors/golang/executer/run.go +++ b/pkg/executors/golang/executer/run.go @@ -20,6 +20,7 @@ func Run( return err } + ui.Verboseln("executing shuttle golang actions") if err := executeAction(ctx, binaries, args...); err != nil { return err } diff --git a/pkg/executors/task.go b/pkg/executors/task.go index 0f8725a..ce5aa66 100644 --- a/pkg/executors/task.go +++ b/pkg/executors/task.go @@ -26,7 +26,7 @@ func executeTask(ctx context.Context, ui *ui.UI, context ActionExecutionContext) args = append(args, value) } - err := executer.Run(ctx, ui, &context.ScriptContext.Project, "shuttle.yaml", args...) + err := executer.Run(ctx, ui, &context.ScriptContext.Project, fmt.Sprintf("%s/shuttle.yaml", context.ScriptContext.Project.ProjectPath), args...) if err != nil { return err }