abhinav / doc2go

Your Go project's documentation, to-go.
https://abhinav.github.io/doc2go
Apache License 2.0
88 stars 7 forks source link

How to generate docs for +1 local Go module? #252

Closed unmultimedio closed 4 hours ago

unmultimedio commented 6 hours ago

Hello, and thank you for this documentation project! I'm reading through the docs https://abhinav.github.io/doc2go/docs/usage/#specifying-the-input and it says we can send multiple inputs to generate a combined documentation for all those targets.

Assume I have a directory such as:

$ tree .
.
├── module1
│   ├── pkgA
│   │   └── v1
│   │       └── foo.go
│   └── go.mod
└── module2
    ├── pkgB
    │   └── v1
    │       └── foo.go
    ├── pkgC
    │   └── v1
    │       └── foo.go
    └── go.mod

Where module1 depends on module2 (via a require in its go.mod file).

When I try to run doc2go I get:

$ doc2go -debug ./module1/... ./module2/...
11.201ms for GOROOT= GOPATH= GO111MODULE=off GOPROXY= PWD=[redacted] go list -e -f {{context.ReleaseTags}} -- unsafe
9.610333ms for GOROOT= GOPATH= GO111MODULE= GOPROXY= PWD=[redacted] go list -tags  -e -json=Name,ImportPath,Error,Dir,GoFiles,IgnoredGoFiles,IgnoredOtherFiles,CFiles,CgoFiles,CXXFiles,MFiles,HFiles,FFiles,SFiles,SwigFiles,SwigCXXFiles,SysoFiles,DepOnly,Imports,ImportMap -compiled=false -test=false -export=false -deps=true -find=false -pgo=off -tags  -- ./module1/... ./module2/...
[./module1/...] -: pattern ./module1/...: directory prefix module1 does not contain main module or its selected dependencies
[./module2/...] -: pattern ./module2/...: directory prefix module2 does not contain main module or its selected dependencies
Rendering directory

And it doesn't render anything. It's different if I go on each directory and generate separatedly:

$ cd module1
$ doc2go ./... # all good
$ cd ../module2
$ doc2go ./... # all good

Am I missing something from the docs? Since module1 depends on module2, I'd like types to be correctly linked in the same docs, so when I navigate on module1.SomeTypeA that contains a field of type module2.SomeOtherTypeB, it takes me to those docs from module2.

unmultimedio commented 5 hours ago

Update: I managed to pass both modules to doc2go and generate docs correctly using a Go workspace:

go 1.22.2

use (
    ./module1
    ./module2
)

And now the command renders all docs for both modules:

$ doc2go -debug ./module1/... ./module2/...
35.061959ms for GOROOT= GOPATH= GO111MODULE=off GOPROXY= PWD=[redacted] go list -e -f {{context.ReleaseTags}} -- unsafe
68.102625ms for GOROOT= GOPATH= GO111MODULE= GOPROXY= PWD=[redacted] go list -tags  -e -json=Name,ImportPath,Error,Dir,GoFiles,IgnoredGoFiles,IgnoredOtherFiles,CFiles,CgoFiles,CXXFiles,MFiles,HFiles,FFiles,SFiles,SwigFiles,SwigCXXFiles,SysoFiles,DepOnly,Imports,ImportMap -compiled=false -test=false -export=false -deps=true -find=false -pgo=off -tags  -- ./module1/... ./module2/...
Rendering package ...
Rendering directory ...

But now the types are not linked correctly, e.g.: if I have

# in module1
package pkga

import pkgb "module2/some/pkgb"

type SomeType struct {
  SomeField pkgb.OtherType
}

It renders pkga.SomeType.SomeField linking to public docs instead of the locally available, just generated one:

- https://pkg.go.dev/module2/pkgB#OtherType // I get
+                  ./module2/pkgB#OtherType // I want

Is there a way I can tell doc2go I want the types that are available in other modules in the same generation to be linked locally, something like the Go replace directive?

abhinav commented 5 hours ago

Hello! I was just about to respond with the go.work trick: Since doc2go depends on go/packages for package discovery, the patterns you give it must be go list-able. So if go list ./module1/... ./module2/... wouldn't work, doc2go's version won't work either.

I have a system where I needed this functionality and I've used:

go work init
go work use ./module1 ./module2
doc2go ./module1/... ./module2/...

