first commit for chrg

This commit is contained in:
wmano
2025-08-16 22:58:22 +08:00
commit 52a3ed5862
2306 changed files with 1021208 additions and 0 deletions
+437
View File
@@ -0,0 +1,437 @@
import re
import pprint
import os
from subprocess import check_output
from optparse import OptionParser
# Constants
rtl_ext_end = ".dfinish"
rtl_ext = None # e.g. '.c.270r.dfinish'. The number '270' will change with gcc version and is auto-detected by the
# function find_rtl_ext
dir = r'.' # Working directory
su_ext = '.su'
obj_ext = '.o'
manual_ext = '.msu'
read_elf_path = "arm-none-eabi-readelf.exe" # You may need to enter the full path here
stdout_encoding = "utf-8" # System dependant
class Printable:
def __repr__(self):
return "<" + type(self).__name__ + "> " + pprint.pformat(vars(self), indent=4, width=1)
class Symbol(Printable):
pass
def read_symbols(file):
from subprocess import check_output
def to_symbol(read_elf_line):
v = read_elf_line.split()
s2 = Symbol()
s2.value = int(v[1], 16)
s2.size = int(v[2])
s2.type = v[3]
s2.binding = v[4]
if len(v) >= 8:
s2.name = v[7]
else:
s2.name = ""
return s2
output = check_output([read_elf_path, "-s", "-W", file]).decode(stdout_encoding)
lines = output.splitlines()[3:]
return [to_symbol(line) for line in lines]
def read_obj(tu, call_graph):
"""
Reads the file tu.o and gets the binding (global or local) for each function
:param tu: name of the translation unit (e.g. for main.c, this would be 'main')
:param call_graph: a object used to store information about each function, results go here
"""
symbols = read_symbols(tu[0:tu.rindex(".")] + obj_ext)
for s in symbols:
if s.type == 'FUNC':
if s.binding == 'GLOBAL':
# Check for multiple declarations
if s.name in call_graph['globals'] or s.name in call_graph['locals']:
raise Exception('Multiple declarations of {}'.format(s.name))
call_graph['globals'][s.name] = {'tu': tu, 'name': s.name, 'binding': s.binding}
elif s.binding == 'LOCAL':
# Check for multiple declarations
if s.name in call_graph['locals'] and tu in call_graph['locals'][s.name]:
raise Exception('Multiple declarations of {}'.format(s.name))
if s.name not in call_graph['locals']:
call_graph['locals'][s.name] = {}
call_graph['locals'][s.name][tu] = {'tu': tu, 'name': s.name, 'binding': s.binding}
elif s.binding == 'WEAK':
if s.name in call_graph['weak']:
raise Exception('Multiple declarations of {}'.format(s.name))
call_graph['weak'][s.name] = {'tu': tu, 'name': s.name, 'binding': s.binding}
else:
raise Exception('Error Unknown Binding "{}" for symbol: {}'.format(s.binding, s.name))
def find_fxn(tu, fxn, call_graph):
"""
Looks up the dictionary associated with the function.
:param tu: The translation unit in which to look for locals functions
:param fxn: The function name
:param call_graph: a object used to store information about each function
:return: the dictionary for the given function or None
"""
if fxn in call_graph['globals']:
return call_graph['globals'][fxn]
else:
try:
return call_graph['locals'][fxn][tu]
except KeyError:
return None
def find_demangled_fxn(tu, fxn, call_graph):
"""
Looks up the dictionary associated with the function.
:param tu: The translation unit in which to look for locals functions
:param fxn: The function name
:param call_graph: a object used to store information about each function
:return: the dictionary for the given function or None
"""
for f in call_graph['globals'].values():
if 'demangledName' in f:
if f['demangledName'] == fxn:
return f
for f in call_graph['locals'].values():
if tu in f:
if 'demangledName' in f[tu]:
if f[tu]['demangledName'] == fxn:
return f[tu]
return None
def read_rtl(tu, call_graph):
"""
Read an RTL file and finds callees for each function and if there are calls via function pointer.
:param tu: the translation unit
:param call_graph: a object used to store information about each function, results go here
"""
# Construct A Call Graph
function = re.compile(r'^;; Function (.*) \((\S+), funcdef_no=\d+(, [a-z_]+=\d+)*\)( \([a-z ]+\))?$')
static_call = re.compile(r'^.*\(call.*"(.*)".*$')
other_call = re.compile(r'^.*call .*$')
for line_ in open(tu + rtl_ext).readlines():
m = function.match(line_)
if m:
fxn_name = m.group(2)
fxn_dict2 = find_fxn(tu, fxn_name, call_graph)
if not fxn_dict2:
pprint.pprint(call_graph)
raise Exception("Error locating function {} in {}".format(fxn_name, tu))
fxn_dict2['demangledName'] = m.group(1)
fxn_dict2['calls'] = set()
fxn_dict2['has_ptr_call'] = False
continue
m = static_call.match(line_)
if m:
fxn_dict2['calls'].add(m.group(1))
# print("Call: {0} -> {1}".format(current_fxn, m.group(1)))
continue
m = other_call.match(line_)
if m:
fxn_dict2['has_ptr_call'] = True
continue
def read_su(tu, call_graph):
"""
Reads the 'local_stack' for each function. Local stack ignores stack used by callees.
:param tu: the translation unit
:param call_graph: a object used to store information about each function, results go here
:return:
"""
su_line = re.compile(r'^([^ :]+):([\d]+):([\d]+):(.+)\t(\d+)\t(\S+)$')
i = 1
for line in open(tu[0:tu.rindex(".")] + su_ext).readlines():
m = su_line.match(line)
if m:
fxn = m.group(4)
fxn_dict2 = find_demangled_fxn(tu, fxn, call_graph)
fxn_dict2['local_stack'] = int(m.group(5))
else:
print("error parsing line {} in file {}".format(i, tu))
i += 1
def read_manual(file, call_graph):
"""
reads the manual stack useage files.
:param file: the file name
:param call_graph: a object used to store information about each function, results go here
"""
for line in open(file).readlines():
fxn, stack_sz = line.split()
if fxn in call_graph:
raise Exception("Redeclared Function {}".format(fxn))
call_graph['globals'][fxn] = {'wcs': int(stack_sz),
'calls': set(),
'has_ptr_call': False,
'local_stack': int(stack_sz),
'is_manual': True,
'name': fxn,
'tu': '#MANUAL',
'binding': 'GLOBAL'}
def validate_all_data(call_graph):
"""
Check that every entry in the call graph has the following fields:
.calls, .has_ptr_call, .local_stack, .scope, .src_line
"""
def validate_dict(d):
if not ('calls' in d and 'has_ptr_call' in d and 'local_stack' in d
and 'name' in d and 'tu' in d):
print("Error data is missing in fxn dictionary {}".format(d))
# Loop through every global and local function
# and resolve each call, save results in r_calls
for fxn_dict2 in call_graph['globals'].values():
validate_dict(fxn_dict2)
for l_dict in call_graph['locals'].values():
for fxn_dict2 in l_dict.values():
validate_dict(fxn_dict2)
def resolve_all_calls(call_graph):
def resolve_calls(fxn_dict2):
fxn_dict2['r_calls'] = []
fxn_dict2['unresolved_calls'] = set()
for call in fxn_dict2['calls']:
call_dict = find_fxn(fxn_dict2['tu'], call, call_graph)
if call_dict:
fxn_dict2['r_calls'].append(call_dict)
else:
fxn_dict2['unresolved_calls'].add(call)
# Loop through every global and local function
# and resolve each call, save results in r_calls
for fxn_dict in call_graph['globals'].values():
resolve_calls(fxn_dict)
for l_dict in call_graph['locals'].values():
for fxn_dict in l_dict.values():
resolve_calls(fxn_dict)
def calc_all_wcs(call_graph):
def calc_wcs(fxn_dict2, call_graph1, parents):
"""
Calculates the worst case stack for a fxn that is declared (or called from) in a given file.
:param parents: This function gets called recursively through the call graph. If a function has recursion the
tuple file, fxn will be in the parents stack and everything between the top of the stack and the matching entry
has recursion.
:return:
"""
# If the wcs is already known, then nothing to do
if 'wcs' in fxn_dict2:
return
# Check for pointer calls
if fxn_dict2['has_ptr_call']:
fxn_dict2['wcs'] = 'unbounded'
return
# Check for recursion
if fxn_dict2 in parents:
fxn_dict2['wcs'] = 'unbounded'
return
# Calculate WCS
call_max = 0
for call_dict in fxn_dict2['r_calls']:
# Calculate the WCS for the called function
parents.append(fxn_dict2)
calc_wcs(call_dict, call_graph1, parents)
parents.pop()
# If the called function is unbounded, so is this function
if call_dict['wcs'] == 'unbounded':
fxn_dict2['wcs'] = 'unbounded'
return
# Keep track of the call with the largest stack use
call_max = max(call_max, call_dict['wcs'])
# Propagate Unresolved Calls
for unresolved_call in call_dict['unresolved_calls']:
fxn_dict2['unresolved_calls'].add(unresolved_call)
fxn_dict2['wcs'] = call_max + fxn_dict2['local_stack']
# Loop through every global and local function
# and resolve each call, save results in r_calls
for fxn_dict in call_graph['globals'].values():
calc_wcs(fxn_dict, call_graph, [])
for l_dict in call_graph['locals'].values():
for fxn_dict in l_dict.values():
calc_wcs(fxn_dict, call_graph, [])
def print_all_fxns(call_graph):
def print_fxn(row_format, fxn_dict2):
unresolved = fxn_dict2['unresolved_calls']
stack = str(fxn_dict2['wcs'])
if unresolved:
unresolved_str = '({})'.format(' ,'.join(unresolved))
if stack != 'unbounded':
stack = "unbounded:" + stack
else:
unresolved_str = ''
print(row_format.format(fxn_dict2['tu'], fxn_dict2['demangledName'], stack, unresolved_str))
def get_order(val):
if val == 'unbounded':
return 1
else:
return -val
# Loop through every global and local function
# and resolve each call, save results in r_calls
d_list = []
for fxn_dict in call_graph['globals'].values():
d_list.append(fxn_dict)
for l_dict in call_graph['locals'].values():
for fxn_dict in l_dict.values():
d_list.append(fxn_dict)
d_list.sort(key=lambda item: get_order(item['wcs']))
# Calculate table width
tu_width = max(max([len(d['tu']) for d in d_list]), 16)
name_width = max(max([len(d['name']) for d in d_list]), 13)
row_format = "{:<" + str(tu_width + 2) + "} {:<" + str(name_width + 2) + "} {:>14} {:<17}"
# Print out the table
print("")
print(row_format.format('Translation Unit', 'Function Name', 'Stack', 'Unresolved Dependencies'))
for d in d_list:
print_fxn(row_format, d)
def find_rtl_ext():
# Find the rtl_extension
global rtl_ext
for root, directories, filenames in os.walk('.'):
for f in filenames:
if (f.endswith(rtl_ext_end)):
rtl_ext = f[f[:-len(rtl_ext_end)].rindex("."):]
print("rtl_ext = " + rtl_ext)
return
print("Could not find any files ending with '.dfinish'. Check that the script is being run from the correct "
"directory. Check that the code was compiled with the correct flags")
exit(-1)
def find_files():
tu = []
manual = []
all_files = []
for root, directories, filenames in os.walk(dir):
for filename in filenames:
all_files.append(os.path.join(root,filename))
files = [f for f in all_files if os.path.isfile(f) and f.endswith(rtl_ext)]
for f in files:
base = f[0:-len(rtl_ext)]
short_base = base[0:base.rindex(".")]
if short_base + su_ext in all_files and short_base + obj_ext in all_files:
tu.append(base)
print('Reading: {}{}, {}{}, {}{}'.format(base, rtl_ext, short_base, su_ext, short_base, obj_ext))
files = [f for f in all_files if os.path.isfile(f) and f.endswith(manual_ext)]
for f in files:
manual.append(f)
print('Reading: {}'.format(f))
# Print some diagnostic messages
if not tu:
print("Could not find any translation units to analyse")
exit(-1)
return tu, manual
def main():
# Find the appropriate RTL extension
find_rtl_ext()
# Find all input files
call_graph = {'locals': {}, 'globals': {}, 'weak': {}}
tu_list, manual_list = find_files()
# Read the input files
for tu in tu_list:
read_obj(tu, call_graph) # This must be first
for fxn in call_graph['weak'].values():
if fxn['name'] not in call_graph['globals'].keys():
call_graph['globals'][fxn['name']] = fxn
for tu in tu_list:
read_rtl(tu, call_graph)
for tu in tu_list:
read_su(tu, call_graph)
# Read manual files
for m in manual_list:
read_manual(m, call_graph)
# Validate Data
validate_all_data(call_graph)
# Resolve All Function Calls
resolve_all_calls(call_graph)
# Calculate Worst Case Stack For Each Function
calc_all_wcs(call_graph)
# Print A Nice Message With Each Function and the WCS
print_all_fxns(call_graph)
def ThreadStackStaticAnalysis(env):
print('Start thread stack static analysis...')
import rtconfig
read_elf_path = rtconfig.EXEC_PATH + r'\readelf.exe'
main()
print('\nThread stack static analysis done!')
return
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
astyle --style=allman --indent=spaces=4 --pad-oper --pad-header --unpad-paren --suffix=none --align-pointer=name --lineend=linux --convert-tabs --verbose $1
+87
View File
@@ -0,0 +1,87 @@
import os
import sys
import shutil
import yaml
from SCons.Script import *
# SCons AttachConfig Command Function
def GenAttachConfigProject(program = None):
Rtt_Root = os.getcwd()
config_file = os.path.join(os.getcwd(), 'rtt_root', Rtt_Root, '.config')
config_bacakup = config_file+'.origin'
rtconfig_file = os.path.join(os.getcwd(), 'rtt_root', Rtt_Root, 'rtconfig.h')
rtconfig__bacakup = rtconfig_file+'.origin'
if GetOption('attach') == '?':
attachconfig=[]
GetAttachConfig("get",attachconfig,0)
print("\033[32m✅ AttachConfig has: \033[0m")
prefix=attachconfig[0]
for line in attachconfig:
temp_prefix=line.split(".", 1)
if prefix!=temp_prefix[0]:
print("\033[42m \033[30m------"+temp_prefix[0]+"------\033[0m")
prefix=temp_prefix[0]
print(line)
elif GetOption('attach') == 'default':
if os.path.exists(config_bacakup):
shutil.copyfile(config_bacakup, config_file)
os.remove(config_bacakup)
if os.path.exists(rtconfig__bacakup):
shutil.copyfile(rtconfig__bacakup, rtconfig_file)
os.remove(rtconfig__bacakup)
print("\033[32m✅ Default .config and rtconfig.h recovery success!\033[0m")
else:
attachconfig=GetOption('attach')
attachconfig_result=[]
GetAttachConfig("search",attachconfig,attachconfig_result)
if attachconfig_result==[]:
print("\033[31m Without this AttachConfig:"+attachconfig+"\033[0m")
return
if os.path.exists(config_bacakup)==False:
shutil.copyfile(config_file, config_bacakup)
if os.path.exists(rtconfig__bacakup)==False:
shutil.copyfile(rtconfig_file, rtconfig__bacakup)
with open(config_file, 'a') as destination:
for line in attachconfig_result:
destination.write(line + '\n')
from env_utility import defconfig
defconfig(Rtt_Root)
print("\033[32m✅ AttachConfig add success!\033[0m")
def GetAttachConfig(action,attachconfig,attachconfig_result):
rtt_root = os.getcwd()
yml_files_content = []
directory = os.path.join(rtt_root, 'rtt_root', rtt_root, '.ci/attachconfig')
if os.path.exists(directory):
for root, dirs, files in os.walk(directory):
for filename in files:
if filename.endswith('attachconfig.yml'):
file_path = os.path.join(root, filename)
if os.path.exists(file_path):
try:
with open(file_path, 'r') as file:
content = yaml.safe_load(file)
if content is None:
continue
yml_files_content.append(content)
except yaml.YAMLError as e:
print(f"::error::Error parsing YAML file: {e}")
continue
except Exception as e:
print(f"::error::Error reading file: {e}")
continue
for projects in yml_files_content:
for name, details in projects.items():
if details.get("kconfig") is None:
continue
if(projects.get(name) is not None):
if action == "get":
attachconfig.append(name)
if action == "search" and name == attachconfig:
from ci.bsp_buildings import get_details_and_dependencies
detail_list=get_details_and_dependencies([name],projects)
for line in detail_list:
attachconfig_result.append(line)
File diff suppressed because it is too large Load Diff
+373
View File
@@ -0,0 +1,373 @@
#
# Copyright (c) 2025, RT-Thread Development Team
#
# SPDX-License-Identifier: Apache-2.0
#
# Change Logs:
# Date Author Notes
# 2025-04-21 supperthomas add the smart yml support and add env
#
import subprocess
import threading
import time
import logging
import sys
import os
import shutil
import re
import multiprocessing
import yaml
def add_summary(text):
"""
add summary to github action.
"""
os.system(f'echo "{text}" >> $GITHUB_STEP_SUMMARY ;')
def run_cmd(cmd, output_info=True):
"""
run command and return output and result.
"""
print('\033[1;32m' + cmd + '\033[0m')
output_str_list = []
res = 0
if output_info:
res = os.system(cmd + " > output.txt 2>&1")
else:
res = os.system(cmd + " > /dev/null 2>output.txt")
with open("output.txt", "r") as file:
output_str_list = file.readlines()
for line in output_str_list:
print(line, end='')
os.remove("output.txt")
return output_str_list, res
def build_bsp(bsp, scons_args='',name='default', pre_build_commands=None, post_build_command=None,build_check_result = None,bsp_build_env=None):
"""
build bsp.
cd {rtt_root}
scons -C bsp/{bsp} --pyconfig-silent > /dev/null
cd {rtt_root}/bsp/{bsp}
pkgs --update > /dev/null
pkgs --list
cd {rtt_root}
scons -C bsp/{bsp} -j{nproc} {scons_args}
cd {rtt_root}/bsp/{bsp}
scons -c > /dev/null
rm -rf packages
"""
success = True
# 设置环境变量
if bsp_build_env is not None:
print("Setting environment variables:")
for key, value in bsp_build_env.items():
print(f"{key}={value}")
os.environ[key] = value # 设置环境变量
os.chdir(rtt_root)
os.makedirs(f'{rtt_root}/output/bsp/{bsp}', exist_ok=True)
if os.path.exists(f"{rtt_root}/bsp/{bsp}/Kconfig"):
os.chdir(rtt_root)
run_cmd(f'scons -C bsp/{bsp} --pyconfig-silent', output_info=True)
os.chdir(f'{rtt_root}/bsp/{bsp}')
run_cmd('pkgs --update-force', output_info=True)
run_cmd('pkgs --list')
nproc = multiprocessing.cpu_count()
if pre_build_commands is not None:
print("Pre-build commands:")
print(pre_build_commands)
for command in pre_build_commands:
print(command)
output, returncode = run_cmd(command, output_info=True)
print(output)
if returncode != 0:
print(f"Pre-build command failed: {command}")
print(output)
os.chdir(rtt_root)
# scons 编译命令
cmd = f'scons -C bsp/{bsp} -j{nproc} {scons_args}' # --debug=time for debug time
output, res = run_cmd(cmd, output_info=True)
if build_check_result is not None:
if res != 0 or not check_output(output, build_check_result):
print("Build failed or build check result not found")
print(output)
if res != 0:
success = False
else:
#拷贝当前的文件夹下面的所有以elf结尾的文件拷贝到rt-thread/output文件夹下
import glob
# 拷贝编译生成的文件到output目录,文件拓展为 elf,bin,hex
for file_type in ['*.elf', '*.bin', '*.hex']:
files = glob.glob(f'{rtt_root}/bsp/{bsp}/{file_type}')
for file in files:
shutil.copy(file, f'{rtt_root}/output/bsp/{bsp}/{name.replace("/", "_")}.{file_type[2:]}')
os.chdir(f'{rtt_root}/bsp/{bsp}')
if post_build_command is not None:
for command in post_build_command:
output, returncode = run_cmd(command, output_info=True)
print(output)
if returncode != 0:
print(f"Post-build command failed: {command}")
print(output)
run_cmd('scons -c', output_info=False)
return success
def append_file(source_file, destination_file):
"""
append file to another file.
"""
with open(source_file, 'r') as source:
with open(destination_file, 'a') as destination:
for line in source:
destination.write(line)
def check_scons_args(file_path):
args = []
with open(file_path, 'r') as file:
for line in file:
match = re.search(r'#\s*scons:\s*(.*)', line)
if match:
args.append(match.group(1).strip())
return ' '.join(args)
def get_details_and_dependencies(details, projects, seen=None):
if seen is None:
seen = set()
detail_list = []
if details is not None:
for dep in details:
if dep not in seen:
dep_details=projects.get(dep)
seen.add(dep)
if dep_details is not None:
if dep_details.get('depends') is not None:
detail_temp=get_details_and_dependencies(dep_details.get('depends'), projects, seen)
for line in detail_temp:
detail_list.append(line)
if dep_details.get('kconfig') is not None:
for line in dep_details.get('kconfig'):
detail_list.append(line)
else:
print(f"::error::There are some problems with attachconfig depend: {dep}");
return detail_list
def build_bsp_attachconfig(bsp, attach_file):
"""
build bsp with attach config.
cp bsp/{bsp}/.config bsp/{bsp}/.config.origin
cat .ci/attachconfig/{attach_file} >> bsp/{bsp}/.config
build_bsp()
cp bsp/{bsp}/.config.origin bsp/{bsp}/.config
rm bsp/{bsp}/.config.origin
"""
config_file = os.path.join(rtt_root, 'bsp', bsp, '.config')
config_bacakup = config_file+'.origin'
shutil.copyfile(config_file, config_bacakup)
attachconfig_dir = os.path.join(rtt_root, 'bsp', bsp, '.ci/attachconfig')
attach_path = os.path.join(attachconfig_dir, attach_file)
append_file(attach_path, config_file)
scons_args = check_scons_args(attach_path)
res = build_bsp(bsp, scons_args,name=attach_file)
shutil.copyfile(config_bacakup, config_file)
os.remove(config_bacakup)
return res
def check_output(output, check_string):
"""检查输出中是否包含指定字符串"""
output_str = ''.join(output) if isinstance(output, list) else str(output)
flag = check_string in output_str
if flag == True:
print('Success: find string ' + check_string)
else:
print(output)
print(f"::error:: can not find string {check_string} output: {output_str}")
return flag
if __name__ == "__main__":
"""
build all bsp and attach config.
1. build all bsp.
2. build all bsp with attach config.
"""
failed = 0
count = 0
ci_build_run_flag = False
qemu_timeout_second = 50
rtt_root = os.getcwd()
srtt_bsp = os.getenv('SRTT_BSP').split(',')
print(srtt_bsp)
for bsp in srtt_bsp:
count += 1
print(f"::group::Compiling BSP: =={count}=== {bsp} ====")
res = build_bsp(bsp)
if not res:
print(f"::error::build {bsp} failed")
add_summary(f"- ❌ build {bsp} failed.")
failed += 1
else:
add_summary(f'- ✅ build {bsp} success.')
print("::endgroup::")
yml_files_content = []
directory = os.path.join(rtt_root, 'bsp', bsp, '.ci/attachconfig')
if os.path.exists(directory):
for root, dirs, files in os.walk(directory):
for filename in files:
if filename.endswith('attachconfig.yml'):
file_path = os.path.join(root, filename)
if os.path.exists(file_path):
try:
with open(file_path, 'r') as file:
content = yaml.safe_load(file)
if content is None:
continue
yml_files_content.append(content)
except yaml.YAMLError as e:
print(f"::error::Error parsing YAML file: {e}")
continue
except Exception as e:
print(f"::error::Error reading file: {e}")
continue
config_file = os.path.join(rtt_root, 'bsp', bsp, '.config')
# 在使用 pre_build_commands 之前,确保它被定义
pre_build_commands = None
build_command = None
post_build_command = None
qemu_command = None
build_check_result = None
commands = None
check_result = None
bsp_build_env = None
for projects in yml_files_content:
for name, details in projects.items():
# 如果是bsp_board_info,读取基本的信息
if(name == 'bsp_board_info'):
print(details)
pre_build_commands = details.get("pre_build").splitlines()
build_command = details.get("build_cmd").splitlines()
post_build_command = details.get("post_build").splitlines()
qemu_command = details.get("run_cmd")
if details.get("kconfig") is not None:
if details.get("buildcheckresult") is not None:
build_check_result = details.get("buildcheckresult")
else:
build_check_result = None
if details.get("msh_cmd") is not None:
commands = details.get("msh_cmd").splitlines()
else:
msh_cmd = None
if details.get("checkresult") is not None:
check_result = details.get("checkresult")
else:
check_result = None
if details.get("ci_build_run_flag") is not None:
ci_build_run_flag = details.get("ci_build_run_flag")
else:
ci_build_run_flag = None
if details.get("pre_build") is not None:
pre_build_commands = details.get("pre_build").splitlines()
if details.get("env") is not None:
bsp_build_env = details.get("env")
else:
bsp_build_env = None
if details.get("build_cmd") is not None:
build_command = details.get("build_cmd").splitlines()
else:
build_command = None
if details.get("post_build") is not None:
post_build_command = details.get("post_build").splitlines()
if details.get("run_cmd") is not None:
qemu_command = details.get("run_cmd")
else:
qemu_command = None
count += 1
config_bacakup = config_file+'.origin'
shutil.copyfile(config_file, config_bacakup)
#加载yml中的配置放到.config文件
with open(config_file, 'a') as destination:
if details.get("kconfig") is None:
#如果没有Kconfig,读取下一个配置
continue
if(projects.get(name) is not None):
# 获取Kconfig中所有的信息
detail_list=get_details_and_dependencies([name],projects)
for line in detail_list:
destination.write(line + '\n')
scons_arg=[]
#如果配置中有scons_arg
if details.get('scons_arg') is not None:
for line in details.get('scons_arg'):
scons_arg.append(line)
scons_arg_str=' '.join(scons_arg) if scons_arg else ' '
print(f"::group::\tCompiling yml project: =={count}==={name}=scons_arg={scons_arg_str}==")
# #开始编译bsp
res = build_bsp(bsp, scons_arg_str,name=name,pre_build_commands=pre_build_commands,post_build_command=post_build_command,build_check_result=build_check_result,bsp_build_env =bsp_build_env)
if not res:
print(f"::error::build {bsp} {name} failed.")
add_summary(f'\t- ❌ build {bsp} {name} failed.')
failed += 1
else:
add_summary(f'\t- ✅ build {bsp} {name} success.')
print("::endgroup::")
shutil.copyfile(config_bacakup, config_file)
os.remove(config_bacakup)
attach_dir = os.path.join(rtt_root, 'bsp', bsp, '.ci/attachconfig')
attach_list = []
#这里是旧的文件方式
for root, dirs, files in os.walk(attach_dir):
for file in files:
if file.endswith('attach'):
file_path = os.path.join(root, file)
relative_path = os.path.relpath(file_path, attach_dir)
attach_list.append(relative_path)
for attach_file in attach_list:
count += 1
print(f"::group::\tCompiling BSP: =={count}=== {bsp} {attach_file}===")
res = build_bsp_attachconfig(bsp, attach_file)
if not res:
print(f"::error::build {bsp} {attach_file} failed.")
add_summary(f'\t- ❌ build {attach_file} failed.')
failed += 1
else:
add_summary(f'\t- ✅ build {attach_file} success.')
print("::endgroup::")
exit(failed)
+206
View File
@@ -0,0 +1,206 @@
#
# Copyright (c) 2024, RT-Thread Development Team
#
# SPDX-License-Identifier: Apache-2.0
#
# Change Logs:
# Date Author Notes
# 2024-08-24 supperthomas the first version
#
# 这个文件会根据bsp中的信息生成对应的bsp_detail.yml文件,这个文件会包含bsp中的一些信息,比如是否有Kconfig文件,是否有template.uvprojx文件等等
# 根据生成的bsp_detail.yml文件,会生成一个toolchain_bsp.yml文件,这个文件会包含所有的gcc编译器的信息,以及对应的bsp文件夹
import os
import pandas as pd
import yaml
from datetime import datetime
import subprocess
#pip install pandas
#pip install tabulate
#pip install pyyaml
# 添加每个工具链的下载地址
download_urls = {
'arm-none-eabi-gcc': 'https://github.com/RT-Thread/toolchains-ci/releases/download/v1.3/gcc-arm-none-eabi-10-2020-q4-major-x86_64-linux.tar.bz2',
'mips-sde-elf-gcc': 'https://github.com/RT-Thread/toolchains-ci/releases/download/v1.6/gcc-arm-10.2-2020.11-x86_64-aarch64-none-elf.tar.xz',
'riscv64-unknown-elf-gcc': 'https://github.com/RT-Thread/toolchains-ci/releases/download/v1.4/riscv64-unknown-elf-toolchain-10.2.0-2020.12.8-x86_64-linux-ubuntu14.tar.gz',
'riscv32-unknown-elf-gcc': 'https://github.com/hpmicro/riscv-gnu-toolchain/releases/download/2022.05.15/riscv32-unknown-elf-newlib-multilib_2022.05.15_linux.tar.gz',
'llvm-arm': 'https://github.com/ARM-software/LLVM-embedded-toolchain-for-Arm/releases/download/release-16.0.0/LLVMEmbeddedToolchainForArm-16.0.0-Linux-x86_64.tar.gz',
'riscv-none-embed-gcc': 'https://github.com/RT-Thread/toolchains-ci/releases/download/v1.5/xpack-riscv-none-embed-gcc-8.3.0-2.3-linux-x64.tar.gz',
'riscv32-esp-elf-gcc': 'https://github.com/espressif/crosstool-NG/releases/download/esp-2022r1-RC1/riscv32-esp-elf-gcc11_2_0-esp-2022r1-RC1-linux-amd64.tar.xz',
'clang': 'https://github.com/ARM-software/LLVM-embedded-toolchain-for-Arm/releases/download/release-16.0.0/LLVMEmbeddedToolchainForArm-16.0.0-Linux-x86_64.tar.gz',
# 添加其他工具链的下载地址
}
# 产生toolchain.yml文件
def generate_toolchain_yaml(input_file, output_file, header_comment):
with open(input_file, 'r', encoding='utf-8') as file:
data = yaml.safe_load(file)
toolchain_data = {}
for folder, details in data.items():
gcc = details.get('gcc')
if gcc:
if gcc not in toolchain_data:
toolchain_data[gcc] = {'bsp': []}
toolchain_data[gcc]['bsp'].append(folder)
# 添加每个工具链的个数
for gcc, details in toolchain_data.items():
details['count'] = len(details['bsp'])
download_url = download_urls.get(gcc)
if download_url:
details['download_url'] = download_url
with open(output_file, 'w', encoding='utf-8') as file:
file.write(f"# {header_comment}\n")
yaml.dump(toolchain_data, file, default_flow_style=False, allow_unicode=True)
# 这个函数通过检查文件是否存在来检查bsp的支持情况
def check_files(root_dir, file_list):
data = []
folders_checked = set()
for projects in sconstruct_paths:
found_arch = False # Flag to track if ARCH has been found
found_cpu = False # Flag to track if ARCH has been found
if projects not in folders_checked:
#file_dict = {file: True if os.path.isfile(os.path.join(projects, file)) else '' for file in file_list}
file_dict = {}
for file in file_list:
file_exists = os.path.isfile(os.path.join(projects, file))
if file == 'template.uvprojx':
file_dict['mdk5'] = True if file_exists else False
elif file == 'template.ewp':
file_dict['iar'] = True if file_exists else False
elif file == 'template.uvproj':
file_dict['mdk4'] = True if file_exists else False
elif file == 'template.Uv2':
file_dict['mdk3'] = True if file_exists else False
elif file == 'Kconfig':
file_dict['menuconfig'] = True if file_exists else False
else:
file_dict[file] = True if file_exists else False
# 提取 rtconfig.py 中的 PREFIX 信息
rtconfig_path = os.path.join(projects, 'rtconfig.py')
if os.path.isfile(rtconfig_path):
print(f"Reading {rtconfig_path}")
with open(rtconfig_path, 'r') as f:
for line in f:
if not found_arch and line.strip().startswith('ARCH'):
arch_value = line.split('=')[1].strip().strip("'\"")
file_dict['arch'] = f"{arch_value}"
print(f"Found ARCH: {arch_value} in {rtconfig_path}")
found_arch = True # Set the flag to True
# 解析CPU属性
if not found_cpu and line.strip().startswith('CPU'):
cpu_value = line.split('=')[1].strip().strip("'\"")
file_dict['cpu'] = f"{cpu_value}"
print(f"Found CPU: {cpu_value} in {rtconfig_path}")
found_cpu = True
if line.strip().startswith('PREFIX'):
prefix_value = line.split('=')[1].strip().strip("'\"")
# 只提取实际的编译器前缀
if 'os.getenv' in prefix_value:
prefix_value = prefix_value.split('or')[-1].strip().strip("'\"")
file_dict['gcc'] = f"{prefix_value}gcc"
print(f"Found PREFIX: {prefix_value} in {rtconfig_path}")
break
else:
print(f"No PREFIX found in {rtconfig_path}")
# 去掉路径中的 '/workspaces/rt-thread/bsp/' 部分
projects2 = projects.replace(root_dir + '/', '')
file_dict['Folder'] = projects2
data.append(file_dict)
#data.append({'Folder': projects2, **file_dict})
folders_checked.add(projects)
df = pd.DataFrame(data)
return df
def find_sconstruct_paths(project_dir, exclude_paths):
sconstruct_paths = []
for root, dirs, files in os.walk(project_dir):
if all(exclude_path not in root for exclude_path in exclude_paths):
if 'SConstruct' in files:
sconstruct_paths.append(root)
return sconstruct_paths
def output_to_markdown(df, output_file):
with open(output_file, 'w', encoding='utf-8') as file:
file.write(df.to_markdown(index=False))
def output_to_yaml(dataframe, output_file, header_comment):
data = dataframe.to_dict(orient='records')
yaml_data = {}
for item in data:
folder = item.pop('Folder')
filtered_item = {k: v for k, v in item.items() if v is True or isinstance(v, str)}
yaml_data[folder] = filtered_item
with open(output_file, 'w', encoding='utf-8') as file:
file.write(f"# {header_comment}\n")
yaml.dump(yaml_data, file, default_flow_style=False, allow_unicode=True)
# Find the rt-thread root directory
rtt_root = os.getcwd()
while not os.path.exists(os.path.join(rtt_root, 'LICENSE')):
rtt_root = os.path.dirname(rtt_root)
bsp_root = os.path.join(rtt_root, 'bsp')
exclude_paths = ['templates', 'doc']
files_to_check = ['README.md','rtconfig.h', '.config','Kconfig', 'template.uvprojx','template.ewp', 'README.md', 'README_ZH.md', 'template.Uv2','template.uvproj']
sconstruct_paths = find_sconstruct_paths(bsp_root, exclude_paths)
result_table = check_files(bsp_root, files_to_check)
print(result_table)
output_file = 'output.md'
output_to_markdown(result_table, output_file)
# 将 output.yml 和 toolchain.yml 文件保存到 bsp 目录下
# 获取今天的日期
today_date = datetime.today().strftime('%Y-%m-%d')
# 获取当前年份
current_year = datetime.today().year
def get_git_user_name():
try:
result = subprocess.run(['git', 'config', 'user.name'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
if result.returncode == 0:
return result.stdout.strip()
else:
return "Unknown Author"
except Exception as e:
return "Unknown Author"
# 获取 Git 用户名
author_name = get_git_user_name()
# 头部注释
header_comment = f"""
# Copyright (c) {current_year}, RT-Thread Development Team
#
# SPDX-License-Identifier: Apache-2.0
#
# Change Logs:
# Date Author Notes
# {today_date} {author_name} the first version
#
"""
# 将 output.yml 和 toolchain.yml 文件保存到 tools/ci 目录下
ci_dir = os.path.join(rtt_root, 'tools', 'ci')
os.makedirs(ci_dir, exist_ok=True)
bsp_detail_file = os.path.join(ci_dir, 'bsp_detail.yml')
output_to_yaml(result_table, bsp_detail_file, header_comment)
toolchain_output_file = os.path.join(ci_dir, 'toolchain_bsp.yml')
generate_toolchain_yaml(bsp_detail_file, toolchain_output_file, header_comment)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,98 @@
#
# Copyright (c) 2006-2023, RT-Thread Development Team
#
# SPDX-License-Identifier: Apache-2.0
#
# Change Logs:
# Date Author Notes
# 2023-06-27 dejavudwh the first version
#
import subprocess
import logging
import os
CONFIG_BSP_USING_X = ["CONFIG_BSP_USING_UART", "CONFIG_BSP_USING_I2C", "CONFIG_BSP_USING_SPI", "CONFIG_BSP_USING_ADC", "CONFIG_BSP_USING_DAC"]
def init_logger():
log_format = "[%(filename)s %(lineno)d %(levelname)s] %(message)s "
date_format = '%Y-%m-%d %H:%M:%S %a '
logging.basicConfig(level=logging.INFO,
format=log_format,
datefmt=date_format,
)
def diff():
result = subprocess.run(['git', 'diff', '--name-only', 'HEAD', 'origin/master', '--diff-filter=ACMR', '--no-renames', '--full-index'], stdout = subprocess.PIPE)
file_list = result.stdout.decode().strip().split('\n')
logging.info(file_list)
bsp_paths = set()
for file in file_list:
if "bsp/" in file:
logging.info("Modifed file: {}".format(file))
bsp_paths.add(file)
dirs = set()
for dir in bsp_paths:
dir = os.path.dirname(dir)
while "bsp/" in dir:
files = os.listdir(dir)
if ".config" in files and "rt-thread.elf" not in files and not dir.endswith("bsp"):
logging.info("Found bsp path: {}".format(dir))
dirs.add(dir)
break
new_dir = os.path.dirname(dir)
dir = new_dir
return dirs
def check_config_in_line(line):
for config in CONFIG_BSP_USING_X:
if config in line and '#' in line:
logging.info("Found in {}".format(line))
return config
return ""
def check_config_in_file(file_path):
configs = set()
found = False
try:
with open(file_path, 'r') as file:
for line in file:
line.strip()
if found:
res = check_config_in_line(line)
if res:
configs.add(res)
elif "On-chip Peripheral Drivers" in line:
logging.info("Found On-chip Peripheral Drivers")
found = True
except FileNotFoundError:
logging.error("The .config file does not exist for this BSP, please recheck the file directory!")
return configs
def modify_config(file_path, configs):
with open(file_path + "/rtconfig.h", 'a') as file:
for item in configs:
define1 = item.replace("CONFIG_BSP", "BSP")
define2 = item.replace("CONFIG_BSP", "RT")
file.write("#define " + define1 + "\n")
file.write("#define " + define2 + "\n")
def recompile_bsp(dir):
logging.info("recomplie bsp: {}".format(dir))
os.system("scons -C " + dir)
if __name__ == '__main__':
init_logger()
recompile_bsp_dirs = diff()
for dir in recompile_bsp_dirs:
dot_config_path = dir + "/" + ".config"
configs = check_config_in_file(dot_config_path)
logging.info("add config:")
logging.info(configs)
logging.info("Add configurations and recompile!")
modify_config(dir, configs)
recompile_bsp(dir)
+118
View File
@@ -0,0 +1,118 @@
#
# Copyright (c) 2006-2023, RT-Thread Development Team
#
# SPDX-License-Identifier: Apache-2.0
#
# Change Logs:
# Date Author Notes
# 2023-05-16 dejavudwh the first version
# 2024-09-12 supperthomas add cppcheck summary for detail
#
import click
import logging
import subprocess
import sys
import format_ignore
import os
'''
--suppress=syntaxError
该选项用于抑制特定的错误类型。在这里,syntaxError 是被忽略的错误类型。这意味着 Cppcheck 不会报告语法错误(syntaxError)。这是因为在某些情况下,分析工具可能会误报语法错误,但 CI(持续集成)系统会在编译时捕获这些错误,因此可以忽略。
--enable=warning
该选项用于启用特定类型的检查。Cppcheck 可以检查不同的级别和种类的问题,如错误、警告、性能问题等。--enable=warning 启用的是警告级别的检查,帮助检测代码中可能存在的问题,但不是严重的错误。
performance
Cppcheck 可以启用多个检查种类,在这里指定了 performance,意味着会检查与代码性能相关的潜在问题,例如不必要的拷贝操作、无效的条件判断等。这有助于提高代码的运行效率。
portability
Cppcheck 会检查代码的可移植性问题。可移植性检查帮助发现代码在不同平台或编译器中可能遇到的问题,如特定平台上不支持的类型、函数或特性。这对跨平台开发非常重要。
--inline-suppr
该选项允许在代码中使用注释来抑制特定的警告或错误信息。通过这种方式,开发者可以直接在代码中添加注释,告诉 Cppcheck 忽略某些检查或警告。例如,开发者可以在代码中添加 // cppcheck-suppress <message> 来抑制特定的警告信息。
--error-exitcode=1
该选项告诉 Cppcheck 如果遇到错误,就返回指定的退出码。在这里指定的是 1。这对自动化工具非常有用,尤其是在 CI 环境中,因为它可以通过检查返回码来判断 Cppcheck 是否发现了错误。如果有错误,CI 系统会将整个任务标记为失败。
--force
这个选项强制 Cppcheck 对所有文件进行检查,即使它检测到编译条件缺失或某些配置问题。通常,如果某些宏定义或依赖项缺失,Cppcheck 可能会跳过某些文件的检查。但 --force 会强制工具继续执行分析,即使有可能缺少某些信息。这在某些大型项目中很有用,可以确保所有文件都经过检查。
'''
def add_summary(text):
"""
add summary to github action.
"""
os.system(f'echo "{text}" >> $GITHUB_STEP_SUMMARY ;')
class CPPCheck:
def __init__(self, file_list):
self.file_list = file_list
def check(self):
file_list_filtered = [file for file in self.file_list if file.endswith(('.c', '.cpp', '.cc', '.cxx'))]
logging.info("Start to static code analysis.")
check_result = True
for file in file_list_filtered:
macros = []
if os.path.basename(file) == 'lwp.c':
macros.append('-DRT_USING_DFS')
result = subprocess.run(
[
'cppcheck',
'-DRT_ASSERT(x)=',
'-DRTM_EXPORT(x)=',
'-Drt_list_for_each_entry(a,b,c)=a=(void*)b;',
'-I include',
'-I thread/components/finsh',
# it's okay because CI will do the real compilation to check this
'--suppress=syntaxError',
'--check-level=exhaustive',
'--enable=warning',
'performance',
'portability',
'--inline-suppr',
'--error-exitcode=1',
'--force',
file
] + macros,
stdout = subprocess.PIPE, stderr = subprocess.PIPE)
logging.info(result.stdout.decode())
logging.info(result.stderr.decode())
if result.stderr:
add_summary("The following errors are for reference only. If they are not actual issues, please ignore them and do not make unnecessary modifications.")
add_summary("以下错误仅供参考,如果发现没有问题,请直接忽略,不需要强行修改")
add_summary(f"- :rotating_light: {result.stderr.decode()}")
check_result = False
return check_result
@click.group()
@click.pass_context
def cli(ctx):
pass
@cli.command()
def check():
"""
static code analysis(cppcheck).
"""
format_ignore.init_logger()
# get modified files list
checkout = format_ignore.CheckOut()
file_list = checkout.get_new_file()
if file_list is None:
logging.error("checkout files fail")
sys.exit(1)
# use cppcheck
cpp_check = CPPCheck(file_list)
cpp_check_result = cpp_check.check()
if not cpp_check_result:
logging.error("static code analysis(cppcheck) fail.")
sys.exit(1)
logging.info("check success.")
sys.exit(0)
if __name__ == '__main__':
cli()
+292
View File
@@ -0,0 +1,292 @@
#
# Copyright (c) 2006-2022, RT-Thread Development Team
#
# SPDX-License-Identifier: Apache-2.0
#
# Change Logs:
# Date Author Notes
# 2021-04-01 LiuKang the first version
#
import os
import re
import sys
import click
import yaml
import chardet
import logging
import datetime
def init_logger():
log_format = "[%(filename)s %(lineno)d %(levelname)s] %(message)s "
date_format = '%Y-%m-%d %H:%M:%S %a '
logging.basicConfig(level=logging.INFO,
format=log_format,
datefmt=date_format,
)
class CheckOut:
def __init__(self, rtt_repo, rtt_branch):
self.root = os.getcwd()
self.rtt_repo = rtt_repo
self.rtt_branch = rtt_branch
def __exclude_file(self, file_path):
dir_number = file_path.split('/')
ignore_path = file_path
# gets the file path depth.
for i in dir_number:
# current directory.
dir_name = os.path.dirname(ignore_path)
ignore_path = dir_name
# judge the ignore file exists in the current directory.
ignore_file_path = os.path.join(dir_name, ".ignore_format.yml")
if not os.path.exists(ignore_file_path):
continue
try:
with open(ignore_file_path) as f:
ignore_config = yaml.safe_load(f.read())
file_ignore = ignore_config.get("file_path", [])
dir_ignore = ignore_config.get("dir_path", [])
except Exception as e:
logging.error(e)
continue
logging.debug("ignore file path: {}".format(ignore_file_path))
logging.debug("file_ignore: {}".format(file_ignore))
logging.debug("dir_ignore: {}".format(dir_ignore))
try:
# judge file_path in the ignore file.
for file in file_ignore:
if file is not None:
file_real_path = os.path.join(dir_name, file)
if file_real_path == file_path:
logging.info("ignore file path: {}".format(file_real_path))
return 0
file_dir_path = os.path.dirname(file_path)
for _dir in dir_ignore:
if _dir is not None:
dir_real_path = os.path.join(dir_name, _dir)
if file_dir_path.startswith(dir_real_path):
logging.info("ignore dir path: {}".format(dir_real_path))
return 0
except Exception as e:
logging.error(e)
continue
return 1
def get_new_file(self):
file_list = list()
try:
os.system('git remote add rtt_repo {}'.format(self.rtt_repo))
os.system('git fetch rtt_repo')
os.system('git merge rtt_repo/{}'.format(self.rtt_branch))
os.system('git reset rtt_repo/{} --soft'.format(self.rtt_branch))
os.system('git status > git.txt')
except Exception as e:
logging.error(e)
return None
try:
with open('git.txt', 'r') as f:
file_lines = f.readlines()
except Exception as e:
logging.error(e)
return None
file_path = ''
for line in file_lines:
if 'new file' in line:
file_path = line.split('new file:')[1].strip()
logging.info('new file -> {}'.format(file_path))
elif 'deleted' in line:
logging.info('deleted file -> {}'.format(line.split('deleted:')[1].strip()))
elif 'modified' in line:
file_path = line.split('modified:')[1].strip()
logging.info('modified file -> {}'.format(file_path))
else:
continue
result = self.__exclude_file(file_path)
if result != 0:
file_list.append(file_path)
return file_list
class FormatCheck:
def __init__(self, file_list):
self.file_list = file_list
def __check_rt_errorcode(self, line):
pattern = re.compile(r'return\s+(RT_ERROR|RT_ETIMEOUT|RT_EFULL|RT_EEMPTY|RT_ENOMEM|RT_ENOSYS|RT_EBUSY|RT_EIO|RT_EINTR|RT_EINVAL|RT_ENOENT|RT_ENOSPC|RT_EPERM|RT_ETRAP|RT_EFAULT)')
match = pattern.search(line)
if match:
return False
else:
return True
def __check_file(self, file_lines, file_path):
line_num = 0
check_result = True
for line in file_lines:
line_num += 1
# check line start
line_start = line.replace(' ', '')
# find tab
if line_start.startswith('\t'):
logging.error("{} line[{}]: please use space replace tab at the start of this line.".format(file_path, line_num))
check_result = False
# check line end
line_end = line.split('\n')[0]
if line_end.endswith(' ') or line_end.endswith('\t'):
logging.error("{} line[{}]: please delete extra space at the end of this line.".format(file_path, line_num))
check_result = False
if self.__check_rt_errorcode(line) == False:
logging.error("{} line[{}]: the RT-Thread error code should return negative value. e.g. return -RT_ERROR".format(file_path, line_num))
check_result = False
return check_result
def check(self):
logging.info("Start to check files format.")
if len(self.file_list) == 0:
logging.warning("There are no files to check format.")
return True
encoding_check_result = True
format_check_fail_files = 0
for file_path in self.file_list:
code = ''
if file_path.endswith(".c") or file_path.endswith(".h"):
try:
with open(file_path, 'rb') as f:
file = f.read()
# get file encoding
chardet_report = chardet.detect(file)
code = chardet_report['encoding']
confidence = chardet_report['confidence']
except Exception as e:
logging.error(e)
else:
continue
if code != 'utf-8' and code != 'ascii' and confidence > 0.8:
logging.error("[{0}]: encoding {1} not utf-8, please format it.".format(file_path, code))
encoding_check_result = False
else:
logging.info('[{0}]: encoding check success.'.format(file_path))
with open(file_path, 'r', encoding = "utf-8") as f:
file_lines = f.readlines()
if not self.__check_file(file_lines, file_path):
format_check_fail_files += 1
if (not encoding_check_result) or (format_check_fail_files != 0):
logging.error("files format check fail.")
return False
logging.info("files format check success.")
return True
class LicenseCheck:
def __init__(self, file_list):
self.file_list = file_list
def check(self):
current_year = datetime.date.today().year
logging.info("current year: {}".format(current_year))
if len(self.file_list) == 0:
logging.warning("There are no files to check license.")
return 0
logging.info("Start to check files license.")
check_result = True
for file_path in self.file_list:
if file_path.endswith(".c") or file_path.endswith(".h"):
try:
with open(file_path, 'r') as f:
file = f.readlines()
except Exception as e:
logging.error(e)
else:
continue
if 'Copyright' in file[1] and 'SPDX-License-Identifier: Apache-2.0' in file[3]:
try:
license_year = re.search(r'2006-\d{4}', file[1]).group()
true_year = '2006-{}'.format(current_year)
if license_year != true_year:
logging.warning("[{0}]: license year: {} is not true: {}, please update.".format(file_path,
license_year,
true_year))
else:
logging.info("[{0}]: license check success.".format(file_path))
except Exception as e:
logging.error(e)
else:
logging.error("[{0}]: license check fail.".format(file_path))
check_result = False
return check_result
@click.group()
@click.pass_context
def cli(ctx):
pass
@cli.command()
@click.option(
'--license',
"check_license",
required=False,
type=click.BOOL,
flag_value=True,
help="Enable File license check.",
)
@click.argument(
'repo',
nargs=1,
type=click.STRING,
default='https://github.com/RT-Thread/rt-thread',
)
@click.argument(
'branch',
nargs=1,
type=click.STRING,
default='master',
)
def check(check_license, repo, branch):
"""
check files license and format.
"""
init_logger()
# get modified files list
checkout = CheckOut(repo, branch)
file_list = checkout.get_new_file()
if file_list is None:
logging.error("checkout files fail")
sys.exit(1)
# check modified files format
format_check = FormatCheck(file_list)
format_check_result = format_check.check()
license_check_result = True
if check_license:
license_check = LicenseCheck(file_list)
license_check_result = license_check.check()
if not format_check_result or not license_check_result:
logging.error("file format check or license check fail.")
sys.exit(1)
logging.info("check success.")
sys.exit(0)
if __name__ == '__main__':
cli()
+84
View File
@@ -0,0 +1,84 @@
#
# Copyright (c) 2006-2023, RT-Thread Development Team
#
# SPDX-License-Identifier: Apache-2.0
#
# Change Logs:
# Date Author Notes
# 2023-05-16 dejavudwh the first version
#
import yaml
import logging
import os
import subprocess
def init_logger():
log_format = "[%(filename)s %(lineno)d %(levelname)s] %(message)s "
date_format = '%Y-%m-%d %H:%M:%S %a '
logging.basicConfig(level=logging.INFO,
format=log_format,
datefmt=date_format,
)
class CheckOut:
def __init__(self):
pass
def __exclude_file(self, file_path):
dir_number = file_path.split('/')
ignore_path = file_path
# gets the file path depth.
for i in dir_number:
# current directory.
dir_name = os.path.dirname(ignore_path)
ignore_path = dir_name
# judge the ignore file exists in the current directory.
ignore_file_path = os.path.join(dir_name, ".ignore_format.yml")
if not os.path.exists(ignore_file_path):
continue
try:
with open(ignore_file_path) as f:
ignore_config = yaml.safe_load(f.read())
file_ignore = ignore_config.get("file_path", [])
dir_ignore = ignore_config.get("dir_path", [])
except Exception as e:
logging.error(e)
continue
logging.debug("ignore file path: {}".format(ignore_file_path))
logging.debug("file_ignore: {}".format(file_ignore))
logging.debug("dir_ignore: {}".format(dir_ignore))
try:
# judge file_path in the ignore file.
for file in file_ignore:
if file is not None:
file_real_path = os.path.join(dir_name, file)
if file_real_path == file_path:
logging.info("ignore file path: {}".format(file_real_path))
return 0
file_dir_path = os.path.dirname(file_path)
for _dir in dir_ignore:
if _dir is not None:
dir_real_path = os.path.join(dir_name, _dir)
if file_dir_path.startswith(dir_real_path):
logging.info("ignore dir path: {}".format(dir_real_path))
return 0
except Exception as e:
logging.error(e)
continue
return 1
def get_new_file(self):
result = subprocess.run(['git', 'diff', '--name-only', 'HEAD', 'origin/master', '--diff-filter=ACMR', '--no-renames', '--full-index'], stdout = subprocess.PIPE)
file_list = result.stdout.decode().strip().split('\n')
new_files = []
for line in file_list:
logging.info("modified file -> {}".format(line))
result = self.__exclude_file(line)
if result != 0:
new_files.append(line)
return new_files
+372
View File
@@ -0,0 +1,372 @@
#
# Copyright (c) 2025, RT-Thread Development Team
#
# SPDX-License-Identifier: Apache-2.0
#
# Change Logs:
# Date Author Notes
# 2025-05-15 supperthomas add PR status show
# 2025-05-22 hydevcode 替换bsp_building.yml的判断文件修改机制,并将PR status show合并进bsp_building.yml
import subprocess
import sys
import os
import re
import argparse
import locale
from typing import List, Dict
import json
from typing import List
class FileDiff:
def __init__(self, path: str, status: str, size_change: int = 0, old_size: int = 0, new_size: int = 0):
self.path = path
self.status = status # A (added), M (modified), D (deleted), R (renamed)
self.size_change = size_change
self.old_size = old_size
self.new_size = new_size
def __str__(self):
if self.status == 'A':
return f"Added {self.path}: {self.size_change} bytes"
elif self.status == 'D':
return f"Deleted {self.path}: was {self.old_size} bytes"
elif self.status == 'M' or self.status == 'R':
return f"Modified {self.path}: {self.size_change} bytes change"
else:
return f"{self.status} {self.path}"
class GitDiffAnalyzer:
def __init__(self, target_branch: str):
self.target_branch = target_branch
self.encoding = locale.getpreferredencoding()
def get_diff_files(self) -> List[FileDiff]:
"""获取当前分支与目标分支之间的差异文件"""
# 找到当前分支和目标分支的最近共同祖先
merge_base = self.get_merge_base()
if not merge_base:
print("No common ancestor found between current branch and target branch")
sys.exit(1)
# 获取差异文件列表
diff_cmd = f"git diff --name-status {merge_base} HEAD"
print(diff_cmd)
try:
output = subprocess.check_output(diff_cmd.split(), stderr=subprocess.STDOUT)
output = output.decode(self.encoding).strip()
print(output)
except subprocess.CalledProcessError as e:
print(f"Error executing git diff: {e.output.decode(self.encoding)}")
sys.exit(1)
if not output:
print("No differences between current branch and target branch")
sys.exit(0)
# 处理可能的换行符问题
output = output.replace('\r\n', '\n')
lines = output.split('\n')
file_diffs = []
for line in lines:
line = line.strip()
if not line:
continue
parts = line.split('\t')
if len(parts) < 2:
# 可能是重命名文件,格式为 "RXXX\told_path\tnew_path"
match = re.match(r'R(\d+)\t(.+)\t(.+)', line)
if match:
status = 'R'
old_path = match.group(2)
new_path = match.group(3)
# 计算重命名文件的修改大小
old_size = self.get_file_size(old_path, self.target_branch)
new_size = self.get_file_size(new_path, 'HEAD')
size_change = new_size - old_size if old_size > 0 else new_size
file_diffs.append(FileDiff(new_path, status, size_change, old_size, new_size))
else:
status = parts[0][0] # 取状态码的第一个字符
path = parts[1]
if status == 'A':
# 新增文件,计算大小
size = self.get_file_size(path, 'HEAD')
file_diffs.append(FileDiff(path, status, size, 0, size))
elif status == 'D':
# 删除文件,计算原大小
size = self.get_file_size(path, self.target_branch)
file_diffs.append(FileDiff(path, status, 0, size, 0))
elif status == 'M':
# 修改文件,计算大小变化
old_size = self.get_file_size(path, self.target_branch)
new_size = self.get_file_size(path, 'HEAD')
size_change = new_size - old_size
file_diffs.append(FileDiff(path, status, size_change, old_size, new_size))
return file_diffs
def get_merge_base(self) -> str:
"""获取当前分支和目标分支的最近共同祖先"""
try:
cmd = f"git merge-base {self.target_branch} HEAD"
print(cmd)
output = subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
return output.decode(self.encoding).strip()
except subprocess.CalledProcessError as e:
print(f"Error executing git merge-base: {e.output.decode(self.encoding)}")
return None
def get_file_size(self, path: str, ref: str) -> int:
"""获取指定分支上文件的大小"""
try:
# 使用 git cat-file 来获取文件内容,然后计算其大小
cmd = f"git cat-file blob {ref}:{path}"
output = subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
return len(output)
except subprocess.CalledProcessError:
# 如果文件不存在或无法获取,返回0
return 0
def format_size(size: int) -> str:
"""将字节大小转换为人类可读的格式"""
if size >= 0:
if size < 1024:
return f"{size} bytes"
elif size < 1024 * 1024:
return f"{size / 1024:.1f} KB"
elif size < 1024 * 1024 * 1024:
return f"{size / (1024 * 1024):.1f} MB"
else:
return f"{size / (1024 * 1024 * 1024):.1f} GB"
else:
temp_size=abs(size)
if temp_size < 1024:
return f"-{temp_size} bytes"
elif temp_size < 1024 * 1024:
return f"-{temp_size / 1024:.1f} KB"
elif temp_size < 1024 * 1024 * 1024:
return f"-{temp_size / (1024 * 1024):.1f} MB"
else:
return f"-{temp_size / (1024 * 1024 * 1024):.1f} GB"
def is_bsp(path):
return os.path.isfile(os.path.join(path, "rtconfig.h"))
def filter_bsp_config(file_diffs: List[FileDiff], config_path: str):
# 读取原始JSON配置
with open(config_path, 'r', encoding='utf-8') as f:
config = json.load(f)
# 获取所有修改的文件路径(统一使用Linux风格路径)
modified_paths = [diff.path.replace('\\', '/') for diff in file_diffs]
print(modified_paths)
if not modified_paths:
print("master分支运行")
return
bsp_paths = set()
bsp_in_but_not_bsp_paths = set()
all_print_paths = set()
for modified_path in modified_paths:
parts = modified_path.strip(os.sep).split('/')
if not parts:
continue
first_level = parts[0]
first_level_path = os.path.join(os.getcwd(), first_level)
#处理bsp路径的逻辑
if first_level == "bsp":
temp_path=os.path.join(os.getcwd(), modified_path)
# 判断是否是文件
if not os.path.isdir(modified_path):
temp_path = os.path.dirname(temp_path)
#循环搜索每一级是否有rtconfig.h
while temp_path !=first_level_path:
if is_bsp(temp_path):
bsp_paths.add(temp_path)
break
temp_path=os.path.dirname(temp_path)
if temp_path ==first_level_path:
bsp_in_but_not_bsp_paths.add(parts[1])
else:
#非bsp路径逻辑
all_print_paths.add(first_level_path)
# 变成相对路径
bsp_list = set()
for path in sorted(bsp_paths):
current_working_dir = os.path.join(os.getcwd(), "bsp/")
if path.startswith(current_working_dir):
bsp_list.add(path[len(current_working_dir):].lstrip(os.sep))
else:
bsp_list.add(path) # 如果 first_level_path 不以 current_working_dir 开头,则保持不变
# 处理修改了bsp外的文件的情况
filtered_bsp = [
path for path in bsp_list
if path.split('/')[0] not in bsp_in_but_not_bsp_paths
]
merged_result = filtered_bsp + list(bsp_in_but_not_bsp_paths)
filtered_legs = []
for leg in config["legs"]:
matched_paths = [
path for path in leg.get("SUB_RTT_BSP", [])
if any(keyword in path for keyword in merged_result)
]
if matched_paths:
filtered_legs.append({**leg, "SUB_RTT_BSP": matched_paths})
# 生成新的配置
new_config = {"legs": filtered_legs}
# 判断有没有修改到bsp外的文件,有的话则编译全部
if not all_print_paths:
print(new_config)
file_name = ".github/ALL_BSP_COMPILE_TEMP.json"
# 将 new_config 写入文件
with open(file_name, "w", encoding="utf-8") as file:
json.dump(new_config, file, ensure_ascii=False, indent=4)
def main():
# 源文件路径
source_file = ".github/ALL_BSP_COMPILE.json" # 替换为你的文件路径
# 目标文件路径
target_file = ".github/ALL_BSP_COMPILE_TEMP.json" # 替换为你的目标文件路径
# 读取源文件并过滤掉带有 // 的行
with open(source_file, "r", encoding="utf-8") as infile, open(target_file, "w", encoding="utf-8") as outfile:
for line in infile:
if not line.lstrip().startswith("//"):
outfile.write(line)
parser = argparse.ArgumentParser(description='Compare current branch with target branch and show file differences.')
parser.add_argument('target_branch', help='Target branch to compare against (e.g., master)')
args = parser.parse_args()
analyzer = GitDiffAnalyzer(args.target_branch)
file_diffs = analyzer.get_diff_files()
# 生成报告
generate_report(file_diffs, args.target_branch)
filter_bsp_config(file_diffs,".github/ALL_BSP_COMPILE_TEMP.json")
def add_summary(text):
"""
add summary to github action.
"""
if "GITHUB_STEP_SUMMARY" in os.environ:
summary_path = os.environ["GITHUB_STEP_SUMMARY"] # 获取摘要文件路径
# 将 text 写入摘要文件(追加模式)
with open(summary_path, "a") as f: # "a" 表示追加模式
f.write(text + "\n") # 写入文本并换行
else:
print("Environment variable $GITHUB_STEP_SUMMARY is not set.")
def summarize_diff(label, count, size=None):
"""格式化输出变更摘要"""
line = f" {label:<12} {count:>3} files"
if size is not None:
line += f" ({format_size(size)})"
add_summary(line)
def generate_report(file_diffs: List[FileDiff], target_branch: str):
"""生成差异报告"""
add_summary(f"# 📊 **Comparison between {target_branch} and Current Branch**\n")
# 分类统计
added_files = [f for f in file_diffs if f.status.startswith('A')]
removed_files = [f for f in file_diffs if f.status.startswith('D')]
modified_files = [f for f in file_diffs if f.status.startswith('M')]
renamed_files = [f for f in file_diffs if f.status.startswith('R')]
copied_files = [f for f in file_diffs if f.status.startswith('C')]
unmerged_files = [f for f in file_diffs if f.status.startswith('U')]
type_changed_files = [f for f in file_diffs if f.status.startswith('T')]
# 计算总变化量
total_added = sum(f.size_change for f in added_files)
total_removed = sum(f.old_size for f in removed_files)
total_modified = sum(f.size_change for f in modified_files)
total_copied = sum(f.size_change for f in copied_files)
total_renamed = sum(f.old_size for f in renamed_files)
total_type_changed = sum(f.size_change for f in type_changed_files)
total_size_change = sum(f.size_change for f in file_diffs)
# === 汇总输出 ===
summarize_diff("Total:", len(file_diffs))
summarize_diff("Added:", len(added_files), total_added)
summarize_diff("Removed:", len(removed_files), total_removed)
summarize_diff("Modified:", len(modified_files), total_modified)
summarize_diff("Renamed:", len(renamed_files), total_renamed)
summarize_diff("Copied:", len(copied_files), total_copied)
summarize_diff("Type Changed:", len(type_changed_files), total_type_changed)
summarize_diff("Unmerged:", len(unmerged_files))
if total_size_change > 0:
change_desc = f"📈 **Increase of {format_size(total_size_change)}**"
elif total_size_change < 0:
change_desc = f"📉 **Reduction of {format_size(abs(total_size_change))}**"
else:
change_desc = " **No net size change**"
add_summary(f"\n### 📦 **Total Size Change:** {change_desc} (Excluding removed files)")
# 显示详细差异文件内容
add_summary("\n## 📂 **Detailed File Changes**\n")
for diff in file_diffs:
add_summary(f"📄 {diff.path} — **[{diff.status}]**")
# 文件状态处理
if diff.status.startswith('A'):
add_summary(f"📦 Size: {format_size(diff.new_size)}")
elif diff.status.startswith(('M', 'R')): # 修改或重命名
add_summary(f"📏 Original size: {format_size(diff.old_size)}")
add_summary(f"📐 New size: {format_size(diff.new_size)}")
delta = diff.size_change
if delta > 0:
change_str = f"📈 Increased by {format_size(delta)}"
elif delta < 0:
change_str = f"📉 Reduced by {format_size(abs(delta))}"
else:
change_str = " No size change"
add_summary(f"📊 Size change: {change_str}")
elif diff.status.startswith('D'):
add_summary(f"🗑️ Original size: {format_size(diff.old_size)}")
elif diff.status.startswith('C'):
add_summary(f"📎 Copied from size: {format_size(diff.old_size)}{format_size(diff.new_size)}")
elif diff.status.startswith('T'):
add_summary("⚙️ File type changed")
elif diff.status.startswith('U'):
add_summary("⚠️ Unmerged conflict detected")
else:
add_summary("❓ Unknown change type")
add_summary("\n\n")
if __name__ == "__main__":
main()
+219
View File
@@ -0,0 +1,219 @@
#!/bin/bash
#
# Copyright (c) 2024, RT-Thread Development Team
#
# SPDX-License-Identifier: Apache-2.0
#
# Change Logs:
# Date Author Notes
# 2024-08-27 Supperthomas the first version
#
#这个脚本用于安装RT-Thread开发环境 请确保网络畅通
# 设置环境变量 如果希望生效请在当前shell中执行source install.sh
export RTT_ROOT=$(pwd)
export RTT_CC=gcc
echo "RTT_ROOT is set to: $RTT_ROOT"
check_if_china_ip() {
# 默认情况下不使用gitee
use_gitee=false
# 尝试通过IP地址判断
ip=$(curl -s https://ifconfig.me/ip)
if [ -n "$ip" ]; then
location=$(curl -s http://www.ip-api.com/json/$ip | grep -o '"country":"China"')
if [ "$location" == '"country":"China"' ]; then
use_gitee=true
echo "Detected China IP. Using gitee."
else
echo "IP location is not in China."
fi
else
echo "Failed to retrieve IP address. Falling back to timezone check."
# 通过时区判断
if [ $(($(date +%z)/100)) -eq 8 ]; then
use_gitee=true
echo "Detected timezone UTC+8. Using gitee."
else
echo "Timezone is not UTC+8."
fi
fi
echo $use_gitee
}
# 检测操作系统类型和发行版
detect_os() {
if command -v uname >/dev/null 2>&1; then
OS=$(uname -s)
else
if [ -f "/etc/os-release" ]; then
OS="Linux"
elif [ -f "/System/Library/CoreServices/SystemVersion.plist" ]; then
OS="macOS"
elif [[ -d "/mnt/c/Windows" || -d "/c/Windows" ]]; then
OS="WSL"
else
OS="UNKNOWN"
fi
fi
if [ "$OS" == "Linux" ]; then
if [ -f /etc/os-release ]; then
. /etc/os-release
DISTRO=$ID
VERSION=$VERSION_ID
elif [ -f /etc/lsb-release ]; then
. /etc/lsb-release
DISTRO=$DISTRIB_ID
VERSION=$DISTRIB_RELEASE
else
DISTRO="UNKNOWN"
VERSION="UNKNOWN"
fi
fi
echo "Detected Operating System: $OS, Distribution: $DISTRO, Version: $VERSION"
}
# 修改的安装函数
install_on_ubuntu() {
echo "Installing on Debian/Ubuntu..."
use_gitee=$(check_if_china_ip)
# 根据检测结果决定是否使用--gitee参数
if [ "$use_gitee" = true ]; then
wget https://raw.githubusercontent.com/RT-Thread/env/master/install_ubuntu.sh
chmod 777 install_ubuntu.sh
echo "Installing on China gitee..."
./install_ubuntu.sh --gitee
else
wget https://raw.githubusercontent.com/RT-Thread/env/master/install_ubuntu.sh
chmod 777 install_ubuntu.sh
echo "Installing on no China..."
./install_ubuntu.sh
fi
rm install_ubuntu.sh
}
install_on_fedora() {
echo "Installing on Fedora..."
}
install_on_centos() {
echo "Installing on CentOS/RHEL..."
}
install_on_arch() {
echo "Installing on Arch Linux..."
}
install_on_macos() {
echo "Installing on macOS..."
use_gitee=$(check_if_china_ip)
# 根据检测结果决定是否使用--gitee参数
if [ "$use_gitee" = true ]; then
wget https://raw.githubusercontent.com/RT-Thread/env/master/install_macos.sh
chmod 777 install_macos.sh
echo "Installing on China gitee..."
./install_macos.sh --gitee
else
wget https://raw.githubusercontent.com/RT-Thread/env/master/install_macos.sh
chmod 777 install_macos.sh
echo "Installing on no China..."
./install_macos.sh
fi
rm ./install_macos.sh
}
install_on_wsl() {
echo "Installing on Windows Subsystem for Linux (WSL)..."
}
install_on_windows() {
echo "Installing on Windows using PowerShell..."
use_gitee=$(check_if_china_ip)
# 根据检测结果决定是否使用--gitee参数
if [ "$use_gitee" = true ]; then
wget https://raw.githubusercontent.com/RT-Thread/env/master/install_windows.ps1
echo "Installing on China gitee..."
./install_windows.ps1 --gitee
else
wget https://raw.githubusercontent.com/RT-Thread/env/master/install_windows.ps1
echo "Installing on no China..."
./install_windows.ps1
fi
rm ./install_windows.ps1
}
install_on_opensuse() {
echo "Installing on openSUSE..."
use_gitee=$(check_if_china_ip)
if [ "$use_gitee" = true ]; then
wget https://raw.githubusercontent.com/RT-Thread/env/master/install_suse.sh
chmod 777 install_suse.sh
echo "Installing on China gitee..."
./install_suse.sh --gitee
else
wget https://raw.githubusercontent.com/RT-Thread/env/master/install_suse.sh
chmod 777 install_suse.sh
echo "Installing on no China..."
./install_suse.sh
fi
rm ./install_suse.sh
}
# 主函数
main() {
detect_os
case "$OS" in
Linux)
case "$DISTRO" in
ubuntu|debian)
install_on_ubuntu
;;
fedora)
install_on_fedora
;;
centos|rhel)
install_on_centos
;;
arch)
install_on_arch
;;
*)
echo "Unsupported Linux distribution: $DISTRO"
exit 1
;;
esac
;;
macOS)
install_on_macos
;;
WSL)
install_on_wsl
;;
Windows)
install_on_windows
;;
*)
echo "Unsupported Operating System: $OS"
exit 1
;;
esac
echo "Installation completed!"
}
# 执行主函数
main
+338
View File
@@ -0,0 +1,338 @@
#
# Copyright (c) 2006-2024, RT-Thread Development Team
#
# SPDX-License-Identifier: Apache-2.0
#
# Change Logs:
# Date Author Notes
# 2024-07-25 supperthomas the first version
#
"""
这个脚本用来编译所有的bsp
这里的脚本是用的arm-none-eabi-gcc, 默认根据本机已经安装的gcc来编译
其他的工具链暂时不支持,其实主要根据运行的环境中支持不支持,目前只打算支持主流的
失败的bsp会存到failed_bsp.log里面
"""
import os
import sys
import shutil
import multiprocessing
from multiprocessing import Process
import yaml
#help说明
def usage():
print('%s all -- build all GCC bsp' % os.path.basename(sys.argv[0]))
print('%s clean -- clean all bsp' % os.path.basename(sys.argv[0]))
print('%s update -- update all prject files' % os.path.basename(sys.argv[0]))
def add_summary(text):
"""
add summary to github action.
"""
os.system(f'echo "{text}" >> $GITHUB_STEP_SUMMARY ;')
def run_cmd(cmd, output_info=True):
"""
这个函数用来执行命令
run command and return output and result.
"""
print('\033[1;32m' + cmd + '\033[0m ' + os.getcwd())
output_str_list = []
res = 0
if output_info:
res = os.system(cmd + " > output.txt 2>&1")
else:
res = os.system(cmd + " > /dev/null 2>output.txt")
try:
with open("output.txt", "r") as file:
output_str_list = file.readlines()
except FileNotFoundError:
with open("output.txt", "w") as file:
file.write("new file")
for line in output_str_list:
print(line, end='')
os.remove("output.txt")
return output_str_list, res
def build_bsp(bsp, scons_args=''):
"""
build bsp.
cd {rtt_root}
scons -C bsp/{bsp} --pyconfig-silent > /dev/null
cd {rtt_root}/bsp/{bsp}
pkgs --update > /dev/null
pkgs --list
cd {rtt_root}
scons -C bsp/{bsp} -j{nproc} {scons_args}
cd {rtt_root}/bsp/{bsp}
scons -c > /dev/null
rm -rf packages
"""
success = True
pwd = os.getcwd()
print('======pwd==='+ os.getcwd()+'===bsp:=='+bsp)
os.chdir(rtt_root)
#有Kconfig 说明可以执行menuconfig
if os.path.exists(f"{rtt_root}/bsp/{bsp}/Kconfig"):
os.chdir(rtt_root)
print('======pwd==='+ os.getcwd()+'===bsp:=='+bsp)
run_cmd(f'scons -C bsp/{bsp} --pyconfig-silent', output_info=True)
os.chdir(f'{rtt_root}/bsp/{bsp}')
print('======pwd222==='+ os.getcwd()+'===bsp:=='+bsp)
run_cmd('pkgs --update', output_info=True)
run_cmd('pkgs --list')
nproc = multiprocessing.cpu_count()
os.chdir(rtt_root)
cmd = f'scons -C bsp/{bsp} -j{nproc} {scons_args}'
result_log, res = run_cmd(cmd, output_info=True)
if res != 0:
# 将失败的bsp写入特定的txt文件
with open(os.path.join(rtt_root, 'failed_bsp_list.txt'), 'a') as file:
file.write(bsp + '\n')
# 打印失败的bsp的log,把它放到对应的文件名文bsp的txt文件中
with open(os.path.join(rtt_root, 'failed_bsp.log'), 'a') as file:
file.write(f'===================={bsp}====================\n')
for line in result_log:
file.write(line)
print(f"::error::build {bsp} failed")
add_summary(f"- ❌ build {bsp} failed.")
success = False
else:
# 如果没有Kconfig直接执行scons
os.chdir(f'{rtt_root}/bsp/{bsp}')
run_cmd('scons', output_info=True)
# 删除packages文件夹
pkg_dir = os.path.join(rtt_root, 'bsp', bsp, 'packages')
shutil.rmtree(pkg_dir, ignore_errors=True)
#恢复到原目录
os.chdir(pwd)
return success
#判断参数是否是2个
if len(sys.argv) != 2:
usage()
sys.exit(0)
#更新MDK等文件
def update_project_file(project_dir):
if os.path.isfile(os.path.join(project_dir, 'template.Uv2')):
print('prepare MDK3 project file on ' + project_dir)
command = ' --target=mdk -s'
os.system('scons --directory=' + project_dir + command + ' > 1.txt')
if os.path.isfile(os.path.join(project_dir, 'template.uvproj')):
print('prepare MDK4 project file on ' + project_dir)
command = ' --target=mdk4 -s'
os.system('scons --directory=' + project_dir + command + ' > 1.txt')
if os.path.isfile(os.path.join(project_dir, 'template.uvprojx')):
print('prepare MDK5 project file on ' + project_dir)
command = ' --target=mdk5 -s'
os.system('scons --directory=' + project_dir + command + ' > 1.txt')
if os.path.isfile(os.path.join(project_dir, 'template.ewp')):
print('prepare IAR project file on ' + project_dir)
command = ' --target=iar -s'
os.system('scons --directory=' + project_dir + command + ' > 1.txt')
#更新所有可以scons的文件夹文件,先执行menuconfig --silent 再执行scons --target=mdk5
#处理带有sconstruct的文件夹
def update_all_project_files(sconstruct_paths):
for projects in sconstruct_paths:
try:
# update rtconfig.h and .config
#执行menuconfig
if os.path.isfile(os.path.join(projects, 'Kconfig')):
if "win32" in sys.platform:
retval = os.getcwd()
os.chdir(projects)
os.system("menuconfig --silent")
os.chdir(retval)
else:
os.system('scons --pyconfig-silent -C {0}'.format(projects))
print('==menuconfig=======projects='+ projects)
else:
print('==no kconfig=in==!!!!!=projects='+ projects)
# update mdk, IAR etc file
update_project_file(projects)
except Exception as e:
print("error message: {}".format(e))
sys.exit(-1)
#找到带有Sconstruct的文件夹
def find_sconstruct_paths(project_dir, exclude_paths, include_paths):
sconstruct_paths = []
bsp_detail_path = os.path.join(rtt_root, 'tools', 'ci', 'bsp_detail.yml')
if os.path.exists(bsp_detail_path):
with open(bsp_detail_path, 'r') as file:
bsp_detail = yaml.safe_load(file)
for root, dirs, files in os.walk(project_dir):
if include_paths:
if any(include_path in root for include_path in include_paths) and all(exclude_path not in root for exclude_path in exclude_paths):
if 'SConstruct' in files:
bsp_name = os.path.relpath(root, bsp_root)
if bsp_name in bsp_detail and bsp_detail[bsp_name].get('gcc') == 'arm-none-eabi-gcc':
sconstruct_paths.append(root)
else:
if all(exclude_path not in root for exclude_path in exclude_paths):
if 'SConstruct' in files:
bsp_name = os.path.relpath(root, bsp_root)
if bsp_name in bsp_detail and bsp_detail[bsp_name].get('gcc') == 'arm-none-eabi-gcc':
sconstruct_paths.append(root)
return sconstruct_paths
#检查EXE命令是否存在,判断环境
def check_command_availability(cmd):
"""
Check if a command is available.
"""
cmd_path = shutil.which(cmd)
if cmd_path is not None:
#print(f"{cmd} command is available at {cmd_path}")
return True
else:
print(f"{cmd} command is not available")
return False
# Find the rt-thread root directory
rtt_root = os.getcwd()
while not os.path.exists(os.path.join(rtt_root, 'LICENSE')):
rtt_root = os.path.dirname(rtt_root)
bsp_root = os.path.join(rtt_root, 'bsp')
#需要排除的文件夹名字
exclude_paths = ['templates', 'doc', 'libraries', 'Libraries', 'template']
include_paths = []#['nrf5x','qemu-vexpress-a9', 'ESP32_C3','simulator']
sconstruct_paths = find_sconstruct_paths(bsp_root, exclude_paths,include_paths)
# get command options
command = ''
command_clean_flag = False
print(rtt_root)
if sys.argv[1] == 'all':
if os.path.exists(os.path.join(rtt_root, 'failed_bsp_list.txt')):
os.remove(os.path.join(rtt_root, 'failed_bsp_list.txt'))
if os.path.exists(os.path.join(rtt_root, 'failed_bsp.log')):
os.remove(os.path.join(rtt_root, 'failed_bsp.log'))
command = ' '
#更新所有的工程
print('begin to update all the bsp projects')
update_all_project_files(sconstruct_paths)
#iarbuild .\project.ewp -clean rt-thread
elif sys.argv[1] == 'clean':
command = ' -c'
command_clean_flag = True
print('begin to clean all the bsp projects')
# 执行所有其他IDE的 update 但是不编译,这个一般不会出错
elif sys.argv[1] == 'update':
print('begin to update all the bsp projects')
#更新所有的工程
update_all_project_files(sconstruct_paths)
print('finished!')
sys.exit(0)
else:
usage()
sys.exit(0)
if sconstruct_paths:
print("包含 'SConstruct' 文件的路径:")
for path in sconstruct_paths:
print(path)
else:
print("未找到包含 'SConstruct' 文件的路径")
#遍历所有的sconstruct_paths 路径中的文件夹
def bsp_scons_worker(project_dir):
print('=========project_dir===='+ project_dir)
#判断有没有SConstruct 文件,
if os.path.isfile(os.path.join(project_dir, 'SConstruct')):
print('==menuconfig=======rtt_root='+ rtt_root)
print('==project_dir=======project_dir='+ project_dir)
# 去掉 'bsp' 前面的三级目录
parts = project_dir.split(os.sep)
if 'bsp' in parts:
bsp_index = parts.index('bsp')
new_project_dir = os.sep.join(parts[bsp_index+1:])
else:
new_project_dir = project_dir
print('==project_dir=======new_project_dir='+ new_project_dir)
#开始编译bsp
build_bsp(new_project_dir)
# 发现有keil相关的,执行keil相关的命令,先检查一下UV4.exe命令有没有,然后执行UV4.exe
if check_command_availability('UV4.exe') :
"""
UV4.exe -b project.uvprojx -q -j0 -t rt-thread -o action_runner.log
ls
sleep 10
cat action_runner.log
"""
if os.path.isfile(os.path.join(project_dir, 'template.uvprojx')):
if check_command_availability('UV4.exe'):
print('Start to build keil project======')
os.chdir(f'{project_dir}')
print('clean keil project======')
run_cmd('UV4.exe -c project.uvprojx -q')
___, res = run_cmd('UV4.exe -b project.uvprojx -q -j0 -t rt-thread -o keil.log')
os.chdir(f'{rtt_root}')
else:
print('UV4.exe is not available, please check your keil installation')
if check_command_availability('iarbuild.exe') :
"""
iarbuild .\project.ewp rt-thread
"""
if os.path.isfile(os.path.join(project_dir, 'template.ewp')):
if check_command_availability('iarbuild.exe'):
print('Start to build iar project======')
os.chdir(f'{project_dir}')
___, res = run_cmd('iarbuild .\project.ewp -clean rt-thread')
if res != 0:
print('run clean failed!!')
___, res = run_cmd('iarbuild .\project.ewp rt-thread > iar.log')
if res != 0:
print('run_cmd1 failed!!')
os.chdir(f'{rtt_root}')
else:
print('iarbuild is not available, please check your iar installation')
processes = []
for project_dir in sconstruct_paths:
bsp_scons_worker(project_dir)
#p = Process(target=bsp_scons_worker, args=(project_dir,))
#p.start()
#processes.append(p)
#for p in processes:
# p.join() # 等待所有进程完成
print('finished!')
# 将failed_bsp_list.txt的内容追加到failed_bsp.log文件中
if os.path.exists(os.path.join(rtt_root, 'failed_bsp_list.txt')):
with open(os.path.join(rtt_root, 'failed_bsp_list.txt'), 'r') as file:
failed_bsp_list = file.read()
if os.path.exists(os.path.join(rtt_root, 'failed_bsp.log')):
with open(os.path.join(rtt_root, 'failed_bsp.log'), 'a') as file:
file.write(failed_bsp_list)
+2
View File
@@ -0,0 +1,2 @@
pyyaml
pandas
+87
View File
@@ -0,0 +1,87 @@
#!/bin/bash
declare -A download_urls=(
["arm-none-eabi-gcc"]="https://github.com/RT-Thread/toolchains-ci/releases/download/v1.3/gcc-arm-none-eabi-10-2020-q4-major-x86_64-linux.tar.bz2"
["mips-sde-elf-gcc"]="https://github.com/RT-Thread/toolchains-ci/releases/download/v1.1/mips-2016.05-7-mips-sde-elf-i686-pc-linux-gnu.tar.bz2"
["aarch64-none-elf-gcc"]="https://github.com/RT-Thread/toolchains-ci/releases/download/v1.6/gcc-arm-10.2-2020.11-x86_64-aarch64-none-elf.tar.xz"
["riscv64-unknown-elf-gcc"]="https://github.com/RT-Thread/toolchains-ci/releases/download/v1.4/riscv64-unknown-elf-toolchain-10.2.0-2020.12.8-x86_64-linux-ubuntu14.tar.gz"
["riscv32-unknown-elf-gcc"]="https://github.com/hpmicro/riscv-gnu-toolchain/releases/download/2022.05.15/riscv32-unknown-elf-newlib-multilib_2022.05.15_linux.tar.gz"
["riscv-none-embed-gcc"]="https://github.com/RT-Thread/toolchains-ci/releases/download/v1.5/xpack-riscv-none-embed-gcc-8.3.0-2.3-linux-x64.tar.gz"
["riscv32-esp-elf-gcc"]="https://github.com/espressif/crosstool-NG/releases/download/esp-2022r1-RC1/riscv32-esp-elf-gcc11_2_0-esp-2022r1-RC1-linux-amd64.tar.xz"
["clang"]="https://github.com/ARM-software/LLVM-embedded-toolchain-for-Arm/releases/download/release-16.0.0/LLVMEmbeddedToolchainForArm-16.0.0-Linux-x86_64.tar.gz"
)
show_help() {
echo "Available toolchains:"
for key in "${!download_urls[@]}"; do
echo " - $key"
done
}
extract_file() {
local file_name=$1
local destination=$2
echo "Extracting $file_name to $destination..."
case "$file_name" in
*.tar.bz2) tar -xjf "$file_name" -C "$destination" --strip-components=1;;
*.tar.gz) tar -xzf "$file_name" -C "$destination" --strip-components=1;;
*.tar.xz) tar -xJf "$file_name" -C "$destination" --strip-components=1;;
*) echo "Unsupported file format: $file_name"; exit 1;;
esac
echo "Extracted to $destination"
}
install_toolchain() {
local toolchain_name=$1
local url="${download_urls[$toolchain_name]}"
if [ -z "$url" ]; then
echo "Toolchain not found."
exit 1
fi
local file_name=$(basename "$url")
echo "Downloading $file_name..."
wget -q "$url"
echo "Extracting $file_name..."
local target_dir="/opt/$toolchain_name"
echo "target_dir $target_dir..."
mkdir -p "$target_dir"
extract_file "$file_name" "$target_dir"
rm $file_name
echo "Installed: $target_dir"
local toolchain_bin="$target_dir/bin"
if [[ ":$PATH:" != *":$toolchain_bin:"* ]]; then
echo "Adding $toolchain_bin to PATH..."
export PATH="$PATH:$toolchain_bin"
echo "export PATH=\$PATH:$toolchain_bin" >> ~/.bashrc
fi
#need source toolchain.sh or source ~/.bashrc to work
#local toolchain_exec=$(find "$toolchain_bin" -type f -executable | head -n 1)
local toolchain_exec=$(which "$toolchain_name")
if [ -x "$toolchain_exec" ]; then
echo "Testing executable: $toolchain_exec"
$toolchain_exec --version
else
echo "No executable found in $toolchain_bin"
fi
}
if [[ $# -eq 1 ]]; then
if [[ "$1" == "help" ]]; then
show_help
elif [ "$1" == "all" ]; then
for toolchain in "${!download_urls[@]}"; do
install_toolchain "$toolchain"
done
else
install_toolchain "$1"
fi
else
show_help
fi
+428
View File
@@ -0,0 +1,428 @@
#
# Copyright (c) 2024, RT-Thread Development Team
#
# SPDX-License-Identifier: Apache-2.0
#
# Change Logs:
# Date Author Notes
# 2024-08-25 supperthomas the first version
#
aarch64-none-elf-gcc:
bsp:
- cvitek/cv18xx_aarch64
- phytium/aarch64
- qemu-virt64-aarch64
- raspberry-pi/raspi3-64
- raspberry-pi/raspi4-64
- rockchip/rk3568
- zynqmp-a53-dfzu2eg
count: 7
arc-elf32-gcc:
bsp:
- synopsys/boards
count: 1
arm-none-eabi-gcc:
bsp:
- CME_M7
- Infineon/psoc6-cy8ckit-062-BLE
- Infineon/psoc6-cy8ckit-062-WIFI-BT
- Infineon/psoc6-cy8ckit-062S2-43012
- Infineon/psoc6-cy8ckit-062s4
- Infineon/psoc6-cy8cproto-062S3-4343W
- Infineon/psoc6-evaluationkit-062S2
- Infineon/xmc7200-kit_xmc7200_evk
- Vango/v85xx
- Vango/v85xxp
- acm32/acm32f0x0-nucleo
- acm32/acm32f4xx-nucleo
- airm2m/air105
- airm2m/air32f103
- allwinner_tina
- amebaz
- apm32/apm32e103ze-evalboard
- apm32/apm32e103ze-tinyboard
- apm32/apm32f030r8-miniboard
- apm32/apm32f051r8-evalboard
- apm32/apm32f072vb-miniboard
- apm32/apm32f091vc-miniboard
- apm32/apm32f103vb-miniboard
- apm32/apm32f103xe-minibroard
- apm32/apm32f107vc-evalboard
- apm32/apm32f407ig-minibroard
- apm32/apm32f407zg-evalboard
- apm32/apm32s103vb-miniboard
- apollo2
- asm9260t
- at32/at32a403a-start
- at32/at32a423-start
- at32/at32f402-start
- at32/at32f403a-start
- at32/at32f405-start
- at32/at32f407-start
- at32/at32f413-start
- at32/at32f415-start
- at32/at32f421-start
- at32/at32f423-start
- at32/at32f425-start
- at32/at32f435-start
- at32/at32f437-start
- at91/at91sam9260
- at91/at91sam9g45
- beaglebone
- dm365
- efm32
- essemi/es32f0654
- essemi/es32f365x
- essemi/es32f369x
- fm33lc026
- frdm-k64f
- ft2004
- ft32/ft32f072xb-starter
- fujitsu/mb9x/mb9bf500r
- fujitsu/mb9x/mb9bf506r
- fujitsu/mb9x/mb9bf568r
- fujitsu/mb9x/mb9bf618s
- gd32/arm/gd32103c-eval
- gd32/arm/gd32105c-eval
- gd32/arm/gd32105r-start
- gd32/arm/gd32107c-eval
- gd32/arm/gd32205r-start
- gd32/arm/gd32207i-eval
- gd32/arm/gd32303c-start
- gd32/arm/gd32303e-eval
- gd32/arm/gd32305r-start
- gd32/arm/gd32307e-start
- gd32/arm/gd32407v-lckfb
- gd32/arm/gd32407v-start
- gd32/arm/gd32450z-eval
- gd32/arm/gd32470z-lckfb
- gd32/arm/gd32h759i-start
- hc32/ev_hc32f448_lqfp80
- hc32/ev_hc32f460_lqfp100_v2
- hc32/ev_hc32f472_lqfp100
- hc32/ev_hc32f4a0_lqfp176
- hc32/lckfb-hc32f4a0-lqfp100
- hc32l136
- hc32l196
- hk32/hk32f030c8-mini
- ht32/ht32f12366
- ht32/ht32f52352
- lm3s8962
- lm3s9b9x
- lm4f232
- maxim/max32660-evsys
- microchip/samc21
- microchip/samd51-adafruit-metro-m4
- microchip/samd51-seeed-wio-terminal
- microchip/same54
- microchip/same70
- microchip/saml10
- mini2440
- mini4020
- mm32/mm32f3270-100ask-pitaya
- mm32f103x
- mm32f327x
- mm32l07x
- mm32l3xx
- msp432e401y-LaunchPad
- n32/n32g43xcl-stb
- n32/n32g457qel-stb
- n32/n32g45xcl-stb
- n32/n32g45xml-stb
- n32/n32g45xrl-stb
- n32/n32g45xvl-stb
- n32/n32g4frml-stb
- n32/n32l40xcl-stb
- n32/n32l436-evb
- n32/n32l43xml-stb
- n32/n32l43xrl-stb
- n32/n32wb45xl-evb
- n32g452xx/n32g452xx-mini-system
- nrf5x/nrf51822
- nrf5x/nrf52832
- nrf5x/nrf52833
- nrf5x/nrf52840
- nrf5x/nrf5340
- nuvoton/ma35-rtp
- nuvoton/nk-980iot
- nuvoton/nk-n9h30
- nuvoton/nk-rtu980
- nuvoton/numaker-hmi-ma35d1
- nuvoton/numaker-iot-m467
- nuvoton/numaker-iot-m487
- nuvoton/numaker-iot-ma35d1
- nuvoton/numaker-m032ki
- nuvoton/numaker-m2354
- nuvoton/numaker-m467hj
- nuvoton/numaker-pfm-m487
- nv32f100x
- nxp/imx/imx6sx/cortex-a9
- nxp/imx/imx6ul
- nxp/imx/imx6ull-smart
- nxp/imx/imxrt/imxrt1021-nxp-evk
- nxp/imx/imxrt/imxrt1052-atk-commander
- nxp/imx/imxrt/imxrt1052-fire-pro
- nxp/imx/imxrt/imxrt1052-nxp-evk
- nxp/imx/imxrt/imxrt1052-seeed-ArchMix
- nxp/imx/imxrt/imxrt1060-nxp-evk
- nxp/imx/imxrt/imxrt1061-forlinx-OK1061-S
- nxp/imx/imxrt/imxrt1064-nxp-evk
- nxp/imx/imxrt/imxrt1170-nxp-evk/m7
- nxp/lpc/lpc1114
- nxp/lpc/lpc176x
- nxp/lpc/lpc178x
- nxp/lpc/lpc2148
- nxp/lpc/lpc2478
- nxp/lpc/lpc408x
- nxp/lpc/lpc43xx/M0
- nxp/lpc/lpc43xx/M4
- nxp/lpc/lpc5410x
- nxp/lpc/lpc54114-lite
- nxp/lpc/lpc54608-LPCXpresso
- nxp/lpc/lpc55sxx/Libraries/template/lpc55s6xxxx
- nxp/lpc/lpc55sxx/lpc55s06_nxp_evk
- nxp/lpc/lpc55sxx/lpc55s16_nxp_evk
- nxp/lpc/lpc55sxx/lpc55s28_nxp_evk
- nxp/lpc/lpc55sxx/lpc55s36_nxp_evk
- nxp/lpc/lpc55sxx/lpc55s69_nxp_evk
- nxp/lpc/lpc824
- nxp/mcx/mcxa/frdm-mcxa153
- nxp/mcx/mcxn/frdm-mcxn236
- nxp/mcx/mcxn/frdm-mcxn947
- phytium/aarch32
- qemu-vexpress-a9
- raspberry-pi/raspi2
- raspberry-pi/raspi3-32
- raspberry-pi/raspi4-32
- raspberry-pico
- renesas/ebf_qi_min_6m5
- renesas/libraries/bsp-template
- renesas/ra2l1-cpk
- renesas/ra4m2-eco
- renesas/ra6m3-ek
- renesas/ra6m3-hmi-board
- renesas/ra6m4-cpk
- renesas/ra6m4-iot
- renesas/ra8d1-ek
- renesas/ra8d1-vision-board
- renesas/ra8m1-ek
- renesas/rzt2m_rsk
- rm48x50
- rockchip/rk2108
- sam7x
- samd21
- smartfusion2
- stm32/stm32f072-st-nucleo
- stm32/stm32f091-st-nucleo
- stm32/stm32f103-100ask-mini
- stm32/stm32f103-100ask-pro
- stm32/stm32f103-atk-nano
- stm32/stm32f103-atk-warshipv3
- stm32/stm32f103-blue-pill
- stm32/stm32f103-dofly-M3S
- stm32/stm32f103-dofly-lyc8
- stm32/stm32f103-fire-arbitrary
- stm32/stm32f103-gizwits-gokitv21
- stm32/stm32f103-hw100k-ibox
- stm32/stm32f103-onenet-nbiot
- stm32/stm32f103-yf-ufun
- stm32/stm32f103-ys-f1pro
- stm32/stm32f107-uc-eval
- stm32/stm32f207-st-nucleo
- stm32/stm32f302-st-nucleo
- stm32/stm32f334-st-nucleo
- stm32/stm32f401-st-nucleo
- stm32/stm32f401-weact-blackpill
- stm32/stm32f405-smdz-breadfruit
- stm32/stm32f405zg-mini-template
- stm32/stm32f407-armfly-v5
- stm32/stm32f407-atk-explorer
- stm32/stm32f407-fk407m2-zgt6
- stm32/stm32f407-lckfb-skystar
- stm32/stm32f407-robomaster-c
- stm32/stm32f407-rt-spark
- stm32/stm32f407-st-discovery
- stm32/stm32f410-st-nucleo
- stm32/stm32f411-atk-nano
- stm32/stm32f411-st-nucleo
- stm32/stm32f411-weact-blackpill
- stm32/stm32f412-st-nucleo
- stm32/stm32f413-st-nucleo
- stm32/stm32f427-robomaster-a
- stm32/stm32f429-armfly-v6
- stm32/stm32f429-atk-apollo
- stm32/stm32f429-fire-challenger
- stm32/stm32f429-st-disco
- stm32/stm32f446-st-nucleo
- stm32/stm32f469-st-disco
- stm32/stm32f723-st-disco
- stm32/stm32f746-st-disco
- stm32/stm32f746-st-nucleo
- stm32/stm32f767-atk-apollo
- stm32/stm32f767-fire-challenger-v1
- stm32/stm32f767-st-nucleo
- stm32/stm32f769-st-disco
- stm32/stm32g070-st-nucleo
- stm32/stm32g071-st-nucleo
- stm32/stm32g431-st-nucleo
- stm32/stm32g474-st-nucleo
- stm32/stm32g491-st-nucleo
- stm32/stm32h503-st-nucleo
- stm32/stm32h563-st-nucleo
- stm32/stm32h743-armfly-v7
- stm32/stm32h743-atk-apollo
- stm32/stm32h743-openmv-h7plus
- stm32/stm32h743-st-nucleo
- stm32/stm32h747-st-discovery
- stm32/stm32h750-armfly-h7-tool
- stm32/stm32h750-artpi
- stm32/stm32h750-fk750m1-vbt6
- stm32/stm32h750-weact-ministm32h7xx
- stm32/stm32h7s7-st-disco
- stm32/stm32l010-st-nucleo
- stm32/stm32l053-st-nucleo
- stm32/stm32l412-st-nucleo
- stm32/stm32l431-BearPi
- stm32/stm32l431-tencentos-tiny-EVB_MX+
- stm32/stm32l432-st-nucleo
- stm32/stm32l433-ali-startkit
- stm32/stm32l433-st-nucleo
- stm32/stm32l452-st-nucleo
- stm32/stm32l475-atk-pandora
- stm32/stm32l475-st-discovery
- stm32/stm32l476-st-nucleo
- stm32/stm32l496-ali-developer
- stm32/stm32l496-st-discovery
- stm32/stm32l496-st-nucleo
- stm32/stm32l4r5-st-nucleo
- stm32/stm32l4r9-st-eval
- stm32/stm32l4r9-st-sensortile-box
- stm32/stm32l552-st-nucleo
- stm32/stm32mp157a-st-discovery
- stm32/stm32mp157a-st-ev1
- stm32/stm32u575-st-nucleo
- stm32/stm32u585-iot02a
- stm32/stm32wb55-st-nucleo
- stm32/stm32wl55-st-nucleo
- stm32/stm32wle5-yizhilian-lm401
- stm32/stm32wle5-yizhilian-lm402
- synwit/swm320-mini
- synwit/swm341-mini
- ti/c28x/tms320f28379d
- tkm32F499
- tm4c123bsp
- tm4c129x
- w60x
- wch/arm/ch32f103c8-core
- wch/arm/ch32f203r-evt
- wch/arm/ch579m
- xplorer4330/M0
- xplorer4330/M4
- yichip/yc3121-pos
- yichip/yc3122-pos
- zynqmp-r5-axu4ev
count: 298
download_url: https://github.com/RT-Thread/toolchains-ci/releases/download/v1.3/gcc-arm-none-eabi-10-2020-q4-major-x86_64-linux.tar.bz2
avr32-gcc:
bsp:
- avr32/at32uc3a0256
- avr32/at32uc3b0256
count: 2
csky-abiv2-elf-gcc:
bsp:
- ck802
- essemi/es32vf2264
count: 2
gcc:
bsp:
- simulator
count: 1
i386-unknown-elf-gcc:
bsp:
- x86
count: 1
m32c-elf-gcc:
bsp:
- m16c62p
- upd70f3454
count: 2
mips-sde-elf-gcc:
bsp:
- loongson/ls1bdev
- loongson/ls1cdev
- loongson/ls2kdev
- mipssim
count: 4
download_url: https://github.com/RT-Thread/toolchains-ci/releases/download/v1.6/gcc-arm-10.2-2020.11-x86_64-aarch64-none-elf.tar.xz
powerpc-eabi-gcc:
bsp:
- taihu
count: 1
riscv-none-embed-gcc:
bsp:
- gd32/risc-v/gd32vf103r-start
- gd32/risc-v/gd32vf103v-eval
- hifive1
- juicevm
- k210
- rv32m1_vega/ri5cy
- wch/risc-v/ch32v103r-evt
- wch/risc-v/ch32v208w-r0
- wch/risc-v/ch32v307v-r1
- wch/risc-v/ch569w-evt
- wch/risc-v/yd-ch32v307vct6
count: 11
download_url: https://github.com/RT-Thread/toolchains-ci/releases/download/v1.5/xpack-riscv-none-embed-gcc-8.3.0-2.3-linux-x64.tar.gz
riscv-nuclei-elf-gcc:
bsp:
- nuclei/gd32vf103_rvstar
- nuclei/nuclei_fpga_eval
count: 2
riscv32-esp-elf-gcc:
bsp:
- ESP32_C3
count: 1
download_url: https://github.com/espressif/crosstool-NG/releases/download/esp-2022r1-RC1/riscv32-esp-elf-gcc11_2_0-esp-2022r1-RC1-linux-amd64.tar.xz
riscv32-unknown-elf-gcc:
bsp:
- core-v-mcu/core-v-cv32e40p
- hpmicro/hpm5300evk
- hpmicro/hpm5301evklite
- hpmicro/hpm6200evk
- hpmicro/hpm6300evk
- hpmicro/hpm6750evk
- hpmicro/hpm6750evk2
- hpmicro/hpm6750evkmini
- hpmicro/hpm6800evk
count: 9
download_url: https://github.com/hpmicro/riscv-gnu-toolchain/releases/download/2022.05.15/riscv32-unknown-elf-newlib-multilib_2022.05.15_linux.tar.gz
riscv64-unknown-elf-gcc:
bsp:
- bluetrum/ab32vg1-ab-prougen
- bouffalo_lab/bl60x
- bouffalo_lab/bl61x
- bouffalo_lab/bl70x
- bouffalo_lab/bl808/lp
- bouffalo_lab/bl808/m0
- cvitek/c906_little
- cvitek/cv18xx_risc-v
- qemu-virt64-riscv
- sparkfun-redv
- thead-smart
count: 11
download_url: https://github.com/RT-Thread/toolchains-ci/releases/download/v1.4/riscv64-unknown-elf-toolchain-10.2.0-2020.12.8-x86_64-linux-ubuntu14.tar.gz
riscv64-unknown-linux-musl-gcc:
bsp:
- allwinner/d1
- allwinner/d1s
- bouffalo_lab/bl808/d0
count: 3
sparc-gaisler-elf-gcc:
bsp:
- bm3803
count: 1
unicore32-linux-gcc:
bsp:
- sep6200
count: 1
+69
View File
@@ -0,0 +1,69 @@
"""
Tool-specific initialization for Clang static analyzer
There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""
__revision__ = "tools/clang-analyze.py 2013-09-06 grissiom"
import os
import os.path
import SCons.Action
import SCons.Builder
import SCons.Defaults
import SCons.Tool
import SCons.Util
import rtconfig
def generate(env):
assert(rtconfig.CROSS_TOOL == 'clang-analyze')
# let gnu_tools setup a basic env(learnt from SCons/Tools/mingw.py)
gnu_tools = ['gcc', 'g++', 'gnulink', 'ar', 'gas', 'm4']
for tool in gnu_tools:
SCons.Tool.Tool(tool)(env)
# then we could stand on the shoulders of gaints
env['CC'] = 'ccc-analyzer'
env['CXX'] = 'c++-analyzer'
env['AS'] = 'true'
env['AR'] = 'true'
env['LINK'] = 'true'
env['CFLAGS'] = ['-fsyntax-only', '-Wall', '-Wno-invalid-source-encoding', '-m32']
env['LINKFLAGS'] = '-Wl,--gc-sections'
env['ARFLAGS'] = '-rc'
# only check, don't compile. ccc-analyzer use CCC_CC as the CC.
# fsyntax-only will give us some additional warning messages
env['ENV']['CCC_CC'] = 'clang'
env['ENV']['CCC_CXX'] = 'clang++'
# setup the output dir and format
env['ENV']['CCC_ANALYZER_HTML'] = './build/'
env['ENV']['CCC_ANALYZER_OUTPUT_FORMAT'] = 'html'
# Some setting from the platform also have to be overridden:
env['OBJSUFFIX'] = '.o'
env['LIBPREFIX'] = 'lib'
env['LIBSUFFIX'] = '.a'
if rtconfig.EXEC_PATH:
if not os.path.exists(rtconfig.EXEC_PATH):
print()
print('warning: rtconfig.EXEC_PATH(%s) does not exists.' % rtconfig.EXEC_PATH)
print()
return
env.AppendENVPath('PATH', rtconfig.EXEC_PATH)
def exists(env):
return env.Detect(['ccc-analyzer', 'c++-analyzer'])
# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:
+191
View File
@@ -0,0 +1,191 @@
#
# File : compile_commands.py
# This file is part of RT-Thread RTOS
# COPYRIGHT (C) 2006 - 2015, RT-Thread Development Team
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Change Logs:
# Date Author Notes
# 2025-03-02 ZhaoCake Create compile_commands.json without bear.
import os
import json
import re
from SCons.Script import *
def collect_compile_info(env):
"""收集编译命令和文件信息"""
print("=> Starting compile command collection")
compile_commands = []
collected_files = set()
def get_command_string(source, target, env, for_signature):
"""从SCons获取实际的编译命令"""
if env.get('CCCOMSTR'):
return env.get('CCCOM')
return '${CCCOM}'
def on_compile(target, source, env):
"""编译动作的回调函数"""
print(f" Processing compilation for {len(source)} source files")
for src in source:
src_path = str(src)
if src_path in collected_files:
continue
collected_files.add(src_path)
directory = os.path.abspath(os.path.dirname(src_path))
# 构建编译命令
command = env.subst(get_command_string(source, target, env, True))
# 解析include路径
includes = []
for path in env.get('CPPPATH', []):
includes.append('-I' + str(path))
# 添加编译命令记录
entry = {
'directory': directory,
'command': f"{command} {' '.join(includes)}",
'file': os.path.abspath(src_path),
'output': str(target[0]) if target else ''
}
compile_commands.append(entry)
print(f" Added compile command for: {os.path.basename(src_path)}")
return on_compile, compile_commands
def generate_compile_commands(env):
"""生成compile_commands.json"""
print("=> Enabling compile commands generation...")
# 获取输出路径并存储到环境变量
output_path = GetOption('compile-commands-out') or 'compile_commands.json'
env['COMPILE_COMMANDS_OUT'] = output_path
print(f" Compile commands will be written to: {os.path.abspath(output_path)}")
# 注册编译回调并保存到环境变量
callback, compile_commands = collect_compile_info(env)
env['COMPILE_COMMANDS'] = compile_commands
env.AddPreAction('*.o', callback)
print(" Registered compile command collector")
# 定义后处理动作
def write_compile_commands(target, source, env):
print("\n=> [DEBUG] Entering write_compile_commands callback")
print(f" Target: {target}")
print(f" Source: {source}")
output_path = env.get('COMPILE_COMMANDS_OUT', 'compile_commands.json')
compile_commands = env.get('COMPILE_COMMANDS', [])
try:
if not compile_commands:
print("Warning: No compile commands collected, skipping file generation")
return
print(f"\n=> Writing compile_commands.json ({len(compile_commands)} entries)")
with open(output_path, 'w') as f:
json.dump(compile_commands, f, indent=2)
print(f"=> Successfully generated: {os.path.abspath(output_path)}")
except PermissionError:
print(f"\nError: Permission denied when writing to {output_path}")
print("Please check file permissions and try again")
except Exception as e:
print(f"\nError writing compile_commands.json: {str(e)}")
import traceback
traceback.print_exc()
# 使用None作为目标以确保总是执行
print("=> Adding post-build action for compile_commands generation")
env.AddPostAction(None, write_compile_commands)
def parse_compile_paths(json_path, rt_thread_root=None):
"""解析compile_commands.json并提取RT-Thread相关的包含路径
Args:
json_path: compile_commands.json的路径
rt_thread_root: RT-Thread根目录路径,默认使用环境变量RTT_ROOT
Returns:
dict: 包含以下键的字典:
'sources': RT-Thread源文件的相对路径列表
'includes': RT-Thread头文件目录的相对路径列表
"""
if rt_thread_root is None:
rt_thread_root = os.getenv('RTT_ROOT')
if not rt_thread_root:
raise ValueError("RT-Thread根目录未指定")
rt_thread_root = os.path.abspath(rt_thread_root)
result = {
'sources': set(),
'includes': set()
}
try:
with open(json_path, 'r') as f:
compile_commands = json.load(f)
for entry in compile_commands:
# 处理源文件
src_file = entry.get('file', '')
if src_file.startswith(rt_thread_root):
rel_path = os.path.relpath(src_file, rt_thread_root)
result['sources'].add(os.path.dirname(rel_path))
# 处理包含路径
command = entry.get('command', '')
include_paths = [p[2:] for p in command.split() if p.startswith('-I')]
for inc_path in include_paths:
if inc_path.startswith(rt_thread_root):
rel_path = os.path.relpath(inc_path, rt_thread_root)
result['includes'].add(rel_path)
# 转换为排序列表
result['sources'] = sorted(list(result['sources']))
result['includes'] = sorted(list(result['includes']))
return result
except Exception as e:
print(f"Error parsing compile_commands.json: {str(e)}")
return None
def get_minimal_dist_paths(json_path=None, rt_thread_root=None):
"""获取最小化发布所需的路径
Args:
json_path: compile_commands.json的路径,默认为当前目录下的compile_commands.json
rt_thread_root: RT-Thread根目录路径
Returns:
list: 需要包含在发布包中的相对路径列表
"""
if json_path is None:
json_path = 'compile_commands.json'
paths = parse_compile_paths(json_path, rt_thread_root)
if not paths:
return []
# 合并源码和头文件路径
all_paths = set(paths['sources']) | set(paths['includes'])
return sorted(list(all_paths))
+136
View File
@@ -0,0 +1,136 @@
# RT-Thread 构建系统文档
欢迎使用RT-Thread构建系统文档。本文档集详细介绍了RT-Thread基于SCons的构建系统的使用方法和技术原理。
## 文档目录
### 📚 用户指南
1. **[构建系统使用指南](构建系统使用指南.md)**
- 快速开始
- 命令行选项详解
- 工具链配置
- 项目生成
- 软件包管理
- 高级功能
- 常见问题解答
2. **[SConscript编写指南](SConscript编写指南.md)**
- 基础语法
- 常用模式
- 高级技巧
- 最佳实践
- 示例集合
### 🔧 技术文档
3. **[构建系统技术原理](构建系统技术原理.md)**
- 系统架构设计
- 核心模块分析
- 构建流程详解
- 依赖管理机制
- 工具链适配层
- 项目生成器架构
- 扩展机制
## 快速导航
### 常用命令
```bash
# 基础编译
scons # 默认编译
scons -j8 # 8线程并行编译
scons -c # 清理编译产物
# 配置管理
menuconfig # 图形化配置
scons --pyconfig # Python脚本配置
# 项目生成
scons --target=mdk5 # 生成Keil MDK5项目
scons --target=iar # 生成IAR项目
scons --target=vsc # 生成VS Code项目
scons --target=cmake # 生成CMake项目
# 软件包管理
pkgs --update # 更新软件包
pkgs --list # 列出已安装包
```
### 核心概念
- **SConstruct**: BSP根目录的主构建脚本
- **SConscript**: 各个组件/目录的构建脚本
- **rtconfig.py**: 工具链和平台配置
- **rtconfig.h**: RT-Thread功能配置
- **DefineGroup**: 定义组件的核心函数
- **GetDepend**: 检查依赖的核心函数
## 构建系统架构图
![arch](./readme_arch.drawio.png)
## 主要特性
**多工具链支持**
- GCC (ARM/RISC-V/x86)
- Keil MDK (ARMCC/ARMClang)
- IAR
- Visual Studio
**灵活的配置系统**
- Kconfig图形配置
- 条件编译支持
- 本地编译选项
**丰富的项目生成器**
- IDE项目文件生成
- CMake支持
- Makefile生成
- VS Code配置
**模块化设计**
- 组件独立构建
- 清晰的依赖管理
- 可扩展架构
## 开发工作流
```mermaid
graph LR
A[配置系统] --> B[编写代码]
B --> C[构建项目]
C --> D[调试运行]
D --> E{是否完成?}
E -->|否| B
E -->|是| F[发布]
A1[menuconfig] -.-> A
C1[scons] -.-> C
C2[IDE项目] -.-> C
```
## 相关链接
- [RT-Thread官网](https://www.rt-thread.org)
- [RT-Thread GitHub](https://github.com/RT-Thread/rt-thread)
- [SCons官方文档](https://scons.org/documentation.html)
## 贡献指南
如果您发现文档中的错误或有改进建议,欢迎:
1. 在GitHub上提交Issue
2. 提交Pull Request
3. 在RT-Thread社区论坛反馈
## 版本信息
- 文档版本:1.0.0
- 更新日期:2024-01
- 适用版本:RT-Thread 4.1.0+
---
**注意**:本文档基于RT-Thread最新版本编写,部分功能可能需要特定版本支持。使用前请确认您的RT-Thread版本。
@@ -0,0 +1,948 @@
# RT-Thread SConscript 编写指南
## 目录
1. [概述](#概述)
2. [基础语法](#基础语法)
3. [常用模式](#常用模式)
4. [高级技巧](#高级技巧)
5. [最佳实践](#最佳实践)
6. [示例集合](#示例集合)
7. [常见问题](#常见问题)
## 概述
SConscript是RT-Thread构建系统中的模块构建脚本,每个组件或目录都可以有自己的SConscript文件。本指南将详细介绍如何编写高质量的SConscript文件。
### SConscript在构建系统中的位置
```
项目根目录/
├── SConstruct # 主构建脚本
├── applications/
│ └── SConscript # 应用层构建脚本
├── drivers/
│ └── SConscript # 驱动层构建脚本
└── components/
├── SConscript # 组件主脚本
└── finsh/
└── SConscript # 子组件脚本
```
## 基础语法
### 1. 基本结构
```python
# 导入构建模块
from building import *
# 获取当前目录
cwd = GetCurrentDir()
# 定义源文件
src = ['main.c', 'app.c']
# 定义头文件路径
CPPPATH = [cwd]
# 定义组
group = DefineGroup('Applications', src, depend = [''], CPPPATH = CPPPATH)
# 返回组对象
Return('group')
```
### 2. 导入building模块
```python
from building import *
```
这行代码导入了RT-Thread构建系统的所有函数,包括:
- `GetCurrentDir()` - 获取当前目录
- `DefineGroup()` - 定义组件组
- `GetDepend()` - 检查依赖
- `Glob()` - 文件通配符匹配
- `SrcRemove()` - 移除源文件
- `DoBuilding()` - 执行构建
### 3. 源文件定义
#### 手动指定文件列表
```python
src = ['file1.c', 'file2.c', 'file3.c']
```
#### 使用通配符
```python
src = Glob('*.c') # 当前目录所有.c文件
src = Glob('src/*.c') # src子目录所有.c文件
src = Glob('**/*.c') # 递归所有子目录的.c文件
```
#### 混合使用
```python
src = ['main.c'] + Glob('drivers/*.c')
```
### 4. 条件编译
#### 基于宏定义
```python
src = ['common.c']
if GetDepend('RT_USING_SERIAL'):
src += ['serial.c']
if GetDepend('RT_USING_I2C'):
src += ['i2c.c']
```
#### 基于平台
```python
import rtconfig
if rtconfig.PLATFORM == 'gcc':
src += ['gcc_specific.c']
elif rtconfig.PLATFORM == 'armcc':
src += ['keil_specific.c']
```
### 5. 移除文件
```python
src = Glob('*.c')
SrcRemove(src, ['test.c', 'debug.c']) # 移除不需要的文件
```
## 常用模式
### 1. 简单组件模式
最基础的SConscript模式:
```python
from building import *
src = Glob('*.c')
CPPPATH = [GetCurrentDir()]
group = DefineGroup('MyComponent', src, depend = ['RT_USING_MYCOMPONENT'],
CPPPATH = CPPPATH)
Return('group')
```
### 2. 条件编译模式
根据配置选项包含不同的源文件:
```python
from building import *
src = ['core.c']
CPPPATH = [GetCurrentDir()]
# 功能模块条件编译
if GetDepend('MYCOMPONENT_USING_FEATURE_A'):
src += ['feature_a.c']
if GetDepend('MYCOMPONENT_USING_FEATURE_B'):
src += ['feature_b.c']
# 平台相关代码
if rtconfig.PLATFORM == 'gcc':
src += ['port_gcc.c']
elif rtconfig.PLATFORM == 'armcc':
src += ['port_keil.c']
elif rtconfig.PLATFORM == 'iccarm':
src += ['port_iar.c']
group = DefineGroup('MyComponent', src, depend = ['RT_USING_MYCOMPONENT'],
CPPPATH = CPPPATH)
Return('group')
```
### 3. 多目录组织模式
处理复杂的目录结构:
```python
from building import *
cwd = GetCurrentDir()
# 源文件来自多个目录
src = Glob('src/*.c')
src += Glob('port/*.c')
src += Glob('hal/*.c')
# 多个头文件路径
CPPPATH = [
cwd,
cwd + '/include',
cwd + '/internal',
cwd + '/port'
]
# 根据配置添加特定目录
if GetDepend('RT_USING_LWIP'):
src += Glob('lwip/*.c')
CPPPATH += [cwd + '/lwip']
group = DefineGroup('Network', src, depend = ['RT_USING_NETWORK'],
CPPPATH = CPPPATH)
Return('group')
```
### 4. 递归子目录模式
自动处理所有子目录的SConscript:
```python
import os
from building import *
objs = []
cwd = GetCurrentDir()
# 获取所有子目录
list = os.listdir(cwd)
# 需要跳过的目录
skip_dirs = ['test', 'doc', 'examples', '.git']
for d in list:
path = os.path.join(cwd, d)
# 检查是否是目录且包含SConscript
if os.path.isdir(path) and d not in skip_dirs:
if os.path.isfile(os.path.join(path, 'SConscript')):
objs = objs + SConscript(os.path.join(d, 'SConscript'))
Return('objs')
```
### 5. 库文件链接模式
链接预编译的库文件:
```python
from building import *
import os
cwd = GetCurrentDir()
# 只包含必要的接口文件
src = ['lib_interface.c']
CPPPATH = [cwd + '/include']
# 库文件配置
LIBS = []
LIBPATH = []
# 根据架构选择库文件
import rtconfig
if rtconfig.ARCH == 'arm':
if rtconfig.CPU == 'cortex-m4':
LIBS += ['mylib_cm4']
LIBPATH += [cwd + '/lib/cortex-m4']
elif rtconfig.CPU == 'cortex-m3':
LIBS += ['mylib_cm3']
LIBPATH += [cwd + '/lib/cortex-m3']
group = DefineGroup('MyLib', src, depend = ['RT_USING_MYLIB'],
CPPPATH = CPPPATH, LIBS = LIBS, LIBPATH = LIBPATH)
Return('group')
```
## 高级技巧
### 1. 本地编译选项
为特定模块设置独立的编译选项:
```python
from building import *
src = Glob('*.c')
CPPPATH = [GetCurrentDir()]
# 全局编译选项(影响依赖此组件的其他组件)
CPPDEFINES = ['GLOBAL_DEFINE']
# 本地编译选项(仅影响当前组件)
LOCAL_CFLAGS = ''
LOCAL_CPPDEFINES = ['LOCAL_DEFINE']
LOCAL_CPPPATH = ['./private']
# 根据编译器设置优化选项
import rtconfig
if rtconfig.PLATFORM == 'gcc':
LOCAL_CFLAGS += ' -O3 -funroll-loops'
elif rtconfig.PLATFORM == 'armcc':
LOCAL_CFLAGS += ' -O3 --loop_optimization_level=2'
group = DefineGroup('HighPerf', src, depend = ['RT_USING_HIGHPERF'],
CPPPATH = CPPPATH,
CPPDEFINES = CPPDEFINES,
LOCAL_CFLAGS = LOCAL_CFLAGS,
LOCAL_CPPDEFINES = LOCAL_CPPDEFINES,
LOCAL_CPPPATH = LOCAL_CPPPATH
)
Return('group')
```
### 2. 动态源文件生成
在构建时生成源文件:
```python
from building import *
import time
def generate_version_file():
"""生成版本信息文件"""
version_c = '''
/* Auto-generated file, do not edit! */
#include "version.h"
const char *build_time = "%s";
const char *version = "%s";
''' % (time.strftime('%Y-%m-%d %H:%M:%S'), '1.0.0')
with open('version_gen.c', 'w') as f:
f.write(version_c)
# 生成文件
generate_version_file()
src = ['main.c', 'version_gen.c']
CPPPATH = [GetCurrentDir()]
group = DefineGroup('App', src, depend = [''], CPPPATH = CPPPATH)
Return('group')
```
### 3. 复杂依赖处理
处理复杂的依赖关系:
```python
from building import *
src = []
CPPPATH = [GetCurrentDir()]
CPPDEFINES = []
# 基础功能
if GetDepend('RT_USING_DEVICE'):
src += ['device_core.c']
# 串口驱动(依赖设备框架)
if GetDepend('RT_USING_SERIAL'):
src += ['serial.c']
# 串口DMA(依赖串口驱动)
if GetDepend('RT_SERIAL_USING_DMA'):
src += ['serial_dma.c']
CPPDEFINES += ['SERIAL_USING_DMA']
# SPI驱动(依赖设备框架)
if GetDepend('RT_USING_SPI'):
src += ['spi.c']
# SPI DMA(依赖SPI驱动)
if GetDepend('RT_SPI_USING_DMA'):
src += ['spi_dma.c']
# 错误检查
if GetDepend('RT_SERIAL_USING_DMA') and not GetDepend('RT_USING_SERIAL'):
print('Error: RT_SERIAL_USING_DMA requires RT_USING_SERIAL!')
exit(1)
group = DefineGroup('Drivers', src, depend = ['RT_USING_DEVICE'],
CPPPATH = CPPPATH, CPPDEFINES = CPPDEFINES)
Return('group')
```
### 4. 平台特定实现
根据不同平台包含不同实现:
```python
from building import *
import rtconfig
cwd = GetCurrentDir()
src = ['common.c']
CPPPATH = [cwd, cwd + '/include']
# 架构相关目录映射
arch_map = {
'arm': {
'cortex-m3': 'arm/cortex-m3',
'cortex-m4': 'arm/cortex-m4',
'cortex-m7': 'arm/cortex-m7',
'cortex-a': 'arm/cortex-a'
},
'risc-v': {
'rv32': 'riscv/rv32',
'rv64': 'riscv/rv64'
},
'x86': {
'i386': 'x86/i386',
'x86_64': 'x86/x86_64'
}
}
# 根据架构和CPU选择实现
if hasattr(rtconfig, 'ARCH') and hasattr(rtconfig, 'CPU'):
arch = rtconfig.ARCH
cpu = rtconfig.CPU
if arch in arch_map and cpu in arch_map[arch]:
port_dir = arch_map[arch][cpu]
port_src = Glob(port_dir + '/*.c')
port_src += Glob(port_dir + '/*.S')
src += port_src
CPPPATH += [cwd + '/' + port_dir]
else:
print('Warning: No port for %s - %s' % (arch, cpu))
group = DefineGroup('MyDriver', src, depend = ['RT_USING_MYDRIVER'],
CPPPATH = CPPPATH)
Return('group')
```
### 5. 第三方库集成
集成第三方库的模板:
```python
from building import *
import os
cwd = GetCurrentDir()
# 第三方库路径
lib_path = cwd + '/3rdparty/libfoo'
# 检查库是否存在
if not os.path.exists(lib_path):
print('Error: libfoo not found at', lib_path)
print('Please run: git submodule update --init')
Return('group')
# 库源文件
src = Glob(lib_path + '/src/*.c')
# 移除不需要的文件
SrcRemove(src, [
lib_path + '/src/test.c',
lib_path + '/src/example.c'
])
# 头文件路径
CPPPATH = [
lib_path + '/include',
cwd + '/port' # 移植层头文件
]
# 添加移植层
src += Glob('port/*.c')
# 配置宏定义
CPPDEFINES = ['LIBFOO_RTOS_RTTHREAD']
# 根据配置启用功能
if GetDepend('LIBFOO_ENABLE_FLOAT'):
CPPDEFINES += ['LIBFOO_USE_FLOAT']
if GetDepend('LIBFOO_ENABLE_STDIO'):
CPPDEFINES += ['LIBFOO_USE_STDIO']
group = DefineGroup('libfoo', src, depend = ['RT_USING_LIBFOO'],
CPPPATH = CPPPATH, CPPDEFINES = CPPDEFINES)
Return('group')
```
### 6. 条件导出符号
根据配置导出不同的API
```python
from building import *
src = ['core.c']
CPPPATH = [GetCurrentDir()]
# API版本控制
if GetDepend('RT_USING_MODULE_API_V2'):
src += ['api_v2.c']
CPPDEFINES = ['MODULE_API_VERSION=2']
else:
src += ['api_v1.c']
CPPDEFINES = ['MODULE_API_VERSION=1']
# 根据配置级别导出不同功能
if GetDepend('MODULE_EXPERT_MODE'):
src += ['expert_api.c']
CPPDEFINES += ['EXPORT_EXPERT_API']
group = DefineGroup('Module', src, depend = ['RT_USING_MODULE'],
CPPPATH = CPPPATH, CPPDEFINES = CPPDEFINES)
Return('group')
```
## 最佳实践
### 1. 文件组织原则
```
component/
├── SConscript # 主构建脚本
├── Kconfig # 配置选项
├── README.md # 组件说明
├── include/ # 公开头文件
│ └── component.h
├── src/ # 源文件
│ ├── core.c
│ └── utils.c
├── port/ # 平台相关代码
│ ├── cortex-m/
│ └── risc-v/
└── examples/ # 示例代码
└── example.c
```
### 2. 依赖管理最佳实践
```python
from building import *
# 1. 明确声明依赖
REQUIRED_DEPS = ['RT_USING_DEVICE', 'RT_USING_HEAP']
# 2. 检查必要依赖
for dep in REQUIRED_DEPS:
if not GetDepend(dep):
print('Error: %s requires %s' % ('MyComponent', dep))
Return('group')
# 3. 可选依赖
src = ['core.c']
# 可选功能
OPTIONAL_FEATURES = {
'RT_MYCOMPONENT_USING_DMA': 'dma.c',
'RT_MYCOMPONENT_USING_INTERRUPT': 'interrupt.c',
'RT_MYCOMPONENT_USING_STATS': 'statistics.c'
}
for macro, file in OPTIONAL_FEATURES.items():
if GetDepend(macro):
src += [file]
group = DefineGroup('MyComponent', src, depend = ['RT_USING_MYCOMPONENT'])
Return('group')
```
### 3. 错误处理
```python
from building import *
import os
# 检查关键文件
critical_files = ['config.h', 'version.h']
for f in critical_files:
if not os.path.exists(f):
print('Error: Missing required file:', f)
# 返回空组,不中断整体构建
group = DefineGroup('MyComponent', [], depend = [''])
Return('group')
# 检查工具链
import rtconfig
supported_toolchains = ['gcc', 'armcc', 'iar']
if rtconfig.PLATFORM not in supported_toolchains:
print('Warning: Toolchain %s not tested' % rtconfig.PLATFORM)
# 正常构建流程
src = Glob('*.c')
group = DefineGroup('MyComponent', src, depend = ['RT_USING_MYCOMPONENT'])
Return('group')
```
### 4. 性能优化
```python
from building import *
import os
# 1. 缓存文件列表(避免重复扫描)
_file_cache = {}
def cached_glob(pattern):
if pattern not in _file_cache:
_file_cache[pattern] = Glob(pattern)
return _file_cache[pattern]
# 2. 延迟加载(仅在需要时扫描)
src = []
if GetDepend('RT_USING_MYCOMPONENT'):
src = cached_glob('*.c')
if GetDepend('RT_MYCOMPONENT_USING_EXTRA'):
src += cached_glob('extra/*.c')
# 3. 避免深度递归(使用显式路径)
# 不好的做法
# src = Glob('**/*.c') # 递归所有子目录
# 好的做法
src = Glob('*.c')
src += Glob('core/*.c')
src += Glob('hal/*.c')
group = DefineGroup('MyComponent', src, depend = ['RT_USING_MYCOMPONENT'])
Return('group')
```
### 5. 文档化
```python
"""
SConscript for MyComponent
This component provides [功能描述]
Configuration:
RT_USING_MYCOMPONENT - Enable this component
RT_MYCOMPONENT_USING_DMA - Enable DMA support
RT_MYCOMPONENT_BUFFER_SIZE - Buffer size (default: 256)
Dependencies:
- RT_USING_DEVICE (required)
- RT_USING_DMA (optional, for DMA support)
"""
from building import *
# ... 构建脚本内容 ...
```
## 示例集合
### 示例1:设备驱动SConscript
```python
from building import *
cwd = GetCurrentDir()
# 驱动源文件
src = []
CPPPATH = [cwd + '/../inc']
# I2C驱动
if GetDepend('RT_USING_I2C'):
src += ['drv_i2c.c']
# SPI驱动
if GetDepend('RT_USING_SPI'):
src += ['drv_spi.c']
# QSPI支持
if GetDepend('RT_USING_QSPI'):
src += ['drv_qspi.c']
# USB驱动
if GetDepend('RT_USING_USB'):
src += ['drv_usb.c']
if GetDepend('RT_USING_USB_HOST'):
src += ['drv_usbh.c']
if GetDepend('RT_USING_USB_DEVICE'):
src += ['drv_usbd.c']
# SDIO驱动
if GetDepend('RT_USING_SDIO'):
src += ['drv_sdio.c']
group = DefineGroup('Drivers', src, depend = [''], CPPPATH = CPPPATH)
Return('group')
```
### 示例2:网络组件SConscript
```python
from building import *
cwd = GetCurrentDir()
src = []
CPPPATH = [cwd]
# 网络接口层
if GetDepend('RT_USING_NETDEV'):
src += Glob('netdev/*.c')
CPPPATH += [cwd + '/netdev']
# SAL套接字抽象层
if GetDepend('RT_USING_SAL'):
src += Glob('sal/src/*.c')
src += Glob('sal/socket/*.c')
CPPPATH += [cwd + '/sal/include']
# AT指令支持
if GetDepend('SAL_USING_AT'):
src += Glob('sal/impl/af_inet_at.c')
# LwIP支持
if GetDepend('SAL_USING_LWIP'):
src += Glob('sal/impl/af_inet_lwip.c')
# AT指令框架
if GetDepend('RT_USING_AT'):
src += Glob('at/src/*.c')
CPPPATH += [cwd + '/at/include']
# AT Socket
if GetDepend('AT_USING_SOCKET'):
src += Glob('at/at_socket/*.c')
group = DefineGroup('Network', src, depend = [''], CPPPATH = CPPPATH)
Return('group')
```
### 示例3:文件系统SConscript
```python
from building import *
cwd = GetCurrentDir()
src = []
CPPPATH = [cwd + '/include']
# DFS框架
if GetDepend('RT_USING_DFS'):
src += Glob('src/*.c')
# ELM FatFS
if GetDepend('RT_USING_DFS_ELMFAT'):
src += Glob('filesystems/elmfat/*.c')
# FatFS版本选择
if GetDepend('RT_DFS_ELM_USE_LFN'):
src += ['filesystems/elmfat/ffunicode.c']
# ROMFS
if GetDepend('RT_USING_DFS_ROMFS'):
src += ['filesystems/romfs/dfs_romfs.c']
# DevFS
if GetDepend('RT_USING_DFS_DEVFS'):
src += ['filesystems/devfs/devfs.c']
# NFS
if GetDepend('RT_USING_DFS_NFS'):
src += Glob('filesystems/nfs/*.c')
CPPPATH += [cwd + '/filesystems/nfs']
group = DefineGroup('Filesystem', src, depend = ['RT_USING_DFS'],
CPPPATH = CPPPATH)
Return('group')
```
### 示例4:使用package.json的SConscript
```python
from building import *
import os
import json
cwd = GetCurrentDir()
# 尝试使用package.json
package_file = os.path.join(cwd, 'package.json')
if os.path.exists(package_file):
# 使用自动构建
objs = BuildPackage(package_file)
else:
# 手动构建
src = Glob('src/*.c')
CPPPATH = [cwd + '/include']
# 读取配置
config_file = os.path.join(cwd, 'config.json')
if os.path.exists(config_file):
with open(config_file, 'r') as f:
config = json.load(f)
# 根据配置添加源文件
for feature in config.get('features', []):
if GetDepend('RT_USING_' + feature.upper()):
src += Glob('src/%s/*.c' % feature)
objs = DefineGroup('MyPackage', src, depend = ['RT_USING_MYPACKAGE'],
CPPPATH = CPPPATH)
Return('objs')
```
## 常见问题
### Q1: 如何调试SConscript
```python
from building import *
# 1. 打印调试信息
print('Current directory:', GetCurrentDir())
print('GetDepend RT_USING_XXX:', GetDepend('RT_USING_XXX'))
# 2. 打印源文件列表
src = Glob('*.c')
print('Source files:', src)
# 3. 条件调试输出
if GetOption('verbose'):
print('Detailed debug info...')
# 4. 检查环境变量
import os
print('RTT_ROOT:', os.getenv('RTT_ROOT'))
```
### Q2: 如何处理可选的依赖?
```python
from building import *
src = ['core.c']
# 可选依赖处理
optional_deps = {
'RT_USING_SERIAL': ['serial.c', 'serial_ops.c'],
'RT_USING_CAN': ['can.c', 'can_ops.c'],
'RT_USING_I2C': ['i2c.c', 'i2c_ops.c']
}
for dep, files in optional_deps.items():
if GetDepend(dep):
src += files
# 检查组合依赖
if GetDepend('RT_USING_SERIAL') and GetDepend('RT_USING_DMA'):
src += ['serial_dma.c']
group = DefineGroup('Drivers', src, depend = ['RT_USING_DEVICE'])
Return('group')
```
### Q3: 如何支持多个工具链?
```python
from building import *
import rtconfig
src = ['common.c']
# 工具链特定文件
toolchain_files = {
'gcc': ['gcc_startup.S', 'gcc_specific.c'],
'armcc': ['keil_startup.s', 'keil_specific.c'],
'iccarm': ['iar_startup.s', 'iar_specific.c']
}
if rtconfig.PLATFORM in toolchain_files:
src += toolchain_files[rtconfig.PLATFORM]
else:
print('Warning: Unknown toolchain', rtconfig.PLATFORM)
# 工具链特定编译选项
LOCAL_CFLAGS = ''
if rtconfig.PLATFORM == 'gcc':
LOCAL_CFLAGS = '-Wno-unused-function'
elif rtconfig.PLATFORM == 'armcc':
LOCAL_CFLAGS = '--diag_suppress=177'
group = DefineGroup('MyComponent', src, depend = [''],
LOCAL_CFLAGS = LOCAL_CFLAGS)
Return('group')
```
### Q4: 如何处理生成的代码?
```python
from building import *
import subprocess
def generate_code():
"""生成代码"""
# 运行代码生成器
cmd = ['python', 'codegen.py', '-o', 'generated.c']
subprocess.check_call(cmd)
# 确保生成代码
if GetDepend('RT_USING_CODEGEN'):
generate_code()
src = ['generated.c']
else:
src = ['default.c']
group = DefineGroup('Generated', src, depend = [''])
Return('group')
```
### Q5: 如何组织大型项目?
```python
# 主SConscript
from building import *
objs = []
# 子模块列表
modules = [
'core',
'drivers',
'network',
'filesystem',
'gui'
]
# 根据配置包含模块
for module in modules:
# 检查模块是否启用
if GetDepend('RT_USING_' + module.upper()):
# 构建子模块
objs += SConscript(module + '/SConscript')
Return('objs')
```
## 总结
编写高质量的SConscript需要:
1. **清晰的结构**:合理组织源文件和目录
2. **正确的依赖**:准确声明和检查依赖关系
3. **平台兼容**:处理不同工具链和平台的差异
4. **性能考虑**:避免不必要的文件扫描
5. **错误处理**:优雅处理各种异常情况
6. **文档完善**:添加必要的注释和说明
通过遵循本指南的建议和最佳实践,可以编写出易维护、可扩展的构建脚本,为RT-Thread项目的构建提供坚实的基础。
Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

@@ -0,0 +1,128 @@
# RT-Thread package.json 构建支持
## 概述
RT-Thread支持使用package.json来定义组件的构建配置,作为传统SConscript的简化替代方案。
## 现有支持
### package.json格式
```json
{
"name": "hello",
"description": "Hello World component for RT-Thread",
"type": "rt-thread-component",
"dependencies": ["RT_USING_HELLO"],
"defines": [],
"sources": [{
"name": "src",
"dependencies": [],
"includes": ["."],
"files": ["hello.c"]
}]
}
```
### 字段说明
- **name**: 组件名称(必需)
- **type**: 必须为"rt-thread-component"(必需)
- **description**: 组件描述
- **dependencies**: 全局依赖,数组形式的宏定义
- **defines**: 全局宏定义
- **sources**: 源文件组数组,每组可包含:
- **name**: 源组名称
- **dependencies**: 源组特定依赖
- **includes**: 头文件搜索路径
- **files**: 源文件列表(支持通配符)
## 使用方式
### 1. 在SConscript中使用
方式一:使用PackageSConscript(推荐)
```python
from building import *
objs = PackageSConscript('package.json')
Return('objs')
```
方式二:直接调用BuildPackage
```python
Import('env')
from package import BuildPackage
objs = BuildPackage('package.json')
Return('objs')
```
### 2. 目录结构示例
```
mycomponent/
├── SConscript
├── package.json
├── mycomponent.c
├── mycomponent.h
└── src/
└── helper.c
```
### 3. 完整示例
package.json:
```json
{
"name": "mycomponent",
"description": "My RT-Thread component",
"type": "rt-thread-component",
"dependencies": ["RT_USING_MYCOMPONENT"],
"defines": ["MY_VERSION=1"],
"sources": [
{
"name": "main",
"dependencies": [],
"includes": ["."],
"files": ["mycomponent.c"]
},
{
"name": "helper",
"dependencies": ["RT_USING_MYCOMPONENT_HELPER"],
"includes": ["src"],
"files": ["src/*.c"]
}
]
}
```
## 工作原理
1. **依赖检查**:首先检查全局dependencies,如果不满足则跳过整个组件
2. **源组处理**:遍历sources数组,每个源组独立检查dependencies
3. **路径处理**includes相对路径基于package.json所在目录
4. **文件匹配**:使用SCons的Glob函数处理文件通配符
5. **构建调用**:最终调用DefineGroup创建构建组
## 与DefineGroup的对比
| 特性 | package.json | DefineGroup |
|------|--------------|-------------|
| 配置方式 | JSON文件 | Python代码 |
| 依赖管理 | 结构化 | 函数参数 |
| 源文件组织 | 分组管理 | 单一列表 |
| 条件编译 | 源组级别 | 整体级别 |
| 灵活性 | 中等 | 高 |
| 易用性 | 高 | 中等 |
## 最佳实践
1. **简单组件优先使用package.json**:配置清晰,易于维护
2. **复杂逻辑使用SConscript**:需要动态逻辑时使用传统方式
3. **混合使用**:可以在同一项目中混用两种方式
## 注意事项
1. package.json必须是有效的JSON格式
2. type字段必须为"rt-thread-component"
3. 文件路径相对于package.json所在目录
4. 依赖不满足时会静默跳过,不会报错
5. 与RT-Thread构建系统完全集成,不支持独立构建
Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

@@ -0,0 +1,618 @@
# RT-Thread 构建系统使用指南
## 目录
1. [概述](#概述)
2. [快速开始](#快速开始)
3. [命令行选项详解](#命令行选项详解)
4. [工具链配置](#工具链配置)
5. [项目生成](#项目生成)
6. [软件包管理](#软件包管理)
7. [高级功能](#高级功能)
8. [常见问题](#常见问题)
## 概述
RT-Thread使用基于SCons的构建系统,提供了统一的跨平台构建体验。构建系统支持:
- 多种编译器和IDEGCC、Keil、IAR、VS Code等)
- 模块化的组件管理
- 灵活的配置系统
- 自动化的依赖处理
- 软件包管理功能
### 系统架构图
![arch](./guide_arch.drawio.png)
## 快速开始
### 基本编译流程
1. **进入BSP目录**
```bash
cd bsp/stm32/stm32f103-blue-pill
```
2. **配置系统**(可选)
```bash
menuconfig # 图形化配置
```
3. **编译项目**
```bash
scons # 默认编译
scons -j8 # 多线程编译
```
4. **生成IDE项目**
```bash
scons --target=mdk5 # 生成Keil MDK5项目
scons --target=iar # 生成IAR项目
scons --target=vsc # 生成VS Code项目
```
### 清理和重建
```bash
scons -c # 清理编译产物
scons -c --target=mdk5 # 清理MDK5项目文件
scons --dist # 生成分发包
```
## 命令行选项详解
### 基础编译选项
| 选项 | 说明 | 示例 |
|------|------|------|
| `-j N` | 多线程编译,N为线程数 | `scons -j8` |
| `-c` | 清理编译产物 | `scons -c` |
| `-s` | 静默模式,不显示命令 | `scons -s` |
| `--verbose` | 详细输出模式 | `scons --verbose` |
### 项目生成选项
| 选项 | 说明 | 生成的文件 |
|------|------|------------|
| `--target=mdk4` | Keil MDK4项目 | project.uvproj |
| `--target=mdk5` | Keil MDK5项目 | project.uvprojx |
| `--target=iar` | IAR工作区 | project.eww |
| `--target=vs2012` | Visual Studio项目 | project.vcxproj |
| `--target=vsc` | VS Code配置 | .vscode/目录 |
| `--target=eclipse` | Eclipse CDT项目 | .project, .cproject |
| `--target=cmake` | CMake项目 | CMakeLists.txt |
| `--target=makefile` | 通用Makefile | Makefile |
### 配置管理选项
| 选项 | 说明 | 使用场景 |
|------|------|----------|
| `--menuconfig` | 启动图形配置界面 | 修改功能配置 |
| `--pyconfig` | 通过Python脚本配置 | 自动化配置 |
| `--pyconfig-silent` | 静默Python配置 | CI/CD环境 |
| `--genconfig` | 从rtconfig.h生成.config | 配置迁移 |
| `--useconfig=xxx` | 使用指定配置文件 | 切换配置 |
### 工具链选项
| 选项 | 说明 | 示例 |
|------|------|------|
| `--exec-path=PATH` | 指定工具链路径 | `--exec-path=/opt/gcc-arm/bin` |
| `--exec-prefix=PREFIX` | 指定工具链前缀 | `--exec-prefix=arm-none-eabi-` |
| `--strict` | 严格编译模式 | 开启-Werror |
### 分发和调试选项
| 选项 | 说明 | 用途 |
|------|------|------|
| `--dist` | 生成分发包 | 项目发布 |
| `--dist-strip` | 生成精简分发包 | 最小化项目 |
| `--dist-ide` | 生成RT-Thread Studio项目 | Studio开发 |
| `--cscope` | 生成cscope数据库 | 代码导航 |
| `--clang-analyzer` | 运行Clang静态分析 | 代码质量检查 |
## 工具链配置
### rtconfig.py配置文件
每个BSP都有一个`rtconfig.py`文件,定义了工具链配置:
```python
import os
# 工具链定义
CROSS_TOOL = 'gcc' # 工具链类型: gcc/keil/iar
PLATFORM = 'armcc' # 平台标识
# 编译器路径
if os.getenv('RTT_EXEC_PATH'):
EXEC_PATH = os.getenv('RTT_EXEC_PATH')
else:
EXEC_PATH = r'C:/Keil_v5/ARM/ARMCC/bin'
# 编译器前缀(GCC工具链)
PREFIX = 'arm-none-eabi-'
# 编译器定义
CC = PREFIX + 'gcc'
CXX = PREFIX + 'g++'
AS = PREFIX + 'gcc'
AR = PREFIX + 'ar'
LINK = PREFIX + 'gcc'
SIZE = PREFIX + 'size'
OBJDUMP = PREFIX + 'objdump'
OBJCPY = PREFIX + 'objcopy'
# 设备相关参数
DEVICE = ' -mcpu=cortex-m3 -mthumb -ffunction-sections -fdata-sections'
# 编译标志
CFLAGS = DEVICE + ' -Dgcc'
AFLAGS = ' -c' + DEVICE + ' -x assembler-with-cpp -Wa,-mimplicit-it=thumb '
LFLAGS = DEVICE + ' -Wl,--gc-sections,-Map=rtthread.map,-cref,-u,Reset_Handler -T link.lds'
# 路径定义
CPATH = ''
LPATH = ''
# 链接脚本
LINK_SCRIPT = 'link.lds'
# 后处理命令
POST_ACTION = OBJCPY + ' -O binary $TARGET rtthread.bin\n' + SIZE + ' $TARGET \n'
```
### 支持的工具链
1. **GCC工具链**
```python
CROSS_TOOL = 'gcc'
PREFIX = 'arm-none-eabi-'
```
2. **Keil MDK**
```python
CROSS_TOOL = 'keil'
PLATFORM = 'armcc' # ARM Compiler 5
# 或
PLATFORM = 'armclang' # ARM Compiler 6
```
3. **IAR**
```python
CROSS_TOOL = 'iar'
PLATFORM = 'iccarm'
```
4. **RISC-V GCC**
```python
CROSS_TOOL = 'gcc'
PREFIX = 'riscv64-unknown-elf-'
```
### 环境变量支持
构建系统支持通过环境变量覆盖配置:
```bash
# 设置工具链路径
export RTT_EXEC_PATH=/opt/gcc-arm-none-eabi-10-2020-q4-major/bin
# 设置工具链前缀
export RTT_CC_PREFIX=arm-none-eabi-
# 设置工具链类型
export RTT_CC=gcc
```
## 项目生成
### VS Code项目配置
使用`scons --target=vsc`生成VS Code项目,会创建以下配置文件:
**.vscode/c_cpp_properties.json** - IntelliSense配置
```json
{
"configurations": [
{
"name": "RT-Thread",
"includePath": [
"${workspaceFolder}/**",
"${workspaceFolder}/../../components/finsh",
"${workspaceFolder}/../../include"
],
"defines": [
"RT_USING_FINSH",
"RT_USING_SERIAL",
"__GNUC__"
],
"compilerPath": "/opt/gcc-arm/bin/arm-none-eabi-gcc",
"cStandard": "c99",
"cppStandard": "c++11"
}
]
}
```
**.vscode/tasks.json** - 构建任务配置
```json
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"type": "shell",
"command": "scons",
"problemMatcher": "$gcc",
"group": {
"kind": "build",
"isDefault": true
}
}
]
}
```
### CMake项目生成
使用`scons --target=cmake`生成CMakeLists.txt
```cmake
cmake_minimum_required(VERSION 3.10)
# 工具链设置
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR cortex-m3)
set(CMAKE_C_COMPILER arm-none-eabi-gcc)
set(CMAKE_ASM_COMPILER arm-none-eabi-gcc)
project(rtthread C ASM)
# 编译选项
add_compile_options(
-mcpu=cortex-m3
-mthumb
-ffunction-sections
-fdata-sections
-Wall
-O0
-g
)
# 头文件路径
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/../../include
)
# 源文件
set(SOURCES
applications/main.c
../../src/clock.c
../../src/components.c
)
# 生成可执行文件
add_executable(${PROJECT_NAME}.elf ${SOURCES})
# 链接选项
target_link_options(${PROJECT_NAME}.elf PRIVATE
-T${CMAKE_CURRENT_SOURCE_DIR}/link.lds
-Wl,-Map=${PROJECT_NAME}.map,--cref
-Wl,--gc-sections
)
```
## 软件包管理
### 使用package.json定义组件
RT-Thread支持使用`package.json`文件定义软件包:
```json
{
"name": "my-driver",
"version": "1.0.0",
"type": "rt-thread-component",
"license": "Apache-2.0",
"dependencies": {
"RT_USING_DEVICE": "latest"
},
"sources": {
"common": {
"source_files": ["src/*.c"],
"header_files": ["inc/*.h"],
"header_path": ["inc"]
},
"cortex-m": {
"condition": "defined(ARCH_ARM_CORTEX_M)",
"source_files": ["port/cortex-m/*.c"]
}
}
}
```
### 在SConscript中使用BuildPackage
```python
from building import *
import os
# 使用package.json构建
objs = BuildPackage('package.json')
# 或者手动指定包路径
pkg_path = os.path.join(GetCurrentDir(), 'package.json')
objs = BuildPackage(pkg_path)
Return('objs')
```
## 高级功能
### 1. 条件编译和依赖管理
**基于宏定义的条件编译**
```python
src = ['common.c']
if GetDepend('RT_USING_SERIAL'):
src += ['serial.c']
if GetDepend(['RT_USING_SPI', 'RT_USING_SFUD']):
src += ['spi_flash.c']
group = DefineGroup('Drivers', src, depend = ['RT_USING_DEVICE'])
```
**复杂依赖表达式**
```python
# 依赖可以是列表(AND关系)
depend = ['RT_USING_LWIP', 'RT_USING_NETDEV']
# 或者使用GetDepend进行复杂判断
if GetDepend('RT_USING_LWIP') and not GetDepend('RT_USING_SAL'):
print('配置错误:LWIP需要SAL支持')
```
### 2. 本地编译选项
为特定模块设置独立的编译选项:
```python
# 全局编译选项
CPPPATH = [GetCurrentDir()]
CPPDEFINES = ['MODULE_VERSION=1']
# 本地编译选项(仅对当前group有效)
LOCAL_CFLAGS = '-O3 -funroll-loops'
LOCAL_CPPPATH = ['./private']
LOCAL_CPPDEFINES = {'BUFFER_SIZE': 1024}
group = DefineGroup('Module', src, depend = [''],
CPPPATH = CPPPATH,
CPPDEFINES = CPPDEFINES,
LOCAL_CFLAGS = LOCAL_CFLAGS,
LOCAL_CPPPATH = LOCAL_CPPPATH,
LOCAL_CPPDEFINES = LOCAL_CPPDEFINES
)
```
### 3. 递归构建子目录
自动扫描并构建子目录:
```python
import os
from building import *
objs = []
cwd = GetCurrentDir()
dirs = os.listdir(cwd)
# 黑名单目录
skip_dirs = ['test', 'doc', 'example']
for d in dirs:
if d in skip_dirs:
continue
path = os.path.join(cwd, d)
if os.path.isdir(path):
sconscript = os.path.join(path, 'SConscript')
if os.path.isfile(sconscript):
objs += SConscript(sconscript)
Return('objs')
```
### 4. 自定义构建动作
添加构建前后的自定义动作:
```python
from building import *
def pre_build_action(target, source, env):
print('开始构建:', target[0])
# 执行预处理操作
def post_build_action(target, source, env):
print('构建完成:', target[0])
# 生成额外文件,如hex文件
import subprocess
subprocess.call(['arm-none-eabi-objcopy', '-O', 'ihex',
str(target[0]), str(target[0]) + '.hex'])
# 注册构建动作
if GetOption('target') == None:
rtconfig.POST_ACTION = post_build_action
```
### 5. 分发包定制
创建自定义分发包:
```python
# 在BSP的SConstruct中添加
def dist_handle(BSP_ROOT, dist_dir):
import shutil
# 复制必要文件
src_files = ['applications', 'board', 'rtconfig.py', 'SConstruct']
for src in src_files:
src_path = os.path.join(BSP_ROOT, src)
dst_path = os.path.join(dist_dir, src)
if os.path.isdir(src_path):
shutil.copytree(src_path, dst_path)
else:
shutil.copy2(src_path, dst_path)
# 创建README
with open(os.path.join(dist_dir, 'README.md'), 'w') as f:
f.write('# RT-Thread BSP 分发包\n')
f.write('构建时间: ' + time.strftime('%Y-%m-%d %H:%M:%S\n'))
# 注册分发处理函数
AddOption('--dist-handle',
dest = 'dist-handle',
action = 'store_true',
default = False,
help = 'Enable dist handle')
if GetOption('dist-handle'):
dist_handle(BSP_ROOT, dist_dir)
```
### 6. 代码分析集成
**Clang静态分析**
```bash
scons --clang-analyzer
```
**生成compile_commands.json**
```bash
scons --target=cmake # CMake项目会包含compile_commands.json
# 或使用
scons --compile-commands
```
**生成Cscope数据库**
```bash
scons --cscope
```
## 常见问题
### Q1: 如何添加新的源文件?
在相应目录的SConscript中添加:
```python
src = Glob('*.c') # 自动包含所有.c文件
# 或
src = ['file1.c', 'file2.c'] # 手动指定
```
### Q2: 如何排除特定文件?
```python
src = Glob('*.c')
SrcRemove(src, ['test.c', 'debug.c'])
```
### Q3: 如何处理不同配置下的源文件?
```python
src = ['common.c']
if rtconfig.PLATFORM == 'gcc':
src += ['gcc_specific.c']
elif rtconfig.PLATFORM == 'armcc':
src += ['keil_specific.c']
```
### Q4: 如何调试构建问题?
1. 使用详细输出模式:
```bash
scons --verbose
```
2. 查看预处理结果:
```bash
scons --target=mdk5 --verbose # 查看生成的项目配置
```
3. 检查依赖关系:
```python
# 在SConscript中添加调试输出
print('GetDepend result:', GetDepend('RT_USING_XXX'))
```
### Q5: 如何加快编译速度?
1. 使用多线程编译:
```bash
scons -j$(nproc) # Linux/macOS
scons -j8 # Windows
```
2. 使用ccacheGCC):
```python
# 在rtconfig.py中
CC = 'ccache ' + PREFIX + 'gcc'
```
3. 优化依赖关系,避免不必要的重编译
### Q6: 如何处理第三方库?
1. **作为源码包含**
```python
# libraries/foo/SConscript
src = Glob('src/*.c')
CPPPATH = [GetCurrentDir() + '/include']
group = DefineGroup('foo', src, depend = ['RT_USING_FOO'],
CPPPATH = CPPPATH)
```
2. **作为预编译库**
```python
# 添加库文件
LIBS = ['foo']
LIBPATH = [GetCurrentDir() + '/lib']
group = DefineGroup('foo', [], depend = ['RT_USING_FOO'],
LIBS = LIBS, LIBPATH = LIBPATH)
```
### Q7: 如何自定义链接脚本?
在rtconfig.py中指定:
```python
# GCC工具链
LINK_SCRIPT = 'board/link.lds'
# Keil MDK
LINK_SCRIPT = 'board/link.sct'
# IAR
LINK_SCRIPT = 'board/link.icf'
```
## 最佳实践
1. **模块化设计**:每个功能模块使用独立的SConscript
2. **依赖管理**:正确设置depend参数,避免编译不需要的代码
3. **路径处理**:使用GetCurrentDir()获取当前路径,避免硬编码
4. **条件编译**:合理使用GetDepend进行条件判断
5. **编译选项**:全局选项放在rtconfig.py,局部选项使用LOCAL_xxx
6. **文档维护**:在SConscript中添加必要的注释说明
## 总结
RT-Thread的构建系统提供了强大而灵活的项目管理能力。通过合理使用各种构建选项和功能,可以高效地进行嵌入式软件开发。建议开发者深入理解构建系统的工作原理,以便更好地利用其功能。
@@ -0,0 +1,841 @@
# RT-Thread 构建系统技术原理
## 目录
1. [系统架构设计](#系统架构设计)
2. [核心模块分析](#核心模块分析)
3. [构建流程详解](#构建流程详解)
4. [依赖管理机制](#依赖管理机制)
5. [工具链适配层](#工具链适配层)
6. [项目生成器架构](#项目生成器架构)
7. [配置系统实现](#配置系统实现)
8. [扩展机制](#扩展机制)
## 系统架构设计
### 整体架构图
![arch](./tech_arch.drawio.png)
### 设计原则
1. **模块化设计**:每个功能模块独立,通过明确的接口交互
2. **可扩展性**:易于添加新的工具链支持和目标生成器
3. **跨平台兼容**:统一的抽象层处理平台差异
4. **配置驱动**:通过配置文件控制构建行为
## 核心模块分析
### 1. building.py - 构建引擎核心
#### 1.1 全局变量管理
```python
BuildOptions = {} # 存储从rtconfig.h解析的宏定义
Projects = [] # 存储所有的组件对象
Rtt_Root = '' # RT-Thread根目录
Env = None # SCons环境对象
```
#### 1.2 PrepareBuilding 函数实现
```python
def PrepareBuilding(env, root_directory, has_libcpu=False, remove_components = []):
"""
准备构建环境
参数:
env: SCons环境对象
root_directory: RT-Thread根目录
has_libcpu: 是否包含libcpu
remove_components: 需要移除的组件列表
"""
# 1. 添加命令行选项
AddOptions()
# 2. 设置全局环境变量
global Env, Rtt_Root
Env = env
Rtt_Root = os.path.abspath(root_directory)
# 3. 配置日志系统
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger('rt-scons')
Env['log'] = logger
# 4. 工具链检测和配置
if not utils.CmdExists(os.path.join(rtconfig.EXEC_PATH, rtconfig.CC)):
# 尝试自动检测工具链
try:
envm = utils.ImportModule('env_utility')
exec_path = envm.GetSDKPath(rtconfig.CC)
if exec_path:
rtconfig.EXEC_PATH = exec_path
except:
pass
# 5. 解析rtconfig.h配置
PreProcessor = create_preprocessor_instance()
with open('rtconfig.h', 'r') as f:
PreProcessor.process_contents(f.read())
BuildOptions = PreProcessor.cpp_namespace
# 6. 处理目标平台
if GetOption('target'):
# 根据目标设置工具链
rtconfig.CROSS_TOOL, rtconfig.PLATFORM = tgt_dict[tgt_name]
return objs
```
#### 1.3 DefineGroup 函数实现
```python
def DefineGroup(name, src, depend, **parameters):
"""
定义一个组件组
参数:
name: 组名称
src: 源文件列表
depend: 依赖条件
**parameters: 编译参数(CPPPATH, CPPDEFINES, LIBS等)
返回:
组对象列表
"""
# 1. 检查依赖条件
if not GetDepend(depend):
return []
# 2. 处理源文件
if isinstance(src, list):
# 过滤掉被移除的文件
src = [s for s in src if s not in removed_src]
# 3. 创建组对象
group = {}
group['name'] = name
group['src'] = src
# 4. 处理编译参数
# 全局参数
if 'CPPPATH' in parameters:
group['CPPPATH'] = parameters['CPPPATH']
# 本地参数(仅对当前组有效)
if 'LOCAL_CPPPATH' in parameters:
paths = parameters['LOCAL_CPPPATH']
group['LOCAL_CPPPATH'] = [os.path.abspath(p) for p in paths]
# 5. 注册到全局项目列表
Projects.append(group)
# 6. 返回SCons对象
if src:
objs = Env.Object(src)
else:
objs = []
return objs
```
### 2. 依赖管理机制
#### 2.1 GetDepend 实现
```python
def GetDepend(depend):
"""
检查依赖条件是否满足
参数:
depend: 字符串或字符串列表
返回:
True: 依赖满足
False: 依赖不满足
"""
# 1. 处理空依赖
if not depend:
return True
# 2. 处理字符串依赖
if isinstance(depend, str):
return _CheckSingleDepend(depend)
# 3. 处理列表依赖(AND关系)
if isinstance(depend, list):
for d in depend:
if not _CheckSingleDepend(d):
return False
return True
return False
def _CheckSingleDepend(depend):
"""检查单个依赖"""
# 1. 检查是否在BuildOptions中定义
if depend in BuildOptions:
# 2. 检查值是否为真
return BuildOptions[depend] != '0'
return False
```
#### 2.2 依赖表达式支持
```python
# 支持的依赖表达式
depend = 'RT_USING_SERIAL' # 单个依赖
depend = ['RT_USING_LWIP', 'SAL'] # AND关系
depend = '' # 无条件包含
# 高级用法 - 在SConscript中
if GetDepend('RT_USING_LWIP'):
if GetDepend('RT_USING_LWIP_TCP'):
src += ['tcp.c']
if GetDepend('RT_USING_LWIP_UDP'):
src += ['udp.c']
```
### 3. 配置解析系统
#### 3.1 预处理器实现
```python
class PreProcessor:
"""
C预处理器实现,用于解析rtconfig.h
"""
def __init__(self):
self.cpp_namespace = {}
self.defines = {}
def process_contents(self, contents):
"""处理文件内容"""
lines = contents.split('\n')
for line in lines:
# 处理 #define 指令
if line.startswith('#define'):
self._process_define(line)
# 处理 #ifdef 等条件编译
elif line.startswith('#ifdef'):
self._process_ifdef(line)
def _process_define(self, line):
"""处理宏定义"""
# #define RT_NAME_MAX 8
parts = line.split(None, 2)
if len(parts) >= 2:
name = parts[1]
value = parts[2] if len(parts) > 2 else '1'
self.cpp_namespace[name] = value
```
#### 3.2 配置文件格式
**rtconfig.h 示例**
```c
/* RT-Thread 配置文件 */
#ifndef RT_CONFIG_H__
#define RT_CONFIG_H__
/* 内核配置 */
#define RT_THREAD_PRIORITY_32
#define RT_THREAD_PRIORITY_MAX 32
#define RT_TICK_PER_SECOND 100
#define RT_USING_TIMER_SOFT
/* 组件配置 */
#define RT_USING_DEVICE
#define RT_USING_SERIAL
#define RT_SERIAL_RB_BUFSZ 64
/* 条件配置 */
#ifdef RT_USING_SERIAL
#define RT_SERIAL_USING_DMA
#endif
#endif /* RT_CONFIG_H__ */
```
### 4. 工具链适配层
#### 4.1 工具链抽象接口
```python
class ToolchainBase:
"""工具链基类"""
def __init__(self):
self.name = ''
self.prefix = ''
self.suffix = ''
def get_cc(self):
"""获取C编译器"""
raise NotImplementedError
def get_cflags(self):
"""获取C编译选项"""
raise NotImplementedError
def get_linkflags(self):
"""获取链接选项"""
raise NotImplementedError
```
#### 4.2 GCC工具链实现
```python
class GccToolchain(ToolchainBase):
def __init__(self, prefix=''):
self.name = 'gcc'
self.prefix = prefix
self.suffix = ''
def get_cc(self):
return self.prefix + 'gcc'
def get_cflags(self):
flags = []
# 基础选项
flags += ['-Wall', '-g']
# 优化选项
if GetOption('optimization') == 'size':
flags += ['-Os']
else:
flags += ['-O0']
# 架构选项
flags += ['-mcpu=cortex-m3', '-mthumb']
return ' '.join(flags)
```
#### 4.3 Keil MDK适配
```python
class KeilToolchain(ToolchainBase):
def __init__(self):
self.name = 'keil'
def setup_environment(self, env):
"""设置Keil特定的环境变量"""
# 修改文件扩展名
env['OBJSUFFIX'] = '.o'
env['LIBPREFIX'] = ''
env['LIBSUFFIX'] = '.lib'
# 设置编译命令
env['CC'] = 'armcc'
env['AS'] = 'armasm'
env['AR'] = 'armar'
env['LINK'] = 'armlink'
# 设置命令格式
env['ARCOM'] = '$AR --create $TARGET $SOURCES'
```
### 5. 项目生成器架构
#### 5.1 生成器基类
```python
class ProjectGenerator:
"""项目生成器基类"""
def __init__(self, env, project):
self.env = env
self.project = project
self.template_dir = ''
def generate(self):
"""生成项目文件"""
self._prepare()
self._generate_project_file()
self._generate_workspace_file()
self._copy_template_files()
self._post_process()
def _collect_source_files(self):
"""收集源文件"""
sources = []
for group in self.project:
sources.extend(group['src'])
return sources
def _collect_include_paths(self):
"""收集头文件路径"""
paths = []
for group in self.project:
if 'CPPPATH' in group:
paths.extend(group['CPPPATH'])
return list(set(paths)) # 去重
```
#### 5.2 VS Code生成器实现
```python
class VSCodeGenerator(ProjectGenerator):
"""VS Code项目生成器"""
def _generate_project_file(self):
"""生成VS Code配置文件"""
# 创建.vscode目录
vscode_dir = os.path.join(self.env['BSP_ROOT'], '.vscode')
if not os.path.exists(vscode_dir):
os.makedirs(vscode_dir)
# 生成c_cpp_properties.json
self._generate_cpp_properties()
# 生成tasks.json
self._generate_tasks()
# 生成launch.json
self._generate_launch()
def _generate_cpp_properties(self):
"""生成IntelliSense配置"""
config = {
"configurations": [{
"name": "RT-Thread",
"includePath": self._collect_include_paths(),
"defines": self._collect_defines(),
"compilerPath": self._get_compiler_path(),
"cStandard": "c99",
"cppStandard": "c++11",
"intelliSenseMode": "gcc-arm"
}],
"version": 4
}
# 写入文件
file_path = os.path.join('.vscode', 'c_cpp_properties.json')
with open(file_path, 'w') as f:
json.dump(config, f, indent=4)
```
#### 5.3 Keil MDK5生成器
```python
class MDK5Generator(ProjectGenerator):
"""Keil MDK5项目生成器"""
def _generate_project_file(self):
"""生成uvprojx文件"""
# 加载XML模板
tree = etree.parse(self.template_file)
root = tree.getroot()
# 更新目标配置
self._update_target_options(root)
# 添加文件组
groups_node = root.find('.//Groups')
for group in self.project:
self._add_file_group(groups_node, group)
# 保存项目文件
tree.write('project.uvprojx', encoding='utf-8',
xml_declaration=True)
def _add_file_group(self, parent, group):
"""添加文件组"""
group_elem = etree.SubElement(parent, 'Group')
# 组名
name_elem = etree.SubElement(group_elem, 'GroupName')
name_elem.text = group['name']
# 文件列表
files_elem = etree.SubElement(group_elem, 'Files')
for src in group['src']:
self._add_file(files_elem, src)
```
### 6. 编译数据库生成
#### 6.1 compile_commands.json生成
```python
def generate_compile_commands(env, project):
"""
生成compile_commands.json用于代码分析工具
"""
commands = []
for group in project:
for src in group['src']:
if src.endswith('.c') or src.endswith('.cpp'):
cmd = {
"directory": env['BSP_ROOT'],
"file": os.path.abspath(src),
"command": _generate_compile_command(env, src, group)
}
commands.append(cmd)
# 写入文件
with open('compile_commands.json', 'w') as f:
json.dump(commands, f, indent=2)
def _generate_compile_command(env, src, group):
"""生成单个文件的编译命令"""
cmd = []
# 编译器
cmd.append(env['CC'])
# 编译选项
cmd.extend(env['CFLAGS'].split())
# 头文件路径
for path in group.get('CPPPATH', []):
cmd.append('-I' + path)
# 宏定义
for define in group.get('CPPDEFINES', []):
if isinstance(define, tuple):
cmd.append('-D{}={}'.format(define[0], define[1]))
else:
cmd.append('-D' + define)
# 源文件
cmd.append(src)
return ' '.join(cmd)
```
### 7. 分发系统实现
#### 7.1 分发包生成流程
```python
def MkDist(program, BSP_ROOT, RTT_ROOT, Env, project):
"""生成分发包"""
# 1. 创建分发目录
dist_name = os.path.basename(BSP_ROOT)
dist_dir = os.path.join(BSP_ROOT, 'dist', dist_name)
# 2. 复制RT-Thread内核
print('=> copy RT-Thread kernel')
copytree(os.path.join(RTT_ROOT, 'src'),
os.path.join(dist_dir, 'rt-thread', 'src'))
copytree(os.path.join(RTT_ROOT, 'include'),
os.path.join(dist_dir, 'rt-thread', 'include'))
# 3. 复制使用的组件
print('=> copy components')
for group in project:
_copy_group_files(group, dist_dir)
# 4. 生成Kconfig文件
_generate_kconfig(dist_dir, project)
# 5. 打包
make_zip(dist_dir, dist_name + '.zip')
```
#### 7.2 精简分发包生成
```python
def MkDist_Strip(program, BSP_ROOT, RTT_ROOT, Env):
"""
基于compile_commands.json生成精简分发包
只包含实际使用的文件
"""
# 1. 解析compile_commands.json
with open('compile_commands.json', 'r') as f:
commands = json.load(f)
# 2. 提取使用的文件
used_files = set()
for cmd in commands:
# 源文件
used_files.add(cmd['file'])
# 解析包含的头文件
includes = _parse_includes(cmd['file'], cmd['command'])
used_files.update(includes)
# 3. 复制文件
for file in used_files:
_copy_with_structure(file, dist_dir)
```
## 构建流程详解
### 完整构建流程图
![process](./process.drawio.png)
### 依赖解析流程
```python
def dependency_resolution_flow():
"""
依赖解析流程示例
"""
# 1. 从rtconfig.h读取所有宏定义
macros = parse_rtconfig_h()
# 例: {'RT_USING_SERIAL': '1', 'RT_USING_PIN': '1'}
# 2. 处理单个组件
for component in components:
# 3. 检查依赖条件
if check_dependencies(component.depends, macros):
# 4. 包含组件
include_component(component)
else:
# 5. 跳过组件
skip_component(component)
# 6. 递归处理子依赖
resolve_sub_dependencies()
```
## 扩展机制
### 1. 添加新的工具链支持
```python
# 1. 在tgt_dict中添加映射
tgt_dict['mycc'] = ('mycc', 'mycc')
# 2. 创建tools/mycc.py
import os
from building import *
def generate_project(env, project):
"""生成项目文件"""
print("Generating MyCC project...")
# 收集信息
info = ProjectInfo(env, project)
# 生成项目文件
# ...
# 3. 在rtconfig.py中配置
CROSS_TOOL = 'mycc'
PLATFORM = 'mycc'
```
### 2. 添加自定义构建步骤
```python
# 在SConstruct或SConscript中
def custom_builder(target, source, env):
"""自定义构建器"""
# 执行自定义操作
cmd = 'custom_tool -o {} {}'.format(target[0], source[0])
os.system(cmd)
# 注册构建器
env['BUILDERS']['CustomBuild'] = Builder(action=custom_builder,
suffix='.out',
src_suffix='.in')
# 使用构建器
custom_out = env.CustomBuild('output.out', 'input.in')
```
### 3. 扩展配置解析器
```python
class ExtendedPreProcessor(PreProcessor):
"""扩展的预处理器"""
def __init__(self):
super().__init__()
self.custom_handlers = {}
def register_handler(self, directive, handler):
"""注册自定义指令处理器"""
self.custom_handlers[directive] = handler
def process_line(self, line):
"""处理单行"""
# 检查自定义指令
for directive, handler in self.custom_handlers.items():
if line.startswith(directive):
return handler(line)
# 默认处理
return super().process_line(line)
```
### 4. 插件系统实现
```python
class BuildPlugin:
"""构建插件基类"""
def __init__(self, name):
self.name = name
def pre_build(self, env, project):
"""构建前钩子"""
pass
def post_build(self, env, project):
"""构建后钩子"""
pass
def configure(self, env):
"""配置环境"""
pass
# 插件管理器
class PluginManager:
def __init__(self):
self.plugins = []
def register(self, plugin):
self.plugins.append(plugin)
def run_pre_build(self, env, project):
for plugin in self.plugins:
plugin.pre_build(env, project)
```
## 性能优化
### 1. 构建缓存机制
```python
class BuildCache:
"""构建缓存"""
def __init__(self, cache_dir='.scache'):
self.cache_dir = cache_dir
self.cache_db = os.path.join(cache_dir, 'cache.db')
def get_hash(self, file):
"""计算文件哈希"""
import hashlib
with open(file, 'rb') as f:
return hashlib.md5(f.read()).hexdigest()
def is_cached(self, source, target):
"""检查是否已缓存"""
# 检查目标文件是否存在
if not os.path.exists(target):
return False
# 检查源文件是否更新
source_hash = self.get_hash(source)
cached_hash = self.load_hash(source)
return source_hash == cached_hash
```
### 2. 并行构建优化
```python
def optimize_parallel_build(env, project):
"""优化并行构建"""
# 1. 分析依赖关系
dep_graph = analyze_dependencies(project)
# 2. 计算最优构建顺序
build_order = topological_sort(dep_graph)
# 3. 分组独立任务
parallel_groups = []
for level in build_order:
# 同一层级可以并行
parallel_groups.append(level)
# 4. 设置并行度
import multiprocessing
num_jobs = multiprocessing.cpu_count()
env.SetOption('num_jobs', num_jobs)
return parallel_groups
```
## 调试技巧
### 1. 构建日志分析
```python
# 启用详细日志
def enable_build_logging():
# 设置SCons日志
env.SetOption('debug', 'explain')
# 自定义日志
class BuildLogger:
def __init__(self, logfile):
self.logfile = logfile
def __call__(self, msg, *args):
with open(self.logfile, 'a') as f:
f.write(msg % args + '\n')
logger = BuildLogger('build.log')
env['PRINT_CMD_LINE_FUNC'] = logger
```
### 2. 依赖关系可视化
```python
def visualize_dependencies(project):
"""生成依赖关系图"""
import graphviz
dot = graphviz.Digraph(comment='Dependencies')
# 添加节点
for group in project:
dot.node(group['name'])
# 添加边
for group in project:
for dep in group.get('depends', []):
if find_group(dep):
dot.edge(dep, group['name'])
# 渲染
dot.render('dependencies', format='png')
```
## 最佳实践
### 1. 模块化设计原则
- 每个功能模块独立的SConscript
- 明确的依赖关系声明
- 避免循环依赖
- 使用统一的命名规范
### 2. 性能优化建议
- 使用Glob谨慎,大目录下性能差
- 合理设置并行编译数
- 使用增量编译
- 避免重复的文件扫描
### 3. 可维护性建议
- 添加充分的注释
- 使用有意义的变量名
- 遵循Python PEP8规范
- 定期清理无用代码
### 4. 跨平台兼容性
- 使用os.path处理路径
- 避免平台特定的命令
- 测试多平台构建
- 处理路径分隔符差异
## 总结
RT-Thread的构建系统是一个精心设计的模块化系统,通过清晰的架构和灵活的扩展机制,为嵌入式开发提供了强大的构建能力。理解其内部原理有助于:
1. 更好地使用和优化构建流程
2. 快速定位和解决构建问题
3. 扩展支持新的工具链和平台
4. 为项目定制构建流程
构建系统的核心价值在于将复杂的嵌入式构建过程标准化和自动化,让开发者能够专注于功能开发而不是构建配置。
+52
View File
@@ -0,0 +1,52 @@
#
# Copyright (c) 2006-2023, RT-Thread Development Team
#
# SPDX-License-Identifier: Apache-2.0
#
# Change Logs:
# Date Author Notes
# 2023-05-10 GuEe-GUI the first version
#
import os, re
from building import *
__dtc_install_tip = """
You should install dtc (devicetree compiler) in your system:
Linux:
Debian/Ubuntu: apt-get install device-tree-compiler
Arch/Manjaro: pacman -Sy dtc
MacOS:
brew install dtc
Windows (MinGW):
msys2: pacman -S dtc
"""
def __check_dtc(value):
if value != 0 and os.system("dtc -v") != 0:
print(__dtc_install_tip)
def dts_to_dtb(RTT_ROOT, dts_list, options = "", include_paths = [], ignore_warning = []):
path = GetCurrentDir() + '/'
warning_ops = ""
for warning in ignore_warning:
warning_ops += " -W no-" + warning
for dts in dts_list:
dtb = dts.replace('.dts', '.dtb')
if not os.path.exists(path + dtb) or os.path.getmtime(path + dtb) < os.path.getmtime(path + dts):
tmp_dts = dts + '.tmp'
Preprocessing(dts, None, output = tmp_dts, CPPPATH=[RTT_ROOT + '/components/drivers/include'] + include_paths)
ret = os.system("dtc -I dts -O dtb -@ -A {} {} {} -o {}".format(warning_ops, options, path + tmp_dts, path + dtb))
__check_dtc(ret)
if os.path.exists(path + tmp_dts):
os.remove(path + tmp_dts)
def dtb_to_dts(RTT_ROOT, dtb_name, dts_name = None, options = ""):
path = GetCurrentDir() + '/'
if dts_name == None:
dts_name = re.sub(r'\.dtb[o]*$', '.dts', dtb_name)
ret = os.system("dtc -I dtb -O dts {} {} -o {}".format(options, path + dtb_name, path + dts_name))
__check_dtc(ret)
+488
View File
@@ -0,0 +1,488 @@
#! /usr/bin/env python
# coding=utf-8
#
# Copyright (c) 2024, RT-Thread Development Team
#
# SPDX-License-Identifier: GPL-2.0
#
# Change Logs:
# Date Author Notes
# 2024-04-20 Bernard the first version
import os
import json
import platform
import re
import sys
import shutil
import hashlib
import operator
def GetEnvPath():
if "ENV_ROOT" in os.environ:
return os.environ["ENV_ROOT"]
if platform.system() == 'Windows':
env_root = os.path.join(os.environ['USERPROFILE'], '.env')
else:
env_root = os.path.join(os.environ['HOME'], '.env')
return env_root
def GetPkgPath():
if "PKGS_DIR" in os.environ:
return os.environ["PKGS_DIR"]
elif "PKGS_ROOT" in os.environ:
return os.environ["PKGS_ROOT"]
elif GetEnvPath():
return os.path.join(GetEnvPath(), "packages")
else:
return None
def GetSDKPackagePath():
env = GetEnvPath()
if env:
return os.path.join(env, "tools", "scripts", "packages")
return None
# get SDK path based on name
# for example, GetSDKPath('arm-none-eabi') = '.env/tools/scripts/packages/arm-none-eabi-gcc-v10.3'
def GetSDKPath(name):
sdk_pkgs = GetSDKPackagePath()
if sdk_pkgs:
# read env/tools/scripts/sdk_cfg.json for curstomized SDK path
if os.path.exists(os.path.join(sdk_pkgs, '..', 'sdk_cfg.json')):
with open(os.path.join(sdk_pkgs, '..', 'sdk_cfg.json'), 'r', encoding='utf-8') as f:
sdk_cfg = json.load(f)
for item in sdk_cfg:
if item['name'] == name:
sdk = os.path.join(sdk_pkgs, item['path'])
return sdk
# read packages.json under env/tools/scripts/packages
with open(os.path.join(sdk_pkgs, 'pkgs.json'), 'r', encoding='utf-8') as f:
# packages_json = f.read()
packages = json.load(f)
for item in packages:
package_path = os.path.join(GetEnvPath(), 'packages', item['path'], 'package.json')
# read package['path']/package.json under env/packages
with open(package_path, 'r', encoding='utf-8') as f:
# package_json = f.read()
package = json.load(f)
if package['name'] == name:
sdk = os.path.join(sdk_pkgs, package['name'] + '-' + item['ver'])
return sdk
# not found named package
return None
def help_info():
print(
"**********************************************************************************\n"
"* Help infomation:\n"
"* Git tool install step.\n"
"* If your system is linux, you can use command below to install git.\n"
"* $ sudo yum install git\n"
"* $ sudo apt-get install git\n"
"* If your system is windows, you should download git software(msysGit).\n"
"* Download path: http://git-scm.com/download/win\n"
"* After you install it, be sure to add the git command execution PATH \n"
"* to your system PATH.\n"
"* Usually, git command PATH is $YOUR_INSTALL_DIR\\Git\\bin\n"
"* If your system is OSX, please download git and install it.\n"
"* Download path: http://git-scm.com/download/mac\n"
"**********************************************************************************\n"
)
def touch_env(use_gitee=False):
if sys.platform != 'win32':
home_dir = os.environ['HOME']
else:
home_dir = os.environ['USERPROFILE']
if use_gitee:
# "Install RT-Thread Environment from Gitee"
sdk_url = 'https://gitee.com/RT-Thread-Mirror/sdk.git'
pkg_url = 'https://gitee.com/RT-Thread-Mirror/packages.git'
env_url = 'https://gitee.com/RT-Thread-Mirror/env.git'
else:
pkg_url = 'https://github.com/RT-Thread/packages.git'
sdk_url = 'https://github.com/RT-Thread/sdk.git'
env_url = 'https://github.com/RT-Thread/env.git'
pkg_url = os.getenv('RTT_PACKAGE_URL') or pkg_url
sdk_url = os.getenv('RTT_SDK_URL') or sdk_url
env_url = os.getenv('RTT_ENV_URL') or env_url
# make .env and other directories
env_dir = os.path.join(home_dir, '.env')
if not os.path.exists(env_dir):
os.mkdir(env_dir)
os.mkdir(os.path.join(env_dir, 'local_pkgs'))
os.mkdir(os.path.join(env_dir, 'packages'))
os.mkdir(os.path.join(env_dir, 'tools'))
kconfig = open(os.path.join(env_dir, 'packages', 'Kconfig'), 'w')
kconfig.close()
# git clone packages
if not os.path.exists(os.path.join(env_dir, 'packages', 'packages')):
try:
ret = os.system('git clone %s %s' % (pkg_url, os.path.join(env_dir, 'packages', 'packages')))
if ret != 0:
shutil.rmtree(os.path.join(env_dir, 'packages', 'packages'))
print(
"********************************************************************************\n"
"* Warnning:\n"
"* Run command error for \"git clone https://github.com/RT-Thread/packages.git\".\n"
"* This error may have been caused by not found a git tool or network error.\n"
"* If the git tool is not installed, install the git tool first.\n"
"* If the git utility is installed, check whether the git command is added to \n"
"* the system PATH.\n"
"* This error may cause the RT-Thread packages to not work properly.\n"
"********************************************************************************\n"
)
help_info()
else:
kconfig = open(os.path.join(env_dir, 'packages', 'Kconfig'), 'w')
kconfig.write('source "$PKGS_DIR/packages/Kconfig"')
kconfig.close()
except:
print(
"**********************************************************************************\n"
"* Warnning:\n"
"* Run command error for \"git clone https://github.com/RT-Thread/packages.git\". \n"
"* This error may have been caused by not found a git tool or git tool not in \n"
"* the system PATH. \n"
"* This error may cause the RT-Thread packages to not work properly. \n"
"**********************************************************************************\n"
)
help_info()
# git clone env scripts
if not os.path.exists(os.path.join(env_dir, 'tools', 'scripts')):
try:
ret = os.system('git clone %s %s' % (env_url, os.path.join(env_dir, 'tools', 'scripts')))
if ret != 0:
shutil.rmtree(os.path.join(env_dir, 'tools', 'scripts'))
print(
"********************************************************************************\n"
"* Warnning:\n"
"* Run command error for \"git clone https://github.com/RT-Thread/env.git\".\n"
"* This error may have been caused by not found a git tool or network error.\n"
"* If the git tool is not installed, install the git tool first.\n"
"* If the git utility is installed, check whether the git command is added \n"
"* to the system PATH.\n"
"* This error may cause script tools to fail to work properly.\n"
"********************************************************************************\n"
)
help_info()
except:
print(
"********************************************************************************\n"
"* Warnning:\n"
"* Run command error for \"git clone https://github.com/RT-Thread/env.git\". \n"
"* This error may have been caused by not found a git tool or git tool not in \n"
"* the system PATH. \n"
"* This error may cause script tools to fail to work properly. \n"
"********************************************************************************\n"
)
help_info()
# git clone sdk
if not os.path.exists(os.path.join(env_dir, 'packages', 'sdk')):
try:
ret = os.system('git clone %s %s' % (sdk_url, os.path.join(env_dir, 'packages', 'sdk')))
if ret != 0:
shutil.rmtree(os.path.join(env_dir, 'packages', 'sdk'))
print(
"********************************************************************************\n"
"* Warnning:\n"
"* Run command error for \"git clone https://github.com/RT-Thread/sdk.git\".\n"
"* This error may have been caused by not found a git tool or network error.\n"
"* If the git tool is not installed, install the git tool first.\n"
"* If the git utility is installed, check whether the git command is added \n"
"* to the system PATH.\n"
"* This error may cause the RT-Thread SDK to not work properly.\n"
"********************************************************************************\n"
)
help_info()
except:
print(
"********************************************************************************\n"
"* Warnning:\n"
"* Run command error for \"https://github.com/RT-Thread/sdk.git\".\n"
"* This error may have been caused by not found a git tool or git tool not in \n"
"* the system PATH. \n"
"* This error may cause the RT-Thread SDK to not work properly. \n"
"********************************************************************************\n"
)
help_info()
# try to create an empty .config file
if not os.path.exists(os.path.join(env_dir, 'tools', '.config')):
kconfig = open(os.path.join(env_dir, 'tools', '.config'), 'w')
kconfig.close()
# copy env.sh or env.ps1, Kconfig
shutil.copy(os.path.join(env_dir, 'tools', 'scripts', 'Kconfig'), os.path.join(home_dir, '.env', 'tools'))
if sys.platform != 'win32':
shutil.copy(os.path.join(env_dir, 'tools', 'scripts', 'env.sh'), os.path.join(home_dir, '.env', 'env.sh'))
else:
shutil.copy(os.path.join(env_dir, 'tools', 'scripts', 'env.ps1'), os.path.join(home_dir, '.env', 'env.ps1'))
# unzip kconfig-mconf.zip
# zip_file = os.path.join(env_dir, 'tools', 'scripts', 'kconfig-mconf.zip')
# if os.path.exists(zip_file):
# zip_file_dir = os.path.join(env_dir, 'tools', 'bin')
# if os.path.exists(zip_file_dir):
# shutil.rmtree(zip_file_dir)
# zip_file_obj = zipfile.ZipFile(zip_file, 'r')
# for file in zip_file_obj.namelist():
# zip_file_obj.extract(file, zip_file_dir)
# zip_file_obj.close()
def is_pkg_special_config(config_str):
'''judge if it's CONFIG_PKG_XX_PATH or CONFIG_PKG_XX_VER'''
if type(config_str) == type('a'):
if config_str.startswith("PKG_") and (config_str.endswith('_PATH') or config_str.endswith('_VER')):
return True
return False
def mk_rtconfig(filename):
try:
config = open(filename, 'r')
except:
print('open config:%s failed' % filename)
return
rtconfig = open('rtconfig.h', 'w')
rtconfig.write('#ifndef RT_CONFIG_H__\n')
rtconfig.write('#define RT_CONFIG_H__\n\n')
empty_line = 1
for line in config:
line = line.lstrip(' ').replace('\n', '').replace('\r', '')
if len(line) == 0:
continue
if line[0] == '#':
if len(line) == 1:
if empty_line:
continue
rtconfig.write('\n')
empty_line = 1
continue
if line.startswith('# CONFIG_'):
line = ' ' + line[9:]
else:
line = line[1:]
rtconfig.write('/*%s */\n' % line)
empty_line = 0
else:
empty_line = 0
setting = line.split('=')
if len(setting) >= 2:
if setting[0].startswith('CONFIG_'):
setting[0] = setting[0][7:]
# remove CONFIG_PKG_XX_PATH or CONFIG_PKG_XX_VER
if is_pkg_special_config(setting[0]):
continue
if setting[1] == 'y':
rtconfig.write('#define %s\n' % setting[0])
else:
rtconfig.write('#define %s %s\n' % (setting[0], re.findall(r"^.*?=(.*)$", line)[0]))
if os.path.isfile('rtconfig_project.h'):
rtconfig.write('#include "rtconfig_project.h"\n')
rtconfig.write('\n')
rtconfig.write('#endif\n')
rtconfig.close()
def get_file_md5(file):
MD5 = hashlib.new('md5')
with open(file, 'r') as fp:
MD5.update(fp.read().encode('utf8'))
fp_md5 = MD5.hexdigest()
return fp_md5
# Exclude utestcases
def exclude_utestcases(RTT_ROOT):
if os.path.isfile(os.path.join(RTT_ROOT, 'examples/utest/testcases/Kconfig')):
return
if not os.path.isfile(os.path.join(RTT_ROOT, 'Kconfig')):
return
with open(os.path.join(RTT_ROOT, 'Kconfig'), 'r') as f:
data = f.readlines()
with open(os.path.join(RTT_ROOT, 'Kconfig'), 'w') as f:
for line in data:
if line.find('examples/utest/testcases/Kconfig') == -1:
f.write(line)
# fix locale for kconfiglib
def kconfiglib_fix_locale():
import os
import locale
# Get the list of supported locales
supported_locales = set(locale.locale_alias.keys())
# Check if LANG is set and its value is not in the supported locales
if 'LANG' in os.environ and os.environ['LANG'] not in supported_locales:
os.environ['LANG'] = 'C'
def kconfiglib_check_installed():
try:
import kconfiglib
except ImportError as e:
print("\033[1;31m**ERROR**: Failed to import kconfiglib, " + str(e))
print("")
print("You may need to install it using:")
print(" pip install kconfiglib\033[0m")
print("")
sys.exit(1)
# set PKGS_DIR envrionment
pkg_dir = GetPkgPath()
if os.path.exists(pkg_dir):
os.environ["PKGS_DIR"] = pkg_dir
elif sys.platform != 'win32':
touch_env()
os.environ["PKGS_DIR"] = GetPkgPath()
else:
print("\033[1;33m**WARNING**: PKGS_DIR not found, please install ENV tools\033[0m")
# menuconfig for Linux and Windows
def menuconfig(RTT_ROOT):
kconfiglib_check_installed()
import menuconfig
# Exclude utestcases
exclude_utestcases(RTT_ROOT)
fn = '.config'
fn_old = '.config.old'
sys.argv = ['menuconfig', 'Kconfig']
# fix vscode console
kconfiglib_fix_locale()
menuconfig._main()
if os.path.isfile(fn):
if os.path.isfile(fn_old):
diff_eq = operator.eq(get_file_md5(fn), get_file_md5(fn_old))
else:
diff_eq = False
else:
sys.exit(-1)
# make rtconfig.h
if diff_eq == False:
shutil.copyfile(fn, fn_old)
mk_rtconfig(fn)
# guiconfig for windows and linux
def guiconfig(RTT_ROOT):
kconfiglib_check_installed()
import guiconfig
# Exclude utestcases
exclude_utestcases(RTT_ROOT)
fn = '.config'
fn_old = '.config.old'
sys.argv = ['guiconfig', 'Kconfig']
guiconfig._main()
if os.path.isfile(fn):
if os.path.isfile(fn_old):
diff_eq = operator.eq(get_file_md5(fn), get_file_md5(fn_old))
else:
diff_eq = False
else:
sys.exit(-1)
# make rtconfig.h
if diff_eq == False:
shutil.copyfile(fn, fn_old)
mk_rtconfig(fn)
# defconfig for windows and linux
def defconfig(RTT_ROOT):
kconfiglib_check_installed()
import defconfig
# Exclude utestcases
exclude_utestcases(RTT_ROOT)
fn = '.config'
sys.argv = ['defconfig', '--kconfig', 'Kconfig', '.config']
defconfig.main()
# silent mode, force to make rtconfig.h
mk_rtconfig(fn)
def genconfig():
from SCons.Script import SCons
PreProcessor = SCons.cpp.PreProcessor()
try:
f = open('rtconfig.h', 'r')
contents = f.read()
f.close()
except:
print("Open rtconfig.h file failed.")
PreProcessor.process_contents(contents)
options = PreProcessor.cpp_namespace
try:
f = open('.config', 'w')
for opt, value in options.items():
if type(value) == type(1):
f.write("CONFIG_%s=%d\n" % (opt, value))
if type(value) == type('') and value == '':
f.write("CONFIG_%s=y\n" % opt)
elif type(value) == type('str'):
f.write("CONFIG_%s=%s\n" % (opt, value))
print("Generate .config done!")
f.close()
except:
print("Generate .config file failed.")
+285
View File
@@ -0,0 +1,285 @@
#
# File : gcc.py
# This file is part of RT-Thread RTOS
# COPYRIGHT (C) 2006 - 2018, RT-Thread Development Team
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Change Logs:
# Date Author Notes
# 2018-05-22 Bernard The first version
# 2023-11-03 idings return file path in GetHeader
import os
import re
import platform
import subprocess
def GetGCCRoot(rtconfig):
exec_path = rtconfig.EXEC_PATH
prefix = rtconfig.PREFIX
if prefix.endswith('-'):
prefix = prefix[:-1]
if exec_path == '/usr/bin':
root_path = os.path.join('/usr/lib', prefix)
else:
root_path = os.path.join(exec_path, '..', prefix)
return root_path
# https://stackoverflow.com/questions/4980819/what-are-the-gcc-default-include-directories
# https://stackoverflow.com/questions/53937211/how-can-i-parse-gcc-output-by-regex-to-get-default-include-paths
def match_pattern(pattern, input, start = 0, stop = -1, flags = 0):
length = len(input)
if length == 0:
return None
end_it = max(0, length - 1)
if start >= end_it:
return None
if stop<0:
stop = length
if stop <= start:
return None
for it in range(max(0, start), min(stop, length)):
elem = input[it]
match = re.match(pattern, elem, flags)
if match:
return it
def GetGccDefaultSearchDirs(rtconfig):
start_pattern = r' *#include <\.\.\.> search starts here: *'
end_pattern = r' *End of search list\. *'
gcc_cmd = os.path.join(rtconfig.EXEC_PATH, rtconfig.CC)
device_flags = rtconfig.DEVICE.split()
command = [gcc_cmd] + device_flags + ['-xc', '-E', '-v', os.devnull]
# if gcc_cmd can not access , return empty list
if not os.access(gcc_cmd, os.X_OK):
return []
if(platform.system() == 'Windows'):
child = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
else:
child = subprocess.Popen(' '.join(command), stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
stdout = child.communicate()
stdout_string = (b''.join(stdout)).decode()
lines = stdout_string.splitlines()
start_it = match_pattern(start_pattern, lines)
if start_it == None:
return []
end_it = match_pattern(end_pattern, lines, start_it)
if end_it == None:
return []
# theres no paths between them
if (end_it - start_it) == 1:
return []
return lines[start_it + 1 : end_it]
def GetHeader(rtconfig, filename):
include_dirs = GetGccDefaultSearchDirs(rtconfig)
for directory in include_dirs:
fn = os.path.join(directory, filename).strip()
if os.path.isfile(fn):
return fn
# fallback to use fixed method if can't autodetect
root = GetGCCRoot(rtconfig)
fn = os.path.join(root, 'include', filename)
if os.path.isfile(fn):
return fn
# Usually the cross compiling gcc toolchain has directory as:
#
# bin
# lib
# share
# arm-none-eabi
# bin
# include
# lib
# share
prefix = rtconfig.PREFIX
if prefix.endswith('-'):
prefix = prefix[:-1]
fn = os.path.join(root, prefix, 'include', filename)
if os.path.isfile(fn):
return fn
return None
# GCC like means the toolchains which are compatible with GCC
def GetGCCLikePLATFORM():
return ['gcc', 'armclang', 'llvm-arm']
def GetPicoLibcVersion(rtconfig):
version = None
try:
rtconfig.PREFIX
except:
return version
# get version from picolibc.h
fn = GetHeader(rtconfig, 'picolibc.h')
if fn:
f = open(fn, 'r')
if f:
for line in f:
if line.find('__PICOLIBC_VERSION__') != -1 and line.find('"') != -1:
version = re.search(r'\"([^"]+)\"', line).groups()[0]
f.close()
return version
def GetNewLibVersion(rtconfig):
version = None
try:
rtconfig.PREFIX
except:
return version
# if find picolibc.h, use picolibc
fn = GetHeader(rtconfig, 'picolibc.h')
if fn:
return version
# get version from _newlib_version.h file
fn = GetHeader(rtconfig, '_newlib_version.h')
# get version from newlib.h
if not fn:
fn = GetHeader(rtconfig, 'newlib.h')
if fn:
f = open(fn, 'r')
for line in f:
if line.find('_NEWLIB_VERSION') != -1 and line.find('"') != -1:
version = re.search(r'\"([^"]+)\"', line).groups()[0]
f.close()
return version
# FIXME: there is no musl version or musl macros can be found officially
def GetMuslVersion(rtconfig):
version = None
try:
rtconfig.PREFIX
except:
return version
if 'musl' in rtconfig.PREFIX:
version = 'unknown'
return version
def GCCResult(rtconfig, str):
result = ''
def checkAndGetResult(pattern, string):
if re.search(pattern, string):
return re.search(pattern, string).group(0)
return None
gcc_cmd = os.path.join(rtconfig.EXEC_PATH, rtconfig.CC)
# use temp file to get more information
f = open('__tmp.c', 'w')
if f:
f.write(str)
f.close()
# '-fdirectives-only',
if(platform.system() == 'Windows'):
child = subprocess.Popen([gcc_cmd, '-E', '-P', '__tmp.c'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
else:
child = subprocess.Popen(gcc_cmd + ' -E -P __tmp.c', stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
stdout, stderr = child.communicate()
# print(stdout)
if stderr != '' and stderr != b'':
print(stderr)
have_fdset = 0
have_sigaction = 0
have_sigevent = 0
have_siginfo = 0
have_sigval = 0
version = None
stdc = '1989'
posix_thread = 0
for line in stdout.split(b'\n'):
line = line.decode()
if re.search('fd_set', line):
have_fdset = 1
# check for sigal
if re.search('struct[ \t]+sigaction', line):
have_sigaction = 1
if re.search('struct[ \t]+sigevent', line):
have_sigevent = 1
if re.search('siginfo_t', line):
have_siginfo = 1
if re.search('union[ \t]+sigval', line):
have_sigval = 1
if re.search(r'char\* version', line):
version = re.search(r'"([^"]+)"', line).groups()[0]
if re.findall(r'iso_c_visible = \d+', line):
stdc = re.findall(r'\d+', line)[0]
if re.findall('pthread_create', line):
posix_thread = 1
if have_fdset:
result += '#define HAVE_FDSET 1\n'
if have_sigaction:
result += '#define HAVE_SIGACTION 1\n'
if have_sigevent:
result += '#define HAVE_SIGEVENT 1\n'
if have_siginfo:
result += '#define HAVE_SIGINFO 1\n'
if have_sigval:
result += '#define HAVE_SIGVAL 1\n'
if version:
result += '#define GCC_VERSION_STR "%s"\n' % version
result += '#define STDC "%s"\n' % stdc
if posix_thread:
result += '#define LIBC_POSIX_THREADS 1\n'
os.remove('__tmp.c')
return result
+49
View File
@@ -0,0 +1,49 @@
# Hello Component
这是一个使用package.json配置的RT-Thread组件示例,展示了如何使用package.json来替代传统的SConscript中DefineGroup的方式。
## 文件结构
```
hello/
├── hello.h # 头文件
├── hello.c # 源文件
├── package.json # 组件配置文件
├── SConscript # 构建脚本
└── README.md # 说明文档
```
## package.json配置说明
package.json文件包含了组件的所有构建信息:
- `name`: 组件名称
- `version`: 版本号
- `description`: 组件描述
- `author`: 作者信息
- `license`: 许可证
- `source_files`: 源文件列表
- `CPPPATH`: 头文件搜索路径
- `CPPDEFINES`: 预定义宏
- `depends`: 依赖的组件
## 使用方法
1. 将hello文件夹复制到你的RT-Thread项目的components目录下
2. 在应用代码中包含头文件:
```c
#include "hello.h"
```
3. 调用hello_world函数:
```c
hello_world(); // 输出: Hello World!
```
## 构建过程
SConscript文件会:
1. 导入package.py模块
2. 调用BuildPackage函数处理package.json
3. 自动创建DefineGroup并返回构建对象
这种方式比传统的SConscript更加简洁和易于维护。
+4
View File
@@ -0,0 +1,4 @@
from package import *
objs = BuildPackage()
Return('objs')
+22
View File
@@ -0,0 +1,22 @@
/*
* Copyright (c) 2025 RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2025-06-21 Bernard First version
*/
#include <rtthread.h>
#include "hello.h"
/**
* @brief Hello world function implementation
*
* This function prints "Hello World!" to the console using rt_kprintf
*/
void hello_world(void)
{
rt_kprintf("Hello World!\n");
}
+29
View File
@@ -0,0 +1,29 @@
/*
* Copyright (c) 2025 RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2025-06-21 Bernard First version
*/
#ifndef __HELLO_H__
#define __HELLO_H__
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Hello world function
*
* This function prints "Hello World!" to the console
*/
void hello_world(void);
#ifdef __cplusplus
}
#endif
#endif /* __HELLO_H__ */
+31
View File
@@ -0,0 +1,31 @@
{
"name": "hello",
"description": "Hello World component for RT-Thread",
"type": "rt-thread-component",
"dependencies": [],
"defines": [
"DEFINE_HELLO"
],
"sources": [
{
"dependencies": [],
"includes": [
"."
],
"files": [
"hello.c"
]
},
{
"dependencies": [
"HELLO_USING_HELPER"
],
"includes": [
"src"
],
"files": [
"src/helper.c"
]
}
]
}
+15
View File
@@ -0,0 +1,15 @@
/*
* Copyright (c) 2025 RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2025-08-03 Bernard First version
*/
#include "helper.h"
void hello_helper()
{
}
+16
View File
@@ -0,0 +1,16 @@
/*
* Copyright (c) 2025 RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2025-08-03 Bernard First version
*/
#ifndef __HELPER__H__
#define __HELPER__H__
void hello_helper();
#endif //!__HELPER__H__
+58
View File
@@ -0,0 +1,58 @@
#
# File : llvm_arm.py
# This file is part of RT-Thread RTOS
# COPYRIGHT (C) 2006 - 2018, RT-Thread Development Team
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Change Logs:
# Date Author Notes
# 2023-05-17 Flybreak The first version
import os
import re
import platform
def GetLLVM_ARMRoot(rtconfig):
exec_path = rtconfig.EXEC_PATH
lib_path = 'lib/clang-runtimes/arm-none-eabi'
root_path = os.path.join(exec_path, '..', lib_path)
return root_path
def CheckHeader(rtconfig, filename):
root = GetLLVM_ARMRoot(rtconfig)
if os.path.isdir(root):
for config in os.listdir(root):
fn = os.path.join(root, config, 'include', filename)
if os.path.isfile(fn):
return True
return False
def GetPicoLibcVersion(rtconfig):
version = None
root = GetLLVM_ARMRoot(rtconfig)
if CheckHeader(rtconfig, 'picolibc.h'): # get version from picolibc.h file
for config in os.listdir(root):
fn = os.path.join(root, config, 'include', 'picolibc.h')
f = open(fn, 'r')
if f:
for line in f:
if line.find('__PICOLIBC_VERSION__') != -1 and line.find('"') != -1:
version = re.search(r'\"([^"]+)\"', line).groups()[0]
f.close()
return version
return version
+339
View File
@@ -0,0 +1,339 @@
#
# File : mkdir.py
# This file is part of RT-Thread RTOS
# COPYRIGHT (C) 2006 - 2018, RT-Thread Development Team
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Change Logs:
# Date Author Notes
# 2017-10-04 Bernard The first version
# 2025-01-07 ZhaoCake components copy and gen doc
# 2025-03-02 ZhaoCake Add MkDist_Strip
import os
import subprocess
import shutil
from shutil import ignore_patterns
from SCons.Script import *
def do_copy_file(src, dst):
# check source file
if not os.path.exists(src):
return
path = os.path.dirname(dst)
# mkdir if path not exist
if not os.path.exists(path):
os.makedirs(path)
shutil.copy2(src, dst)
def do_copy_folder(src_dir, dst_dir, ignore=None):
import shutil
# check source directory
if not os.path.exists(src_dir):
return
try:
if os.path.exists(dst_dir):
shutil.rmtree(dst_dir)
except:
print('Deletes folder: %s failed.' % dst_dir)
return
shutil.copytree(src_dir, dst_dir, ignore = ignore)
source_ext = ['c', 'h', 's', 'S', 'cpp', 'cxx', 'cc', 'xpm']
source_list = []
def walk_children(child):
global source_list
global source_ext
# print child
full_path = child.rfile().abspath
file_type = full_path.rsplit('.',1)[1]
#print file_type
if file_type in source_ext:
if full_path not in source_list:
source_list.append(full_path)
children = child.all_children()
if children != []:
for item in children:
walk_children(item)
def walk_kconfig(RTT_ROOT, source_list):
for parent, dirnames, filenames in os.walk(RTT_ROOT):
if 'bsp' in parent:
continue
if '.git' in parent:
continue
if 'tools' in parent:
continue
if 'Kconfig' in filenames:
pathfile = os.path.join(parent, 'Kconfig')
source_list.append(pathfile)
if 'KConfig' in filenames:
pathfile = os.path.join(parent, 'KConfig')
source_list.append(pathfile)
def bsp_copy_files(bsp_root, dist_dir):
# copy BSP files
do_copy_folder(os.path.join(bsp_root), dist_dir,
ignore_patterns('build', '__pycache__', 'dist', '*.pyc', '*.old', '*.map', 'rtthread.bin', '.sconsign.dblite', '*.elf', '*.axf', 'cconfig.h'))
def bsp_update_sconstruct(dist_dir):
with open(os.path.join(dist_dir, 'SConstruct'), 'r') as f:
data = f.readlines()
with open(os.path.join(dist_dir, 'SConstruct'), 'w') as f:
for line in data:
if line.find('RTT_ROOT') != -1:
if line.find('sys.path') != -1:
f.write('# set RTT_ROOT\n')
f.write('if not os.getenv("RTT_ROOT"): \n RTT_ROOT="rt-thread"\n\n')
f.write(line)
def bsp_update_kconfig_testcases(dist_dir):
# delete testcases in rt-thread/Kconfig
if not os.path.isfile(os.path.join(dist_dir, 'rt-thread/Kconfig')):
return
with open(os.path.join(dist_dir, 'rt-thread/Kconfig'), 'r') as f:
data = f.readlines()
with open(os.path.join(dist_dir, 'rt-thread/Kconfig'), 'w') as f:
for line in data:
if line.find('examples/utest/testcases/Kconfig') == -1:
f.write(line)
def bsp_update_kconfig(dist_dir):
# change RTT_ROOT in Kconfig
if not os.path.isfile(os.path.join(dist_dir, 'Kconfig')):
return
with open(os.path.join(dist_dir, 'Kconfig'), 'r') as f:
data = f.readlines()
with open(os.path.join(dist_dir, 'Kconfig'), 'w') as f:
for line in data:
if line.find('RTT_DIR') != -1 and line.find(':=') != -1:
line = 'RTT_DIR := rt-thread\n'
f.write(line)
def bsp_update_kconfig_library(dist_dir):
# change RTT_ROOT in Kconfig
if not os.path.isfile(os.path.join(dist_dir, 'Kconfig')):
return
with open(os.path.join(dist_dir, 'Kconfig'), 'r') as f:
data = f.readlines()
with open(os.path.join(dist_dir, 'Kconfig'), 'w') as f:
for line in data:
if line.find('source') != -1 and line.find('../libraries') != -1:
line = line.replace('../libraries', 'libraries')
f.write(line)
# change board/kconfig path
if not os.path.isfile(os.path.join(dist_dir, 'board/Kconfig')):
return
with open(os.path.join(dist_dir, 'board/Kconfig'), 'r') as f:
data = f.readlines()
with open(os.path.join(dist_dir, 'board/Kconfig'), 'w') as f:
for line in data:
if line.find('source') != -1 and line.find('../libraries') != -1:
line = line.replace('../libraries', 'libraries')
f.write(line)
def zip_dist(dist_dir, dist_name):
import zipfile
zip_filename = os.path.join(dist_dir)
zip = zipfile.ZipFile(zip_filename + '.zip', 'w')
pre_len = len(os.path.dirname(dist_dir))
for parent, dirnames, filenames in os.walk(dist_dir):
for filename in filenames:
pathfile = os.path.join(parent, filename)
arcname = pathfile[pre_len:].strip(os.path.sep)
zip.write(pathfile, arcname)
zip.close()
def MkDist(program, BSP_ROOT, RTT_ROOT, Env, project_name, project_path):
print('make distribution....')
if project_path == None:
dist_dir = os.path.join(BSP_ROOT, 'dist', project_name)
else:
dist_dir = project_path
rtt_dir_path = os.path.join(dist_dir, 'rt-thread')
# copy BSP files
print('=> %s' % os.path.basename(BSP_ROOT))
bsp_copy_files(BSP_ROOT, dist_dir)
# do bsp special dist handle
if 'dist_handle' in Env:
print("=> start dist handle")
dist_handle = Env['dist_handle']
dist_handle(BSP_ROOT, dist_dir)
# copy tools directory
print('=> components')
do_copy_folder(os.path.join(RTT_ROOT, 'components'), os.path.join(rtt_dir_path, 'components'))
# skip documentation directory
# skip examples
# copy include directory
print('=> include')
do_copy_folder(os.path.join(RTT_ROOT, 'include'), os.path.join(rtt_dir_path, 'include'))
# copy all libcpu/ARCH directory
print('=> libcpu')
import rtconfig
do_copy_folder(os.path.join(RTT_ROOT, 'libcpu', rtconfig.ARCH), os.path.join(rtt_dir_path, 'libcpu', rtconfig.ARCH))
do_copy_file(os.path.join(RTT_ROOT, 'libcpu', 'Kconfig'), os.path.join(rtt_dir_path, 'libcpu', 'Kconfig'))
do_copy_file(os.path.join(RTT_ROOT, 'libcpu', 'SConscript'), os.path.join(rtt_dir_path, 'libcpu', 'SConscript'))
# copy src directory
print('=> src')
do_copy_folder(os.path.join(RTT_ROOT, 'src'), os.path.join(rtt_dir_path, 'src'))
# copy tools directory
print('=> tools')
do_copy_folder(os.path.join(RTT_ROOT, 'tools'), os.path.join(rtt_dir_path, 'tools'), ignore_patterns('*.pyc'))
do_copy_file(os.path.join(RTT_ROOT, 'Kconfig'), os.path.join(rtt_dir_path, 'Kconfig'))
do_copy_file(os.path.join(RTT_ROOT, 'AUTHORS'), os.path.join(rtt_dir_path, 'AUTHORS'))
do_copy_file(os.path.join(RTT_ROOT, 'COPYING'), os.path.join(rtt_dir_path, 'COPYING'))
do_copy_file(os.path.join(RTT_ROOT, 'README.md'), os.path.join(rtt_dir_path, 'README.md'))
do_copy_file(os.path.join(RTT_ROOT, 'README_zh.md'), os.path.join(rtt_dir_path, 'README_zh.md'))
print('Update configuration files...')
# change RTT_ROOT in SConstruct
bsp_update_sconstruct(dist_dir)
# change RTT_ROOT in Kconfig
bsp_update_kconfig(dist_dir)
bsp_update_kconfig_library(dist_dir)
# delete testcases in Kconfig
bsp_update_kconfig_testcases(dist_dir)
target_project_type = GetOption('target')
if target_project_type:
child = subprocess.Popen('scons --target={} --project-name="{}"'.format(target_project_type, project_name), cwd=dist_dir, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
stdout, stderr = child.communicate()
if child.returncode == 0:
print(stdout)
else:
print(stderr)
else:
print('suggest to use command scons --dist [--target=xxx] [--project-name="xxx"] [--project-path="xxx"]')
# make zip package
if project_path == None:
zip_dist(dist_dir, project_name)
print('dist project successfully!')
def MkDist_Strip(program, BSP_ROOT, RTT_ROOT, env, project_name, project_path=None):
"""Create a minimal distribution based on compile_commands.json but keeping all build system files.
First copies everything like MkDist, then only removes unused source files while keeping all headers.
"""
print('Making minimal distribution for project...')
if project_path == None:
dist_dir = os.path.join(BSP_ROOT, 'dist', project_name)
else:
dist_dir = project_path
# First do a full distribution copy
MkDist(program, BSP_ROOT, RTT_ROOT, env, project_name, project_path)
print('\n=> Starting source files cleanup...')
# Get the minimal required source paths
import compile_commands
used_paths = compile_commands.get_minimal_dist_paths(
os.path.join(BSP_ROOT, 'compile_commands.json'),
RTT_ROOT
)
# Clean up RT-Thread directory except tools and build files
rt_thread_dir = os.path.join(dist_dir, 'rt-thread')
source_extensions = ('.c', '.cpp', '.cxx', '.cc', '.s', '.S')
removed_files = []
removed_dirs = []
for root, dirs, files in os.walk(rt_thread_dir, topdown=False):
rel_path = os.path.relpath(root, rt_thread_dir)
if rel_path.startswith('tools') or rel_path.startswith('include'):
continue
keep_files = {
'SConscript',
'Kconfig',
'Sconscript',
'.config',
'rtconfig.h'
}
for f in files:
if f in keep_files:
continue
if not f.endswith(source_extensions):
continue
file_path = os.path.join(root, f)
rel_file_path = os.path.relpath(file_path, rt_thread_dir)
dir_name = os.path.dirname(rel_file_path)
if dir_name not in used_paths and rel_file_path not in used_paths:
os.remove(file_path)
removed_files.append(rel_file_path)
# Remove empty directories
try:
if not os.listdir(root):
os.rmdir(root)
removed_dirs.append(rel_path)
except:
pass
# Output summary
if removed_files:
print("Removed {} unused source files".format(len(removed_files)))
log_file = os.path.join(dist_dir, 'cleanup.log')
with open(log_file, 'w') as f:
f.write("Removed source files:\n")
f.write('\n'.join(removed_files))
if removed_dirs:
f.write("\n\nRemoved empty directories:\n")
f.write('\n'.join(removed_dirs))
print("Details have been written to {}".format(log_file))
else:
print("No unused source files found")
# Make zip package like MkDist
if project_path is None:
zip_dist(dist_dir, project_name)
print("Distribution package created: {}.zip".format(dist_dir))
print('=> Distribution stripped successfully')
+267
View File
@@ -0,0 +1,267 @@
#!/usr/bin/env python
import sys
import os
import struct
from collections import namedtuple
import io
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('rootdir', type=str, help='the path to rootfs')
parser.add_argument('output', type=argparse.FileType('wb'), nargs='?', help='output file name')
parser.add_argument('--dump', action='store_true', help='dump the fs hierarchy')
parser.add_argument('--binary', action='store_true', help='output binary file')
parser.add_argument('--addr', default='0', help='set the base address of the binary file, default to 0.')
class File(object):
def __init__(self, name):
self._name = name
self._data = open(name, 'rb').read()
@property
def name(self):
return self._name
@property
def c_name(self):
return '_' + self._name.replace('.', '_')
@property
def bin_name(self):
# Pad to 4 bytes boundary with \0
pad_len = 4
bn = self._name + '\0' * (pad_len - len(self._name) % pad_len)
return bn
def c_data(self, prefix=''):
'''Get the C code represent of the file content.'''
head = 'static const rt_uint8_t %s[] = {\n' % \
(prefix + self.c_name)
tail = '\n};'
if self.entry_size == 0:
return ''
if len(self._data) > 0 and type(self._data[0]) == int:
return head + ','.join(('0x%02x' % i for i in self._data)) + tail
else:
return head + ','.join(('0x%02x' % ord(i) for i in self._data)) + tail
@property
def entry_size(self):
return len(self._data)
def bin_data(self, base_addr=0x0):
return bytes(self._data)
def dump(self, indent=0):
print('%s%s' % (' ' * indent, self._name))
class Folder(object):
bin_fmt = struct.Struct('IIII')
bin_item = namedtuple('dirent', 'type, name, data, size')
def __init__(self, name):
self._name = name
self._children = []
@property
def name(self):
return self._name
@property
def c_name(self):
# add _ to avoid conflict with C key words.
return '_' + self._name
@property
def bin_name(self):
# Pad to 4 bytes boundary with \0
pad_len = 4
bn = self._name + '\0' * (pad_len - len(self._name) % pad_len)
return bn
def walk(self):
# os.listdir will return unicode list if the argument is unicode.
# TODO: take care of the unicode names
for ent in os.listdir(u'.'):
if os.path.isdir(ent):
cwd = os.getcwd()
d = Folder(ent)
# depth-first
os.chdir(os.path.join(cwd, ent))
d.walk()
# restore the cwd
os.chdir(cwd)
self._children.append(d)
else:
self._children.append(File(ent))
def sort(self):
def _sort(x, y):
if x.name == y.name:
return 0
elif x.name > y.name:
return 1
else:
return -1
from functools import cmp_to_key
self._children.sort(key=cmp_to_key(_sort))
# sort recursively
for c in self._children:
if isinstance(c, Folder):
c.sort()
def dump(self, indent=0):
print('%s%s' % (' ' * indent, self._name))
for c in self._children:
c.dump(indent + 1)
def c_data(self, prefix=''):
'''get the C code represent of the folder.
It is recursive.'''
# make the current dirent
# static is good. Only root dirent is global visible.
if self.entry_size == 0:
return ''
dhead = 'static const struct romfs_dirent %s[] = {\n' % (prefix + self.c_name)
dtail = '\n};'
body_fmt = ' {{{type}, "{name}", (rt_uint8_t *){data}, sizeof({data})/sizeof({data}[0])}}'
body_fmt0= ' {{{type}, "{name}", RT_NULL, 0}}'
# prefix of children
cpf = prefix+self.c_name
body_li = []
payload_li = []
for c in self._children:
entry_size = c.entry_size
if isinstance(c, File):
tp = 'ROMFS_DIRENT_FILE'
elif isinstance(c, Folder):
tp = 'ROMFS_DIRENT_DIR'
else:
assert False, 'Unkown instance:%s' % str(c)
if entry_size == 0:
body_li.append(body_fmt0.format(type=tp, name = c.name))
else:
body_li.append(body_fmt.format(type=tp,
name=c.name,
data=cpf+c.c_name))
payload_li.append(c.c_data(prefix=cpf))
# All the data we need is defined in payload so we should append the
# dirent to it. It also meet the depth-first policy in this code.
payload_li.append(dhead + ',\n'.join(body_li) + dtail)
return '\n\n'.join(payload_li)
@property
def entry_size(self):
return len(self._children)
def bin_data(self, base_addr=0x0):
'''Return StringIO object'''
# The binary layout is different from the C code layout. We put the
# dirent before the payload in this mode. But the idea is still simple:
# Depth-First.
#{
# rt_uint32_t type;
# const char *name;
# const rt_uint8_t *data;
# rt_size_t size;
#}
d_li = []
# payload base
p_base = base_addr + self.bin_fmt.size * self.entry_size
# the length to record how many data is in
v_len = p_base
# payload
p_li = []
for c in self._children:
if isinstance(c, File):
# ROMFS_DIRENT_FILE
tp = 0
elif isinstance(c, Folder):
# ROMFS_DIRENT_DIR
tp = 1
else:
assert False, 'Unkown instance:%s' % str(c)
name = bytes(c.bin_name.encode('utf-8'))
name_addr = v_len
v_len += len(name)
data = c.bin_data(base_addr=v_len)
data_addr = v_len
# pad the data to 4 bytes boundary
pad_len = 4
if len(data) % pad_len != 0:
data += ('\0' * (pad_len - len(data) % pad_len)).encode('utf-8')
v_len += len(data)
d_li.append(self.bin_fmt.pack(*self.bin_item(
type=tp,
name=name_addr,
data=data_addr,
size=c.entry_size)))
p_li.extend((name, data))
return bytes().join(d_li) + bytes().join(p_li)
def get_c_data(tree):
# Handle the root dirent specially.
root_dirent_fmt = '''/* Generated by mkromfs. Edit with caution. */
#include <rtthread.h>
#include <dfs_romfs.h>
{data}
const struct romfs_dirent {name} = {{
ROMFS_DIRENT_DIR, "/", (rt_uint8_t *){rootdirent}, sizeof({rootdirent})/sizeof({rootdirent}[0])
}};
'''
return root_dirent_fmt.format(name='romfs_root',
rootdirent=tree.c_name,
data=tree.c_data())
def get_bin_data(tree, base_addr):
v_len = base_addr + Folder.bin_fmt.size
name = bytes('/\0\0\0'.encode("utf-8"))
name_addr = v_len
v_len += len(name)
data_addr = v_len
# root entry
data = Folder.bin_fmt.pack(*Folder.bin_item(type=1,
name=name_addr,
data=data_addr,
size=tree.entry_size))
return data + name + tree.bin_data(v_len)
if __name__ == '__main__':
args = parser.parse_args()
os.chdir(args.rootdir)
tree = Folder('romfs_root')
tree.walk()
tree.sort()
if args.dump:
tree.dump()
if args.binary:
data = get_bin_data(tree, int(args.addr, 16))
else:
data = get_c_data(tree).encode()
output = args.output
if not output:
output = sys.stdout
output.write(data)
+345
View File
@@ -0,0 +1,345 @@
# RT-Thread Next Generation Build System
## 概述
RT-Thread NGNext Generation)构建系统是对现有构建系统的面向对象重构,在保持完全向后兼容的同时,提供了更清晰的架构和更强的可扩展性。
## 特性
-**完全向后兼容**:现有的SConscript无需修改
-**面向对象设计**:清晰的类层次结构和职责分离
-**SCons最佳实践**:充分利用SCons的Environment对象
-**可扩展架构**:易于添加新的工具链和项目生成器
-**类型安全**:更好的类型提示和错误处理
## 架构设计
### 核心模块
```
ng/
├── __init__.py # 包初始化
├── core.py # 核心类:BuildContext
├── environment.py # 环境扩展:RTEnv类,注入到SCons Environment
├── config.py # 配置管理:解析rtconfig.h
├── project.py # 项目管理:ProjectGroup和Registry
├── toolchain.py # 工具链抽象:GCC、Keil、IAR等
├── generator.py # 项目生成器:VS Code、CMake等
├── utils.py # 工具函数:路径、版本等
├── adapter.py # 适配器:与building.py集成
└── building_ng.py # 示例:最小化修改的building.py
```
### 类图
```mermaid
classDiagram
class BuildContext {
+root_directory: str
+config_manager: ConfigManager
+project_registry: ProjectRegistry
+toolchain_manager: ToolchainManager
+prepare_environment(env)
+get_dependency(depend): bool
}
class ConfigManager {
+load_from_file(filepath)
+get_dependency(depend): bool
+get_option(name): ConfigOption
}
class ProjectGroup {
+name: str
+sources: List[str]
+dependencies: List[str]
+build(env): List[Object]
}
class Toolchain {
<<abstract>>
+get_name(): str
+detect(): bool
+configure_environment(env)
}
class ProjectGenerator {
<<abstract>>
+generate(context, info): bool
+clean(): bool
}
BuildContext --> ConfigManager
BuildContext --> ProjectRegistry
BuildContext --> ToolchainManager
ProjectRegistry --> ProjectGroup
ToolchainManager --> Toolchain
```
## 使用方法
### 1. 最小化集成(推荐)
在building.py中添加少量代码即可集成新系统:
```python
# 在building.py的开头添加
try:
from ng.adapter import (
init_build_context,
inject_environment_methods,
load_rtconfig as ng_load_rtconfig
)
USE_NG = True
except ImportError:
USE_NG = False
# 在PrepareBuilding函数中添加
def PrepareBuilding(env, root_directory, has_libcpu=False, remove_components=[]):
# ... 原有代码 ...
# 集成新系统
if USE_NG:
context = init_build_context(root_directory)
inject_environment_methods(env)
ng_load_rtconfig('rtconfig.h')
# ... 继续原有代码 ...
```
### 2. 使用新的环境方法
集成后,SCons Environment对象会自动获得新方法:
```python
# 在SConscript中使用新方法
Import('env')
# 使用环境方法(推荐)
src = env.GlobFiles('*.c')
group = env.DefineGroup('MyComponent', src, depend=['RT_USING_XXX'])
# 也可以使用传统方式(保持兼容)
from building import *
group = DefineGroup('MyComponent', src, depend=['RT_USING_XXX'])
```
### 3. 新的项目生成器
新系统提供了改进的项目生成器:
```bash
# 生成VS Code项目
scons --target=vscode
# 生成CMake项目
scons --target=cmake
```
## API参考
### 环境方法
所有方法都被注入到SCons Environment对象中:
#### env.DefineGroup(name, src, depend, **kwargs)
定义一个组件组。
**参数:**
- `name`: 组名称
- `src`: 源文件列表
- `depend`: 依赖条件(字符串或列表)
- `**kwargs`: 额外参数
- `CPPPATH`: 头文件路径
- `CPPDEFINES`: 宏定义
- `CFLAGS`/`CXXFLAGS`: 编译选项
- `LOCAL_CFLAGS`/`LOCAL_CPPPATH`: 仅对当前组有效的选项
- `LIBS`/`LIBPATH`: 库配置
**返回:** 构建对象列表
**示例:**
```python
src = ['driver.c', 'hal.c']
group = env.DefineGroup('Driver',
src,
depend=['RT_USING_DEVICE'],
CPPPATH=[env.GetCurrentDir()],
LOCAL_CFLAGS='-O3'
)
```
#### env.GetDepend(depend)
检查依赖是否满足。
**参数:**
- `depend`: 依赖名称或列表
**返回:** True如果依赖满足
**示例:**
```python
if env.GetDepend('RT_USING_SERIAL'):
src += ['serial.c']
if env.GetDepend(['RT_USING_SERIAL', 'RT_SERIAL_USING_DMA']):
src += ['serial_dma.c']
```
#### env.SrcRemove(src, remove)
从源文件列表中移除文件。
**参数:**
- `src`: 源文件列表(就地修改)
- `remove`: 要移除的文件
**示例:**
```python
src = env.GlobFiles('*.c')
env.SrcRemove(src, ['test.c', 'debug.c'])
```
#### env.BuildPackage(package_path)
从package.json构建软件包。
**参数:**
- `package_path`: package.json路径
**返回:** 构建对象列表
**示例:**
```python
objs = env.BuildPackage('package.json')
```
#### env.GetContext()
获取当前构建上下文。
**返回:** BuildContext实例
**示例:**
```python
context = env.GetContext()
if context:
context.logger.info("Building component...")
```
## 高级特性
### 1. 自定义工具链
创建自定义工具链:
```python
from ng.toolchain import Toolchain
class MyToolchain(Toolchain):
def get_name(self):
return "mycc"
def detect(self):
# 检测工具链
return shutil.which("mycc") is not None
def configure_environment(self, env):
env['CC'] = 'mycc'
env['CFLAGS'] = '-O2 -Wall'
# 注册工具链
context = env.GetContext()
context.toolchain_manager.register_toolchain('mycc', MyToolchain())
```
### 2. 自定义项目生成器
创建自定义项目生成器:
```python
from ng.generator import ProjectGenerator
class MyGenerator(ProjectGenerator):
def get_name(self):
return "myide"
def generate(self, context, project_info):
# 生成项目文件
self._ensure_output_dir()
# ... 生成逻辑 ...
return True
# 注册生成器
context.generator_registry.register('myide', MyGenerator)
```
### 3. 构建钩子
使用构建上下文添加钩子:
```python
context = env.GetContext()
# 添加日志
context.logger.info("Starting build...")
# 访问配置
if context.config_manager.get_option('RT_THREAD_PRIORITY_MAX'):
print("Max priority:", context.config_manager.get_value('RT_THREAD_PRIORITY_MAX'))
# 获取项目信息
info = context.project_registry.get_project_info()
print(f"Total sources: {len(info['all_sources'])}")
```
## 迁移指南
### 从旧版本迁移
1. **无需修改**:现有的SConscript文件无需任何修改即可工作
2. **可选升级**:可以逐步将`DefineGroup`调用改为`env.DefineGroup`
3. **新功能**:可以开始使用新的特性如`env.BuildPackage`
### 最佳实践
1. **使用环境方法**:优先使用`env.DefineGroup`而不是全局函数
2. **类型提示**:在Python 3.5+中使用类型提示
3. **错误处理**:使用context.logger记录错误和警告
4. **路径处理**:使用PathService处理跨平台路径
## 性能优化
新系统包含多项性能优化:
1. **配置缓存**:依赖检查结果会被缓存
2. **延迟加载**:工具链和生成器按需加载
3. **并行支持**:项目生成可以并行执行
## 测试
运行测试套件:
```bash
cd tools/ng
python -m pytest tests/
```
## 贡献
欢迎贡献代码!请遵循以下准则:
1. 保持向后兼容性
2. 添加类型提示
3. 编写单元测试
4. 更新文档
## 路线图
- [ ] 完整的测试覆盖
- [ ] 性能基准测试
- [ ] 插件系统
- [ ] 更多项目生成器(Eclipse、Qt Creator等)
- [ ] 构建缓存系统
- [ ] 分布式构建支持
## 许可证
本项目遵循RT-Thread的Apache License 2.0许可证。
+25
View File
@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
"""
RT-Thread Next Generation Build System
This module provides an object-oriented implementation of the RT-Thread build system
while maintaining backward compatibility with the existing building.py interface.
"""
from .core import BuildContext
from .environment import RTEnv
from .config import ConfigManager
from .project import ProjectRegistry, ProjectGroup
from .toolchain import ToolchainManager
from .generator import GeneratorRegistry
__version__ = "1.0.0"
__all__ = [
'BuildContext',
'RTEnv',
'ConfigManager',
'ProjectRegistry',
'ProjectGroup',
'ToolchainManager',
'GeneratorRegistry'
]
+218
View File
@@ -0,0 +1,218 @@
# -*- coding: utf-8 -*-
"""
Adapter module to integrate new OOP implementation with existing building.py.
This module provides the bridge between the legacy function-based API and the new
object-oriented implementation, ensuring backward compatibility.
"""
import os
from typing import List, Dict, Any, Optional
from .core import BuildContext
from .environment import RTEnv
from .generator import GeneratorConfig, GeneratorRegistry
# Global variables for compatibility
_context: Optional[BuildContext] = None
def init_build_context(root_directory: str) -> BuildContext:
"""
Initialize the build context.
This function should be called early in PrepareBuilding.
Args:
root_directory: RT-Thread root directory
Returns:
BuildContext instance
"""
global _context
_context = BuildContext(root_directory)
return _context
def get_build_context() -> Optional[BuildContext]:
"""Get the current build context."""
return _context
def inject_environment_methods(env) -> None:
"""
Inject RT-Thread methods into SCons Environment.
This should be called in PrepareBuilding after environment setup.
Args:
env: SCons Environment object
"""
RTEnv.inject_methods(env)
# Also set the environment in context
if _context:
_context.prepare_environment(env)
def load_rtconfig(config_file: str = 'rtconfig.h') -> Dict[str, Any]:
"""
Load configuration from rtconfig.h.
Args:
config_file: Configuration file name
Returns:
Dictionary of build options
"""
if _context:
_context.load_configuration(config_file)
return _context.build_options
return {}
def DefineGroup(name: str, src: List[str], depend: Any = None, **kwargs) -> List:
"""
Legacy DefineGroup function for backward compatibility.
This function delegates to the environment method.
Args:
name: Group name
src: Source files
depend: Dependencies
**kwargs: Additional parameters
Returns:
List of build objects
"""
if _context and _context.environment:
return _context.environment.DefineGroup(name, src, depend, **kwargs)
else:
# Fallback behavior
print(f"Warning: DefineGroup called before environment setup for group '{name}'")
return []
def GetDepend(depend: Any) -> bool:
"""
Legacy GetDepend function for backward compatibility.
Args:
depend: Dependency to check
Returns:
True if dependency is satisfied
"""
if _context:
return _context.get_dependency(depend)
return False
def GetCurrentDir() -> str:
"""
Get current directory.
Returns:
Current directory path
"""
return os.path.abspath('.')
def SrcRemove(src: List[str], remove: List[str]) -> None:
"""
Remove files from source list.
Args:
src: Source list (modified in place)
remove: Files to remove
"""
if not isinstance(remove, list):
remove = [remove]
for item in remove:
if item in src:
src.remove(item)
def GetBuildOptions() -> Dict[str, Any]:
"""
Get build options.
Returns:
Dictionary of build options
"""
if _context:
return _context.build_options
return {}
def MergeGroups() -> List:
"""
Merge all registered groups.
Returns:
List of all build objects
"""
if _context:
return _context.merge_groups()
return []
def GenerateProject(target: str, env, projects: List) -> None:
"""
Generate IDE project files.
Args:
target: Target type (mdk5, iar, vscode, etc.)
env: SCons Environment
projects: Project list
"""
if not _context:
print("Error: Build context not initialized")
return
# Get project info from registry
project_info = _context.project_registry.get_project_info()
# Create generator config
config = GeneratorConfig(
output_dir=os.getcwd(),
project_name=os.path.basename(os.getcwd()),
target_name="rtthread.elf"
)
# Create and run generator
try:
generator = _context.generator_registry.create_generator(target, config)
if generator.generate(_context, project_info):
print(f"Successfully generated {target} project files")
else:
print(f"Failed to generate {target} project files")
except Exception as e:
print(f"Error generating {target} project: {e}")
def PrepareModuleBuilding(env, root_directory, bsp_directory) -> None:
"""
Prepare for building a module.
This is a simplified version of PrepareBuilding for module compilation.
Args:
env: SCons Environment
root_directory: RT-Thread root directory
bsp_directory: BSP directory
"""
# Initialize context
context = init_build_context(root_directory)
context.bsp_directory = bsp_directory
# Inject methods
inject_environment_methods(env)
# Load configuration
config_path = os.path.join(bsp_directory, 'rtconfig.h')
if os.path.exists(config_path):
load_rtconfig(config_path)
+115
View File
@@ -0,0 +1,115 @@
# -*- coding: utf-8 -*-
"""
Next Generation building.py with minimal modifications.
This file shows how to integrate the new OOP system with minimal changes to building.py.
The actual implementation would modify the original building.py file.
"""
# Import everything from original building.py
import sys
import os
# Add parent directory to path to import original building
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from building import *
# Import new OOP modules
from ng.adapter import (
init_build_context,
inject_environment_methods,
load_rtconfig as ng_load_rtconfig,
GenerateProject as ng_GenerateProject
)
# Override PrepareBuilding to integrate new system
_original_PrepareBuilding = PrepareBuilding
def PrepareBuilding(env, root_directory, has_libcpu=False, remove_components=[]):
"""
Enhanced PrepareBuilding that integrates the new OOP system.
This function wraps the original PrepareBuilding and adds OOP functionality.
"""
# Initialize new build context
context = init_build_context(root_directory)
# Call original PrepareBuilding
result = _original_PrepareBuilding(env, root_directory, has_libcpu, remove_components)
# Inject new methods into environment
inject_environment_methods(env)
# Load configuration into new system
ng_load_rtconfig('rtconfig.h')
# Store context in environment for access
env['_BuildContext'] = context
return result
# Override DefineGroup to use new implementation
_original_DefineGroup = DefineGroup
def DefineGroup(name, src, depend, **parameters):
"""
Enhanced DefineGroup that uses the new OOP implementation.
This maintains backward compatibility while using the new system internally.
"""
# Get environment from global Env
global Env
if Env and hasattr(Env, 'DefineGroup'):
# Use new method if available
return Env.DefineGroup(name, src, depend, **parameters)
else:
# Fallback to original
return _original_DefineGroup(name, src, depend, **parameters)
# Override GetDepend to use new implementation
_original_GetDepend = GetDepend
def GetDepend(depend):
"""
Enhanced GetDepend that uses the new OOP implementation.
"""
global Env
if Env and hasattr(Env, 'GetDepend'):
# Use new method if available
return Env.GetDepend(depend)
else:
# Fallback to original
return _original_GetDepend(depend)
# Override DoBuilding to integrate project generation
_original_DoBuilding = DoBuilding
def DoBuilding(target, objects):
"""
Enhanced DoBuilding that integrates new project generation.
"""
# Call original DoBuilding
_original_DoBuilding(target, objects)
# Handle project generation with new system
if GetOption('target'):
target_name = GetOption('target')
global Env, Projects
# Use new generator if available
try:
ng_GenerateProject(target_name, Env, Projects)
except Exception as e:
print(f"Falling back to original generator: {e}")
# Call original GenTargetProject
from building import GenTargetProject
GenTargetProject(Projects, program=target)
# Export enhanced functions
__all__ = ['PrepareBuilding', 'DefineGroup', 'GetDepend', 'DoBuilding'] + \
[name for name in dir(sys.modules['building']) if not name.startswith('_')]
+297
View File
@@ -0,0 +1,297 @@
# -*- coding: utf-8 -*-
"""
Configuration management for RT-Thread build system.
This module handles parsing and managing configuration from rtconfig.h files.
"""
import re
import os
from typing import Dict, List, Any, Optional, Union
from dataclasses import dataclass
from enum import Enum
class ConfigType(Enum):
"""Configuration value types."""
BOOLEAN = "boolean"
INTEGER = "integer"
STRING = "string"
UNDEFINED = "undefined"
@dataclass
class ConfigOption:
"""Configuration option with metadata."""
name: str
value: Any
type: ConfigType
line_number: int = 0
comment: str = ""
def as_bool(self) -> bool:
"""Get value as boolean."""
if self.type == ConfigType.BOOLEAN:
return bool(self.value)
elif self.type == ConfigType.INTEGER:
return self.value != 0
elif self.type == ConfigType.STRING:
return bool(self.value)
return False
def as_int(self) -> int:
"""Get value as integer."""
if self.type == ConfigType.INTEGER:
return self.value
elif self.type == ConfigType.BOOLEAN:
return 1 if self.value else 0
elif self.type == ConfigType.STRING:
try:
return int(self.value)
except ValueError:
return 0
return 0
def as_str(self) -> str:
"""Get value as string."""
if self.type == ConfigType.STRING:
return self.value
return str(self.value)
class ConfigParser:
"""Parser for rtconfig.h files."""
# Regular expressions for parsing
RE_DEFINE = re.compile(r'^\s*#\s*define\s+(\w+)(?:\s+(.*))?', re.MULTILINE)
RE_UNDEF = re.compile(r'^\s*#\s*undef\s+(\w+)', re.MULTILINE)
RE_IFDEF = re.compile(r'^\s*#\s*ifdef\s+(\w+)', re.MULTILINE)
RE_IFNDEF = re.compile(r'^\s*#\s*ifndef\s+(\w+)', re.MULTILINE)
RE_ENDIF = re.compile(r'^\s*#\s*endif', re.MULTILINE)
RE_COMMENT = re.compile(r'/\*.*?\*/', re.DOTALL)
RE_LINE_COMMENT = re.compile(r'//.*$', re.MULTILINE)
def __init__(self):
self.options: Dict[str, ConfigOption] = {}
self.conditions: List[str] = []
def parse_file(self, filepath: str) -> Dict[str, ConfigOption]:
"""
Parse configuration file.
Args:
filepath: Path to rtconfig.h
Returns:
Dictionary of configuration options
"""
if not os.path.exists(filepath):
raise FileNotFoundError(f"Configuration file not found: {filepath}")
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
return self.parse_content(content)
def parse_content(self, content: str) -> Dict[str, ConfigOption]:
"""
Parse configuration content.
Args:
content: File content
Returns:
Dictionary of configuration options
"""
# Remove comments
content = self.RE_COMMENT.sub('', content)
content = self.RE_LINE_COMMENT.sub('', content)
# Parse line by line
lines = content.split('\n')
for i, line in enumerate(lines):
self._parse_line(line, i + 1)
return self.options
def _parse_line(self, line: str, line_number: int) -> None:
"""Parse a single line."""
# Check for #define
match = self.RE_DEFINE.match(line)
if match:
name = match.group(1)
value = match.group(2) if match.group(2) else '1'
# Parse value
parsed_value, value_type = self._parse_value(value.strip())
# Create option
option = ConfigOption(
name=name,
value=parsed_value,
type=value_type,
line_number=line_number
)
self.options[name] = option
return
# Check for #undef
match = self.RE_UNDEF.match(line)
if match:
name = match.group(1)
if name in self.options:
del self.options[name]
return
def _parse_value(self, value: str) -> tuple:
"""
Parse configuration value.
Returns:
Tuple of (parsed_value, ConfigType)
"""
if not value or value == '1':
return (True, ConfigType.BOOLEAN)
# Try integer
try:
return (int(value, 0), ConfigType.INTEGER) # Support hex/octal
except ValueError:
pass
# Try string (remove quotes)
if value.startswith('"') and value.endswith('"'):
return (value[1:-1], ConfigType.STRING)
# Default to string
return (value, ConfigType.STRING)
class ConfigManager:
"""
Configuration manager for build system.
This class manages configuration options and provides dependency checking.
"""
def __init__(self):
self.parser = ConfigParser()
self.options: Dict[str, ConfigOption] = {}
self.cache: Dict[str, bool] = {}
def load_from_file(self, filepath: str) -> None:
"""
Load configuration from file.
Args:
filepath: Path to rtconfig.h
"""
self.options = self.parser.parse_file(filepath)
self.cache.clear() # Clear dependency cache
def get_option(self, name: str) -> Optional[ConfigOption]:
"""
Get configuration option.
Args:
name: Option name
Returns:
ConfigOption or None
"""
return self.options.get(name)
def get_value(self, name: str, default: Any = None) -> Any:
"""
Get configuration value.
Args:
name: Option name
default: Default value if not found
Returns:
Configuration value
"""
option = self.options.get(name)
if option:
return option.value
return default
def get_dependency(self, depend: Union[str, List[str]]) -> bool:
"""
Check if dependency is satisfied.
Args:
depend: Single dependency or list of dependencies
Returns:
True if all dependencies are satisfied
"""
# Handle empty dependency
if not depend:
return True
# Convert to list
if isinstance(depend, str):
depend = [depend]
# Check cache
cache_key = ','.join(sorted(depend))
if cache_key in self.cache:
return self.cache[cache_key]
# Check all dependencies (AND logic)
result = all(self._check_single_dependency(d) for d in depend)
# Cache result
self.cache[cache_key] = result
return result
def _check_single_dependency(self, name: str) -> bool:
"""Check a single dependency."""
option = self.options.get(name)
if not option:
return False
# For RT-Thread, any defined macro is considered True
# except if explicitly set to 0
if option.type == ConfigType.INTEGER:
return option.value != 0
elif option.type == ConfigType.BOOLEAN:
return option.value
elif option.type == ConfigType.STRING:
return bool(option.value)
return True
def get_all_options(self) -> Dict[str, Any]:
"""
Get all configuration options as a simple dictionary.
Returns:
Dictionary of option names to values
"""
return {name: opt.value for name, opt in self.options.items()}
def validate(self) -> List[str]:
"""
Validate configuration.
Returns:
List of validation errors
"""
errors = []
# Check for common issues
if 'RT_NAME_MAX' in self.options:
name_max = self.options['RT_NAME_MAX'].as_int()
if name_max < 4:
errors.append("RT_NAME_MAX should be at least 4")
if 'RT_THREAD_PRIORITY_MAX' in self.options:
prio_max = self.options['RT_THREAD_PRIORITY_MAX'].as_int()
if prio_max not in [8, 32, 256]:
errors.append("RT_THREAD_PRIORITY_MAX should be 8, 32, or 256")
return errors
+176
View File
@@ -0,0 +1,176 @@
# -*- coding: utf-8 -*-
"""
Core module for RT-Thread build system.
This module provides the central BuildContext class that manages the build state
and coordinates between different components.
"""
import os
import logging
from typing import Dict, List, Optional, Any
from dataclasses import dataclass, field
from .config import ConfigManager
from .project import ProjectRegistry
from .toolchain import ToolchainManager
from .generator import GeneratorRegistry
from .utils import PathService
class BuildContext:
"""
Central build context that manages all build-related state.
This class replaces the global variables in building.py with a proper
object-oriented design while maintaining compatibility.
"""
# Class variable to store the current context (for backward compatibility)
_current_context: Optional['BuildContext'] = None
def __init__(self, root_directory: str):
"""
Initialize build context.
Args:
root_directory: RT-Thread root directory path
"""
self.root_directory = os.path.abspath(root_directory)
self.bsp_directory = os.getcwd()
# Initialize managers
self.config_manager = ConfigManager()
self.project_registry = ProjectRegistry()
self.toolchain_manager = ToolchainManager()
self.generator_registry = GeneratorRegistry()
self.path_service = PathService(self.bsp_directory)
# Build environment
self.environment = None
self.build_options = {}
# Logging
self.logger = self._setup_logger()
# Set as current context
BuildContext._current_context = self
@classmethod
def get_current(cls) -> Optional['BuildContext']:
"""Get the current build context."""
return cls._current_context
@classmethod
def set_current(cls, context: Optional['BuildContext']) -> None:
"""Set the current build context."""
cls._current_context = context
def _setup_logger(self) -> logging.Logger:
"""Setup logger for build system."""
logger = logging.getLogger('rtthread.build')
if not logger.handlers:
handler = logging.StreamHandler()
formatter = logging.Formatter('[%(levelname)s] %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.INFO)
return logger
def prepare_environment(self, env) -> None:
"""
Prepare the build environment.
Args:
env: SCons Environment object
"""
self.environment = env
# Set environment variables
env['RTT_ROOT'] = self.root_directory
env['BSP_ROOT'] = self.bsp_directory
# Add to Python path
import sys
tools_path = os.path.join(self.root_directory, 'tools')
if tools_path not in sys.path:
sys.path.insert(0, tools_path)
self.logger.debug(f"Prepared environment with RTT_ROOT={self.root_directory}")
def load_configuration(self, config_file: str = 'rtconfig.h') -> None:
"""
Load configuration from rtconfig.h.
Args:
config_file: Path to configuration file
"""
config_path = os.path.join(self.bsp_directory, config_file)
if os.path.exists(config_path):
self.config_manager.load_from_file(config_path)
self.build_options = self.config_manager.get_all_options()
self.logger.info(f"Loaded configuration from {config_file}")
else:
self.logger.warning(f"Configuration file {config_file} not found")
def get_dependency(self, depend: Any) -> bool:
"""
Check if dependency is satisfied.
Args:
depend: Dependency name or list of names
Returns:
True if dependency is satisfied
"""
return self.config_manager.get_dependency(depend)
def register_project_group(self, group) -> None:
"""
Register a project group.
Args:
group: ProjectGroup instance
"""
self.project_registry.register_group(group)
def merge_groups(self) -> List:
"""
Merge all registered project groups.
Returns:
List of build objects
"""
return self.project_registry.merge_groups(self.environment)
@dataclass
class BuildOptions:
"""Build options container."""
verbose: bool = False
strict: bool = False
target: Optional[str] = None
jobs: int = 1
clean: bool = False
@dataclass
class ProjectInfo:
"""Project information for generators."""
name: str = "rtthread"
target_name: str = "rtthread.elf"
# File collections
source_files: List[str] = field(default_factory=list)
include_paths: List[str] = field(default_factory=list)
defines: Dict[str, str] = field(default_factory=dict)
# Compiler options
cflags: str = ""
cxxflags: str = ""
asflags: str = ""
ldflags: str = ""
# Libraries
libs: List[str] = field(default_factory=list)
lib_paths: List[str] = field(default_factory=list)
+298
View File
@@ -0,0 +1,298 @@
# -*- coding: utf-8 -*-
"""
Environment extensions for RT-Thread build system.
This module provides methods that are injected into the SCons Environment object
to provide RT-Thread-specific functionality.
"""
import os
from typing import List, Union, Dict, Any, Optional
from SCons.Script import *
from .core import BuildContext
from .project import ProjectGroup
class RTEnv:
"""
RT-Thread environment extensions (RTEnv).
This class provides methods that are added to the SCons Environment object.
"""
@staticmethod
def inject_methods(env):
"""
Inject RT-Thread methods into SCons Environment.
Args:
env: SCons Environment object
"""
# Core build methods
env.AddMethod(RTEnv.DefineGroup, 'DefineGroup')
env.AddMethod(RTEnv.GetDepend, 'GetDepend')
env.AddMethod(RTEnv.SrcRemove, 'SrcRemove')
env.AddMethod(RTEnv.GetCurrentDir, 'GetCurrentDir')
env.AddMethod(RTEnv.BuildPackage, 'BuildPackage')
# Utility methods
env.AddMethod(RTEnv.Glob, 'GlobFiles')
env.AddMethod(RTEnv.GetBuildOptions, 'GetBuildOptions')
env.AddMethod(RTEnv.GetContext, 'GetContext')
# Path utilities
env.AddMethod(RTEnv.GetRTTRoot, 'GetRTTRoot')
env.AddMethod(RTEnv.GetBSPRoot, 'GetBSPRoot')
@staticmethod
def DefineGroup(env, name: str, src: List[str], depend: Any = None, **kwargs) -> List:
"""
Define a component group.
This method maintains compatibility with the original DefineGroup function
while using the new object-oriented implementation.
Args:
env: SCons Environment
name: Group name
src: Source file list
depend: Dependency conditions
**kwargs: Additional parameters (CPPPATH, CPPDEFINES, etc.)
Returns:
List of build objects
"""
context = BuildContext.get_current()
if not context:
raise RuntimeError("BuildContext not initialized")
# Check dependencies
if depend and not env.GetDepend(depend):
return []
# Process source files
if isinstance(src, str):
src = [src]
# Create project group
group = ProjectGroup(
name=name,
sources=src,
dependencies=depend if isinstance(depend, list) else [depend] if depend else [],
environment=env
)
# Process parameters
group.include_paths = kwargs.get('CPPPATH', [])
group.defines = kwargs.get('CPPDEFINES', {})
group.cflags = kwargs.get('CFLAGS', '')
group.cxxflags = kwargs.get('CXXFLAGS', '')
group.local_cflags = kwargs.get('LOCAL_CFLAGS', '')
group.local_cxxflags = kwargs.get('LOCAL_CXXFLAGS', '')
group.local_include_paths = kwargs.get('LOCAL_CPPPATH', [])
group.local_defines = kwargs.get('LOCAL_CPPDEFINES', {})
group.libs = kwargs.get('LIBS', [])
group.lib_paths = kwargs.get('LIBPATH', [])
# Build objects
objects = group.build(env)
# Register group
context.register_project_group(group)
return objects
@staticmethod
def GetDepend(env, depend: Any) -> bool:
"""
Check if dependency is satisfied.
Args:
env: SCons Environment
depend: Dependency name or list of names
Returns:
True if dependency is satisfied
"""
context = BuildContext.get_current()
if not context:
# Fallback to checking environment variables
if isinstance(depend, str):
return env.get(depend, False)
elif isinstance(depend, list):
return all(env.get(d, False) for d in depend)
return False
return context.get_dependency(depend)
@staticmethod
def SrcRemove(env, src: List[str], remove: List[str]) -> None:
"""
Remove files from source list.
Args:
env: SCons Environment
src: Source file list (modified in place)
remove: Files to remove
"""
if not isinstance(remove, list):
remove = [remove]
for item in remove:
# Handle both exact matches and pattern matches
if item in src:
src.remove(item)
else:
# Try pattern matching
import fnmatch
to_remove = [f for f in src if fnmatch.fnmatch(f, item)]
for f in to_remove:
src.remove(f)
@staticmethod
def GetCurrentDir(env) -> str:
"""
Get current directory.
Args:
env: SCons Environment
Returns:
Current directory path
"""
return Dir('.').abspath
@staticmethod
def BuildPackage(env, package_path: str = None) -> List:
"""
Build package from package.json.
Args:
env: SCons Environment
package_path: Path to package.json. If None, looks for package.json in current directory.
Returns:
List of build objects
"""
# Import the existing package module
import sys
import os
# Get the building module path
building_path = os.path.dirname(os.path.abspath(__file__))
tools_path = os.path.dirname(building_path)
# Add to path if not already there
if tools_path not in sys.path:
sys.path.insert(0, tools_path)
# Import and use the existing BuildPackage
try:
from package import BuildPackage as build_package_func
# BuildPackage uses global functions, so we need to set up the context
# Save current directory
current_dir = os.getcwd()
# Change to the directory where we want to build
if package_path is None:
work_dir = env.GetCurrentDir()
elif os.path.isdir(package_path):
work_dir = package_path
else:
work_dir = os.path.dirname(package_path)
os.chdir(work_dir)
try:
# Call the original BuildPackage
result = build_package_func(package_path)
finally:
# Restore directory
os.chdir(current_dir)
return result
except ImportError:
# Fallback if import fails
context = BuildContext.get_current()
if context:
context.logger.error("Failed to import package module")
return []
@staticmethod
def Glob(env, pattern: str) -> List[str]:
"""
Enhanced glob with better error handling.
Args:
env: SCons Environment
pattern: File pattern
Returns:
List of matching files
"""
try:
files = Glob(pattern, strings=True)
return sorted(files) # Sort for consistent ordering
except Exception as e:
context = BuildContext.get_current()
if context:
context.logger.warning(f"Glob pattern '{pattern}' failed: {e}")
return []
@staticmethod
def GetBuildOptions(env) -> Dict[str, Any]:
"""
Get build options.
Args:
env: SCons Environment
Returns:
Dictionary of build options
"""
context = BuildContext.get_current()
if context:
return context.build_options
return {}
@staticmethod
def GetContext(env) -> Optional[BuildContext]:
"""
Get current build context.
Args:
env: SCons Environment
Returns:
BuildContext instance or None
"""
return BuildContext.get_current()
@staticmethod
def GetRTTRoot(env) -> str:
"""
Get RT-Thread root directory.
Args:
env: SCons Environment
Returns:
RT-Thread root path
"""
return env.get('RTT_ROOT', '')
@staticmethod
def GetBSPRoot(env) -> str:
"""
Get BSP root directory.
Args:
env: SCons Environment
Returns:
BSP root path
"""
return env.get('BSP_ROOT', '')
+368
View File
@@ -0,0 +1,368 @@
# -*- coding: utf-8 -*-
"""
Project generator framework for RT-Thread build system.
This module provides the base classes for project generators (MDK, IAR, VS Code, etc.).
"""
import os
import shutil
import json
import xml.etree.ElementTree as ET
from abc import ABC, abstractmethod
from typing import Dict, List, Any, Optional
from dataclasses import dataclass
from .utils import PathService
@dataclass
class GeneratorConfig:
"""Configuration for project generators."""
output_dir: str
project_name: str = "rtthread"
target_name: str = "rtthread.elf"
class ProjectGenerator(ABC):
"""Abstract base class for project generators."""
def __init__(self, config: GeneratorConfig):
self.config = config
self.path_service = PathService(os.getcwd())
@abstractmethod
def get_name(self) -> str:
"""Get generator name."""
pass
@abstractmethod
def generate(self, context, project_info: Dict[str, Any]) -> bool:
"""
Generate project files.
Args:
context: BuildContext instance
project_info: Project information from registry
Returns:
True if successful
"""
pass
@abstractmethod
def clean(self) -> bool:
"""
Clean generated files.
Returns:
True if successful
"""
pass
def _ensure_output_dir(self) -> None:
"""Ensure output directory exists."""
os.makedirs(self.config.output_dir, exist_ok=True)
def _copy_template(self, template_name: str, output_name: str = None) -> str:
"""
Copy template file to output directory.
Args:
template_name: Template file name
output_name: Output file name (defaults to template_name)
Returns:
Output file path
"""
if output_name is None:
output_name = template_name
template_dir = os.path.join(os.path.dirname(__file__), '..', 'targets')
template_path = os.path.join(template_dir, template_name)
output_path = os.path.join(self.config.output_dir, output_name)
if os.path.exists(template_path):
shutil.copy2(template_path, output_path)
return output_path
else:
raise FileNotFoundError(f"Template not found: {template_path}")
class VscodeGenerator(ProjectGenerator):
"""Visual Studio Code project generator."""
def get_name(self) -> str:
return "vscode"
def generate(self, context, project_info: Dict[str, Any]) -> bool:
"""Generate VS Code project files."""
self._ensure_output_dir()
# Create .vscode directory
vscode_dir = os.path.join(self.config.output_dir, '.vscode')
os.makedirs(vscode_dir, exist_ok=True)
# Generate c_cpp_properties.json
self._generate_cpp_properties(vscode_dir, context, project_info)
# Generate tasks.json
self._generate_tasks(vscode_dir, context)
# Generate launch.json
self._generate_launch(vscode_dir, context)
# Generate settings.json
self._generate_settings(vscode_dir)
return True
def clean(self) -> bool:
"""Clean VS Code files."""
vscode_dir = os.path.join(self.config.output_dir, '.vscode')
if os.path.exists(vscode_dir):
shutil.rmtree(vscode_dir)
return True
def _generate_cpp_properties(self, vscode_dir: str, context, project_info: Dict) -> None:
"""Generate c_cpp_properties.json."""
# Get toolchain info
toolchain = context.toolchain_manager.get_current()
compiler_path = ""
if toolchain and toolchain.info:
if toolchain.get_name() == "gcc":
compiler_path = os.path.join(toolchain.info.path, toolchain.info.prefix + "gcc")
config = {
"configurations": [
{
"name": "RT-Thread",
"includePath": [
"${workspaceFolder}/**"
] + project_info.get('all_includes', []),
"defines": [f"{k}={v}" if v != '1' else k
for k, v in project_info.get('all_defines', {}).items()],
"compilerPath": compiler_path,
"cStandard": "c99",
"cppStandard": "c++11",
"intelliSenseMode": "gcc-arm" if "arm" in compiler_path else "gcc-x64"
}
],
"version": 4
}
output_path = os.path.join(vscode_dir, 'c_cpp_properties.json')
with open(output_path, 'w') as f:
json.dump(config, f, indent=4)
def _generate_tasks(self, vscode_dir: str, context) -> None:
"""Generate tasks.json."""
tasks = {
"version": "2.0.0",
"tasks": [
{
"label": "build",
"type": "shell",
"command": "scons",
"problemMatcher": "$gcc",
"group": {
"kind": "build",
"isDefault": True
}
},
{
"label": "clean",
"type": "shell",
"command": "scons -c",
"problemMatcher": "$gcc"
},
{
"label": "rebuild",
"type": "shell",
"command": "scons -c && scons",
"problemMatcher": "$gcc"
}
]
}
output_path = os.path.join(vscode_dir, 'tasks.json')
with open(output_path, 'w') as f:
json.dump(tasks, f, indent=4)
def _generate_launch(self, vscode_dir: str, context) -> None:
"""Generate launch.json."""
launch = {
"version": "0.2.0",
"configurations": [
{
"name": "Cortex Debug",
"type": "cortex-debug",
"request": "launch",
"servertype": "openocd",
"cwd": "${workspaceRoot}",
"executable": "${workspaceRoot}/" + self.config.target_name,
"device": "STM32F103C8",
"configFiles": [
"interface/stlink-v2.cfg",
"target/stm32f1x.cfg"
]
}
]
}
output_path = os.path.join(vscode_dir, 'launch.json')
with open(output_path, 'w') as f:
json.dump(launch, f, indent=4)
def _generate_settings(self, vscode_dir: str) -> None:
"""Generate settings.json."""
settings = {
"files.associations": {
"*.h": "c",
"*.c": "c",
"*.cpp": "cpp",
"*.cc": "cpp",
"*.cxx": "cpp"
},
"C_Cpp.errorSquiggles": "Enabled"
}
output_path = os.path.join(vscode_dir, 'settings.json')
with open(output_path, 'w') as f:
json.dump(settings, f, indent=4)
class CMakeGenerator(ProjectGenerator):
"""CMake project generator."""
def get_name(self) -> str:
return "cmake"
def generate(self, context, project_info: Dict[str, Any]) -> bool:
"""Generate CMakeLists.txt."""
self._ensure_output_dir()
# Get toolchain info
toolchain = context.toolchain_manager.get_current()
lines = [
"cmake_minimum_required(VERSION 3.10)",
"",
"# RT-Thread CMake Project",
f"project({self.config.project_name} C CXX ASM)",
"",
"# C Standard",
"set(CMAKE_C_STANDARD 99)",
"set(CMAKE_CXX_STANDARD 11)",
""
]
# Toolchain configuration
if toolchain and toolchain.get_name() == "gcc":
lines.extend([
"# Toolchain",
f"set(CMAKE_C_COMPILER {toolchain.info.prefix}gcc)",
f"set(CMAKE_CXX_COMPILER {toolchain.info.prefix}g++)",
f"set(CMAKE_ASM_COMPILER {toolchain.info.prefix}gcc)",
""
])
# Include directories
lines.extend([
"# Include directories",
"include_directories("
])
for inc in project_info.get('all_includes', []):
lines.append(f" {inc}")
lines.extend([")", ""])
# Definitions
lines.extend([
"# Definitions",
"add_definitions("
])
for k, v in project_info.get('all_defines', {}).items():
if v == '1':
lines.append(f" -D{k}")
else:
lines.append(f" -D{k}={v}")
lines.extend([")", ""])
# Source files
lines.extend([
"# Source files",
"set(SOURCES"
])
for src in project_info.get('all_sources', []):
lines.append(f" {src}")
lines.extend([")", ""])
# Executable
lines.extend([
"# Executable",
f"add_executable(${{PROJECT_NAME}} ${{SOURCES}})",
""
])
# Libraries
if project_info.get('all_libs'):
lines.extend([
"# Libraries",
f"target_link_libraries(${{PROJECT_NAME}}"
])
for lib in project_info['all_libs']:
lines.append(f" {lib}")
lines.extend([")", ""])
# Write file
output_path = os.path.join(self.config.output_dir, 'CMakeLists.txt')
with open(output_path, 'w') as f:
f.write('\n'.join(lines))
return True
def clean(self) -> bool:
"""Clean CMake files."""
files_to_remove = ['CMakeLists.txt', 'CMakeCache.txt']
dirs_to_remove = ['CMakeFiles']
for file in files_to_remove:
file_path = os.path.join(self.config.output_dir, file)
if os.path.exists(file_path):
os.remove(file_path)
for dir in dirs_to_remove:
dir_path = os.path.join(self.config.output_dir, dir)
if os.path.exists(dir_path):
shutil.rmtree(dir_path)
return True
class GeneratorRegistry:
"""Registry for project generators."""
def __init__(self):
self.generators: Dict[str, type] = {}
self._register_default_generators()
def _register_default_generators(self) -> None:
"""Register default generators."""
self.register("vscode", VscodeGenerator)
self.register("vsc", VscodeGenerator) # Alias
self.register("cmake", CMakeGenerator)
def register(self, name: str, generator_class: type) -> None:
"""Register a generator class."""
self.generators[name] = generator_class
def create_generator(self, name: str, config: GeneratorConfig) -> ProjectGenerator:
"""Create a generator instance."""
if name not in self.generators:
raise ValueError(f"Unknown generator: {name}")
return self.generators[name](config)
def list_generators(self) -> List[str]:
"""List available generators."""
return list(self.generators.keys())
+178
View File
@@ -0,0 +1,178 @@
# -*- coding: utf-8 -*-
"""
Example of minimal changes needed in building.py to integrate the new OOP system.
This file shows the exact changes that would be made to the original building.py.
"""
# =============================================================================
# CHANGES TO ADD AT THE BEGINNING OF building.py
# =============================================================================
"""
# Add after the imports section in building.py (around line 45)
# Try to import new OOP system
try:
from ng.adapter import (
init_build_context,
inject_environment_methods,
load_rtconfig as ng_load_rtconfig,
MergeGroups as ng_MergeGroups
)
NG_AVAILABLE = True
except ImportError:
NG_AVAILABLE = False
"""
# =============================================================================
# CHANGES IN PrepareBuilding FUNCTION
# =============================================================================
"""
# Add these lines in PrepareBuilding function after setting up Env (around line 70)
# Initialize new OOP system if available
if NG_AVAILABLE:
# Initialize build context
ng_context = init_build_context(Rtt_Root)
# Inject methods into environment
inject_environment_methods(Env)
# Store context reference
Env['__NG_Context'] = ng_context
"""
# =============================================================================
# CHANGES AFTER PARSING rtconfig.h
# =============================================================================
"""
# Add after parsing rtconfig.h (around line 430)
# Load configuration into new system
if NG_AVAILABLE and 'rtconfig.h' in os.listdir(Bsp_Root):
ng_load_rtconfig('rtconfig.h')
"""
# =============================================================================
# ENHANCED DefineGroup FUNCTION
# =============================================================================
"""
# Replace the original DefineGroup function (around line 565) with:
def DefineGroup(name, src, depend, **parameters):
global Env
if Env is None:
return []
# Try to use new implementation if available
if NG_AVAILABLE and hasattr(Env, 'DefineGroup'):
return Env.DefineGroup(name, src, depend, **parameters)
# Original implementation continues below...
# [Keep all the original DefineGroup code here]
"""
# =============================================================================
# ENHANCED GetDepend FUNCTION
# =============================================================================
"""
# Replace the original GetDepend function (around line 655) with:
def GetDepend(depend):
global Env
# Try to use new implementation if available
if NG_AVAILABLE and Env and hasattr(Env, 'GetDepend'):
return Env.GetDepend(depend)
# Original implementation continues below...
# [Keep all the original GetDepend code here]
"""
# =============================================================================
# ENHANCED MergeGroup FUNCTION
# =============================================================================
"""
# Replace the original MergeGroup function (around line 700) with:
def MergeGroup(src_group, group):
# Try to use new implementation if available
if NG_AVAILABLE and Env and hasattr(Env, '__NG_Context'):
context = Env['__NG_Context']
if context:
# Register groups with new system
from ng.project import ProjectGroup
for g in group:
if 'name' in g:
pg = ProjectGroup(
name=g['name'],
sources=g.get('src', []),
dependencies=[],
environment=Env
)
context.register_project_group(pg)
# Original implementation continues below...
# [Keep all the original MergeGroup code here]
"""
# =============================================================================
# EXAMPLE USAGE IN SCONSCRIPT
# =============================================================================
def example_sconscript():
"""
Example of how to use the new features in a SConscript file.
"""
sconscript_content = '''
from building import *
# Get environment
env = GetEnvironment()
# Method 1: Use new environment methods (if available)
if hasattr(env, 'DefineGroup'):
# New OOP style
src = env.GlobFiles('*.c')
group = env.DefineGroup('MyComponent', src, depend=['RT_USING_XXX'])
else:
# Fallback to traditional style
src = Glob('*.c')
group = DefineGroup('MyComponent', src, depend=['RT_USING_XXX'])
# Method 2: Always compatible style
src = Glob('*.c')
group = DefineGroup('MyComponent', src, depend=['RT_USING_XXX'])
Return('group')
'''
return sconscript_content
# =============================================================================
# MINIMAL CHANGES SUMMARY
# =============================================================================
"""
Summary of changes needed in building.py:
1. Add imports at the beginning (5 lines)
2. Add initialization in PrepareBuilding (6 lines)
3. Add config loading after rtconfig.h parsing (3 lines)
4. Modify DefineGroup to check for new method (3 lines)
5. Modify GetDepend to check for new method (3 lines)
6. Enhance MergeGroup to register with new system (15 lines)
Total: ~35 lines of code added/modified in building.py
Benefits:
- Fully backward compatible
- Opt-in design (works even if ng module is not present)
- Gradual migration path
- No changes needed in existing SConscript files
"""
+260
View File
@@ -0,0 +1,260 @@
# -*- coding: utf-8 -*-
"""
Project and group management for RT-Thread build system.
This module provides classes for managing project groups and their compilation.
"""
import os
from typing import List, Dict, Any, Optional
from dataclasses import dataclass, field
from SCons.Script import *
@dataclass
class ProjectGroup:
"""
Represents a project group (component).
This class encapsulates the information from DefineGroup calls.
"""
name: str
sources: List[str]
dependencies: List[str] = field(default_factory=list)
environment: Any = None # SCons Environment
# Paths and defines
include_paths: List[str] = field(default_factory=list)
defines: Dict[str, str] = field(default_factory=dict)
# Compiler flags
cflags: str = ""
cxxflags: str = ""
asflags: str = ""
ldflags: str = ""
# Local options (only for this group)
local_cflags: str = ""
local_cxxflags: str = ""
local_include_paths: List[str] = field(default_factory=list)
local_defines: Dict[str, str] = field(default_factory=dict)
# Libraries
libs: List[str] = field(default_factory=list)
lib_paths: List[str] = field(default_factory=list)
# Build objects
objects: List[Any] = field(default_factory=list)
def build(self, env) -> List:
"""
Build the group and return objects.
Args:
env: SCons Environment
Returns:
List of build objects
"""
if not self.sources:
return []
# Clone environment if we have local options
build_env = env
if self._has_local_options():
build_env = env.Clone()
self._apply_local_options(build_env)
# Apply global options
self._apply_global_options(build_env)
# Build objects
self.objects = []
for src in self.sources:
if isinstance(src, str):
# Build single file
obj = build_env.Object(src)
self.objects.extend(obj if isinstance(obj, list) else [obj])
else:
# Already a Node
self.objects.append(src)
return self.objects
def _has_local_options(self) -> bool:
"""Check if group has local options."""
return bool(
self.local_cflags or
self.local_cxxflags or
self.local_include_paths or
self.local_defines
)
def _apply_local_options(self, env) -> None:
"""Apply local options to environment."""
if self.local_cflags:
env.AppendUnique(CFLAGS=self.local_cflags.split())
if self.local_cxxflags:
env.AppendUnique(CXXFLAGS=self.local_cxxflags.split())
if self.local_include_paths:
paths = [os.path.abspath(p) for p in self.local_include_paths]
env.AppendUnique(CPPPATH=paths)
if self.local_defines:
env.AppendUnique(CPPDEFINES=self.local_defines)
def _apply_global_options(self, env) -> None:
"""Apply global options to environment."""
# These options affect dependent groups too
if self.include_paths:
paths = [os.path.abspath(p) for p in self.include_paths]
env.AppendUnique(CPPPATH=paths)
if self.defines:
env.AppendUnique(CPPDEFINES=self.defines)
if self.cflags and 'CFLAGS' not in env:
env['CFLAGS'] = self.cflags
if self.cxxflags and 'CXXFLAGS' not in env:
env['CXXFLAGS'] = self.cxxflags
if self.libs:
env.AppendUnique(LIBS=self.libs)
if self.lib_paths:
paths = [os.path.abspath(p) for p in self.lib_paths]
env.AppendUnique(LIBPATH=paths)
def get_info(self) -> Dict[str, Any]:
"""
Get group information for project generators.
Returns:
Dictionary with group information
"""
return {
'name': self.name,
'sources': self.sources,
'include_paths': self.include_paths + self.local_include_paths,
'defines': {**self.defines, **self.local_defines},
'cflags': f"{self.cflags} {self.local_cflags}".strip(),
'cxxflags': f"{self.cxxflags} {self.local_cxxflags}".strip(),
'libs': self.libs,
'lib_paths': self.lib_paths
}
class ProjectRegistry:
"""
Registry for all project groups.
This class manages all registered project groups and provides
methods for querying and merging them.
"""
def __init__(self):
self.groups: List[ProjectGroup] = []
self._group_index: Dict[str, ProjectGroup] = {}
def register_group(self, group: ProjectGroup) -> None:
"""
Register a project group.
Args:
group: ProjectGroup instance
"""
self.groups.append(group)
self._group_index[group.name] = group
def get_group(self, name: str) -> Optional[ProjectGroup]:
"""
Get group by name.
Args:
name: Group name
Returns:
ProjectGroup or None
"""
return self._group_index.get(name)
def get_all_groups(self) -> List[ProjectGroup]:
"""Get all registered groups."""
return self.groups.copy()
def get_groups_by_dependency(self, dependency: str) -> List[ProjectGroup]:
"""
Get groups that depend on a specific macro.
Args:
dependency: Dependency name
Returns:
List of matching groups
"""
return [g for g in self.groups if dependency in g.dependencies]
def merge_groups(self, env) -> List:
"""
Merge all groups into a single list of objects.
Args:
env: SCons Environment
Returns:
List of all build objects
"""
all_objects = []
for group in self.groups:
if group.objects:
all_objects.extend(group.objects)
return all_objects
def get_project_info(self) -> Dict[str, Any]:
"""
Get complete project information for generators.
Returns:
Dictionary with project information
"""
# Collect all unique values
all_sources = []
all_includes = set()
all_defines = {}
all_libs = []
all_lib_paths = set()
for group in self.groups:
info = group.get_info()
# Sources
all_sources.extend(info['sources'])
# Include paths
all_includes.update(info['include_paths'])
# Defines
all_defines.update(info['defines'])
# Libraries
all_libs.extend(info['libs'])
all_lib_paths.update(info['lib_paths'])
return {
'groups': [g.get_info() for g in self.groups],
'all_sources': all_sources,
'all_includes': sorted(list(all_includes)),
'all_defines': all_defines,
'all_libs': all_libs,
'all_lib_paths': sorted(list(all_lib_paths))
}
def clear(self) -> None:
"""Clear all registered groups."""
self.groups.clear()
self._group_index.clear()
+396
View File
@@ -0,0 +1,396 @@
# -*- coding: utf-8 -*-
"""
Toolchain management for RT-Thread build system.
This module provides abstraction for different toolchains (GCC, Keil, IAR, etc.).
"""
import os
import shutil
import subprocess
from abc import ABC, abstractmethod
from typing import Dict, List, Optional, Tuple
from dataclasses import dataclass
@dataclass
class ToolchainInfo:
"""Toolchain information."""
name: str
version: str
path: str
prefix: str = ""
suffix: str = ""
class Toolchain(ABC):
"""Abstract base class for toolchains."""
def __init__(self):
self.info = None
@abstractmethod
def get_name(self) -> str:
"""Get toolchain name."""
pass
@abstractmethod
def detect(self) -> bool:
"""Detect if toolchain is available."""
pass
@abstractmethod
def configure_environment(self, env) -> None:
"""Configure SCons environment for this toolchain."""
pass
@abstractmethod
def get_compile_flags(self, cpu: str, fpu: str = None, float_abi: str = None) -> Dict[str, str]:
"""Get compilation flags for target CPU."""
pass
def get_version(self) -> Optional[str]:
"""Get toolchain version."""
return self.info.version if self.info else None
def _run_command(self, cmd: List[str]) -> Tuple[int, str, str]:
"""Run command and return (returncode, stdout, stderr)."""
try:
result = subprocess.run(cmd, capture_output=True, text=True)
return result.returncode, result.stdout, result.stderr
except Exception as e:
return -1, "", str(e)
class GccToolchain(Toolchain):
"""GCC toolchain implementation."""
def __init__(self, prefix: str = ""):
super().__init__()
self.prefix = prefix or "arm-none-eabi-"
def get_name(self) -> str:
return "gcc"
def detect(self) -> bool:
"""Detect GCC toolchain."""
gcc_path = shutil.which(self.prefix + "gcc")
if not gcc_path:
return False
# Get version
ret, stdout, _ = self._run_command([gcc_path, "--version"])
if ret == 0:
lines = stdout.split('\n')
if lines:
version = lines[0].split()[-1]
self.info = ToolchainInfo(
name="gcc",
version=version,
path=os.path.dirname(gcc_path),
prefix=self.prefix
)
return True
return False
def configure_environment(self, env) -> None:
"""Configure environment for GCC."""
env['CC'] = self.prefix + 'gcc'
env['CXX'] = self.prefix + 'g++'
env['AS'] = self.prefix + 'gcc'
env['AR'] = self.prefix + 'ar'
env['LINK'] = self.prefix + 'gcc'
env['SIZE'] = self.prefix + 'size'
env['OBJDUMP'] = self.prefix + 'objdump'
env['OBJCPY'] = self.prefix + 'objcopy'
# Set default flags
env['ARFLAGS'] = '-rc'
env['ASFLAGS'] = '-x assembler-with-cpp'
# Path
if self.info and self.info.path:
env.PrependENVPath('PATH', self.info.path)
def get_compile_flags(self, cpu: str, fpu: str = None, float_abi: str = None) -> Dict[str, str]:
"""Get GCC compilation flags."""
flags = {
'CFLAGS': [],
'CXXFLAGS': [],
'ASFLAGS': [],
'LDFLAGS': []
}
# CPU flags
cpu_flags = {
'cortex-m0': '-mcpu=cortex-m0 -mthumb',
'cortex-m0+': '-mcpu=cortex-m0plus -mthumb',
'cortex-m3': '-mcpu=cortex-m3 -mthumb',
'cortex-m4': '-mcpu=cortex-m4 -mthumb',
'cortex-m7': '-mcpu=cortex-m7 -mthumb',
'cortex-m23': '-mcpu=cortex-m23 -mthumb',
'cortex-m33': '-mcpu=cortex-m33 -mthumb',
'cortex-a7': '-mcpu=cortex-a7',
'cortex-a9': '-mcpu=cortex-a9'
}
if cpu in cpu_flags:
base_flags = cpu_flags[cpu]
for key in ['CFLAGS', 'CXXFLAGS', 'ASFLAGS']:
flags[key].append(base_flags)
# FPU flags
if fpu:
fpu_flag = f'-mfpu={fpu}'
for key in ['CFLAGS', 'CXXFLAGS']:
flags[key].append(fpu_flag)
# Float ABI
if float_abi:
abi_flag = f'-mfloat-abi={float_abi}'
for key in ['CFLAGS', 'CXXFLAGS']:
flags[key].append(abi_flag)
# Common flags
common_flags = ['-ffunction-sections', '-fdata-sections']
flags['CFLAGS'].extend(common_flags)
flags['CXXFLAGS'].extend(common_flags)
# Linker flags
flags['LDFLAGS'].extend(['-Wl,--gc-sections'])
# Convert lists to strings
return {k: ' '.join(v) for k, v in flags.items()}
class ArmccToolchain(Toolchain):
"""ARM Compiler (Keil) toolchain implementation."""
def get_name(self) -> str:
return "armcc"
def detect(self) -> bool:
"""Detect ARM Compiler toolchain."""
armcc_path = shutil.which("armcc")
if not armcc_path:
# Try common Keil installation paths
keil_paths = [
r"C:\Keil_v5\ARM\ARMCC\bin",
r"C:\Keil\ARM\ARMCC\bin",
"/opt/arm/bin"
]
for path in keil_paths:
test_path = os.path.join(path, "armcc")
if os.path.exists(test_path):
armcc_path = test_path
break
if not armcc_path:
return False
# Get version
ret, stdout, _ = self._run_command([armcc_path, "--version"])
if ret == 0:
lines = stdout.split('\n')
for line in lines:
if "ARM Compiler" in line:
version = line.split()[-1]
self.info = ToolchainInfo(
name="armcc",
version=version,
path=os.path.dirname(armcc_path)
)
return True
return False
def configure_environment(self, env) -> None:
"""Configure environment for ARM Compiler."""
env['CC'] = 'armcc'
env['CXX'] = 'armcc'
env['AS'] = 'armasm'
env['AR'] = 'armar'
env['LINK'] = 'armlink'
# ARM Compiler specific settings
env['ARCOM'] = '$AR --create $TARGET $SOURCES'
env['LIBPREFIX'] = ''
env['LIBSUFFIX'] = '.lib'
env['LIBLINKPREFIX'] = ''
env['LIBLINKSUFFIX'] = '.lib'
env['LIBDIRPREFIX'] = '--userlibpath '
# Path
if self.info and self.info.path:
env.PrependENVPath('PATH', self.info.path)
def get_compile_flags(self, cpu: str, fpu: str = None, float_abi: str = None) -> Dict[str, str]:
"""Get ARM Compiler flags."""
flags = {
'CFLAGS': [],
'CXXFLAGS': [],
'ASFLAGS': [],
'LDFLAGS': []
}
# CPU selection
cpu_map = {
'cortex-m0': '--cpu Cortex-M0',
'cortex-m0+': '--cpu Cortex-M0+',
'cortex-m3': '--cpu Cortex-M3',
'cortex-m4': '--cpu Cortex-M4',
'cortex-m7': '--cpu Cortex-M7'
}
if cpu in cpu_map:
cpu_flag = cpu_map[cpu]
for key in flags:
flags[key].append(cpu_flag)
# Common flags
flags['CFLAGS'].extend(['--c99', '--gnu'])
flags['CXXFLAGS'].extend(['--cpp', '--gnu'])
return {k: ' '.join(v) for k, v in flags.items()}
class IarToolchain(Toolchain):
"""IAR toolchain implementation."""
def get_name(self) -> str:
return "iar"
def detect(self) -> bool:
"""Detect IAR toolchain."""
iccarm_path = shutil.which("iccarm")
if not iccarm_path:
# Try common IAR installation paths
iar_paths = [
r"C:\Program Files (x86)\IAR Systems\Embedded Workbench 8.0\arm\bin",
r"C:\Program Files\IAR Systems\Embedded Workbench 8.0\arm\bin",
"/opt/iar/bin"
]
for path in iar_paths:
test_path = os.path.join(path, "iccarm.exe" if os.name == 'nt' else "iccarm")
if os.path.exists(test_path):
iccarm_path = test_path
break
if not iccarm_path:
return False
self.info = ToolchainInfo(
name="iar",
version="8.x", # IAR version detection is complex
path=os.path.dirname(iccarm_path)
)
return True
def configure_environment(self, env) -> None:
"""Configure environment for IAR."""
env['CC'] = 'iccarm'
env['CXX'] = 'iccarm'
env['AS'] = 'iasmarm'
env['AR'] = 'iarchive'
env['LINK'] = 'ilinkarm'
# IAR specific settings
env['LIBPREFIX'] = ''
env['LIBSUFFIX'] = '.a'
env['LIBLINKPREFIX'] = ''
env['LIBLINKSUFFIX'] = '.a'
# Path
if self.info and self.info.path:
env.PrependENVPath('PATH', self.info.path)
def get_compile_flags(self, cpu: str, fpu: str = None, float_abi: str = None) -> Dict[str, str]:
"""Get IAR flags."""
flags = {
'CFLAGS': [],
'CXXFLAGS': [],
'ASFLAGS': [],
'LDFLAGS': []
}
# CPU selection
cpu_map = {
'cortex-m0': '--cpu=Cortex-M0',
'cortex-m0+': '--cpu=Cortex-M0+',
'cortex-m3': '--cpu=Cortex-M3',
'cortex-m4': '--cpu=Cortex-M4',
'cortex-m7': '--cpu=Cortex-M7'
}
if cpu in cpu_map:
cpu_flag = cpu_map[cpu]
flags['CFLAGS'].append(cpu_flag)
flags['CXXFLAGS'].append(cpu_flag)
# Common flags
flags['CFLAGS'].extend(['-e', '--dlib_config', 'DLib_Config_Normal.h'])
return {k: ' '.join(v) for k, v in flags.items()}
class ToolchainManager:
"""Manager for toolchain selection and configuration."""
def __init__(self):
self.toolchains: Dict[str, Toolchain] = {}
self.current_toolchain: Optional[Toolchain] = None
self._register_default_toolchains()
def _register_default_toolchains(self) -> None:
"""Register default toolchains."""
# Try to detect available toolchains
toolchain_classes = [
(GccToolchain, ['arm-none-eabi-', 'riscv32-unknown-elf-', 'riscv64-unknown-elf-']),
(ArmccToolchain, ['']),
(IarToolchain, [''])
]
for toolchain_class, prefixes in toolchain_classes:
for prefix in prefixes:
if toolchain_class == GccToolchain:
tc = toolchain_class(prefix)
else:
tc = toolchain_class()
if tc.detect():
name = f"{tc.get_name()}-{prefix}" if prefix else tc.get_name()
self.register_toolchain(name, tc)
def register_toolchain(self, name: str, toolchain: Toolchain) -> None:
"""Register a toolchain."""
self.toolchains[name] = toolchain
def select_toolchain(self, name: str) -> Toolchain:
"""Select a toolchain by name."""
if name not in self.toolchains:
# Try to create it
if name == 'gcc':
tc = GccToolchain()
elif name == 'armcc' or name == 'keil':
tc = ArmccToolchain()
elif name == 'iar':
tc = IarToolchain()
else:
raise ValueError(f"Unknown toolchain: {name}")
if tc.detect():
self.register_toolchain(name, tc)
else:
raise RuntimeError(f"Toolchain '{name}' not found")
self.current_toolchain = self.toolchains[name]
return self.current_toolchain
def get_current(self) -> Optional[Toolchain]:
"""Get current toolchain."""
return self.current_toolchain
def list_toolchains(self) -> List[str]:
"""List available toolchains."""
return list(self.toolchains.keys())
+339
View File
@@ -0,0 +1,339 @@
# -*- coding: utf-8 -*-
"""
Utility functions for RT-Thread build system.
This module provides common utility functions used throughout the build system.
"""
import os
import sys
import platform
from typing import List, Tuple, Optional
class PathService:
"""Service for path manipulation and normalization."""
def __init__(self, base_path: str = None):
self.base_path = base_path or os.getcwd()
def normalize_path(self, path: str) -> str:
"""
Normalize path for cross-platform compatibility.
Args:
path: Path to normalize
Returns:
Normalized path
"""
# Convert to absolute path if relative
if not os.path.isabs(path):
path = os.path.abspath(os.path.join(self.base_path, path))
# Normalize separators
path = os.path.normpath(path)
# Convert to forward slashes for consistency
if platform.system() == 'Windows':
path = path.replace('\\', '/')
return path
def make_relative(self, path: str, base: str = None) -> str:
"""
Make path relative to base.
Args:
path: Path to make relative
base: Base path (defaults to self.base_path)
Returns:
Relative path
"""
if base is None:
base = self.base_path
path = self.normalize_path(path)
base = self.normalize_path(base)
try:
rel_path = os.path.relpath(path, base)
# Convert to forward slashes
if platform.system() == 'Windows':
rel_path = rel_path.replace('\\', '/')
return rel_path
except ValueError:
# Different drives on Windows
return path
def split_path(self, path: str) -> List[str]:
"""
Split path into components.
Args:
path: Path to split
Returns:
List of path components
"""
path = self.normalize_path(path)
parts = []
while True:
head, tail = os.path.split(path)
if tail:
parts.insert(0, tail)
if head == path: # Reached root
if head:
parts.insert(0, head)
break
path = head
return parts
def common_prefix(self, paths: List[str]) -> str:
"""
Find common prefix of multiple paths.
Args:
paths: List of paths
Returns:
Common prefix path
"""
if not paths:
return ""
# Normalize all paths
normalized = [self.normalize_path(p) for p in paths]
# Find common prefix
prefix = os.path.commonpath(normalized)
return self.normalize_path(prefix)
class PlatformInfo:
"""Platform and system information."""
@staticmethod
def get_platform() -> str:
"""Get platform name (Windows, Linux, Darwin)."""
return platform.system()
@staticmethod
def get_architecture() -> str:
"""Get system architecture."""
return platform.machine()
@staticmethod
def is_windows() -> bool:
"""Check if running on Windows."""
return platform.system() == 'Windows'
@staticmethod
def is_linux() -> bool:
"""Check if running on Linux."""
return platform.system() == 'Linux'
@staticmethod
def is_macos() -> bool:
"""Check if running on macOS."""
return platform.system() == 'Darwin'
@staticmethod
def get_python_version() -> Tuple[int, int, int]:
"""Get Python version tuple."""
return sys.version_info[:3]
@staticmethod
def check_python_version(min_version: Tuple[int, int]) -> bool:
"""
Check if Python version meets minimum requirement.
Args:
min_version: Minimum version tuple (major, minor)
Returns:
True if version is sufficient
"""
current = sys.version_info[:2]
return current >= min_version
class FileUtils:
"""File operation utilities."""
@staticmethod
def read_file(filepath: str, encoding: str = 'utf-8') -> str:
"""
Read file content.
Args:
filepath: File path
encoding: File encoding
Returns:
File content
"""
with open(filepath, 'r', encoding=encoding) as f:
return f.read()
@staticmethod
def write_file(filepath: str, content: str, encoding: str = 'utf-8') -> None:
"""
Write content to file.
Args:
filepath: File path
content: Content to write
encoding: File encoding
"""
# Ensure directory exists
directory = os.path.dirname(filepath)
if directory:
os.makedirs(directory, exist_ok=True)
with open(filepath, 'w', encoding=encoding) as f:
f.write(content)
@staticmethod
def copy_file(src: str, dst: str) -> None:
"""
Copy file from src to dst.
Args:
src: Source file path
dst: Destination file path
"""
import shutil
# Ensure destination directory exists
dst_dir = os.path.dirname(dst)
if dst_dir:
os.makedirs(dst_dir, exist_ok=True)
shutil.copy2(src, dst)
@staticmethod
def find_files(directory: str, pattern: str, recursive: bool = True) -> List[str]:
"""
Find files matching pattern.
Args:
directory: Directory to search
pattern: File pattern (supports wildcards)
recursive: Search recursively
Returns:
List of matching file paths
"""
import fnmatch
matches = []
if recursive:
for root, dirnames, filenames in os.walk(directory):
for filename in filenames:
if fnmatch.fnmatch(filename, pattern):
matches.append(os.path.join(root, filename))
else:
try:
filenames = os.listdir(directory)
for filename in filenames:
if fnmatch.fnmatch(filename, pattern):
filepath = os.path.join(directory, filename)
if os.path.isfile(filepath):
matches.append(filepath)
except OSError:
pass
return sorted(matches)
class VersionUtils:
"""Version comparison utilities."""
@staticmethod
def parse_version(version_str: str) -> Tuple[int, ...]:
"""
Parse version string to tuple.
Args:
version_str: Version string (e.g., "1.2.3")
Returns:
Version tuple
"""
try:
parts = version_str.split('.')
return tuple(int(p) for p in parts if p.isdigit())
except (ValueError, AttributeError):
return (0,)
@staticmethod
def compare_versions(v1: str, v2: str) -> int:
"""
Compare two version strings.
Args:
v1: First version
v2: Second version
Returns:
-1 if v1 < v2, 0 if equal, 1 if v1 > v2
"""
t1 = VersionUtils.parse_version(v1)
t2 = VersionUtils.parse_version(v2)
# Pad shorter version with zeros
if len(t1) < len(t2):
t1 = t1 + (0,) * (len(t2) - len(t1))
elif len(t2) < len(t1):
t2 = t2 + (0,) * (len(t1) - len(t2))
if t1 < t2:
return -1
elif t1 > t2:
return 1
else:
return 0
@staticmethod
def version_satisfies(version: str, requirement: str) -> bool:
"""
Check if version satisfies requirement.
Args:
version: Version string
requirement: Requirement string (e.g., ">=1.2.0")
Returns:
True if satisfied
"""
import re
# Parse requirement
match = re.match(r'([<>=]+)\s*(.+)', requirement)
if not match:
# Exact match required
return version == requirement
op, req_version = match.groups()
cmp = VersionUtils.compare_versions(version, req_version)
if op == '>=':
return cmp >= 0
elif op == '<=':
return cmp <= 0
elif op == '>':
return cmp > 0
elif op == '<':
return cmp < 0
elif op == '==':
return cmp == 0
elif op == '!=':
return cmp != 0
else:
return False
+156
View File
@@ -0,0 +1,156 @@
#
# File : options.py
# This file is part of RT-Thread RTOS
# COPYRIGHT (C) 2006 - 2015, RT-Thread Development Team
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Change Logs:
# Date Author Notes
# 2022-04-20 WuGensheng Add Options to SCons
# 2025-03-02 ZhaoCake Add Options about compile_commands
from SCons.Script import AddOption
import platform
def AddOptions():
''' ===== Add generic options to SCons ===== '''
AddOption('--dist',
dest = 'make-dist',
action = 'store_true',
default = False,
help = 'make distribution')
AddOption('--dist-ide', '--dist-rtstudio',
dest = 'make-dist-ide',
action = 'store_true',
default = False,
help = 'make distribution for RT-Thread Studio IDE')
AddOption('--project-path',
dest = 'project-path',
type = 'string',
default = None,
help = 'set project output path')
AddOption('--project-name',
dest = 'project-name',
type = 'string',
default = "project",
help = 'set project name')
AddOption('--cscope',
dest = 'cscope',
action = 'store_true',
default = False,
help = 'Build Cscope cross reference database. Requires cscope installed.')
AddOption('--clang-analyzer',
dest = 'clang-analyzer',
action = 'store_true',
default = False,
help = 'Perform static analyze with Clang-analyzer. ' + \
'Requires Clang installed.' + \
'It is recommended to use with scan-build like this:' + \
'`scan-build scons --clang-analyzer`' + \
'If things goes well, scan-build will instruct you to invoke scan-view.')
AddOption('--buildlib',
dest = 'buildlib',
type = 'string',
help = 'building library of a component')
AddOption('--cleanlib',
dest = 'cleanlib',
action = 'store_true',
default = False,
help = 'clean up the library by --buildlib')
AddOption('--target',
dest = 'target',
type = 'string',
help = 'set target project: mdk/mdk4/mdk5/iar/vs/vsc/ua/cdk/ses/makefile/eclipse/codelite/cmake/vsc_workspace')
AddOption('--cmsispack',
dest = 'cmsispack',
type = 'string',
help = 'set pack: <cmsispack path>')
AddOption('--strict',
dest='strict-compiling',
help='Compiling project with strict mode and ALL warning will be errors',
action='store_true',
default=False)
AddOption('--verbose',
dest = 'verbose',
action = 'store_true',
default = False,
help = 'print verbose information during build')
AddOption('--cc-prefix', '--exec-prefix',
dest = 'exec-prefix',
type = 'string',
help = 'set RTT_CC_PREFIX temperately')
AddOption('--cc-path', '--exec-path',
dest = 'exec-path',
type = 'string',
help = 'set RTT_EXEC_PATH temperately')
AddOption('--stackanalysis',
dest = 'stackanalysis',
action = 'store_true',
default = False,
help = 'thread stack static analysis')
AddOption('--genconfig',
dest = 'genconfig',
action = 'store_true',
default = False,
help = 'Generate .config from rtconfig.h')
AddOption('--useconfig',
dest = 'useconfig',
type = 'string',
help = 'make rtconfig.h from config file.')
AddOption('--global-macros',
dest = 'global-macros',
type = 'string',
help = 'attach global macros in the project. '+\
'e.g. scons --global-config=RT_USING_XX,RT_USING_YY'+\
' or scons --global-config="RT_USING_XX, RT_USING_YY"')
AddOption('--reset-project-config',
dest = 'reset-project-config',
action = 'store_true',
default = False,
help = 'reset the project configurations to default')
AddOption('--guiconfig', '--pyconfig',
dest = 'guiconfig',
action = 'store_true',
default = False,
help = 'Python GUI menuconfig for RT-Thread BSP')
AddOption('--defconfig', '--pyconfig-silent',
dest = 'defconfig',
action = 'store_true',
default = False,
help = 'Don`t show Python GUI menuconfig window')
AddOption('--menuconfig',
dest = 'menuconfig',
action = 'store_true',
default = False,
help = 'make menuconfig for RT-Thread BSP')
AddOption('--cdb',
dest = 'cdb',
action = 'store_true',
default = False,
help = 'make compile_commands.json')
AddOption('--attach',
dest = 'attach',
type = 'string',
help = 'View attachconfig or add attach to.config.'+\
'e.g. scons --attach=? View all attachconfig for the current bsp.'+\
' or scons --attach=component.cherryusb_cdc Set option component.cherryusb_cdc inside attachconfig to.config.'+\
' or scons --attach=default Restore.config and rtconfig to before attch was set.')
AddOption('--dist-strip',
dest='dist_strip',
action='store_true',
default=False,
help='create minimal distribution based on compile_commands.json.'+\
'So you should run `bear -- scons` to generate compile_commands.json first.')
+130
View File
@@ -0,0 +1,130 @@
#
# File : package.py
# This file is part of RT-Thread RTOS
# COPYRIGHT (C) 2006 - 2015, RT-Thread Development Team
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Change Logs:
# Date Author Notes
# 2015-04-10 Bernard First version
#
# this script is used to build group with package.json instead of SConscript
import os
import json
from building import *
def ExtendPackageVar(package, var):
v = []
if var not in package:
return v
for item in package[var]:
v = v + [item]
return v
def BuildPackage(package = None):
if package is None:
package = os.path.join(GetCurrentDir(), 'package.json')
elif os.path.isdir(package):
# support directory path
package = os.path.join(package, 'package.json')
# get package.json path
cwd = os.path.dirname(os.path.abspath(package))
if not os.path.isfile(package):
# silent return for conditional usage
return []
with open(package, 'r') as f:
package_json = f.read()
package = json.loads(package_json)
# check package name
if 'name' not in package or 'type' not in package or package['type'] != 'rt-thread-component':
return []
# get depends
depend = []
if 'dependencies' in package:
depend = ExtendPackageVar(package, 'dependencies')
# check dependencies
if depend:
group_enable = False
for item in depend:
if GetDepend(item):
group_enable = True
break
if not group_enable:
return []
CPPDEFINES = []
if 'defines' in package:
CPPDEFINES = ExtendPackageVar(package, 'defines')
src = []
CPPPATH = []
if 'sources' in package:
src_depend = []
src_CPPPATH = []
for item in package['sources']:
if 'includes' in item:
includes = item['includes']
for include in includes:
if include.startswith('/') and os.path.isdir(include):
src_CPPPATH = src_CPPPATH + [include]
else:
path = os.path.abspath(os.path.join(cwd, include))
src_CPPPATH = src_CPPPATH + [path]
if 'dependencies' in item:
src_depend = src_depend + ExtendPackageVar(item, 'dependencies')
src_enable = False
if src_depend == []:
src_enable = True
else:
for d in src_depend:
if GetDepend(d):
src_enable = True
break
if src_enable:
files = []
src_files = []
if 'files' in item:
files += ExtendPackageVar(item, 'files')
for item in files:
# handle glob patterns relative to package.json directory
old_dir = os.getcwd()
os.chdir(cwd)
try:
src_files += Glob(item)
finally:
os.chdir(old_dir)
src += src_files
CPPPATH += src_CPPPATH
objs = DefineGroup(package['name'], src, depend = depend, CPPPATH = CPPPATH, CPPDEFINES = CPPDEFINES)
return objs
+89
View File
@@ -0,0 +1,89 @@
# -*- coding: utf-8 -*-
#
# File : preprocessor.py
# This file is part of RT-Thread RTOS
# COPYRIGHT (C) 2006 - 2025, RT-Thread Development Team
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Change Logs:
# Date Author Notes
# 2025-01-05 Assistant Extract SCons PreProcessor patch to independent class
from SCons.Script import *
class SConsPreProcessorPatch:
"""
SCons PreProcessor patch class
This class provides methods to patch the SCons PreProcessor
to handle conditional compilation directives properly.
"""
def __init__(self):
"""Initialize the PreProcessor patch"""
self._patched_preprocessor = None
self._apply_patch()
def _apply_patch(self):
"""
Apply the patch to SCons PreProcessor
This method patches the SCons.cpp.PreProcessor class with
custom methods for handling includes during conditional compilation.
"""
from SCons import cpp
# Store reference to original PreProcessor
self._patched_preprocessor = cpp.PreProcessor
# Create bound methods for the patch
def start_handling_includes(preprocessor_self, t=None):
d = preprocessor_self.dispatch_table
p = preprocessor_self.stack[-1] if preprocessor_self.stack else preprocessor_self.default_table
for k in ('import', 'include', 'include_next', 'define'):
d[k] = p[k]
def stop_handling_includes(preprocessor_self, t=None):
d = preprocessor_self.dispatch_table
d['import'] = preprocessor_self.do_nothing
d['include'] = preprocessor_self.do_nothing
d['include_next'] = preprocessor_self.do_nothing
d['define'] = preprocessor_self.do_nothing
# Apply the patch methods
self._patched_preprocessor.start_handling_includes = start_handling_includes
self._patched_preprocessor.stop_handling_includes = stop_handling_includes
def get_patched_preprocessor(self):
return self._patched_preprocessor
def create_preprocessor_instance(self):
return self._patched_preprocessor()
# Global instance for easy access
_preprocessor_patch = None
def get_patched_preprocessor():
global _preprocessor_patch
if _preprocessor_patch is None:
_preprocessor_patch = SConsPreProcessorPatch()
return _preprocessor_patch.get_patched_preprocessor()
def create_preprocessor_instance():
global _preprocessor_patch
if _preprocessor_patch is None:
_preprocessor_patch = SConsPreProcessorPatch()
return _preprocessor_patch.create_preprocessor_instance()
+52
View File
@@ -0,0 +1,52 @@
# 版本发布前自动更新与部署
在ENV环境下,并在release文件夹下执行 `python buildbot.py update` 可完成自动版本发布**前** **部分** 准备工作。 欢迎补充其他发布前自动化脚本。
目前可以自动更新和部署的内容包括:
1. 更新所有BSP工程,包括.config文件、rtconfig文件更新,以及Keil\IAR等工程的刷新
2. STM32启动文件更新:
1. 对gcc的汇编启动文件中main替换为entry函数
2. 将启动文件heap降为0(Keil IAR)
3. 将GCC的堆大小扩展到0x400,与Keil IAR保持一致
## clang-format代码自动格式化
run-clang-format.py 根据`.clang-format``.clang-format-ignore`文件,使用clang-format工具对代码进行格式化。
如果**不希望**对某个文件夹进行格式化,那么在该文件夹下增加一个`.clang-format`,内容为:
```yaml
---
Language: Cpp
DisableFormat: true
---
```
如果**不希望**对某个代码片段进行格式化,那么在代码中插入`// clang-format off/on`
```c
int formatted_code;
// clang-format off
void unformatted_code ;
// clang-format on
void formatted_code_again;
```
使用以下命令,将对除了bsp、elmfat、lwip等文件夹之外的所有代码进行格式化:
```shell
# 安装clang-format
pip install clang-format
# 切换到RTT目录
cd $RTT_ROOT
# 执行格式化
# -r递归子目录,-i是将格式化结果写入文件,-e是排除目录,-j是并行线程,.是当前目录
python tools/release/run-clang-format.py -r -i -e bsp/**/* -j 10 .
```
如果格式化过程中提示以下错误,一般是文件中存在UTF-8编码无法识别的字符。
```shell
error: Command 'clang-format -i libcpu\aarch64\common\asm-fpu.h' returned non-zero exit status 1
```
+87
View File
@@ -0,0 +1,87 @@
import os
import sys
def usage():
print('%s all -- build all bsp' % os.path.basename(sys.argv[0]))
print('%s clean -- clean all bsp' % os.path.basename(sys.argv[0]))
print('%s update -- update all prject files' % os.path.basename(sys.argv[0]))
BSP_ROOT = os.path.join("..", "..", "bsp")
if len(sys.argv) != 2:
usage()
sys.exit(0)
def update_project_file(project_dir):
if os.path.isfile(os.path.join(project_dir, 'template.Uv2')):
print('prepare MDK3 project file on ' + project_dir)
command = ' --target=mdk -s'
os.system('scons --directory=' + project_dir + command + ' > 1.txt')
if os.path.isfile(os.path.join(project_dir, 'template.uvproj')):
print('prepare MDK4 project file on ' + project_dir)
command = ' --target=mdk4 -s'
os.system('scons --directory=' + project_dir + command + ' > 1.txt')
if os.path.isfile(os.path.join(project_dir, 'template.uvprojx')):
print('prepare MDK5 project file on ' + project_dir)
command = ' --target=mdk5 -s'
os.system('scons --directory=' + project_dir + command + ' > 1.txt')
if os.path.isfile(os.path.join(project_dir, 'template.ewp')):
print('prepare IAR project file on ' + project_dir)
command = ' --target=iar -s'
os.system('scons --directory=' + project_dir + command + ' > 1.txt')
def update_all_project_files(root_path):
# current path is dir
if os.path.isdir(root_path):
projects = os.listdir(root_path)
# is a project path?
if "SConstruct" in projects:
try:
# update rtconfig.h and .config
if "Kconfig" in projects:
if "win32" in sys.platform:
retval = os.getcwd()
os.chdir(root_path)
os.system("menuconfig --silent")
os.chdir(retval)
else:
os.system('scons --pyconfig-silent -C {0}'.format(root_path))
update_project_file(root_path)
except Exception as e:
print("error message: {}".format(e))
sys.exit(-1)
else:
for i in projects:
new_root_path = os.path.join(root_path, i)
update_all_project_files(new_root_path)
# get command options
command = ''
if sys.argv[1] == 'all':
command = ' '
elif sys.argv[1] == 'clean':
command = ' -c'
elif sys.argv[1] == 'update':
print('begin to update all the bsp projects')
from stm32_update import stm32_update
stm32_update(os.path.join(BSP_ROOT, 'stm32'))
update_all_project_files(BSP_ROOT)
print('finished!')
sys.exit(0)
else:
usage()
sys.exit(0)
projects = os.listdir(BSP_ROOT)
for item in projects:
project_dir = os.path.join(BSP_ROOT, item)
if os.path.isfile(os.path.join(project_dir, 'SConstruct')):
if os.system('scons --directory=' + project_dir + command) != 0:
print('build failed!!')
break
+426
View File
@@ -0,0 +1,426 @@
#!/usr/bin/env python
"""A wrapper script around clang-format, suitable for linting multiple files
and to use for continuous integration.
This is an alternative API for the clang-format command line.
It runs over multiple files and directories in parallel.
A diff output is produced and a sensible exit code is returned.
"""
from __future__ import print_function, unicode_literals
import argparse
import codecs
import difflib
import fnmatch
import io
import errno
import multiprocessing
import os
import signal
import subprocess
import sys
import traceback
import platform
from functools import partial
try:
from subprocess import DEVNULL # py3k
except ImportError:
DEVNULL = open(os.devnull, "wb")
DEFAULT_EXTENSIONS = "c,h,C,H,cpp,hpp,cc,hh,c++,h++,cxx,hxx"
DEFAULT_CLANG_FORMAT_IGNORE = ".clang-format-ignore"
class ExitStatus:
SUCCESS = 0
DIFF = 1
TROUBLE = 2
def excludes_from_file(ignore_file):
excludes = []
try:
with io.open(ignore_file, "r", encoding="utf-8") as f:
for line in f:
if line.startswith("#"):
# ignore comments
continue
pattern = line.rstrip()
if not pattern:
# allow empty lines
continue
excludes.append(pattern)
except EnvironmentError as e:
if e.errno != errno.ENOENT:
raise
return excludes
def list_files(files, recursive=False, extensions=None, exclude=None):
if extensions is None:
extensions = []
if exclude is None:
exclude = []
out = []
for file in files:
if recursive and os.path.isdir(file):
for dirpath, dnames, fnames in os.walk(file):
fpaths = [
os.path.relpath(os.path.join(dirpath, fname), os.getcwd())
for fname in fnames
]
for pattern in exclude:
# os.walk() supports trimming down the dnames list
# by modifying it in-place,
# to avoid unnecessary directory listings.
dnames[:] = [
x
for x in dnames
if not fnmatch.fnmatch(os.path.join(dirpath, x), pattern)
]
fpaths = [x for x in fpaths if not fnmatch.fnmatch(x, pattern)]
for f in fpaths:
ext = os.path.splitext(f)[1][1:]
if ext in extensions:
out.append(f)
else:
out.append(file)
return out
def make_diff(file, original, reformatted):
return list(
difflib.unified_diff(
original,
reformatted,
fromfile="{}\t(original)".format(file),
tofile="{}\t(reformatted)".format(file),
n=3,
)
)
class DiffError(Exception):
def __init__(self, message, errs=None):
super(DiffError, self).__init__(message)
self.errs = errs or []
class UnexpectedError(Exception):
def __init__(self, message, exc=None):
super(UnexpectedError, self).__init__(message)
self.formatted_traceback = traceback.format_exc()
self.exc = exc
def run_clang_format_diff_wrapper(args, file):
try:
ret = run_clang_format_diff(args, file)
return ret
except DiffError:
raise
except Exception as e:
raise UnexpectedError("{}: {}: {}".format(file, e.__class__.__name__, e), e)
def run_clang_format_diff(args, file):
# try:
# with io.open(file, "r", encoding="utf-8") as f:
# original = f.readlines()
# except IOError as exc:
# raise DiffError(str(exc))
if args.in_place:
invocation = [args.clang_format_executable, "-i", file]
else:
invocation = [args.clang_format_executable, file]
if args.style:
invocation.extend(["--style", args.style])
if args.dry_run:
print(" ".join(invocation))
return [], []
# Use of utf-8 to decode the process output.
#
# Hopefully, this is the correct thing to do.
#
# It's done due to the following assumptions (which may be incorrect):
# - clang-format will returns the bytes read from the files as-is,
# without conversion, and it is already assumed that the files use utf-8.
# - if the diagnostics were internationalized, they would use utf-8:
# > Adding Translations to Clang
# >
# > Not possible yet!
# > Diagnostic strings should be written in UTF-8,
# > the client can translate to the relevant code page if needed.
# > Each translation completely replaces the format string
# > for the diagnostic.
# > -- http://clang.llvm.org/docs/InternalsManual.html#internals-diag-translation
#
# It's not pretty, due to Python 2 & 3 compatibility.
encoding_py3 = {}
if sys.version_info[0] >= 3:
encoding_py3["encoding"] = "utf-8"
try:
proc = subprocess.Popen(
invocation,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True,
**encoding_py3
)
except OSError as exc:
raise DiffError(
"Command '{}' failed to start: {}".format(
subprocess.list2cmdline(invocation), exc
)
)
proc_stdout = proc.stdout
proc_stderr = proc.stderr
if sys.version_info[0] < 3:
# make the pipes compatible with Python 3,
# reading lines should output unicode
encoding = "utf-8"
proc_stdout = codecs.getreader(encoding)(proc_stdout)
proc_stderr = codecs.getreader(encoding)(proc_stderr)
# hopefully the stderr pipe won't get full and block the process
outs = list(proc_stdout.readlines())
errs = list(proc_stderr.readlines())
proc.wait()
if proc.returncode:
raise DiffError(
"Command '{}' returned non-zero exit status {}".format(
subprocess.list2cmdline(invocation), proc.returncode
),
errs,
)
if args.in_place:
return [], errs
return make_diff(file, original, outs), errs
def bold_red(s):
return "\x1b[1m\x1b[31m" + s + "\x1b[0m"
def colorize(diff_lines):
def bold(s):
return "\x1b[1m" + s + "\x1b[0m"
def cyan(s):
return "\x1b[36m" + s + "\x1b[0m"
def green(s):
return "\x1b[32m" + s + "\x1b[0m"
def red(s):
return "\x1b[31m" + s + "\x1b[0m"
for line in diff_lines:
if line[:4] in ["--- ", "+++ "]:
yield bold(line)
elif line.startswith("@@ "):
yield cyan(line)
elif line.startswith("+"):
yield green(line)
elif line.startswith("-"):
yield red(line)
else:
yield line
def print_diff(diff_lines, use_color):
if use_color:
diff_lines = colorize(diff_lines)
if sys.version_info[0] < 3:
sys.stdout.writelines((l.encode("utf-8") for l in diff_lines))
else:
sys.stdout.writelines(diff_lines)
def print_trouble(prog, message, use_colors):
error_text = "error:"
if use_colors:
error_text = bold_red(error_text)
print("{}: {} {}".format(prog, error_text, message), file=sys.stderr)
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--clang-format-executable",
metavar="EXECUTABLE",
help="path to the clang-format executable",
default="clang-format",
)
parser.add_argument(
"--extensions",
help="comma separated list of file extensions (default: {})".format(
DEFAULT_EXTENSIONS
),
default=DEFAULT_EXTENSIONS,
)
parser.add_argument(
"-r",
"--recursive",
action="store_true",
help="run recursively over directories",
)
parser.add_argument(
"-d", "--dry-run", action="store_true", help="just print the list of files"
)
parser.add_argument(
"-i",
"--in-place",
action="store_true",
help="format file instead of printing differences",
)
parser.add_argument("files", metavar="file", nargs="+")
parser.add_argument(
"-q",
"--quiet",
action="store_true",
help="disable output, useful for the exit code",
)
parser.add_argument(
"-j",
metavar="N",
type=int,
default=0,
help="run N clang-format jobs in parallel" " (default number of cpus + 1)",
)
parser.add_argument(
"--color",
default="auto",
choices=["auto", "always", "never"],
help="show colored diff (default: auto)",
)
parser.add_argument(
"-e",
"--exclude",
metavar="PATTERN",
action="append",
default=[],
help="exclude paths matching the given glob-like pattern(s)"
" from recursive search",
)
parser.add_argument(
"--style",
default="file",
help="formatting style to apply (LLVM, Google, Chromium, Mozilla, WebKit)",
)
args = parser.parse_args()
# use default signal handling, like diff return SIGINT value on ^C
# https://bugs.python.org/issue14229#msg156446
signal.signal(signal.SIGINT, signal.SIG_DFL)
try:
signal.SIGPIPE
except AttributeError:
# compatibility, SIGPIPE does not exist on Windows
pass
else:
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
colored_stdout = False
colored_stderr = False
if args.color == "always":
colored_stdout = True
colored_stderr = True
elif args.color == "auto":
colored_stdout = sys.stdout.isatty()
colored_stderr = sys.stderr.isatty()
version_invocation = [args.clang_format_executable, str("--version")]
try:
subprocess.check_call(version_invocation, stdout=DEVNULL)
except subprocess.CalledProcessError as e:
print_trouble(parser.prog, str(e), use_colors=colored_stderr)
return ExitStatus.TROUBLE
except OSError as e:
print_trouble(
parser.prog,
"Command '{}' failed to start: {}".format(
subprocess.list2cmdline(version_invocation), e
),
use_colors=colored_stderr,
)
return ExitStatus.TROUBLE
retcode = ExitStatus.SUCCESS
if os.path.exists(DEFAULT_CLANG_FORMAT_IGNORE):
excludes = excludes_from_file(DEFAULT_CLANG_FORMAT_IGNORE)
else:
excludes = []
excludes.extend(args.exclude)
files = list_files(
args.files,
recursive=args.recursive,
exclude=excludes,
extensions=args.extensions.split(","),
)
if not files:
return
njobs = args.j
if njobs == 0:
njobs = multiprocessing.cpu_count() + 1
njobs = min(len(files), njobs)
if njobs == 1:
# execute directly instead of in a pool,
# less overhead, simpler stacktraces
it = (run_clang_format_diff_wrapper(args, file) for file in files)
pool = None
else:
pool = multiprocessing.Pool(njobs)
it = pool.imap_unordered(partial(run_clang_format_diff_wrapper, args), files)
pool.close()
while True:
try:
outs, errs = next(it)
except StopIteration:
break
except DiffError as e:
print_trouble(parser.prog, str(e), use_colors=colored_stderr)
retcode = ExitStatus.TROUBLE
sys.stderr.writelines(e.errs)
except UnexpectedError as e:
print_trouble(parser.prog, str(e), use_colors=colored_stderr)
sys.stderr.write(e.formatted_traceback)
retcode = ExitStatus.TROUBLE
# stop at the first unexpected error,
# something could be very wrong,
# don't process all files unnecessarily
if pool:
pool.terminate()
break
else:
sys.stderr.writelines(errs)
if outs == []:
continue
if not args.quiet:
print_diff(outs, use_color=colored_stdout)
if retcode == ExitStatus.SUCCESS:
retcode = ExitStatus.DIFF
if pool:
pool.join()
return retcode
if __name__ == "__main__":
sys.exit(main())
+125
View File
@@ -0,0 +1,125 @@
# Copyright (c) 2006-2022, RT-Thread Development Team
#
# SPDX-License-Identifier: Apache-2.0
#
# Change Logs:
# Date Author Notes
# 2021-10-11 Meco Man First version
# STM32 startup assembly language file:
# 1.replace main to entry (GCC)
# 2.reduce the heap size as 0x000 (Keil IAR)
# 3.extend the GCC stack size as 0x400, which is the same as Keil and IAR startup files.
import os
import re
# replace 'bl main' to 'bl entry'
def stm32update_main2entry(path):
oldline = ''
newline = ''
for root, dirs, files in os.walk(path):
for file in files:
if os.path.splitext(file)[1] == '.s': # find .s files (Keil MDK)
file_path = os.path.join(root,file)
flag_need_replace = False
with open(file_path,'r+',) as f:
while True:
line = f.readline()
if line == '':
break
elif ('bl' in line) and ('main' in line): # find 'bl main'
oldline = line # bl main
newline = line.replace('main', 'entry') # use 'entry' to replace 'main'
flag_need_replace = True # mark that need to be replaced
break
if (flag_need_replace == True): # use 'entry' to replace 'main'
f.seek(0)
content = f.read()
f.seek(0)
f.truncate()
newcontent = content.replace(oldline, newline)
f.write(newcontent)
#reduce the heap size as 0x000
def stm32update_heap2zero(path):
oldline = ''
newline = ''
for root, dirs, files in os.walk(path):
for file in files:
file_path = os.path.join(root,file)
if os.path.splitext(file)[1] == '.s': # find .s files (Keil MDK)
with open(file_path,'r+',) as f:
flag_need_replace = False
while True:
line = f.readline()
if line == '':
break
re_result = re.match('\s*Heap_Size\s+EQU\s+0[xX][0-9a-fA-F]+', line)
if re_result != None:
oldline = line
newline = re.sub('0[xX][0-9a-fA-F]+','0x00000000', oldline)
flag_need_replace = True
break
if flag_need_replace == True:
f.seek(0)
content = f.read()
f.seek(0)
f.truncate()
newcontent = content.replace(oldline, newline)
f.write(newcontent)
elif os.path.splitext(file)[1] == '.icf': # find .icf files (IAR)
with open(file_path,'r+',) as f:
flag_need_replace = False
while True:
line = f.readline()
if line == '':
break
re_result = re.match('\s*define\s+symbol\s+__ICFEDIT_size_heap__\s*=\s*0[xX][0-9a-fA-F]+', line)
if re_result != None:
oldline = line
newline = re.sub('0[xX][0-9a-fA-F]+','0x000', oldline)
flag_need_replace = True
break
if flag_need_replace == True:
f.seek(0)
content = f.read()
f.seek(0)
f.truncate()
newcontent = content.replace(oldline, newline)
f.write(newcontent)
elif os.path.splitext(file)[1] == '.lds': # find .lds files (GCC)
with open(file_path,'r+',) as f:
flag_need_replace = False
while True:
line = f.readline()
if line == '':
break
re_result = re.match('\s*_system_stack_size\s*=\s*0[xX][0-9a-fA-F]+', line)
if re_result != None:
oldline = line
newline = re.sub('0[xX][0-9a-fA-F]+','0x400', oldline)
flag_need_replace = True
break
if flag_need_replace == True:
f.seek(0)
content = f.read()
f.seek(0)
f.truncate()
newcontent = content.replace(oldline, newline)
f.write(newcontent)
def stm32_update(path):
stm32update_main2entry(path)
stm32update_heap2zero(path)
+5
View File
@@ -0,0 +1,5 @@
scons>=4.0.1
requests>=2.27.1
tqdm>=4.67.1
kconfiglib>=13.7.1
PyYAML>=6.0
+476
View File
@@ -0,0 +1,476 @@
#! /usr/bin/env python
#coding=utf-8
#
# File : sconsui.py
# This file is part of RT-Thread RTOS
# COPYRIGHT (C) 2006 - 2015, RT-Thread Development Team
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Change Logs:
# Date Author Notes
# 2015-01-20 Bernard Add copyright information
#
import sys
py2 = py30 = py31 = False
version = sys.hexversion
if version >= 0x020600F0 and version < 0x03000000 :
py2 = True # Python 2.6 or 2.7
from Tkinter import *
import ttk
elif version >= 0x03000000 and version < 0x03010000 :
py30 = True
from tkinter import *
import ttk
elif version >= 0x03010000:
py31 = True
from tkinter import *
import tkinter.ttk as ttk
else:
print ("""
You do not have a version of python supporting ttk widgets..
You need a version >= 2.6 to execute PAGE modules.
""")
sys.exit()
import ScrolledText
import tkFileDialog
import tkMessageBox
import os
import threading
import platform
builder = None
executor = None
lock = None
class CmdExecutor(threading.Thread):
def __init__(self, cmd, output):
threading.Thread.__init__(self)
self.cmd = cmd
self.child = None
def run(self):
global executor, builder, lock
if platform.system() == 'Windows':
try:
from win32spawn import Win32Spawn
subprocess = Win32Spawn(self.cmd)
subprocess.start_pipe()
builder.progressbar.start()
while not subprocess.is_terminated or subprocess.qsize() > 0:
try:
line = subprocess.get(timeout=1)
line = line.replace('\r', '')
if line:
lock.acquire()
builder.output.see(END)
builder.output.insert(END, line)
lock.release()
except:
pass
builder.progressbar.stop()
except:
pass
executor = None
if builder.is_makeing_project:
builder.output.insert(END, 'Done')
builder.is_makeing_project = False
def ExecCmd(cmd):
global executor
if executor:
print 'last task does not exit'
return
executor = CmdExecutor(cmd, builder)
executor.start()
class DirSelectBox(ttk.Frame):
def __init__(self, master=None, **kw):
ttk.Frame.__init__(self, master, **kw)
self.dir_var = StringVar()
self.entry = ttk.Entry(self, textvariable = self.dir_var)
self.entry.pack(fill=BOTH, expand=1,side=LEFT)
self.entry.configure(width = 50)
self.browser_button = ttk.Button(self, text="Browser", command=self.browser)
self.browser_button.pack(side=RIGHT)
def browser(self):
dir = tkFileDialog.askdirectory(parent=self, title='Open directory', initialdir=self.dir_var.get())
if dir != '':
self.dir_var.set(dir)
def set_path(self, path):
path = path.replace('\\', '/')
self.dir_var.set(path)
def get_path(self):
return self.dir_var.get()
COMPILER = [
("GNU GCC", "GCC"),
("Keil ARMCC", "ARMCC"),
("IAR Compiler", "IAR"),
]
IDE = [
('Keil MDK4', 'mdk4'),
('Keil MDK', 'mdk'),
('IAR Compiler', 'iar')
]
class SconsUI():
def __init__(self, master=None):
style = ttk.Style()
theme = style.theme_use()
default = style.lookup(theme, 'background')
master.configure(background=default)
notebook = ttk.Notebook(master)
notebook.pack(fill=BOTH, padx=5, pady=5)
# building page
page_building = ttk.Frame(notebook)
notebook.add(page_building, padding=3)
notebook.tab(0, text='Build', underline="-1")
self.setup_building_ui(page_building)
self.building_page = page_building
# make project page
page_project = ttk.Frame(notebook)
notebook.add(page_project, padding = 3)
notebook.tab(1, text = 'Project', underline = '-1')
self.setup_project_ui(page_project)
self.project_page = page_project
# setting page
page_setting = ttk.Frame(notebook)
notebook.add(page_setting, padding = 3)
notebook.tab(2, text = 'Setting', underline = '-1')
self.setup_setting_ui(page_setting)
self.setting_page = page_setting
padding = ttk.Frame(master)
padding.pack(fill=X)
quit = ttk.Button(padding, text='Quit', command = self.quit)
quit.pack(side=RIGHT)
# set notebook to self
self.notebook = notebook
# read setting
self.read_setting()
self.is_makeing_project = False
def read_setting(self):
import platform
import os
home = ''
if platform.system() == 'Windows':
driver = os.environ['HOMEDRIVE']
home = os.environ['HOMEPATH']
home = os.path.join(driver, home)
else:
home = os.environ['HOME']
setting_path = os.path.join(home, '.rtt_scons')
if os.path.exists(setting_path):
setting = open(os.path.join(home, '.rtt_scons'))
for line in setting:
line = line.replace('\n', '')
line = line.replace('\r', '')
if line.find('=') != -1:
items = line.split('=')
if items[0] == 'RTTRoot':
self.RTTRoot.set_path(items[1])
elif items[0] == 'BSPRoot':
self.BSPRoot.set_path(items[1])
elif items[0] == 'compiler':
compiler = items[1]
else:
self.CompilersPath[items[0]].set_path(items[1])
setting.close()
# set RT-Thread Root Directory according environ
if 'RTT_ROOT' in os.environ:
self.RTTRoot.set_path(os.environ['RTT_ROOT'])
if self.RTTRoot.get_path() == '':
rtt_root = ''
# detect RT-Thread directory
if os.path.exists(os.path.join('..', 'include', 'rtthread.h')):
rtt_root = os.path.join('..')
elif os.path.exists(os.path.join('..', '..', 'include', 'rtthread.h')):
rtt_root = os.path.join('..', '..')
if rtt_root:
self.RTTRoot.set_path(os.path.abspath(rtt_root))
# detect compiler path
if platform.system() == 'Windows':
# Keil MDK
if not self.CompilersPath['ARMCC'].get_path():
if os.path.exists('C:\\Keil'):
self.CompilersPath['ARMCC'].set_path('C:\\Keil')
elif os.path.exists('D:\\Keil'):
self.CompilersPath['ARMCC'].set_path('D:\\Keil')
elif os.path.exists('E:\\Keil'):
self.CompilersPath['ARMCC'].set_path('E:\\Keil')
elif os.path.exists('F:\\Keil'):
self.CompilersPath['ARMCC'].set_path('F:\\Keil')
elif os.path.exists('G:\\Keil'):
self.CompilersPath['ARMCC'].set_path('G:\\Keil')
# GNU GCC
if not self.CompilersPath['GCC'].get_path():
paths = os.environ['PATH']
paths = paths.split(';')
for path in paths:
if path.find('CodeSourcery') != -1:
self.CompilersPath['GCC'].set_path(path)
break
elif path.find('GNU Tools ARM Embedded') != -1:
self.CompilersPath['GCC'].set_path(path)
break
def save_setting(self):
import platform
import os
home = ''
if platform.system() == 'Windows':
driver = os.environ['HOMEDRIVE']
home = os.environ['HOMEPATH']
home = os.path.join(driver, home)
else:
home = os.environ['HOME']
setting = open(os.path.join(home, '.rtt_scons'), 'w+')
# current comiler
# line = '%s=%s\n' % ('compiler', self.compilers.get()))
line = '%s=%s\n' % ('compiler', 'iar')
setting.write(line)
# RTT Root Folder
if self.RTTRoot.get_path():
line = '%s=%s\n' % ('RTTRoot', self.RTTRoot.get_path())
setting.write(line)
# BSP Root Folder
if self.BSPRoot.get_path():
line = '%s=%s\n' % ('BSPRoot', self.BSPRoot.get_path())
setting.write(line)
for (compiler, path) in self.CompilersPath.iteritems():
if path.get_path():
line = '%s=%s\n' % (compiler, path.get_path())
setting.write(line)
setting.close()
tkMessageBox.showinfo("RT-Thread SCons UI",
"Save setting successfully")
def setup_building_ui(self, frame):
padding = ttk.Frame(frame)
padding.pack(fill=X)
button = ttk.Button(padding, text='Clean', command=self.do_clean)
button.pack(side=RIGHT)
button = ttk.Button(padding, text='Build', command=self.do_build)
button.pack(side=RIGHT)
label = ttk.Label(padding, relief = 'flat', text = 'Click Build or Clean to build or clean system -->')
label.pack(side=RIGHT, ipady = 5)
self.progressbar = ttk.Progressbar(frame)
self.progressbar.pack(fill=X)
separator = ttk.Separator(frame)
separator.pack(fill=X)
self.output = ScrolledText.ScrolledText(frame)
self.output.pack(fill=X)
def setup_project_ui(self, frame):
label = ttk.Label(frame, relief = 'flat', text = 'Choose Integrated Development Environment:')
label.pack(fill=X, pady = 5)
separator = ttk.Separator(frame)
separator.pack(fill=X)
self.ide = StringVar()
self.ide.set("mdk4") # initialize
for text,mode in IDE:
radiobutton = ttk.Radiobutton(frame, text=text, variable = self.ide, value = mode)
radiobutton.pack(fill=X, padx=10)
bottom = ttk.Frame(frame)
bottom.pack(side=BOTTOM, fill=X)
button = ttk.Button(bottom, text="Make Project", command = self.do_make_project)
button.pack(side=RIGHT, padx = 10, pady = 10)
def setup_setting_ui(self, frame):
row = 0
label = ttk.Label (frame, relief = 'flat', text='RT-Thread Root Folder:')
label.grid(row=row, column=0,ipadx=5, ipady=5, padx = 5)
self.RTTRoot = DirSelectBox(frame)
self.RTTRoot.grid(row=row, column=1, sticky=E+W)
row = row + 1
label = ttk.Label (frame, relief = 'flat', text='Board Support Folder:')
label.grid(row=row, column=0,ipadx=5, ipady=5, padx = 5)
self.BSPRoot = DirSelectBox(frame)
self.BSPRoot.grid(row=row, column=1, sticky=E+W)
row = row + 1
label = ttk.Label (frame, relief='flat', text='Toolchain:')
label.grid(row=row, column=0,ipadx=5, ipady=5, sticky=E+W)
row = row + 1
separator = ttk.Separator(frame)
separator.grid(row = row, column = 0, columnspan = 2, sticky = E+W)
row = row + 1
self.compilers = StringVar()
self.compilers.set("GCC") # initialize
self.CompilersPath = {}
for text,compiler in COMPILER:
radiobutton = ttk.Radiobutton(frame, text=text, variable = self.compilers, value = compiler)
radiobutton.grid(row=row, column = 0, sticky = W, ipadx = 5, ipady = 5, padx = 20)
self.CompilersPath[compiler] = DirSelectBox(frame)
self.CompilersPath[compiler].grid(row=row, column=1, sticky=E+W)
row = row + 1
button = ttk.Button(frame, text='Save Setting', command = self.save_setting)
button.grid(row = row, column = 1, sticky = E)
row = row + 1
def prepare_build(self):
# get compiler
compiler = self.compilers.get()
if compiler == 'GCC':
compiler = 'gcc'
elif compiler == 'ARMCC':
compiler = 'keil'
elif compiler == 'IAR':
compiler = 'iar'
# get RTT Root
rtt_root = self.RTTRoot.get_path()
# get Compiler path
exec_path = self.CompilersPath[self.compilers.get()].get_path()
command = ''
os.environ['RTT_ROOT'] = rtt_root
os.environ['RTT_CC'] = compiler
os.environ['RTT_EXEC_PATH'] = exec_path
return command
def check_path(self):
result = True
if self.BSPRoot.get_path() == '':
result = False
if self.RTTRoot.get_path() == '':
result = False
if not result:
tkMessageBox.showinfo("RT-Thread SCons UI",
"Folder is empty, please choose correct directory.")
return result
def do_build(self):
self.prepare_build()
command = 'scons'
if not self.check_path():
return
bsp = self.BSPRoot.get_path()
os.chdir(bsp)
self.output.delete(1.0, END)
self.output.insert(END, 'building project...\n')
ExecCmd(command)
def do_clean(self):
self.prepare_build()
command = 'scons -c'
if not self.check_path():
return
bsp = self.BSPRoot.get_path()
os.chdir(bsp)
self.output.delete(1.0, END)
self.output.insert(END, 'clean project...\n')
ExecCmd(command)
def do_make_project(self):
ide = self.ide.get()
self.prepare_build()
command = 'scons --target=%s -s' % ide
if not self.check_path():
return
# select build page
self.notebook.select(self.building_page)
bsp = self.BSPRoot.get_path()
os.chdir(bsp)
self.output.delete(1.0, END)
self.output.insert(END, 'Generate project ...\n')
self.is_makeing_project = True
ExecCmd(command)
def quit(self):
exit(0)
def StartSConsUI(path=None):
global val, root, builder, lock
root = Tk()
root.title('RT-Thread SCons UI')
#root.geometrygeometry('590x510+50+50')
lock = threading.RLock()
builder = SconsUI(root)
if path:
builder.BSPRoot.set_path(path)
root.mainloop()
if __name__ == '__main__':
StartSConsUI()
+79
View File
@@ -0,0 +1,79 @@
# -*- coding: utf-8 -*-
#
# File : __init__.py
# This file is part of RT-Thread RTOS
# COPYRIGHT (C) 2006 - 2015, RT-Thread Development Team
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Change Logs:
# Date Author Notes
# 2025-01-XX Bernard Create targets module for IDE project generators
# Import all target generators
from . import keil
from . import iar
from . import vs
from . import vs2012
from . import codeblocks
from . import ua
from . import vsc
from . import cdk
from . import ses
from . import eclipse
from . import codelite
from . import cmake
from . import xmake
from . import esp_idf
from . import zigbuild
from . import makefile
from . import rt_studio
# Export all target generator functions
__all__ = [
# Keil MDK
'keil',
# IAR
'iar',
# Visual Studio
'vs',
'vs2012',
# Code::Blocks
'codeblocks',
# Universal ARM
'ua',
# VSCode
'vsc',
# CDK
'cdk',
# SEGGER Embedded Studio
'ses',
# Eclipse
'eclipse',
# CodeLite
'codelite',
# CMake
'cmake',
# XMake
'xmake',
# ESP-IDF
'esp_idf',
# Zig
'zigbuild',
# Make
'makefile',
# RT-Studio
'rt_studio'
]
+137
View File
@@ -0,0 +1,137 @@
#
# File : keil.py
# This file is part of RT-Thread RTOS
# COPYRIGHT (C) 2006 - 2015, RT-Thread Development Team
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Change Logs:
# Date Author Notes
# 2017-10-16 Tanek Add CDK IDE support
#
import os
import sys
import string
import xml.etree.ElementTree as etree
from xml.etree.ElementTree import SubElement
from utils import _make_path_relative
from utils import xml_indent
def SDKAddGroup(ProjectFiles, parent, name, files, project_path):
# don't add an empty group
if len(files) == 0:
return
group = SubElement(parent, 'VirtualDirectory', attrib={'Name': name})
for f in files:
fn = f.rfile()
name = fn.name
path = os.path.dirname(fn.abspath)
basename = os.path.basename(path)
path = _make_path_relative(project_path, path)
elm_attr_name = os.path.join(path, name)
file = SubElement(group, 'File', attrib={'Name': elm_attr_name})
return group
def _CDKProject(tree, target, script):
project_path = os.path.dirname(os.path.abspath(target))
root = tree.getroot()
out = open(target, 'w')
out.write('<?xml version="1.0" encoding="UTF-8"?>\n')
CPPPATH = []
CPPDEFINES = []
LINKFLAGS = ''
CCFLAGS = ''
LIBS = []
ProjectFiles = []
for child in root:
if child.tag == 'VirtualDirectory':
root.remove(child)
for group in script:
group_tree = SDKAddGroup(ProjectFiles, root, group['name'], group['src'], project_path)
# get each include path
if 'CPPPATH' in group and group['CPPPATH']:
if CPPPATH:
CPPPATH += group['CPPPATH']
else:
CPPPATH += group['CPPPATH']
# get each group's definitions
if 'CPPDEFINES' in group and group['CPPDEFINES']:
if CPPDEFINES:
CPPDEFINES += group['CPPDEFINES']
else:
CPPDEFINES += group['CPPDEFINES']
# get each group's cc flags
if 'CCFLAGS' in group and group['CCFLAGS']:
if CCFLAGS:
CCFLAGS += ' ' + group['CCFLAGS']
else:
CCFLAGS += group['CCFLAGS']
# get each group's link flags
if 'LINKFLAGS' in group and group['LINKFLAGS']:
if LINKFLAGS:
LINKFLAGS += ' ' + group['LINKFLAGS']
else:
LINKFLAGS += group['LINKFLAGS']
# todo: cdk add lib
if 'LIBS' in group and group['LIBS']:
LIBS += group['LIBS']
# write include path, definitions and link flags
text = ';'.join([_make_path_relative(project_path, os.path.normpath(i)) for i in CPPPATH])
IncludePath = tree.find('BuildConfigs/BuildConfig/Compiler/IncludePath')
IncludePath.text = text
IncludePath = tree.find('BuildConfigs/BuildConfig/Asm/IncludePath')
IncludePath.text = text
Define = tree.find('BuildConfigs/BuildConfig/Compiler/Define')
Define.text = '; '.join(set(CPPDEFINES))
CC_Misc = tree.find('BuildConfigs/BuildConfig/Compiler/OtherFlags')
CC_Misc.text = CCFLAGS
LK_Misc = tree.find('BuildConfigs/BuildConfig/Linker/OtherFlags')
LK_Misc.text = LINKFLAGS
LibName = tree.find('BuildConfigs/BuildConfig/Linker/LibName')
if LibName.text:
LibName.text=LibName.text+';'+';'.join(LIBS)
else:
LibName.text=';'.join(LIBS)
xml_indent(root)
out.write(etree.tostring(root, encoding='utf-8'))
out.close()
def CDKProject(target, script):
template_tree = etree.parse('template.cdkproj')
_CDKProject(template_tree, target, script)
+345
View File
@@ -0,0 +1,345 @@
"""
* Copyright (c) 2006-2025 RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2019-05-24 klivelinux first version
* 2021-04-19 liukangcc add c++ support and libpath
* 2021-06-25 Guozhanxin fix path issue
* 2021-06-30 Guozhanxin add scons --target=cmake-armclang
* 2022-03-16 liukangcc 通过 SCons生成 CMakefile.txt 使用相对路径
* 2022-04-12 mysterywolf rtconfig.CROSS_TOOL->rtconfig.PLATFORM
* 2022-04-29 SunJun8 默认开启生成编译数据库
* 2024-03-18 wirano fix the issue of the missing link flags added in Sconscript
* 2024-07-04 kaidegit Let cmake generator get more param from `rtconfig.py`
* 2024-08-07 imi415 Updated CMake generator handles private macros, using OBJECT and INTERFACE libraries.
* 2024-11-18 kaidegit fix processing groups with similar name
* 2025-02-22 kaidegit fix missing some flags added in Sconscript
* 2025-02-24 kaidegit remove some code that is unnecessary but takes time, get them from env
"""
import os
import sys
import re
import utils
import rtconfig
from utils import _make_path_relative
from collections import defaultdict, Counter
def GenerateCFiles(env, project, project_name):
"""
Generate CMakeLists.txt files
"""
PROJECT_NAME = project_name if project_name != "project" else "rtthread"
tool_path_conv = defaultdict(lambda : {"name":"", "path": ""})
tool_path_conv_helper = lambda tool: {"name": tool, "path": os.path.join(rtconfig.EXEC_PATH, tool).replace('\\', "/")}
tool_path_conv["CMAKE_C_COMPILER"] = tool_path_conv_helper(rtconfig.CC)
if 'CXX' in dir(rtconfig):
tool_path_conv["CMAKE_CXX_COMPILER"] = tool_path_conv_helper(rtconfig.CXX)
tool_path_conv["CMAKE_ASM_COMPILER"] = tool_path_conv_helper(rtconfig.AS)
tool_path_conv["CMAKE_AR"] = tool_path_conv_helper(rtconfig.AR)
tool_path_conv["CMAKE_LINKER"] = tool_path_conv_helper(rtconfig.LINK)
if rtconfig.PLATFORM in ['gcc']:
tool_path_conv["CMAKE_SIZE"] = tool_path_conv_helper(rtconfig.SIZE)
tool_path_conv["CMAKE_OBJDUMP"] = tool_path_conv_helper(rtconfig.OBJDUMP)
tool_path_conv["CMAKE_OBJCOPY"] = tool_path_conv_helper(rtconfig.OBJCPY)
elif rtconfig.PLATFORM in ['armcc', 'armclang']:
tool_path_conv["CMAKE_FROMELF"] = tool_path_conv_helper(rtconfig.FROMELF)
CC = tool_path_conv["CMAKE_C_COMPILER"]["path"]
CXX = tool_path_conv["CMAKE_CXX_COMPILER"]["path"]
AS = tool_path_conv["CMAKE_ASM_COMPILER"]["path"]
AR = tool_path_conv["CMAKE_AR"]["path"]
LINK = tool_path_conv["CMAKE_LINKER"]["path"]
SIZE = tool_path_conv["CMAKE_SIZE"]["path"]
OBJDUMP = tool_path_conv["CMAKE_OBJDUMP"]["path"]
OBJCOPY = tool_path_conv["CMAKE_OBJCOPY"]["path"]
FROMELF = tool_path_conv["CMAKE_FROMELF"]["path"]
CFLAGS = env['CFLAGS'].replace('\\', "/").replace('\"', "\\\"")
if 'CXXFLAGS' in dir(rtconfig):
cflag_str=''.join(env['CXXFLAGS'])
CXXFLAGS = cflag_str.replace('\\', "/").replace('\"', "\\\"")
else:
CXXFLAGS = CFLAGS
AFLAGS = env['ASFLAGS'].replace('\\', "/").replace('\"', "\\\"")
LFLAGS = env['LINKFLAGS'].replace('\\', "/").replace('\"', "\\\"")
POST_ACTION = rtconfig.POST_ACTION
# replace the tool name with the cmake variable
for cmake_var, each_tool in tool_path_conv.items():
tool_name = each_tool['name']
if tool_name == "": continue
if "win32" in sys.platform:
while f"{tool_name}.exe" in POST_ACTION: # find the tool with `.exe` suffix first
POST_ACTION = POST_ACTION.replace(tool_name, "string_to_replace")
while tool_name in POST_ACTION:
POST_ACTION = POST_ACTION.replace(tool_name, "string_to_replace")
while "string_to_replace" in POST_ACTION:
POST_ACTION = POST_ACTION.replace("string_to_replace", f"${{{cmake_var}}}")
# replace the `$TARGET` with `${CMAKE_PROJECT_NAME}.elf`
while "$TARGET" in POST_ACTION:
POST_ACTION = POST_ACTION.replace("$TARGET", "${CMAKE_PROJECT_NAME}.elf")
# add COMMAAND before each command
POST_ACTION = POST_ACTION.split('\n')
POST_ACTION = [each_line.strip() for each_line in POST_ACTION]
POST_ACTION = [f"\tCOMMAND {each_line}" for each_line in POST_ACTION if each_line != '']
POST_ACTION = "\n".join(POST_ACTION)
if "win32" in sys.platform:
CC += ".exe"
if CXX != '':
CXX += ".exe"
AS += ".exe"
AR += ".exe"
LINK += ".exe"
if rtconfig.PLATFORM in ['gcc']:
SIZE += ".exe"
OBJDUMP += ".exe"
OBJCOPY += ".exe"
elif rtconfig.PLATFORM in ['armcc', 'armclang']:
FROMELF += ".exe"
if not os.path.exists(CC) or not os.path.exists(AS) or not os.path.exists(AR) or not os.path.exists(LINK):
print("'Cannot found toolchain directory, please check RTT_CC and RTT_EXEC_PATH'")
sys.exit(-1)
with open("CMakeLists.txt", "w") as cm_file:
cm_file.write("CMAKE_MINIMUM_REQUIRED(VERSION 3.10)\n\n")
cm_file.write("SET(CMAKE_SYSTEM_NAME Generic)\n")
cm_file.write("SET(CMAKE_SYSTEM_PROCESSOR " + rtconfig.CPU +")\n")
cm_file.write("#SET(CMAKE_VERBOSE_MAKEFILE ON)\n\n")
cm_file.write("SET(CMAKE_EXPORT_COMPILE_COMMANDS ON)\n\n")
cm_file.write("SET(CMAKE_C_COMPILER \""+ CC + "\")\n")
cm_file.write("SET(CMAKE_ASM_COMPILER \""+ AS + "\")\n")
cm_file.write("SET(CMAKE_C_FLAGS \""+ CFLAGS + "\")\n")
cm_file.write("SET(CMAKE_ASM_FLAGS \""+ AFLAGS + "\")\n")
cm_file.write("SET(CMAKE_C_COMPILER_WORKS TRUE)\n\n")
if CXX != '':
cm_file.write("SET(CMAKE_CXX_COMPILER \""+ CXX + "\")\n")
cm_file.write("SET(CMAKE_CXX_FLAGS \""+ CXXFLAGS + "\")\n")
cm_file.write("SET(CMAKE_CXX_COMPILER_WORKS TRUE)\n\n")
if rtconfig.PLATFORM in ['gcc']:
cm_file.write("SET(CMAKE_OBJCOPY \""+ OBJCOPY + "\")\n")
cm_file.write("SET(CMAKE_SIZE \""+ SIZE + "\")\n\n")
elif rtconfig.PLATFORM in ['armcc', 'armclang']:
cm_file.write("SET(CMAKE_FROMELF \""+ FROMELF + "\")\n\n")
LINKER_FLAGS = ''
LINKER_LIBS = ''
if rtconfig.PLATFORM in ['gcc']:
LINKER_FLAGS += '-T'
elif rtconfig.PLATFORM in ['armcc', 'armclang']:
LINKER_FLAGS += '--scatter'
for group in project:
if 'LIBPATH' in group.keys():
for f in group['LIBPATH']:
LINKER_LIBS += ' --userlibpath ' + f.replace("\\", "/")
for group in project:
if 'LIBS' in group.keys():
for f in group['LIBS']:
LINKER_LIBS += ' ' + f.replace("\\", "/") + '.lib'
cm_file.write("SET(CMAKE_EXE_LINKER_FLAGS \""+ re.sub(LINKER_FLAGS + r'(\s*)', LINKER_FLAGS + r' ${CMAKE_SOURCE_DIR}/', LFLAGS) + LINKER_LIBS + "\")\n\n")
# get the c/cpp standard version from compilation flags
# not support the version with alphabet in `-std` param yet
pattern = re.compile(r'-std=[\w+]+')
c_standard = 11
if '-std=' in CFLAGS:
c_standard = re.search(pattern, CFLAGS).group(0)
c_standard = "".join([each for each in c_standard if each.isdigit()])
else:
print(f"Cannot find the param of the c standard in build flag, set to default {c_standard}")
cm_file.write(f"SET(CMAKE_C_STANDARD {c_standard})\n")
if CXX != '':
cpp_standard = 17
if '-std=' in CXXFLAGS:
cpp_standard = re.search(pattern, CXXFLAGS).group(0)
cpp_standard = "".join([each for each in cpp_standard if each.isdigit()])
else:
print(f"Cannot find the param of the cpp standard in build flag, set to default {cpp_standard}")
cm_file.write(f"SET(CMAKE_CXX_STANDARD {cpp_standard})\n")
cm_file.write('\n')
cm_file.write(f"PROJECT({PROJECT_NAME} C {'CXX' if CXX != '' else ''} ASM)\n")
cm_file.write('\n')
cm_file.write("INCLUDE_DIRECTORIES(\n")
for i in env['CPPPATH']:
# use relative path
path = _make_path_relative(os.getcwd(), i)
cm_file.write( "\t" + path.replace("\\", "/") + "\n")
cm_file.write(")\n\n")
cm_file.write("ADD_DEFINITIONS(\n")
for i in env['CPPDEFINES']:
cm_file.write("\t-D" + i + "\n")
cm_file.write(")\n\n")
libgroups = []
interfacelibgroups = []
for group in project:
if group['name'] == 'Applications':
continue
# When a group is provided without sources, add it to the <INTERFACE> library list
if len(group['src']) == 0:
interfacelibgroups.append(group)
else:
libgroups.append(group)
# Process groups whose names differ only in capitalization.
# (Groups have same name should be merged into one before)
for group in libgroups:
group['alias'] = group['name'].lower()
names = [group['alias'] for group in libgroups]
counter = Counter(names)
names = [name for name in names if counter[name] > 1]
for group in libgroups:
if group['alias'] in names:
counter[group['alias']] -= 1
group['alias'] = f"{group['name']}_{counter[group['alias']]}"
print(f"Renamed {group['name']} to {group['alias']}")
group['name'] = group['alias']
cm_file.write("# Library source files\n")
for group in project:
cm_file.write("SET(RT_{:s}_SOURCES\n".format(group['name'].upper()))
for f in group['src']:
# use relative path
path = _make_path_relative(os.getcwd(), os.path.normpath(f.rfile().abspath))
cm_file.write( "\t" + path.replace("\\", "/") + "\n" )
cm_file.write(")\n\n")
cm_file.write("# Library search paths\n")
for group in libgroups + interfacelibgroups:
if not 'LIBPATH' in group.keys():
continue
if len(group['LIBPATH']) == 0:
continue
cm_file.write("SET(RT_{:s}_LINK_DIRS\n".format(group['name'].upper()))
for f in group['LIBPATH']:
cm_file.write("\t"+ f.replace("\\", "/") + "\n" )
cm_file.write(")\n\n")
cm_file.write("# Library local macro definitions\n")
for group in libgroups:
if not 'LOCAL_CPPDEFINES' in group.keys():
continue
if len(group['LOCAL_CPPDEFINES']) == 0:
continue
cm_file.write("SET(RT_{:s}_DEFINES\n".format(group['name'].upper()))
for f in group['LOCAL_CPPDEFINES']:
cm_file.write("\t"+ f.replace("\\", "/") + "\n" )
cm_file.write(")\n\n")
cm_file.write("# Library dependencies\n")
for group in libgroups + interfacelibgroups:
if not 'LIBS' in group.keys():
continue
if len(group['LIBS']) == 0:
continue
cm_file.write("SET(RT_{:s}_LIBS\n".format(group['name'].upper()))
for f in group['LIBS']:
cm_file.write("\t"+ "{}\n".format(f.replace("\\", "/")))
cm_file.write(")\n\n")
cm_file.write("# Libraries\n")
for group in libgroups:
cm_file.write("ADD_LIBRARY(rtt_{:s} OBJECT ${{RT_{:s}_SOURCES}})\n"
.format(group['name'], group['name'].upper()))
cm_file.write("\n")
cm_file.write("# Interface libraries\n")
for group in interfacelibgroups:
cm_file.write("ADD_LIBRARY(rtt_{:s} INTERFACE)\n".format(group['name']))
cm_file.write("\n")
cm_file.write("# Private macros\n")
for group in libgroups:
if not 'LOCAL_CPPDEFINES' in group.keys():
continue
if len(group['LOCAL_CPPDEFINES']) == 0:
continue
cm_file.write("TARGET_COMPILE_DEFINITIONS(rtt_{:s} PRIVATE ${{RT_{:s}_DEFINES}})\n"
.format(group['name'], group['name'].upper()))
cm_file.write("\n")
cm_file.write("# Interface library search paths\n")
if rtconfig.PLATFORM in ['gcc']:
for group in libgroups:
if not 'LIBPATH' in group.keys():
continue
if len(group['LIBPATH']) == 0:
continue
cm_file.write("TARGET_LINK_DIRECTORIES(rtt_{:s} INTERFACE ${{RT_{:s}_LINK_DIRS}})\n"
.format(group['name'], group['name'].upper()))
for group in libgroups:
if not 'LIBS' in group.keys():
continue
if len(group['LIBS']) == 0:
continue
cm_file.write("TARGET_LINK_LIBRARIES(rtt_{:s} INTERFACE ${{RT_{:s}_LIBS}})\n"
.format(group['name'], group['name'].upper()))
cm_file.write("\n")
cm_file.write("ADD_EXECUTABLE(${CMAKE_PROJECT_NAME}.elf ${RT_APPLICATIONS_SOURCES})\n")
cm_file.write("TARGET_LINK_LIBRARIES(${CMAKE_PROJECT_NAME}.elf\n")
for group in libgroups + interfacelibgroups:
cm_file.write("\trtt_{:s}\n".format(group['name']))
cm_file.write(")\n\n")
cm_file.write("ADD_CUSTOM_COMMAND(TARGET ${CMAKE_PROJECT_NAME}.elf POST_BUILD \n" + POST_ACTION + '\n)\n')
# auto inclue `custom.cmake` for user custom settings
custom_cmake = \
'''
# if custom.cmake is exist, add it
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/custom.cmake)
include(${CMAKE_CURRENT_SOURCE_DIR}/custom.cmake)
endif()
'''
custom_cmake = custom_cmake.split('\n')
custom_cmake = [each.strip() for each in custom_cmake]
custom_cmake = "\n".join(custom_cmake)
cm_file.write(custom_cmake)
return
def CMakeProject(env, project, project_name):
print('Update setting files for CMakeLists.txt...')
GenerateCFiles(env, project, project_name)
print('Done!')
return
+143
View File
@@ -0,0 +1,143 @@
#
# File : codeblocks.py
# This file is part of RT-Thread RTOS
# COPYRIGHT (C) 2006 - 2015, RT-Thread Development Team
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Change Logs:
# Date Author Notes
# 2015-01-20 Bernard Add copyright information
#
import os
import sys
import string
import uuid
import utils
from xml.etree.ElementTree import SubElement
from utils import _make_path_relative
from utils import xml_indent
# Add parent directory to path to import building
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import building
import xml.etree.ElementTree as etree
fs_encoding = sys.getfilesystemencoding()
def CB_AddHeadFiles(program, elem, project_path):
utils.source_ext = []
utils.source_ext = ["h"]
for item in program:
utils.walk_children(item)
utils.source_list.sort()
# print utils.source_list
for f in utils.source_list:
path = _make_path_relative(project_path, f)
Unit = SubElement(elem, 'Unit')
Unit.set('filename', path.decode(fs_encoding))
def CB_AddCFiles(ProjectFiles, parent, gname, files, project_path):
for f in files:
fn = f.rfile()
name = fn.name
path = os.path.dirname(fn.abspath)
path = _make_path_relative(project_path, path)
path = os.path.join(path, name)
Unit = SubElement(parent, 'Unit')
Unit.set('filename', path.decode(fs_encoding))
Option = SubElement(Unit, 'Option')
Option.set('compilerVar', "CC")
def CBProject(target, script, program):
project_path = os.path.dirname(os.path.abspath(target))
if os.path.isfile('template.cbp'):
tree = etree.parse('template.cbp')
else:
tree = etree.parse(os.path.join(os.path.dirname(__file__), 'template.cbp'))
root = tree.getroot()
out = open(target, 'w')
out.write('<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>\n')
ProjectFiles = []
# SECTION 1. add "*.c|*.h" files group
for elem in tree.iter(tag='Project'):
# print elem.tag, elem.attrib
break
# add c files
for group in script:
group_xml = CB_AddCFiles(ProjectFiles, elem, group['name'], group['src'], project_path)
# add h files
CB_AddHeadFiles(program, elem, project_path)
# SECTION 2.
# write head include path
if 'CPPPATH' in building.Env:
cpp_path = building.Env['CPPPATH']
paths = set()
for path in cpp_path:
inc = _make_path_relative(project_path, os.path.normpath(path))
paths.add(inc) #.replace('\\', '/')
paths = [i for i in paths]
paths.sort()
# write include path, definitions
for elem in tree.iter(tag='Compiler'):
break
for path in paths:
Add = SubElement(elem, 'Add')
Add.set('directory', path)
for macro in building.Env.get('CPPDEFINES', []):
Add = SubElement(elem, 'Add')
for d in macro:
Add.set('option', "-D"+d)
# write link flags
'''
# write lib dependence
if 'LIBS' in building.Env:
for elem in tree.iter(tag='Tool'):
if elem.attrib['Name'] == 'VCLinkerTool':
break
libs_with_extention = [i+'.lib' for i in building.Env['LIBS']]
libs = ' '.join(libs_with_extention)
elem.set('AdditionalDependencies', libs)
# write lib include path
if 'LIBPATH' in building.Env:
lib_path = building.Env['LIBPATH']
paths = set()
for path in lib_path:
inc = _make_path_relative(project_path, os.path.normpath(path))
paths.add(inc) #.replace('\\', '/')
paths = [i for i in paths]
paths.sort()
lib_paths = ';'.join(paths)
elem.set('AdditionalLibraryDirectories', lib_paths)
'''
xml_indent(root)
out.write(etree.tostring(root, encoding='utf-8'))
out.close()
+217
View File
@@ -0,0 +1,217 @@
#
# File : codelite.py
# This file is part of RT-Thread RTOS
# COPYRIGHT (C) 2006 - 2020, RT-Thread Development Team
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Change Logs:
# Date Author Notes
# 2020-10-14 LiuMin Add copyright information
#
import os
import sys
import string
import uuid
import utils
from xml.etree.ElementTree import SubElement
from utils import _make_path_relative
from utils import xml_indent
# Add parent directory to path to import building
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import building
import xml.etree.ElementTree as etree
fs_encoding = sys.getfilesystemencoding()
def CLSetCFlags(root, flags):
node = root.find('Settings').find('Configuration').find('Compiler')
node.attrib['C_Options'] = flags
def CLSetCxxFlags(root, flags):
node = root.find('Settings').find('Configuration').find('Compiler')
node.attrib['Options'] = flags
def CLSetAsFlags(root, flags):
node = root.find('Settings').find('Configuration').find('Compiler')
node.attrib['Assembler'] = flags
def CLAddIncludePath(root, path):
node = root.find('Settings').find('Configuration').find('Compiler')
node = SubElement(node, 'IncludePath')
node.attrib['Value'] = path
def CLAddPreprocessor(root, value):
node = root.find('Settings').find('Configuration').find('Compiler')
node = SubElement(node, 'Preprocessor')
node.attrib['Value'] = value
def CLSetLdFlags(root, flags):
node = root.find('Settings').find('Configuration').find('Linker')
node.attrib['Options'] = flags
def CLAddLibrary_path(root, path):
node = root.find('Settings').find('Configuration').find('Linker')
node = SubElement(node, 'LibraryPath')
node.attrib['Value'] = path
def CLAddLibrary(root, lib):
node = root.find('Settings').find('Configuration').find('Linker')
node = SubElement(node, 'Library')
node.attrib['Value'] = lib
def CLAddFile(root, file_path):
file_path = file_path.replace('\\', '/')
dir_list = file_path.split('/')
dir_list.pop()
if not len(dir_list):
dir_list.append(os.path.abspath('.').replace('\\', '/').split('/')[-1])
parent = root
for dir_name in dir_list:
if dir_name == '..':
continue
node = None
nodes = parent.findall('VirtualDirectory')
for iter in nodes:
if iter.attrib['Name'] == dir_name:
node = iter
break
if node is None:
node = SubElement(parent, 'VirtualDirectory')
node.attrib['Name'] = dir_name
parent = node
if parent != root:
node = SubElement(parent, 'File')
node.attrib['Name'] = file_path
def CLAddHeaderFiles(parent, program, project_path):
utils.source_ext = []
utils.source_ext = ["h"]
for item in program:
utils.walk_children(item)
utils.source_list.sort()
for f in utils.source_list:
path = _make_path_relative(project_path, f)
CLAddFile(parent, path)
def CLAddCFiles(parent, files, project_path):
for f in files:
fn = f.rfile()
name = fn.name
path = os.path.dirname(fn.abspath)
path = _make_path_relative(project_path, path)
path = os.path.join(path, name)
CLAddFile(parent, path)
def CLGenWorkspace(project_name, project_path):
if os.path.isfile('codelite_template.workspace'):
tree = etree.parse('codelite_template.workspace')
else:
tree = etree.parse(os.path.join(os.path.dirname(__file__), 'codelite_template.workspace'))
root = tree.getroot()
root.attrib['Name'] = project_name
node = root.find('Project')
node.attrib['Name'] = project_name
node.attrib['Path'] = project_name + '.project'
node = root.find('BuildMatrix').find('WorkspaceConfiguration').find('Project')
node.attrib['Name'] = project_name
out = open(project_name + '.workspace', 'w')
out.write('<?xml version="1.0" encoding="UTF-8"?>\n')
xml_indent(root)
out.write(etree.tostring(root, encoding='utf-8'))
out.close()
def TargetCodelite(script, program):
project_name = os.path.abspath('.').replace('\\', '/').split('/')[-1]
#project_name.replace('-', '_')
project_path = os.path.abspath('.')
CLGenWorkspace(project_name, project_path)
if os.path.isfile('codelite_template.project'):
tree = etree.parse('codelite_template.project')
else:
tree = etree.parse(os.path.join(os.path.dirname(__file__), 'codelite_template.project'))
root = tree.getroot()
root.attrib['Name'] = project_name
out = open(project_name + '.project', 'w')
out.write('<?xml version="1.0" encoding="UTF-8"?>\n')
# add files
for group in script:
CLAddCFiles(root, group['src'], project_path)
# add header file
CLAddHeaderFiles(root, program, project_path)
# SECTION 2.
# write head include path
if 'CPPPATH' in building.Env:
cpp_path = building.Env['CPPPATH']
paths = set()
for path in cpp_path:
inc = _make_path_relative(project_path, os.path.normpath(path))
paths.add(inc) #.replace('\\', '/')
paths = [i for i in paths]
paths.sort()
# write include path, definitions
for elem in tree.iter(tag='Compiler'):
break
for path in paths:
CLAddIncludePath(root, path)
#print building.Env.get('LIBPATH', [])
#print building.Env.get('LIBS', [])
CLSetCFlags(root, building.Env.get('CFLAGS', []))
CLSetCxxFlags(root, building.Env.get('CFLAGS', []))
asflags = building.Env.get('ASFLAGS', [])
asflags = asflags.replace('-ffunction-sections', '')
asflags = asflags.replace('-fdata-sections', '')
asflags = asflags.replace('-x', '')
asflags = asflags.replace('-Wa,', '')
asflags = asflags.replace('assembler-with-cpp', '')
CLSetAsFlags(root, asflags)
CLSetLdFlags(root, building.Env.get('LINKFLAGS', []))
for macro in building.Env.get('CPPDEFINES', []):
for d in macro:
CLAddPreprocessor(root, d)
xml_indent(root)
out.write(etree.tostring(root, encoding='utf-8'))
out.close()
@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<CodeLite_Project Name="project" Version="11000" InternalType="">
<Description/>
<Dependencies/>
<Settings Type="Executable">
<GlobalSettings>
<Compiler Options="" C_Options="" Assembler="">
<IncludePath Value="."/>
</Compiler>
<Linker Options="">
<LibraryPath Value="."/>
</Linker>
<ResourceCompiler Options=""/>
</GlobalSettings>
<Configuration Name="Debug" CompilerType="Cross GCC ( arm-none-eabi )" DebuggerType="GNU gdb debugger" Type="Executable" BuildCmpWithGlobalSettings="append" BuildLnkWithGlobalSettings="append" BuildResWithGlobalSettings="append">
<Compiler Options="" C_Options="" Assembler="" Required="yes" PreCompiledHeader="" PCHInCommandLine="no" PCHFlags="" PCHFlagsPolicy="0">
</Compiler>
<Linker Options="" Required="yes">
</Linker>
<ResourceCompiler Options="" Required="no"/>
<General OutputFile="$(IntermediateDirectory)/$(ProjectName).elf" IntermediateDirectory="$(ConfigurationName)" Command="$(OutputFile)" CommandArguments="" UseSeparateDebugArgs="no" DebugArguments="" WorkingDirectory="" PauseExecWhenProcTerminates="yes" IsGUIProgram="no" IsEnabled="yes"/>
<BuildSystem Name="Default"/>
<Environment EnvVarSetName="&lt;Use Defaults&gt;" DbgSetName="&lt;Use Defaults&gt;">
<![CDATA[]]>
</Environment>
<Debugger IsRemote="yes" RemoteHostName="127.0.0.1" RemoteHostPort="2331" DebuggerPath="" IsExtended="no">
<DebuggerSearchPaths/>
<PostConnectCommands>monitor reset
monitor halt
load</PostConnectCommands>
<StartupCommands/>
</Debugger>
<PreBuild/>
<PostBuild>
<Command Enabled="yes">arm-none-eabi-objcopy -O ihex $(IntermediateDirectory)/$(ProjectName).elf $(IntermediateDirectory)/$(ProjectName).hex</Command>
<Command Enabled="yes">arm-none-eabi-objcopy -I ihex -O binary $(IntermediateDirectory)/$(ProjectName).hex $(IntermediateDirectory)/$(ProjectName).bin</Command>
<Command Enabled="yes">arm-none-eabi-size $(IntermediateDirectory)/$(ProjectName).elf</Command>
</PostBuild>
<CustomBuild Enabled="no">
<RebuildCommand/>
<CleanCommand/>
<BuildCommand/>
<PreprocessFileCommand/>
<SingleFileCommand/>
<MakefileGenerationCommand/>
<ThirdPartyToolName/>
<WorkingDirectory/>
</CustomBuild>
<AdditionalRules>
<CustomPostBuild/>
<CustomPreBuild/>
</AdditionalRules>
<Completion EnableCpp11="no" EnableCpp14="no">
<ClangCmpFlagsC/>
<ClangCmpFlags/>
<ClangPP/>
<SearchPaths/>
</Completion>
</Configuration>
</Settings>
</CodeLite_Project>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<CodeLite_Workspace Name="project" Database="" Version="10000">
<Project Name="project" Path="project.project" Active="Yes"/>
<BuildMatrix>
<WorkspaceConfiguration Name="Debug" Selected="yes">
<Environment/>
<Project Name="project" ConfigName="Debug"/>
</WorkspaceConfiguration>
</BuildMatrix>
</CodeLite_Workspace>
+587
View File
@@ -0,0 +1,587 @@
#
# Copyright (c) 2006-2022, RT-Thread Development Team
#
# SPDX-License-Identifier: Apache-2.0
#
# Change Logs:
# Date Author Notes
# 2019-03-21 Bernard the first version
# 2019-04-15 armink fix project update error
#
import glob
import xml.etree.ElementTree as etree
from xml.etree.ElementTree import SubElement
from . import rt_studio
import sys
import os
# Add parent directory to path to import building and utils
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from building import *
from utils import *
from utils import _make_path_relative
from utils import xml_indent
MODULE_VER_NUM = 6
source_pattern = ['*.c', '*.cpp', '*.cxx', '*.cc', '*.s', '*.S', '*.asm','*.cmd']
def OSPath(path):
import platform
if type(path) == type('str'):
if platform.system() == 'Windows':
return path.replace('/', '\\')
else:
return path.replace('\\', '/')
else:
if platform.system() == 'Windows':
return [item.replace('/', '\\') for item in path]
else:
return [item.replace('\\', '/') for item in path]
# collect the build source code path and parent path
def CollectPaths(paths):
all_paths = []
def ParentPaths(path):
ret = os.path.dirname(path)
if ret == path or ret == '':
return []
return [ret] + ParentPaths(ret)
for path in paths:
# path = os.path.abspath(path)
path = path.replace('\\', '/')
all_paths = all_paths + [path] + ParentPaths(path)
cwd = os.getcwd()
for path in os.listdir(cwd):
temp_path = cwd.replace('\\', '/') + '/' + path
if os.path.isdir(temp_path):
all_paths = all_paths + [temp_path]
all_paths = list(set(all_paths))
return sorted(all_paths)
'''
Collect all of files under paths
'''
def CollectFiles(paths, pattern):
files = []
for path in paths:
if type(pattern) == type(''):
files = files + glob.glob(path + '/' + pattern)
else:
for item in pattern:
# print('--> %s' % (path + '/' + item))
files = files + glob.glob(path + '/' + item)
return sorted(files)
def CollectAllFilesinPath(path, pattern):
files = []
for item in pattern:
files += glob.glob(path + '/' + item)
list = os.listdir(path)
if len(list):
for item in list:
if item.startswith('.'):
continue
if item == 'bsp':
continue
if os.path.isdir(os.path.join(path, item)):
files = files + CollectAllFilesinPath(os.path.join(path, item), pattern)
return files
'''
Exclude files from infiles
'''
def ExcludeFiles(infiles, files):
in_files = set([OSPath(file) for file in infiles])
exl_files = set([OSPath(file) for file in files])
exl_files = in_files - exl_files
return exl_files
# caluclate the exclude path for project
def ExcludePaths(rootpath, paths):
ret = []
files = os.listdir(OSPath(rootpath))
for file in files:
if file.startswith('.'):
continue
fullname = os.path.join(OSPath(rootpath), file)
if os.path.isdir(fullname):
# print(fullname)
if not fullname in paths:
ret = ret + [fullname]
else:
ret = ret + ExcludePaths(fullname, paths)
return ret
rtt_path_prefix = '"${workspace_loc://${ProjName}//'
def ConverToRttEclipsePathFormat(path):
return rtt_path_prefix + path + '}"'
def IsRttEclipsePathFormat(path):
if path.startswith(rtt_path_prefix):
return True
else:
return False
# all libs added by scons should be ends with five whitespace as a flag
rtt_lib_flag = 5 * " "
def ConverToRttEclipseLibFormat(lib):
return str(lib) + str(rtt_lib_flag)
def IsRttEclipseLibFormat(path):
if path.endswith(rtt_lib_flag):
return True
else:
return False
def IsCppProject():
return GetDepend('RT_USING_CPLUSPLUS')
def HandleToolOption(tools, env, project, reset):
is_cpp_prj = IsCppProject()
BSP_ROOT = os.path.abspath(env['BSP_ROOT'])
CPPDEFINES = project['CPPDEFINES']
paths = [ConverToRttEclipsePathFormat(RelativeProjectPath(env, os.path.normpath(i)).replace('\\', '/')) for i in project['CPPPATH']]
compile_include_paths_options = []
compile_include_files_options = []
compile_defs_options = []
linker_scriptfile_option = None
linker_script_option = None
linker_nostart_option = None
linker_libs_option = None
linker_paths_option = None
linker_newlib_nano_option = None
for tool in tools:
if tool.get('id').find('compile') != 1:
options = tool.findall('option')
# find all compile options
for option in options:
option_id = option.get('id')
if ('compiler.include.paths' in option_id) or ('compiler.option.includepaths' in option_id) or ('compiler.tasking.include' in option_id):
compile_include_paths_options += [option]
elif option.get('id').find('compiler.include.files') != -1 or option.get('id').find('compiler.option.includefiles') != -1 :
compile_include_files_options += [option]
elif option.get('id').find('compiler.defs') != -1 or option.get('id').find('compiler.option.definedsymbols') != -1:
compile_defs_options += [option]
if tool.get('id').find('linker') != -1:
options = tool.findall('option')
# find all linker options
for option in options:
# the project type and option type must equal
if is_cpp_prj != (option.get('id').find('cpp.linker') != -1):
continue
if option.get('id').find('linker.scriptfile') != -1:
linker_scriptfile_option = option
elif option.get('id').find('linker.option.script') != -1:
linker_script_option = option
elif option.get('id').find('linker.nostart') != -1:
linker_nostart_option = option
elif option.get('id').find('linker.libs') != -1:
linker_libs_option = option
elif option.get('id').find('linker.paths') != -1 and 'LIBPATH' in env:
linker_paths_option = option
elif option.get('id').find('linker.usenewlibnano') != -1:
linker_newlib_nano_option = option
# change the inclue path
for option in compile_include_paths_options:
# find all of paths in this project
include_paths = option.findall('listOptionValue')
for item in include_paths:
if reset is True or IsRttEclipsePathFormat(item.get('value')) :
# clean old configuration
option.remove(item)
# print('c.compiler.include.paths')
paths = sorted(paths)
for item in paths:
SubElement(option, 'listOptionValue', {'builtIn': 'false', 'value': item})
# change the inclue files (default) or definitions
for option in compile_include_files_options:
# add '_REENT_SMALL' to CPPDEFINES when --specs=nano.specs has select
if linker_newlib_nano_option is not None and linker_newlib_nano_option.get('value') == 'true' and '_REENT_SMALL' not in CPPDEFINES:
CPPDEFINES += ['_REENT_SMALL']
file_header = '''
#ifndef RTCONFIG_PREINC_H__
#define RTCONFIG_PREINC_H__
/* Automatically generated file; DO NOT EDIT. */
/* RT-Thread pre-include file */
'''
file_tail = '\n#endif /*RTCONFIG_PREINC_H__*/\n'
rtt_pre_inc_item = '"${workspace_loc:/${ProjName}/rtconfig_preinc.h}"'
# save the CPPDEFINES in to rtconfig_preinc.h
with open('rtconfig_preinc.h', mode = 'w+') as f:
f.write(file_header)
for cppdef in CPPDEFINES:
f.write("#define " + cppdef.replace('=', ' ') + '\n')
f.write(file_tail)
# change the c.compiler.include.files
files = option.findall('listOptionValue')
find_ok = False
for item in files:
if item.get('value') == rtt_pre_inc_item:
find_ok = True
break
if find_ok is False:
SubElement(option, 'listOptionValue', {'builtIn': 'false', 'value': rtt_pre_inc_item})
if len(compile_include_files_options) == 0:
for option in compile_defs_options:
defs = option.findall('listOptionValue')
project_defs = []
for item in defs:
if reset is True:
# clean all old configuration
option.remove(item)
else:
project_defs += [item.get('value')]
if len(project_defs) > 0:
cproject_defs = set(CPPDEFINES) - set(project_defs)
else:
cproject_defs = CPPDEFINES
# print('c.compiler.defs')
cproject_defs = sorted(cproject_defs)
for item in cproject_defs:
SubElement(option, 'listOptionValue', {'builtIn': 'false', 'value': item})
# update linker script config
if linker_scriptfile_option is not None :
option = linker_scriptfile_option
linker_script = 'link.lds'
items = env['LINKFLAGS'].split(' ')
if '-T' in items:
linker_script = items[items.index('-T') + 1]
linker_script = ConverToRttEclipsePathFormat(linker_script)
listOptionValue = option.find('listOptionValue')
if listOptionValue != None:
if reset is True or IsRttEclipsePathFormat(listOptionValue.get('value')):
listOptionValue.set('value', linker_script)
else:
SubElement(option, 'listOptionValue', {'builtIn': 'false', 'value': linker_script})
# scriptfile in stm32cubeIDE
if linker_script_option is not None :
option = linker_script_option
items = env['LINKFLAGS'].split(' ')
if '-T' in items:
linker_script = ConverToRttEclipsePathFormat(items[items.index('-T') + 1]).strip('"')
option.set('value', linker_script)
# update nostartfiles config
if linker_nostart_option is not None :
option = linker_nostart_option
if env['LINKFLAGS'].find('-nostartfiles') != -1:
option.set('value', 'true')
else:
option.set('value', 'false')
# update libs
if linker_libs_option is not None:
option = linker_libs_option
# remove old libs
for item in option.findall('listOptionValue'):
if IsRttEclipseLibFormat(item.get("value")):
option.remove(item)
# add new libs
if 'LIBS' in env:
for lib in env['LIBS']:
lib_name = os.path.basename(str(lib))
if lib_name.endswith('.a'):
if lib_name.startswith('lib'):
lib = lib_name[3:].split('.')[0]
else:
lib = ':' + lib_name
formatedLib = ConverToRttEclipseLibFormat(lib)
SubElement(option, 'listOptionValue', {
'builtIn': 'false', 'value': formatedLib})
# update lib paths
if linker_paths_option is not None:
option = linker_paths_option
# remove old lib paths
for item in option.findall('listOptionValue'):
if IsRttEclipsePathFormat(item.get('value')):
# clean old configuration
option.remove(item)
# add new old lib paths
for path in env['LIBPATH']:
SubElement(option, 'listOptionValue', {'builtIn': 'false', 'value': ConverToRttEclipsePathFormat(RelativeProjectPath(env, path).replace('\\', '/'))})
return
def UpdateProjectStructure(env, prj_name):
bsp_root = env['BSP_ROOT']
rtt_root = env['RTT_ROOT']
project = etree.parse('.project')
root = project.getroot()
if rtt_root.startswith(bsp_root):
linkedResources = root.find('linkedResources')
if linkedResources == None:
linkedResources = SubElement(root, 'linkedResources')
links = linkedResources.findall('link')
# delete all RT-Thread folder links
for link in links:
if link.find('name').text.startswith('rt-thread'):
linkedResources.remove(link)
if prj_name:
name = root.find('name')
if name == None:
name = SubElement(root, 'name')
name.text = prj_name
out = open('.project', 'w')
out.write('<?xml version="1.0" encoding="UTF-8"?>\n')
xml_indent(root)
out.write(etree.tostring(root, encoding='utf-8').decode('utf-8'))
out.close()
return
def GenExcluding(env, project):
rtt_root = os.path.abspath(env['RTT_ROOT'])
bsp_root = os.path.abspath(env['BSP_ROOT'])
coll_dirs = CollectPaths(project['DIRS'])
all_paths_temp = [OSPath(path) for path in coll_dirs]
all_paths = []
# add used path
for path in all_paths_temp:
if path.startswith(rtt_root) or path.startswith(bsp_root):
all_paths.append(path)
if bsp_root.startswith(rtt_root):
# bsp folder is in the RT-Thread root folder, such as the RT-Thread source code on GitHub
exclude_paths = ExcludePaths(rtt_root, all_paths)
elif rtt_root.startswith(bsp_root):
# RT-Thread root folder is in the bsp folder, such as project folder which generate by 'scons --dist' cmd
check_path = []
exclude_paths = []
# analyze the primary folder which relative to BSP_ROOT and in all_paths
for path in all_paths:
if path.startswith(bsp_root):
folders = RelativeProjectPath(env, path).split('\\')
if folders[0] != '.' and '\\' + folders[0] not in check_path:
check_path += ['\\' + folders[0]]
# exclue the folder which has managed by scons
for path in check_path:
exclude_paths += ExcludePaths(bsp_root + path, all_paths)
else:
exclude_paths = ExcludePaths(rtt_root, all_paths)
exclude_paths += ExcludePaths(bsp_root, all_paths)
paths = exclude_paths
exclude_paths = []
# remove the folder which not has source code by source_pattern
for path in paths:
# add bsp and libcpu folder and not collect source files (too more files)
if path.endswith('rt-thread\\bsp') or path.endswith('rt-thread\\libcpu'):
exclude_paths += [path]
continue
set = CollectAllFilesinPath(path, source_pattern)
if len(set):
exclude_paths += [path]
exclude_paths = [RelativeProjectPath(env, path).replace('\\', '/') for path in exclude_paths]
all_files = CollectFiles(all_paths, source_pattern)
src_files = project['FILES']
exclude_files = ExcludeFiles(all_files, src_files)
exclude_files = [RelativeProjectPath(env, file).replace('\\', '/') for file in exclude_files]
env['ExPaths'] = exclude_paths
env['ExFiles'] = exclude_files
return exclude_paths + exclude_files
def RelativeProjectPath(env, path):
project_root = os.path.abspath(env['BSP_ROOT'])
rtt_root = os.path.abspath(env['RTT_ROOT'])
if path.startswith(project_root):
return _make_path_relative(project_root, path)
if path.startswith(rtt_root):
return 'rt-thread/' + _make_path_relative(rtt_root, path)
# TODO add others folder
print('ERROR: the ' + path + ' not support')
return path
def HandleExcludingOption(entry, sourceEntries, excluding):
old_excluding = []
if entry != None:
exclud = entry.get('excluding')
if exclud != None:
old_excluding = entry.get('excluding').split('|')
sourceEntries.remove(entry)
value = ''
for item in old_excluding:
if item.startswith('//'):
old_excluding.remove(item)
else:
if value == '':
value = item
else:
value += '|' + item
for item in excluding:
# add special excluding path prefix for RT-Thread
item = '//' + item
if value == '':
value = item
else:
value += '|' + item
SubElement(sourceEntries, 'entry', {'excluding': value, 'flags': 'VALUE_WORKSPACE_PATH|RESOLVED', 'kind':'sourcePath', 'name':""})
def UpdateCproject(env, project, excluding, reset, prj_name):
excluding = sorted(excluding)
cproject = etree.parse('.cproject')
root = cproject.getroot()
cconfigurations = root.findall('storageModule/cconfiguration')
for cconfiguration in cconfigurations:
tools = cconfiguration.findall('storageModule/configuration/folderInfo/toolChain/tool')
HandleToolOption(tools, env, project, reset)
sourceEntries = cconfiguration.find('storageModule/configuration/sourceEntries')
if sourceEntries != None:
entry = sourceEntries.find('entry')
HandleExcludingOption(entry, sourceEntries, excluding)
# update refreshScope
if prj_name:
prj_name = '/' + prj_name
configurations = root.findall('storageModule/configuration')
for configuration in configurations:
resource = configuration.find('resource')
configuration.remove(resource)
SubElement(configuration, 'resource', {'resourceType': "PROJECT", 'workspacePath': prj_name})
# write back to .cproject
out = open('.cproject', 'w')
out.write('<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n')
out.write('<?fileVersion 4.0.0?>')
xml_indent(root)
out.write(etree.tostring(root, encoding='utf-8').decode('utf-8'))
out.close()
def TargetEclipse(env, reset=False, prj_name=None):
global source_pattern
print('Update eclipse setting...')
# generate cproject file
if not os.path.exists('.cproject'):
if rt_studio.gen_cproject_file(os.path.abspath(".cproject")) is False:
print('Fail!')
return
# generate project file
if not os.path.exists('.project'):
if rt_studio.gen_project_file(os.path.abspath(".project")) is False:
print('Fail!')
return
# generate projcfg.ini file
if not os.path.exists('.settings/projcfg.ini'):
# if search files with uvprojx or uvproj suffix
file = ""
items = os.listdir(".")
if len(items) > 0:
for item in items:
if item.endswith(".uvprojx") or item.endswith(".uvproj"):
file = os.path.abspath(item)
break
chip_name = rt_studio.get_mcu_info(file)
if rt_studio.gen_projcfg_ini_file(chip_name, prj_name, os.path.abspath(".settings/projcfg.ini")) is False:
print('Fail!')
return
# enable lowwer .s file compiled in eclipse cdt
if not os.path.exists('.settings/org.eclipse.core.runtime.prefs'):
if rt_studio.gen_org_eclipse_core_runtime_prefs(
os.path.abspath(".settings/org.eclipse.core.runtime.prefs")) is False:
print('Fail!')
return
# add clean2 target to fix issues when files too many
if not os.path.exists('makefile.targets'):
if rt_studio.gen_makefile_targets(os.path.abspath("makefile.targets")) is False:
print('Fail!')
return
project = ProjectInfo(env)
# update the project file structure info on '.project' file
UpdateProjectStructure(env, prj_name)
# generate the exclude paths and files
excluding = GenExcluding(env, project)
# update the project configuration on '.cproject' file
UpdateCproject(env, project, excluding, reset, prj_name)
print('done!')
return
+58
View File
@@ -0,0 +1,58 @@
import os
import re
import utils
from utils import _make_path_relative
def GenerateCFiles(env,project):
"""
Generate CMakeLists.txt files
"""
info = utils.ProjectInfo(env)
init_export = []
main_component_dir = os.path.join(os.getcwd(), 'main')
cm_file = open(os.path.join(main_component_dir, 'CMakeLists.txt'), 'w')
if cm_file:
cm_file.write("idf_component_register(\n")
cm_file.write("\tSRCS\n")
for group in project:
for f in group['src']:
path = _make_path_relative(main_component_dir, os.path.normpath(f.rfile().abspath))
cm_file.write( "\t" + path.replace("\\", "/") + "\n" )
src = open(f.rfile().abspath, 'r')
for line in src.readlines():
if re.match(r'INIT_(BOARD|PREV|DEVICE|COMPONENT|ENV|APP)_EXPORT\(.+\)', line):
init_export.append(re.search(r'\(.+\)', line).group(0)[1:-1])
src.close()
cm_file.write("\n")
cm_file.write("\tINCLUDE_DIRS\n")
for i in info['CPPPATH']:
path = _make_path_relative(main_component_dir, i)
cm_file.write( "\t" + path.replace("\\", "/") + "\n")
cm_file.write(")\n\n")
n = len(init_export)
if n:
cm_file.write("target_link_libraries(${COMPONENT_LIB}\n")
for i in range(n):
cm_file.write("\tINTERFACE \"-u __rt_init_" + init_export[i] + "\"\n")
cm_file.write(")\n")
cm_file.close()
cm_file = open('CMakeLists.txt', 'w')
if cm_file:
cm_file.write("cmake_minimum_required(VERSION 3.16)\n")
cm_file.write("set(COMPONENTS esptool_py main)\n")
cm_file.write("include($ENV{IDF_PATH}/tools/cmake/project.cmake)\n")
freertos_root = os.getcwd().replace('\\', '/') + '/packages/FreeRTOS_Wrapper-latest/FreeRTOS'
cm_file.write("set(freertos_root " + freertos_root + ')\n')
cm_file.write("project(rtthread)\n")
cm_file.close()
def ESPIDFProject(env,project):
print('Update setting files for CMakeLists.txt...')
GenerateCFiles(env,project)
print('Done!')
+209
View File
@@ -0,0 +1,209 @@
#
# File : iar.py
# This file is part of RT-Thread RTOS
# COPYRIGHT (C) 2006 - 2015, RT-Thread Development Team
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Change Logs:
# Date Author Notes
# 2015-01-20 Bernard Add copyright information
#
import os
import sys
import string
import utils
from SCons.Script import *
import xml.etree.ElementTree as etree
from xml.etree.ElementTree import SubElement
from utils import _make_path_relative
from utils import xml_indent
fs_encoding = sys.getfilesystemencoding()
iar_workspace = r'''<?xml version="1.0" encoding="iso-8859-1"?>
<workspace>
<project>
<path>$WS_DIR$\%s</path>
</project>
<batchBuild/>
</workspace>
'''
def IARAddGroup(parent, name, files, project_path):
group = SubElement(parent, 'group')
group_name = SubElement(group, 'name')
group_name.text = name
for f in files:
fn = f.rfile()
name = fn.name
path = os.path.dirname(fn.abspath)
basename = os.path.basename(path)
path = _make_path_relative(project_path, path)
path = os.path.join(path, name)
file = SubElement(group, 'file')
file_name = SubElement(file, 'name')
if os.path.isabs(path):
file_name.text = path # path.decode(fs_encoding)
else:
file_name.text = '$PROJ_DIR$\\' + path # ('$PROJ_DIR$\\' + path).decode(fs_encoding)
def IARWorkspace(target):
# make an workspace
workspace = target.replace('.ewp', '.eww')
out = open(workspace, 'w')
xml = iar_workspace % target
out.write(xml)
out.close()
def IARProject(env, target, script):
project_path = os.path.dirname(os.path.abspath(target))
tree = etree.parse('template.ewp')
root = tree.getroot()
out = open(target, 'w')
CPPPATH = []
CPPDEFINES = env.get('CPPDEFINES', [])
LOCAL_CPPDEFINES = []
LINKFLAGS = ''
CFLAGS = ''
Libs = []
lib_prefix = ['lib', '']
lib_suffix = ['.a', '.o', '']
def searchLib(group):
for path_item in group['LIBPATH']:
for prefix_item in lib_prefix:
for suffix_item in lib_suffix:
lib_full_path = os.path.join(path_item, prefix_item + item + suffix_item)
if os.path.isfile(lib_full_path):
return lib_full_path
else:
return ''
# add group
for group in script:
IARAddGroup(root, group['name'], group['src'], project_path)
# get each include path
if 'CPPPATH' in group and group['CPPPATH']:
CPPPATH += group['CPPPATH']
if 'LOCAL_CPPDEFINES' in group and group['LOCAL_CPPDEFINES']:
LOCAL_CPPDEFINES += group['LOCAL_CPPDEFINES']
# get each group's link flags
if 'LINKFLAGS' in group and group['LINKFLAGS']:
LINKFLAGS += group['LINKFLAGS']
if 'LIBS' in group and group['LIBS']:
for item in group['LIBS']:
lib_path = searchLib(group)
if lib_path != '':
lib_path = _make_path_relative(project_path, lib_path)
Libs += [lib_path]
# print('found lib isfile: ' + lib_path)
else:
print('not found LIB: ' + item)
# make relative path
paths = set()
for path in CPPPATH:
inc = _make_path_relative(project_path, os.path.normpath(path))
paths.add(inc) #.replace('\\', '/')
# setting options
options = tree.findall('configuration/settings/data/option')
for option in options:
# print option.text
name = option.find('name')
if name.text == 'CCIncludePath2' or name.text == 'newCCIncludePaths':
for path in paths:
state = SubElement(option, 'state')
if os.path.isabs(path) or path.startswith('$'):
state.text = path
else:
state.text = '$PROJ_DIR$\\' + path
if name.text == 'CCDefines':
for define in CPPDEFINES:
state = SubElement(option, 'state')
state.text = define
for define in LOCAL_CPPDEFINES:
state = SubElement(option, 'state')
state.text = define
if name.text == 'IlinkAdditionalLibs':
for path in Libs:
state = SubElement(option, 'state')
if os.path.isabs(path) or path.startswith('$'):
path = path.decode(fs_encoding)
else:
path = ('$PROJ_DIR$\\' + path).decode(fs_encoding)
state.text = path
xml_indent(root)
out.write(etree.tostring(root, encoding='utf-8').decode())
out.close()
IARWorkspace(target)
def IARPath():
import rtconfig
# backup environ
old_environ = os.environ
os.environ['RTT_CC'] = 'iar'
# get iar path
path = rtconfig.EXEC_PATH
# restore environ
os.environ = old_environ
return path
def IARVersion():
import subprocess
import re
path = IARPath()
if os.path.exists(path):
cmd = os.path.join(path, 'iccarm.exe')
else:
return "0.0"
child = subprocess.Popen([cmd, '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
stdout, stderr = child.communicate()
if not isinstance(stdout, str):
stdout = str(stdout, 'utf8') # Patch for Python 3
# example stdout: IAR ANSI C/C++ Compiler V8.20.1.14183/W32 for ARM
iar_version = re.search(r'[\d\.]+', stdout).group(0)
return iar_version
+513
View File
@@ -0,0 +1,513 @@
#
# File : keil.py
# This file is part of RT-Thread RTOS
# COPYRIGHT (C) 2006 - 2015, RT-Thread Development Team
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Change Logs:
# Date Author Notes
# 2015-01-20 Bernard Add copyright information
#
import os
import sys
import string
import shutil
import xml.etree.ElementTree as etree
from xml.etree.ElementTree import SubElement
from utils import _make_path_relative
from utils import xml_indent
fs_encoding = sys.getfilesystemencoding()
def _get_filetype(fn):
if fn.rfind('.cpp') != -1 or fn.rfind('.cxx') != -1:
return 8
if fn.rfind('.c') != -1 or fn.rfind('.C') != -1:
return 1
# assemble file type
if fn.rfind('.s') != -1 or fn.rfind('.S') != -1:
return 2
# header type
if fn.rfind('.h') != -1:
return 5
if fn.rfind('.lib') != -1:
return 4
if fn.rfind('.o') != -1:
return 3
# other filetype
return 5
def MDK4AddGroupForFN(ProjectFiles, parent, name, filename, project_path):
group = SubElement(parent, 'Group')
group_name = SubElement(group, 'GroupName')
group_name.text = name
name = os.path.basename(filename)
path = os.path.dirname (filename)
basename = os.path.basename(path)
path = _make_path_relative(project_path, path)
path = os.path.join(path, name)
files = SubElement(group, 'Files')
file = SubElement(files, 'File')
file_name = SubElement(file, 'FileName')
name = os.path.basename(path)
if name.find('.cpp') != -1:
obj_name = name.replace('.cpp', '.o')
elif name.find('.c') != -1:
obj_name = name.replace('.c', '.o')
elif name.find('.s') != -1:
obj_name = name.replace('.s', '.o')
elif name.find('.S') != -1:
obj_name = name.replace('.s', '.o')
else:
obj_name = name
if ProjectFiles.count(obj_name):
name = basename + '_' + name
ProjectFiles.append(obj_name)
try: # python 2
file_name.text = name.decode(fs_encoding)
except: # python 3
file_name.text = name
file_type = SubElement(file, 'FileType')
file_type.text = '%d' % _get_filetype(name)
file_path = SubElement(file, 'FilePath')
try: # python 2
file_path.text = path.decode(fs_encoding)
except: # python 3
file_path.text = path
return group
def MDK4AddLibToGroup(ProjectFiles, group, name, filename, project_path):
name = os.path.basename(filename)
path = os.path.dirname (filename)
basename = os.path.basename(path)
path = _make_path_relative(project_path, path)
path = os.path.join(path, name)
files = SubElement(group, 'Files')
file = SubElement(files, 'File')
file_name = SubElement(file, 'FileName')
name = os.path.basename(path)
if name.find('.cpp') != -1:
obj_name = name.replace('.cpp', '.o')
elif name.find('.c') != -1:
obj_name = name.replace('.c', '.o')
elif name.find('.s') != -1:
obj_name = name.replace('.s', '.o')
elif name.find('.S') != -1:
obj_name = name.replace('.s', '.o')
else:
obj_name = name
if ProjectFiles.count(obj_name):
name = basename + '_' + name
ProjectFiles.append(obj_name)
try:
file_name.text = name.decode(fs_encoding)
except:
file_name.text = name
file_type = SubElement(file, 'FileType')
file_type.text = '%d' % _get_filetype(name)
file_path = SubElement(file, 'FilePath')
try:
file_path.text = path.decode(fs_encoding)
except:
file_path.text = path
return group
def MDK4AddGroup(ProjectFiles, parent, name, files, project_path, group_scons):
# don't add an empty group
if len(files) == 0:
return
group = SubElement(parent, 'Group')
group_name = SubElement(group, 'GroupName')
group_name.text = name
for f in files:
fn = f.rfile()
name = fn.name
path = os.path.dirname(fn.abspath)
basename = os.path.basename(path)
path = _make_path_relative(project_path, path)
path = os.path.join(path, name)
files = SubElement(group, 'Files')
file = SubElement(files, 'File')
file_name = SubElement(file, 'FileName')
name = os.path.basename(path)
if name.find('.cpp') != -1:
obj_name = name.replace('.cpp', '.o')
elif name.find('.c') != -1:
obj_name = name.replace('.c', '.o')
elif name.find('.s') != -1:
obj_name = name.replace('.s', '.o')
elif name.find('.S') != -1:
obj_name = name.replace('.s', '.o')
if ProjectFiles.count(obj_name):
name = basename + '_' + name
ProjectFiles.append(obj_name)
file_name.text = name # name.decode(fs_encoding)
file_type = SubElement(file, 'FileType')
file_type.text = '%d' % _get_filetype(name)
file_path = SubElement(file, 'FilePath')
file_path.text = path # path.decode(fs_encoding)
# for local LOCAL_CFLAGS/LOCAL_CXXFLAGS/LOCAL_CCFLAGS/LOCAL_CPPPATH/LOCAL_CPPDEFINES
MiscControls_text = ' '
if file_type.text == '1' and 'LOCAL_CFLAGS' in group_scons:
MiscControls_text = MiscControls_text + group_scons['LOCAL_CFLAGS']
elif file_type.text == '8' and 'LOCAL_CXXFLAGS' in group_scons:
MiscControls_text = MiscControls_text + group_scons['LOCAL_CXXFLAGS']
if 'LOCAL_CCFLAGS' in group_scons:
MiscControls_text = MiscControls_text + group_scons['LOCAL_CCFLAGS']
if MiscControls_text != ' ' or ('LOCAL_CPPDEFINES' in group_scons):
FileOption = SubElement(file, 'FileOption')
FileArmAds = SubElement(FileOption, 'FileArmAds')
Cads = SubElement(FileArmAds, 'Cads')
VariousControls = SubElement(Cads, 'VariousControls')
MiscControls = SubElement(VariousControls, 'MiscControls')
MiscControls.text = MiscControls_text
Define = SubElement(VariousControls, 'Define')
if 'LOCAL_CPPDEFINES' in group_scons:
Define.text = ', '.join(set(group_scons['LOCAL_CPPDEFINES']))
else:
Define.text = ' '
Undefine = SubElement(VariousControls, 'Undefine')
Undefine.text = ' '
IncludePath = SubElement(VariousControls, 'IncludePath')
if 'LOCAL_CPPPATH' in group_scons:
IncludePath.text = ';'.join([_make_path_relative(project_path, os.path.normpath(i)) for i in group_scons['LOCAL_CPPPATH']])
else:
IncludePath.text = ' '
return group
# The common part of making MDK4/5 project
def MDK45Project(env, tree, target, script):
project_path = os.path.dirname(os.path.abspath(target))
root = tree.getroot()
out = open(target, 'w')
out.write('<?xml version="1.0" encoding="UTF-8" standalone="no" ?>\n')
CPPPATH = []
CPPDEFINES = env.get('CPPDEFINES', [])
LINKFLAGS = ''
CXXFLAGS = ''
CCFLAGS = ''
CFLAGS = ''
ProjectFiles = []
# add group
groups = tree.find('Targets/Target/Groups')
if groups is None:
groups = SubElement(tree.find('Targets/Target'), 'Groups')
groups.clear() # clean old groups
for group in script:
group_tree = MDK4AddGroup(ProjectFiles, groups, group['name'], group['src'], project_path, group)
# get each include path
if 'CPPPATH' in group and group['CPPPATH']:
if CPPPATH:
CPPPATH += group['CPPPATH']
else:
CPPPATH += group['CPPPATH']
# get each group's link flags
if 'LINKFLAGS' in group and group['LINKFLAGS']:
if LINKFLAGS:
LINKFLAGS += ' ' + group['LINKFLAGS']
else:
LINKFLAGS += group['LINKFLAGS']
# get each group's CXXFLAGS flags
if 'CXXFLAGS' in group and group['CXXFLAGS']:
if CXXFLAGS:
CXXFLAGS += ' ' + group['CXXFLAGS']
else:
CXXFLAGS += group['CXXFLAGS']
# get each group's CCFLAGS flags
if 'CCFLAGS' in group and group['CCFLAGS']:
if CCFLAGS:
CCFLAGS += ' ' + group['CCFLAGS']
else:
CCFLAGS += group['CCFLAGS']
# get each group's CFLAGS flags
if 'CFLAGS' in group and group['CFLAGS']:
if CFLAGS:
CFLAGS += ' ' + group['CFLAGS']
else:
CFLAGS += group['CFLAGS']
# get each group's LIBS flags
if 'LIBS' in group and group['LIBS']:
for item in group['LIBPATH']:
full_path = os.path.join(item, group['name'] + '.lib')
if os.path.isfile(full_path): # has this library
if group_tree != None:
MDK4AddLibToGroup(ProjectFiles, group_tree, group['name'], full_path, project_path)
else:
group_tree = MDK4AddGroupForFN(ProjectFiles, groups, group['name'], full_path, project_path)
# write include path, definitions and link flags
IncludePath = tree.find('Targets/Target/TargetOption/TargetArmAds/Cads/VariousControls/IncludePath')
IncludePath.text = ';'.join([_make_path_relative(project_path, os.path.normpath(i)) for i in set(CPPPATH)])
Define = tree.find('Targets/Target/TargetOption/TargetArmAds/Cads/VariousControls/Define')
Define.text = ', '.join(set(CPPDEFINES))
if 'c99' in CXXFLAGS or 'c99' in CCFLAGS or 'c99' in CFLAGS:
uC99 = tree.find('Targets/Target/TargetOption/TargetArmAds/Cads/uC99')
uC99.text = '1'
if 'gnu' in CXXFLAGS or 'gnu' in CCFLAGS or 'gnu' in CFLAGS:
uGnu = tree.find('Targets/Target/TargetOption/TargetArmAds/Cads/uGnu')
uGnu.text = '1'
Misc = tree.find('Targets/Target/TargetOption/TargetArmAds/LDads/Misc')
Misc.text = LINKFLAGS
xml_indent(root)
out.write(etree.tostring(root, encoding='utf-8').decode())
out.close()
def MDK4Project(env, target, script):
if os.path.isfile('template.uvproj') is False:
print ('Warning: The template project file [template.uvproj] not found!')
return
template_tree = etree.parse('template.uvproj')
MDK45Project(env, template_tree, target, script)
# remove project.uvopt file
project_uvopt = os.path.abspath(target).replace('uvproj', 'uvopt')
if os.path.isfile(project_uvopt):
os.unlink(project_uvopt)
# copy uvopt file
if os.path.exists('template.uvopt'):
import shutil
shutil.copy2('template.uvopt', '{}.uvopt'.format(os.path.splitext(target)[0]))
import threading
import time
def monitor_log_file(log_file_path):
if not os.path.exists(log_file_path):
open(log_file_path, 'w').close()
empty_line_count = 0
with open(log_file_path, 'r') as log_file:
while True:
line = log_file.readline()
if line:
print(line.strip())
if 'Build Time Elapsed' in line:
break
empty_line_count = 0
else:
empty_line_count += 1
time.sleep(1)
if empty_line_count > 30:
print("Timeout reached or too many empty lines, exiting log monitoring thread.")
break
def MDK5Project(env, target, script):
if os.path.isfile('template.uvprojx') is False:
print ('Warning: The template project file [template.uvprojx] not found!')
return
template_tree = etree.parse('template.uvprojx')
MDK45Project(env, template_tree, target, script)
# remove project.uvopt file
project_uvopt = os.path.abspath(target).replace('uvprojx', 'uvoptx')
if os.path.isfile(project_uvopt):
os.unlink(project_uvopt)
# copy uvopt file
if os.path.exists('template.uvoptx'):
import shutil
shutil.copy2('template.uvoptx', '{}.uvoptx'.format(os.path.splitext(target)[0]))
# build with UV4.exe
if shutil.which('UV4.exe') is not None:
target_name = template_tree.find('Targets/Target/TargetName')
print('target_name:', target_name.text)
log_file_path = 'keil.log'
if os.path.exists(log_file_path):
os.remove(log_file_path)
log_thread = threading.Thread(target=monitor_log_file, args=(log_file_path,))
log_thread.start()
cmd = 'UV4.exe -b project.uvprojx -q -j0 -t '+ target_name.text +' -o '+log_file_path
print('Start to build keil project')
print(cmd)
os.system(cmd)
else:
print('UV4.exe is not available, please check your keil installation')
def MDK2Project(env, target, script):
template = open(os.path.join(os.path.dirname(__file__), 'template.Uv2'), 'r')
lines = template.readlines()
project = open(target, "w")
project_path = os.path.dirname(os.path.abspath(target))
line_index = 5
# write group
for group in script:
lines.insert(line_index, 'Group (%s)\r\n' % group['name'])
line_index += 1
lines.insert(line_index, '\r\n')
line_index += 1
# write file
ProjectFiles = []
CPPPATH = []
CPPDEFINES = env.get('CPPDEFINES', [])
LINKFLAGS = ''
CFLAGS = ''
# number of groups
group_index = 1
for group in script:
# print group['name']
# get each include path
if 'CPPPATH' in group and group['CPPPATH']:
if CPPPATH:
CPPPATH += group['CPPPATH']
else:
CPPPATH += group['CPPPATH']
# get each group's link flags
if 'LINKFLAGS' in group and group['LINKFLAGS']:
if LINKFLAGS:
LINKFLAGS += ' ' + group['LINKFLAGS']
else:
LINKFLAGS += group['LINKFLAGS']
# generate file items
for node in group['src']:
fn = node.rfile()
name = fn.name
path = os.path.dirname(fn.abspath)
basename = os.path.basename(path)
path = _make_path_relative(project_path, path)
path = os.path.join(path, name)
if ProjectFiles.count(name):
name = basename + '_' + name
ProjectFiles.append(name)
lines.insert(line_index, 'File %d,%d,<%s><%s>\r\n'
% (group_index, _get_filetype(name), path, name))
line_index += 1
group_index = group_index + 1
lines.insert(line_index, '\r\n')
line_index += 1
# remove repeat path
paths = set()
for path in CPPPATH:
inc = _make_path_relative(project_path, os.path.normpath(path))
paths.add(inc) #.replace('\\', '/')
paths = [i for i in paths]
CPPPATH = string.join(paths, ';')
definitions = [i for i in set(CPPDEFINES)]
CPPDEFINES = string.join(definitions, ', ')
while line_index < len(lines):
if lines[line_index].startswith(' ADSCINCD '):
lines[line_index] = ' ADSCINCD (' + CPPPATH + ')\r\n'
if lines[line_index].startswith(' ADSLDMC ('):
lines[line_index] = ' ADSLDMC (' + LINKFLAGS + ')\r\n'
if lines[line_index].startswith(' ADSCDEFN ('):
lines[line_index] = ' ADSCDEFN (' + CPPDEFINES + ')\r\n'
line_index += 1
# write project
for line in lines:
project.write(line)
project.close()
def ARMCC_Version():
import rtconfig
import subprocess
import re
path = rtconfig.EXEC_PATH
if(rtconfig.PLATFORM == 'armcc'):
path = os.path.join(path, 'armcc.exe')
elif(rtconfig.PLATFORM == 'armclang'):
path = os.path.join(path, 'armlink.exe')
if os.path.exists(path):
cmd = path
else:
return "0.0"
child = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
stdout, stderr = child.communicate()
'''
example stdout:
Product: MDK Plus 5.24
Component: ARM Compiler 5.06 update 5 (build 528)
Tool: armcc [4d3621]
return version: MDK Plus 5.24/ARM Compiler 5.06 update 5 (build 528)/armcc [4d3621]
'''
if not isinstance(stdout, str):
stdout = str(stdout, 'utf8') # Patch for Python 3
version_Product = re.search(r'Product: (.+)', stdout).group(1)
version_Product = version_Product[:-1]
version_Component = re.search(r'Component: (.*)', stdout).group(1)
version_Component = version_Component[:-1]
version_Tool = re.search(r'Tool: (.*)', stdout).group(1)
version_Tool = version_Tool[:-1]
version_str_format = '%s/%s/%s'
version_str = version_str_format % (version_Product, version_Component, version_Tool)
return version_str
+159
View File
@@ -0,0 +1,159 @@
#
# File : makefile.py
# This file is part of RT-Thread RTOS
# COPYRIGHT (C) 2006 - 2015, RT-Thread Development Team
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Change Logs:
# Date Author Notes
# 2015-01-20 Bernard Add copyright information
import os
import sys
# Add parent directory to path to import utils
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from utils import *
from utils import _make_path_relative
import rtconfig
makefile = '''phony := all
all:
include config.mk
ifneq ($(MAKE_LIB),1)
TARGET := rtthread.elf
include src.mk
endif
$(if $(strip $(RTT_ROOT)),,$(error RTT_ROOT not defined))
include $(RTT_ROOT)/tools/rtthread.mk
'''
def TargetMakefile(env):
project = ProjectInfo(env)
BSP_ROOT = os.path.abspath(env['BSP_ROOT'])
RTT_ROOT = os.path.abspath(env['RTT_ROOT'])
match_bsp = False
if BSP_ROOT.startswith(RTT_ROOT):
match_bsp = True
make = open('config.mk', 'w')
make.write('BSP_ROOT ?= %s\n' % BSP_ROOT.replace('\\', '/'))
make.write('RTT_ROOT ?= %s\n' % RTT_ROOT.replace('\\', '/'))
make.write('\n')
cross = os.path.abspath(rtconfig.EXEC_PATH)
cross = os.path.join(cross, rtconfig.PREFIX)
make.write('CROSS_COMPILE ?=%s' % cross.replace('\\', '\\\\'))
make.write('\n')
make.write('\n')
make.write('CFLAGS :=%s' % (rtconfig.CFLAGS))
make.write('\n')
make.write('AFLAGS :=%s' % (rtconfig.AFLAGS))
make.write('\n')
make.write('LFLAGS :=%s' % (rtconfig.LFLAGS))
make.write('\n')
if 'CXXFLAGS' in dir(rtconfig):
make.write('CXXFLAGS :=%s' % (rtconfig.CXXFLAGS))
make.write('\n')
if ('LIBS' in env):
make.write('EXTERN_LIB := ')
for tlib in env['LIBS']:
make.write('-l%s ' % (tlib))
if ('LIBPATH' in env):
for tlibpath in env['LIBPATH']:
make.write('-L%s ' % (tlibpath))
make.write('\n')
make.write('\n')
Files = project['FILES']
Headers = project['HEADERS']
CPPDEFINES = project['CPPDEFINES']
paths = [os.path.normpath(i) for i in project['CPPPATH']]
CPPPATH = []
for path in paths:
fn = os.path.normpath(path)
if match_bsp:
if fn.startswith(BSP_ROOT):
fn = '$(BSP_ROOT)' + fn.replace(BSP_ROOT, '')
elif fn.startswith(RTT_ROOT):
fn = '$(RTT_ROOT)' + fn.replace(RTT_ROOT, '')
else:
if fn.startswith(RTT_ROOT):
fn = '$(RTT_ROOT)' + fn.replace(RTT_ROOT, '')
elif fn.startswith(BSP_ROOT):
fn = '$(BSP_ROOT)' + fn.replace(BSP_ROOT, '')
CPPPATH.append(fn)
path = ''
paths = CPPPATH
for item in paths:
path += '\t-I%s \\\n' % item
make.write('CPPPATHS :=')
if path[0] == '\t': path = path[1:]
length = len(path)
if path[length - 2] == '\\': path = path[:length - 2]
make.write(path)
make.write('\n')
make.write('\n')
defines = ''
for item in project['CPPDEFINES']:
defines += ' -D%s' % item
make.write('DEFINES :=')
make.write(defines)
make.write('\n')
files = Files
Files = []
for file in files:
fn = os.path.normpath(file)
if match_bsp:
if fn.startswith(BSP_ROOT):
fn = '$(BSP_ROOT)' + fn.replace(BSP_ROOT, '')
elif fn.startswith(RTT_ROOT):
fn = '$(RTT_ROOT)' + fn.replace(RTT_ROOT, '')
else:
if fn.startswith(RTT_ROOT):
fn = '$(RTT_ROOT)' + fn.replace(RTT_ROOT, '')
elif fn.startswith(BSP_ROOT):
fn = '$(BSP_ROOT)' + fn.replace(BSP_ROOT, '')
Files.append(fn)
# print(fn)
src = open('src.mk', 'w')
files = Files
src.write('SRC_FILES :=\n')
for item in files:
src.write('SRC_FILES +=%s\n' % item.replace('\\', '/'))
make = open('Makefile', 'w')
make.write(makefile)
make.close()
return
+357
View File
@@ -0,0 +1,357 @@
import os
import re
from string import Template
try:
import rtconfig
except ImportError:
# Mock rtconfig for testing
class MockRtconfig:
pass
rtconfig = MockRtconfig()
import shutil
import time
# version
MODULE_VER_NUM = 1
cproject_temp = """<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?fileVersion 4.0.0?><cproject storage_type_id="org.eclipse.cdt.core.XmlProjectDescriptionStorage">
<storageModule moduleId="org.eclipse.cdt.core.settings">
<cconfiguration id="ilg.gnuarmeclipse.managedbuild.cross.config.elf.debug.553091094">
<storageModule buildSystemId="org.eclipse.cdt.managedbuilder.core.configurationDataProvider" id="ilg.gnuarmeclipse.managedbuild.cross.config.elf.debug.553091094" moduleId="org.eclipse.cdt.core.settings" name="Debug">
<externalSettings/>
<extensions>
<extension id="org.eclipse.cdt.core.ELF" point="org.eclipse.cdt.core.BinaryParser"/>
<extension id="org.eclipse.cdt.core.GASErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.GmakeErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.GLDErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.CWDLocator" point="org.eclipse.cdt.core.ErrorParser"/>
<extension id="org.eclipse.cdt.core.GCCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
</extensions>
</storageModule>
<storageModule moduleId="cdtBuildSystem" version="4.0.0">
<configuration artifactName="rtthread" buildArtefactType="org.eclipse.cdt.build.core.buildArtefactType.exe" buildProperties="org.eclipse.cdt.build.core.buildArtefactType=org.eclipse.cdt.build.core.buildArtefactType.exe,org.eclipse.cdt.build.core.buildType=org.eclipse.cdt.build.core.buildType.debug" cleanCommand="${cross_rm} -rf" description="" id="ilg.gnuarmeclipse.managedbuild.cross.config.elf.debug.553091094" name="Debug" parent="ilg.gnuarmeclipse.managedbuild.cross.config.elf.debug">
<folderInfo id="ilg.gnuarmeclipse.managedbuild.cross.config.elf.debug.553091094." name="/" resourcePath="">
<toolChain id="ilg.gnuarmeclipse.managedbuild.cross.toolchain.elf.debug.1201710416" name="ARM Cross GCC" superClass="ilg.gnuarmeclipse.managedbuild.cross.toolchain.elf.debug">
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.addtools.createflash.251260409" name="Create flash image" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.addtools.createflash" useByScannerDiscovery="false" value="true" valueType="boolean"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.addtools.createlisting.1365878149" name="Create extended listing" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.addtools.createlisting" useByScannerDiscovery="false"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.addtools.printsize.709136944" name="Print size" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.addtools.printsize" useByScannerDiscovery="false" value="true" valueType="boolean"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.optimization.level.1986446770" name="Optimization Level" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.optimization.level" useByScannerDiscovery="true" value="ilg.gnuarmeclipse.managedbuild.cross.option.optimization.level.none" valueType="enumerated"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.optimization.messagelength.1312975261" name="Message length (-fmessage-length=0)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.optimization.messagelength" useByScannerDiscovery="true" value="false" valueType="boolean"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.optimization.signedchar.1538128212" name="'char' is signed (-fsigned-char)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.optimization.signedchar" useByScannerDiscovery="true" value="false" valueType="boolean"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.optimization.functionsections.2136804218" name="Function sections (-ffunction-sections)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.optimization.functionsections" useByScannerDiscovery="true" value="true" valueType="boolean"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.optimization.datasections.244767666" name="Data sections (-fdata-sections)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.optimization.datasections" useByScannerDiscovery="true" value="true" valueType="boolean"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.debugging.level.1055848773" name="Debug level" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.debugging.level" useByScannerDiscovery="true" value="ilg.gnuarmeclipse.managedbuild.cross.option.debugging.level.default" valueType="enumerated"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.debugging.format.501941135" name="Debug format" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.debugging.format" useByScannerDiscovery="true" value="ilg.gnuarmeclipse.managedbuild.cross.option.debugging.format.dwarf2" valueType="enumerated"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.toolchain.name.1696308067" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.toolchain.name" useByScannerDiscovery="false" value="GNU Tools for ARM Embedded Processors" valueType="string"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.architecture.1558403188" name="Architecture" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.architecture" useByScannerDiscovery="false" value="ilg.gnuarmeclipse.managedbuild.cross.option.architecture.arm" valueType="enumerated"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.arm.target.family.749415257" name="ARM family" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.arm.target.family" useByScannerDiscovery="false" value="ilg.gnuarmeclipse.managedbuild.cross.option.arm.target.mcpu.cortex-m4" valueType="enumerated"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.arm.target.instructionset.2114153533" name="Instruction set" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.arm.target.instructionset" useByScannerDiscovery="false" value="ilg.gnuarmeclipse.managedbuild.cross.option.arm.target.instructionset.thumb" valueType="enumerated"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.command.prefix.1600865811" name="Prefix" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.command.prefix" useByScannerDiscovery="false" value="arm-none-eabi-" valueType="string"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.command.c.1109963929" name="C compiler" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.command.c" useByScannerDiscovery="false" value="gcc" valueType="string"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.command.cpp.1040883831" name="C++ compiler" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.command.cpp" useByScannerDiscovery="false" value="g++" valueType="string"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.command.ar.1678200391" name="Archiver" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.command.ar" useByScannerDiscovery="false" value="ar" valueType="string"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.command.objcopy.1171840296" name="Hex/Bin converter" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.command.objcopy" useByScannerDiscovery="false" value="objcopy" valueType="string"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.command.objdump.342604837" name="Listing generator" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.command.objdump" useByScannerDiscovery="false" value="objdump" valueType="string"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.command.size.898269225" name="Size command" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.command.size" useByScannerDiscovery="false" value="size" valueType="string"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.command.make.2016398076" name="Build command" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.command.make" useByScannerDiscovery="false" value="make" valueType="string"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.command.rm.1606171496" name="Remove command" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.command.rm" useByScannerDiscovery="false" value="rm" valueType="string"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.toolchain.id.540792084" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.toolchain.id" useByScannerDiscovery="false" value="1287942917" valueType="string"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.arm.target.architecture.430121817" name="Architecture" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.arm.target.architecture" useByScannerDiscovery="false" value="ilg.gnuarmeclipse.managedbuild.cross.option.arm.target.arch.none" valueType="enumerated"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.arm.target.fpu.abi.966735324" name="Float ABI" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.arm.target.fpu.abi" useByScannerDiscovery="true" value="ilg.gnuarmeclipse.managedbuild.cross.option.arm.target.fpu.abi.hard" valueType="enumerated"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.warnings.allwarn.1381561249" name="Enable all common warnings (-Wall)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.warnings.allwarn" useByScannerDiscovery="true" value="true" valueType="boolean"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.target.other.2041717463" name="Other target flags" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.target.other" useByScannerDiscovery="true" value="" valueType="string"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.arm.target.fpu.unit.1463655269" name="FPU Type" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.arm.target.fpu.unit" useByScannerDiscovery="true" value="ilg.gnuarmeclipse.managedbuild.cross.option.arm.target.fpu.unit.fpv4spd16" valueType="enumerated"/>
<targetPlatform archList="all" binaryParser="org.eclipse.cdt.core.ELF" id="ilg.gnuarmeclipse.managedbuild.cross.targetPlatform.1798638225" isAbstract="false" osList="all" superClass="ilg.gnuarmeclipse.managedbuild.cross.targetPlatform"/>
<builder buildPath="${workspace_loc:/${ProjName}/Debug" cleanBuildTarget="clean2" id="ilg.gnuarmeclipse.managedbuild.cross.builder.1736709688" keepEnvironmentInBuildfile="false" managedBuildOn="true" name="Gnu Make Builder" parallelBuildOn="true" parallelizationNumber="optimal" superClass="ilg.gnuarmeclipse.managedbuild.cross.builder"/>
<tool commandLinePattern="${COMMAND} ${cross_toolchain_flags} ${FLAGS} -c ${OUTPUT_FLAG} ${OUTPUT_PREFIX}${OUTPUT} ${INPUTS}" id="ilg.gnuarmeclipse.managedbuild.cross.tool.assembler.1810966071" name="GNU ARM Cross Assembler" superClass="ilg.gnuarmeclipse.managedbuild.cross.tool.assembler">
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.assembler.usepreprocessor.1072524326" name="Use preprocessor" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.assembler.usepreprocessor" useByScannerDiscovery="false" value="true" valueType="boolean"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.assembler.include.paths.161242639" name="Include paths (-I)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.assembler.include.paths" useByScannerDiscovery="true"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.assembler.defs.1521934876" name="Defined symbols (-D)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.assembler.defs" useByScannerDiscovery="true"/>
<option IS_BUILTIN_EMPTY="false" IS_VALUE_EMPTY="false" id="ilg.gnuarmeclipse.managedbuild.cross.option.assembler.flags.1325367962" name="Assembler flags" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.assembler.flags" useByScannerDiscovery="false" valueType="stringList">
<listOptionValue builtIn="false" value="-mimplicit-it=thumb"/>
</option>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.assembler.other.647856572" name="Other assembler flags" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.assembler.other" useByScannerDiscovery="false" value="a_misc_flag" valueType="string"/>
<inputType id="ilg.gnuarmeclipse.managedbuild.cross.tool.assembler.input.1843333483" superClass="ilg.gnuarmeclipse.managedbuild.cross.tool.assembler.input"/>
</tool>
<tool commandLinePattern="${COMMAND} ${cross_toolchain_flags} ${FLAGS} -c ${OUTPUT_FLAG} ${OUTPUT_PREFIX}${OUTPUT} ${INPUTS}" id="ilg.gnuarmeclipse.managedbuild.cross.tool.c.compiler.1570350559" name="GNU ARM Cross C Compiler" superClass="ilg.gnuarmeclipse.managedbuild.cross.tool.c.compiler">
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.c.compiler.include.paths.634882052" name="Include paths (-I)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.c.compiler.include.paths" useByScannerDiscovery="true"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.c.compiler.defs.100549972" name="Defined symbols (-D)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.c.compiler.defs" useByScannerDiscovery="true"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.c.compiler.other.2133065240" name="Other compiler flags" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.c.compiler.other" useByScannerDiscovery="true" value="c_misc_flag" valueType="string"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.c.compiler.include.files.714348818" name="Include files (-include)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.c.compiler.include.files" useByScannerDiscovery="true"/>
<inputType id="ilg.gnuarmeclipse.managedbuild.cross.tool.c.compiler.input.992053063" superClass="ilg.gnuarmeclipse.managedbuild.cross.tool.c.compiler.input"/>
</tool>
<tool commandLinePattern="${COMMAND} ${cross_toolchain_flags} ${FLAGS} ${OUTPUT_FLAG} ${OUTPUT_PREFIX}${OUTPUT} ${INPUTS}" id="ilg.gnuarmeclipse.managedbuild.cross.tool.c.linker.869072473" name="Cross ARM C Linker" superClass="ilg.gnuarmeclipse.managedbuild.cross.tool.c.linker">
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.c.linker.gcsections.1167322178" name="Remove unused sections (-Xlinker --gc-sections)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.c.linker.gcsections" useByScannerDiscovery="false" value="true" valueType="boolean"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.c.linker.nostart.351692886" name="Do not use standard start files (-nostartfiles)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.c.linker.nostart" useByScannerDiscovery="false" value="false" valueType="boolean"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.c.linker.nostdlibs.1009243715" name="No startup or default libs (-nostdlib)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.c.linker.nostdlibs" useByScannerDiscovery="false" value="false" valueType="boolean"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.c.linker.nodeflibs.2016026082" name="Do not use default libraries (-nodefaultlibs)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.c.linker.nodeflibs" useByScannerDiscovery="false" value="false" valueType="boolean"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.c.linker.usenewlibnano.923990336" name="Use newlib-nano (--specs=nano.specs)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.c.linker.usenewlibnano" useByScannerDiscovery="false" value="false" valueType="boolean"/>
<option defaultValue="true" id="ilg.gnuarmeclipse.managedbuild.cross.option.c.linker.shared.548869459" name="Shared (-shared)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.c.linker.shared" useByScannerDiscovery="false" valueType="boolean"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.c.linker.scriptfile.1818777301" name="Script files (-T)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.c.linker.scriptfile" useByScannerDiscovery="false"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.c.linker.libs.1135656995" name="Libraries (-l)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.c.linker.libs" useByScannerDiscovery="false"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.c.linker.paths.36884122" name="Library search path (-L)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.c.linker.paths" useByScannerDiscovery="false"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.c.linker.other.396049466" name="Other linker flags" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.c.linker.other" useByScannerDiscovery="false" value="c_link_misc_flag" valueType="string"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.c.linker.cref.1645737861" name="Cross reference (-Xlinker --cref)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.c.linker.cref" useByScannerDiscovery="false" value="true" valueType="boolean"/>
<inputType id="ilg.gnuarmeclipse.managedbuild.cross.tool.c.linker.input.334732222" superClass="ilg.gnuarmeclipse.managedbuild.cross.tool.c.linker.input">
<additionalInput kind="additionalinputdependency" paths="$(USER_OBJS)"/>
<additionalInput kind="additionalinput" paths="$(LIBS)"/>
</inputType>
</tool>
<tool commandLinePattern="${COMMAND} ${cross_toolchain_flags} ${FLAGS} ${OUTPUT_FLAG} ${OUTPUT_PREFIX}${OUTPUT} ${INPUTS}" id="ilg.gnuarmeclipse.managedbuild.cross.tool.cpp.linker.1601059928" name="GNU ARM Cross C++ Linker" superClass="ilg.gnuarmeclipse.managedbuild.cross.tool.cpp.linker">
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.linker.gcsections.437759352" name="Remove unused sections (-Xlinker --gc-sections)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.linker.gcsections" useByScannerDiscovery="false" value="true" valueType="boolean"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.linker.scriptfile.1101974459" name="Script files (-T)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.linker.scriptfile" useByScannerDiscovery="false"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.linker.cref.2007675975" name="Cross reference (-Xlinker --cref)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.linker.cref" useByScannerDiscovery="false" value="true" valueType="boolean"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.linker.usenewlibnano.2105838438" name="Use newlib-nano (--specs=nano.specs)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.linker.usenewlibnano" useByScannerDiscovery="false" value="true" valueType="boolean"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.linker.libs.934137837" name="Libraries (-l)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.linker.libs" useByScannerDiscovery="false"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.linker.nostart.2118356996" name="Do not use standard start files (-nostartfiles)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.linker.nostart" useByScannerDiscovery="false" value="false" valueType="boolean"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.linker.nodeflibs.1427884346" name="Do not use default libraries (-nodefaultlibs)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.linker.nodeflibs" useByScannerDiscovery="false" value="false" valueType="boolean"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.linker.nostdlibs.1433863653" name="No startup or default libs (-nostdlib)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.linker.nostdlibs" useByScannerDiscovery="false" value="false" valueType="boolean"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.linker.printgcsections.1387745410" name="Print removed sections (-Xlinker --print-gc-sections)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.linker.printgcsections" useByScannerDiscovery="false" value="false" valueType="boolean"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.linker.strip.1230158061" name="Omit all symbol information (-s)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.linker.strip" useByScannerDiscovery="false" value="false" valueType="boolean"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.linker.printmap.1307581821" name="Print link map (-Xlinker --print-map)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.linker.printmap" useByScannerDiscovery="false" value="false" valueType="boolean"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.linker.useprintffloat.960778920" name="Use float with nano printf (-u _printf_float)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.linker.useprintffloat" useByScannerDiscovery="false" value="false" valueType="boolean"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.linker.usescanffloat.637205035" name="Use float with nano scanf (-u _scanf_float)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.linker.usescanffloat" useByScannerDiscovery="false" value="false" valueType="boolean"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.linker.usenewlibnosys.1948314201" name="Do not use syscalls (--specs=nosys.specs)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.linker.usenewlibnosys" useByScannerDiscovery="false" value="false" valueType="boolean"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.linker.verbose.273162112" name="Verbose (-v)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.linker.verbose" useByScannerDiscovery="false" value="false" valueType="boolean"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.linker.paths.1399535143" name="Library search path (-L)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.linker.paths" useByScannerDiscovery="false"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.linker.other.882307902" name="Other linker flags" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.linker.other" useByScannerDiscovery="false" value="cpp_link_misc_flag" valueType="string"/>
<inputType id="ilg.gnuarmeclipse.managedbuild.cross.tool.cpp.linker.input.262373798" superClass="ilg.gnuarmeclipse.managedbuild.cross.tool.cpp.linker.input">
<additionalInput kind="additionalinputdependency" paths="$(USER_OBJS)"/>
<additionalInput kind="additionalinput" paths="$(LIBS)"/>
</inputType>
</tool>
<tool id="ilg.gnuarmeclipse.managedbuild.cross.tool.archiver.506412204" name="GNU ARM Cross Archiver" superClass="ilg.gnuarmeclipse.managedbuild.cross.tool.archiver"/>
<tool id="ilg.gnuarmeclipse.managedbuild.cross.tool.createflash.1461589245" name="GNU ARM Cross Create Flash Image" superClass="ilg.gnuarmeclipse.managedbuild.cross.tool.createflash">
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.createflash.choice.1937707052" name="Output file format (-O)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.createflash.choice" useByScannerDiscovery="false" value="ilg.gnuarmeclipse.managedbuild.cross.option.createflash.choice.binary" valueType="enumerated"/>
</tool>
<tool id="ilg.gnuarmeclipse.managedbuild.cross.tool.createlisting.82359725" name="GNU ARM Cross Create Listing" superClass="ilg.gnuarmeclipse.managedbuild.cross.tool.createlisting">
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.createlisting.source.601724476" name="Display source (--source|-S)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.createlisting.source" value="true" valueType="boolean"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.createlisting.allheaders.692505279" name="Display all headers (--all-headers|-x)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.createlisting.allheaders" value="true" valueType="boolean"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.createlisting.demangle.97345172" name="Demangle names (--demangle|-C)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.createlisting.demangle" value="true" valueType="boolean"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.createlisting.linenumbers.1342893377" name="Display line numbers (--line-numbers|-l)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.createlisting.linenumbers" value="true" valueType="boolean"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.createlisting.wide.1533725981" name="Wide lines (--wide|-w)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.createlisting.wide" value="true" valueType="boolean"/>
</tool>
<tool id="ilg.gnuarmeclipse.managedbuild.cross.tool.printsize.1073550295" name="GNU ARM Cross Print Size" superClass="ilg.gnuarmeclipse.managedbuild.cross.tool.printsize">
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.printsize.format.946451386" name="Size format" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.printsize.format" useByScannerDiscovery="false"/>
</tool>
<tool commandLinePattern="${COMMAND} ${cross_toolchain_flags} ${FLAGS} -c ${OUTPUT_FLAG} ${OUTPUT_PREFIX}${OUTPUT} ${INPUTS}" id="ilg.gnuarmeclipse.managedbuild.cross.tool.cpp.compiler.1302177015" name="GNU ARM Cross C++ Compiler" superClass="ilg.gnuarmeclipse.managedbuild.cross.tool.cpp.compiler">
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.compiler.defs.704468062" name="Defined symbols (-D)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.compiler.defs" useByScannerDiscovery="true"/>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.compiler.include.paths.302877723" name="Include paths (-I)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.compiler.include.paths" useByScannerDiscovery="true"/>
<option IS_BUILTIN_EMPTY="false" IS_VALUE_EMPTY="false" id="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.compiler.include.files.343249373" name="Include files (-include)" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.compiler.include.files" useByScannerDiscovery="true" valueType="includeFiles">
<listOptionValue builtIn="false" value="&quot;${workspace_loc:/${ProjName}/rtconfig_preinc.h}&quot;"/>
</option>
<option id="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.compiler.other.465079095" name="Other compiler flags" superClass="ilg.gnuarmeclipse.managedbuild.cross.option.cpp.compiler.other" useByScannerDiscovery="true" value="cpp_misc_flag" valueType="string"/>
<inputType id="ilg.gnuarmeclipse.managedbuild.cross.tool.cpp.compiler.input.45918001" superClass="ilg.gnuarmeclipse.managedbuild.cross.tool.cpp.compiler.input"/>
</tool>
</toolChain>
</folderInfo>
<sourceEntries>
<entry excluding="|" flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name=""/>
</sourceEntries>
</configuration>
</storageModule>
<storageModule moduleId="org.eclipse.cdt.core.externalSettings"/>
</cconfiguration>
</storageModule>
<storageModule moduleId="cdtBuildSystem" version="4.0.0">
<project id="qemu-vexpress-a9.ilg.gnuarmeclipse.managedbuild.cross.target.elf.860020518" name="Executable" projectType="ilg.gnuarmeclipse.managedbuild.cross.target.elf"/>
</storageModule>
<storageModule moduleId="scannerConfiguration">
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
<scannerConfigBuildInfo instanceId="ilg.gnuarmeclipse.managedbuild.cross.config.elf.debug.553091094;ilg.gnuarmeclipse.managedbuild.cross.config.elf.debug.553091094.;ilg.gnuarmeclipse.managedbuild.cross.tool.c.compiler.1570350559;ilg.gnuarmeclipse.managedbuild.cross.tool.c.compiler.input.992053063">
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
</scannerConfigBuildInfo>
</storageModule>
<storageModule moduleId="org.eclipse.cdt.core.LanguageSettingsProviders"/>
<storageModule moduleId="refreshScope" versionNumber="2">
<configuration configurationName="Debug">
<resource resourceType="PROJECT" workspacePath="/f429_tmp"/>
</configuration>
</storageModule>
<storageModule moduleId="org.eclipse.cdt.make.core.buildtargets"/>
<storageModule moduleId="org.eclipse.cdt.internal.ui.text.commentOwnerProjectMappings">
<doc-comment-owner id="org.eclipse.cdt.ui.doxygen">
<path value=""/>
</doc-comment-owner>
</storageModule>
</cproject>"""
project_temp = """<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>__project_name_flag__</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.cdt.managedbuilder.core.genmakebuilder</name>
<triggers>clean,full,incremental,</triggers>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder</name>
<triggers>full,incremental,</triggers>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.cdt.core.cnature</nature>
<nature>org.rt-thread.studio.rttnature</nature>
<nature>org.eclipse.cdt.managedbuilder.core.managedBuildNature</nature>
<nature>org.eclipse.cdt.managedbuilder.core.ScannerConfigNature</nature>
</natures>
</projectDescription>"""
projcfg_ini_temp = """#RT-Thread Studio Project Configuration
# $time
cfg_version=v3.0
board_name=
bsp_version=
bsp_path=
chip_name=
project_base_rtt_bsp=true
is_use_scons_build=true
hardware_adapter=
selected_rtt_version=latest
board_base_nano_proj=false
is_base_example_project=false
example_name=
project_type=rt-thread
os_branch=master
os_version=latest
project_name=$project_name
output_project_path=$output_project_path"""
eclipse_core_runtime_temp = """content-types/enabled=true
content-types/org.eclipse.cdt.core.asmSource/file-extensions=s
eclipse.preferences.version=1"""
makefile_targets_temp = """clean2:
\t-$(RM) $(CC_DEPS)$(C++_DEPS)$(C_UPPER_DEPS)$(CXX_DEPS)$(SECONDARY_FLASH)$(SECONDARY_SIZE)$(ASM_DEPS)$(S_UPPER_DEPS)$(C_DEPS)$(CPP_DEPS)
\t-$(RM) $(OBJS) *.elf
\t-@echo ' '
*.elf: $(wildcard ../linkscripts/*/*.lds) $(wildcard ../linkscripts/*/*/*.lds)"""
def get_mcu_info(uvproj_file_path):
if os.path.exists(uvproj_file_path):
with open(uvproj_file_path, mode='r') as f:
data = f.read()
result = re.search("<Device>(.*)</Device>", data)
if result:
return result.group(1)
else:
return "unknown"
else:
return "unknown"
def gen_makefile_targets(output_file_path):
try:
w_str = makefile_targets_temp
dir_name = os.path.dirname(output_file_path)
if not os.path.exists(dir_name):
os.makedirs(dir_name)
with open(output_file_path, 'w') as f:
f.write(w_str)
return True
except Exception as e:
print(e)
return False
def gen_org_eclipse_core_runtime_prefs(output_file_path):
try:
w_str = eclipse_core_runtime_temp
dir_name = os.path.dirname(output_file_path)
if not os.path.exists(dir_name):
os.makedirs(dir_name)
with open(output_file_path, 'w') as f:
f.write(w_str)
return True
except Exception as e:
print(e)
return False
def gen_cproject_file(output_file_path):
template_file_path = os.path.join(os.path.dirname(__file__), 'template.cproject')
if os.path.exists(template_file_path):
try:
shutil.copy(template_file_path, output_file_path)
except Exception as e:
print(e)
return True
else:
CFLAGS = rtconfig.CFLAGS
AFLAGS = rtconfig.AFLAGS
LFLAGS = rtconfig.LFLAGS
if 'CXXFLAGS' in dir(rtconfig):
CXXFLAGS = rtconfig.CXXFLAGS
else:
CXXFLAGS = ""
if "-T" in LFLAGS:
items = str(LFLAGS).split()
t_index = items.index("-T")
items[t_index] = ""
items[t_index + 1] = ""
LFLAGS = " ".join(items)
try:
w_str = cproject_temp
if "a_misc_flag" in w_str:
w_str = w_str.replace("a_misc_flag", AFLAGS)
if "c_misc_flag" in w_str:
w_str = w_str.replace("c_misc_flag", CFLAGS)
if "cpp_misc_flag" in w_str:
w_str = w_str.replace("cpp_misc_flag", CXXFLAGS)
if "c_link_misc_flag" in w_str:
w_str = w_str.replace("c_link_misc_flag", LFLAGS)
if "cpp_link_misc_flag" in w_str:
w_str = w_str.replace("cpp_link_misc_flag", LFLAGS)
dir_name = os.path.dirname(output_file_path)
if not os.path.exists(dir_name):
os.makedirs(dir_name)
with open(output_file_path, 'w') as f:
f.write(w_str)
return True
except Exception as e:
return False
def gen_project_file(output_file_path):
try:
w_str = project_temp
dir_name = os.path.dirname(output_file_path)
if not os.path.exists(dir_name):
os.makedirs(dir_name)
with open(output_file_path, 'w') as f:
f.write(w_str)
return True
except Exception as e:
return False
def gen_projcfg_ini_file(chip_name, project_name, output_file_path):
try:
projcfg_file_tmp = Template(projcfg_ini_temp)
w_str = projcfg_file_tmp.substitute(time=time.strftime("%a %b %d %H:%M:%S %Y", time.localtime()),
project_name=project_name,
output_project_path=os.path.abspath(""))
dir_name = os.path.dirname(output_file_path)
if not os.path.exists(dir_name):
os.makedirs(dir_name)
with open(output_file_path, 'w') as f:
f.write(w_str)
return True
except Exception as e:
return False
+92
View File
@@ -0,0 +1,92 @@
# SEGGER Embedded Studio Project Generator
import os
import sys
import xml.etree.ElementTree as etree
from xml.etree.ElementTree import SubElement
from utils import _make_path_relative
from utils import xml_indent
from utils import ProjectInfo
def SDKAddGroup(parent, name, files, project_path):
# don't add an empty group
if len(files) == 0:
return
group = SubElement(parent, 'folder', attrib={'Name': name})
for f in files:
fn = f.rfile()
name = fn.name
path = os.path.dirname(fn.abspath)
basename = os.path.basename(path)
path = _make_path_relative(project_path, path)
elm_attr_name = os.path.join(path, name)
file = SubElement(group, 'file', attrib={'file_name': elm_attr_name})
return group
def SESProject(env) :
target = 'project.emProject'
tree = etree.parse('template.emProject')
# print(etree.dump(tree.getroot()))
# etree.dump(tree.getroot())
project = ProjectInfo(env)
# print(project)
# return
project_path = os.path.abspath(env['BSP_ROOT'])
script = env['project']
root = tree.getroot()
out = file(target, 'w')
out.write('<!DOCTYPE CrossStudio_Project_File>\n')
CPPPATH = []
CPPDEFINES = []
LINKFLAGS = ''
CFLAGS = ''
project_node = tree.find('project')
for group in script:
# print(group)
group_tree = SDKAddGroup(project_node, group['name'], group['src'], project_path)
# get each group's cc flags
if 'CFLAGS' in group and group['CFLAGS']:
if CFLAGS:
CFLAGS += ' ' + group['CFLAGS']
else:
CFLAGS += group['CFLAGS']
# get each group's link flags
if 'LINKFLAGS' in group and group['LINKFLAGS']:
if LINKFLAGS:
LINKFLAGS += ' ' + group['LINKFLAGS']
else:
LINKFLAGS += group['LINKFLAGS']
# write include path, definitions and link flags
path = ';'.join([_make_path_relative(project_path, os.path.normpath(i)) for i in project['CPPPATH']])
path = path.replace('\\', '/')
defines = ';'.join(set(project['CPPDEFINES']))
node = tree.findall('project/configuration')
for item in node:
if item.get('c_preprocessor_definitions'):
item.set('c_preprocessor_definitions', defines)
if item.get('c_user_include_directories'):
item.set('c_user_include_directories', path)
xml_indent(root)
out.write(etree.tostring(root, encoding='utf-8'))
out.close()
return
+41
View File
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<CodeBlocks_project_file>
<FileVersion major="1" minor="6" />
<Project>
<Option title="project" />
<Option pch_mode="2" />
<Option compiler="gcc" />
<Build>
<Target title="Debug">
<Option output="build/bin/Debug/project" prefix_auto="1" extension_auto="1" />
<Option object_output="build/obj/Debug/" />
<Option type="1" />
<Option compiler="gcc" />
<Compiler>
<Add option="-g" />
</Compiler>
</Target>
<Target title="Release">
<Option output="build/bin/Release/project" prefix_auto="1" extension_auto="1" />
<Option object_output="build/obj/Release/" />
<Option type="1" />
<Option compiler="gcc" />
<Compiler>
<Add option="-O2" />
</Compiler>
<Linker>
<Add option="-s" />
</Linker>
</Target>
</Build>
<Compiler>
<Add option="-Wall" />
</Compiler>
<Extensions>
<code_completion />
<envvars />
<debugger />
<lib_finder disable_auto="1" />
</Extensions>
</Project>
</CodeBlocks_project_file>
+101
View File
@@ -0,0 +1,101 @@
#
# File : ua.py
# This file is part of RT-Thread RTOS
# COPYRIGHT (C) 2006 - 2015, RT-Thread Development Team
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Change Logs:
# Date Author Notes
# 2015-01-20 Bernard Add copyright information
#
import os
import sys
from utils import _make_path_relative
def PrefixPath(prefix, path):
path = os.path.abspath(path)
prefix = os.path.abspath(prefix)
if sys.platform == 'win32':
prefix = prefix.lower()
path = path.lower()
if path.startswith(prefix):
return True
return False
def PrepareUA(project, RTT_ROOT, BSP_ROOT):
with open('rtua.py', 'w') as ua:
# ua.write('import os\n')
# ua.write('import sys\n')
ua.write('\n')
print(RTT_ROOT)
CPPPATH = []
CPPDEFINES = []
for group in project:
# get each include path
if 'CPPPATH' in group and group['CPPPATH']:
CPPPATH += group['CPPPATH']
# get each group's definitions
if 'CPPDEFINES' in group and group['CPPDEFINES']:
CPPDEFINES += group['CPPDEFINES']
if len(CPPPATH):
# use absolute path
for i in range(len(CPPPATH)):
CPPPATH[i] = os.path.abspath(CPPPATH[i])
# remove repeat path
paths = [i for i in set(CPPPATH)]
CPPPATH = []
for path in paths:
if PrefixPath(RTT_ROOT, path):
CPPPATH += ['RTT_ROOT + "/%s",' % _make_path_relative(RTT_ROOT, path).replace('\\', '/')]
elif PrefixPath(BSP_ROOT, path):
CPPPATH += ['BSP_ROOT + "/%s",' % _make_path_relative(BSP_ROOT, path).replace('\\', '/')]
else:
CPPPATH += ['"%s",' % path.replace('\\', '/')]
CPPPATH.sort()
ua.write('def GetCPPPATH(BSP_ROOT, RTT_ROOT):\n')
ua.write('\tCPPPATH=[\n')
for path in CPPPATH:
ua.write('\t\t%s\n' % path)
ua.write('\t]\n\n')
ua.write('\treturn CPPPATH\n\n')
else:
ua.write('def GetCPPPATH(BSP_ROOT, RTT_ROOT):\n')
ua.write('\tCPPPATH=[]\n\n')
ua.write('\treturn CPPPATH\n\n')
if len(CPPDEFINES):
CPPDEFINES = [i for i in set(CPPDEFINES)]
ua.write('def GetCPPDEFINES():\n')
ua.write('\tCPPDEFINES=%s\n' % str(CPPDEFINES))
ua.write('\treturn CPPDEFINES\n\n')
else:
ua.write('def GetCPPDEFINES():\n')
ua.write('\tCPPDEFINES=""\n\n')
ua.write('\treturn CPPDEFINES\n\n')
+189
View File
@@ -0,0 +1,189 @@
#
# File : vs.py
# This file is part of RT-Thread RTOS
# COPYRIGHT (C) 2006 - 2015, RT-Thread Development Team
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Change Logs:
# Date Author Notes
# 2015-01-20 Bernard Add copyright information
#
import os
import sys
import string
import uuid
import utils
from xml.etree.ElementTree import SubElement
from utils import _make_path_relative
from utils import xml_indent
# Add parent directory to path to import building
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import building
import xml.etree.ElementTree as etree
fs_encoding = sys.getfilesystemencoding()
def VS_AddGroup(ProjectFiles, parent, name, files, libs, project_path):
Filter = SubElement(parent, 'Filter')
Filter.set('Name', name) #set group name to group
for f in files:
fn = f.rfile()
name = fn.name
path = os.path.dirname(fn.abspath)
path = _make_path_relative(project_path, path)
path = os.path.join(path, name)
try:
path = path.decode(fs_encoding)
except:
path = path
File = SubElement(Filter, 'File')
File.set('RelativePath', path)
for lib in libs:
name = os.path.basename(lib)
path = os.path.dirname(lib)
path = _make_path_relative(project_path, path)
path = os.path.join(path, name)
File = SubElement(Filter, 'File')
try:
path = path.decode(fs_encoding)
except:
path = path
File.set('RelativePath', path)
def VS_AddHeadFilesGroup(program, elem, project_path):
utils.source_ext = []
utils.source_ext = ["h"]
for item in program:
utils.walk_children(item)
utils.source_list.sort()
# print utils.source_list
for f in utils.source_list:
path = _make_path_relative(project_path, f)
File = SubElement(elem, 'File')
try:
path = path.decode(fs_encoding)
except:
path = path
File.set('RelativePath', path)
def VSProject(target, script, program):
project_path = os.path.dirname(os.path.abspath(target))
tree = etree.parse('template_vs2005.vcproj')
root = tree.getroot()
out = open(target, 'w')
out.write('<?xml version="1.0" encoding="UTF-8"?>\r\n')
ProjectFiles = []
# add "*.c" files group
for elem in tree.iter(tag='Filter'):
if elem.attrib['Name'] == 'Source Files':
#print elem.tag, elem.attrib
break
for group in script:
libs = []
if 'LIBS' in group and group['LIBS']:
for item in group['LIBS']:
lib_path = ''
for path_item in group['LIBPATH']:
full_path = os.path.join(path_item, item + '.lib')
if os.path.isfile(full_path): # has this library
lib_path = full_path
if lib_path != '':
libs.append(lib_path)
group_xml = VS_AddGroup(ProjectFiles, elem, group['name'], group['src'], libs, project_path)
# add "*.h" files group
for elem in tree.iter(tag='Filter'):
if elem.attrib['Name'] == 'Header Files':
break
VS_AddHeadFilesGroup(program, elem, project_path)
# write head include path
if 'CPPPATH' in building.Env:
cpp_path = building.Env['CPPPATH']
paths = set()
for path in cpp_path:
inc = _make_path_relative(project_path, os.path.normpath(path))
paths.add(inc) #.replace('\\', '/')
paths = [i for i in paths]
paths.sort()
cpp_path = ';'.join(paths)
# write include path, definitions
for elem in tree.iter(tag='Tool'):
if elem.attrib['Name'] == 'VCCLCompilerTool':
#print elem.tag, elem.attrib
break
elem.set('AdditionalIncludeDirectories', cpp_path)
# write cppdefinitons flags
if 'CPPDEFINES' in building.Env:
CPPDEFINES = building.Env['CPPDEFINES']
definitions = []
if type(CPPDEFINES[0]) == type(()):
for item in CPPDEFINES:
definitions += [i for i in item]
definitions = ';'.join(definitions)
else:
definitions = ';'.join(building.Env['CPPDEFINES'])
elem.set('PreprocessorDefinitions', definitions)
# write link flags
# write lib dependence
if 'LIBS' in building.Env:
for elem in tree.iter(tag='Tool'):
if elem.attrib['Name'] == 'VCLinkerTool':
break
libs_with_extention = [i+'.lib' for i in building.Env['LIBS']]
libs = ' '.join(libs_with_extention)
elem.set('AdditionalDependencies', libs)
# write lib include path
if 'LIBPATH' in building.Env:
lib_path = building.Env['LIBPATH']
paths = set()
for path in lib_path:
inc = _make_path_relative(project_path, os.path.normpath(path))
paths.add(inc) #.replace('\\', '/')
paths = [i for i in paths]
paths.sort()
lib_paths = ';'.join(paths)
elem.set('AdditionalLibraryDirectories', lib_paths)
xml_indent(root)
text = etree.tostring(root, encoding='utf-8')
try:
text = text.decode(encoding="utf-8")
except:
text = text
out.write(text)
out.close()
+284
View File
@@ -0,0 +1,284 @@
#
# File : vs2012.py
# This file is part of RT-Thread RTOS
# COPYRIGHT (C) 2006 - 2015, RT-Thread Development Team
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Change Logs:
# Date Author Notes
# 2015-01-20 Bernard Add copyright information
#
import os
import sys
import string
import uuid
import utils
from xml.etree.ElementTree import SubElement
from utils import _make_path_relative
from utils import xml_indent
# Add parent directory to path to import building
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import building
import xml.etree.ElementTree as etree
fs_encoding = sys.getfilesystemencoding()
#reference
# http://woodpecker.org.cn/diveintopython3/xml.html
# https://pycoders-weekly-chinese.readthedocs.org/en/latest/issue6/processing-xml-in-python-with-element-tree.html
# http://www.cnblogs.com/ifantastic/archive/2013/04/12/3017110.html
filter_project = etree.Element('Project', attrib={'ToolsVersion':'4.0'})
def get_uuid():
id = uuid.uuid1() # UUID('3e5526c0-2841-11e3-a376-20cf3048bcb3')
if sys.version > '3':
idstr = id.urn[9:] #'urn:uuid:3e5526c0-2841-11e3-a376-20cf3048bcb3'[9:]
else:
# python3 is no decode function
idstr = id.get_urn()[9:] #'urn:uuid:3e5526c0-2841-11e3-a376-20cf3048bcb3'[9:]
return '{'+idstr+'}'
def VS2012_AddGroup(parent, group_name, files, project_path):
for f in files:
fn = f.rfile()
name = fn.name
path = os.path.dirname(fn.abspath)
path = _make_path_relative(project_path, path)
path = os.path.join(path, name)
ClCompile = SubElement(parent, 'ClCompile')
if sys.version > '3':
ClCompile.set('Include', path)
else:
# python3 is no decode function
ClCompile.set('Include', path.decode(fs_encoding))
Filter = SubElement(ClCompile, 'Filter')
Filter.text='Source Files\\'+group_name
def VS2012_CreateFilter(script, project_path):
c_ItemGroup = SubElement(filter_project, 'ItemGroup')
filter_ItemGroup = SubElement(filter_project, 'ItemGroup')
Filter = SubElement(filter_ItemGroup, 'Filter')
Filter.set('Include', 'Source Files')
UniqueIdentifier = SubElement(Filter, 'UniqueIdentifier')
UniqueIdentifier.text = get_uuid()
Extensions = SubElement(Filter, 'Extensions')
Extensions.text = 'cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx'
Filter = SubElement(filter_ItemGroup, 'Filter')
Filter.set('Include', 'Header Files')
UniqueIdentifier = SubElement(Filter, 'UniqueIdentifier')
UniqueIdentifier.text = get_uuid()
Extensions = SubElement(Filter, 'Extensions')
Extensions.text = 'h;hpp;hxx;hm;inl;inc;xsd'
for group in script:
VS2012_AddGroup(c_ItemGroup, group['name'], group['src'], project_path)
Filter = SubElement(filter_ItemGroup, 'Filter')
Filter.set('Include', 'Source Files\\'+group['name'])
UniqueIdentifier = SubElement(Filter, 'UniqueIdentifier')
UniqueIdentifier.text = get_uuid()
#program: object from scons
# parent: xml node
# file_type: C or H
# files: c/h list
# project_path
def VS_add_ItemGroup(parent, file_type, files, project_path):
from building import Rtt_Root
RTT_ROOT = os.path.normpath(Rtt_Root)
file_dict = {'C':"ClCompile", 'H':'ClInclude'}
item_tag = file_dict[file_type]
ItemGroup = SubElement(parent, 'ItemGroup')
for f in files:
fn = f.rfile()
name = fn.name
path = os.path.dirname(fn.abspath)
objpath = path.lower()
if len(project_path) >= len(RTT_ROOT) :
if objpath.startswith(project_path.lower()) :
objpath = ''.join('bsp'+objpath[len(project_path):])
else :
objpath = ''.join('kernel'+objpath[len(RTT_ROOT):])
else :
if objpath.startswith(RTT_ROOT.lower()) :
objpath = ''.join('kernel'+objpath[len(RTT_ROOT):])
else :
objpath = ''.join('bsp'+objpath[len(project_path):])
path = _make_path_relative(project_path, path)
path = os.path.join(path, name)
File = SubElement(ItemGroup, item_tag)
if sys.version > '3':
File.set('Include', path)
else:
# python3 is no decode function
File.set('Include', path.decode(fs_encoding))
if file_type == 'C' :
ObjName = SubElement(File, 'ObjectFileName')
ObjName.text = ''.join('$(IntDir)'+objpath+'\\')
def VS_add_HeadFiles(program, elem, project_path):
utils.source_ext = []
utils.source_ext = ["h"]
for item in program:
utils.walk_children(item)
utils.source_list.sort()
# print utils.source_list
ItemGroup = SubElement(elem, 'ItemGroup')
filter_h_ItemGroup = SubElement(filter_project, 'ItemGroup')
for f in utils.source_list:
path = _make_path_relative(project_path, f)
File = SubElement(ItemGroup, 'ClInclude')
if sys.version > '3':
File.set('Include', path)
else:
# python3 is no decode function
File.set('Include', path.decode(fs_encoding))
# add project.vcxproj.filter
ClInclude = SubElement(filter_h_ItemGroup, 'ClInclude')
if sys.version > '3':
ClInclude.set('Include', path)
else:
# python3 is no decode function
ClInclude.set('Include', path.decode(fs_encoding))
Filter = SubElement(ClInclude, 'Filter')
Filter.text='Header Files'
def VS2012Project(target, script, program):
project_path = os.path.dirname(os.path.abspath(target))
tree = etree.parse('template_vs2012.vcxproj')
root = tree.getroot()
elem = root
out = open(target, 'w')
out.write('<?xml version="1.0" encoding="UTF-8"?>\r\n')
ProjectFiles = []
# add "*.c or *.h" files
VS2012_CreateFilter(script, project_path)
# add "*.c" files
for group in script:
VS_add_ItemGroup(elem, 'C', group['src'], project_path)
# add "*.h" files
VS_add_HeadFiles(program, elem, project_path)
# write head include path
if 'CPPPATH' in building.Env:
cpp_path = building.Env['CPPPATH']
paths = set()
for path in cpp_path:
inc = _make_path_relative(project_path, os.path.normpath(path))
paths.add(inc) #.replace('\\', '/')
paths = [i for i in paths]
paths.sort()
cpp_path = ';'.join(paths) + ';%(AdditionalIncludeDirectories)'
# write include path
for elem in tree.iter(tag='AdditionalIncludeDirectories'):
elem.text = cpp_path
break
# write cppdefinitons flags
if 'CPPDEFINES' in building.Env:
for elem in tree.iter(tag='PreprocessorDefinitions'):
CPPDEFINES = building.Env['CPPDEFINES']
definitions = []
if type(CPPDEFINES[0]) == type(()):
for item in CPPDEFINES:
definitions += [i for i in item]
definitions = ';'.join(definitions)
else:
definitions = ';'.join(building.Env['CPPDEFINES'])
definitions = definitions + ';%(PreprocessorDefinitions)'
elem.text = definitions
break
# write link flags
# write lib dependence (Link)
if 'LIBS' in building.Env:
for elem in tree.iter(tag='AdditionalDependencies'):
libs_with_extention = [i+'.lib' for i in building.Env['LIBS']]
libs = ';'.join(libs_with_extention) + ';%(AdditionalDependencies)'
elem.text = libs
break
# write lib include path
if 'LIBPATH' in building.Env:
lib_path = building.Env['LIBPATH']
paths = set()
for path in lib_path:
inc = _make_path_relative(project_path, os.path.normpath(path))
paths.add(inc)
paths = [i for i in paths]
paths.sort()
lib_paths = ';'.join(paths) + ';%(AdditionalLibraryDirectories)'
for elem in tree.iter(tag='AdditionalLibraryDirectories'):
elem.text = lib_paths
break
xml_indent(root)
if sys.version > '3':
vcxproj_string = etree.tostring(root, encoding='unicode')
else:
# python3 is no decode function
vcxproj_string = etree.tostring(root, encoding='utf-8')
root_node=r'<Project DefaultTargets="Build" ToolsVersion="4.0">'
out.write(r'<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">')
out.write(vcxproj_string[len(root_node):])
out.close()
xml_indent(filter_project)
if sys.version > '3':
filter_string = etree.tostring(filter_project, encoding='unicode')
else:
# python3 is no decode function
filter_string = etree.tostring(filter_project, encoding='utf-8')
out = open('project.vcxproj.filters', 'w')
out.write('<?xml version="1.0" encoding="UTF-8"?>\r\n')
root_node=r'<Project ToolsVersion="4.0">'
out.write(r'<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">')
out.write(filter_string[len(root_node):])
out.close()
+478
View File
@@ -0,0 +1,478 @@
#
# File : vsc.py
# This file is part of RT-Thread RTOS
# COPYRIGHT (C) 2006 - 2018, RT-Thread Development Team
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Change Logs:
# Date Author Notes
# 2018-05-30 Bernard The first version
# 2023-03-03 Supperthomas Add the vscode workspace config file
# 2024-12-13 Supperthomas covert compile_commands.json to vscode workspace file
# 2025-07-05 Bernard Add support for generating .vscode/c_cpp_properties.json
# and .vscode/settings.json files
"""
Utils for VSCode
"""
import os
import json
import utils
import rtconfig
from SCons.Script import GetLaunchDir
from utils import _make_path_relative
def find_first_node_with_two_children(tree):
for key, subtree in tree.items():
if len(subtree) >= 2:
return key, subtree
result = find_first_node_with_two_children(subtree)
if result:
return result
return None, None
def filt_tree(tree):
key, subtree = find_first_node_with_two_children(tree)
if key:
return {key: subtree}
return {}
def add_path_to_tree(tree, path):
parts = path.split(os.sep)
current_level = tree
for part in parts:
if part not in current_level:
current_level[part] = {}
current_level = current_level[part]
def build_tree(paths):
tree = {}
current_working_directory = os.getcwd()
current_folder_name = os.path.basename(current_working_directory)
# Filter out invalid and non-existent paths
relative_dirs = []
for path in paths:
normalized_path = os.path.normpath(path)
try:
rel_path = os.path.relpath(normalized_path, start=current_working_directory)
add_path_to_tree(tree, normalized_path)
except ValueError:
print(f"Remove unexcpect dir:{path}")
return tree
def print_tree(tree, indent=''):
for key, subtree in sorted(tree.items()):
print(indent + key)
print_tree(subtree, indent + ' ')
def extract_source_dirs(compile_commands):
source_dirs = set()
for entry in compile_commands:
file_path = os.path.abspath(entry['file'])
if file_path.endswith('.c'):
dir_path = os.path.dirname(file_path)
source_dirs.add(dir_path)
# command or arguments
command = entry.get('command') or entry.get('arguments')
if isinstance(command, str):
parts = command.split()
else:
parts = command
# 读取-I或者/I
for i, part in enumerate(parts):
if part.startswith('-I'):
include_dir = part[2:] if len(part) > 2 else parts[i + 1]
source_dirs.add(os.path.abspath(include_dir))
elif part.startswith('/I'):
include_dir = part[2:] if len(part) > 2 else parts[i + 1]
source_dirs.add(os.path.abspath(include_dir))
return sorted(source_dirs)
def is_path_in_tree(path, tree):
parts = path.split(os.sep)
current_level = tree
found_first_node = False
root_key = list(tree.keys())[0]
index_start = parts.index(root_key)
length = len(parts)
try:
for i in range(index_start, length):
current_level = current_level[parts[i]]
return True
except KeyError:
return False
def generate_code_workspace_file(source_dirs,command_json_path,root_path):
current_working_directory = os.getcwd()
current_folder_name = os.path.basename(current_working_directory)
relative_dirs = []
for dir_path in source_dirs:
try:
rel_path = os.path.relpath(dir_path, root_path)
relative_dirs.append(rel_path)
except ValueError:
continue
root_rel_path = os.path.relpath(root_path, current_working_directory)
command_json_path = os.path.relpath(current_working_directory, root_path) + os.sep
workspace_data = {
"folders": [
{
"path": f"{root_rel_path}"
}
],
"settings": {
"clangd.arguments": [
f"--compile-commands-dir={command_json_path}",
"--header-insertion=never"
],
"files.exclude": {dir.replace('\\','/'): True for dir in sorted(relative_dirs)}
}
}
workspace_filename = f'{current_folder_name}.code-workspace'
with open(workspace_filename, 'w') as f:
json.dump(workspace_data, f, indent=4)
print(f'Workspace file {workspace_filename} created.')
def command_json_to_workspace(root_path,command_json_path):
with open('build/compile_commands.json', 'r') as f:
compile_commands = json.load(f)
source_dirs = extract_source_dirs(compile_commands)
tree = build_tree(source_dirs)
#print_tree(tree)
filtered_tree = filt_tree(tree)
print("Filtered Directory Tree:")
#print_tree(filtered_tree)
# 打印filtered_tree的root节点的相对路径
root_key = list(filtered_tree.keys())[0]
print(f"Root node relative path: {root_key}")
# 初始化exclude_fold集合
exclude_fold = set()
# os.chdir(root_path)
# 轮询root文件夹下面的每一个文件夹和子文件夹
for root, dirs, files in os.walk(root_path):
# 检查当前root是否在filtered_tree中
if not is_path_in_tree(root, filtered_tree):
exclude_fold.add(root)
dirs[:] = [] # 不往下轮询子文件夹
continue
for dir in dirs:
dir_path = os.path.join(root, dir)
if not is_path_in_tree(dir_path, filtered_tree):
exclude_fold.add(dir_path)
generate_code_workspace_file(exclude_fold,command_json_path,root_path)
def delete_repeatelist(data):
temp_dict = set([str(item) for item in data])
data = [eval(i) for i in temp_dict]
return data
def GenerateCFiles(env):
"""
Generate c_cpp_properties.json and build/compile_commands.json files
"""
if not os.path.exists('.vscode'):
os.mkdir('.vscode')
with open('.vscode/c_cpp_properties.json', 'w') as vsc_file:
info = utils.ProjectInfo(env)
cc = os.path.join(rtconfig.EXEC_PATH, rtconfig.CC)
cc = os.path.abspath(cc).replace('\\', '/')
config_obj = {}
config_obj['name'] = 'Linux'
config_obj['defines'] = info['CPPDEFINES']
intelliSenseMode = 'linux-gcc-arm'
if cc.find('aarch64') != -1:
intelliSenseMode = 'linux-gcc-arm64'
elif cc.find('arm') != -1:
intelliSenseMode = 'linux-gcc-arm'
config_obj['intelliSenseMode'] = intelliSenseMode
config_obj['compilerPath'] = cc
config_obj['cStandard'] = "c99"
config_obj['cppStandard'] = "c++11"
config_obj['compileCommands'] ="build/compile_commands.json"
# format "a/b," to a/b. remove first quotation mark("),and remove end (",)
includePath = []
for i in info['CPPPATH']:
if i[0] == '\"' and i[len(i) - 2:len(i)] == '\",':
includePath.append(_make_path_relative(os.getcwd(), i[1:len(i) - 2]))
else:
includePath.append(_make_path_relative(os.getcwd(), i))
config_obj['includePath'] = includePath
json_obj = {}
json_obj['configurations'] = [config_obj]
vsc_file.write(json.dumps(json_obj, ensure_ascii=False, indent=4))
"""
Generate vscode.code-workspace files by build/compile_commands.json
"""
if os.path.exists('build/compile_commands.json'):
command_json_to_workspace(env['RTT_ROOT'],'build/compile_commands.json')
return
"""
Generate vscode.code-workspace files
"""
with open('vscode.code-workspace', 'w') as vsc_space_file:
info = utils.ProjectInfo(env)
path_list = []
for i in info['CPPPATH']:
if _make_path_relative(os.getcwd(), i)[0] == '.':
if i[0] == '\"' and i[len(i) - 2:len(i)] == '\",':
path_list.append({'path':_make_path_relative(os.getcwd(), i[1:len(i) - 2])})
else:
path_list.append({'path':_make_path_relative(os.getcwd(), i)})
for i in info['DIRS']:
if _make_path_relative(os.getcwd(), i)[0] == '.':
if i[0] == '\"' and i[len(i) - 2:len(i)] == '\",':
path_list.append({'path':_make_path_relative(os.getcwd(), i[1:len(i) - 2])})
else:
path_list.append({'path':_make_path_relative(os.getcwd(), i)})
json_obj = {}
path_list = delete_repeatelist(path_list)
path_list = sorted(path_list, key=lambda x: x["path"])
for path in path_list:
if path['path'] != '.':
normalized_path = path['path'].replace('\\', os.path.sep)
segments = [p for p in normalized_path.split(os.path.sep) if p != '..']
path['name'] = 'rtthread/' + '/'.join(segments)
json_obj['folders'] = path_list
if os.path.exists('build/compile_commands.json'):
json_obj['settings'] = {
"clangd.arguments": [
"--compile-commands-dir=.",
"--header-insertion=never"
]
}
vsc_space_file.write(json.dumps(json_obj, ensure_ascii=False, indent=4))
return
def GenerateProjectFiles(env):
"""
Generate project.json file
"""
if not os.path.exists('.vscode'):
os.mkdir('.vscode')
project = env['project']
with open('.vscode/project.json', 'w') as vsc_file:
groups = []
for group in project:
if len(group['src']) > 0:
item = {}
item['name'] = group['name']
item['path'] = _make_path_relative(os.getcwd(), group['path'])
item['files'] = []
for fn in group['src']:
item['files'].append(str(fn))
# append SConscript if exist
if os.path.exists(os.path.join(item['path'], 'SConscript')):
item['files'].append(os.path.join(item['path'], 'SConscript'))
groups.append(item)
json_dict = {}
json_dict['RT-Thread'] = env['RTT_ROOT']
json_dict['Groups'] = groups
# write groups to project.json
vsc_file.write(json.dumps(json_dict, ensure_ascii=False, indent=4))
return
def GenerateVSCode(env):
print('Update setting files for VSCode...')
GenerateProjectFiles(env)
GenerateCFiles(env)
print('Done!')
return
import os
def find_rtconfig_dirs(bsp_dir, project_dir):
"""
Search for subdirectories containing 'rtconfig.h' under 'bsp_dir' (up to 4 levels deep), excluding 'project_dir'.
Args:
bsp_dir (str): The root directory to search (absolute path).
project_dir (str): The subdirectory to exclude from the search (absolute path).
Returns
list: A list of absolute paths to subdirectories containing 'rtconfig.h'.
"""
result = []
project_dir = os.path.normpath(project_dir)
# list the bsp_dir to add result
list = os.listdir(bsp_dir)
for item in list:
item = os.path.join(bsp_dir, item)
# if item is a directory
if not os.path.isdir(item):
continue
# print(item, project_dir)
if not project_dir.startswith(item):
result.append(os.path.abspath(item))
parent_dir = os.path.dirname(project_dir)
if parent_dir != bsp_dir:
list = os.listdir(parent_dir)
for item in list:
item = os.path.join(parent_dir, item)
rtconfig_path = os.path.join(item, 'rtconfig.h')
if os.path.isfile(rtconfig_path):
abs_path = os.path.abspath(item)
if abs_path != project_dir:
result.append(abs_path)
# print(result)
return result
def GenerateVSCodeWorkspace(env):
"""
Generate vscode.code files
"""
print('Update workspace files for VSCode...')
# get the launch directory
cwd = GetLaunchDir()
# get .vscode/workspace.json file
workspace_file = os.path.join(cwd, '.vscode', 'workspace.json')
if not os.path.exists(workspace_file):
print('Workspace file not found, skip generating.')
return
try:
# read the workspace file
with open(workspace_file, 'r') as f:
workspace_data = json.load(f)
# get the bsp directories from the workspace data, bsps/folder
bsp_dir = os.path.join(cwd, workspace_data.get('bsps', {}).get('folder', ''))
if not bsp_dir:
print('No BSP directories found in the workspace file, skip generating.')
return
except Exception as e:
print('Error reading workspace file, skip generating.')
return
# check if .vscode folder exists, if not, create it
if not os.path.exists(os.path.join(cwd, '.vscode')):
os.mkdir(os.path.join(cwd, '.vscode'))
with open(os.path.join(cwd, '.vscode/c_cpp_properties.json'), 'w') as vsc_file:
info = utils.ProjectInfo(env)
cc = os.path.join(rtconfig.EXEC_PATH, rtconfig.CC)
cc = os.path.abspath(cc).replace('\\', '/')
config_obj = {}
config_obj['name'] = 'Linux'
config_obj['defines'] = info['CPPDEFINES']
intelliSenseMode = 'linux-gcc-arm'
if cc.find('aarch64') != -1:
intelliSenseMode = 'linux-gcc-arm64'
elif cc.find('arm') != -1:
intelliSenseMode = 'linux-gcc-arm'
config_obj['intelliSenseMode'] = intelliSenseMode
config_obj['compilerPath'] = cc
config_obj['cStandard'] = "c99"
config_obj['cppStandard'] = "c++11"
# format "a/b," to a/b. remove first quotation mark("),and remove end (",)
includePath = []
for i in info['CPPPATH']:
if i[0] == '\"' and i[len(i) - 2:len(i)] == '\",':
includePath.append(_make_path_relative(cwd, i[1:len(i) - 2]))
else:
includePath.append(_make_path_relative(cwd, i))
# make sort for includePath
includePath = sorted(includePath, key=lambda x: x.lower())
config_obj['includePath'] = includePath
json_obj = {}
json_obj['configurations'] = [config_obj]
vsc_file.write(json.dumps(json_obj, ensure_ascii=False, indent=4))
# generate .vscode/settings.json
vsc_settings = {}
settings_path = os.path.join(cwd, '.vscode/settings.json')
if os.path.exists(settings_path):
with open(settings_path, 'r') as f:
# read the existing settings file and load to vsc_settings
vsc_settings = json.load(f)
with open(settings_path, 'w') as vsc_file:
vsc_settings['files.exclude'] = {
"**/__pycache__": True,
"tools/kconfig-frontends": True,
}
result = find_rtconfig_dirs(bsp_dir, os.getcwd())
if result:
# sort the result
result = sorted(result, key=lambda x: x.lower())
for item in result:
# make the path relative to the current working directory
rel_path = os.path.relpath(item, cwd)
# add the path to files.exclude
vsc_settings['files.exclude'][rel_path] = True
vsc_settings['search.exclude'] = vsc_settings['files.exclude']
# write the settings to the file
vsc_file.write(json.dumps(vsc_settings, ensure_ascii=False, indent=4))
print('Done!')
return
+145
View File
@@ -0,0 +1,145 @@
add_rules("mode.debug", "mode.release")
toolchain("arm-none-eabi")
set_kind("standalone")
set_sdkdir("/home/bernard/.env/tools/scripts/packages/arm-none-eabi-gcc-v13.2.rel1")
toolchain_end()
target("rt-thread")
set_kind("binary")
set_toolchains("arm-none-eabi")
add_files(
"applications/main.c",
"../../../components/libc/compilers/common/cctype.c",
"../../../components/libc/compilers/common/cstdlib.c",
"../../../components/libc/compilers/common/cstring.c",
"../../../components/libc/compilers/common/ctime.c",
"../../../components/libc/compilers/common/cunistd.c",
"../../../components/libc/compilers/common/cwchar.c",
"../../../components/libc/compilers/newlib/syscalls.c",
"../../../components/drivers/core/device.c",
"../../../components/drivers/ipc/completion_comm.c",
"../../../components/drivers/ipc/completion_up.c",
"../../../components/drivers/ipc/condvar.c",
"../../../components/drivers/ipc/dataqueue.c",
"../../../components/drivers/ipc/pipe.c",
"../../../components/drivers/ipc/ringblk_buf.c",
"../../../components/drivers/ipc/ringbuffer.c",
"../../../components/drivers/ipc/waitqueue.c",
"../../../components/drivers/ipc/workqueue.c",
"../../../components/drivers/pin/dev_pin.c",
"../../../components/drivers/serial/dev_serial.c",
"../libraries/HAL_Drivers/drivers/drv_gpio.c",
"../libraries/HAL_Drivers/drivers/drv_usart.c",
"../libraries/HAL_Drivers/drv_common.c",
"board/CubeMX_Config/Src/stm32f4xx_hal_msp.c",
"board/board.c",
"../../../components/finsh/shell.c",
"../../../components/finsh/msh.c",
"../../../components/finsh/msh_parse.c",
"../../../components/finsh/cmd.c",
"../../../src/clock.c",
"../../../src/components.c",
"../../../src/cpu_up.c",
"../../../src/defunct.c",
"../../../src/idle.c",
"../../../src/ipc.c",
"../../../src/irq.c",
"../../../src/kservice.c",
"../../../src/mem.c",
"../../../src/mempool.c",
"../../../src/object.c",
"../../../src/scheduler_comm.c",
"../../../src/scheduler_up.c",
"../../../src/thread.c",
"../../../src/timer.c",
"../../../src/klibc/kstring.c",
"../../../src/klibc/rt_vsscanf.c",
"../../../src/klibc/kstdio.c",
"../../../src/klibc/rt_vsnprintf_tiny.c",
"../../../src/klibc/kerrno.c",
"../../../libcpu/arm/common/atomic_arm.c",
"../../../libcpu/arm/common/div0.c",
"../../../libcpu/arm/common/showmem.c",
"../../../libcpu/arm/cortex-m4/context_gcc.S",
"../../../libcpu/arm/cortex-m4/cpuport.c",
"packages/stm32f4_cmsis_driver-latest/Source/Templates/gcc/startup_stm32f412zx.s",
"packages/stm32f4_cmsis_driver-latest/Source/Templates/system_stm32f4xx.c",
"packages/stm32f4_hal_driver-latest/Src/stm32f4xx_hal_dma_ex.c",
"packages/stm32f4_hal_driver-latest/Src/stm32f4xx_hal_usart.c",
"packages/stm32f4_hal_driver-latest/Src/stm32f4xx_hal_pwr_ex.c",
"packages/stm32f4_hal_driver-latest/Src/stm32f4xx_hal_cryp.c",
"packages/stm32f4_hal_driver-latest/Src/stm32f4xx_hal_gpio.c",
"packages/stm32f4_hal_driver-latest/Src/stm32f4xx_hal_rcc.c",
"packages/stm32f4_hal_driver-latest/Src/stm32f4xx_hal.c",
"packages/stm32f4_hal_driver-latest/Src/stm32f4xx_hal_cortex.c",
"packages/stm32f4_hal_driver-latest/Src/stm32f4xx_hal_pwr.c",
"packages/stm32f4_hal_driver-latest/Src/stm32f4xx_hal_cec.c",
"packages/stm32f4_hal_driver-latest/Src/stm32f4xx_hal_cryp_ex.c",
"packages/stm32f4_hal_driver-latest/Src/stm32f4xx_hal_dma.c",
"packages/stm32f4_hal_driver-latest/Src/stm32f4xx_hal_uart.c",
"packages/stm32f4_hal_driver-latest/Src/stm32f4xx_hal_rcc_ex.c",
"packages/stm32f4_hal_driver-latest/Src/stm32f4xx_hal_crc.c",
"packages/stm32f4_hal_driver-latest/Src/stm32f4xx_hal_rng.c"
)
add_includedirs(
"applications",
"packages/CMSIS-Core-latest/Include",
"../../../components/libc/compilers/newlib",
"../../../components/libc/compilers/common/include",
"../../../components/drivers/include",
"../../../components/drivers/smp_call",
"../../../components/drivers/phy",
"board",
"board/CubeMX_Config/Inc",
"../libraries/HAL_Drivers/drivers",
"../libraries/HAL_Drivers/drivers/config",
"../libraries/HAL_Drivers",
"../../../components/finsh",
".",
"../../../include",
"../../../libcpu/arm/common",
"../../../libcpu/arm/cortex-m4",
"../../../components/libc/posix/ipc",
"../../../components/libc/posix/io/poll",
"../../../components/libc/posix/io/eventfd",
"../../../components/libc/posix/io/epoll",
"packages/stm32f4_cmsis_driver-latest/Include",
"packages/stm32f4_hal_driver-latest/Inc",
"packages/stm32f4_hal_driver-latest/Inc/Legacy"
)
add_defines(
"RT_USING_LIBC",
"RT_USING_NEWLIBC",
"STM32F412Zx",
"USE_HAL_DRIVER",
"_POSIX_C_SOURCE=1",
"__RTTHREAD__"
)
add_cflags(
" -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -Dgcc -O0 -gdwarf-2 -g" ,{force = true}
)
add_cxxflags(
" -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -Dgcc -O0 -gdwarf-2 -g" ,{force = true}
)
add_asflags(
" -c -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -x assembler-with-cpp -Wa,-mimplicit-it=thumb -gdwarf-2" ,{force = true}
)
add_ldflags(
" -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=hard -ffunction-sections -fdata-sections -Wl,--gc-sections,-Map=rt-thread.map,-cref,-u,Reset_Handler -T board/linker_scripts/link.lds" ,{force = true}
)
set_targetdir("./")
set_filename("rtthread.elf")
after_build(function(target)
os.exec("/home/bernard/.env/tools/scripts/packages/arm-none-eabi-gcc-v13.2.rel1/bin/arm-none-eabi-objcopy -O ihex rtthread.elf rtthread.hex")
os.exec("/home/bernard/.env/tools/scripts/packages/arm-none-eabi-gcc-v13.2.rel1/bin/arm-none-eabi-objcopy -O binary rtthread.elf rtthread.bin")
os.exec("/home/bernard/.env/tools/scripts/packages/arm-none-eabi-gcc-v13.2.rel1/bin/arm-none-eabi-size rtthread.elf")
end)
+93
View File
@@ -0,0 +1,93 @@
"""
Utils for CMake
Author: https://github.com/klivelinux
"""
import os
import utils
from string import Template
import rtconfig
from utils import _make_path_relative
class XmakeProject:
def __init__(self, env, project):
self.env = env
self.project = project
self.sdkdir = ""
self.bindir = ""
self.toolchain = ""
self.src_path = ""
self.inc_path = ""
self.cflags = ""
self.cxxflags = ""
self.ldflags = ""
self.asflags = ""
self.define = ""
def set_toolchain_path(self):
self.bindir = os.path.abspath(rtconfig.EXEC_PATH).replace('\\', "/")
self.sdkdir = self.bindir[:-4]
# delete -
self.toolchain = rtconfig.PREFIX[:-1]
def set_target_config(self):
info = utils.ProjectInfo(self.env)
# 1. config src path
for group in self.project:
for f in group['src']:
# use relative path
path = _make_path_relative(os.getcwd(), os.path.normpath(f.rfile().abspath))
self.src_path += "\t\"{0}\",\n".format(path.replace("\\", "/"))
self.src_path = self.src_path[:-2]
# 2. config dir path
for i in info['CPPPATH']:
# use relative path
path = _make_path_relative(os.getcwd(), i)
self.inc_path += "\t\"{0}\",\n".format(path.replace("\\", "/"))
self.inc_path = self.inc_path[:-2]
# 3. config cflags
self.cflags = rtconfig.CFLAGS.replace('\\', "/").replace('\"', "\\\"")
# 4. config cxxflags
if 'CXXFLAGS' in dir(rtconfig):
self.cxxflags = rtconfig.CXXFLAGS.replace('\\', "/").replace('\"', "\\\"")
else:
self.cxxflags = self.cflags
# 5. config asflags
self.asflags = rtconfig.AFLAGS.replace('\\', "/").replace('\"', "\\\"")
# 6. config lflags
self.ldflags = rtconfig.LFLAGS.replace('\\', "/").replace('\"', "\\\"")
# 7. config define
for i in info['CPPDEFINES']:
self.define += "\t\"{0}\",\n".format(i)
self.define = self.define[:-2]
def generate_xmake_file(self):
if os.getenv('RTT_ROOT'):
RTT_ROOT = os.getenv('RTT_ROOT')
else:
RTT_ROOT = os.path.normpath(os.getcwd() + '/../../..')
template_path = os.path.join(RTT_ROOT, "tools", "targets", "xmake.lua")
with open(template_path, "r") as f:
data = f.read()
data = Template(data)
data = data.safe_substitute(toolchain=self.toolchain, sdkdir=self.sdkdir, bindir=self.bindir, src_path=self.src_path, inc_path=self.inc_path,
define=self.define, cflags=self.cflags, cxxflags=self.cxxflags, asflags=self.asflags,
ldflags=self.ldflags, target="rt-thread")
with open(os.path.join(os.path.dirname(__file__), "xmake.lua"), "w") as f:
f.write(data)
def XMakeProject(env,project):
print('Update setting files for xmake.lua...')
xmake_project = XmakeProject(env, project)
xmake_project.set_toolchain_path()
xmake_project.set_target_config()
xmake_project.generate_xmake_file()
print('Done!')
return
+100
View File
@@ -0,0 +1,100 @@
"""
Utils for CMake
Author: https://github.com/klivelinux
"""
import os
import sys
import re
import utils
import rtconfig
from utils import _make_path_relative
def GenerateCFiles(env,project):
info = utils.ProjectInfo(env)
ARCH = ".thumb" if rtconfig.CPU in ['cortex-m0', 'cortex-m3', 'cortex-m4', 'cortex-m7','cortex-m23','cortex-m33','cortex-m85'] else ".arm"
CFLAGS = rtconfig.CFLAGS.replace('\\', "/").replace('\"', "\\\"")
LFLAGS = rtconfig.LFLAGS.replace('\\', "/").replace('\"', "\\\"")
zig_file = open('build.zig', 'w')
if zig_file:
zig_file.write("const std = @import(\"std\");\n\n")
zig_file.write("const target = std.zig.CrossTarget{\n")
zig_file.write(" .cpu_arch = {},\n".format(ARCH))
zig_file.write(" .cpu_model = .{{ .explicit = &std.Target.{}.cpu.{} }},\n".format(rtconfig.ARCH, rtconfig.CPU.replace('-', '_')))
zig_file.write(" .os_tag = .freestanding,\n")
zig_file.write(" .abi = .eabi,\n")
zig_file.write("};\n\n")
zig_file.write("const c_includes = [_][]const u8{\n")
for i in info['CPPPATH']:
# use relative path
path = _make_path_relative(os.getcwd(), i)
zig_file.write("\t\"{}\",\n".format(path.replace("\\", "/")))
zig_file.write("};\n\n")
zig_file.write("const c_sources = [_][]const u8{\n")
for group in project:
for f in group['src']:
# use relative path
path = _make_path_relative(os.getcwd(), os.path.normpath(f.rfile().abspath))
zig_file.write("\t\"{}\",\n".format(path.replace("\\", "/")))
zig_file.write("};\n\n")
zig_file.write("const c_flags = [_][]const u8{\n")
zig_file.write("\t\"-std=c99\",\n")
zig_file.write("\t\"-ffunction-sections\",\n")
zig_file.write("\t\"-fdata-sections\",\n")
# conver CDefines to CFlags
for i in info['CPPDEFINES']:
zig_file.write("\t\"-D{}\",\n".format(i))
# conver LocalCDefines to CFlags
for group in project:
if 'LOCAL_CPPDEFINES' in group and group['LOCAL_CPPDEFINES']:
for i in group['LOCAL_CPPDEFINES']:
zig_file.write("\t\"-D{}\",\n".format(i))
zig_file.write("};\n\n")
zig_file.write("pub fn build(b: *std.Build) void {\n")
zig_file.write(" const optimize = .ReleaseSafe;\n\n")
zig_file.write(" const elf = b.addExecutable(.{\n")
zig_file.write(" .name = \"rtthread.elf\",\n")
zig_file.write(" .target = b.resolveTargetQuery(target),\n")
zig_file.write(" .optimize = optimize,\n")
zig_file.write(" .strip = false,\n")
zig_file.write(" });\n\n")
zig_file.write(" elf.entry = .{ .symbol_name = \"Reset_Handler\" };\n\n")
zig_file.write(" elf.addCSourceFiles(.{ .files = &c_sources, .flags = &c_flags });\n")
zig_file.write(" for (c_includes) |include| {\n")
zig_file.write(" elf.addIncludePath(b.path(include));\n")
zig_file.write(" }\n\n")
# find link script in rtconfig.LFLAGS
LINK_SCRIPT = re.search(r'-T\s*(\S+)', LFLAGS)
zig_file.write(" elf.setLinkerScript(b.path(\"{}\"));\n".format(LINK_SCRIPT.group(1)))
zig_file.write(" const copy_elf = b.addInstallArtifact(elf, .{});\n")
zig_file.write(" b.default_step.dependOn(&copy_elf.step);\n\n")
zig_file.write(" const bin = b.addObjCopy(elf.getEmittedBin(), .{ .format = .bin });\n")
zig_file.write(" bin.step.dependOn(&elf.step);\n")
zig_file.write(" const copy_bin = b.addInstallBinFile(bin.getOutput(), \"rtthread.bin\");\n")
zig_file.write(" b.default_step.dependOn(&copy_bin.step);\n")
zig_file.write("}\n")
zig_file.close()
return
def ZigBuildProject(env,project):
print('Update setting files for build.zig...')
GenerateCFiles(env,project)
print('Done!')
return
+32
View File
@@ -0,0 +1,32 @@
# 测试用例目录
本目录包含 RT-Thread 工具的测试脚本。
## 测试脚本
### test_preprocessor.py
SCons PreProcessor 补丁功能测试脚本。测试与 building.py 的集成,验证预处理器补丁是否正常工作。
### test_refactor.py
验证目标模块重构是否成功的测试脚本。测试内容包括:
- 目标模块导入
- Building.py 导入
- 目标函数调用
### mock_rtconfig.py
用于测试的模拟 rtconfig 模块。在实际 rtconfig 不可用的测试场景中提供模拟的 rtconfig 模块。
## 使用方法
要运行测试,请导航到此目录并执行:
```bash
python test_preprocessor.py
python test_refactor.py
```
## 说明
- 这些测试脚本用于验证 RT-Thread 工具的功能
- 可以独立运行或作为测试套件的一部分
- mock_rtconfig.py 文件被其他测试脚本用来模拟 rtconfig 模块
@@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
#
# Mock rtconfig module for testing purposes
#
# Mock configuration variables
CROSS_TOOL = 'gcc'
PLATFORM = 'gcc'
CC = 'gcc'
CXX = 'g++'
AS = 'as'
AR = 'ar'
LINK = 'gcc'
EXEC_PATH = '/usr/bin'
# Mock functions
def GetDepend(depend):
return True
# Mock environment
class MockEnv:
def __init__(self):
self.CPPPATH = []
self.CPPDEFINES = []
self.LIBS = []
self.LIBPATH = []
self.CFLAGS = []
self.CXXFLAGS = []
self.LINKFLAGS = []
self.ASFLAGS = []
# Global variables
Env = MockEnv()
Rtt_Root = '/mock/rt-thread'
Projects = []
@@ -0,0 +1,116 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# File : test_preprocessor_patch.py
# This file is part of RT-Thread RTOS
# COPYRIGHT (C) 2006 - 2025, RT-Thread Development Team
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Change Logs:
# Date Author Notes
# 2025-01-05 Assistant Test file for SCons PreProcessor patch
import sys
import os
# Add current directory to path for imports
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
def test_preprocessor_patch():
"""Test the SCons PreProcessor patch functionality"""
try:
from scons_preprocessor_patch import SConsPreProcessorPatch, create_preprocessor_instance
print("Testing SCons PreProcessor patch...")
# Test creating patch instance
patch = SConsPreProcessorPatch()
print("✓ SConsPreProcessorPatch instance created successfully")
# Test getting patched preprocessor
patched_class = patch.get_patched_preprocessor()
print("✓ Patched PreProcessor class retrieved successfully")
# Test creating preprocessor instance
preprocessor = create_preprocessor_instance()
print("✓ PreProcessor instance created successfully")
# Test basic functionality
test_content = """
#define TEST_MACRO 1
#ifdef TEST_MACRO
#define ENABLED_FEATURE 1
#else
#define DISABLED_FEATURE 1
#endif
"""
preprocessor.process_contents(test_content)
namespace = preprocessor.cpp_namespace
print("✓ PreProcessor processed test content successfully")
print(f" - TEST_MACRO: {namespace.get('TEST_MACRO', 'Not found')}")
print(f" - ENABLED_FEATURE: {namespace.get('ENABLED_FEATURE', 'Not found')}")
print(f" - DISABLED_FEATURE: {namespace.get('DISABLED_FEATURE', 'Not found')}")
print("\n✓ All tests passed! SCons PreProcessor patch is working correctly.")
return True
except ImportError as e:
print(f"✗ Import error: {e}")
print("Make sure SCons is available in the environment")
return False
except Exception as e:
print(f"✗ Test failed: {e}")
return False
def test_building_integration():
"""Test integration with building.py"""
try:
# Test that the function is available from the patch module
from scons_preprocessor_patch import create_preprocessor_instance
print("\nTesting scons_preprocessor_patch integration...")
# Test that the function is available
preprocessor = create_preprocessor_instance()
print("✓ create_preprocessor_instance function works from scons_preprocessor_patch")
# Test basic processing
test_content = "#define BUILD_TEST 1"
preprocessor.process_contents(test_content)
namespace = preprocessor.cpp_namespace
print(f"✓ Integration test passed: BUILD_TEST = {namespace.get('BUILD_TEST', 'Not found')}")
return True
except Exception as e:
print(f"✗ Integration test failed: {e}")
return False
if __name__ == "__main__":
print("SCons PreProcessor Patch Test Suite")
print("=" * 40)
success1 = test_preprocessor_patch()
success2 = test_building_integration()
if success1 and success2:
print("\n🎉 All tests passed! The refactoring was successful.")
sys.exit(0)
else:
print("\n❌ Some tests failed. Please check the implementation.")
sys.exit(1)
+121
View File
@@ -0,0 +1,121 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Test script to verify the refactoring is successful
import sys
import os
# Add current directory to path
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
# Mock rtconfig module for testing
import mock_rtconfig
sys.modules['rtconfig'] = mock_rtconfig
def test_targets_import():
"""Test if all target modules can be imported successfully"""
print("Testing targets module imports...")
try:
# Test importing targets module
import targets
print("✓ targets module imported successfully")
# Test importing individual target modules
target_modules = [
'keil', 'iar', 'vs', 'vs2012', 'codeblocks', 'ua',
'vsc', 'cdk', 'ses', 'eclipse', 'codelite',
'cmake', 'xmake', 'esp_idf', 'zigbuild', 'makefile', 'rt_studio'
]
for module_name in target_modules:
try:
module = getattr(targets, module_name)
print(f"{module_name} module imported successfully")
except AttributeError as e:
print(f"✗ Failed to import {module_name}: {e}")
return False
return True
except ImportError as e:
print(f"✗ Failed to import targets module: {e}")
return False
def test_building_import():
"""Test if building.py can import target modules"""
print("\nTesting building.py imports...")
try:
# Test importing building module
import building
print("✓ building module imported successfully")
# Test if GenTargetProject function exists
if hasattr(building, 'GenTargetProject'):
print("✓ GenTargetProject function found")
else:
print("✗ GenTargetProject function not found")
return False
return True
except ImportError as e:
print(f"✗ Failed to import building module: {e}")
return False
def test_target_functions():
"""Test if target functions can be called"""
print("\nTesting target function calls...")
try:
# Test importing specific target functions
from targets.keil import MDK4Project, MDK5Project
print("✓ Keil target functions imported successfully")
from targets.iar import IARProject
print("✓ IAR target functions imported successfully")
from targets.eclipse import TargetEclipse
print("✓ Eclipse target functions imported successfully")
from targets.cmake import CMakeProject
print("✓ CMake target functions imported successfully")
import targets.rt_studio
print("✓ RT-Studio target functions imported successfully")
return True
except ImportError as e:
print(f"✗ Failed to import target functions: {e}")
return False
def main():
"""Main test function"""
print("RT-Thread Tools Refactoring Test")
print("=" * 40)
success = True
# Run all tests
if not test_targets_import():
success = False
if not test_building_import():
success = False
if not test_target_functions():
success = False
print("\n" + "=" * 40)
if success:
print("✓ All tests passed! Refactoring is successful.")
return 0
else:
print("✗ Some tests failed. Please check the errors above.")
return 1
if __name__ == '__main__':
sys.exit(main())
+328
View File
@@ -0,0 +1,328 @@
#
# File : utils.py
# This file is part of RT-Thread RTOS
# COPYRIGHT (C) 2006 - 2015, RT-Thread Development Team
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Change Logs:
# Date Author Notes
# 2015-01-20 Bernard Add copyright information
# 2024-04-21 Bernard Add ImportModule to import local module
import sys
import os
import re
def splitall(loc):
"""
Return a list of the path components in loc. (Used by relpath_).
The first item in the list will be either ``os.curdir``, ``os.pardir``, empty,
or the root directory of loc (for example, ``/`` or ``C:\\).
The other items in the list will be strings.
Adapted from *path.py* by Jason Orendorff.
"""
parts = []
while loc != os.curdir and loc != os.pardir:
prev = loc
loc, child = os.path.split(prev)
if loc == prev:
break
parts.append(child)
parts.append(loc)
parts.reverse()
return parts
def _make_path_relative(origin, dest):
"""
Return the relative path between origin and dest.
If it's not possible return dest.
If they are identical return ``os.curdir``
Adapted from `path.py <http://www.jorendorff.com/articles/python/path/>`_ by Jason Orendorff.
"""
origin = os.path.abspath(origin).replace('\\', '/')
dest = os.path.abspath(dest).replace('\\', '/')
#
orig_list = splitall(os.path.normcase(origin))
# Don't normcase dest! We want to preserve the case.
dest_list = splitall(dest)
#
if orig_list[0] != os.path.normcase(dest_list[0]):
# Can't get here from there.
return dest
#
# Find the location where the two paths start to differ.
i = 0
for start_seg, dest_seg in zip(orig_list, dest_list):
if start_seg != os.path.normcase(dest_seg):
break
i += 1
#
# Now i is the point where the two paths diverge.
# Need a certain number of "os.pardir"s to work up
# from the origin to the point of divergence.
segments = [os.pardir] * (len(orig_list) - i)
# Need to add the diverging part of dest_list.
segments += dest_list[i:]
if len(segments) == 0:
# If they happen to be identical, use os.curdir.
return os.curdir
else:
# return os.path.join(*segments).replace('\\', '/')
return os.path.join(*segments)
def xml_indent(elem, level=0):
i = "\n" + level*" "
if len(elem):
if not elem.text or not elem.text.strip():
elem.text = i + " "
if not elem.tail or not elem.tail.strip():
elem.tail = i
for elem in elem:
xml_indent(elem, level+1)
if not elem.tail or not elem.tail.strip():
elem.tail = i
else:
if level and (not elem.tail or not elem.tail.strip()):
elem.tail = i
source_ext = ["c", "h", "s", "S", "cpp", "cxx", "cc", "xpm"]
source_list = []
def walk_children(child):
global source_list
global source_ext
# print child
full_path = child.rfile().abspath
file_type_list = full_path.rsplit('.',1)
#print file_type
if (len(file_type_list) > 1):
file_type = full_path.rsplit('.',1)[1]
if file_type in source_ext:
if full_path not in source_list:
source_list.append(full_path)
children = child.all_children()
if children != []:
for item in children:
walk_children(item)
def PrefixPath(prefix, path):
path = os.path.abspath(path)
prefix = os.path.abspath(prefix)
if sys.platform == 'win32':
prefix = prefix.lower()
path = path.lower()
if path.startswith(prefix):
return True
return False
def ListMap(l):
ret_list = []
for item in l:
if type(item) == type(()):
ret = ListMap(item)
ret_list += ret
elif type(item) == type([]):
ret = ListMap(item)
ret_list += ret
else:
ret_list.append(item)
return ret_list
def TargetGetList(env, postfix):
global source_ext
global source_list
target = env['target']
source_ext = postfix
for item in target:
walk_children(item)
source_list.sort()
return source_list
def ProjectInfo(env):
project = env['project']
RTT_ROOT = env['RTT_ROOT']
BSP_ROOT = env['BSP_ROOT']
FILES = []
DIRS = []
HEADERS = []
CPPPATH = []
CPPDEFINES = []
for group in project:
# get each files
if 'src' in group and group['src']:
FILES += group['src']
# get each include path
if 'CPPPATH' in group and group['CPPPATH']:
CPPPATH += group['CPPPATH']
if 'CPPDEFINES' in env:
CPPDEFINES = env['CPPDEFINES']
CPPDEFINES = ListMap(CPPDEFINES)
# process FILES and DIRS
if len(FILES):
# use absolute path
for i in range(len(FILES)):
FILES[i] = os.path.abspath(str(FILES[i]))
DIRS.append(os.path.dirname(FILES[i]))
FILES.sort()
DIRS = list(set(DIRS))
DIRS.sort()
# process HEADERS
HEADERS = TargetGetList(env, ['h'])
# process CPPPATH
if len(CPPPATH):
# use absolute path
for i in range(len(CPPPATH)):
CPPPATH[i] = os.path.abspath(CPPPATH[i])
# remove repeat path
paths = []
for p in CPPPATH:
if p not in paths:
paths.append(p)
CPPPATH = []
for path in paths:
if PrefixPath(RTT_ROOT, path):
CPPPATH += [os.path.abspath(path).replace('\\', '/')]
elif PrefixPath(BSP_ROOT, path):
CPPPATH += [os.path.abspath(path).replace('\\', '/')]
else:
CPPPATH += ['"%s",' % path.replace('\\', '/')]
# process CPPDEFINES
if len(CPPDEFINES):
CPPDEFINES = [i for i in set(CPPDEFINES)]
CPPDEFINES.sort()
proj = {}
proj['FILES'] = FILES
proj['DIRS'] = DIRS
proj['HEADERS'] = HEADERS
proj['CPPPATH'] = CPPPATH
proj['CPPDEFINES'] = CPPDEFINES
return proj
def VersionCmp(ver1, ver2):
la=[]
if ver1:
la = re.split("[. ]", ver1)
lb = re.split("[. ]", ver2)
f = 0
if len(la) > len(lb):
f = len(la)
else:
f = len(lb)
for i in range(f):
try:
if int(la[i]) > int(lb[i]):
return 1
elif int(la[i]) == int(lb[i]):
continue
else:
return -1
except (IndexError, ValueError) as e:
if len(la) > len(lb):
return 1
else:
return -1
return 0
def GCCC99Patch(cflags):
import building
gcc_version = building.GetDepend('GCC_VERSION_STR')
if gcc_version:
gcc_version = gcc_version.replace('"', '')
if VersionCmp(gcc_version, "4.8.0") == 1:
# remove -std=c99 after GCC 4.8.x
cflags = cflags.replace('-std=c99', '')
return cflags
def ReloadModule(module):
import sys
if sys.version_info.major >= 3:
import importlib
importlib.reload(module)
else:
reload(module)
def ImportModule(module):
import sys
if sys.version_info.major >= 3:
import importlib.util
path = os.path.dirname(__file__)
spec = importlib.util.spec_from_file_location(module, os.path.join(path, module+".py"))
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
else:
return __import__(module, fromlist=[module])
def VerTuple(version_str):
ver_parts = version_str.split('.')
ver = tuple(int(part) for part in ver_parts)
return ver
def CmdExists(cmd):
# Check if the path directly points to an existing file.
if os.path.isfile(cmd):
return True
else:
# On Windows systems, check for common script file extensions
# if the file does not exist as specified.
if sys.platform.startswith('win'):
# Loop through possible extensions to cover cases where the extension is omitted in the input.
for ext in ['.exe', '.bat', '.ps1']:
# Append the extension to the command path and check if this file exists.
if os.path.isfile(cmd + ext):
return True
# If none of the checks confirm the file exists, return False.
return False
+190
View File
@@ -0,0 +1,190 @@
import os
import sys
import json
def create_need_files(targetId, packpath):
"""
Generate pyocd.yaml files
"""
yaml_file = open('pyocd.yaml', 'w')
if yaml_file:
yaml_file.write('\npack:\n')
yaml_file.write(' - ' + packpath + '\n')
yaml_file.close()
"""
Generate .vscode/launch.json files
"""
vsc_launch_file = open('.vscode/launch.json', 'w')
if vsc_launch_file:
config_obj = {}
config_obj['name'] = 'Cortex Debug'
config_obj['cwd'] = '${workspaceFolder}'
config_obj['executable'] = 'rt-thread.elf'
config_obj['request'] = 'launch'
config_obj['type'] = 'cortex-debug'
config_obj['runToEntryPoint'] = 'Reset_Handler'
config_obj['servertype'] = 'pyocd'
if os.getenv('RTT_EXEC_PATH'):
config_obj['armToolchainPath'] = os.getenv('RTT_EXEC_PATH').replace('\\', '/')
else:
print('env <RTT_EXEC_PATH> not set!')
config_obj['toolchainPrefix'] = 'arm-none-eabi'
config_obj['targetId'] = targetId
json_obj = {}
json_obj['version'] = '0.2.0'
json_obj['configurations'] = [config_obj]
vsc_launch_file.write(json.dumps(json_obj, ensure_ascii=False, indent=4))
vsc_launch_file.close()
"""
Generate .vscode/tasks.json files
"""
vsc_tasks_file = open('.vscode/tasks.json', 'w')
if vsc_tasks_file:
task_build_obj = {}
task_build_obj['type'] = 'shell'
task_build_obj['label'] = 'Build target files'
task_build_obj['command'] = 'scons'
task_build_obj['args'] = ['-j12']
task_build_obj['problemMatcher'] = ['$gcc']
task_build_obj['group'] = 'build'
task_download_obj = {}
task_download_obj['type'] = 'shell'
task_download_obj['label'] = 'Download code to flash memory'
task_download_obj['command'] = 'python'
task_download_obj['args'] = ['-m', 'pyocd', 'flash', '--erase', 'chip', '--target', \
targetId, 'rt-thread.elf']
task_download_obj['problemMatcher'] = ['$gcc']
task_download_obj['group'] = 'build'
task_build_download_obj = task_download_obj.copy()
task_build_download_obj['label'] = 'Build and Download'
task_build_download_obj['dependsOn'] = 'Build target files'
json_obj = {}
json_obj['version'] = '2.0.0'
json_obj['tasks'] = [task_build_obj, task_download_obj, task_build_download_obj]
vsc_tasks_file.write(json.dumps(json_obj, ensure_ascii=False, indent=4))
vsc_tasks_file.close()
def similar_char_num(str1, str2):
lstr1 = len(str1)
lstr2 = len(str2)
record = [[0 for i in range(lstr2+1)] for j in range(lstr1+1)]
similar_num = 0
for i in range(lstr1):
for j in range(lstr2):
if str1[i] == str2[j]:
record[i+1][j+1] = record[i][j] + 1
if record[i+1][j+1] > similar_num:
similar_num = record[i+1][j+1]
return similar_num
def get_socName_from_rtconfig():
socName = None
rtconfig_file = open('rtconfig.h', 'r')
if rtconfig_file:
for line in rtconfig_file.readlines():
if 'SOC' in line and 'FAMILY' not in line and 'SERIES' not in line:
socName = line.strip().split('_')[-1]
rtconfig_file.close()
return socName
def get_pack_from_env():
if os.environ.get('ENV_ROOT') == None:
if sys.platform == 'win32':
home_dir = os.environ['USERPROFILE']
env_dir = os.path.join(home_dir, '.env')
else:
home_dir = os.environ['HOME']
env_dir = os.path.join(home_dir, '.env')
else:
env_dir = os.environ.get('ENV_ROOT')
pack_dir = env_dir.replace('\\', '/') + '/tools/cmsisPacks/'
if not os.path.exists(pack_dir):
print('<%s> does not exist, please create.' % pack_dir)
return
# get soc name from <rtconfig.h> file
socName = get_socName_from_rtconfig()
if socName == None:
return
# Find the pack that best matches soc name
max_similar_num = 0
max_similar_pack = None
for file in os.listdir(pack_dir):
if str(file).endswith('.pack'):
similar_num = similar_char_num(socName, file)
if(similar_num > max_similar_num):
max_similar_num = similar_num
max_similar_pack = file
print('SOC<%s> match the pack is <%s>' % (socName, max_similar_pack))
if max_similar_pack == None:
return
return pack_dir + max_similar_pack
def get_trgetId_from_pack(pack):
# get soc name from <rtconfig.h> file
socName = get_socName_from_rtconfig()
if socName == None:
return
# View the soc supported by the most similar cmsisPack
result = os.popen('python -m pyocd json --target --pack ' + pack)
pyocd_json = json.loads(result.read())
if pyocd_json['status'] != 0:
return
# Find the targetId that best matches soc name
max_similar_num = 0
max_similar_targetId = None
for target in pyocd_json['targets']:
if (target['source'] == 'pack'):
similar_num = similar_char_num(socName.lower(), target['name'])
if(similar_num > max_similar_num):
max_similar_num = similar_num
max_similar_targetId = target['name']
print('SOC<%s> match the targetId is <%s>' % (socName, max_similar_targetId))
if max_similar_targetId == None:
return
if max_similar_num < len(socName):
print('<%s> not match the <%s>' % (socName, pack))
return
return max_similar_targetId
def GenerateVSCodePyocdConfig(pack):
if pack == 'env':
pack = get_pack_from_env()
if pack == None:
return
else:
# Check is exist
if not os.path.exists(pack):
return
# Check is file
if not os.path.isfile(pack):
return
# Check is pack
if not str(pack).endswith('.pack'):
return
pack = pack.replace('\\', '/')
targetId = get_trgetId_from_pack(pack)
if targetId ==None:
return
create_need_files(targetId, pack)
print('Pyocd Config Done!')
return
+65
View File
@@ -0,0 +1,65 @@
#
# File : win32spawn.py
# This file is part of RT-Thread RTOS
# COPYRIGHT (C) 2006 - 2025, RT-Thread Development Team
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Change Logs:
# Date Author Notes
# 2015-01-20 Bernard Add copyright information
#
import os
import subprocess
class Win32Spawn:
def spawn(self, sh, escape, cmd, args, env):
# deal with the cmd build-in commands which cannot be used in
# subprocess.Popen
if cmd == 'del':
for f in args[1:]:
try:
os.remove(f)
except Exception as e:
print('Error removing file: ' + e)
return -1
return 0
newargs = ' '.join(args[1:])
cmdline = cmd + " " + newargs
# Make sure the env is constructed by strings
_e = dict([(k, str(v)) for k, v in env.items()])
# Windows(tm) CreateProcess does not use the env passed to it to find
# the executables. So we have to modify our own PATH to make Popen
# work.
old_path = os.environ['PATH']
os.environ['PATH'] = _e['PATH']
try:
proc = subprocess.Popen(cmdline, env=_e, shell=False)
except Exception as e:
print('Error in calling command:' + cmdline.split(' ')[0])
print('Exception: ' + os.strerror(e.errno))
if (os.strerror(e.errno) == "No such file or directory"):
print ("\nPlease check Toolchains PATH setting.\n")
return e.errno
finally:
os.environ['PATH'] = old_path
return proc.wait()
+95
View File
@@ -0,0 +1,95 @@
#! /usr/bin/env python
#coding=utf-8
#
# File : wizard.py
# This file is part of RT-Thread RTOS
# COPYRIGHT (C) 2006 - 2015, RT-Thread Development Team
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Change Logs:
# Date Author Notes
# 2015-01-20 Bernard Add copyright information
#
"""
wizard.py - a script to generate SConscript in RT-Thread RTOS.
`wizard --component name' to generate SConscript for name component.
`wizard --bridge' to generate SConscript as a bridge to connect each
SConscript script file of sub-directory.
"""
import sys
SConscript_com = '''# RT-Thread building script for component
from building import *
cwd = GetCurrentDir()
src = Glob('*.c') + Glob('*.cpp') + Glob('*.cxx') + Glob('*.cc')
CPPPATH = [cwd]
group = DefineGroup('COMPONENT_NAME', src, depend = [''], CPPPATH = CPPPATH)
Return('group')
'''
SConscript_bridge = '''# RT-Thread building script for bridge
import os
from building import *
cwd = GetCurrentDir()
objs = []
list = os.listdir(cwd)
for d in list:
path = os.path.join(cwd, d)
if os.path.isfile(os.path.join(path, 'SConscript')):
objs = objs + SConscript(os.path.join(d, 'SConscript'))
Return('objs')
'''
def usage():
print('wizard --component name')
print('wizard --bridge')
def gen_component(name):
print('generate SConscript for ' + name)
text = SConscript_com.replace('COMPONENT_NAME', name)
f = open('SConscript', 'w')
f.write(text)
f.close()
def gen_bridge():
print('generate SConscript for bridge')
f = open('SConscript', 'w')
f.write(SConscript_bridge)
f.close()
if __name__ == '__main__':
if len(sys.argv) == 1:
usage()
sys.exit(2)
if sys.argv[1] == '--component':
gen_component(sys.argv[2])
elif sys.argv[1] == '--bridge':
gen_bridge()
else:
usage()