|
- #!/usr/bin/env python3
- # -*- coding: utf-8; mode: python; tab-width: 4; indent-tabs-mode: nil -*-
- # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 fileencoding=utf-8
- #
- # THIS FILE IS PART OF tools PROJECT
- #
- # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
- # Version 2, December 2004
- #
- # Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
- #
- # Everyone is permitted to copy and distribute verbatim or modified
- # copies of this license document, and changing it is allowed as long
- # as the name is changed.
- #
- # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
- # TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
- #
- # 0. You just DO WHAT THE FUCK YOU WANT TO.
- #
- # Copyright (C) 2021- donkey <anjingyu_ws@foxmail.com>
-
- import os
- import sys
- import json
- import argparse
- import shutil
- import re
- import uuid
- import tarfile
- import traceback
-
- import crayons
-
- from .__init__ import __version__
- from .tpl import Tpl
- from .adutil import (
- AdUtil,
- Logger,
- AdMakeHelper,
- AdGitHelper,
- NexusHelper,
- LibMerger,
- W,
- E,
- D,
- I,
- )
- from . import admakeos
-
-
- __author__ = ['"donkey" <anjingyu_ws@foxmail.com>']
-
- # Globals
- SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
- DEFAULT_BUILD_ROOT = ".build"
- DEFAULT_ADMAKE_NAME = ".admake"
- DEFAULT_MODULE_GROUP = "ns-module"
- DEFAULT_THIRDPARTY_NAME = "3rd-party"
- DEFAULT_VERSION_NAME = "master"
- SUPPORTED_OS = admakeos.get()
-
-
- def build_base_dir_name(args) -> str:
- return f"{args.os}_{args.platform}"
-
-
- def get_build_dir_name(args) -> str:
- base_dir = build_base_dir_name(args)
-
- build_dir = "{}{}{}".format(
- base_dir,
- "_release" if args.release else "_debug",
- "_test" if args.build_test else "",
- )
- return build_dir
-
-
- def get_build_info(args) -> tuple:
- macros = args.D
- args._build_info.append(f"Platform: {args.os}({args.platform})")
-
- for m in macros:
- if m.startswith("CMAKE_TOOLCHAIN_FILE"):
- if "=" in m: # -DCMAKE_TOOLCHAIN_FILE=xxx.cmake
- args._toolchain_file = m.split("=")[-1].strip("\"' ")
-
- build_dir = get_build_dir_name(args)
- macros_list = list()
- if args.build_test:
- macros_list.append("ADMAKE_BUILD_TEST")
-
- if args.release:
- macros_list.append("CMAKE_BUILD_TYPE=Release")
- else:
- macros_list.append("CMAKE_BUILD_TYPE=Debug")
-
- macros.extend(macros_list)
-
- return build_dir, macros
-
-
- def get_cmake_module_path(args) -> str:
- ret = []
-
- cmake_module_path_env = os.getenv("CMAKE_MODULE_PATH", "")
- if cmake_module_path_env:
- ret.append(cmake_module_path_env)
-
- ret.append(os.path.join(SCRIPT_DIR, "cmake").replace("\\", "/"))
-
- # The <project>/cmake
- pcmake = os.path.join(args.dir, "cmake").replace("\\", "/")
- if os.path.isdir(pcmake):
- ret.append(pcmake)
-
- return ";".join(ret)
-
-
- class DotDict(dict):
- """dot.notation access to dictionary attributes"""
-
- __getattr__ = dict.get
- __setattr__ = dict.__setitem__
- __delattr__ = dict.__delitem__
-
-
- def update_deps(args):
- admake_helper = AdMakeHelper(args.dir)
- adgit_helper = AdGitHelper()
-
- # The parent directory is the default workspace directory
- # You can change it via environment variable: ADMAKE_WS_DIR
- if os.getenv("ADMAKE_WS_DIR", ""):
- group_dir = os.getenv("ADMAKE_WS_DIR", "")
- else:
- group_dir = os.path.dirname(args.dir)
-
- projects, _ = admake_helper.modules()
- projs_num = len(projects)
-
- for p, head in zip(projects, AdUtil.gen_list_header(projs_num)):
- p_path = os.path.join(group_dir, p)
- print(crayons.cyan(f"{head} -> "), end="")
- if os.path.exists(p_path):
- with AdUtil.chdir(p_path):
- print(crayons.yellow("Updating project: {: <20} ".format(p)), end="")
- sys.stdout.flush()
- if adgit_helper.pull()[0] == 0:
- print(crayons.green("[o]"))
- # If successfully, remove the built artifacts
- fake_args = DotDict(
- {"dir": p_path, "platform": args.platform, "os": args.os}
- )
- clean_dist(fake_args, None)
- else:
- print(crayons.red("[x]"))
- else:
- print(crayons.cyan("Skipped, CMake will clone the project [o]"))
-
-
- def _default_docker_list(args) -> list:
- docker_is_required = False
- r = []
-
- has_colon = False
- image_name, image_tag = "", ""
- default_name = "admake-cross-{}-{}".format(args.os, args.platform)
-
- if args.docker:
- docker_is_required = True
- if ":" in args.docker:
- has_colon = True
- image_name, image_tag = args.docker.split(":")
- else:
- r.append(f"{args.docker}:latest")
- r.append(f"{default_name}:{args.docker}")
- else:
- # Check the corresponding image for the cross compile environment
- cross_env_key = "ADMAKE_CROSS_{}_{}".format(
- args.os.upper(), args.platform.upper()
- )
- cross_env = os.getenv(cross_env_key, "")
-
- if not cross_env:
- r.append(f"{default_name}:latest")
- else:
- if ":" in cross_env:
- has_colon = True
- image_name, image_tag = cross_env.split(":")
- else:
- r.append(f"{cross_env}:latest")
- r.append(f"{default_name}:{cross_env}")
-
- if has_colon:
- img = ""
- if image_name:
- img = image_name
- else:
- img = default_name
-
- if image_tag:
- img = f"{img}:{image_tag}"
- else:
- img = f"{img}:latest"
-
- r.append(img)
-
- return docker_is_required, r
-
-
- def check_docker_container(args) -> tuple:
- if args.os == "Windows":
- return None, None
-
- docker_is_required, docker_list = _default_docker_list(args)
-
- if not AdUtil.command_exists("docker"):
- if docker_is_required:
- raise RuntimeError("`docker` command is not found!")
- W("`docker` command is not found!")
- return None, None
-
- for image_name in docker_list:
- cmd_list = ["docker", "images", "-q", image_name]
- code, ret = AdUtil.run_command(cmd_list, True)
- if code == 0 and ret != "":
- # Use interactive mode, otherwise bash will never load
- # the `/etc/bash.bashrc` but we need it.
- return image_name, Tpl.docker_cmd(image_name, "/bin/bash -ic", args.dir)
-
- if docker_is_required:
- raise RuntimeError(f"Required image(s) `{docker_list}` are not found!")
-
- W(f"Required image(s) `{docker_list}` are not found!")
- return None, None
-
-
- class VersionSourceWrapper:
- DEFAULT_VERSION_FILE = "version"
-
- def __init__(self, args):
- self._args = args
- self._file_content = None
- self._file_path = None
-
- def __enter__(self):
- if not self._args.gen_version:
- return
- else:
- gh = AdGitHelper()
- file_name = os.path.join(
- "source", f"{VersionSourceWrapper.DEFAULT_VERSION_FILE}.cpp"
- )
- self._file_path = os.path.join(self._args.dir, file_name)
- if not os.path.isfile(self._file_path):
- W(
- f"The required version definition file is missing: <{self._file_path}>"
- )
- return
-
- version_info = gh.get_revision(cur_dir=self._args.dir)
- if not version_info:
- W(f"Failed to generate the version string for <{self._file_path}>")
- return
-
- if "short_commit" not in version_info:
- ver = f"{version_info['version']}-({version_info['commit']})"
- else:
- ver = f"{version_info['version']}-({version_info['short_commit']})"
-
- debug = "" if self._args.release else " Debug"
- self._file_content = open(self._file_path).read()
- m = re.sub(
- r"(.*)std::string\s+kVersionString(.*)",
- rf'\1std::string kVersionString = "{ver}{debug}";',
- self._file_content,
- )
- open(self._file_path, "w+").write(m)
-
- def __exit__(self, exc_type, exc_val, traceback):
- # Recover the file content
- if not self._args.verbose:
- if self._file_content:
- open(self._file_path, "w+").write(self._file_content)
- return True
-
-
- def build_or_rebuild(args, other_args):
- build_dir, macros = get_build_info(args)
- rebuild = True if args.sub_cmd == "rebuild" else False
- exit_code = 0
-
- with AdUtil.chdir(args.dir):
- build_dir = os.path.join(DEFAULT_BUILD_ROOT, build_dir)
-
- # Before all the other actions, do update if needed
- if args.update:
- D("Attempt to update the dependencies")
- update_deps(args)
-
- if rebuild and os.path.exists(build_dir):
- shutil.rmtree(build_dir)
- AdUtil.mkdirs(build_dir)
-
- macro = ""
- for m in macros:
- macro += " -D" + m
- if m.find("=") == -1:
- macro += "=1"
-
- platform_macro = '-DAD_OS="{}" -DAD_PLATFORM="{}"'.format(
- SUPPORTED_OS[args.os].name, args.platform
- )
-
- # XXX(anjingyu): A patch for arm-none, riscv-none
- if args.platform.endswith("-none"):
- if not args.macro:
- args.macro = list()
- args.macro.append("OS_NONE")
- # If specify the OS type, use it
- # otherwise use the host OS type as default.
- # If the the host OS type is not supported, ignore this macro.
- elif args.os and args.os in SUPPORTED_OS:
- if not args.macro:
- args.macro = list()
- args.macro.append(SUPPORTED_OS[args.os].macro)
-
- lang_macros = ""
- if args.macro:
- lang_macros = '-DCUSTOM_MACROS="-D{}"'.format(" -D".join(args.macro))
-
- abs_build_dir = os.path.join(args.dir, build_dir)
-
- cmake_cmd_list = [
- "cmake --no-warn-unused-cli",
- # NOTE(anjingyu): For new CMake, use the following options
- # is a better way, but for compatibility, use the old mode.
- # f'-S "{args.dir}"',
- # f'-B "{abs_build_dir}"',
- "../..",
- "-DCMAKE_VERBOSE_MAKEFILE:BOOL=ON" if args.verbose else "",
- "-DCMAKE_EXPORT_COMPILE_COMMANDS:BOOL=ON" if args.ccjson else "",
- # Always find cmake files and modules
- # in our own cmake path firstly
- f'-DCMAKE_MODULE_PATH="{get_cmake_module_path(args)}"',
- macro,
- f'-DBUILD_DIR="{abs_build_dir}"',
- platform_macro,
- lang_macros,
- SUPPORTED_OS[args.os].cmake_generator(args)
- if not args.cmake_generator
- else '-G "{}"'.format(args.cmake_generator),
- ]
-
- cmake_cmd_list += SUPPORTED_OS[args.os].cmake_args(args)
-
- # Output platform information
- print(crayons.cyan("; ".join(args._build_info)), end="")
- print(crayons.yellow(" [{}]".format("Release" if args.release else "Debug")))
-
- with AdUtil.chdir(build_dir):
- with VersionSourceWrapper(args):
- # Run `cmake`
- exit_code, _ = AdUtil.run_command(cmake_cmd_list)
- if exit_code == 0:
- # Run `make`
- exit_code = SUPPORTED_OS[args.os].run_make(args)
- else:
- E(f"Failed to run cmake command: ${exit_code}!")
-
- # For some editors, compile_commands.json is very useful,
- # so we attempt to create symbolic to compile_commands.json
- # if it exists.
- ccjson_path = os.path.join(build_dir, "compile_commands.json")
- if os.path.isfile(ccjson_path):
- # Delete the old symbolic link before create new one
- oldone = os.path.join(args.dir, "compile_commands.json")
- AdUtil.remove(oldone)
- AdUtil.mklink(ccjson_path, oldone)
- D(
- crayons.blue(
- f"Create symbolic link compile_commands.json -> {ccjson_path}"
- )
- )
-
- return exit_code
-
-
- def clean(args, other_args):
- """Cleanup all the build directories for current host"""
- build_dir = os.path.join(args.dir, DEFAULT_BUILD_ROOT)
- if not os.path.isdir(build_dir):
- return 0
-
- with AdUtil.chdir(build_dir):
- base_dir = build_base_dir_name(args)
- build_dirs = [
- f"{base_dir}_release",
- f"{base_dir}_debug",
- f"{base_dir}_debug_test",
- f"{base_dir}_release_test",
- ]
- for build_dir in build_dirs:
- if os.path.exists(build_dir):
- shutil.rmtree(build_dir)
- return 0
-
-
- def clean_dist(args, other_args):
- """It is a dangerous operation, will clear lib, bin, .build directories"""
- clean(args, other_args)
-
- dist_dir = os.path.join(args.dir, DEFAULT_BUILD_ROOT, args.os, args.platform)
- if not os.path.isdir(dist_dir):
- return 0
-
- with AdUtil.chdir(dist_dir):
- for built_dir in [
- os.path.join("lib", "Debug"),
- os.path.join("bin", "Debug"),
- os.path.join("lib", "Release"),
- os.path.join("bin", "Release"),
- ]:
- if os.path.exists(built_dir):
- shutil.rmtree(built_dir)
- return 0
-
-
- def generate_version(args, other_args):
- with AdUtil.chdir(args.dir):
- ver = "UNKNOWN"
- gh = AdGitHelper()
-
- version_info = gh.get_revision(cur_dir=args.dir)
- if not version_info:
- W(f"Failed to generate the version string")
- else:
- ver = json.dumps(version_info, indent=4)
-
- return ver
-
-
- def generate_version_info_file(args, other_args):
- file_path = os.path.join(
- args.dir, f"{VersionSourceWrapper.DEFAULT_VERSION_FILE}.json"
- )
- open(file_path, "w+").write(generate_version(args, other_args))
-
- return file_path
-
-
- def publish(args, other_args):
- nh = NexusHelper()
- # Create or update the version information
- with AdUtil.chdir(args.dir):
- version_file = generate_version_info_file(args, other_args)
- version_file = os.path.basename(version_file)
- los = os.path.join(DEFAULT_BUILD_ROOT, args.os, args.platform)
- sub_dir = os.path.join(args.os.lower(), args.platform)
- dir_list = [los, DEFAULT_THIRDPARTY_NAME, "cmake"]
- file_list = ["README.md", "RELEASE.md", version_file]
- pkg_dir = os.path.basename(args.dir)
- module_name = f"{pkg_dir}-{args.os}-{args.platform}-{args.pver}.tar.gz"
- files = []
-
- if args.pver != DEFAULT_VERSION_NAME:
- pkg_dir = os.path.join(pkg_dir, args.pver)
-
- if args.udf:
- fdict = {}
- for item in args.udf:
- if os.path.isdir(item):
- fs = AdUtil.get_files(item)
- bn = os.path.join(pkg_dir, sub_dir, os.path.basename(item))
- for subf in fs:
- fdict[subf] = subf.replace(item, bn)
- files += fs
- elif os.path.isfile(item):
- fdict[item] = os.path.join(pkg_dir, os.path.basename(item))
- files.append(item)
-
- for d in dir_list:
- if os.path.isdir(d):
- for f in AdUtil.get_files(d):
- fdict[f] = os.path.join(pkg_dir, f)
- files.append(f)
-
- for f in file_list:
- if os.path.isfile(f):
- fdict[f] = os.path.join(pkg_dir, os.path.basename(f))
- files.append(f)
-
- def frepl(f):
- return (f, fdict[f])
-
- tar_file = AdUtil.pack_tar(
- os.path.join(DEFAULT_BUILD_ROOT, module_name), files, frepl
- )
- else:
- if not os.path.isdir(los):
- W("Please generate the library firstly via admake build.")
- return -1
-
- # If current project contains the include directory,
- # always update the include directory that will be packed.
- if os.path.isdir("include"):
- dst_include = os.path.join(los, "include")
- if os.path.isdir(dst_include):
- shutil.rmtree(dst_include)
-
- shutil.copytree("include", dst_include)
-
- # Get all the files
- for d in dir_list:
- if os.path.isdir(d):
- files += AdUtil.get_files(d)
-
- for f in file_list:
- if os.path.isfile(f):
- files.append(f)
-
- def frepl(f):
- if f.startswith(DEFAULT_BUILD_ROOT):
- return (f, f.replace(DEFAULT_BUILD_ROOT, pkg_dir))
- return (f, os.path.join(pkg_dir, f))
-
- tar_file = AdUtil.pack_tar(
- os.path.join(DEFAULT_BUILD_ROOT, module_name), files, frepl
- )
-
- # Send to the server via HTTP POST
- r = nh.upload(DEFAULT_MODULE_GROUP, args.location, tar_file)
-
- if r:
- fstat = os.stat(tar_file)
- size_str = AdUtil.format_size(fstat.st_size)
-
- # Output the size of the package
- if args.verbose:
- print(
- crayons.cyan(f"{module_name}:"),
- crayons.green(f"{size_str} ({fstat.st_size} B)"),
- )
- else:
- print(crayons.cyan(f"{module_name}:"), crayons.green(size_str))
-
- # Delete the temporary TAR file
- AdUtil.remove(tar_file)
- return 0 if r else -1
-
-
- def install(args, other_args):
- base_dir = build_base_dir_name(args)
- target_dir = os.path.join(
- args.dir,
- DEFAULT_BUILD_ROOT,
- "{}_{}".format(base_dir, "release" if args.release else "debug"),
- )
-
- with AdUtil.chdir(target_dir):
- return AdUtil.run_command(["cmake", "--build .", "--target", "install"])[0]
-
-
- def package(args, other_args):
- base_dir = build_base_dir_name(args)
- target_dir = os.path.join(
- args.dir,
- DEFAULT_BUILD_ROOT,
- "{}_{}".format(base_dir, "release" if args.release else "debug"),
- )
- with AdUtil.chdir(target_dir):
- return AdUtil.run_command(["cmake", "--build .", "--target", "package"])[0]
-
-
- def fetch(args, other_args):
- if not other_args:
- return 0
-
- nh = NexusHelper()
- r = True
-
- # name convention: <pkg>-<os>-<arch>-<ver>.tar.gz
- for p in other_args:
- dpath = os.path.join(AdUtil.home(), DEFAULT_ADMAKE_NAME, args.location)
- pkg_name = f"{p}-{args.os}-{args.platform}-{args.pver}.tar.gz"
-
- if args.pver:
- if args.pver == DEFAULT_VERSION_NAME:
- pkg_path = os.path.join(dpath, p)
- else:
- pkg_path = os.path.join(dpath, p, args.pver)
-
- tmp_dir = ""
- version_info = "[{}] [{}({})]".format(
- crayons.yellow(args.pver),
- crayons.cyan(args.os),
- crayons.cyan(args.platform),
- )
-
- # Check the specific os/architecture
- pkg_path_arch = os.path.join(pkg_path, args.os, args.platform)
- if os.path.isdir(pkg_path_arch):
- print("Updating package {} {} ...".format(crayons.green(p), version_info))
- tmp_dir = os.path.join(
- os.path.dirname(pkg_path), ".{}".format(str(uuid.uuid4()))
- )
- os.rename(pkg_path_arch, tmp_dir)
- else:
- print("Fetching package {} {} ...".format(crayons.green(p), version_info))
-
- r = nh.fetch(
- DEFAULT_MODULE_GROUP, args.location, pkg_name, os.path.join(dpath, pkg_name)
- )
-
- if os.path.isdir(tmp_dir):
- if r:
- AdUtil.remove(tmp_dir)
- else:
- os.rename(tmp_dir, pkg_path_arch)
-
- return 0 if r else -1
-
-
- def import_package(args, other_args):
- dpath = os.path.join(AdUtil.home(), DEFAULT_ADMAKE_NAME, args.location)
- all_input = []
-
- for fs in args.input:
- all_input.extend(fs)
-
- input_num = len(all_input)
-
- for fpath, head in zip(all_input, AdUtil.gen_list_header(input_num)):
- fpath = os.path.abspath(fpath)
- try:
- tarf = tarfile.open(name=fpath, mode="r:gz")
- tarf.extractall(path=dpath)
- tarf.close()
- except tarfile.ReadError:
- W(f"Unable to extract file: {fpath}!")
- return -1
- I(f"{head} -> {os.path.basename(fpath)}")
- return 0
-
-
- def export_package(args, other_args):
- """Export source code of current project, and all the 3rd-party libraries as binary dependencies."""
- admake_helper = AdMakeHelper(args.dir)
-
- # The parent directory is the default workspace directory
- # You can change it via environment variable: ADMAKE_WS_DIR
- if os.getenv("ADMAKE_WS_DIR", ""):
- group_dir = os.getenv("ADMAKE_WS_DIR", "")
- else:
- group_dir = os.path.dirname(args.dir)
-
- projects, third_mods = admake_helper.modules()
-
- if len(projects) > 0:
- print(crayons.cyan("Modules:"))
- for p, head in zip(projects, AdUtil.gen_list_header(len(projects))):
- p_path = os.path.join(group_dir, p)
- print(crayons.cyan(f"{head} -> "), end="")
- print(crayons.yellow("{: <25} ".format(p)), end="")
- sys.stdout.flush()
- if os.path.exists(p_path):
- print(crayons.green("[o]"))
- else:
- print(crayons.red("[x]"))
- print("")
-
- if len(third_mods) > 0:
- third_dir = os.path.join(
- AdUtil.home(),
- DEFAULT_ADMAKE_NAME,
- DEFAULT_THIRDPARTY_NAME)
-
- print(crayons.cyan("Thirdparty modules:"))
- for p, head in zip(third_mods, AdUtil.gen_list_header(len(third_mods))):
- p_path = os.path.join(third_dir, p)
- print(crayons.cyan(f"{head} -> "), end="")
- print(crayons.magenta("{: <25} ".format(p)), end="")
- sys.stdout.flush()
- if os.path.exists(p_path):
- print(crayons.green("[o]"))
- else:
- print(crayons.red("[x]"))
-
- return 0
-
-
- def lcm_gen(args, other_args):
- if not args.input or not args.output:
- raise RuntimeError("Required arguments are missing, -i and -o are required!")
-
- lcmgen = args.tool
- output = os.path.abspath(args.output)
-
- all_input = list()
- for sub_dir in args.input:
- all_input.extend(sub_dir)
-
- file_list = list()
- for lcmpath in all_input:
- file_list.extend(AdUtil.get_files(os.path.abspath(lcmpath), ".lcm"))
-
- home = AdUtil.home()
- arch = AdUtil.arch()
-
- if not os.path.isfile(lcmgen) and not AdUtil.command_exists(lcmgen):
- lcm_bin_dir = os.path.join(
- home,
- DEFAULT_ADMAKE_NAME,
- DEFAULT_THIRDPARTY_NAME,
- "lcm",
- AdUtil.SYSTEM.lower(),
- arch,
- "bin",
- "Release",
- )
- if not os.path.isdir(lcm_bin_dir):
- raise RuntimeError(
- "Could not find lcm-gen, please install lcm or prepare the 3rd-party lcm package first!"
- )
- os.environ["PATH"] = os.pathsep.join([os.environ["PATH"], lcm_bin_dir])
- lcmgen = "lcm-gen"
-
- # On Windows, we should append the dependent library path to PATH
- # or ensure that the dependencies are in the CWD.
- # Here we choose the second way.
- work_dir = "."
- if AdUtil.SYSTEM == "Windows":
- win_lib_path = os.path.join(
- os.path.dirname(AdUtil.home()),
- DEFAULT_ADMAKE_NAME,
- DEFAULT_THIRDPARTY_NAME,
- "lcm",
- arch,
- DEFAULT_THIRDPARTY_NAME,
- "glib2.0",
- "bin",
- )
-
- os.environ["PATH"] = os.pathsep.join([os.environ["PATH"], win_lib_path])
-
- # Make sure the output directory is existent
- if not os.path.exists(output):
- os.makedirs(output)
-
- lcm_args = []
-
- if "--" in other_args:
- idx = other_args.index("--")
- if idx < len(other_args):
- lcm_args.extend(other_args[idx + 1 :])
-
- with AdUtil.chdir(work_dir):
- for f in file_list:
- if not lcm_args:
- code, result = AdUtil.run_command(
- [lcmgen, "--lazy", "-x", f, "--cpp-hpath", output], need_output=True
- )
- else:
- cmds = [lcmgen]
- cmds.extend(lcm_args)
- cmds.append(f)
- with AdUtil.chdir(output):
- code, result = AdUtil.run_command(cmds, need_output=True)
-
- if code != 0:
- raise Exception(result)
-
- if not lcm_args:
- extension = ".hpp"
- if args.rename:
- extension = ".h"
-
- msg_file_name_dict = dict()
- msg_file_list = list()
-
- for root, _, files in os.walk(output):
- for fname in files:
- if fname.endswith(".hpp"):
- msg_file_list.append(os.path.join(root, fname))
- file_name = os.path.splitext(fname)[0]
- if args.rename:
- msg_file_name_dict[fname] = f"{file_name}{extension}"
- msg_file_name_dict[
- f"(void*){file_name}::getHash"
- ] = f"&{file_name}::getHash"
-
- for msg_file in msg_file_list:
- file_name = os.path.splitext(msg_file)[0]
- msg_file_new = f"{file_name}{extension}"
-
- if args.rename:
- if os.path.isfile(msg_file_new):
- os.remove(msg_file_new)
- os.rename(msg_file, msg_file_new)
-
- content = open(msg_file_new, encoding="utf-8").read()
- for k, v in msg_file_name_dict.items():
- content = content.replace(k, v)
- open(msg_file_new, "w", encoding="utf-8").write(content)
-
- return 0
-
-
- def merge_lib(args, other_args):
- output_file = None
- if args.output:
- output_file = args.output
-
- if not output_file:
- raise RuntimeError("Required arguments are missing, -o is required!")
-
- liba = LibMerger()
-
- if args.directory:
- for sub_dir in args.directory:
- liba.add_directories(sub_dir)
-
- if args.file:
- for fp in args.file:
- liba.add_libraries(fp)
-
- if args.tool:
- liba.set_ar(args.tool)
-
- if args.base:
- liba.set_base_archive(os.path.abspath(args.base))
-
- try:
- liba.merge(output_file)
- return 0
- except Exception:
- traceback.print_exc()
-
- return -1
-
-
- def _should_ignore_exe(bname, ext) -> bool:
- if not bname.endswith(ext):
- return True
-
- for ignore_suffix in [f"_test{ext}", f"_bench{ext}"]:
- if bname.endswith(ignore_suffix):
- return True
-
- return False
-
-
- def run_exe(args, other_args):
- bin_dir = os.path.join(
- args.dir,
- DEFAULT_BUILD_ROOT,
- args.os,
- args.platform,
- "bin",
- "Release" if args.release else "Debug",
- )
-
- if not os.path.isdir(bin_dir):
- W(
- "The directory {} is not existent, please BUILD EXEs first.".format(
- crayons.cyan(bin_dir)
- )
- )
- return 1
-
- ext = ".exe" if AdUtil.SYSTEM == "Windows" else ""
-
- exit_code = 0
-
- suffix = ""
- exes = []
-
- wd = args.dir
- for twd in ["test-data", "test_data"]:
- tmp_wd = os.path.join(args.dir, twd)
- if os.path.isdir(tmp_wd):
- wd = tmp_wd
- break
-
- if args.name:
- if args.sub_cmd == "test":
- suffix = f"_test{ext}"
- elif args.sub_cmd == "bench":
- suffix = f"_bench{ext}"
-
- exe_path = os.path.join(bin_dir, f"{args.name}{suffix}")
-
- if os.path.isfile(exe_path) and os.access(exe_path, os.X_OK):
- exes.append((os.path.basename(exe_path), exe_path))
- else:
- if args.sub_cmd == "test":
- suffix = f"_test{ext}"
- elif args.sub_cmd == "bench":
- suffix = f"_bench{ext}"
-
- # Findout the executable
- for exe in os.listdir(bin_dir):
- exe_path = os.path.join(bin_dir, exe)
- if os.path.isfile(exe_path) and os.access(exe_path, os.X_OK):
- if suffix:
- if exe_path.endswith(suffix):
- exes.append((os.path.basename(exe_path), exe_path))
- else:
- bname = os.path.basename(exe_path)
- if _should_ignore_exe(bname, ext):
- continue
- exes.append((bname, exe_path))
-
- exe_args = []
-
- if len(exes) > 0:
- flag = False
- if "--" in other_args:
- idx = other_args.index("--")
- if idx < len(other_args):
- exe_args.extend(other_args[idx + 1 :])
-
- ran = False
- with AdUtil.chdir(wd):
- for exe in exes:
- print(
- "Run {} in {}{}".format(
- crayons.green(exe[0]),
- crayons.cyan(wd),
- ":"
- if not exe_args
- else " with arguments: {}".format(
- crayons.yellow(" ".join(exe_args))
- ),
- )
- )
- exit_code |= os.system("{} {}".format(exe[1], " ".join(exe_args)))
- ran = True
-
- if not ran:
- W("Can not find exes in {}".format(crayons.cyan(bin_dir)))
-
- return exit_code
-
-
- def config(args, other_args):
- if args.all:
- print(crayons.cyan("----------------------------------------"))
- # Environment variables
- envs = ["ADMAKE_WS_DIR", "NEXUS_HOST", "NEXUS_USERNAME", "NEXUS_PASSWORD"]
- print(crayons.yellow("[Environment Variables]"))
- for e in envs:
- print(f"{e}:", os.getenv(e, ""))
- print(crayons.cyan("----------------------------------------"))
-
- # CMAKE_MODULE_PATH
- print(crayons.yellow("[CMAKE_MODULE_PATH]"))
- print(os.path.join(SCRIPT_DIR, "cmake").replace("\\", "/"))
- print(crayons.cyan("----------------------------------------"))
-
- # Thirdparty path
- print(crayons.yellow("[Thirdparty Path]"))
- print(os.path.join(AdUtil.home(), DEFAULT_ADMAKE_NAME, DEFAULT_THIRDPARTY_NAME))
- print(crayons.cyan("----------------------------------------"))
-
- return 0
-
- if args.cmake:
- print(os.path.join(SCRIPT_DIR, "cmake").replace("\\", "/"))
-
- if args.library:
- print(os.path.join(AdUtil.home(), DEFAULT_ADMAKE_NAME, DEFAULT_THIRDPARTY_NAME))
-
- if args.docker:
- if ":" == args.docker:
- os_name = AdUtil.SYSTEM.lower()
- arch = AdUtil.arch()
- args.docker = f"admake-cross-{os_name}-{arch}:latest"
-
- print(" ".join(Tpl.docker_cmd(args.docker)))
-
- if args.prelude:
- print(Tpl.cmake_prelude)
-
- return 0
-
-
- SUB_CMDS = [
- ["build", "Incrementally build the project", build_or_rebuild],
- ["rebuild", "Rebuild the project", build_or_rebuild],
- ["run", "Run all the executable in this project", run_exe],
- ["test", "Run all the executable with suffix: _test", run_exe],
- ["bench", "Run all the executable with suffix: _bench", run_exe],
- ["clean", "Clean the project", clean],
- ["publish", "Public the specify project to server", publish],
- ["fetch", "Fetch the specified 3rdparty package", fetch],
- ["export", "Export current project and deps as a package", export_package],
- ["version", "Generate the version string for this project", generate_version],
- [
- "version_info",
- "Generate the version information for this project",
- generate_version_info_file,
- ],
- ["install", "Attempt install this project if the target is existent", install],
- [
- "package",
- "Attempt make a deb package for this project if the target is existent",
- package,
- ],
- ]
-
-
- def get_admake_arg_parser(extend_parser=None):
- # Get typical usages
- typical_usages = (
- "Typical usage:\n"
- "\t admake build\n"
- "\t admake rebulid\n"
- "\t admake clean\n"
- "\t admake publish\n"
- "\t admake version\n"
- )
- for k in SUPPORTED_OS:
- typical_usages += SUPPORTED_OS[k].get_typical_usage()
-
- # Get suported architectures
- typical_usages += "\nSupported Architectures:\n"
- for k in SUPPORTED_OS:
- typical_usages += "\t %-12s : " % (k)
- first = True
- for arch in SUPPORTED_OS[k].supported_archs():
- if first:
- typical_usages += arch
- first = False
-
- else:
- typical_usages += f", {arch}"
-
- typical_usages += ".\n"
-
- # Create parser
- parser = argparse.ArgumentParser(
- formatter_class=argparse.RawDescriptionHelpFormatter, epilog=typical_usages
- )
- parser.add_argument("-v", "--version", action="version", version="{}".format(__version__))
-
- parent_parser = argparse.ArgumentParser(add_help=False)
-
- subparsers = parser.add_subparsers(dest="sub_cmd", help="Sub-command help")
-
- parents = (
- [parent_parser, extend_parser] if extend_parser is not None else [parent_parser]
- )
-
- # Genetate supported architectures
- supported_archs = []
- for k in SUPPORTED_OS:
- supported_archs = list(
- set(supported_archs).union(set(SUPPORTED_OS[k].supported_archs()))
- )
-
- for c in SUB_CMDS:
- sub_parser = subparsers.add_parser(c[0], help=c[1], parents=parents)
- sub_parser.set_defaults(func=c[2])
- sub_parser.add_argument(
- "-d",
- "--dir",
- default=".",
- help="Specify the directory contains the CMakeLists.txt",
- )
- sub_parser.add_argument(
- "-p",
- "--platform",
- choices=supported_archs,
- default="",
- help="Default to AD_PLATFORM or x64/x86 if it doesn't exist.",
- )
- sub_parser.add_argument(
- "-o",
- "--os",
- default=AdUtil.SYSTEM.lower(),
- choices=SUPPORTED_OS.keys(),
- help="Target operating system. Supported operating systems: {}".format(
- ", ".join(list(SUPPORTED_OS.keys()))
- ),
- )
- sub_parser.add_argument(
- "-c",
- "--docker",
- const=":",
- default="",
- type=str,
- nargs="?",
- help="Specify the docker container that you want to run in, <name>:<tag>, or <name>, or <tag>",
- )
- sub_parser.add_argument(
- "-t",
- "--build-test",
- action="store_true",
- help="Build the test project with the static library.",
- )
- sub_parser.add_argument(
- "-D",
- default=[],
- action="append",
- help="-D argument pass to CMake, e.g: -DENABLE_LOG -DTEST_BUILD=1",
- )
- sub_parser.add_argument(
- "-r", "--release", action="store_true", help="Build release edition"
- )
- sub_parser.add_argument(
- "-m",
- "--macro",
- action="append",
- help="C/C++ macros pass to compilers, e.g: -m BUILD_UNIT_TEST",
- )
- sub_parser.add_argument(
- "-v",
- "--verbose",
- action="store_true",
- default=False,
- help="Output the verbose information in the building progress",
- )
- sub_parser.add_argument(
- "-l",
- "--location",
- default=DEFAULT_THIRDPARTY_NAME,
- help="Publish the module to the specific server",
- )
- sub_parser.add_argument(
- "-u",
- "--update",
- action="store_true",
- default=False,
- help="Update the dependencies automatically",
- )
- sub_parser.add_argument(
- "-g",
- "--gen-version",
- action="store_true",
- default=False,
- help="Attempt to generate version information",
- )
- sub_parser.add_argument(
- "--ccjson",
- action="store_true",
- help="Generate compile_commands.json in the project root directory.",
- )
- sub_parser.add_argument(
- "--cmake-generator",
- default="",
- help="Specify the cmake generator, used by automatical derivation",
- )
- sub_parser.add_argument(
- "--pver",
- default=DEFAULT_VERSION_NAME,
- help="Specify the version of specified packages, default: master",
- )
-
- # for run command
- if c[0] in ["run", "test", "bench"]:
- sub_parser.add_argument(
- "-n",
- "--name",
- default="",
- help="Specify the name of the executable",
- )
-
- sub_parser.add_argument(
- "--workdir",
- default="",
- help="Specify the working directory, default: test-data or test_data",
- )
-
- if c[0] == "publish":
- sub_parser.add_argument(
- "--udf",
- default=[],
- action="append",
- help="User defined items(dirs, files)",
- )
-
- for k in SUPPORTED_OS:
- SUPPORTED_OS[k].update_argument(sub_parser)
-
- # A patch for import
- sub_parser = subparsers.add_parser(
- "import", help="Import 3rd-party libraries from packages", parents=parents
- )
- sub_parser.set_defaults(func=import_package)
- sub_parser.add_argument(
- "-i",
- "--input",
- nargs="+",
- action="append",
- help="The path of packages you want to import",
- )
- sub_parser.add_argument(
- "-l",
- "--location",
- default=DEFAULT_THIRDPARTY_NAME,
- help=f"Specify the location that packages will be imported, default: {DEFAULT_THIRDPARTY_NAME}",
- )
-
- # A patch for LCM
- sub_parser = subparsers.add_parser(
- "lcm",
- help="Build lcm files and generate corresponding C++ files",
- parents=parents,
- )
- sub_parser.set_defaults(func=lcm_gen)
- sub_parser.add_argument(
- "-i",
- "--input",
- nargs="*",
- action="append",
- help="Specify the input directory which contains the message files",
- )
- sub_parser.add_argument("-o", "--output", help="Specify the output directory")
- sub_parser.add_argument(
- "-t", "--tool", help="Specify the tool path", default="lcm-gen"
- )
- sub_parser.add_argument(
- "-r",
- "--rename",
- action="store_true",
- default=False,
- help="Rename the generated C++ files with .h extension.",
- )
-
- # static library merger
- sub_parser = subparsers.add_parser(
- "merge",
- help="Megre static libraries to a fat one on Linux.",
- parents=parents,
- )
- sub_parser.set_defaults(func=merge_lib)
- sub_parser.add_argument(
- "-t",
- "--tool",
- default="ar",
- help="Spedify the AR tool path, default: ar, REQUIRED",
- )
- sub_parser.add_argument("-o", "--output", help="Path of output file REQUIRED")
- sub_parser.add_argument(
- "-b",
- "--base",
- help="Path of the base archive file, which never need to be unpacked.",
- )
- sub_parser.add_argument(
- "-f", "--file", nargs="*", action="append", help="The specified libraries"
- )
- sub_parser.add_argument(
- "-d",
- "--directory",
- nargs="*",
- action="append",
- help="Specify the directory contains library files",
- )
-
- # config command
- sub_parser = subparsers.add_parser(
- "config",
- help="List or set configurations",
- parents=parents,
- )
- sub_parser.set_defaults(func=config)
- sub_parser.add_argument(
- "-a",
- "--all",
- action="store_true",
- help="List current configurations of adtools.",
- )
- sub_parser.add_argument(
- "-c",
- "--cmake",
- action="store_true",
- help="List the CMAKE_MODULE_PATH appended by admake.",
- )
- sub_parser.add_argument(
- "-l",
- "--library",
- action="store_true",
- help="List the path of 3rd-party library repository.",
- )
- sub_parser.add_argument(
- "-d",
- "--docker",
- const=":",
- default="",
- type=str,
- nargs="?",
- help="Specify the docker container that you want to run in, <name>:<tag>",
- )
- sub_parser.add_argument(
- "-p",
- "--prelude",
- action="store_true",
- help="Output the prelude script for pure CMake or IDE.",
- )
-
- return parser
-
-
- def check_cmake_file(args):
- with AdUtil.chdir(args.dir):
- if not os.path.isfile("CMakeLists.txt"):
- E(f"Required CMakeLists.txt is not existent in path: {args.dir}!")
- sys.exit(1)
-
- if not os.path.isdir(DEFAULT_BUILD_ROOT):
- AdUtil.mkdirs(DEFAULT_BUILD_ROOT)
-
-
- def check_cross_build(args):
- if args.sub_cmd not in ["build", "rebuild"]:
- return False
- return AdUtil.SYSTEM.lower() != args.os or AdUtil.arch() != args.platform
-
-
- def run():
- parser = get_admake_arg_parser()
- if len(sys.argv) <= 1:
- parser.print_help()
- return 0, ""
-
- args, other_args = parser.parse_known_args()
-
- if args.sub_cmd in ["lcm", "import", "merge", "config"]:
- return args.func(args, other_args), args.sub_cmd
-
- if "os" not in args:
- raise RuntimeError("Unsupported OS!")
-
- args.os = args.os.lower()
- args.dir = os.path.abspath(args.dir)
-
- if args.verbose:
- Logger.instance().enable()
-
- if not args.platform:
- args.platform = SUPPORTED_OS[args.os].guess_arch(args)
-
- args.platform = args.platform.lower()
-
- # Never check CMakeLists.txt for the following subcommands
- if args.sub_cmd in ["fetch", "publish"]:
- return args.func(args, other_args), args.sub_cmd
-
- check_cmake_file(args)
-
- docker_wrapper = None
- # Attempt to start docker container automatically when build for cross build
- if check_cross_build(args) or args.docker:
- image_name, docker_wrapper = check_docker_container(args)
- if docker_wrapper:
- # Filter the -p option
- arg_list = list()
- argv_len = len(sys.argv[1:])
- i = 1
- while i <= argv_len:
- if sys.argv[i] == "-c" or sys.argv[i] == "--docker":
- if i + 1 <= argv_len:
- if not sys.argv[i + 1].startswith("-"):
- i += 1
- else:
- if sys.argv[i][0] == "-":
- arg_list.append(sys.argv[i])
- else:
- # NOTE: Append double quotation marks for parameters,
- # Maybe there are some spaces in it.
- arg_list.append('\\"' + sys.argv[i] + '\\"')
- i += 1
- command = '{} "{} {}"'.format(
- " ".join(docker_wrapper), "admake", " ".join(arg_list)
- )
- W(f"Will run in docker: {image_name}")
- D(f"Running Command: {command}")
- return os.system(command), args.sub_cmd
-
- args._build_info = list()
- args._toolchain_file = ""
-
- if not docker_wrapper:
- return args.func(args, other_args), args.sub_cmd
-
-
- def main():
- ret = 0
- msg = ""
- with AdUtil.time_consumed():
- ret, msg = run()
-
- if ret != 0:
- E(f"Failed to run command: {msg}, {ret}")
-
- return ret
-
-
- if __name__ == "__main__":
- sys.exit(main())
|