Files
2025-03-31 11:48:40 +08:00

482 lines
21 KiB
Python

# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
#
# SPDX-License-Identifier: MIT
# SConstruct
from pathlib import Path
import os, sys, shutil
import copy
from SCons.Node.FS import File
import subprocess
import parse
from collections.abc import Iterable
import configparser
import subprocess
import uuid
# from parse import compile as parse_compile
SDK_PATH = os.path.normpath(os.environ.get('SDK_PATH', str(Path(os.getcwd())/'..'/'..')))
PROJECT_NAME = os.environ.get('PROJECT_NAME', os.path.basename(sys.path[0]))
PROJECT_PATH = os.environ.get('PROJECT_PATH', sys.path[0])
BUILD_PATH = os.environ.get('BUILD_PATH', str(Path(PROJECT_PATH)/'build'))
BUILD_CONFIG_PATH = os.environ.get('BUILD_CONFIG_PATH', str(Path(PROJECT_PATH)/'build'/'config'))
CONFIG_TOOL_FILE = os.environ.get('CONFIG_TOOL_FILE', str(Path(SDK_PATH)/'tools'/'kconfig'/'genconfig.py'))
CONFIG_DEFAULT_FILE = os.environ.get('CONFIG_DEFAULT_FILE', str(Path(PROJECT_PATH)/'config_defaults.mk'))
KCONFIG_FILE = os.environ.get('KCONFIG_FILE', str(Path(SDK_PATH)/'Kconfig'))
GLOBAL_CONFIG_MK_FILE = os.environ.get('GLOBAL_CONFIG_MK_FILE', str(Path(PROJECT_PATH)/'build'/'config'/'global_config.mk'))
GLOBAL_CONFIG_H_FILE = os.environ.get('GLOBAL_CONFIG_H_FILE', str(Path(PROJECT_PATH)/'build'/'config'/'global_config.h'))
TOOL_FILE = os.environ.get('TOOL_FILE', str(Path(SDK_PATH)/'tools'))
GIT_REPO_PATH = os.environ.get('GIT_REPO_PATH', str(Path(SDK_PATH)/'github_source'))
GIT_REPO_FILE = os.environ.get('GIT_REPO_FILE', str(Path(GIT_REPO_PATH)/'source-list.sh'))
if 'GIT_REPO_PATH' not in os.environ:
os.environ['GIT_REPO_PATH'] = GIT_REPO_PATH
def ourspawn(sh, escape, cmd, args, e):
filename = str(uuid.uuid4())
newargs = ' '.join(args[1:])
cmdline = cmd + " " + newargs
if (len(cmdline) > 16 * 1024):
f = open(filename, 'w')
f.write(' '.join(args[1:]).replace('\\', '/'))
f.close()
# exec
cmdline = cmd + " @" + filename
proc = subprocess.Popen(cmdline, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, shell = False, env = e)
data, err = proc.communicate()
rv = proc.wait()
def res_output(_output, _s):
if len(_s):
if isinstance(_s, str):
_output(_s)
elif isinstance(_s, bytes):
_output(str(_s, 'UTF-8'))
else:
_output(str(_s))
res_output(sys.stderr.write, err)
res_output(sys.stdout.write, data)
if os.path.isfile(filename):
os.remove(filename)
return rv
def menuconfig_fun():
os.makedirs(BUILD_CONFIG_PATH, exist_ok=True)
build_type = 'Debug'
if not os.path.exists(CONFIG_TOOL_FILE):
print("[ERROR] kconfig tool not found:", CONFIG_TOOL_FILE)
exit(1)
# get default files
# config_files = get_config_files(project_args.config_file, sdk_path, project_path)
cmd = [sys.executable, CONFIG_TOOL_FILE, "--kconfig", KCONFIG_FILE]
for path in [CONFIG_DEFAULT_FILE]:
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", GLOBAL_CONFIG_MK_FILE])
cmd.extend(["--output", "header", GLOBAL_CONFIG_H_FILE])
subprocess.call(cmd)
exit(0)
def clean_fun():
subprocess.call(['scons', '-c'])
exit(0)
def distclean_fun():
paths_to_remove = ['build', 'dist', '.sconsign.dblite', '.config.old', '.config', '.config.mk']
for path in paths_to_remove:
try:
if os.path.isdir(path):
shutil.rmtree(path)
else:
os.remove(path)
except :
pass
exit(0)
def save_fun():
if os.path.exists(GLOBAL_CONFIG_MK_FILE):
shutil.copy(GLOBAL_CONFIG_MK_FILE, CONFIG_DEFAULT_FILE)
exit(0)
def set_tools_fun():
config_file = GLOBAL_CONFIG_MK_FILE
for confs in sys.argv:
if confs.startswith('CROSS_DIR'):
with open(config_file, 'a') as fs:
fs.write("\n")
fs.write(f'CONFIG_TOOLCHAIN_PATH="{confs.split("=")[1]}"\n')
elif confs.startswith('CROSS'):
with open(config_file, 'a') as fs:
fs.write("\n")
fs.write(f'CONFIG_TOOLCHAIN_PREFIX="{confs.split("=")[1]}"\n')
exit(0)
def push_fun():
if not os.path.exists('setup.ini'):
print("please creat setup.ini!")
exit(1)
config = configparser.ConfigParser()
config.read(str(Path(PROJECT_PATH)/'setup.ini'))
cmd = [sys.executable, str(Path(SDK_PATH)/'tools'/'scons'/'push.py'), config['ssh']['local_file_path']
, config['ssh']['remote_file_path']
, config['ssh']['remote_host']
, config['ssh']['remote_port']
, config['ssh']['username']
, config['ssh']['password']]
print(cmd)
subprocess.call(cmd)
exit(0)
env = None
task_lists = {}
def Fatal(env, message):
print("Fatal error: " + message)
env.Exit(1)
def copy_file(target, source, env):
source_path = str(source[0])
target_path = str(target[0])
os.makedirs(os.path.dirname(target_path), exist_ok=True)
if os.path.isfile(source_path):
shutil.copy2(source_path, target_path)
elif os.path.isdir(source_path):
shutil.copytree(source_path, target_path)
def build_task_init():
global env
global task_lists
fun_list = {'menuconfig':menuconfig_fun, 'clean':clean_fun, 'distclean':distclean_fun, 'save':save_fun, 'SET_CROSS':set_tools_fun, 'push':push_fun}
for fun_name in fun_list:
if fun_name in sys.argv:
fun_list[fun_name]()
if not os.path.exists(GLOBAL_CONFIG_MK_FILE):
os.makedirs(BUILD_CONFIG_PATH, exist_ok=True)
build_type = 'Debug'
tool_path = CONFIG_TOOL_FILE
if not os.path.exists(tool_path):
print("[ERROR] kconfig tool not found:", tool_path)
exit(1)
cmd = [sys.executable, tool_path, "--kconfig", KCONFIG_FILE]
for path in [CONFIG_DEFAULT_FILE]:
cmd.extend(["--defaults", path])
cmd.extend(["--env", f'SDK_PATH={SDK_PATH}'])
cmd.extend(["--env", f'PROJECT_PATH={PROJECT_PATH}'])
cmd.extend(["--env", f'BUILD_TYPE={build_type}'])
cmd.extend(["--output", "makefile", GLOBAL_CONFIG_MK_FILE])
cmd.extend(["--output", "header", GLOBAL_CONFIG_H_FILE])
subprocess.call(cmd)
try:
with open(GLOBAL_CONFIG_MK_FILE, 'r') as f:
pattern = parse.compile("CONFIG_{}={}\n")
for line in f.readlines():
Pobj = pattern.parse(line)
if Pobj:
key, value = Pobj.fixed
key = 'CONFIG_' + key
if key in os.environ:
continue
os.environ[key] = value.strip('"')
except:
print('Error in parsing the {}'.format(GLOBAL_CONFIG_MK_FILE))
exit(-1)
SConsignFile('build/sconsign.dblite')
env = Environment(tools=['gcc', 'g++', 'gnulink', 'ar', 'gas', 'as'])
env['GCCPREFIX'] = ''
env['GCCSUFFIX'] = ''
env['LINK'] = '$SMARTLINK'
env['SHLINK'] = '$LINK'
env['OBJPREFIX'] = ''
env['OBJSUFFIX'] = '.o'
env['LIBPREFIX'] = 'lib'
env['LIBSUFFIX'] = '.a'
env['SHLIBPREFIX'] = '$LIBPREFIX'
env['SHLIBSUFFIX'] = '.so'
env['INCPREFIX'] = '-I'
env['INCSUFFIX'] = ''
env['LIBDIRPREFIX'] = '-L'
env['LIBDIRSUFFIX'] = ''
env['LIBLINKPREFIX'] = '-l'
env['LIBLINKSUFFIX'] = ''
env['CPPDEFPREFIX'] = '-D'
env['CPPDEFSUFFIX'] = ''
env['PROGSUFFIX'] = ''
env['ARFLAGS'] = ['rc']
env['SHCCFLAGS'] = ['$CCFLAGS', '-fPIC']
env['SHLINKFLAGS'] = ['$LINKFLAGS', '-shared']
env['CFLAGS'] = []
env['CXXFLAGS'] = []
env['ASFLAGS'] = []
env['LINKFLAGS'] = []
env['CCCOM'] = '$CC -o $TARGET -c $CFLAGS $CCFLAGS $_CCCOMCOM $SOURCES'
env['CXXCOM'] = '$CXX -o $TARGET -c $CXXFLAGS $CCFLAGS $_CCCOMCOM $SOURCES'
env['ASCOM'] ='$AS $ASFLAGS -o $TARGET $SOURCES'
env['ARCOM'] = '$AR $ARFLAGS $TARGET $SOURCES'
env['SHCCCOM'] = '$SHCC -o $TARGET -c $SHCFLAGS $SHCCFLAGS $_CCCOMCOM $SOURCES'
env['SHCXXCOM'] = '$SHCXX -o $TARGET -c $SHCXXFLAGS $SHCCFLAGS $_CCCOMCOM $SOURCES'
env['LINKCOM'] = '$LINK -o $TARGET $SOURCES $LINKFLAGS $__RPATH $_LIBDIRFLAGS $_LIBFLAGS'
env['SHLINKCOM'] = '$SHLINK -o $TARGET $SHLINKFLAGS $__SHLIBVERSIONFLAGS $__RPATH $SOURCES $_LIBDIRFLAGS $_LIBFLAGS'
env['STRIPCOM'] = '$STRIP $SOURCES'
if 'CONFIG_COMMPILE_DEBUG' not in os.environ:
env['CCCOMSTR'] = "CC $SOURCES"
env['CXXCOMSTR'] = "CXX $SOURCES"
env['SHCCCOMSTR'] = "CC -fPIC $SOURCES"
env['SHCXXCOMSTR'] = "CXX -fPIC $SOURCES"
env['ARCOMSTR'] = "LD $TARGET"
env['SHLINKCOMSTR'] = "Linking $TARGET"
env['LINKCOMSTR'] = 'Linking $TARGET'
env.AddMethod(Fatal, "Fatal")
if os.environ['CONFIG_TOOLCHAIN_PREFIX']:
env['GCCPREFIX'] = os.environ['CONFIG_TOOLCHAIN_PREFIX'].strip('"')
if env['HOST_OS'].startswith('win'):
if os.environ['CONFIG_TOOLCHAIN_PATH']:
env['ENV']['PATH'] = os.environ['CONFIG_TOOLCHAIN_PATH'].strip('"') + ';' + env['ENV']['PATH'] + ';' + os.getenv('Path')
else:
env['ENV']['PATH'] = env['ENV']['PATH'] + ';' + os.getenv('Path')
env['GCCSUFFIX'] = '.exe'
env['SPAWN'] = ourspawn
elif env['HOST_OS'].startswith('posix'):
if os.environ['CONFIG_TOOLCHAIN_PATH']:
env['ENV']['PATH'] = os.environ['CONFIG_TOOLCHAIN_PATH'].strip('"') + ':' + env['ENV']['PATH']
else:
print('unknow os!', env['HOST_OS'])
env['GCCPREFIX'] = os.path.join(os.environ['CONFIG_TOOLCHAIN_PATH'].strip('"'), os.environ['CONFIG_TOOLCHAIN_PREFIX'].strip('"'))
if os.environ['CONFIG_TOOLCHAIN_FLAGS']:
env.MergeFlags(os.environ['CONFIG_TOOLCHAIN_FLAGS'])
env.Append(CPPPATH=[BUILD_CONFIG_PATH])
env['CC'] = '${_concat(GCCPREFIX, "gcc", GCCSUFFIX, __env__)}'
env['CXX'] = '${_concat(GCCPREFIX, "g++", GCCSUFFIX, __env__)}'
env['AR'] = '${_concat(GCCPREFIX, "ar", GCCSUFFIX, __env__)}'
env['AS'] = '${_concat(GCCPREFIX, "as", GCCSUFFIX, __env__)}'
env['STRIP'] = '${_concat(GCCPREFIX, "strip", GCCSUFFIX, __env__)}'
if 'CONFIG_GCC_DUMPMACHINE' in os.environ:
env['GCC_DUMPMACHINE'] = os.environ['CONFIG_GCC_DUMPMACHINE'].strip('"')
else:
try:
env.ParseConfig('${CC} -v 2> gcc_out.txt')
version_r = parse.compile("gcc version {} {}")
Target_r = parse.compile("Target: {}\n")
with open('gcc_out.txt', 'r') as conf_file:
for line in conf_file.readlines():
version = version_r.parse(line)
if version:
env['CCVERSION'] = version[0]
Target = Target_r.parse(line)
if Target:
env['GCC_DUMPMACHINE'] = Target[0]
os.remove('gcc_out.txt')
except:
print('Failed to obtain GCC parameters!')
env['GIT_REPO_LISTS'] = {}
try:
with open(GIT_REPO_FILE, 'r') as f:
pattern = parse.compile("{start}_clone_and_checkout_commit {url} {commit}")
pattern_name = parse.compile("{}://{}/{}/{}.git")
for line in f.readlines():
try:
Pobj = pattern.parse(line)
if Pobj and '#' not in Pobj.named['start']:
git_repo_url = Pobj.named['url'].replace(" ", "")
git_repo_commit = Pobj.named['commit'].replace(" ", "").replace("\n", "").replace("\r", "")
git_repo_name = pattern_name.parse(git_repo_url)[3]
env['GIT_REPO_LISTS'][git_repo_name] = {'url':git_repo_url, 'commit':git_repo_commit, "path":str(Path(SDK_PATH)/'github_source'/git_repo_name)}
except:
pass
except:
pass
# print(env.Dump())
env['PROJECT_PATH'] = PROJECT_PATH
env['PROJECT_NAME'] = os.path.basename(PROJECT_PATH)
env['PROJECT_TOOL_S'] = str(Path(SDK_PATH)/'tools'/'scons'/'SConstruct_tool.py')
env['COMPONENTS'] = []
env['COMPONENTS_ENV'] = env.Clone()
env['COMPONENTS_PATH'] = [str(Path(SDK_PATH)/'components')]
if 'EXT_COMPONENTS_PATH' in os.environ:
for ecp in os.environ['EXT_COMPONENTS_PATH'].split(':'):
env['COMPONENTS_PATH'].append(ecp)
for component in env['COMPONENTS_PATH']:
if os.path.exists(component):
for component_name in os.listdir(component):
if os.path.exists(str(Path(component)/component_name/'SConstruct')):
env['component_dir'] = str(Path(component)/component_name)
SConscript(str(Path(component)/component_name/'SConstruct'), exports='env')
for project_dir in os.listdir(PROJECT_PATH):
if project_dir.startswith("main"):
env['component_dir'] = str(Path(PROJECT_PATH)/project_dir)
SConscript(str(Path(PROJECT_PATH)/project_dir/'SConstruct'), exports='env')
for iteam in env['COMPONENTS']:
task_lists[iteam['target']] = iteam
def creat_commpile_Program():
if not os.path.exists('build'):
os.mkdir('build')
for task in task_lists:
component = task_lists[task]
_BUILD_ENV = env.Clone()
component_build_dir = str(Path('build')/component['target'])
_TARGET = str(Path(component_build_dir)/component['target'])
component['_component_build_dir'] = component_build_dir
if not os.path.exists(component_build_dir):
os.mkdir(component_build_dir)
def deep_iter_or_return(obj):
if isinstance(obj, Iterable) and not isinstance(obj, str):
for item in obj:
yield from deep_iter_or_return(item)
else:
yield obj
def get_srcs(obj):
file = str(obj)
if file.startswith(SDK_PATH):
file = file[len(SDK_PATH) + 1:]
ofile = file
if os.path.isabs(ofile):
ofile = os.path.join(component_build_dir, ofile[1:] + '.o')
else:
ofile = os.path.join(component_build_dir, ofile + '.o')
return ofile
_srcs = [str(o) for o in deep_iter_or_return(component['SRCS'])]
_srco = list(map(get_srcs, _srcs))
_srcs_custom = {}
if 'SRCS_CUSTOM' in component:
_srcs_custom = component['SRCS_CUSTOM']
_srcs_list = list(_srcs_custom.keys())
_srco_custom = list(map(get_srcs, _srcs_list))
for index, obj in enumerate(_srcs_list):
_srcs_custom[obj]['SRCO'] = _srco_custom[index]
if len(_srcs) == 0:
empty_src_file = str(Path(component_build_dir)/'empty_src_file.cpp')
open(empty_src_file, 'w').close()
_srcs.append(empty_src_file)
_srco.append(empty_src_file + '.o')
_BUILD_ENV.Append(CPPPATH=component['INCLUDE'])
_BUILD_ENV.Append(CPPPATH=component['PRIVATE_INCLUDE'])
_BUILD_ENV.MergeFlags(component['DEFINITIONS'])
_BUILD_ENV.MergeFlags(component['DEFINITIONS_PRIVATE'])
_BUILD_ENV.Append(LINKFLAGS=component['LDFLAGS'])
_BUILD_ENV.Append(LIBPATH=component['LINK_SEARCH_PATH'])
_LIBO = []
for requirement in component['REQUIREMENTS']:
if requirement in task_lists:
for c_requirement in task_lists[requirement]['REQUIREMENTS']:
if c_requirement in task_lists:
component['REQUIREMENTS'].append(c_requirement)
for requirement in component['REQUIREMENTS']:
if requirement in task_lists:
_BUILD_ENV.Append(CPPPATH=task_lists[requirement]['INCLUDE'])
_BUILD_ENV.Append(LIBS=[requirement])
_BUILD_ENV.Append(LIBPATH=str(Path('build')/requirement))
_BUILD_ENV.Append(LIBPATH=task_lists[requirement]['LINK_SEARCH_PATH'])
_BUILD_ENV.Append(LINKFLAGS=task_lists[requirement]['LDFLAGS'])
_BUILD_ENV.MergeFlags(task_lists[requirement]['DEFINITIONS'])
_LIBO += list(map(lambda o: str(o), task_lists[requirement]['STATIC_LIB'] + task_lists[requirement]['DYNAMIC_LIB']))
for c_requirement in task_lists[requirement]['REQUIREMENTS']:
if c_requirement not in task_lists:
_BUILD_ENV.Append(LIBS=[c_requirement])
else:
_BUILD_ENV.Append(LIBS=[requirement])
if component['REGISTER'] == 'static':
_OBJS = list(map(lambda file: _BUILD_ENV.Object(target = file[0], source = file[1]), list(zip(_srco, _srcs))))
for src_file in _srcs_custom:
_OBJS += _BUILD_ENV.Object(target = _srcs_custom[src_file]['SRCO'], source = str(src_file), CPPFLAGS=_srcs_custom[src_file]['CPPFLAGS'], CCFLAGS=_srcs_custom[src_file]['CCFLAGS'])
component['_target'] = _BUILD_ENV.Library(target = _TARGET, source = _OBJS)
component['_target_build_env'] = _BUILD_ENV
elif component['REGISTER'] == 'shared':
_OBJS = list(map(lambda file: _BUILD_ENV.SharedObject(target = file[0], source = file[1]), list(zip(_srco, _srcs))))
for src_file in _srcs_custom:
_OBJS += _BUILD_ENV.SharedObject(target = _srcs_custom[src_file]['SRCO'], source = str(src_file), CPPFLAGS=_srcs_custom[src_file]['CPPFLAGS'], CCFLAGS=_srcs_custom[src_file]['CCFLAGS'])
component['_target'] = _BUILD_ENV.SharedLibrary(target = _TARGET, source = _OBJS)
component['_target_build_env'] = _BUILD_ENV
if 'CONFIG_TOOLCHAIN_SYSTEM_UNIX' in os.environ:
_BUILD_ENV.Command(os.path.join('dist', f'lib{component["target"]}.so'), str(Path('build')/component['target']/f'lib{component["target"]}.so'), action=copy_file)
elif 'CONFIG_TOOLCHAIN_SYSTEM_WIN' in os.environ:
_BUILD_ENV.Command(os.path.join('dist', f'lib{component["target"]}.dll'), str(Path('build')/component['target']/f'lib{component["target"]}.dll'), action=copy_file)
else:
pass
elif component['REGISTER'] == 'project':
_OBJS = list(map(lambda file: _BUILD_ENV.Object(target = file[0], source = file[1]), list(zip(_srco, _srcs))))
for src_file in _srcs_custom:
_OBJS += _BUILD_ENV.Object(target = _srcs_custom[src_file]['SRCO'], source = str(src_file), CPPFLAGS=_srcs_custom[src_file]['CPPFLAGS'], CCFLAGS=_srcs_custom[src_file]['CCFLAGS'])
_LIBO += list(map(lambda o: str(o), component['STATIC_LIB'] + component['DYNAMIC_LIB']))
_BUILD_ENV.Library(target = _TARGET, source = _OBJS)
empty_src_file = str(Path(component_build_dir)/'empty_src_file.cpp')
open(empty_src_file, 'w').close()
if component['DYNAMIC_LIB']:
_BUILD_ENV.Append(LINKFLAGS=['-Wl,-rpath=./'])
component['_target'] = _BUILD_ENV.Program(target = _TARGET, source = [empty_src_file, component_build_dir + '/lib' + component['target'] + '.a'] + _LIBO)
component['_target_build_env'] = _BUILD_ENV
if 'CONFIG_TOOLCHAIN_SYSTEM_UNIX' in os.environ:
_BUILD_ENV.Command(os.path.join('dist', component['target']), str(Path('build')/component['target']/component['target']), action=copy_file)
elif 'CONFIG_TOOLCHAIN_SYSTEM_WIN' in os.environ:
_BUILD_ENV.Command(os.path.join('dist', component['target']) + '.exe', [str(Path('build')/component['target']/component['target']) + '.exe', 'dist'], action=copy_file)
else:
pass
for lib_file in component['DYNAMIC_LIB']:
_BUILD_ENV.Command(os.path.join('dist', os.path.basename(str(lib_file))), str(lib_file), action=copy_file)
if 'STATIC_FILES' in component:
for file in component['STATIC_FILES']:
_BUILD_ENV.Command(os.path.join('dist', os.path.basename(str(file))), str(file), action=copy_file)
def add_commpile_Program_requirements():
for task in task_lists:
component = task_lists[task]
for requirement in component['REQUIREMENTS']:
if requirement in task_lists:
Depends(component['_target'], task_lists[requirement]['_target'])
build_task_init()
creat_commpile_Program()
add_commpile_Program_requirements()