You've already forked M5Stack_Linux_Libs
mirror of
https://github.com/m5stack/M5Stack_Linux_Libs.git
synced 2026-05-20 11:01:38 -07:00
347 lines
14 KiB
Python
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)
|
|
|