first commit for chrg
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -0,0 +1,2 @@
|
||||
pyyaml
|
||||
pandas
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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:
|
||||
@@ -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))
|
||||
@@ -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**: 检查依赖的核心函数
|
||||
|
||||
## 构建系统架构图
|
||||
|
||||

|
||||
|
||||
## 主要特性
|
||||
|
||||
✅ **多工具链支持**
|
||||
- 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的构建系统,提供了统一的跨平台构建体验。构建系统支持:
|
||||
|
||||
- 多种编译器和IDE(GCC、Keil、IAR、VS Code等)
|
||||
- 模块化的组件管理
|
||||
- 灵活的配置系统
|
||||
- 自动化的依赖处理
|
||||
- 软件包管理功能
|
||||
|
||||
### 系统架构图
|
||||
|
||||

|
||||
|
||||
## 快速开始
|
||||
|
||||
### 基本编译流程
|
||||
|
||||
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. 使用ccache(GCC):
|
||||
```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. [扩展机制](#扩展机制)
|
||||
|
||||
## 系统架构设计
|
||||
|
||||
### 整体架构图
|
||||
|
||||

|
||||
|
||||
### 设计原则
|
||||
|
||||
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)
|
||||
```
|
||||
|
||||
## 构建流程详解
|
||||
|
||||
### 完整构建流程图
|
||||
|
||||

|
||||
|
||||
### 依赖解析流程
|
||||
|
||||
```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. 为项目定制构建流程
|
||||
|
||||
构建系统的核心价值在于将复杂的嵌入式构建过程标准化和自动化,让开发者能够专注于功能开发而不是构建配置。
|
||||
@@ -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)
|
||||
@@ -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.")
|
||||
@@ -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
|
||||
@@ -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更加简洁和易于维护。
|
||||
@@ -0,0 +1,4 @@
|
||||
from package import *
|
||||
|
||||
objs = BuildPackage()
|
||||
Return('objs')
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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__ */
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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()
|
||||
{
|
||||
}
|
||||
@@ -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__
|
||||
@@ -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
|
||||
@@ -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')
|
||||
@@ -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)
|
||||
@@ -0,0 +1,345 @@
|
||||
# RT-Thread Next Generation Build System
|
||||
|
||||
## 概述
|
||||
|
||||
RT-Thread NG(Next 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许可证。
|
||||
@@ -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'
|
||||
]
|
||||
@@ -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)
|
||||
@@ -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('_')]
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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', '')
|
||||
@@ -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())
|
||||
@@ -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
|
||||
"""
|
||||
@@ -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()
|
||||
@@ -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())
|
||||
@@ -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
|
||||
@@ -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.')
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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
|
||||
```
|
||||
@@ -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
|
||||
@@ -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())
|
||||
@@ -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)
|
||||
@@ -0,0 +1,5 @@
|
||||
scons>=4.0.1
|
||||
requests>=2.27.1
|
||||
tqdm>=4.67.1
|
||||
kconfiglib>=13.7.1
|
||||
PyYAML>=6.0
|
||||
@@ -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()
|
||||
@@ -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'
|
||||
]
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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="<Use Defaults>" DbgSetName="<Use Defaults>">
|
||||
<![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>
|
||||
@@ -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
|
||||
@@ -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!')
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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=""${workspace_loc:/${ProjName}/rtconfig_preinc.h}""/>
|
||||
</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
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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')
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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(©_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(©_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
|
||||
@@ -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)
|
||||
@@ -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())
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user