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