vppapigen: fix incorrect comments in json
[vpp.git] / src / tools / vppapigen / generate_go.py
1 #!/usr/bin/env python3
2
3 import argparse
4 import os
5 import pathlib
6 import subprocess
7 import tarfile
8 import shutil
9
10 import requests
11 import sys
12
13 #
14 # GoVPP API generator generates Go bindings compatible with the local VPP
15 #
16
17 DefaultGoVppCommit = "16a47ef937b3a5ce6acf45885386062b323c8d25"
18
19
20 def version_geq(ver_a, ver_b):
21     major_a, minor_a, patch_a = ver_a.split(".")
22     major_b, minor_b, patch_b = ver_b.split(".")
23     if major_a > major_b:
24         return True
25     elif major_a == major_b and minor_a > minor_b:
26         return True
27     elif major_a == major_b and minor_a == minor_b and patch_a >= patch_b:
28         return True
29     return False
30
31
32 def execute(cli, cwd=None):
33     p = subprocess.Popen(
34         cli.split(),
35         cwd=cwd,
36         stdout=subprocess.PIPE,
37         stderr=subprocess.PIPE,
38         universal_newlines=True,
39     )
40     output, error = p.communicate()
41     if p.returncode != 0:
42         print("Command `%s` failed: %d %s" % (cli, p.returncode, error))
43         sys.exit(1)
44     return output, error
45
46
47 def get_go_version(go_root):
48     # Returns version of the installed Go
49     output, _ = execute("./go version", cwd=go_root + "/bin")
50     return output.replace("go version go", "", 1).rstrip("\n")
51
52
53 # Returns version of the installed binary API generator
54 def get_binapi_gen_version(go_path):
55     output, _ = execute("./binapi-generator -version", cwd=go_path + "/bin")
56     return output.replace("govpp", "", 1).rstrip("\n")
57
58
59 # Verifies local Go installation and installs the latest
60 # one if missing
61 def install_golang(go_root):
62     go_bin = go_root + "/bin/go"
63
64     if os.path.exists(go_bin) and os.path.isfile(go_bin):
65         print("Go " + get_go_version(go_root) + " is already installed")
66         return
67
68     filename = (
69         requests.get("https://golang.org/VERSION?m=text").text + ".linux-amd64.tar.gz"
70     )
71     url = "https://dl.google.com/go/" + filename
72
73     print("Go binary not found, installing the latest version...")
74     print("Download url      = %s" % url)
75     print("Install directory = %s" % go_root)
76     text = input("[Y/n] ?")
77
78     if text.strip().lower() != "y" and text.strip().lower() != "yes":
79         print("Aborting...")
80         exit(1)
81
82     go_folders = ["src", "pkg", "bin"]
83
84     for f in go_folders:
85         if not os.path.exists(os.path.join(go_root, f)):
86             os.makedirs(os.path.join(go_root, f))
87     r = requests.get(url)
88     with open("/tmp/" + filename, "wb") as f:
89         f.write(r.content)
90
91     go_tf = tarfile.open("/tmp/" + filename)
92     # Strip /go dir from the go_root path as it will
93     # be created while extracting the tar file
94     go_root_head, _ = os.path.split(go_root)
95     go_tf.extractall(path=go_root_head)
96     go_tf.close()
97     os.remove("/tmp/" + filename)
98
99     print("Go " + get_go_version(go_root) + " was installed")
100
101
102 # Installs latest binary API generator
103 def install_binapi_gen(c, go_root, go_path):
104     go_version = get_go_version(go_root)
105     if version_geq(go_version, "1.18.0"):
106         execute(
107             "./go install git.fd.io/govpp.git/cmd/binapi-generator@" + c,
108             cwd=go_root + "/bin",
109         )
110     else:
111         os.environ["GO111MODULE"] = "on"
112         execute(
113             "./go get git.fd.io/govpp.git/cmd/binapi-generator@" + c,
114             cwd=go_root + "/bin",
115         )
116     bg_ver = get_binapi_gen_version(go_path)
117     print("Installed binary API generator " + bg_ver)
118
119
120 # Creates generated bindings using GoVPP binapigen to the target folder
121 def generate_api(output_dir, vpp_dir, api_list, import_prefix, no_source, go_path):
122     json_dir = vpp_dir + "/build-root/install-vpp-native/vpp/share/vpp/api"
123
124     if not os.path.exists(json_dir):
125         print("Missing JSON api definitions")
126         sys.exit(1)
127
128     print("Generating API")
129     cmd = ["./binapi-generator", "--input-dir=" + json_dir]
130     if output_dir:
131         cmd += ["--output-dir=" + output_dir]
132     if len(api_list):
133         print("Following API files were requested by 'GO_API_FILES': " + str(api_list))
134         print("Note that dependency requirements may generate additional API files")
135         cmd.append(api_list)
136     if import_prefix:
137         cmd.append("-import-prefix=" + import_prefix)
138     if no_source:
139         cmd.append("-no-source-path-info")
140
141     _, out = execute(" ".join(cmd), cwd=go_path + "/bin")
142     # Print nice output of the binapi generator
143     for msg in out.split():
144         if "=" in msg:
145             print()
146         print(msg, end=" ")
147
148     print("\n")
149     print("Go API bindings were generated to " + output_dir)
150
151
152 def get_go_variables():
153     # go specific environment variables
154     if "GOROOT" in os.environ:
155         go_root = os.environ["GOROOT"]
156     else:
157         go_binary = shutil.which("go")
158         if go_binary != "":
159             go_binary_dir, _ = os.path.split(go_binary)
160             go_root = os.path.join(go_binary_dir, "..")
161         else:
162             go_root = os.environ["HOME"] + "/.go"
163     if "GOPATH" in os.environ:
164         go_path = os.environ["GOPATH"]
165     else:
166         go_path = os.environ["HOME"] + "/go"
167
168     return go_root, go_path
169
170
171 def main():
172     # project root directory
173     root = pathlib.Path(os.path.dirname(os.path.abspath(__file__)))
174     vpp_dir = root.parent.parent.parent
175
176     parser = argparse.ArgumentParser()
177     parser.add_argument(
178         "-govpp-commit",
179         "--govpp-commit",
180         help="GoVPP commit or branch ",
181         default=DefaultGoVppCommit,
182         type=str,
183     )
184     parser.add_argument(
185         "-output-dir",
186         "--output-dir",
187         help="output target directory for generated bindings",
188         type=str,
189         default=os.path.join(vpp_dir, "vppbinapi"),
190     )
191     parser.add_argument(
192         "-api-files",
193         "--api-files",
194         help="api files to generate (without commas)",
195         nargs="+",
196         type=str,
197         default=[],
198     )
199     parser.add_argument(
200         "-import-prefix",
201         "--import-prefix",
202         help="prefix imports in the generated go code",
203         default="",
204         type=str,
205     )
206     parser.add_argument(
207         "-no-source-path-info",
208         "--no-source-path-info",
209         help="disable source path info in generated files",
210         nargs="?",
211         const=True,
212         default=True,
213     )
214     args = parser.parse_args()
215
216     go_root, go_path = get_go_variables()
217     install_golang(go_root)
218
219     if not (
220         os.path.exists(go_root + "/bin/go") and os.path.isfile(go_root + "/bin/go")
221     ):
222         print(go_root + "/bin/go does not exist")
223         sys.exit(1)
224
225     install_binapi_gen(args.govpp_commit, go_root, go_path)
226     generate_api(
227         args.output_dir,
228         str(vpp_dir),
229         args.api_files,
230         args.import_prefix,
231         args.no_source_path_info,
232         go_path,
233     )
234
235
236 if __name__ == "__main__":
237     main()