From 3ff1598846901e10ca837a77e33ce94594a93496 Mon Sep 17 00:00:00 2001 From: Vladimir Lavor Date: Thu, 8 Apr 2021 13:08:04 +0200 Subject: [PATCH] misc: auto-generate go bindings Type: feature Added target 'make go-api-files' creating compatible go bindings using JSON API definition and GoVPP binary API generator. Signed-off-by: Vladimir Lavor Change-Id: I5bae113b85eaf5ebda8e292d34c9826075ef19b5 --- Makefile | 5 + src/tools/vppapigen/generate_go.py | 183 +++++++++++++++++++++++++++++++++++++ 2 files changed, 188 insertions(+) create mode 100755 src/tools/vppapigen/generate_go.py diff --git a/Makefile b/Makefile index 0f0af4af008..9573d8222a7 100644 --- a/Makefile +++ b/Makefile @@ -216,6 +216,7 @@ help: @echo " featurelist - dump feature list in markdown" @echo " json-api-files - (re)-generate json api files" @echo " json-api-files-debug - (re)-generate json api files for debug target" + @echo " go-api-files - (re)-generate golang api files" @echo " docs - Build the Sphinx documentation" @echo " docs-venv - Build the virtual environment for the Sphinx docs" @echo " docs-clean - Remove the generated files from the Sphinx docs" @@ -613,6 +614,10 @@ json-api-files: json-api-files-debug: $(WS_ROOT)/src/tools/vppapigen/generate_json.py --debug-target +.PHONY: go-api-files +go-api-files: json-api-files + $(WS_ROOT)/src/tools/vppapigen/generate_go.py + .PHONY: ctags ctags: ctags.files @ctags --totals --tag-relative=yes -L $< diff --git a/src/tools/vppapigen/generate_go.py b/src/tools/vppapigen/generate_go.py new file mode 100755 index 00000000000..1e072ef1e1c --- /dev/null +++ b/src/tools/vppapigen/generate_go.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python3 + +import argparse +import os +import pathlib +import subprocess +import tarfile + +import requests +import sys + +# +# GoVPP API generator generates Go bindings compatible with the local VPP +# + +parser = argparse.ArgumentParser() +parser.add_argument("-govpp-commit", help="GoVPP commit or branch (defaults to v0.3.5-45-g671f16c)", + default="671f16c", # fixed GoVPP version + type=str) +parser.add_argument("-output-dir", help="output target directory for generated bindings", type=str) +parser.add_argument("-api-files", help="api files to generate (without commas)", nargs="+", type=str) +parser.add_argument("-import-prefix", help="prefix imports in the generated go code", type=str) +parser.add_argument("-no-source-path-info", help="disable source path info in generated files", nargs='?', const=True, + default=False) +args = parser.parse_args() + + +# Check input arguments +def validate_args(vpp_dir, o, f, c, i): + if o is not None: + if not os.path.exists(o) or os.path.isfile(o): + print(o + " is not a valid output path") + sys.exit(1) + else: + o = vpp_dir + if f is None: + f = [] + if c is None: + c = "671f16c" + if i is None: + i = "" + + return str(o), f, c, i + + +# Returns version of the installed Go +def get_go_version(go_root): + p = subprocess.Popen(["./go", "version"], + cwd=go_root + "/bin", + stdout=subprocess.PIPE, + universal_newlines=True, ) + output, _ = p.communicate() + output_fmt = output.replace("go version go", "", 1) + + return output_fmt.rstrip("\n") + + +# Returns version of the installed binary API generator +def get_binapi_gen_version(go_path): + p = subprocess.Popen(["./binapi-generator", "-version"], + cwd=go_path + "/bin", + stdout=subprocess.PIPE, + universal_newlines=True, ) + output, _ = p.communicate() + output_fmt = output.replace("govpp", "", 1) + + return output_fmt.rstrip("\n") + + +# Verifies local Go installation and installs the latest +# one if missing +def install_golang(go_root): + go_bin = go_root + "/bin/go" + + if os.path.exists(go_bin) and os.path.isfile(go_bin): + print('Go ' + get_go_version(go_root) + ' is already installed') + return + + print("Go binary not found, installing the latest version...") + go_folders = ['src', 'pkg', 'bin'] + + for f in go_folders: + if not os.path.exists(os.path.join(go_root, f)): + os.makedirs(os.path.join(go_root, f)) + + filename = requests.get('https://golang.org/VERSION?m=text').text + ".linux-amd64.tar.gz" + url = "https://dl.google.com/go/" + filename + r = requests.get(url) + with open("/tmp/" + filename, 'wb') as f: + f.write(r.content) + + go_tf = tarfile.open("/tmp/" + filename) + # Strip /go dir from the go_root path as it will + # be created while extracting the tar file + go_root_head, _ = os.path.split(go_root) + go_tf.extractall(path=go_root_head) + go_tf.close() + os.remove("/tmp/" + filename) + + print('Go ' + get_go_version(go_root) + ' was installed') + + +# Installs latest binary API generator +def install_binapi_gen(c, go_root, go_path): + os.environ['GO111MODULE'] = "on" + if os.path.exists(go_root + "/bin/go") & os.path.isfile(go_root + "/bin/go"): + p = subprocess.Popen(["./go", "get", "git.fd.io/govpp.git/cmd/binapi-generator@" + c], + cwd=go_root + "/bin", + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True, ) + _, error = p.communicate() + if p.returncode != 0: + print("binapi generator installation failed: %d %s" % (p.returncode, error)) + sys.exit(1) + bg_ver = get_binapi_gen_version(go_path) + print('Installed binary API generator ' + bg_ver) + + +# Creates generated bindings using GoVPP binapigen to the target folder +def generate_api(output_dir, vpp_dir, api_list, import_prefix, no_source, go_path): + output_binapi = output_dir + "binapi" if output_dir[-1] == "/" else output_dir + "/binapi" + json_dir = vpp_dir + "/build-root/install-vpp-native/vpp/share/vpp/api" + + if not os.path.exists(json_dir): + print("Missing JSON api definitions") + sys.exit(1) + + print("Generating API") + cmd = ["./binapi-generator", "--output-dir=" + output_binapi, "--input-dir=" + json_dir] + if len(api_list): + print("Following API files were requested by 'GO_API_FILES': " + str(api_list)) + print("Note that dependency requirements may generate additional API files") + cmd.append(api_list) + if not import_prefix == "": + cmd.append("-import-prefix=" + import_prefix) + if no_source: + cmd.append("-no-source-path-info") + p = subprocess.Popen(cmd, cwd=go_path + "/bin", + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True, ) + + out = p.communicate()[1] + if p.returncode != 0: + print("go api generate failed: %d %s" % (p.returncode, out)) + sys.exit(1) + + # Print nice output of the binapi generator + for msg in out.split(): + if "=" in msg: + print() + print(msg, end=" ") + + print("\n") + print("Go API bindings were generated to " + output_binapi) + + +def main(): + # project root directory + root = pathlib.Path(os.path.dirname(os.path.abspath(__file__))) + vpp_dir: str = root.parent.parent.parent + + o, f, c, i = validate_args(vpp_dir, args.output_dir, args.api_files, args.govpp_commit, + args.import_prefix) + + # go specific environment variables + if "GOROOT" in os.environ: + go_root = os.environ['GOROOT'] + else: + go_root = os.environ['HOME'] + "/.go" + if "GOPATH" in os.environ: + go_path = os.environ['GOPATH'] + else: + go_path = os.environ['HOME'] + "/go" + + install_golang(go_root) + install_binapi_gen(c, go_root, go_path) + generate_api(o, str(vpp_dir), f, i, args.no_source_path_info, go_path) + + +if __name__ == "__main__": + main() -- 2.16.6