Skip to content

Commit

Permalink
Fix linking submodule of go workspace (#27)
Browse files Browse the repository at this point in the history
  • Loading branch information
timebertt authored Apr 24, 2024
1 parent 130b4a3 commit eaaa873
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 9 deletions.
13 changes: 12 additions & 1 deletion internal/link/link.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ import (
"sort"
"strings"

"github.com/ironcore-dev/vgopath/internal/module"
"github.com/spf13/pflag"

"github.com/ironcore-dev/vgopath/internal/module"
)

type Node struct {
Expand Down Expand Up @@ -261,7 +262,17 @@ func linkNode(dir string, node Node) error {
return err
}

childNames := make(map[string]struct{}, len(node.Children))
for _, child := range node.Children {
childNames[child.Segment] = struct{}{}
}

for _, entry := range entries {
// skip linking directories of the module hierarchy, they will be handled by a dedicated call
if _, ok := childNames[entry.Name()]; ok {
continue
}

srcPath := filepath.Join(srcDir, entry.Name())
dstPath := filepath.Join(dstDir, entry.Name())
if err := os.Symlink(srcPath, dstPath); err != nil {
Expand Down
117 changes: 109 additions & 8 deletions internal/link/link_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,52 +8,64 @@ import (
"go/build"
"os"
"path/filepath"
"slices"

. "github.com/ironcore-dev/vgopath/internal/link"
"github.com/ironcore-dev/vgopath/internal/module"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/format"
"github.com/onsi/gomega/types"

. "github.com/ironcore-dev/vgopath/internal/link"
"github.com/ironcore-dev/vgopath/internal/module"
)

var _ = Describe("Internal", func() {
var (
tmpDir string
moduleA, moduleB, moduleB1, moduleB11, moduleB2, moduleC, moduleD module.Module
allModules []module.Module
)
BeforeEach(func() {
var err error
tmpDir, err = os.MkdirTemp("", "test")
Expect(err).NotTo(HaveOccurred())

allModules = []module.Module{}
moduleA = module.Module{
Path: "a",
Dir: "/tmp/a",
Dir: filepath.Join("a"),
Main: true,
}
allModules = append(allModules, moduleA)
moduleB = module.Module{
Path: "example.org/b",
Dir: "/tmp/example.org/b",
Dir: filepath.Join("example.org", "b"),
}
allModules = append(allModules, moduleB)
moduleB1 = module.Module{
Path: "example.org/b/1",
Dir: "/tmp/example.org/b/1",
Dir: filepath.Join("example.org", "b", "1"),
}
allModules = append(allModules, moduleB1)
moduleB11 = module.Module{
Path: "example.org/b/1/1",
Dir: "/tmp/example.org/b/1/1",
Dir: filepath.Join("example.org", "b", "1", "1"),
}
allModules = append(allModules, moduleB11)
moduleB2 = module.Module{
Path: "example.org/b/2",
Dir: "/tmp/example.org/b/2",
Dir: filepath.Join("example.org", "b", "2"),
}
allModules = append(allModules, moduleB2)
moduleC = module.Module{
Path: "example.org/user/c",
Dir: "/tmp/example.org/user/c",
Dir: filepath.Join("example.org", "user", "c"),
}
allModules = append(allModules, moduleC)
moduleD = module.Module{
Path: "example.org/d",
}
allModules = append(allModules, moduleD)
})
AfterEach(func() {
if tmpDir != "" {
Expand Down Expand Up @@ -138,6 +150,28 @@ var _ = Describe("Internal", func() {
Expect(os.MkdirAll(dstGopathDir, 0777)).To(Succeed())
})

Describe("Nodes", func() {
It("should correctly handle submodules", func() {
Expect(makeModules(srcGopathDir, &moduleB, &moduleB1, &moduleB11, &moduleB2)).NotTo(HaveOccurred())

nodes, err := BuildModuleNodes([]module.Module{moduleB, moduleB1, moduleB11, moduleB2})
Expect(err).NotTo(HaveOccurred())

Expect(Nodes(dstGopathDir, nodes)).To(Succeed())

Expect(dstGopathDir).To(HaveEntries(map[string]types.GomegaMatcher{
filepath.Join("example.org", "b"): BeADirectory(),
filepath.Join("example.org", "b", "go.mod"): BeASymlinkTo(filepath.Join(moduleB.Dir, "go.mod")),
filepath.Join("example.org", "b", "1"): BeADirectory(),
filepath.Join("example.org", "b", "1", "go.mod"): BeASymlinkTo(filepath.Join(moduleB1.Dir, "go.mod")),
filepath.Join("example.org", "b", "1", "1"): BeADirectory(),
filepath.Join("example.org", "b", "1", "1", "go.mod"): BeASymlinkTo(filepath.Join(moduleB11.Dir, "go.mod")),
filepath.Join("example.org", "b", "2"): BeADirectory(),
filepath.Join("example.org", "b", "2", "go.mod"): BeASymlinkTo(filepath.Join(moduleB2.Dir, "go.mod")),
}))
})
})

Describe("GoBin", func() {
var (
srcGoBinDir string
Expand Down Expand Up @@ -190,6 +224,73 @@ var _ = Describe("Internal", func() {
})
})

func makeModules(gopath string, mods ...*module.Module) error {
for _, mod := range mods {
// update dir to include gopath prefix
mod.Dir = filepath.Join(gopath, mod.Dir)

if err := os.MkdirAll(mod.Dir, 0777); err != nil {
return err
}

if err := os.WriteFile(filepath.Join(mod.Dir, "go.mod"), []byte("module "+mod.Path+"\n"), 0666); err != nil {
return err
}
}
return nil
}

func HaveEntries(expected map[string]types.GomegaMatcher) types.GomegaMatcher {
return &haveEntriesMatcher{matchers: expected}
}

// haveEntriesMatcher is very similar to matchers.AndMatcher.
type haveEntriesMatcher struct {
matchers map[string]types.GomegaMatcher

// state
baseDir string
firstFailedFilename string
}

func (m *haveEntriesMatcher) Match(actual interface{}) (success bool, err error) {
m.firstFailedFilename = ""

var ok bool
m.baseDir, ok = actual.(string)
if !ok {
return false, fmt.Errorf("HaveEntries matcher expects a string but got %T", actual)
}

// sort matchers by filename for stable test results even though maps are unsorted
filenames := make([]string, 0, len(m.matchers))
for filename := range m.matchers {
filenames = append(filenames, filename)
}
slices.Sort(filenames)

for _, filename := range filenames {
matcher := m.matchers[filename]

success, err := matcher.Match(filepath.Join(m.baseDir, filename))
if !success || err != nil {
m.firstFailedFilename = filename
return false, err
}
}

return true, nil
}

func (m *haveEntriesMatcher) FailureMessage(actual interface{}) (message string) {
return m.matchers[m.firstFailedFilename].FailureMessage(filepath.Join(m.baseDir, m.firstFailedFilename))
}

func (m *haveEntriesMatcher) NegatedFailureMessage(actual interface{}) (message string) {
// not the most beautiful list of matchers, but not bad either...
return format.Message(actual, "not to have these entries: %s", m.matchers)
}

func BeASymlinkTo(filename string) types.GomegaMatcher {
return &beASymlinkToMatcher{filename}
}
Expand Down

0 comments on commit eaaa873

Please sign in to comment.