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