(I suspect go work use -r . would also work but an explicit list is needed for the doc2go invocation since go list wouldn'5 do anything there.)

But now the types are not linked correctly, e.g.: if I have

Huh, that doesn't sound right. Cross-module links work correctly when I'm doing the same. Let me see if I can repro locally.

abhinav commented 5 hours ago

Yeah, confirmed. Given:

-- bar/bar.go --
package bar

import "foo"

func DoThings(foo.Foo) {}
-- bar/go.mod --
module bar

go 1.23.2
-- foo/foo.go --
package foo

type Foo struct{}
-- foo/go.mod --
module foo

go 1.23.2
-- go.work --
go 1.23.2

use (
    ./bar
    ./foo
)

I ran:

❯ doc2go foo/... bar/...

The link to foo.Foo in bar is:

<a href="../foo#Foo">
abhinav commented 5 hours ago

@unmultimedio I see you're doing: doc2go ./module1/... ./module2/... Try the import path for the modules instead of relative directory paths? e.g. doc2go example.com/module1/... example.com/module2/...

If that works, my guess is that the relative import path handling logic doesn't necessarily handle workspaces correctly.

abhinav commented 4 hours ago

I just noticed this question:

Is there a way I can tell doc2go I want the types that are available in other modules in the same generation to be linked locally, something like the Go replace directive?

First: When operating correctly, all code that is part of the same code generation request will be interlinked locally automatically. So the above should just work.

However, if you do end up in a situation where you want control over these links, you can use the -pkg-doc flag. Usage is documented here: https://abhinav.github.io/doc2go/docs/usage/links/.

unmultimedio commented 4 hours ago

Thanks for your fast response! Here's a repro:

-- go.work --
go 1.22.2

use (
    ./mod1
    ./mod2
)
-- mod1/go.mod --
module github.com/unmultimedio/mod1

go 1.22.2
-- mod1/pkga/a.go --
package pkga

type TypeA struct {
    Field1 int
    Field2 string
}
-- mod2/go.mod --
module github.com/unmultimedio/mod2

go 1.22.2

require (
    github.com/unmultimedio/mod1 v1.2.3
    google.golang.org/protobuf v1.34.2
)

require github.com/google/go-cmp v0.5.9 // indirect
-- mod2/pkgb/b.go --
package pkgb

import (
    "github.com/unmultimedio/mod1"
    protoreflect "google.golang.org/protobuf/reflect/protoreflect"
)

type TypeB struct {
    FieldA        mod1.TypeA
    FieldB        int
    FieldExternal protoreflect.Message
}

Notes:

Now, when I do:

$ doc2go ./mod1/... ./mod2/...

The generated docs in http://127.0.0.1:8080/github.com/unmultimedio/mod2/pkgb/#TypeB link:

Now, I assume the issue vs your example has to do with the fact that I have an actual import in my mod2/go.mod, and my import uses the complete module name import "github.com/unmultimedio/mod1", and not a "local" import package as your example does: import "foo".

@unmultimedio I see you're doing: doc2go ./module1/... ./module2/... Try the import path for the modules instead of relative directory paths? e.g. doc2go example.com/module1/... example.com/module2/...

If that works, my guess is that the relative import path handling logic doesn't necessarily handle workspaces correctly.

Tried that too, same result 😞

abhinav commented 4 hours ago

Oh, I see the problem. There's no such thing as mod1.TypeA. The import path in pkgb/b.go is: github.com/unmultimedio/mod1, but TypeA is at github.com/unmultimedio/mod1/pkga.

It works once I fix the import path:

 import (
-   "github.com/unmultimedio/mod1"
+   mod1 "github.com/unmultimedio/mod1/pkga"
    protoreflect "google.golang.org/protobuf/reflect/protoreflect"
 )
❯ rg -o '<a href="[^"]+?TypeA">' _site/github.com/unmultimedio/mod2
_site/github.com/unmultimedio/mod2/pkgb/index.html
28:<a href="../../mod1/pkga#TypeA">
unmultimedio commented 4 hours ago

I just realized it's a nit from my side, I manually crafted this example, so I was missing the pkg name in the import:

import (
-   "github.com/unmultimedio/mod1"
+   "github.com/unmultimedio/mod1/pkga"
    protoreflect "google.golang.org/protobuf/reflect/protoreflect"
)

type TypeB struct {
-   FieldA        mod1.TypeA
+   FieldA        pkga.TypeA
    FieldB        int
    FieldExternal protoreflect.Message
}

Just see that you found the same issue.

Thank you so much for all your help, I can work with this now. Btw, there's no need for a replace directive, neither in go.mod in any module, or in the go.work file.

Have a nice weekend!