Skip to content

support sub-path in goinstall:// (implement same behaviour as go buid)#266

Draft
gebi wants to merge 2 commits into
marcosnils:masterfrom
gebi:t/goinstall-support-subpath
Draft

support sub-path in goinstall:// (implement same behaviour as go buid)#266
gebi wants to merge 2 commits into
marcosnils:masterfrom
gebi:t/goinstall-support-subpath

Conversation

@gebi

@gebi gebi commented Jan 26, 2026

Copy link
Copy Markdown

Hi,

this PR adds support for sub-path's in goinstall:// provider as is supported in go install itself too.

It implements the same behaviour as go build to find base module path.
Sadly https://pkg.go.dev/golang.org/x/tools/go/vcs is deprecated, thus this is implemented as recommended via call to go list.

The only real downside is, that this PR requires at least one additional external call to go list for every pkg that is handled via goinstall:// even if no subpath is actually used (for actual sub-paths it results in one network call per sub-path component on each invocation).

But we get nice sub-path support

% ./bin install --debug goinstall://github.com/mgit-at/godeb/cmd/godeb
  • debug logs enabled, version: dev

  • Config directory is: /home/gebi/.config/bin
  • Download path set to /home/gebi/bin
  • Using base module github.com/mgit-at/godeb with sub path "/cmd/godeb"
^^^ new debug output if base module was found
  • Using provider 'goinstall' for 'goinstall://github.com/mgit-at/godeb/cmd/godeb'
  • Getting latest release for github.com/mgit-at/godeb
  • Copying for godeb@v0.0.0-20251128154812-dd468d8cd01d into /home/gebi/bin/godeb
  • Done installing godeb v0.0.0-20251128154812-dd468d8cd01d

Without this PR the call results in an error (go install directly works though)

% bin install --debug goinstall://github.com/mgit-at/godeb/cmd/godeb
  • debug logs enabled, version: dev

  • Config directory is: /home/gebi/.config/bin
  • Download path set to /home/gebi/bin
  • Using provider 'goinstall' for 'goinstall://github.com/mgit-at/godeb/cmd/godeb'
  • Getting latest release for github.com/mgit-at/godeb/cmd/godeb
  ⨯ command failed                                   error=failed to get latest version: invalid character 'o' in literal null (expecting 'u')
% go install github.com/mgit-at/godeb/cmd/godeb@latest
^^^ works, thus this PR

output of list cmd

% ./bin list

Path                          Version                             URL                                             Status
/home/gebi/bin/bin            v0.24.2                             goinstall://github.com/marcosnils/bin           OK
/home/gebi/bin/godeb          v0.0.0-20251128154812-dd468d8cd01d  goinstall://github.com/mgit-at/godeb/cmd/godeb  OK

bin/config.json looks fine

        "/home/gebi/bin/godeb": {
            "path": "/home/gebi/bin/godeb",
            "remote_name": "godeb",
            "version": "v0.0.0-20251128154812-dd468d8cd01d",
            "hash": "12596ca928ce8ed94d495ae152b4dc60a532ec3b3157c415bb645ebe3e35b473",
            "url": "goinstall://github.com/mgit-at/godeb/cmd/godeb",
            "provider": "goinstall",
            "package_path": "",
            "pinned": false
        },

update / ensure / remove also works

% ./bin update --debug
  • debug logs enabled, version: dev

  • Config directory is: /home/gebi/.config/bin
  • Download path set to /home/gebi/bin
  • Using provider 'goinstall' for 'goinstall://github.com/marcosnils/bin'
  • Checking updates for /home/gebi/bin/bin
  • Using base module github.com/mgit-at/godeb with sub path "/cmd/godeb"
  • Using provider 'goinstall' for 'goinstall://github.com/mgit-at/godeb/cmd/godeb'
  • Checking updates for /home/gebi/bin/godeb
...
% ./bin ensure
% ./bin remove godeb

