Files
M5Stack_Linux_Libs/tools/cmake/project.py
T
dianjixz 49df8c5def [init]
2024-04-10 12:21:46 +08:00

347 lines
14 KiB
Python

#
# @file from https://github.com/Neutree/c_cpp_project_framework
# @author neucrack
# @license MIT
#
import argparse
import os, sys, time, re, shutil
import subprocess
from multiprocessing import cpu_count
if not os.path.exists("CMakeLists.txt") or not os.path.exists("main"):
print("please run me at project folder!")
exit(1)
try:
# sdk_path from project's project.py
sdk_path = sdk_path
except Exception:
sdk_path = os.path.abspath("../../")
project_path = sys.path[0]
project_name = ""
project_cmake_path = project_path+"/CMakeLists.txt"
project_cmake_content = ""
with open(project_cmake_path) as f:
project_cmake_content = f.read()
match = re.findall(r"set\(PROJECT_NAME (.*)\)", project_cmake_content)
if len(match) != 0:
project_name = match[0]
if("project_dir_name" in project_name):
project_name = os.path.basename(project_path)
print("-- project name: {}".format(project_name))
if project_name == "":
print("[ERROR] Can not find project name in {}, not set(PROJECT_NAME {})".format(project_cmake_path, "${project_name}"))
exit(1)
# find extra tools
tools_dir = os.path.join(sdk_path, "tools")
sys.path.insert(1, tools_dir)
# find all .py files in tools dir
extra_tools_names = []
parsers = []
extra_tools = {}
for name in os.listdir(tools_dir):
if name.endswith(".py"):
extra_tools_names.append(name[:-3])
# import all tools
for name in extra_tools_names:
# import from tool file
tool = __import__(name)
hasattr(tool, "parser") and parsers.append(tool.parser)
if hasattr(tool, "cmds"):
extra_tools[tool.__name__] = {
"tool": tool,
"cmds": tool.cmds,
}
project_parser = argparse.ArgumentParser(description='build tool, e.g. `python project.py build`', prog="project.py", parents=parsers)
project_parser.add_argument('--toolchain',
help='toolchain path ( absolute path )',
metavar='PATH',
default="")
project_parser.add_argument('--toolchain-prefix',
help='toolchain prefix(e.g. mips-elf-',
metavar='PREFIX',
default="")
project_parser.add_argument('--config_file',
help='config file path, e.g. config_defaultd.mk',
metavar='PATH',
default="{}/config_defaults.mk".format(project_path))
project_parser.add_argument('--verbose',
help='for build command, execute `cmake -build . --verbose` to compile',
action="store_true",
default=False)
project_parser.add_argument('-G', '--generator', default="", help="project type to generate, supported type on your platform see `cmake --help`")
project_parser.add_argument('--release', action="store_true", default=False, help="release mode, default is debug mode")
project_parser.add_argument('--build-type', default=None, help="build type, [Debug, Release, MinRelSize, RelWithDebInfo], you can also set build type by CMAKE_BUILD_TYPE environment variable")
cmd_help ='''
project command:
config: config toolchain path
clean_conf: clean toolchain path config
menuconfig: open menuconfig pannel, a visual config pannel
build: start compile project, temp files in `build` dir, dist files in `dist` dir
rebuild: update cmakefiles and build, if new file added, shoud use this command
clean: clean build files, won't clean configuration
distclean: clean all build files and configuration except configuration configed by config conmand
flash: burn firmware to board's flash
'''
cmd_choices = ["config", "build", "rebuild", "menuconfig", "clean", "distclean", "clean_conf"]
for k, v in extra_tools.items():
cmd_choices.extend(v["cmds"])
cmd_choices = list(set(cmd_choices))
project_parser.add_argument("cmd",
help=cmd_help,
choices=cmd_choices
)
project_args = project_parser.parse_args()
cwd = sys.path[0]
os.chdir(cwd)
config_filename = ".config.mk"
def load_config_mk(path):
configs = {}
if os.path.exists(config_filename):
with open(config_filename) as f:
content = f.read()
lines = content.split("\n")
for line in lines:
line = line.strip()
if (not line) or line.startswith("#"):
continue
k, v = line.split("=")
if v.startswith('"') and v.endswith('"'):
v = v[1:-1]
elif v in ["y", "n"]:
v = True if v == "y" else False
else:
try:
if v.startswith("0x"):
v = int(v, 16)
else:
v = int(v)
except:
pass
configs[k] = v
return configs
def dump_config_mk(configs, path):
with open(config_filename, "w") as f:
for k, v in configs.items():
if isinstance(v, bool):
v = "y" if v else "n"
elif isinstance(v, int):
v = str(v)
elif isinstance(v, str):
v = '"' + v + '"'
f.write("{}={}\n".format(k, v))
def get_config_files(config_file, sdk_path, project_path):
files = []
config_mk = os.path.join(project_path, ".config.mk")
config_default = os.path.join(project_path, "config_defaults.mk")
if config_file and os.path.exists(config_file):
files.append(config_file)
elif os.path.exists(config_default):
files.append(config_default)
if os.path.exists(config_mk):
files.append(config_mk)
return files
configs = load_config_mk(config_filename)
configs_old = configs.copy()
header = "# Generated by config.py, DO NOT edit!\n\n"
config_content = header
if project_args.generator.strip():
configs["CONFIG_CMAKE_GENERATOR"] = project_args.generator
if project_args.toolchain.strip() != "":
if not os.path.exists(project_args.toolchain):
print("[ERROR] config toolchain path error:", project_args.toolchain)
exit(1)
project_args.toolchain = project_args.toolchain.strip().replace("\\","/")
configs["CONFIG_TOOLCHAIN_PATH"] = project_args.toolchain
if project_args.toolchain_prefix.strip() != "":
project_args.toolchain_prefix = project_args.toolchain_prefix.strip().replace("\\","/")
configs["CONFIG_TOOLCHAIN_PREFIX"] = project_args.toolchain_prefix
if "CONFIG_CMAKE_GENERATOR" not in configs or not configs["CONFIG_CMAKE_GENERATOR"]:
configs["CONFIG_CMAKE_GENERATOR"] = "Unix Makefiles"
print('no generator set, will use "Unix Makefiles" as default')
if configs != configs_old:
dump_config_mk(configs, config_filename)
if os.path.exists("build/config/global_config.mk"):
os.remove("build/config/global_config.mk")
print("generate config file at: {}".format(config_filename))
if project_args.build_type:
build_type = project_args.build_type
elif project_args.release:
build_type = "MinSizeRel"
elif "CMAKE_BUILD_TYPE" in os.environ:
build_type = os.environ["CMAKE_BUILD_TYPE"]
else:
build_type = "Debug"
thread_num = cpu_count()
print("-- CPU count: {}".format(thread_num))
# config
if project_args.cmd == "config":
print("config complete")
# rebuild / build
elif project_args.cmd == "build" or project_args.cmd == "rebuild":
print("build now")
time_start = time.time()
if not os.path.exists("build"):
os.mkdir("build")
os.chdir("build")
if not os.path.exists("Makefile") or project_args.cmd == "rebuild":
if not os.path.isabs(project_args.config_file):
project_args.config_file = os.path.join(project_path, project_args.config_file)
config_path = os.path.abspath(project_args.config_file)
if not os.path.exists(config_path):
print("config file path error:{}".format(config_path))
exit(1)
print("-- build type: {}".format(build_type))
res = subprocess.call(["cmake", "-G", configs["CONFIG_CMAKE_GENERATOR"],
"-DCMAKE_BUILD_TYPE={}".format(build_type),
"-DDEFAULT_CONFIG_FILE={}".format(config_path), ".."])
if res != 0:
exit(1)
if project_args.verbose:
if configs["CONFIG_CMAKE_GENERATOR"] == "Unix Makefiles":
res = subprocess.call(["cmake", "--build", ".", "--target", "all", "--", "VERBOSE=1"])
elif configs["CONFIG_CMAKE_GENERATOR"] == "Ninja":
res = subprocess.call(["cmake", "--build", ".", "--target", "all", "--", "-v"])
else:
res = subprocess.call(["cmake", "--build", ".", "--target", "all"])
else:
if configs["CONFIG_CMAKE_GENERATOR"] in ["Unix Makefiles", "Ninja"]:
res = subprocess.call(["cmake", "--build", ".", "--target", "all", "--", "-j{}".format(thread_num)])
else:
res = subprocess.call(["cmake", "--build", ".", "--target", "all"])
if res != 0:
exit(1)
time_end = time.time()
print("==================================")
print("build end, time last:%.2fs" %(time_end-time_start))
print("==================================")
# clean
elif project_args.cmd == "clean":
print("clean now")
if os.path.exists("build"):
os.chdir("build")
p =subprocess.Popen(["cmake", "--build", ".", "--target", "clean"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output, err = p.communicate("")
res = p.returncode
if res == 0:
print(output.decode(encoding="gbk" if os.name == "nt" else "utf-8"))
print("clean complete")
# distclean
elif project_args.cmd == "distclean":
print("clean now")
if os.path.exists("build"):
os.chdir("build")
p =subprocess.Popen(["cmake", "--build", ".", "--target", "clean"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output, err = p.communicate("")
res = p.returncode
if res == 0:
print(output.decode(encoding="gbk" if os.name == "nt" else "utf-8"))
os.chdir("..")
shutil.rmtree("build")
if os.path.exists("dist"):
shutil.rmtree("dist")
print("clean complete")
# menuconfig
elif project_args.cmd == "menuconfig":
time_start = time.time()
if not os.path.exists("build"):
os.mkdir("build")
os.chdir("build")
binary_path = os.path.abspath(os.getcwd())
if not os.path.exists("build/config/global_config.mk"):
if not os.path.isabs(project_args.config_file):
project_args.config_file = os.path.join(project_path, project_args.config_file)
config_path = os.path.abspath(project_args.config_file)
if not os.path.exists(config_path):
print("config file path error:{}".format(config_path))
exit(1)
res = subprocess.call(["cmake", "-G", configs["CONFIG_CMAKE_GENERATOR"],
"-DCMAKE_BUILD_TYPE={}".format(build_type),
"-DDEFAULT_CONFIG_FILE={}".format(config_path), ".."])
if res != 0:
exit(1)
# res = subprocess.call(["cmake", "--build", ".", "--parallel", "1", "--target", "menuconfig"])
# when use Ninja, menuconfig will not work, so use python script instead, need help here, PR is welcome
tool_path = os.path.join(sdk_path, "tools/kconfig/genconfig.py")
if not os.path.exists(tool_path):
print("[ERROR] kconfig tool not found:", tool_path)
exit(1)
# get default files
config_files = get_config_files(project_args.config_file, sdk_path, project_path)
cmd = [sys.executable, tool_path, "--kconfig", os.path.join(sdk_path, "Kconfig")]
for path in config_files:
cmd.extend(["--defaults", path])
cmd.extend(["--menuconfig", "True", "--env", "SDK_PATH={}".format(sdk_path),
"--env", "PROJECT_PATH={}".format(project_path),
"--env", "BUILD_TYPE={}".format(build_type)])
cmd.extend(["--output", "makefile", os.path.join(binary_path, "config", "global_config.mk")])
cmd.extend(["--output", "cmake", os.path.join(binary_path, "config", "global_config.cmake")])
cmd.extend(["--output", "header", os.path.join(binary_path, "config", "global_config.h")])
res = subprocess.call(cmd)
if res != 0:
exit(1)
# clean_conf
elif project_args.cmd == "clean_conf":
print("clean now")
# clean cmake config files
if os.path.exists(config_filename):
os.remove(config_filename)
if os.path.exists("build/config/"):
shutil.rmtree("build/config")
# clean extra tools config file
for k, v in extra_tools.items():
if project_args.cmd in v["cmds"]:
vars = {
"project_path": project_path,
"project_name": project_name,
"sdk_path": sdk_path,
"build_type": build_type,
"project_parser": project_parser,
"project_args": project_args,
"configs": configs,
}
print("-- call tool <{}>'s clean_conf cmd".format(k))
v["tool"].main(vars)
print("clean complete")
# extra tools
elif project_args.cmd in cmd_choices:
for k, v in extra_tools.items():
if project_args.cmd in v["cmds"]:
tool = v["tool"]
vars = {
"project_path": project_path,
"project_name": project_name,
"sdk_path": sdk_path,
"build_type": build_type,
"project_parser": project_parser,
"project_args": project_args,
"configs": configs,
}
print("\n-------- {} start ---------".format(project_args.cmd))
ret = tool.main(vars)
print("-------- {} end ---------".format(project_args.cmd))
exit(ret)
else:
print("Error: Unknown command")
exit(1)