1# -*- coding: utf-8 -*-
2#
3# File      : building.py
4# This file is part of RT-Thread RTOS
5# COPYRIGHT (C) 2006 - 2015, RT-Thread Development Team
6#
7#  This program is free software; you can redistribute it and/or modify
8#  it under the terms of the GNU General Public License as published by
9#  the Free Software Foundation; either version 2 of the License, or
10#  (at your option) any later version.
11#
12#  This program is distributed in the hope that it will be useful,
13#  but WITHOUT ANY WARRANTY; without even the implied warranty of
14#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15#  GNU General Public License for more details.
16#
17#  You should have received a copy of the GNU General Public License along
18#  with this program; if not, write to the Free Software Foundation, Inc.,
19#  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20#
21# Change Logs:
22# Date           Author       Notes
23# 2015-01-20     Bernard      Add copyright information
24# 2015-07-25     Bernard      Add LOCAL_CCFLAGS/LOCAL_CPPPATH/LOCAL_CPPDEFINES for
25#                             group definition.
26# 2024-04-21     Bernard      Add toolchain detection in sdk packages
27# 2025-01-05     Bernard      Add logging as Env['log']
28# 2025-03-02     ZhaoCake     Add MkDist_Strip
29# 2025-01-05     Assistant    Refactor SCons PreProcessor patch to independent class
30
31import os
32import sys
33import string
34import utils
35import operator
36import rtconfig
37import platform
38import logging
39from SCons.Script import *
40from utils import _make_path_relative
41from mkdist import do_copy_file
42from options import AddOptions
43from preprocessor import create_preprocessor_instance
44from win32spawn import Win32Spawn
45
46BuildOptions = {}
47Projects = []
48Rtt_Root = ''
49Env = None
50
51def PrepareBuilding(env, root_directory, has_libcpu=False, remove_components = []):
52
53    global BuildOptions
54    global Projects
55    global Env
56    global Rtt_Root
57
58    AddOptions()
59
60    Env = env
61    # export the default environment
62    Export('env')
63
64    # prepare logging and set log
65    logging.basicConfig(level=logging.INFO, format="%(message)s")
66    logger = logging.getLogger('rt-scons')
67    if GetOption('verbose'):
68        logger.setLevel(logging.DEBUG)
69    Env['log'] = logger
70
71    Rtt_Root = os.path.abspath(root_directory)
72
73    # make an absolute root directory
74    RTT_ROOT = Rtt_Root
75    Export('RTT_ROOT')
76
77    # set RTT_ROOT in ENV
78    Env['RTT_ROOT'] = Rtt_Root
79    os.environ["RTT_DIR"] = Rtt_Root
80    # set BSP_ROOT in ENV
81    Env['BSP_ROOT'] = Dir('#').abspath
82    os.environ["BSP_DIR"] = Dir('#').abspath
83
84    sys.path += os.path.join(Rtt_Root, 'tools')
85
86    # {target_name:(CROSS_TOOL, PLATFORM)}
87    tgt_dict = {'mdk':('keil', 'armcc'),
88                'mdk4':('keil', 'armcc'),
89                'mdk5':('keil', 'armcc'),
90                'iar':('iar', 'iccarm'),
91                'vs':('msvc', 'cl'),
92                'vs2012':('msvc', 'cl'),
93                'vsc' : ('gcc', 'gcc'),
94                'vsc_workspace':('gcc', 'gcc'),
95                'cb':('keil', 'armcc'),
96                'ua':('gcc', 'gcc'),
97                'cdk':('gcc', 'gcc'),
98                'makefile':('gcc', 'gcc'),
99                'eclipse':('gcc', 'gcc'),
100                'ses' : ('gcc', 'gcc'),
101                'cmake':('gcc', 'gcc'),
102                'cmake-armclang':('keil', 'armclang'),
103                'xmake':('gcc', 'gcc'),
104                'codelite' : ('gcc', 'gcc'),
105                'esp-idf': ('gcc', 'gcc'),
106                'zig':('gcc', 'gcc')}
107    tgt_name = GetOption('target')
108
109    if tgt_name:
110        # --target will change the toolchain settings which clang-analyzer is
111        # depend on
112        if GetOption('clang-analyzer'):
113            print ('--clang-analyzer cannot be used with --target')
114            sys.exit(1)
115
116        SetOption('no_exec', 1)
117        try:
118            rtconfig.CROSS_TOOL, rtconfig.PLATFORM = tgt_dict[tgt_name]
119            # replace the 'RTT_CC' to 'CROSS_TOOL'
120            os.environ['RTT_CC'] = rtconfig.CROSS_TOOL
121        except KeyError:
122            print('Unknow target: '+ tgt_name+'. Avaible targets: ' +', '.join(tgt_dict.keys()))
123            sys.exit(1)
124
125    exec_prefix = GetOption('exec-prefix')
126    if exec_prefix:
127        os.environ['RTT_CC_PREFIX'] = exec_prefix
128
129    # auto change the 'RTT_EXEC_PATH' when 'rtconfig.EXEC_PATH' get failed
130    if not utils.CmdExists(os.path.join(rtconfig.EXEC_PATH, rtconfig.CC)):
131        Env['log'].debug('To detect CC because CC path in rtconfig.py is invalid:')
132        Env['log'].debug('  rtconfig.py cc ->' + os.path.join(rtconfig.EXEC_PATH, rtconfig.CC))
133        if 'RTT_EXEC_PATH' in os.environ:
134            # del the 'RTT_EXEC_PATH' and using the 'EXEC_PATH' setting on rtconfig.py
135            del os.environ['RTT_EXEC_PATH']
136
137        try:
138            # try to detect toolchains in env
139            envm = utils.ImportModule('env_utility')
140            # from env import GetSDKPath
141            exec_path = envm.GetSDKPath(rtconfig.CC)
142            if exec_path != None:
143                if 'gcc' in rtconfig.CC:
144                    exec_path = os.path.join(exec_path, 'bin')
145
146                if os.path.exists(exec_path):
147                    Env['log'].debug('set CC to ' + exec_path)
148                    rtconfig.EXEC_PATH = exec_path
149                    os.environ['RTT_EXEC_PATH'] = exec_path
150                else:
151                    Env['log'].debug('No Toolchain found in path(%s).' % exec_path)
152        except Exception as e:
153            # detect failed, ignore
154            Env['log'].debug(e)
155            pass
156
157    exec_path = GetOption('exec-path')
158    if exec_path:
159        os.environ['RTT_EXEC_PATH'] = exec_path
160
161    utils.ReloadModule(rtconfig) # update environment variables to rtconfig.py
162
163    # some env variables have loaded in Environment() of SConstruct before re-load rtconfig.py;
164    # after update rtconfig.py's variables, those env variables need to synchronize
165    if exec_prefix:
166        env['CC'] = rtconfig.CC
167        env['CXX'] = rtconfig.CXX
168        env['AS'] = rtconfig.AS
169        env['AR'] = rtconfig.AR
170        env['LINK'] = rtconfig.LINK
171    if exec_path:
172        env.PrependENVPath('PATH', rtconfig.EXEC_PATH)
173    env['ASCOM']= env['ASPPCOM']
174
175    if GetOption('strict-compiling'):
176        STRICT_FLAGS = ''
177        if rtconfig.PLATFORM in ['gcc']:
178            STRICT_FLAGS += ' -Werror' #-Wextra
179            env.Append(CFLAGS=STRICT_FLAGS, CXXFLAGS=STRICT_FLAGS)
180
181    # add compability with Keil MDK 4.6 which changes the directory of armcc.exe
182    if rtconfig.PLATFORM in ['armcc', 'armclang']:
183        if rtconfig.PLATFORM == 'armcc' and not os.path.isfile(os.path.join(rtconfig.EXEC_PATH, 'armcc.exe')):
184            if rtconfig.EXEC_PATH.find('bin40') > 0:
185                rtconfig.EXEC_PATH = rtconfig.EXEC_PATH.replace('bin40', 'armcc/bin')
186                Env['LINKFLAGS'] = Env['LINKFLAGS'].replace('RV31', 'armcc')
187
188        # reset AR command flags
189        env['ARCOM'] = '$AR --create $TARGET $SOURCES'
190        env['LIBPREFIX'] = ''
191        env['LIBSUFFIX'] = '.lib'
192        env['LIBLINKPREFIX'] = ''
193        env['LIBLINKSUFFIX'] = '.lib'
194        env['LIBDIRPREFIX'] = '--userlibpath '
195
196    elif rtconfig.PLATFORM == 'iccarm':
197        env['LIBPREFIX'] = ''
198        env['LIBSUFFIX'] = '.a'
199        env['LIBLINKPREFIX'] = ''
200        env['LIBLINKSUFFIX'] = '.a'
201        env['LIBDIRPREFIX'] = '--search '
202
203    # patch for win32 spawn
204    if env['PLATFORM'] == 'win32':
205        win32_spawn = Win32Spawn()
206        win32_spawn.env = env
207        env['SPAWN'] = win32_spawn.spawn
208
209    if env['PLATFORM'] == 'win32':
210        os.environ['PATH'] = rtconfig.EXEC_PATH + ";" + os.environ['PATH']
211    else:
212        os.environ['PATH'] = rtconfig.EXEC_PATH + ":" + os.environ['PATH']
213
214    # add program path
215    env.PrependENVPath('PATH', os.environ['PATH'])
216    # add rtconfig.h/BSP path into Kernel group
217    DefineGroup("Kernel", [], [], CPPPATH=[str(Dir('#').abspath)])
218
219    # add library build action
220    act = SCons.Action.Action(BuildLibInstallAction, 'Install compiled library... $TARGET')
221    bld = Builder(action = act)
222    Env.Append(BUILDERS = {'BuildLib': bld})
223
224    # parse rtconfig.h to get used component
225    PreProcessor = create_preprocessor_instance()
226    f = open('rtconfig.h', 'r')
227    contents = f.read()
228    f.close()
229    PreProcessor.process_contents(contents)
230    BuildOptions = PreProcessor.cpp_namespace
231
232    if GetOption('clang-analyzer'):
233        # perform what scan-build does
234        env.Replace(
235                CC   = 'ccc-analyzer',
236                CXX  = 'c++-analyzer',
237                # skip as and link
238                LINK = 'true',
239                AS   = 'true',)
240        env["ENV"].update(x for x in os.environ.items() if x[0].startswith("CCC_"))
241        # only check, don't compile. ccc-analyzer use CCC_CC as the CC.
242        # fsyntax-only will give us some additional warning messages
243        env['ENV']['CCC_CC']  = 'clang'
244        env.Append(CFLAGS=['-fsyntax-only', '-Wall', '-Wno-invalid-source-encoding'])
245        env['ENV']['CCC_CXX'] = 'clang++'
246        env.Append(CXXFLAGS=['-fsyntax-only', '-Wall', '-Wno-invalid-source-encoding'])
247        # remove the POST_ACTION as it will cause meaningless errors(file not
248        # found or something like that).
249        rtconfig.POST_ACTION = ''
250
251    # auto append '_REENT_SMALL' when using newlib 'nano.specs' option
252    if rtconfig.PLATFORM in ['gcc'] and str(env['LINKFLAGS']).find('nano.specs') != -1:
253        env.AppendUnique(CPPDEFINES = ['_REENT_SMALL'])
254
255    attach_global_macros = GetOption('global-macros')
256    if attach_global_macros:
257        attach_global_macros = attach_global_macros.split(',')
258        if isinstance(attach_global_macros, list):
259            for config in attach_global_macros:
260                if isinstance(config, str):
261                    AddDepend(attach_global_macros)
262                    env.Append(CFLAGS=' -D' + config, CXXFLAGS=' -D' + config, AFLAGS=' -D' + config)
263                else:
264                    print('--global-macros arguments are illegal!')
265        else:
266            print('--global-macros arguments are illegal!')
267
268    if GetOption('attach'):
269        from attachconfig import GenAttachConfigProject
270        GenAttachConfigProject()
271        exit(0)
272
273    if GetOption('genconfig'):
274        from env_utility import genconfig
275        genconfig()
276        exit(0)
277
278    if GetOption('stackanalysis'):
279        from WCS import ThreadStackStaticAnalysis
280        ThreadStackStaticAnalysis(Env)
281        exit(0)
282
283    if GetOption('menuconfig'):
284        from env_utility import menuconfig
285        menuconfig(Rtt_Root)
286        exit(0)
287
288    if GetOption('defconfig'):
289        from env_utility import defconfig
290        defconfig(Rtt_Root)
291        exit(0)
292
293    elif GetOption('guiconfig'):
294        from env_utility import guiconfig
295        guiconfig(Rtt_Root)
296        exit(0)
297
298    configfn = GetOption('useconfig')
299    if configfn:
300        from env_utility import mk_rtconfig
301        mk_rtconfig(configfn)
302        exit(0)
303
304
305    if not GetOption('verbose'):
306        # override the default verbose command string
307        env.Replace(
308            ARCOMSTR = 'AR $TARGET',
309            ASCOMSTR = 'AS $TARGET',
310            ASPPCOMSTR = 'AS $TARGET',
311            CCCOMSTR = 'CC $TARGET',
312            CXXCOMSTR = 'CXX $TARGET',
313            LINKCOMSTR = 'LINK $TARGET'
314        )
315
316    # fix the linker for C++
317    if GetDepend('RT_USING_CPLUSPLUS'):
318        if env['LINK'].find('gcc') != -1:
319            env['LINK'] = env['LINK'].replace('gcc', 'g++')
320
321    # we need to seperate the variant_dir for BSPs and the kernels. BSPs could
322    # have their own components etc. If they point to the same folder, SCons
323    # would find the wrong source code to compile.
324    bsp_vdir = 'build'
325    kernel_vdir = 'build/kernel'
326    # board build script
327    objs = SConscript('SConscript', variant_dir=bsp_vdir, duplicate=0)
328    # include kernel
329    objs.extend(SConscript(Rtt_Root + '/src/SConscript', variant_dir=kernel_vdir + '/src', duplicate=0))
330    # include libcpu
331    if not has_libcpu:
332        objs.extend(SConscript(Rtt_Root + '/libcpu/SConscript',
333                    variant_dir=kernel_vdir + '/libcpu', duplicate=0))
334
335    # include components
336    objs.extend(SConscript(Rtt_Root + '/components/SConscript',
337                           variant_dir=kernel_vdir + '/components',
338                           duplicate=0,
339                           exports='remove_components'))
340    # include testcases
341    if os.path.isfile(os.path.join(Rtt_Root, 'examples/utest/testcases/SConscript')):
342        objs.extend(SConscript(Rtt_Root + '/examples/utest/testcases/SConscript',
343                           variant_dir=kernel_vdir + '/examples/utest/testcases',
344                           duplicate=0))
345
346    return objs
347
348def PrepareModuleBuilding(env, root_directory, bsp_directory):
349
350    global BuildOptions
351    global Env
352    global Rtt_Root
353
354    # patch for win32 spawn
355    if env['PLATFORM'] == 'win32':
356        win32_spawn = Win32Spawn()
357        win32_spawn.env = env
358        env['SPAWN'] = win32_spawn.spawn
359
360    Env = env
361    Rtt_Root = root_directory
362
363    # parse bsp rtconfig.h to get used component
364    PreProcessor = create_preprocessor_instance()
365    f = open(bsp_directory + '/rtconfig.h', 'r')
366    contents = f.read()
367    f.close()
368    PreProcessor.process_contents(contents)
369    BuildOptions = PreProcessor.cpp_namespace
370
371    AddOption('--buildlib',
372                      dest = 'buildlib',
373                      type = 'string',
374                      help = 'building library of a component')
375    AddOption('--cleanlib',
376                      dest = 'cleanlib',
377                      action = 'store_true',
378                      default = False,
379                      help = 'clean up the library by --buildlib')
380
381    # add program path
382    env.PrependENVPath('PATH', rtconfig.EXEC_PATH)
383
384def GetConfigValue(name):
385    assert type(name) == str, 'GetConfigValue: only string parameter is valid'
386    try:
387        return BuildOptions[name]
388    except:
389        return ''
390
391def GetDepend(depend):
392    building = True
393    if type(depend) == type('str'):
394        if not depend in BuildOptions or BuildOptions[depend] == 0:
395            building = False
396        elif BuildOptions[depend] != '':
397            return BuildOptions[depend]
398
399        return building
400
401    # for list type depend
402    for item in depend:
403        if item != '':
404            if not item in BuildOptions or BuildOptions[item] == 0:
405                building = False
406
407    return building
408
409def LocalOptions(config_filename):
410    from SCons.Script import SCons
411
412    # parse wiced_config.h to get used component
413    PreProcessor = SCons.cpp.PreProcessor()
414
415    f = open(config_filename, 'r')
416    contents = f.read()
417    f.close()
418
419    PreProcessor.process_contents(contents)
420    local_options = PreProcessor.cpp_namespace
421
422    return local_options
423
424def GetLocalDepend(options, depend):
425    building = True
426    if type(depend) == type('str'):
427        if not depend in options or options[depend] == 0:
428            building = False
429        elif options[depend] != '':
430            return options[depend]
431
432        return building
433
434    # for list type depend
435    for item in depend:
436        if item != '':
437            if not depend in options or item == 0:
438                building = False
439
440    return building
441
442def AddDepend(option):
443    if isinstance(option, str):
444        BuildOptions[option] = 1
445    elif isinstance(option, list):
446        for obj in option:
447            if isinstance(obj, str):
448                BuildOptions[obj] = 1
449            else:
450                print('AddDepend arguements are illegal!')
451    else:
452        print('AddDepend arguements are illegal!')
453
454def Preprocessing(input, suffix, output = None, CPPPATH = None):
455    if hasattr(rtconfig, "CPP") and hasattr(rtconfig, "CPPFLAGS"):
456        if output == None:
457            import re
458            output = re.sub(r'[\.]+.*', suffix, input)
459        inc = ' '
460        cpppath = CPPPATH
461        for cpppath_item in cpppath:
462            inc += ' -I' + cpppath_item
463        CPP = rtconfig.EXEC_PATH + '/' + rtconfig.CPP
464        if not os.path.exists(CPP):
465            CPP = rtconfig.CPP
466        CPP += rtconfig.CPPFLAGS
467        path = GetCurrentDir() + '/'
468        os.system(CPP + inc + ' ' + path + input + ' -o ' + path + output)
469    else:
470        print('CPP tool or CPPFLAGS is undefined in rtconfig!')
471
472def MergeGroup(src_group, group):
473    src_group['src'] = src_group['src'] + group['src']
474    src_group['src'].sort()
475    if 'CFLAGS' in group:
476        if 'CFLAGS' in src_group:
477            src_group['CFLAGS'] = src_group['CFLAGS'] + group['CFLAGS']
478        else:
479            src_group['CFLAGS'] = group['CFLAGS']
480    if 'CCFLAGS' in group:
481        if 'CCFLAGS' in src_group:
482            src_group['CCFLAGS'] = src_group['CCFLAGS'] + group['CCFLAGS']
483        else:
484            src_group['CCFLAGS'] = group['CCFLAGS']
485    if 'CXXFLAGS' in group:
486        if 'CXXFLAGS' in src_group:
487            src_group['CXXFLAGS'] = src_group['CXXFLAGS'] + group['CXXFLAGS']
488        else:
489            src_group['CXXFLAGS'] = group['CXXFLAGS']
490    if 'CPPPATH' in group:
491        if 'CPPPATH' in src_group:
492            src_group['CPPPATH'] = src_group['CPPPATH'] + group['CPPPATH']
493        else:
494            src_group['CPPPATH'] = group['CPPPATH']
495    if 'CPPDEFINES' in group:
496        if 'CPPDEFINES' in src_group:
497            src_group['CPPDEFINES'] = src_group['CPPDEFINES'] + group['CPPDEFINES']
498        else:
499            src_group['CPPDEFINES'] = group['CPPDEFINES']
500    if 'ASFLAGS' in group:
501        if 'ASFLAGS' in src_group:
502            src_group['ASFLAGS'] = src_group['ASFLAGS'] + group['ASFLAGS']
503        else:
504            src_group['ASFLAGS'] = group['ASFLAGS']
505
506    # for local CCFLAGS/CPPPATH/CPPDEFINES
507    if 'LOCAL_CFLAGS' in group:
508        if 'LOCAL_CFLAGS' in src_group:
509            src_group['LOCAL_CFLAGS'] = src_group['LOCAL_CFLAGS'] + group['LOCAL_CFLAGS']
510        else:
511            src_group['LOCAL_CFLAGS'] = group['LOCAL_CFLAGS']
512    if 'LOCAL_CCFLAGS' in group:
513        if 'LOCAL_CCFLAGS' in src_group:
514            src_group['LOCAL_CCFLAGS'] = src_group['LOCAL_CCFLAGS'] + group['LOCAL_CCFLAGS']
515        else:
516            src_group['LOCAL_CCFLAGS'] = group['LOCAL_CCFLAGS']
517    if 'LOCAL_CXXFLAGS' in group:
518        if 'LOCAL_CXXFLAGS' in src_group:
519            src_group['LOCAL_CXXFLAGS'] = src_group['LOCAL_CXXFLAGS'] + group['LOCAL_CXXFLAGS']
520        else:
521            src_group['LOCAL_CXXFLAGS'] = group['LOCAL_CXXFLAGS']
522    if 'LOCAL_CPPPATH' in group:
523        if 'LOCAL_CPPPATH' in src_group:
524            src_group['LOCAL_CPPPATH'] = src_group['LOCAL_CPPPATH'] + group['LOCAL_CPPPATH']
525        else:
526            src_group['LOCAL_CPPPATH'] = group['LOCAL_CPPPATH']
527    if 'LOCAL_CPPDEFINES' in group:
528        if 'LOCAL_CPPDEFINES' in src_group:
529            src_group['LOCAL_CPPDEFINES'] = src_group['LOCAL_CPPDEFINES'] + group['LOCAL_CPPDEFINES']
530        else:
531            src_group['LOCAL_CPPDEFINES'] = group['LOCAL_CPPDEFINES']
532
533    if 'LINKFLAGS' in group:
534        if 'LINKFLAGS' in src_group:
535            src_group['LINKFLAGS'] = src_group['LINKFLAGS'] + group['LINKFLAGS']
536        else:
537            src_group['LINKFLAGS'] = group['LINKFLAGS']
538    if 'LIBS' in group:
539        if 'LIBS' in src_group:
540            src_group['LIBS'] = src_group['LIBS'] + group['LIBS']
541        else:
542            src_group['LIBS'] = group['LIBS']
543    if 'LIBPATH' in group:
544        if 'LIBPATH' in src_group:
545            src_group['LIBPATH'] = src_group['LIBPATH'] + group['LIBPATH']
546        else:
547            src_group['LIBPATH'] = group['LIBPATH']
548    if 'LOCAL_ASFLAGS' in group:
549        if 'LOCAL_ASFLAGS' in src_group:
550            src_group['LOCAL_ASFLAGS'] = src_group['LOCAL_ASFLAGS'] + group['LOCAL_ASFLAGS']
551        else:
552            src_group['LOCAL_ASFLAGS'] = group['LOCAL_ASFLAGS']
553
554def _PretreatListParameters(target_list):
555    while '' in target_list: # remove null strings
556        target_list.remove('')
557    while ' ' in target_list: # remove ' '
558        target_list.remove(' ')
559
560    if(len(target_list) == 0):
561        return False # ignore this list, don't add this list to the parameter
562
563    return True # permit to add this list to the parameter
564
565def DefineGroup(name, src, depend, **parameters):
566    global Env
567    if not GetDepend(depend):
568        return []
569
570    # find exist group and get path of group
571    group_path = ''
572    for g in Projects:
573        if g['name'] == name:
574            group_path = g['path']
575    if group_path == '':
576        group_path = GetCurrentDir()
577
578    group = parameters
579    group['name'] = name
580    group['path'] = group_path
581    if type(src) == type([]):
582        # remove duplicate elements from list
583        src = list(set(src))
584        group['src'] = File(src)
585    else:
586        group['src'] = src
587
588    if 'CFLAGS' in group:
589        target = group['CFLAGS']
590        if len(target) > 0:
591            Env.AppendUnique(CFLAGS = target)
592    if 'CCFLAGS' in group:
593        target = group['CCFLAGS']
594        if len(target) > 0:
595            Env.AppendUnique(CCFLAGS = target)
596    if 'CXXFLAGS' in group:
597        target = group['CXXFLAGS']
598        if len(target) > 0:
599            Env.AppendUnique(CXXFLAGS = target)
600    if 'CPPPATH' in group:
601        target = group['CPPPATH']
602        if _PretreatListParameters(target) == True:
603            paths = []
604            for item in target:
605                paths.append(os.path.abspath(item))
606            target = paths
607            Env.AppendUnique(CPPPATH = target)
608    if 'CPPDEFINES' in group:
609        target = group['CPPDEFINES']
610        if _PretreatListParameters(target) == True:
611            Env.AppendUnique(CPPDEFINES = target)
612    if 'LINKFLAGS' in group:
613        target = group['LINKFLAGS']
614        if len(target) > 0:
615            Env.AppendUnique(LINKFLAGS = target)
616    if 'ASFLAGS' in group:
617        target = group['ASFLAGS']
618        if len(target) > 0:
619            Env.AppendUnique(ASFLAGS = target)
620    if 'LOCAL_CPPPATH' in group:
621        paths = []
622        for item in group['LOCAL_CPPPATH']:
623            paths.append(os.path.abspath(item))
624        group['LOCAL_CPPPATH'] = paths
625
626
627    if rtconfig.PLATFORM in ['gcc']:
628        if 'CFLAGS' in group:
629            group['CFLAGS'] = utils.GCCC99Patch(group['CFLAGS'])
630        if 'CCFLAGS' in group:
631            group['CCFLAGS'] = utils.GCCC99Patch(group['CCFLAGS'])
632        if 'CXXFLAGS' in group:
633            group['CXXFLAGS'] = utils.GCCC99Patch(group['CXXFLAGS'])
634        if 'LOCAL_CCFLAGS' in group:
635            group['LOCAL_CCFLAGS'] = utils.GCCC99Patch(group['LOCAL_CCFLAGS'])
636        if 'LOCAL_CXXFLAGS' in group:
637            group['LOCAL_CXXFLAGS'] = utils.GCCC99Patch(group['LOCAL_CXXFLAGS'])
638        if 'LOCAL_CFLAGS' in group:
639            group['LOCAL_CFLAGS'] = utils.GCCC99Patch(group['LOCAL_CFLAGS'])
640    # check whether to clean up library
641    if GetOption('cleanlib') and os.path.exists(os.path.join(group['path'], GroupLibFullName(name, Env))):
642        if group['src'] != []:
643            print('Remove library:'+ GroupLibFullName(name, Env))
644            fn = os.path.join(group['path'], GroupLibFullName(name, Env))
645            if os.path.exists(fn):
646                os.unlink(fn)
647
648    if 'LIBS' in group:
649        target = group['LIBS']
650        if _PretreatListParameters(target) == True:
651            Env.AppendUnique(LIBS = target)
652    if 'LIBPATH' in group:
653        target = group['LIBPATH']
654        if _PretreatListParameters(target) == True:
655            Env.AppendUnique(LIBPATH = target)
656
657    # check whether to build group library
658    if 'LIBRARY' in group:
659        objs = Env.Library(name, group['src'])
660    else:
661        # only add source
662        objs = group['src']
663
664    # merge group
665    for g in Projects:
666        if g['name'] == name:
667            # merge to this group
668            MergeGroup(g, group)
669            return objs
670
671    def PriorityInsertGroup(groups, group):
672        length = len(groups)
673        for i in range(0, length):
674            if operator.gt(groups[i]['name'].lower(), group['name'].lower()):
675                groups.insert(i, group)
676                return
677        groups.append(group)
678
679    # add a new group
680    PriorityInsertGroup(Projects, group)
681
682    return objs
683
684def GetCurrentDir():
685    conscript = File('SConscript')
686    fn = conscript.rfile()
687    name = fn.name
688    path = os.path.dirname(fn.abspath)
689    return path
690
691PREBUILDING = []
692def RegisterPreBuildingAction(act):
693    global PREBUILDING
694    assert callable(act), 'Could only register callable objects. %s received' % repr(act)
695    PREBUILDING.append(act)
696
697def PreBuilding():
698    global PREBUILDING
699    for a in PREBUILDING:
700        a()
701
702def GroupLibName(name, env):
703
704    if rtconfig.PLATFORM in ['armcc']:
705        return name + '_rvds'
706    elif rtconfig.PLATFORM in ['gcc']:
707        return name + '_gcc'
708
709    return name
710
711def GroupLibFullName(name, env):
712    return env['LIBPREFIX'] + GroupLibName(name, env) + env['LIBSUFFIX']
713
714def BuildLibInstallAction(target, source, env):
715    lib_name = GetOption('buildlib')
716    for Group in Projects:
717        if Group['name'] == lib_name:
718            lib_name = GroupLibFullName(Group['name'], env)
719            dst_name = os.path.join(Group['path'], lib_name)
720            print('Copy '+lib_name+' => ' + dst_name)
721            do_copy_file(lib_name, dst_name)
722            break
723
724def DoBuilding(target, objects):
725
726    # merge all objects into one list
727    def one_list(l):
728        lst = []
729        for item in l:
730            if type(item) == type([]):
731                lst += one_list(item)
732            else:
733                lst.append(item)
734        return lst
735
736    # handle local group
737    def local_group(group, objects):
738        if 'LOCAL_CFLAGS' in group or 'LOCAL_CXXFLAGS' in group or 'LOCAL_CCFLAGS' in group or 'LOCAL_CPPPATH' in group or 'LOCAL_CPPDEFINES' in group or 'LOCAL_ASFLAGS' in group:
739            CFLAGS = Env.get('CFLAGS', '') + group.get('LOCAL_CFLAGS', '')
740            CCFLAGS = Env.get('CCFLAGS', '') + group.get('LOCAL_CCFLAGS', '')
741            CXXFLAGS = Env.get('CXXFLAGS', '') + group.get('LOCAL_CXXFLAGS', '')
742            CPPPATH = list(Env.get('CPPPATH', [''])) + group.get('LOCAL_CPPPATH', [''])
743            CPPDEFINES = list(Env.get('CPPDEFINES', [''])) + group.get('LOCAL_CPPDEFINES', [''])
744            ASFLAGS = Env.get('ASFLAGS', '') + group.get('LOCAL_ASFLAGS', '')
745
746            for source in group['src']:
747                objects.append(Env.Object(source, CFLAGS = CFLAGS, CCFLAGS = CCFLAGS, CXXFLAGS = CXXFLAGS, ASFLAGS = ASFLAGS,
748                    CPPPATH = CPPPATH, CPPDEFINES = CPPDEFINES))
749
750            return True
751
752        return False
753
754    PreBuilding()
755    objects = one_list(objects)
756
757    program = None
758    # check whether special buildlib option
759    lib_name = GetOption('buildlib')
760    if lib_name:
761        objects = [] # remove all of objects
762        # build library with special component
763        for Group in Projects:
764            if Group['name'] == lib_name:
765                lib_name = GroupLibName(Group['name'], Env)
766                if not local_group(Group, objects):
767                    objects = Env.Object(Group['src'])
768
769                program = Env.Library(lib_name, objects)
770
771                # add library copy action
772                Env.BuildLib(lib_name, program)
773
774                break
775    else:
776        # generate build/compile_commands.json
777        if GetOption('cdb') and utils.VerTuple(SCons.__version__) >= (4, 0, 0):
778            Env.Tool("compilation_db")
779            Env.CompilationDatabase('build/compile_commands.json')
780
781        # remove source files with local flags setting
782        for group in Projects:
783            if 'LOCAL_CFLAGS' in group or 'LOCAL_CXXFLAGS' in group or 'LOCAL_CCFLAGS' in group or 'LOCAL_CPPPATH' in group or 'LOCAL_CPPDEFINES' in group:
784                for source in group['src']:
785                    for obj in objects:
786                        if source.abspath == obj.abspath or (len(obj.sources) > 0 and source.abspath == obj.sources[0].abspath):
787                            objects.remove(obj)
788
789        # re-add the source files to the objects
790
791        objects_in_group = []
792        for group in Projects:
793            local_group(group, objects_in_group)
794
795        # sort seperately, because the data type of
796        # the members of the two lists are different
797        objects_in_group = sorted(objects_in_group)
798        objects = sorted(objects)
799        objects.append(objects_in_group)
800
801        program = Env.Program(target, objects)
802
803    EndBuilding(target, program)
804
805def GenTargetProject(program = None):
806
807    if GetOption('target') in ['mdk', 'mdk4', 'mdk5']:
808        from targets.keil import MDK2Project, MDK4Project, MDK5Project, ARMCC_Version
809
810        if os.path.isfile('template.uvprojx') and GetOption('target') not in ['mdk4']: # Keil5
811            MDK5Project(Env, GetOption('project-name') + '.uvprojx', Projects)
812            print("Keil5 project is generating...")
813        elif os.path.isfile('template.uvproj') and GetOption('target') not in ['mdk5']: # Keil4
814            MDK4Project(Env, GetOption('project-name') + '.uvproj', Projects)
815            print("Keil4 project is generating...")
816        elif os.path.isfile('template.Uv2') and GetOption('target') not in ['mdk4', 'mdk5']: # Keil2
817            MDK2Project(Env, GetOption('project-name') + '.Uv2', Projects)
818            print("Keil2 project is generating...")
819        else:
820            print ('No template project file found.')
821            exit(1)
822        print("Keil Version: " + ARMCC_Version())
823        print("Keil-MDK project has generated successfully!")
824
825    if GetOption('target') == 'iar':
826        from targets.iar import IARProject, IARVersion
827        print("IAR Version: " + IARVersion())
828        IARProject(Env, GetOption('project-name') + '.ewp', Projects)
829        print("IAR project has generated successfully!")
830
831    if GetOption('target') == 'vs':
832        from targets.vs import VSProject
833        VSProject(GetOption('project-name') + '.vcproj', Projects, program)
834
835    if GetOption('target') == 'vs2012':
836        from targets.vs2012 import VS2012Project
837        VS2012Project(GetOption('project-name') + '.vcxproj', Projects, program)
838
839    if GetOption('target') == 'cb':
840        from targets.codeblocks import CBProject
841        CBProject(GetOption('project-name') + '.cbp', Projects, program)
842
843    if GetOption('target') == 'ua':
844        from targets.ua import PrepareUA
845        PrepareUA(Projects, Rtt_Root, str(Dir('#')))
846
847    if GetOption('target') == 'vsc':
848        from targets.vsc import GenerateVSCode
849        GenerateVSCode(Env)
850        if GetOption('cmsispack'):
851            from vscpyocd import GenerateVSCodePyocdConfig
852            GenerateVSCodePyocdConfig(GetOption('cmsispack'))
853
854    if GetOption('target') == 'vsc_workspace':
855        from targets.vsc import GenerateVSCodeWorkspace
856        GenerateVSCodeWorkspace(Env)
857
858    if GetOption('target') == 'cdk':
859        from targets.cdk import CDKProject
860        CDKProject(GetOption('project-name') + '.cdkproj', Projects)
861
862    if GetOption('target') == 'ses':
863        from targets.ses import SESProject
864        SESProject(Env)
865
866    if GetOption('target') == 'makefile':
867        from targets.makefile import TargetMakefile
868        TargetMakefile(Env)
869
870    if GetOption('target') == 'eclipse':
871        from targets.eclipse import TargetEclipse
872        TargetEclipse(Env, GetOption('reset-project-config'), GetOption('project-name'))
873
874    if GetOption('target') == 'codelite':
875        from targets.codelite import TargetCodelite
876        TargetCodelite(Projects, program)
877
878    if GetOption('target') == 'cmake' or GetOption('target') == 'cmake-armclang':
879        from targets.cmake import CMakeProject
880        CMakeProject(Env, Projects, GetOption('project-name'))
881
882    if GetOption('target') == 'xmake':
883        from targets.xmake import XMakeProject
884        XMakeProject(Env, Projects)
885
886    if GetOption('target') == 'esp-idf':
887        from targets.esp_idf import ESPIDFProject
888        ESPIDFProject(Env, Projects)
889
890    if GetOption('target') == 'zig':
891        from targets.zigbuild import ZigBuildProject
892        ZigBuildProject(Env, Projects)
893
894def EndBuilding(target, program = None):
895    from mkdist import MkDist, MkDist_Strip
896
897    need_exit = False
898
899    Env['target']  = program
900    Env['project'] = Projects
901
902    if hasattr(rtconfig, 'BSP_LIBRARY_TYPE'):
903        Env['bsp_lib_type'] = rtconfig.BSP_LIBRARY_TYPE
904
905    if hasattr(rtconfig, 'dist_handle'):
906        Env['dist_handle'] = rtconfig.dist_handle
907
908    Env.AddPostAction(target, rtconfig.POST_ACTION)
909    # Add addition clean files
910    Clean(target, 'cconfig.h')
911    Clean(target, 'rtua.py')
912    Clean(target, 'rtua.pyc')
913    Clean(target, '.sconsign.dblite')
914
915    if GetOption('target'):
916        GenTargetProject(program)
917        need_exit = True
918
919    BSP_ROOT = Dir('#').abspath
920
921    project_name = GetOption('project-name')
922    project_path = GetOption('project-path')
923
924    # 合并处理打包相关选项
925    if program != None:
926        if GetOption('make-dist'):
927            MkDist(program, BSP_ROOT, Rtt_Root, Env, project_name, project_path)
928            need_exit = True
929        elif GetOption('dist_strip'):
930            MkDist_Strip(program, BSP_ROOT, Rtt_Root, Env, project_name, project_path)
931            need_exit = True
932        elif GetOption('make-dist-ide'):
933            import subprocess
934            if not isinstance(project_path, str) or len(project_path) == 0:
935                project_path = os.path.join(BSP_ROOT, 'rt-studio-project')
936            MkDist(program, BSP_ROOT, Rtt_Root, Env, project_name, project_path)
937            child = subprocess.Popen('scons --target=eclipse --project-name="{}"'.format(project_name),
938                                   cwd=project_path, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
939            stdout, stderr = child.communicate()
940            need_exit = True
941
942    if GetOption('cscope'):
943        from cscope import CscopeDatabase
944        CscopeDatabase(Projects)
945
946    if not GetOption('help') and not GetOption('target'):
947        if not os.path.exists(rtconfig.EXEC_PATH):
948            print ("Error: the toolchain path (" + rtconfig.EXEC_PATH + ") is not exist, please check 'EXEC_PATH' in path or rtconfig.py.")
949            need_exit = True
950
951    if need_exit:
952        exit(0)
953
954def SrcRemove(src, remove):
955    if not src:
956        return
957
958    src_bak = src[:]
959
960    if type(remove) == type('str'):
961        if os.path.isabs(remove):
962            remove = os.path.relpath(remove, GetCurrentDir())
963        remove = os.path.normpath(remove)
964
965        for item in src_bak:
966            if type(item) == type('str'):
967                item_str = item
968            else:
969                item_str = item.rstr()
970
971            if os.path.isabs(item_str):
972                item_str = os.path.relpath(item_str, GetCurrentDir())
973            item_str = os.path.normpath(item_str)
974
975            if item_str == remove:
976                src.remove(item)
977    else:
978        for remove_item in remove:
979            remove_str = str(remove_item)
980            if os.path.isabs(remove_str):
981                remove_str = os.path.relpath(remove_str, GetCurrentDir())
982            remove_str = os.path.normpath(remove_str)
983
984            for item in src_bak:
985                if type(item) == type('str'):
986                    item_str = item
987                else:
988                    item_str = item.rstr()
989
990                if os.path.isabs(item_str):
991                    item_str = os.path.relpath(item_str, GetCurrentDir())
992                item_str = os.path.normpath(item_str)
993
994                if item_str == remove_str:
995                    src.remove(item)
996
997def GetVersion():
998    import SCons.cpp
999    import string
1000
1001    rtdef = os.path.join(Rtt_Root, 'include', 'rtdef.h')
1002
1003    # parse rtdef.h to get RT-Thread version
1004    prepcessor = create_preprocessor_instance()
1005    f = open(rtdef, 'r')
1006    contents = f.read()
1007    f.close()
1008    prepcessor.process_contents(contents)
1009    def_ns = prepcessor.cpp_namespace
1010
1011    version = int([ch for ch in def_ns['RT_VERSION_MAJOR'] if ch in '0123456789.'])
1012    subversion = int([ch for ch in def_ns['RT_VERSION_MINOR'] if ch in '0123456789.'])
1013
1014    if 'RT_VERSION_PATCH' in def_ns:
1015        revision = int([ch for ch in def_ns['RT_VERSION_PATCH'] if ch in '0123456789.'])
1016        return '%d.%d.%d' % (version, subversion, revision)
1017
1018    return '0.%d.%d' % (version, subversion)
1019
1020def GlobSubDir(sub_dir, ext_name):
1021    import os
1022    import glob
1023
1024    def glob_source(sub_dir, ext_name):
1025        list = os.listdir(sub_dir)
1026        src = glob.glob(os.path.join(sub_dir, ext_name))
1027
1028        for item in list:
1029            full_subdir = os.path.join(sub_dir, item)
1030            if os.path.isdir(full_subdir):
1031                src += glob_source(full_subdir, ext_name)
1032        return src
1033
1034    dst = []
1035    src = glob_source(sub_dir, ext_name)
1036    for item in src:
1037        dst.append(os.path.relpath(item, sub_dir))
1038    return dst
1039
1040def PackageSConscript(package):
1041    from package import BuildPackage
1042
1043    return BuildPackage(package)
1044