#!/usr/bin/env python3 import argparse import os import pathlib import subprocess import tarfile import shutil import requests import sys # # GoVPP API generator generates Go bindings compatible with the local VPP # DefaultGoVppCommit = "16a47ef937b3a5ce6acf45885386062b323c8d25" def version_geq(ver_a, ver_b): major_a, minor_a, patch_a = ver_a.split(".") major_b, minor_b, patch_b = ver_b.split(".") if major_a > major_b: return True elif major_a == major_b and minor_a > minor_b: return True elif major_a == major_b and minor_a == minor_b and patch_a >= patch_b: return True return False def execute(cli, cwd=None): p = subprocess.Popen( cli.split(), cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, ) output, error = p.communicate() if p.returncode != 0: print("Command `%s` failed: %d %s" % (cli, p.returncode, error)) sys.exit(1) return output, error def get_go_version(go_root): # Returns version of the installed Go output, _ = execute("./go version", cwd=go_root + "/bin") return output.replace("go version go", "", 1).rstrip("\n") # Returns version of the installed binary API generator def get_binapi_gen_version(go_path): output, _ = execute("./binapi-generator -version", cwd=go_path + "/bin") return output.replace("govpp", "", 1).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 filename = ( requests.get("https://golang.org/VERSION?m=text").text + ".linux-amd64.tar.gz" ) url = "https://dl.google.com/go/" + filename print("Go binary not found, installing the latest version...") print("Download url = %s" % url) print("Install directory = %s" % go_root) text = input("[Y/n] ?") if text.strip().lower() != "y" and text.strip().lower() != "yes": print("Aborting...") exit(1) 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)) 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): go_version = get_go_version(go_root) if version_geq(go_version, "1.18.0"): execute( "./go install git.fd.io/govpp.git/cmd/binapi-generator@" + c, cwd=go_root + "/bin", ) else: os.environ["GO111MODULE"] = "on" execute( "./go get git.fd.io/govpp.git/cmd/binapi-generator@" + c, cwd=go_root + "/bin", ) 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): 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", "--input-dir=" + json_dir] if output_dir: cmd += ["--output-dir=" + output_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 import_prefix: cmd.append("-import-prefix=" + import_prefix) if no_source: cmd.append("-no-source-path-info") _, out = execute(" ".join(cmd), cwd=go_path + "/bin") # 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_dir) def get_go_variables(): # go specific environment variables if "GOROOT" in os.environ: go_root = os.environ["GOROOT"] else: go_binary = shutil.which("go") if go_binary != "": go_binary_dir, _ = os.path.split(go_binary) go_root = os.path.join(go_binary_dir, "..") else: go_root = os.environ["HOME"] + "/.go" if "GOPATH" in os.environ: go_path = os.environ["GOPATH"] else: go_path = os.environ["HOME"] + "/go" return go_root, go_path def main(): # project root directory root = pathlib.Path(os.path.dirname(os.path.abspath(__file__))) vpp_dir = root.parent.parent.parent parser = argparse.ArgumentParser() parser.add_argument( "-govpp-commit", "--govpp-commit", help="GoVPP commit or branch ", default=DefaultGoVppCommit, type=str, ) parser.add_argument( "-output-dir", "--output-dir", help="output target directory for generated bindings", type=str, default=os.path.join(vpp_dir, "vppbinapi"), ) parser.add_argument( "-api-files", "--api-files", help="api files to generate (without commas)", nargs="+", type=str, default=[], ) parser.add_argument( "-import-prefix", "--import-prefix", help="prefix imports in the generated go code", default="", type=str, ) parser.add_argument( "-no-source-path-info", "--no-source-path-info", help="disable source path info in generated files", nargs="?", const=True, default=True, ) args = parser.parse_args() go_root, go_path = get_go_variables() install_golang(go_root) if not ( os.path.exists(go_root + "/bin/go") and os.path.isfile(go_root + "/bin/go") ): print(go_root + "/bin/go does not exist") sys.exit(1) install_binapi_gen(args.govpp_commit, go_root, go_path) generate_api( args.output_dir, str(vpp_dir), args.api_files, args.import_prefix, args.no_source_path_info, go_path, ) if __name__ == "__main__": main()