-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add support for workspaces and external modules in the same repo (
#199) * feat: fix bug Signed-off-by: Kasper J. Hermansen <[email protected]> * fix: set all output to stderr from ui Signed-off-by: Kasper J. Hermansen <[email protected]> * feat: with stderr instead Signed-off-by: Kasper J. Hermansen <[email protected]> * feat: with local dir Signed-off-by: Kasper J. Hermansen <[email protected]> * feat: with go mod Signed-off-by: Kasper J. Hermansen <[email protected]> * feat: with root module Signed-off-by: Kasper J. Hermansen <[email protected]> * feat: add workspace Signed-off-by: Kasper J. Hermansen <[email protected]> * feat: add more logs (#200) Signed-off-by: Kasper J. Hermansen <[email protected]> * refactor: into different files Signed-off-by: Kasper J. Hermansen <[email protected]> * refactor: into package finder Signed-off-by: Kasper J. Hermansen <[email protected]> * feat: add shuttle Signed-off-by: Kasper J. Hermansen <[email protected]> * feat: refactor go mod Signed-off-by: Kasper J. Hermansen <[email protected]> * refactor: patch gomodule Signed-off-by: Kasper J. Hermansen <[email protected]> * feat: with split workspace patcher Signed-off-by: Kasper J. Hermansen <[email protected]> * refactor: into modfile and module Signed-off-by: Kasper J. Hermansen <[email protected]> * feat: add mod file test Signed-off-by: Kasper J. Hermansen <[email protected]> * chore: fix typo Signed-off-by: Kasper J. Hermansen <[email protected]> --------- Signed-off-by: Kasper J. Hermansen <[email protected]>
- Loading branch information
Showing
24 changed files
with
838 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(), | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
}) | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
Oops, something went wrong.