Files

742 lines
28 KiB
Python
Raw Permalink Normal View History

2024-05-28 14:28:07 +08:00
# SPDX-FileCopyrightText: 2024 M5Stack Technology CO LTD
#
# SPDX-License-Identifier: MIT
2024-04-10 12:21:46 +08:00
# SConstruct
from pathlib import Path
2025-09-17 10:30:24 +08:00
import os
import sys
import shutil
2024-04-10 12:21:46 +08:00
import copy
from SCons.Node.FS import File
import subprocess
import parse
from collections.abc import Iterable
import configparser
2024-04-17 20:15:59 +08:00
import uuid
2025-09-17 10:30:24 +08:00
import logging
2024-04-17 20:15:59 +08:00
2025-09-17 10:30:24 +08:00
# Setup paths and environment variables
SDK_PATH = os.path.normpath(os.environ.get('SDK_PATH', str(Path(os.getcwd())/'..'/'..')))
2024-04-10 12:21:46 +08:00
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'))
2025-09-17 10:30:24 +08:00
# Set GIT_REPO_PATH in environment if not already set
if 'GIT_REPO_PATH' not in os.environ:
os.environ['GIT_REPO_PATH'] = GIT_REPO_PATH
2024-04-17 20:15:59 +08:00
2025-09-17 10:30:24 +08:00
# Global variables
env = None
task_lists = {}
def setup_logging():
"""Configure logging for the build system"""
logging.basicConfig(
level=logging.INFO,
format='%(levelname)s: %(message)s'
)
return logging.getLogger('build')
logger = setup_logging()
2024-04-17 20:15:59 +08:00
def ourspawn(sh, escape, cmd, args, e):
2025-09-17 10:30:24 +08:00
"""Custom spawn function for executing commands with long argument lists"""
2024-04-17 20:15:59 +08:00
filename = str(uuid.uuid4())
newargs = ' '.join(args[1:])
cmdline = cmd + " " + newargs
2025-09-17 10:30:24 +08:00
# Handle command line length limit by writing arguments to a file
2024-04-17 20:15:59 +08:00
if (len(cmdline) > 16 * 1024):
2025-09-17 10:30:24 +08:00
with open(filename, 'w') as f:
f.write(' '.join(args[1:]).replace('\\', '/'))
# Execute with response file
2024-04-17 20:15:59 +08:00
cmdline = cmd + " @" + filename
2025-09-17 10:30:24 +08:00
# Execute the command
proc = subprocess.Popen(
cmdline,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
shell=False,
env=e
)
2024-04-17 20:15:59 +08:00
data, err = proc.communicate()
rv = proc.wait()
2025-09-17 10:30:24 +08:00
# Output handling function
2024-04-17 20:15:59 +08:00
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))
2025-09-17 10:30:24 +08:00
# Write output to stdout/stderr
2024-04-17 20:15:59 +08:00
res_output(sys.stderr.write, err)
res_output(sys.stdout.write, data)
2025-09-17 10:30:24 +08:00
# Clean up temporary file if created
2024-04-17 20:15:59 +08:00
if os.path.isfile(filename):
os.remove(filename)
2025-09-17 10:30:24 +08:00
2024-04-17 20:15:59 +08:00
return rv
2025-09-17 10:30:24 +08:00
def run_kconfig_tool(menuconfig=False, build_type='Debug'):
"""Run the Kconfig tool to generate configuration files"""
os.makedirs(BUILD_CONFIG_PATH, exist_ok=True)
if not os.path.exists(CONFIG_TOOL_FILE):
logger.error("Kconfig tool not found: {}".format(CONFIG_TOOL_FILE))
sys.exit(1)
cmd = [sys.executable, CONFIG_TOOL_FILE, "--kconfig", KCONFIG_FILE]
cmd.extend(["--defaults", CONFIG_DEFAULT_FILE])
# Environment variables
cmd.extend([
"--env", "SDK_PATH={}".format(SDK_PATH),
"--env", "PROJECT_PATH={}".format(PROJECT_PATH),
"--env", "BUILD_TYPE={}".format(build_type)
])
# Add menuconfig option if requested
if menuconfig:
cmd.extend(["--menuconfig", "True"])
# Output files
cmd.extend([
"--output", "makefile", GLOBAL_CONFIG_MK_FILE,
"--output", "header", GLOBAL_CONFIG_H_FILE
])
return subprocess.call(cmd)
def copy_file(target, source, env):
"""Copy files or directories from source to target"""
source_path = str(source[0])
target_path = str(target[0])
# Create target directory if it doesn't exist
os.makedirs(os.path.dirname(target_path), exist_ok=True)
# Copy file or directory
if os.path.isfile(source_path):
shutil.copy2(source_path, target_path)
elif os.path.isdir(source_path):
shutil.copytree(source_path, target_path)
2024-04-17 20:15:59 +08:00
2024-04-10 12:21:46 +08:00
def menuconfig_fun():
2025-09-17 10:30:24 +08:00
"""Run menuconfig and exit"""
run_kconfig_tool(menuconfig=True)
sys.exit(0)
2024-04-10 12:21:46 +08:00
def clean_fun():
2025-09-17 10:30:24 +08:00
"""Clean build artifacts and exit"""
2024-04-10 12:21:46 +08:00
subprocess.call(['scons', '-c'])
2025-09-17 10:30:24 +08:00
sys.exit(0)
2024-04-10 12:21:46 +08:00
def distclean_fun():
2025-09-17 10:30:24 +08:00
"""Remove all build artifacts and configuration files"""
2024-04-10 12:21:46 +08:00
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)
2025-09-17 10:30:24 +08:00
except Exception as e:
logger.debug("Failed to remove {}: {}".format(path, e))
sys.exit(0)
2024-04-10 12:21:46 +08:00
def save_fun():
2025-09-17 10:30:24 +08:00
"""Save current configuration to default config file"""
2024-04-10 12:21:46 +08:00
if os.path.exists(GLOBAL_CONFIG_MK_FILE):
shutil.copy(GLOBAL_CONFIG_MK_FILE, CONFIG_DEFAULT_FILE)
2025-09-17 10:30:24 +08:00
logger.info("Configuration saved to {}".format(CONFIG_DEFAULT_FILE))
else:
logger.error("Configuration file {} not found".format(GLOBAL_CONFIG_MK_FILE))
sys.exit(0)
2024-04-10 12:21:46 +08:00
def set_tools_fun():
2025-09-17 10:30:24 +08:00
"""Set toolchain configuration"""
2024-04-10 12:21:46 +08:00
config_file = GLOBAL_CONFIG_MK_FILE
2025-09-17 10:30:24 +08:00
if not os.path.exists(config_file):
logger.error("Configuration file {} not found".format(config_file))
sys.exit(1)
with open(config_file, 'a') as fs:
fs.write("\n")
for confs in sys.argv:
if confs.startswith('CROSS_DIR'):
fs.write('CONFIG_TOOLCHAIN_PATH="{}"\n'.format(confs.split("=")[1]))
elif confs.startswith('CROSS'):
fs.write('CONFIG_TOOLCHAIN_PREFIX="{}"\n'.format(confs.split("=")[1]))
logger.info("Toolchain configuration updated")
sys.exit(0)
2024-04-10 12:21:46 +08:00
def push_fun():
2025-09-17 10:30:24 +08:00
"""Push files to remote server using SSH"""
2024-04-10 12:21:46 +08:00
if not os.path.exists('setup.ini'):
2025-09-17 10:30:24 +08:00
logger.error("Please create setup.ini!")
sys.exit(1)
2024-04-10 12:21:46 +08:00
config = configparser.ConfigParser()
config.read(str(Path(PROJECT_PATH)/'setup.ini'))
2025-09-17 10:30:24 +08:00
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']
]
logger.info("Pushing files to {}".format(config['ssh']['remote_host']))
2024-04-10 12:21:46 +08:00
subprocess.call(cmd)
2025-09-17 10:30:24 +08:00
sys.exit(0)
2024-04-10 12:21:46 +08:00
2024-06-03 20:01:31 +08:00
def Fatal(env, message):
2025-09-17 10:30:24 +08:00
"""Fatal error handler for SCons environment"""
logger.error(message)
2024-06-03 20:01:31 +08:00
env.Exit(1)
2025-09-17 10:30:24 +08:00
def load_config_mk():
"""Load configuration from global_config.mk file"""
2024-05-31 12:10:44 +08:00
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
2025-09-17 10:30:24 +08:00
if key not in os.environ:
os.environ[key] = value.strip('"')
except Exception as e:
logger.error('Error parsing {}: {}'.format(GLOBAL_CONFIG_MK_FILE, e))
sys.exit(-1)
2025-09-17 10:30:24 +08:00
def load_git_repos(env):
"""Load git repository information from source-list.sh"""
env['GIT_REPO_LISTS'] = {}
2024-07-28 15:13:34 +08:00
2025-09-17 10:30:24 +08:00
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 Exception as e:
logger.debug("Failed to parse git repo line: {}".format(e))
except Exception as e:
logger.debug("Failed to load git repos: {}".format(e))
def setup_environment():
"""Set up the SCons build environment"""
global env
# Create sconsign file in build directory
2024-07-28 15:13:34 +08:00
SConsignFile('build/sconsign.dblite')
2025-09-17 10:30:24 +08:00
# Initialize environment with basic tools
env = Environment(tools=['gcc', 'g++', 'gnulink', 'ar', 'gas', 'as'])
2025-09-17 10:30:24 +08:00
# Set up basic environment variables
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'] = []
2024-04-10 12:21:46 +08:00
2025-09-17 10:30:24 +08:00
# Set up command strings
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'
2025-09-17 10:30:24 +08:00
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'
2024-04-10 12:21:46 +08:00
2025-09-17 10:30:24 +08:00
# Set up command string display (for less verbose output)
if 'CONFIG_COMMPILE_DEBUG' not in os.environ:
2025-09-17 10:30:24 +08:00
env['ASCOMSTR'] = "AS $SOURCES"
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'
2024-04-10 12:21:46 +08:00
2025-09-17 10:30:24 +08:00
# Add Fatal method to environment
2024-06-03 20:01:31 +08:00
env.AddMethod(Fatal, "Fatal")
2025-09-17 10:30:24 +08:00
# Configure toolchain prefix if specified
if os.environ.get('CONFIG_TOOLCHAIN_PREFIX'):
env['GCCPREFIX'] = os.environ['CONFIG_TOOLCHAIN_PREFIX'].strip('"')
2025-09-17 10:30:24 +08:00
# Configure PATH based on host OS
if env['HOST_OS'].startswith('win'):
2025-09-17 10:30:24 +08:00
if os.environ.get('CONFIG_TOOLCHAIN_PATH'):
env['ENV']['PATH'] = os.environ['CONFIG_TOOLCHAIN_PATH'].strip('"') + ';' + env['ENV']['PATH'] + ';' + os.getenv('Path')
else:
2025-09-17 10:30:24 +08:00
env['ENV']['PATH'] = env['ENV']['PATH'] + ';' + os.getenv('Path')
env['GCCSUFFIX'] = '.exe'
env['SPAWN'] = ourspawn
elif env['HOST_OS'].startswith('posix'):
2025-09-17 10:30:24 +08:00
if os.environ.get('CONFIG_TOOLCHAIN_PATH'):
env['ENV']['PATH'] = os.environ['CONFIG_TOOLCHAIN_PATH'].strip('"') + ':' + env['ENV']['PATH']
else:
2025-09-17 10:30:24 +08:00
logger.warning('Unknown OS: {}'.format(env["HOST_OS"]))
2024-06-03 20:01:31 +08:00
2025-09-17 10:30:24 +08:00
# Set full toolchain prefix path
if os.environ.get('CONFIG_TOOLCHAIN_PATH') and os.environ.get('CONFIG_TOOLCHAIN_PREFIX'):
env['GCCPREFIX'] = os.path.join(
os.environ['CONFIG_TOOLCHAIN_PATH'].strip('"'),
os.environ['CONFIG_TOOLCHAIN_PREFIX'].strip('"')
)
# Add toolchain flags if specified
if os.environ.get('CONFIG_TOOLCHAIN_FLAGS'):
env.MergeFlags(os.environ['CONFIG_TOOLCHAIN_FLAGS'])
2025-09-17 10:30:24 +08:00
# Add build config path to include paths
env.Append(CPPPATH=[BUILD_CONFIG_PATH])
2025-09-17 10:30:24 +08:00
# Set up compiler tools
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__)}'
env['OBJCOPY'] = '${_concat(GCCPREFIX, "objcopy", GCCSUFFIX, __env__)}'
env['SIZE'] = '${_concat(GCCPREFIX, "size", GCCSUFFIX, __env__)}'
2025-09-17 10:30:24 +08:00
# Get GCC version and target information
2024-05-31 12:10:44 +08:00
try:
2025-09-17 10:30:24 +08:00
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 Exception as e:
logger.warning('Failed to obtain GCC parameters: {}'.format(e))
2024-05-31 12:10:44 +08:00
2025-09-17 10:30:24 +08:00
# Load git repository information
load_git_repos(env)
2024-04-10 12:21:46 +08:00
2025-09-17 10:30:24 +08:00
# Set project information
2024-04-10 12:21:46 +08:00
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')
2025-09-17 10:30:24 +08:00
# Initialize components list and environment
2024-04-10 12:21:46 +08:00
env['COMPONENTS'] = []
env['COMPONENTS_ENV'] = env.Clone()
env['COMPONENTS_PATH'] = [str(Path(SDK_PATH)/'components')]
2025-09-17 10:30:24 +08:00
# Add external component paths if specified
2024-08-02 17:34:35 +08:00
if 'EXT_COMPONENTS_PATH' in os.environ:
for ecp in os.environ['EXT_COMPONENTS_PATH'].split(':'):
env['COMPONENTS_PATH'].append(ecp)
2024-04-10 12:21:46 +08:00
2025-09-17 10:30:24 +08:00
def load_components():
"""Load components from component directories and main project"""
global env
# Load components from component directories
for component_path in env['COMPONENTS_PATH']:
if os.path.exists(component_path):
for component_name in os.listdir(component_path):
component_scons = str(Path(component_path)/component_name/'SConstruct')
if os.path.exists(component_scons):
env['component_dir'] = str(Path(component_path)/component_name)
SConscript(component_scons, exports='env')
2024-04-10 12:21:46 +08:00
2025-09-17 10:30:24 +08:00
# Load main project components
2024-04-10 12:21:46 +08:00
for project_dir in os.listdir(PROJECT_PATH):
if project_dir.startswith("main"):
2025-09-17 10:30:24 +08:00
main_scons = str(Path(PROJECT_PATH)/project_dir/'SConstruct')
if os.path.exists(main_scons):
env['component_dir'] = str(Path(PROJECT_PATH)/project_dir)
SConscript(main_scons, exports='env')
2024-04-10 12:21:46 +08:00
2025-09-17 10:30:24 +08:00
# Store components in task_lists
for item in env['COMPONENTS']:
task_lists[item['target']] = item
2024-04-10 12:21:46 +08:00
2025-09-17 10:30:24 +08:00
def deep_iter_or_return(obj):
"""Recursively iterate through nested iterables"""
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_object_path(src_file, component_build_dir):
"""Get the object file path for a source file"""
file = str(src_file)
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:] + env['OBJSUFFIX'])
else:
ofile = os.path.join(component_build_dir, ofile + env['OBJSUFFIX'])
return ofile
def create_empty_source_file(component_build_dir):
"""Create an empty source file for components with no sources"""
empty_src_file = str(Path(component_build_dir)/'empty_src_file.cpp')
if 'CONFIG_EMPTY_SRC_FILE_C' in os.environ:
empty_src_file = str(Path(component_build_dir)/'empty_src_file.c')
with open(empty_src_file, 'w'):
pass # Create empty file
return empty_src_file
def setup_build_environment(component, base_env):
"""Set up build environment for a component"""
component_build_env = base_env.Clone()
# Add include paths
component_build_env.Append(CPPPATH=component['INCLUDE'])
component_build_env.Append(CPPPATH=component['PRIVATE_INCLUDE'])
# Add definitions
component_build_env.MergeFlags(component['DEFINITIONS'])
component_build_env.MergeFlags(component['DEFINITIONS_PRIVATE'])
# Add linker flags
component_build_env.Append(LINKFLAGS=component['LDFLAGS'])
# Add library search paths
component_build_env.Append(LIBPATH=component['LINK_SEARCH_PATH'])
# Process requirements
required_libraries = []
for requirement in component['REQUIREMENTS']:
if requirement in task_lists:
req_component = task_lists[requirement]
# Add include paths from requirement
component_build_env.Append(CPPPATH=req_component['INCLUDE'])
# Add library
component_build_env.Append(LIBS=[requirement])
# Add library paths
component_build_env.Append(LIBPATH=str(Path('build')/requirement))
component_build_env.Append(LIBPATH=req_component['LINK_SEARCH_PATH'])
# Add linker flags
component_build_env.Append(LINKFLAGS=req_component['LDFLAGS'])
# Add definitions
component_build_env.MergeFlags(req_component['DEFINITIONS'])
# Add static and dynamic libraries
required_libraries += list(map(str, req_component['STATIC_LIB'] + req_component['DYNAMIC_LIB']))
# Add transitive requirements
for c_requirement in req_component['REQUIREMENTS']:
if c_requirement not in task_lists:
component_build_env.Append(LIBS=[c_requirement])
else:
component_build_env.Append(LIBS=[requirement])
return component_build_env, required_libraries
def build_static_library(component, build_env, source_files, object_files, custom_source_files, target_path):
"""Build a static library component"""
# Create object files
compiled_objects = list(map(lambda file: build_env.Object(target=file[0], source=file[1]), list(zip(object_files, source_files))))
# Add custom source files
for src_file in custom_source_files:
compiled_objects += build_env.Object(
target=custom_source_files[src_file]['SRCO'],
source=str(src_file),
CPPFLAGS=custom_source_files[src_file]['CPPFLAGS'],
CCFLAGS=custom_source_files[src_file]['CCFLAGS']
)
# Build library
component['_target'] = build_env.Library(target=target_path, source=compiled_objects)
component['_target_build_env'] = build_env
def build_shared_library(component, build_env, source_files, object_files, custom_source_files, target_path):
"""Build a shared library component"""
# Create shared object files
compiled_objects = list(map(lambda file: build_env.SharedObject(target=file[0], source=file[1]), list(zip(object_files, source_files))))
# Add custom source files
for src_file in custom_source_files:
compiled_objects += build_env.SharedObject(
target=custom_source_files[src_file]['SRCO'],
source=str(src_file),
CPPFLAGS=custom_source_files[src_file]['CPPFLAGS'],
CCFLAGS=custom_source_files[src_file]['CCFLAGS']
)
# Build shared library
component['_target'] = build_env.SharedLibrary(target=target_path, source=compiled_objects)
component['_target_build_env'] = build_env
# Copy to dist directory based on system type
if 'CONFIG_TOOLCHAIN_SYSTEM_UNIX' in os.environ:
build_env.Command(
os.path.join('dist', 'lib{}.so'.format(component["target"])),
str(Path('build')/component['target']/'lib{}.so'.format(component["target"])),
action=copy_file
)
elif 'CONFIG_TOOLCHAIN_SYSTEM_WIN' in os.environ:
build_env.Command(
os.path.join('dist', 'lib{}.dll'.format(component["target"])),
str(Path('build')/component['target']/'lib{}.dll'.format(component["target"])),
action=copy_file
)
def build_project(component, build_env, source_files, object_files, custom_source_files, target_path, required_libraries, component_build_dir):
"""Build a project component (executable)"""
# Create object files
compiled_objects = list(map(lambda file: build_env.Object(target=file[0], source=file[1]), list(zip(object_files, source_files))))
# Add custom source files
for src_file in custom_source_files:
compiled_objects += build_env.Object(
target=custom_source_files[src_file]['SRCO'],
source=str(src_file),
CPPFLAGS=custom_source_files[src_file]['CPPFLAGS'],
CCFLAGS=custom_source_files[src_file]['CCFLAGS']
)
# Add static and dynamic libraries
required_libraries += list(map(str, component['STATIC_LIB'] + component['DYNAMIC_LIB']))
# Build static library from objects
build_env.Library(target=target_path, source=compiled_objects)
itemized_srcs = []
if 'ITEMIZED_SRCS' in component:
itemized_srcs += component['ITEMIZED_SRCS']
# Create empty source file for linking
# if len(itemized_srcs) == 0:
itemized_srcs.append(create_empty_source_file(component_build_dir))
itemized_object_files = [get_object_path(src, component_build_dir) for src in itemized_srcs]
itemized_objects = list(map(lambda file: build_env.Object(target=file[0], source=file[1]), list(zip(itemized_object_files, itemized_srcs))))
# Add rpath for dynamic libraries if needed
if component['DYNAMIC_LIB']:
build_env.Append(LINKFLAGS=['-Wl,-rpath=./'])
# Build program
component['_target'] = build_env.Program(
target=target_path,
source=[itemized_objects, '{}/lib{}.a'.format(component_build_dir, component['target'])] + required_libraries
)
component['_target_build_env'] = build_env
# Copy to dist directory based on system type
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
)
# Copy dynamic libraries to dist directory
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
)
# Copy static files to dist directory if specified
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 create_compile_program():
"""Create compilation targets for all components"""
global env, task_lists
# Create build directory if it doesn't exist
2024-04-10 12:21:46 +08:00
if not os.path.exists('build'):
os.mkdir('build')
2025-09-17 10:30:24 +08:00
# Create dist directory if it doesn't exist
if not os.path.exists('dist'):
os.mkdir('dist')
# Process each component
2024-04-10 12:21:46 +08:00
for task in task_lists:
component = task_lists[task]
2025-09-17 10:30:24 +08:00
# Create component build directory
2024-04-10 12:21:46 +08:00
component_build_dir = str(Path('build')/component['target'])
component['_component_build_dir'] = component_build_dir
if not os.path.exists(component_build_dir):
os.mkdir(component_build_dir)
2025-09-17 10:30:24 +08:00
# Get source files and object files
source_files = [str(o) for o in deep_iter_or_return(component['SRCS'])]
object_files = [get_object_path(src, component_build_dir) for src in source_files]
# Process custom source files
custom_source_files = {}
if 'SRCS_CUSTOM' in component:
custom_source_files = component['SRCS_CUSTOM']
custom_sources_list = list(custom_source_files.keys())
custom_objects_list = [get_object_path(src, component_build_dir) for src in custom_sources_list]
2024-04-10 12:21:46 +08:00
2025-09-17 10:30:24 +08:00
for index, obj in enumerate(custom_sources_list):
custom_source_files[obj]['SRCO'] = custom_objects_list[index]
# Create empty source file if no sources
if len(source_files) == 0:
empty_src_file = create_empty_source_file(component_build_dir)
source_files.append(empty_src_file)
object_files.append(empty_src_file + env['OBJSUFFIX'])
# Set up build environment
target_path = str(Path(component_build_dir)/component['target'])
component_build_env, required_libraries = setup_build_environment(component, env)
# Build component based on its type
if component['REGISTER'] == 'static':
build_static_library(component, component_build_env, source_files, object_files, custom_source_files, target_path)
elif component['REGISTER'] == 'shared':
build_shared_library(component, component_build_env, source_files, object_files, custom_source_files, target_path)
2024-04-10 12:21:46 +08:00
elif component['REGISTER'] == 'project':
2025-09-17 10:30:24 +08:00
build_project(component, component_build_env, source_files, object_files, custom_source_files, target_path, required_libraries, component_build_dir)
else:
logger.error("Unknown component type: {}".format(component['REGISTER']))
sys.exit(1)
2024-06-03 20:01:31 +08:00
2025-09-17 10:30:24 +08:00
def add_compile_program_requirements():
"""Add dependencies between components"""
global task_lists
2024-04-10 12:21:46 +08:00
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'])
2025-09-17 10:30:24 +08:00
def build_task_init():
"""Initialize the build task"""
global env
# Check for special commands
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]()
2024-04-10 12:21:46 +08:00
2025-09-17 10:30:24 +08:00
# Generate configuration if it doesn't exist
if not os.path.exists(GLOBAL_CONFIG_MK_FILE):
os.makedirs(BUILD_CONFIG_PATH, exist_ok=True)
run_kconfig_tool(menuconfig=False)
# Load configuration
load_config_mk()
# Set up build environment
setup_environment()
# Load components
load_components()
# Main execution
2024-04-10 12:21:46 +08:00
build_task_init()
2025-09-17 10:30:24 +08:00
create_compile_program()
add_compile_program_requirements()
env['task_list'] = task_lists
Return('env')