import os import sys import argparse import subprocess from pathlib import Path # {{{1 misc -------------------------------------------------------------------- # Disables output of ANSI codes if the terminal doesn't support them if os.name == "nt": from ctypes import windll, c_int, byref stdout_handle = windll.kernel32.GetStdHandle(c_int(-11)) mode = c_int(0) windll.kernel32.GetConsoleMode(c_int(stdout_handle), byref(mode)) ansi_on = (mode.value & 4) != 0 else: ansi_on = True #------------------------------------------------------------------------------- def _header(*args, ansi=96): if ansi_on: print(f"\x1b[{ansi}m##", *args, end="") print("\x1b[0m") else: print("\n##", *args) #------------------------------------------------------------------------------- def _run_checked(cmd, *args, **kwargs): _header(cmd, *args, ansi=97) ret = subprocess.run((cmd, *args), **kwargs, bufsize=0) if ret.returncode: raise RuntimeError("Failed running " + str(cmd)) #------------------------------------------------------------------------------- def _get_ip(): import socket s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) try: s.connect(("172.31.255.255", 1)) return s.getsockname()[0] except: return "127.0.0.1" finally: s.close() #------------------------------------------------------------------------------- def _find_binary(name): name += ".exe" if os.name == "nt" else "" for prefix in os.getenv("PATH", "").split(os.pathsep): path = Path(prefix) / name if path.is_file(): return path raise EnvironmentError(f"Unable to find '{name}' in the path") #------------------------------------------------------------------------------- class _DaemonAutoKill(object): def __del__(self): # Roundabout way to avoid orphaned git-daemon processes if os.name == "nt": os.system("taskkill /f /im git-daemon.exe") # {{{1 local ------------------------------------------------------------------- #------------------------------------------------------------------------------- def _local(args): # Parse arguments desc = "Build Zen on a remote host" parser = argparse.ArgumentParser(description=desc) parser.add_argument("remotehost", help="") parser.add_argument("action", default="build", nargs="?", help="") parser.add_argument("--commit", default=None, help="Commit to act on") parser.add_argument("--keyfile", default=None, help="SSH key file") parser.add_argument("--gitport", default=None, help="Use an exist git daemon at the given port") parser.add_argument("--outdir", default=None, help="Put .zip bundles here") args = parser.parse_args(args) # Find the binaries we'll need _header("Finding tools") git_bin = _find_binary("git") print(f"Using git from '{git_bin.name}' from '{git_bin.parent}'") def find_git_tool(git_bin, tool_name): print(f"Locating {tool_name}...") tool_suffix = "usr/bin/" + tool_name tool_suffix += ".exe" if os.name == "nt" else "" for parent in git_bin.parents: tool_path = parent / tool_suffix if tool_path.is_file(): return tool_path return _find_binary(tool_name) ssh_bin = find_git_tool(git_bin, "ssh") scp_bin = find_git_tool(git_bin, "scp") print(f"Using '{ssh_bin.name}' from '{ssh_bin.parent}'") print(f"Using '{scp_bin.name}' from '{scp_bin.parent}'") # Find the Zen repository root for parent in Path(__file__).resolve().parents: if (parent / ".git").is_dir(): zen_dir = parent break; else: raise EnvironmentError("Unable to find '.git/' directory") _header("Validating remote host and credentials") # Validate key file. Git's SSH uses OpenSSL which needs UNIX line-endings if args.keyfile: """ with open(args.keyfile, "rt") as key_file: lines = [x.strip() for x in key_file] with open(args.keyfile, "wb") as key_file: for line in lines: key_file.write(line.encode() + b"\n") """ identity = ("-i", args.keyfile) else: identity = () # Validate remote host host = args.remotehost if host == "linux": host = os.getenv("ZEN_REMOTE_HOST_LINUX", "arn-lin-12345") if host == "mac": host = os.getenv("ZEN_REMOTE_HOST_MAC", "imacpro-arn.local") """ keygen_bin = find_git_tool(git_bin, "ssh-keygen") print(f"Using '{keygen_bin.name}' from '{keygen_bin.parent}'") known_host = subprocess.run((keygen_bin, "-F", host)).returncode if not known_host: print("Adding", host, "as a known host") print("ANSWER 'yes'") known_host = subprocess.run((ssh_bin, *identity, "zenbuild@" + host, "uname -a")).returncode raise IndexError """ host = "zenbuild@" + host print(f"Using host '{host}'") # Start a git daemon to use as a transfer mechanism _header("Git daemon") daemon_port = args.gitport if not daemon_port: daemon_port = 4493 print("Starting out own one up") print("Port:", daemon_port) print("Base-path: ", zen_dir) print("Host: ", _get_ip()) daemon = subprocess.Popen( ( git_bin, "daemon", "--port=" + str(daemon_port), "--export-all", "--reuseaddr", "--verbose", "--informative-errors", "--base-path=" + str(zen_dir) ), #stdout = daemon_log, stderr = subprocess.STDOUT ) daemon_killer = _DaemonAutoKill() else: print("Using existing instance") print("Port:", daemon_port) # Run this script on the remote machine _header("Running SSH") commit = args.commit if args.commit else "origin/main" remote_zen_dir = "%s_%s_%s" % (os.getlogin(), _get_ip(), str(daemon_port)) print(f"Using zen '~/{remote_zen_dir}'") print(f"Running {__file__} remotely") with open(__file__, "rt") as self_file: _run_checked( ssh_bin, *identity, "-tA", host, f"python3 -u - !remote {_get_ip()}:{daemon_port} '{remote_zen_dir}' {commit} '{args.action}'", stdin=self_file) # If we're bundling, collect zip files from the remote machine if args.action == "bundle": build_dir = Path(args.outdir) or (zen_dir / "build") build_dir.mkdir(exist_ok=True) scp_args = (*identity, host + f":zen/{remote_zen_dir}/build/*.zip", build_dir) _run_checked("scp", *scp_args) # {{{1 remote ------------------------------------------------------------------ #------------------------------------------------------------------------------- def _remote(args): # Parse arguments desc = "Build Zen on a remote host" parser = argparse.ArgumentParser(description=desc) parser.add_argument("gitaddr", help="Host's Git daemon address") parser.add_argument("reponame", help="Repository name clone into and work in") parser.add_argument("commit", help="Zen commit to operate on") parser.add_argument("action", help="The action to do") args = parser.parse_args(args) # Homeward bound and out zen_dir = Path().home() / "zen" os.chdir(zen_dir) # Mutual exclusion """ lock_path = zen_dir / "../.remote_lock" try: lock_file = open(lock_path, "xb") except: raise RuntimeError("Failed to lock", lock_path) """ # Check for a clone, create it, chdir to it _header("REMOTE:", f"Clone/pull from {args.gitaddr}") clone_dir = zen_dir / args.reponame if not clone_dir.is_dir(): _run_checked("git", "clone", f"git://{args.gitaddr}/", clone_dir) os.chdir(clone_dir) _run_checked("git", "clean", "-fd") _run_checked("git", "fetch", "origin") _run_checked("git", "checkout", args.commit) _header("REMOTE:", f"Performing action '{args.action}'") # Find xmake xmake_bin = max(x for x in (zen_dir / "xmake").glob("*")) xmake_bin /= "usr/local/bin/xmake" # Run xmake xmake_env = {} xmake_env["VCPKG_ROOT"] = zen_dir / "vcpkg" if sys.platform == "linux": xmake_env["CXX"] = "g++-11" print("xmake environment:") for key, value in xmake_env.items(): print(" ", key, "=", value) xmake_env.update(os.environ) def run_xmake(*args): print("starting xmake...", end="\r") _run_checked(xmake_bin, args[0], "--yes", *args[1:], env=xmake_env) if args.action.startswith("build"): mode = "debug" if args.action == "build.debug" else "release" run_xmake("config", "--mode=" + mode) run_xmake("build") elif args.action == "bundle": run_xmake("bundle") elif args.action == "test": run_xmake("config", "--mode=debug") run_xmake("test") elif args.action == "clean": _run_checked("git", "reset") _run_checked("git", "checkout", ".") _run_checked("git", "clean", "-xdf") # {{{1 entry ------------------------------------------------------------------- if __name__ == "__main__": if "!remote" in sys.argv[1:2]: ret = _remote(sys.argv[2:]) raise SystemExit(ret) ret = _local(sys.argv[1:]) raise SystemExit(ret) # vim: expandtab foldlevel=1 foldmethod=marker