gebi added 2 commits January 26, 2026 03:38
implement same behaviour as go build to find base module path.
Sadly https://pkg.go.dev/golang.org/x/tools/go/vcs is deprecated
thus this is implemented as recommended via call to `go list`
baseModulePath needs to be called without version
for len(parts) > 0 {
mod := strings.Join(parts, "/")
out, err := exec.Command("go", "list", "-m", "-f", "{{.Path}}", mod+"@latest").Output()
if err == nil {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what happens if go list fails for other error than "no matching version". Error handling seems it needs improvement.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are right, if the PR is "ok" in principle i'd add unit tests too, because the behaviour currently expected by the call to go list should be tested too.

parts := strings.Split(noVer, "/")
for len(parts) > 0 {
mod := strings.Join(parts, "/")
out, err := exec.Command("go", "list", "-m", "-f", "{{.Path}}", mod+"@latest").Output()

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd really prefer avoiding having to do this. Is this how go install actually does it?

@gebi gebi Jan 26, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think so, yes...

% go install -x github.com/mgit-at/godeb/cmd/godeb@latest
# get https://proxy.golang.org/github.com/@v/list
# get https://proxy.golang.org/github.com/mgit-at/godeb/cmd/@v/list
# get https://proxy.golang.org/github.com/mgit-at/godeb/@v/list
# get https://proxy.golang.org/github.com/mgit-at/@v/list
# get https://proxy.golang.org/github.com/mgit-at/godeb/cmd/godeb/@v/list
# get https://proxy.golang.org/github.com/mgit-at/godeb/@v/list: 200 OK (0.028s)
# get https://proxy.golang.org/github.com/mgit-at/godeb/@latest
# get https://proxy.golang.org/github.com/mgit-at/godeb/cmd/@v/list: 404 Not Found (0.052s)
# get https://proxy.golang.org/github.com/@v/list: 404 Not Found (0.052s)
# get https://proxy.golang.org/github.com/mgit-at/godeb/cmd/godeb/@v/list: 404 Not Found (0.056s)
# get https://proxy.golang.org/github.com/mgit-at/godeb/@latest: 200 OK (0.028s)
# get https://proxy.golang.org/github.com/mgit-at/@v/list: 404 Not Found (0.057s)
WORK=/tmp/go-build3389364809

It would also be possible to call proxy.golang.org directly and implement the walking via the http api but at the expense of breaking and re-implementing the whole local/private/... module machinery that's hidden beneath go list.

sadly i could not find any other way...
the pkg holding RepoRootForImportPath as a separate and externally usable implementation for this got deprecated because of code rot https://pkg.go.dev/golang.org/x/tools/go/vcs and the only suggested solution is to call go list instead.

The real implementation can be found here vcs.RepoRootForImportPath and it's imho too complex and sensitive to extract, as it requires other packages from go/internal too so much so that even the pkg in x/tools/go gave up tracking it...

i have a implementation that parses the -x (trace) output of go list, as this tool leaks the output of vcs.RepoRootForImportPath there, which would require no recursive calls but after having done the impl it's that ugly to parse trace output and brittle i looked for other possibilities...

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think i've an idea how to make the default case quicker and still have a somewhat sane impl, i'll cook something up and push into this PR

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sadly i could not find any other way...
the pkg holding RepoRootForImportPath as a separate and externally usable implementation for this got deprecated because of code rot pkg.go.dev/golang.org/x/tools/go/vcs and the only suggested solution is to call go list instead.

btw not sure if this helps but we've recently also fixed and extracted this functionality to use it in github.com/dagger/dagger so if needed, it can be used from here: https://github.com/dagger/dagger/blob/ba4c738ed055b37f7191678f43c3436e741c910a/engine/vcs/vcs.go#L342

@gebi gebi Jan 28, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice, i've done something similar, though i extracted my base version from the current state of go/src/cmd/go/internal/vcs/vcs.go and the one in dagger seems to be the old deprecated version from x/tools/go/vcs/vcs.go.

I'd rather not touch a code that upstream deemed unmaintainable and insecure because it diverted so much that security patches can't be applied.

My idea was to implement RepoRootForImportPath via taking repoRootFromVCSPaths from current upstream and implement a stub for repoRootForImportDynamic using go list.

thus we would be shielded from much of the complexity with only the implementation of static matches/hosts (like github/gitlab/...) and one vcsPing call for verification from go and everything else goes out of process via go list.

current state is, i've a complete extract of up-to-date vcs.go running. But after doing that it's imho kinda ugly and defies the purpose of being exactly the same mess and unmaintainable over time, thus my idea from above...

in progress... https://github.com/gebi/goxt

@gebi gebi marked this pull request as draft January 28, 2026 11:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants