1#! /usr/bin/env python
2# coding=utf-8
3#
4# Copyright (c) 2024, RT-Thread Development Team
5#
6# SPDX-License-Identifier: GPL-2.0
7#
8# Change Logs:
9# Date           Author       Notes
10# 2024-04-20     Bernard      the first version
11
12import os
13import json
14import platform
15import re
16import sys
17import shutil
18import hashlib
19import operator
20
21
22def GetEnvPath():
23    if "ENV_ROOT" in os.environ:
24        return os.environ["ENV_ROOT"]
25
26    if platform.system() == 'Windows':
27        env_root = os.path.join(os.environ['USERPROFILE'], '.env')
28    else:
29        env_root = os.path.join(os.environ['HOME'], '.env')
30
31    return env_root
32
33
34def GetPkgPath():
35    if "PKGS_DIR" in os.environ:
36        return os.environ["PKGS_DIR"]
37    elif "PKGS_ROOT" in os.environ:
38        return os.environ["PKGS_ROOT"]
39    elif GetEnvPath():
40        return os.path.join(GetEnvPath(), "packages")
41    else:
42        return None
43
44def GetSDKPackagePath():
45    env = GetEnvPath()
46
47    if env:
48        return os.path.join(env, "tools", "scripts", "packages")
49
50    return None
51
52# get SDK path based on name
53# for example, GetSDKPath('arm-none-eabi') = '.env/tools/scripts/packages/arm-none-eabi-gcc-v10.3'
54def GetSDKPath(name):
55    sdk_pkgs = GetSDKPackagePath()
56
57    if sdk_pkgs:
58        # read env/tools/scripts/sdk_cfg.json for curstomized SDK path
59        if os.path.exists(os.path.join(sdk_pkgs, '..', 'sdk_cfg.json')):
60            with open(os.path.join(sdk_pkgs, '..', 'sdk_cfg.json'), 'r', encoding='utf-8') as f:
61                sdk_cfg = json.load(f)
62                for item in sdk_cfg:
63                    if item['name'] == name:
64                        sdk = os.path.join(sdk_pkgs, item['path'])
65                        return sdk
66
67        # read packages.json under env/tools/scripts/packages
68        with open(os.path.join(sdk_pkgs, 'pkgs.json'), 'r', encoding='utf-8') as f:
69            # packages_json = f.read()
70            packages = json.load(f)
71
72            for item in packages:
73                package_path = os.path.join(GetEnvPath(), 'packages', item['path'], 'package.json')
74                # read package['path']/package.json under env/packages
75                with open(package_path, 'r', encoding='utf-8') as f:
76                    # package_json = f.read()
77                    package = json.load(f)
78
79                    if package['name'] == name:
80                        sdk = os.path.join(sdk_pkgs, package['name'] + '-' + item['ver'])
81                        return sdk
82
83    # not found named package
84    return None
85
86def help_info():
87    print(
88        "**********************************************************************************\n"
89        "* Help infomation:\n"
90        "* Git tool install step.\n"
91        "* If your system is linux, you can use command below to install git.\n"
92        "* $ sudo yum install git\n"
93        "* $ sudo apt-get install git\n"
94        "* If your system is windows, you should download git software(msysGit).\n"
95        "* Download path: http://git-scm.com/download/win\n"
96        "* After you install it, be sure to add the git command execution PATH \n"
97        "* to your system PATH.\n"
98        "* Usually, git command PATH is $YOUR_INSTALL_DIR\\Git\\bin\n"
99        "* If your system is OSX, please download git and install it.\n"
100        "* Download path:  http://git-scm.com/download/mac\n"
101        "**********************************************************************************\n"
102    )
103
104
105def touch_env(use_gitee=False):
106    if sys.platform != 'win32':
107        home_dir = os.environ['HOME']
108    else:
109        home_dir = os.environ['USERPROFILE']
110
111    if use_gitee:
112        # "Install RT-Thread Environment from Gitee"
113        sdk_url = 'https://gitee.com/RT-Thread-Mirror/sdk.git'
114        pkg_url = 'https://gitee.com/RT-Thread-Mirror/packages.git'
115        env_url = 'https://gitee.com/RT-Thread-Mirror/env.git'
116    else:
117        pkg_url = 'https://github.com/RT-Thread/packages.git'
118        sdk_url = 'https://github.com/RT-Thread/sdk.git'
119        env_url = 'https://github.com/RT-Thread/env.git'
120
121    pkg_url = os.getenv('RTT_PACKAGE_URL') or pkg_url
122    sdk_url = os.getenv('RTT_SDK_URL') or sdk_url
123    env_url = os.getenv('RTT_ENV_URL') or env_url
124
125    # make .env and other directories
126    env_dir = os.path.join(home_dir, '.env')
127    if not os.path.exists(env_dir):
128        os.mkdir(env_dir)
129        os.mkdir(os.path.join(env_dir, 'local_pkgs'))
130        os.mkdir(os.path.join(env_dir, 'packages'))
131        os.mkdir(os.path.join(env_dir, 'tools'))
132        kconfig = open(os.path.join(env_dir, 'packages', 'Kconfig'), 'w')
133        kconfig.close()
134
135    # git clone packages
136    if not os.path.exists(os.path.join(env_dir, 'packages', 'packages')):
137        try:
138            ret = os.system('git clone %s %s' % (pkg_url, os.path.join(env_dir, 'packages', 'packages')))
139            if ret != 0:
140                shutil.rmtree(os.path.join(env_dir, 'packages', 'packages'))
141                print(
142                    "********************************************************************************\n"
143                    "* Warnning:\n"
144                    "* Run command error for \"git clone https://github.com/RT-Thread/packages.git\".\n"
145                    "* This error may have been caused by not found a git tool or network error.\n"
146                    "* If the git tool is not installed, install the git tool first.\n"
147                    "* If the git utility is installed, check whether the git command is added to \n"
148                    "* the system PATH.\n"
149                    "* This error may cause the RT-Thread packages to not work properly.\n"
150                    "********************************************************************************\n"
151                )
152                help_info()
153            else:
154                kconfig = open(os.path.join(env_dir, 'packages', 'Kconfig'), 'w')
155                kconfig.write('source "$PKGS_DIR/packages/Kconfig"')
156                kconfig.close()
157        except:
158            print(
159                "**********************************************************************************\n"
160                "* Warnning:\n"
161                "* Run command error for \"git clone https://github.com/RT-Thread/packages.git\". \n"
162                "* This error may have been caused by not found a git tool or git tool not in \n"
163                "* the system PATH. \n"
164                "* This error may cause the RT-Thread packages to not work properly. \n"
165                "**********************************************************************************\n"
166            )
167            help_info()
168
169    # git clone env scripts
170    if not os.path.exists(os.path.join(env_dir, 'tools', 'scripts')):
171        try:
172            ret = os.system('git clone %s %s' % (env_url, os.path.join(env_dir, 'tools', 'scripts')))
173            if ret != 0:
174                shutil.rmtree(os.path.join(env_dir, 'tools', 'scripts'))
175                print(
176                    "********************************************************************************\n"
177                    "* Warnning:\n"
178                    "* Run command error for \"git clone https://github.com/RT-Thread/env.git\".\n"
179                    "* This error may have been caused by not found a git tool or network error.\n"
180                    "* If the git tool is not installed, install the git tool first.\n"
181                    "* If the git utility is installed, check whether the git command is added \n"
182                    "* to the system PATH.\n"
183                    "* This error may cause script tools to fail to work properly.\n"
184                    "********************************************************************************\n"
185                )
186                help_info()
187        except:
188            print(
189                "********************************************************************************\n"
190                "* Warnning:\n"
191                "* Run command error for \"git clone https://github.com/RT-Thread/env.git\". \n"
192                "* This error may have been caused by not found a git tool or git tool not in \n"
193                "* the system PATH. \n"
194                "* This error may cause script tools to fail to work properly. \n"
195                "********************************************************************************\n"
196            )
197            help_info()
198
199    # git clone sdk
200    if not os.path.exists(os.path.join(env_dir, 'packages', 'sdk')):
201        try:
202            ret = os.system('git clone %s %s' % (sdk_url, os.path.join(env_dir, 'packages', 'sdk')))
203            if ret != 0:
204                shutil.rmtree(os.path.join(env_dir, 'packages', 'sdk'))
205                print(
206                    "********************************************************************************\n"
207                    "* Warnning:\n"
208                    "* Run command error for \"git clone https://github.com/RT-Thread/sdk.git\".\n"
209                    "* This error may have been caused by not found a git tool or network error.\n"
210                    "* If the git tool is not installed, install the git tool first.\n"
211                    "* If the git utility is installed, check whether the git command is added \n"
212                    "* to the system PATH.\n"
213                    "* This error may cause the RT-Thread SDK to not work properly.\n"
214                    "********************************************************************************\n"
215                )
216                help_info()
217        except:
218            print(
219                "********************************************************************************\n"
220                "* Warnning:\n"
221                "* Run command error for \"https://github.com/RT-Thread/sdk.git\".\n"
222                "* This error may have been caused by not found a git tool or git tool not in \n"
223                "* the system PATH. \n"
224                "* This error may cause the RT-Thread SDK to not work properly. \n"
225                "********************************************************************************\n"
226            )
227            help_info()
228
229    # try to create an empty .config file
230    if not os.path.exists(os.path.join(env_dir, 'tools', '.config')):
231        kconfig = open(os.path.join(env_dir, 'tools', '.config'), 'w')
232        kconfig.close()
233
234    # copy env.sh or env.ps1, Kconfig
235    shutil.copy(os.path.join(env_dir, 'tools', 'scripts', 'Kconfig'), os.path.join(home_dir, '.env', 'tools'))
236    if sys.platform != 'win32':
237        shutil.copy(os.path.join(env_dir, 'tools', 'scripts', 'env.sh'), os.path.join(home_dir, '.env', 'env.sh'))
238    else:
239        shutil.copy(os.path.join(env_dir, 'tools', 'scripts', 'env.ps1'), os.path.join(home_dir, '.env', 'env.ps1'))
240        # unzip kconfig-mconf.zip
241        # zip_file = os.path.join(env_dir, 'tools', 'scripts', 'kconfig-mconf.zip')
242        # if os.path.exists(zip_file):
243        #     zip_file_dir = os.path.join(env_dir, 'tools', 'bin')
244        #     if os.path.exists(zip_file_dir):
245        #         shutil.rmtree(zip_file_dir)
246        #     zip_file_obj = zipfile.ZipFile(zip_file, 'r')
247        #     for file in zip_file_obj.namelist():
248        #         zip_file_obj.extract(file, zip_file_dir)
249        #     zip_file_obj.close()
250
251
252def is_pkg_special_config(config_str):
253    '''judge if it's CONFIG_PKG_XX_PATH or CONFIG_PKG_XX_VER'''
254
255    if type(config_str) == type('a'):
256        if config_str.startswith("PKG_") and (config_str.endswith('_PATH') or config_str.endswith('_VER')):
257            return True
258    return False
259
260
261def mk_rtconfig(filename):
262    try:
263        config = open(filename, 'r')
264    except:
265        print('open config:%s failed' % filename)
266        return
267
268    rtconfig = open('rtconfig.h', 'w')
269    rtconfig.write('#ifndef RT_CONFIG_H__\n')
270    rtconfig.write('#define RT_CONFIG_H__\n\n')
271
272    empty_line = 1
273
274    for line in config:
275        line = line.lstrip(' ').replace('\n', '').replace('\r', '')
276
277        if len(line) == 0:
278            continue
279
280        if line[0] == '#':
281            if len(line) == 1:
282                if empty_line:
283                    continue
284
285                rtconfig.write('\n')
286                empty_line = 1
287                continue
288
289            if line.startswith('# CONFIG_'):
290                line = ' ' + line[9:]
291            else:
292                line = line[1:]
293                rtconfig.write('/*%s */\n' % line)
294
295            empty_line = 0
296        else:
297            empty_line = 0
298            setting = line.split('=')
299            if len(setting) >= 2:
300                if setting[0].startswith('CONFIG_'):
301                    setting[0] = setting[0][7:]
302
303                # remove CONFIG_PKG_XX_PATH or CONFIG_PKG_XX_VER
304                if is_pkg_special_config(setting[0]):
305                    continue
306
307                if setting[1] == 'y':
308                    rtconfig.write('#define %s\n' % setting[0])
309                else:
310                    rtconfig.write('#define %s %s\n' % (setting[0], re.findall(r"^.*?=(.*)$", line)[0]))
311
312    if os.path.isfile('rtconfig_project.h'):
313        rtconfig.write('#include "rtconfig_project.h"\n')
314
315    rtconfig.write('\n')
316    rtconfig.write('#endif\n')
317    rtconfig.close()
318
319
320def get_file_md5(file):
321    MD5 = hashlib.new('md5')
322    with open(file, 'r') as fp:
323        MD5.update(fp.read().encode('utf8'))
324        fp_md5 = MD5.hexdigest()
325        return fp_md5
326
327
328# Exclude utestcases
329def exclude_utestcases(RTT_ROOT):
330    if os.path.isfile(os.path.join(RTT_ROOT, 'examples/utest/testcases/Kconfig')):
331        return
332
333    if not os.path.isfile(os.path.join(RTT_ROOT, 'Kconfig')):
334        return
335
336    with open(os.path.join(RTT_ROOT, 'Kconfig'), 'r') as f:
337        data = f.readlines()
338    with open(os.path.join(RTT_ROOT, 'Kconfig'), 'w') as f:
339        for line in data:
340            if line.find('examples/utest/testcases/Kconfig') == -1:
341                f.write(line)
342
343
344# fix locale for kconfiglib
345def kconfiglib_fix_locale():
346    import os
347    import locale
348
349    # Get the list of supported locales
350    supported_locales = set(locale.locale_alias.keys())
351
352    # Check if LANG is set and its value is not in the supported locales
353    if 'LANG' in os.environ and os.environ['LANG'] not in supported_locales:
354        os.environ['LANG'] = 'C'
355
356
357def kconfiglib_check_installed():
358    try:
359        import kconfiglib
360    except ImportError as e:
361        print("\033[1;31m**ERROR**: Failed to import kconfiglib, " + str(e))
362        print("")
363        print("You may need to install it using:")
364        print("    pip install kconfiglib\033[0m")
365        print("")
366        sys.exit(1)
367
368    # set PKGS_DIR envrionment
369    pkg_dir = GetPkgPath()
370    if os.path.exists(pkg_dir):
371        os.environ["PKGS_DIR"] = pkg_dir
372    elif sys.platform != 'win32':
373        touch_env()
374        os.environ["PKGS_DIR"] = GetPkgPath()
375    else:
376        print("\033[1;33m**WARNING**: PKGS_DIR not found, please install ENV tools\033[0m")
377
378
379# menuconfig for Linux and Windows
380def menuconfig(RTT_ROOT):
381    kconfiglib_check_installed()
382
383    import menuconfig
384
385    # Exclude utestcases
386    exclude_utestcases(RTT_ROOT)
387
388    fn = '.config'
389    fn_old = '.config.old'
390
391    sys.argv = ['menuconfig', 'Kconfig']
392
393    # fix vscode console
394    kconfiglib_fix_locale()
395
396    menuconfig._main()
397
398    if os.path.isfile(fn):
399        if os.path.isfile(fn_old):
400            diff_eq = operator.eq(get_file_md5(fn), get_file_md5(fn_old))
401        else:
402            diff_eq = False
403    else:
404        sys.exit(-1)
405
406    # make rtconfig.h
407    if diff_eq == False:
408        shutil.copyfile(fn, fn_old)
409        mk_rtconfig(fn)
410
411
412# guiconfig for windows and linux
413def guiconfig(RTT_ROOT):
414    kconfiglib_check_installed()
415
416    import guiconfig
417
418    # Exclude utestcases
419    exclude_utestcases(RTT_ROOT)
420
421    fn = '.config'
422    fn_old = '.config.old'
423
424    sys.argv = ['guiconfig', 'Kconfig']
425    guiconfig._main()
426
427    if os.path.isfile(fn):
428        if os.path.isfile(fn_old):
429            diff_eq = operator.eq(get_file_md5(fn), get_file_md5(fn_old))
430        else:
431            diff_eq = False
432    else:
433        sys.exit(-1)
434
435    # make rtconfig.h
436    if diff_eq == False:
437        shutil.copyfile(fn, fn_old)
438        mk_rtconfig(fn)
439
440
441# defconfig for windows and linux
442def defconfig(RTT_ROOT):
443    kconfiglib_check_installed()
444
445    import defconfig
446
447    # Exclude utestcases
448    exclude_utestcases(RTT_ROOT)
449
450    fn = '.config'
451
452    sys.argv = ['defconfig', '--kconfig', 'Kconfig', '.config']
453    defconfig.main()
454
455    # silent mode, force to make rtconfig.h
456    mk_rtconfig(fn)
457
458
459def genconfig():
460    from SCons.Script import SCons
461
462    PreProcessor = SCons.cpp.PreProcessor()
463
464    try:
465        f = open('rtconfig.h', 'r')
466        contents = f.read()
467        f.close()
468    except:
469        print("Open rtconfig.h file failed.")
470
471    PreProcessor.process_contents(contents)
472    options = PreProcessor.cpp_namespace
473
474    try:
475        f = open('.config', 'w')
476        for opt, value in options.items():
477            if type(value) == type(1):
478                f.write("CONFIG_%s=%d\n" % (opt, value))
479
480            if type(value) == type('') and value == '':
481                f.write("CONFIG_%s=y\n" % opt)
482            elif type(value) == type('str'):
483                f.write("CONFIG_%s=%s\n" % (opt, value))
484
485        print("Generate .config done!")
486        f.close()
487    except:
488        print("Generate .config file failed.")
489