Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,27 @@ to builders without direct coupling.
| `provenance.go` | `Provenance` — ties a spec JSON pointer to the source position of the Go construct that produced it; emitted via `Options.OnProvenance` (cross-ref linker, source side) |
| `classify/` | Classification predicates usable from both scanner and builders (e.g. `IsAllowedExtension`) |

### `internal/cliopts/` — the command-line option surface

Every knob on `codescan.Options` as a flag, declared once and shared by the commands (`cmd/genspec`,
`cmd/genspec-wasi`) so a flag means the same thing whichever one you reach for. Entries are keyed by
the field's *setter*, never by a name derived from the field, and a guard in the package tests fails
when a value-typed option lands with no flag (or excuse). Flag names are the kebab-case of the field
without exception. `-loader` (`go|own|auto`) is the tri-state that discharges `ToolchainFreeLoader`.
Options that are not values — `FS`, `ExportData`, `InputSpec`, the callbacks — are the command's
business, not the table's.

### `internal/cliconf/` — the configuration-file contract

Where a `.codescan.yaml` is found (searching upwards), what may be in it, and how it loses to
anything typed. Values arrive as a plain flat map, so the source is the command's business: `cmd/genspec`
feeds it through koanf; a command that must stay dependency-free calls `cliconf.Parse` (YAML, via the
`go.yaml.in/yaml/v3` the root module already has). `cliconf.YAML` satisfies koanf's parser interface
*structurally*, so this package owes koanf no import — which is what keeps it usable from
`cmd/genspec-wasi` under wasm. Keys are `<section>.<flag-name>`: sections come from
`cliopts.ConfigSchema()` merged with the command's own, an unknown section is skipped (another
command's half of a shared file) and an unknown key inside a known section is an error.

### `internal/packages/` — toolchain-free package loader (experimental)

Owns **both** ways of resolving a package graph, behind one `Loader`: delegate to
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ internal/exportdata/exportdata.zip

# stray binaries from "go build ./hack/..."
/genexportdata
/genspec
/genspec-wasi
/loader-benchmark

Expand Down
28 changes: 25 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,31 @@ diagnostics and spec validation — is on the doc site:

## Generate a spec from the command line

`genspec-wasi` is the headless counterpart: it writes the specification to standard output and takes no
dependency beyond the library, so it also cross-compiles to WebAssembly and runs under a WASI
runtime with no Go toolchain installed.
`genspec` is the headless counterpart: it writes the specification to standard output, or to the file
`-output` names, and reports what the scan observed as colored diagnostics on standard error.

```cmd
go install github.com/go-openapi/codescan/cmd/genspec@latest
```

```cmd
genspec -workdir [my source location] ./...
```

Every option the library takes is a flag. Beyond those, it writes YAML as readily as JSON
(`-output swagger.yaml` is enough), merges its discoveries into an existing document with `-input`,
and checks what it produced with `-validate`. Its exit status says which of those went wrong.

It does the same job as go-swagger's `swagger generate spec`, which drives this same library, but is
released on its own — so fixes and enhancements reach it at this project's pace, and it carries only
the dependencies a spec generator needs.

See [cmd/genspec/README.md](cmd/genspec/README.md).

### Where there is no Go toolchain

`genspec-wasi` runs the same scan taking no dependency beyond the library, so it cross-compiles to
WebAssembly and runs under a WASI runtime with no Go toolchain installed and no subprocess.

```cmd
go install github.com/go-openapi/codescan/cmd/genspec-wasi@latest
Expand Down
61 changes: 12 additions & 49 deletions cmd/genspec-wasi/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import (
"path/filepath"

"github.com/go-openapi/codescan"
"github.com/go-openapi/codescan/internal/cliopts"
"github.com/go-openapi/codescan/internal/exportdata"
)

Expand All @@ -55,12 +56,10 @@ func main() {
type config struct {
set *flag.FlagSet

workdir *string
bools map[string]*bool
buildTags *string
goos *string
goarch *string
loader *string
// scan is every knob the library takes, declared once in internal/cliopts and shared with the
// other commands. What follows is this command's own: where the document goes, and in what shape.
scan *cliopts.Values

exportData *string
format *string
output *string
Expand All @@ -70,14 +69,8 @@ type config struct {

func registerFlags(fs *flag.FlagSet) *config {
return &config{
set: fs,
workdir: fs.String("workdir", ".", "directory the scan runs from; patterns are relative to it"),
bools: registerBools(fs),
buildTags: fs.String("build-tags", "", "comma-separated go build tags to apply while loading"),
goos: fs.String("goos", "", "GOOS the scanned code is built for (default: this machine's)"),
goarch: fs.String("goarch", "", "GOARCH the scanned code is built for (default: this machine's)"),
loader: fs.String("loader", "auto",
`package loader: "go" runs go list, "own" needs no toolchain, "auto" picks own where there is no exec`),
set: fs,
scan: cliopts.Register(fs),
exportData: fs.String("export-data", "",
"directory or .zip of pre-computed export data for dependencies (see hack/genexportdata):\n"+
"full fidelity, none of the cost of type-checking them from source"),
Expand Down Expand Up @@ -144,18 +137,10 @@ func resolveFormat(format string) (bool, error) {
}

func (c *config) options(patterns []string, stderr io.Writer, sink *collector) (*codescan.Options, error) {
if len(patterns) == 0 {
patterns = []string{"./..."}
}

opts := &codescan.Options{
Packages: patterns,
WorkDir: *c.workdir,
BuildTags: *c.buildTags,
GOOS: *c.goos,
GOARCH: *c.goarch,
opts := &codescan.Options{Packages: cliopts.Patterns(patterns)}
if err := c.scan.Apply(opts); err != nil {
return nil, err
}
applyBools(opts, c.bools)

switch {
case *c.exportData != "":
Expand All @@ -171,16 +156,11 @@ func (c *config) options(patterns []string, stderr io.Writer, sink *collector) (
}
}

useOwn, err := resolveLoader(*c.loader)
if err != nil {
return nil, err
}
opts.ToolchainFreeLoader = useOwn
if useOwn {
if opts.ToolchainFreeLoader {
// The loader reads the host filesystem directly, so -workdir has to name a place that exists
// there rather than a place relative to wherever the process happens to be. Under WASI it never
// is: a guest starts at "/", which is not a preopen, so only an absolute path names a mount.
abs, err := absolutePath(*c.workdir)
abs, err := absolutePath(opts.WorkDir)
if err != nil {
return nil, err
}
Expand All @@ -203,23 +183,6 @@ func (c *config) options(patterns []string, stderr io.Writer, sink *collector) (
return opts, nil
}

// resolveLoader reports whether to use codescan's own loader.
//
// "auto" asks whether this build can start a subprocess at all: on wasm it cannot, so `go list` is
// not an option and the choice makes itself.
func resolveLoader(mode string) (bool, error) {
switch mode {
case "own":
return true, nil
case "go":
return false, nil
case "auto":
return !canExec(), nil
default:
return false, fmt.Errorf("%w: -loader %q is not one of go, own, auto", errBadFlag, mode)
}
}

// absolutePath resolves p against the working directory, in the host's own notion of a path.
//
// Absolute means what the host means by it, not "starts with a slash": a Windows -workdir starts
Expand Down
136 changes: 0 additions & 136 deletions cmd/genspec-wasi/options.go

This file was deleted.

Loading
Loading