talos
173 строки · 4.3 Кб
1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5// structprotogen is a tool to generate proto files from Go structs.
6package main
7
8//nolint:gci
9import (
10"fmt"
11"os"
12"path"
13"path/filepath"
14
15"github.com/spf13/cobra"
16
17"github.com/siderolabs/structprotogen/ast"
18"github.com/siderolabs/structprotogen/consts"
19"github.com/siderolabs/structprotogen/loader"
20"github.com/siderolabs/structprotogen/proto"
21"github.com/siderolabs/structprotogen/types"
22)
23
24// rootCmd represents the base command when called without any subcommands.
25var rootCmd = &cobra.Command{
26Use: "structprotogen path dest",
27Short: "This CLI is used to generate proto files from Go structs into one proto file",
28Example: "gotagsrewrite github.com/siderolabs/talos/pkg/machinery/resources/... ./api/resource/definitions",
29Args: cobra.ExactArgs(2),
30Version: "v1.0.0",
31RunE: func(cmd *cobra.Command, args []string) error {
32return run(args[0], args[1])
33},
34SilenceUsage: true,
35DisableAutoGenTag: true,
36}
37
38func main() {
39err := rootCmd.Execute()
40if err != nil {
41os.Exit(1)
42}
43}
44
45// TODO(DmitriyMV): get comments for fields
46
47//nolint:gocyclo
48func run(pkgPath, dst string) error {
49loadedPkgs, err := loader.LoadPackages(pkgPath)
50if err != nil {
51return err
52}
53
54constants, err := consts.FindIn(loadedPkgs)
55if err != nil {
56return err
57}
58
59taggedStructs := ast.FindAllTaggedStructs(loadedPkgs)
60printFoundStructs(taggedStructs)
61
62sortedPkgs, err := types.FindPkgDecls(taggedStructs, loadedPkgs)
63if err != nil {
64return fmt.Errorf("error finding path '%s' declarations: %w", pkgPath, err)
65}
66
67pkgsTypes, err := types.ParseDeclsData(sortedPkgs, taggedStructs)
68if err != nil {
69return fmt.Errorf("error parsing path '%s' declarations data: %w", pkgPath, err)
70}
71
72externalTypes := types.FindExternalTypes(pkgsTypes, taggedStructs)
73for i := 0; i < externalTypes.Len(); i++ {
74externalType := externalTypes.Get(i)
75
76if constants.HaveType(externalType.Pkg, externalType.Name) {
77continue
78}
79
80if !proto.IsSupportedExternalType(externalType) {
81return fmt.Errorf("external type '%s.%s' is not supported", externalType.Pkg, externalType.Name)
82}
83}
84
85data := proto.PrepareProtoData(pkgsTypes, constants)
86
87for i := 0; i < data.Len(); i++ {
88protoData := data.Get(i)
89
90fmt.Println("--------")
91protoData.WriteDebug(os.Stdout)
92}
93
94err = os.MkdirAll(dst, 0o755)
95if err != nil {
96return fmt.Errorf("failed to create directory for proto files: %w", err)
97}
98
99if len(constants) > 0 {
100err = withFile(filepath.Join(dst, "enums", "enums.proto"), func(f *os.File) error {
101return constants.FormatProtoFile(f)
102})
103if err != nil {
104return fmt.Errorf("failed to write enums proto file: %w", err)
105}
106}
107
108for i := 0; i < data.Len(); i++ {
109protoData := data.Get(i)
110
111dstDir, err := filepath.Abs(filepath.Join(dst, protoData.Name))
112if err != nil {
113return fmt.Errorf("failed to get absolute path for pkg '%s': %w", protoData.GoPkg, err)
114}
115
116err = os.MkdirAll(dstDir, 0o755)
117if err != nil {
118return fmt.Errorf("failed to create directory for pkg '%s' proto files: %w", protoData.GoPkg, err)
119}
120
121dstFile, err := filepath.Abs(filepath.Join(dstDir, path.Base(protoData.GoPkg)+".proto"))
122if err != nil {
123return fmt.Errorf("failed to get absolute path for destination file: %w", err)
124}
125
126fmt.Println("writing file", dstFile)
127
128err = withFile(dstFile, func(f *os.File) error {
129protoData.Format(f)
130
131return nil
132})
133if err != nil {
134return fmt.Errorf("failed to write file '%s': %w", dstFile, err)
135}
136}
137
138return nil
139}
140
141func printFoundStructs(structs ast.TaggedStructs) {
142for decl := range structs {
143fmt.Printf("found tagged struct '%s' in pkg '%s'\n", decl.Name, decl.Pkg)
144}
145}
146
147func withFile(filename string, fn func(f *os.File) error) error {
148dir := filepath.Dir(filename)
149
150_, err := os.Stat(dir)
151if os.IsNotExist(err) {
152err = os.MkdirAll(dir, 0o755)
153if err != nil {
154return err
155}
156} else if err != nil {
157return err
158}
159
160file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, os.ModePerm)
161if err != nil {
162return err
163}
164
165defer func() {
166err := file.Close()
167if err != nil {
168fmt.Println("failed to close file:", err)
169}
170}()
171
172return fn(file)
173}
174