1#
2# File      : keil.py
3# This file is part of RT-Thread RTOS
4# COPYRIGHT (C) 2006 - 2015, RT-Thread Development Team
5#
6#  This program is free software; you can redistribute it and/or modify
7#  it under the terms of the GNU General Public License as published by
8#  the Free Software Foundation; either version 2 of the License, or
9#  (at your option) any later version.
10#
11#  This program is distributed in the hope that it will be useful,
12#  but WITHOUT ANY WARRANTY; without even the implied warranty of
13#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14#  GNU General Public License for more details.
15#
16#  You should have received a copy of the GNU General Public License along
17#  with this program; if not, write to the Free Software Foundation, Inc.,
18#  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19#
20# Change Logs:
21# Date           Author       Notes
22# 2015-01-20     Bernard      Add copyright information
23#
24
25import os
26import sys
27import string
28import shutil
29
30import xml.etree.ElementTree as etree
31from xml.etree.ElementTree import SubElement
32from utils import _make_path_relative
33from utils import xml_indent
34
35fs_encoding = sys.getfilesystemencoding()
36
37def _get_filetype(fn):
38    if fn.rfind('.cpp') != -1 or fn.rfind('.cxx') != -1:
39        return 8
40
41    if fn.rfind('.c') != -1 or fn.rfind('.C') != -1:
42        return 1
43
44    # assemble file type
45    if fn.rfind('.s') != -1 or fn.rfind('.S') != -1:
46        return 2
47
48    # header type
49    if fn.rfind('.h') != -1:
50        return 5
51
52    if fn.rfind('.lib') != -1:
53        return 4
54
55    if fn.rfind('.o') != -1:
56        return 3
57
58    # other filetype
59    return 5
60
61def MDK4AddGroupForFN(ProjectFiles, parent, name, filename, project_path):
62    group = SubElement(parent, 'Group')
63    group_name = SubElement(group, 'GroupName')
64    group_name.text = name
65
66    name = os.path.basename(filename)
67    path = os.path.dirname (filename)
68
69    basename = os.path.basename(path)
70    path = _make_path_relative(project_path, path)
71    path = os.path.join(path, name)
72    files = SubElement(group, 'Files')
73    file = SubElement(files, 'File')
74    file_name = SubElement(file, 'FileName')
75    name = os.path.basename(path)
76
77    if name.find('.cpp') != -1:
78        obj_name = name.replace('.cpp', '.o')
79    elif name.find('.c') != -1:
80        obj_name = name.replace('.c', '.o')
81    elif name.find('.s') != -1:
82        obj_name = name.replace('.s', '.o')
83    elif name.find('.S') != -1:
84        obj_name = name.replace('.s', '.o')
85    else:
86        obj_name = name
87
88    if ProjectFiles.count(obj_name):
89        name = basename + '_' + name
90    ProjectFiles.append(obj_name)
91    try: # python 2
92        file_name.text = name.decode(fs_encoding)
93    except: # python 3
94        file_name.text = name
95    file_type = SubElement(file, 'FileType')
96    file_type.text = '%d' % _get_filetype(name)
97    file_path = SubElement(file, 'FilePath')
98    try: # python 2
99        file_path.text = path.decode(fs_encoding)
100    except: # python 3
101        file_path.text = path
102
103
104    return group
105
106def MDK4AddLibToGroup(ProjectFiles, group, name, filename, project_path):
107    name = os.path.basename(filename)
108    path = os.path.dirname (filename)
109
110    basename = os.path.basename(path)
111    path = _make_path_relative(project_path, path)
112    path = os.path.join(path, name)
113    files = SubElement(group, 'Files')
114    file = SubElement(files, 'File')
115    file_name = SubElement(file, 'FileName')
116    name = os.path.basename(path)
117
118    if name.find('.cpp') != -1:
119        obj_name = name.replace('.cpp', '.o')
120    elif name.find('.c') != -1:
121        obj_name = name.replace('.c', '.o')
122    elif name.find('.s') != -1:
123        obj_name = name.replace('.s', '.o')
124    elif name.find('.S') != -1:
125        obj_name = name.replace('.s', '.o')
126    else:
127        obj_name = name
128
129    if ProjectFiles.count(obj_name):
130        name = basename + '_' + name
131    ProjectFiles.append(obj_name)
132    try:
133        file_name.text = name.decode(fs_encoding)
134    except:
135        file_name.text = name
136    file_type = SubElement(file, 'FileType')
137    file_type.text = '%d' % _get_filetype(name)
138    file_path = SubElement(file, 'FilePath')
139
140    try:
141        file_path.text = path.decode(fs_encoding)
142    except:
143        file_path.text = path
144
145    return group
146
147def MDK4AddGroup(ProjectFiles, parent, name, files, project_path, group_scons):
148    # don't add an empty group
149    if len(files) == 0:
150        return
151
152    group = SubElement(parent, 'Group')
153    group_name = SubElement(group, 'GroupName')
154    group_name.text = name
155
156    for f in files:
157        fn = f.rfile()
158        name = fn.name
159        path = os.path.dirname(fn.abspath)
160
161        basename = os.path.basename(path)
162        path = _make_path_relative(project_path, path)
163        path = os.path.join(path, name)
164
165        files = SubElement(group, 'Files')
166        file = SubElement(files, 'File')
167        file_name = SubElement(file, 'FileName')
168        name = os.path.basename(path)
169
170        if name.find('.cpp') != -1:
171            obj_name = name.replace('.cpp', '.o')
172        elif name.find('.c') != -1:
173            obj_name = name.replace('.c', '.o')
174        elif name.find('.s') != -1:
175            obj_name = name.replace('.s', '.o')
176        elif name.find('.S') != -1:
177            obj_name = name.replace('.s', '.o')
178
179        if ProjectFiles.count(obj_name):
180            name = basename + '_' + name
181        ProjectFiles.append(obj_name)
182        file_name.text = name # name.decode(fs_encoding)
183        file_type = SubElement(file, 'FileType')
184        file_type.text = '%d' % _get_filetype(name)
185        file_path = SubElement(file, 'FilePath')
186        file_path.text = path # path.decode(fs_encoding)
187
188        # for local LOCAL_CFLAGS/LOCAL_CXXFLAGS/LOCAL_CCFLAGS/LOCAL_CPPPATH/LOCAL_CPPDEFINES
189        MiscControls_text = ' '
190        if file_type.text == '1' and 'LOCAL_CFLAGS' in group_scons:
191            MiscControls_text = MiscControls_text + group_scons['LOCAL_CFLAGS']
192        elif file_type.text == '8' and 'LOCAL_CXXFLAGS' in group_scons:
193            MiscControls_text = MiscControls_text + group_scons['LOCAL_CXXFLAGS']
194        if 'LOCAL_CCFLAGS' in group_scons:
195            MiscControls_text = MiscControls_text + group_scons['LOCAL_CCFLAGS']
196        if MiscControls_text != ' ' or ('LOCAL_CPPDEFINES' in group_scons):
197            FileOption     = SubElement(file,  'FileOption')
198            FileArmAds     = SubElement(FileOption, 'FileArmAds')
199            Cads            = SubElement(FileArmAds, 'Cads')
200            VariousControls = SubElement(Cads, 'VariousControls')
201            MiscControls    = SubElement(VariousControls, 'MiscControls')
202            MiscControls.text = MiscControls_text
203            Define          = SubElement(VariousControls, 'Define')
204            if 'LOCAL_CPPDEFINES' in group_scons:
205                Define.text     = ', '.join(set(group_scons['LOCAL_CPPDEFINES']))
206            else:
207                Define.text     = ' '
208            Undefine        = SubElement(VariousControls, 'Undefine')
209            Undefine.text   = ' '
210            IncludePath     = SubElement(VariousControls, 'IncludePath')
211            if 'LOCAL_CPPPATH' in group_scons:
212                IncludePath.text = ';'.join([_make_path_relative(project_path, os.path.normpath(i)) for i in group_scons['LOCAL_CPPPATH']])
213            else:
214                IncludePath.text = ' '
215
216    return group
217
218# The common part of making MDK4/5 project
219def MDK45Project(env, tree, target, script):
220    project_path = os.path.dirname(os.path.abspath(target))
221
222    root = tree.getroot()
223    out = open(target, 'w')
224    out.write('<?xml version="1.0" encoding="UTF-8" standalone="no" ?>\n')
225
226    CPPPATH = []
227    CPPDEFINES = env.get('CPPDEFINES', [])
228    LINKFLAGS = ''
229    CXXFLAGS = ''
230    CCFLAGS = ''
231    CFLAGS = ''
232    ProjectFiles = []
233
234    # add group
235    groups = tree.find('Targets/Target/Groups')
236    if groups is None:
237        groups = SubElement(tree.find('Targets/Target'), 'Groups')
238    groups.clear() # clean old groups
239    for group in script:
240        group_tree = MDK4AddGroup(ProjectFiles, groups, group['name'], group['src'], project_path, group)
241
242        # get each include path
243        if 'CPPPATH' in group and group['CPPPATH']:
244            if CPPPATH:
245                CPPPATH += group['CPPPATH']
246            else:
247                CPPPATH += group['CPPPATH']
248
249        # get each group's link flags
250        if 'LINKFLAGS' in group and group['LINKFLAGS']:
251            if LINKFLAGS:
252                LINKFLAGS += ' ' + group['LINKFLAGS']
253            else:
254                LINKFLAGS += group['LINKFLAGS']
255
256        # get each group's CXXFLAGS flags
257        if 'CXXFLAGS' in group and group['CXXFLAGS']:
258            if CXXFLAGS:
259                CXXFLAGS += ' ' + group['CXXFLAGS']
260            else:
261                CXXFLAGS += group['CXXFLAGS']
262
263        # get each group's CCFLAGS flags
264        if 'CCFLAGS' in group and group['CCFLAGS']:
265            if CCFLAGS:
266                CCFLAGS += ' ' + group['CCFLAGS']
267            else:
268                CCFLAGS += group['CCFLAGS']
269
270        # get each group's CFLAGS flags
271        if 'CFLAGS' in group and group['CFLAGS']:
272            if CFLAGS:
273                CFLAGS += ' ' + group['CFLAGS']
274            else:
275                CFLAGS += group['CFLAGS']
276
277        # get each group's LIBS flags
278        if 'LIBS' in group and group['LIBS']:
279            for item in group['LIBPATH']:
280                full_path = os.path.join(item, group['name'] + '.lib')
281                if os.path.isfile(full_path): # has this library
282                    if group_tree != None:
283                        MDK4AddLibToGroup(ProjectFiles, group_tree, group['name'], full_path, project_path)
284                    else:
285                        group_tree = MDK4AddGroupForFN(ProjectFiles, groups, group['name'], full_path, project_path)
286
287    # write include path, definitions and link flags
288    IncludePath = tree.find('Targets/Target/TargetOption/TargetArmAds/Cads/VariousControls/IncludePath')
289    IncludePath.text = ';'.join([_make_path_relative(project_path, os.path.normpath(i)) for i in set(CPPPATH)])
290
291    Define = tree.find('Targets/Target/TargetOption/TargetArmAds/Cads/VariousControls/Define')
292    Define.text = ', '.join(set(CPPDEFINES))
293
294    if 'c99' in CXXFLAGS or 'c99' in CCFLAGS or 'c99' in CFLAGS:
295        uC99 = tree.find('Targets/Target/TargetOption/TargetArmAds/Cads/uC99')
296        uC99.text = '1'
297
298    if 'gnu' in CXXFLAGS or 'gnu' in CCFLAGS or 'gnu' in CFLAGS:
299        uGnu = tree.find('Targets/Target/TargetOption/TargetArmAds/Cads/uGnu')
300        uGnu.text = '1'
301
302    Misc = tree.find('Targets/Target/TargetOption/TargetArmAds/LDads/Misc')
303    Misc.text = LINKFLAGS
304
305    xml_indent(root)
306    out.write(etree.tostring(root, encoding='utf-8').decode())
307    out.close()
308
309def MDK4Project(env, target, script):
310
311    if os.path.isfile('template.uvproj') is False:
312        print ('Warning: The template project file [template.uvproj] not found!')
313        return
314
315    template_tree = etree.parse('template.uvproj')
316
317    MDK45Project(env, template_tree, target, script)
318
319    # remove project.uvopt file
320    project_uvopt = os.path.abspath(target).replace('uvproj', 'uvopt')
321    if os.path.isfile(project_uvopt):
322        os.unlink(project_uvopt)
323
324    # copy uvopt file
325    if os.path.exists('template.uvopt'):
326        import shutil
327        shutil.copy2('template.uvopt', '{}.uvopt'.format(os.path.splitext(target)[0]))
328import threading
329import time
330def monitor_log_file(log_file_path):
331    if not os.path.exists(log_file_path):
332        open(log_file_path, 'w').close()
333    empty_line_count = 0
334    with open(log_file_path, 'r') as log_file:
335        while True:
336            line = log_file.readline()
337            if line:
338                print(line.strip())
339                if 'Build Time Elapsed' in line:
340                    break
341                empty_line_count = 0
342            else:
343                empty_line_count += 1
344                time.sleep(1)
345            if empty_line_count > 30:
346                print("Timeout reached or too many empty lines, exiting log monitoring thread.")
347                break
348def MDK5Project(env, target, script):
349
350    if os.path.isfile('template.uvprojx') is False:
351        print ('Warning: The template project file [template.uvprojx] not found!')
352        return
353
354    template_tree = etree.parse('template.uvprojx')
355
356    MDK45Project(env, template_tree, target, script)
357
358    # remove project.uvopt file
359    project_uvopt = os.path.abspath(target).replace('uvprojx', 'uvoptx')
360    if os.path.isfile(project_uvopt):
361        os.unlink(project_uvopt)
362    # copy uvopt file
363    if os.path.exists('template.uvoptx'):
364        import shutil
365        shutil.copy2('template.uvoptx', '{}.uvoptx'.format(os.path.splitext(target)[0]))
366        # build with UV4.exe
367
368        if shutil.which('UV4.exe') is not None:
369            target_name = template_tree.find('Targets/Target/TargetName')
370            print('target_name:', target_name.text)
371            log_file_path = 'keil.log'
372            if os.path.exists(log_file_path):
373                os.remove(log_file_path)
374            log_thread = threading.Thread(target=monitor_log_file, args=(log_file_path,))
375            log_thread.start()
376            cmd = 'UV4.exe -b project.uvprojx -q -j0 -t '+ target_name.text +' -o '+log_file_path
377            print('Start to build keil project')
378            print(cmd)
379            os.system(cmd)
380        else:
381            print('UV4.exe is not available, please check your keil installation')
382
383def MDK2Project(env, target, script):
384    template = open(os.path.join(os.path.dirname(__file__), 'template.Uv2'), 'r')
385    lines = template.readlines()
386
387    project = open(target, "w")
388    project_path = os.path.dirname(os.path.abspath(target))
389
390    line_index = 5
391    # write group
392    for group in script:
393        lines.insert(line_index, 'Group (%s)\r\n' % group['name'])
394        line_index += 1
395
396    lines.insert(line_index, '\r\n')
397    line_index += 1
398
399    # write file
400
401    ProjectFiles = []
402    CPPPATH = []
403    CPPDEFINES = env.get('CPPDEFINES', [])
404    LINKFLAGS = ''
405    CFLAGS = ''
406
407    # number of groups
408    group_index = 1
409    for group in script:
410        # print group['name']
411
412        # get each include path
413        if 'CPPPATH' in group and group['CPPPATH']:
414            if CPPPATH:
415                CPPPATH += group['CPPPATH']
416            else:
417                CPPPATH += group['CPPPATH']
418
419        # get each group's link flags
420        if 'LINKFLAGS' in group and group['LINKFLAGS']:
421            if LINKFLAGS:
422                LINKFLAGS += ' ' + group['LINKFLAGS']
423            else:
424                LINKFLAGS += group['LINKFLAGS']
425
426        # generate file items
427        for node in group['src']:
428            fn = node.rfile()
429            name = fn.name
430            path = os.path.dirname(fn.abspath)
431            basename = os.path.basename(path)
432            path = _make_path_relative(project_path, path)
433            path = os.path.join(path, name)
434            if ProjectFiles.count(name):
435                name = basename + '_' + name
436            ProjectFiles.append(name)
437            lines.insert(line_index, 'File %d,%d,<%s><%s>\r\n'
438                % (group_index, _get_filetype(name), path, name))
439            line_index += 1
440
441        group_index = group_index + 1
442
443    lines.insert(line_index, '\r\n')
444    line_index += 1
445
446    # remove repeat path
447    paths = set()
448    for path in CPPPATH:
449        inc = _make_path_relative(project_path, os.path.normpath(path))
450        paths.add(inc) #.replace('\\', '/')
451
452    paths = [i for i in paths]
453    CPPPATH = string.join(paths, ';')
454
455    definitions = [i for i in set(CPPDEFINES)]
456    CPPDEFINES = string.join(definitions, ', ')
457
458    while line_index < len(lines):
459        if lines[line_index].startswith(' ADSCINCD '):
460            lines[line_index] = ' ADSCINCD (' + CPPPATH + ')\r\n'
461
462        if lines[line_index].startswith(' ADSLDMC ('):
463            lines[line_index] = ' ADSLDMC (' + LINKFLAGS + ')\r\n'
464
465        if lines[line_index].startswith(' ADSCDEFN ('):
466            lines[line_index] = ' ADSCDEFN (' + CPPDEFINES + ')\r\n'
467
468        line_index += 1
469
470    # write project
471    for line in lines:
472        project.write(line)
473
474    project.close()
475
476def ARMCC_Version():
477    import rtconfig
478    import subprocess
479    import re
480
481    path = rtconfig.EXEC_PATH
482    if(rtconfig.PLATFORM == 'armcc'):
483        path = os.path.join(path, 'armcc.exe')
484    elif(rtconfig.PLATFORM == 'armclang'):
485        path = os.path.join(path, 'armlink.exe')
486
487    if os.path.exists(path):
488        cmd = path
489    else:
490        return "0.0"
491
492    child = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
493    stdout, stderr = child.communicate()
494
495    '''
496    example stdout:
497    Product: MDK Plus 5.24
498    Component: ARM Compiler 5.06 update 5 (build 528)
499    Tool: armcc [4d3621]
500
501    return version: MDK Plus 5.24/ARM Compiler 5.06 update 5 (build 528)/armcc [4d3621]
502    '''
503    if not isinstance(stdout, str):
504        stdout = str(stdout, 'utf8') # Patch for Python 3
505    version_Product = re.search(r'Product: (.+)', stdout).group(1)
506    version_Product = version_Product[:-1]
507    version_Component = re.search(r'Component: (.*)', stdout).group(1)
508    version_Component = version_Component[:-1]
509    version_Tool = re.search(r'Tool: (.*)', stdout).group(1)
510    version_Tool = version_Tool[:-1]
511    version_str_format = '%s/%s/%s'
512    version_str = version_str_format % (version_Product, version_Component, version_Tool)
513    return version_str
514