misc: auto-generate go bindings 30/31930/8
authorVladimir Lavor <vlavor@cisco.com>
Thu, 8 Apr 2021 11:08:04 +0000 (13:08 +0200)
committerDamjan Marion <dmarion@me.com>
Tue, 27 Apr 2021 09:24:48 +0000 (09:24 +0000)
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 <vlavor@cisco.com>
Change-Id: I5bae113b85eaf5ebda8e292d34c9826075ef19b5

Makefile
src/tools/vppapigen/generate_go.py [new file with mode: 0755]

index 0f0af4a..9573d82 100644 (file)
--- 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 (executable)
index 0000000..1e072ef
--- /dev/null
@@ -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()