diff --git a/cmd/cue/cmd/testdata/script/cmd_exp_gengo_alias.txtar b/cmd/cue/cmd/testdata/script/cmd_exp_gengo_alias.txtar new file mode 100644 index 00000000000..61b025b4eea --- /dev/null +++ b/cmd/cue/cmd/testdata/script/cmd_exp_gengo_alias.txtar @@ -0,0 +1,46 @@ +# reproduction for https://github.com/cue-lang/cue/issues/3974 +exec cue exp gengotypes +cmp cue_types_main_gen.go expect-gengo + +-- cue.mod/module.cue -- +module: "mod.test/foo" +language: version: "v0.13.0" + +-- cue.mod/gen/k8s.io/apimachinery/pkg/apis/meta/v1/types_go_gen.cue -- +package v1 + +#ObjectMeta: {} + +-- cue.mod/gen/k8s.io/api/core/v1/types_go_gen.cue -- +package v1 + +#PodSpec: {} + +-- main.cue -- +package main + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/api/core/v1" +) + +#Spec: { + meta: metav1.#ObjectMeta + spec: v1.#PodSpec +} + +-- expect-gengo -- +// Code generated by "cue exp gengotypes"; DO NOT EDIT. + +package main + +import ( + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type Spec struct { + Meta metav1.ObjectMeta `json:"meta"` + + Spec v1.PodSpec `json:"spec"` +} diff --git a/internal/encoding/gotypes/generate.go b/internal/encoding/gotypes/generate.go index 318cda71525..5e6cee45592 100644 --- a/internal/encoding/gotypes/generate.go +++ b/internal/encoding/gotypes/generate.go @@ -70,6 +70,15 @@ func Generate(ctx *cue.Context, insts ...*build.Instance) error { g.pkgRoot = instVal g.importCuePkgAsGoPkg = make(map[string]string) + // gather the import aliases for the instance. + // NOTE: this implementation means that if an import alias is employed in one file + // it will be used anywhere that package is referenced + importAliases, err := gatherImportAliases(inst) + if err != nil { + return err + } + g.importAliases = importAliases + iter, err := instVal.Fields(cue.Definitions(true)) if err != nil { return err @@ -100,7 +109,7 @@ func Generate(ctx *cue.Context, insts ...*build.Instance) error { buf = fmt.Appendf(buf, format, args...) } printf("// Code generated by \"cue exp gengotypes\"; DO NOT EDIT.\n\n") - goPkgName := goPkgNameForInstance(inst, instVal) + goPkgName := goPkgNameForInstance(inst, instVal, g.importAliases) if prev, ok := goPkgNamesDoneByDir[inst.Dir]; ok && prev != goPkgName { return fmt.Errorf("cannot generate two Go packages in one directory; %s and %s", prev, goPkgName) } else { @@ -112,7 +121,12 @@ func Generate(ctx *cue.Context, insts ...*build.Instance) error { if len(importedGo) > 0 { printf("import (\n") for _, path := range importedGo { - printf("\t%q\n", path) + quotedPath := fmt.Sprintf("%q", path) + alias, ok := g.importAliases[path] + if ok { + quotedPath = fmt.Sprintf("%s %s", alias, quotedPath) + } + printf("\t%s\n", quotedPath) } printf(")\n") } @@ -217,6 +231,9 @@ type generator struct { // def tracks the generation state for a single CUE definition. def *generatedDef + + // importAliases maps package names to the alias + importAliases map[string]string } type qualifiedPath = string // [build.Instance.ImportPath] + " " + [cue.Path.String] @@ -323,6 +340,7 @@ func (g *generator) emitType(val cue.Value, optionalStg optionalStrategy) (typeF if err != nil { return facts, fmt.Errorf("cannot parse @go type expression: %w", err) } + for _, pkgPath := range importedByName { g.importCuePkgAsGoPkg[pkgPath] = pkgPath } @@ -679,11 +697,15 @@ func goValueAttr(val cue.Value) cue.Attribute { // goPkgNameForInstance determines what to name a Go package generated from a CUE instance. // By default this is the CUE package name, but it can be overriden by a @go() package attribute. -func goPkgNameForInstance(inst *build.Instance, instVal cue.Value) string { +// Import aliases will also checked at this point and inserted if found. +func goPkgNameForInstance(inst *build.Instance, instVal cue.Value, aliasLookup map[string]string) string { attr := goValueAttr(instVal) if s, _ := attr.String(0); s != "" { return s } + if alias, ok := aliasLookup[inst.ImportPath]; ok { + return alias + } return inst.PkgName } @@ -751,7 +773,7 @@ func (g *generator) emitTypeReference(val cue.Value) (bool, typeFacts, error) { facts.isNillable = true // pointers can be nil } if root != g.pkgRoot { - g.def.printf("%s.", goPkgNameForInstance(inst, root)) + g.def.printf("%s.", goPkgNameForInstance(inst, root, g.importAliases)) } g.def.printf("%s", name) return true, facts, nil @@ -772,3 +794,32 @@ func emitDocs(printf func(string, ...any), name string, groups []*ast.CommentGro } } } + +// gatherImportAliases collects the aliases from imports across the instance. +// We explicitly ignore "_" aliases because CUE does not support. +func gatherImportAliases(inst *build.Instance) (map[string]string, error) { + fileAliases := make(map[string]string) + for _, f := range inst.Files { + for _, d := range f.Decls { + impDecl, ok := d.(*ast.ImportDecl) + if !ok { + continue + } + for _, s := range impDecl.Specs { + if s.Path == nil || s.Name == nil { + continue + } + alias := s.Name.Name + if alias == "_" { + continue + } + path, err := strconv.Unquote(s.Path.Value) + if err != nil { + return nil, err + } + fileAliases[path] = alias + } + } + } + return fileAliases, nil +}