c9eef2d697
Most tools used for compliance and SBOM generation use SPDX identifiers This change brings us a step closer to an easy SBOM generation. Signed-off-by: Alin Jerpelea <alin.jerpelea@sony.com>
1077 lines
34 KiB
Python
Executable File
1077 lines
34 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
############################################################################
|
|
# tools/ide_exporter.py
|
|
#
|
|
# SPDX-License-Identifier: BSD-3-Clause
|
|
#
|
|
#
|
|
# Copyright (C) 2016 Kha Vo. All rights reserved.
|
|
# Author: Kha Vo <canhkha@gmail.com>
|
|
#
|
|
# Based on convert_make2file_list.py and add_source_in_iar.py
|
|
# Author: avyhovanec@yahoo.com
|
|
#
|
|
# Redistribution and use in source and binary forms, with or without
|
|
# modification, are permitted provided that the following conditions
|
|
# are met:
|
|
#
|
|
# 1. Redistributions of source code must retain the above copyright
|
|
# notice, this list of conditions and the following disclaimer.
|
|
# 2. Redistributions in binary form must reproduce the above copyright
|
|
# notice, this list of conditions and the following disclaimer in
|
|
# the documentation and/or other materials provided with the
|
|
# distribution.
|
|
# 3. Neither the name NuttX nor the names of its contributors may be
|
|
# used to endorse or promote products derived from this software
|
|
# without specific prior written permission.
|
|
#
|
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
|
# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
|
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
# POSSIBILITY OF SUCH DAMAGE.
|
|
#
|
|
############################################################################
|
|
|
|
import argparse
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
from copy import deepcopy
|
|
|
|
from lxml import etree as ET
|
|
|
|
HELP = """
|
|
ide_exporter.pyis a tool for generation nuttx iar/keil workspace
|
|
usage: ide_exporter.py [-h] [-v] [-o OUT_DIR] [-d]
|
|
build_log {iar,uvision_gcc,uvision_armcc} template_dir
|
|
|
|
positional arguments:
|
|
build_log Log file from make V=1
|
|
{iar,uvision_gcc,uvision_armcc}
|
|
The target IDE: iar, uvision_gcc, (uvision_armcc is
|
|
experimental)
|
|
template_dir Directory that contains IDEs template projects
|
|
template_nuttx.eww : iar template workspace
|
|
template_nuttx_main.ewp : iar template project
|
|
template_nuttx_lib.ewp : iar library project
|
|
or
|
|
template_nuttx.uvmpw : uVision template workspace
|
|
template_nuttx_main.uvproj : uVision template project
|
|
template_nuttx_lib.uvproj : uVision library project
|
|
|
|
optional arguments:
|
|
-h, --help show this help message and exit
|
|
-v, --version show program's version number and exit
|
|
-o OUT_DIR, --output OUT_DIR
|
|
Output directory
|
|
-d, --dump Dump project structure tree
|
|
"""
|
|
|
|
IAR = "iar"
|
|
UVISION_GCC = "uvision_gcc"
|
|
UVISION_ARMCC = "uvision_armcc"
|
|
|
|
COMPILE_PREFIX_LIST = ("CC: ", "AS: ", "CXX:")
|
|
LIB_PREFIX_LIST = "AR: "
|
|
LINK_PREFIX_LIST = "LD: "
|
|
MAKE_ENTER_DIR = "Entering directory"
|
|
PREFIX_LEN = 4
|
|
|
|
IAR_EXT_REMAP = {r"gnu/(\w+)\.S$": r"iar/\g<1>.S"}
|
|
ARMCC_EXT_REMAP = {r"gnu/(\w+)\.S$": r"armcc/\g<1>.S", r"(\w+)\.a$": r"\g<1>.lib"}
|
|
UVISION_GCC_EXT_REMAP = {}
|
|
|
|
# file ext to FileTye in uVision project
|
|
UVISION_FILE_TYPE_MAP = {".c": "1", ".S": "1", ".cxx": "8", ".lib": "4", ".a": "4"}
|
|
|
|
# tags convention: tag[0] = root_tags, create if doesn't exist
|
|
# tag[1] = (sub_tag,) tag without text, create new
|
|
# tag[2] = (leaf_tag,) with text, create new
|
|
IAR_PRJ_SETTINGS = {
|
|
"group_tags": ("", ("group",), ("name",)),
|
|
"file_tags": ("", ("file",), ("name",)),
|
|
"rel_base": "$PROJ_DIR$/",
|
|
"cleared_nodes": (
|
|
"group",
|
|
"file",
|
|
),
|
|
"include_pnodes": (".//*[name='CCIncludePath2']", ".//*[name='AUserIncludes']"),
|
|
"include_tag": "state",
|
|
"output_path": {"exe": "Obj", "obj": "Obj", "lst": "Lst"},
|
|
"ext_remap": IAR_EXT_REMAP,
|
|
}
|
|
|
|
IAR_WSP_SETTINGS = {
|
|
"group_tags": ("",),
|
|
"file_tags": ("", ("project",), ("path",)),
|
|
"rel_base": "$WS_DIR$/",
|
|
"cleared_nodes": ("project",),
|
|
}
|
|
|
|
|
|
UVISION_ARMCC_PRJ_SETTINGS = {
|
|
"root_group": "",
|
|
"group_tags": (".//Targets/Target/Groups", ("Group",), ("GroupName",)),
|
|
"file_tags": (
|
|
"Files",
|
|
("File",),
|
|
(
|
|
"FileName",
|
|
"FileType",
|
|
"FilePath",
|
|
),
|
|
),
|
|
"rel_base": "",
|
|
"cleared_nodes": (".//Group",),
|
|
"include_pnodes": ".//VariousControls/IncludePath",
|
|
"output_path": {"exe": "Obj", "obj": "Obj", "lst": "Lst"},
|
|
"ext_remap": ARMCC_EXT_REMAP,
|
|
"uv_file_type": UVISION_FILE_TYPE_MAP,
|
|
}
|
|
|
|
UVISION_GCC_PRJ_SETTINGS = {
|
|
"root_group": "",
|
|
"group_tags": (".//Targets/Target/Groups", ("Group",), ("GroupName",)),
|
|
"file_tags": (
|
|
"Files",
|
|
("File",),
|
|
(
|
|
"FileName",
|
|
"FileType",
|
|
"FilePath",
|
|
),
|
|
),
|
|
"rel_base": "",
|
|
"cleared_nodes": (".//Group",),
|
|
"include_pnodes": ".//VariousControls/IncludePath",
|
|
"saved_tags": (".//FileOption",),
|
|
"output_path": {"exe": "Obj", "obj": "Obj", "lst": "Lst"},
|
|
"ext_remap": UVISION_GCC_EXT_REMAP,
|
|
"uv_file_type": UVISION_FILE_TYPE_MAP,
|
|
"c_misc": (".//Carm", "-fno-builtin -Wall -Wstrict-prototypes -Wshadow -Wundef -g"),
|
|
"cxx_misc": (
|
|
".//Carm",
|
|
"-fno-builtin -fno-exceptions -fcheck-new -fno-rtti -Wall -Wshadow -Wundef -g",
|
|
),
|
|
"ld_misc": (".//LDarm", "--entry=__start -lgcc"),
|
|
"cxx_def": (".//Carm", ""),
|
|
}
|
|
|
|
UVISION_WSP_SETTINGS = {
|
|
"group_tags": ("",),
|
|
"file_tags": ("", ("project",), ("PathAndName",)),
|
|
"rel_base": "",
|
|
"cleared_nodes": ("project",),
|
|
}
|
|
|
|
|
|
LIB_EXTS = (".a", ".lib")
|
|
ASM_EXTS = (".s", ".S")
|
|
|
|
|
|
def get_common_dir(dir_list):
|
|
"""Get common parent directory of a given directory list"""
|
|
com_dir = dir_list[0]
|
|
found = False
|
|
while not found:
|
|
found = True
|
|
com_dir = os.path.split(com_dir)[0]
|
|
for directory in dir_list:
|
|
if com_dir not in directory:
|
|
found = False
|
|
break
|
|
|
|
if found:
|
|
return com_dir
|
|
else:
|
|
return "/" # return root
|
|
|
|
|
|
class SourceInfo(object):
|
|
"""Source file information
|
|
|
|
Attributes:
|
|
src: source file
|
|
include: List of including dir in compiled command
|
|
flags: other compiled flags
|
|
"""
|
|
|
|
def __init__(self, src, include=None, flags=""):
|
|
self.include = []
|
|
if include is not None:
|
|
self.include = include
|
|
self.src = src
|
|
self.flags = flags
|
|
self.include = include
|
|
|
|
@staticmethod
|
|
def get_common_src_dir(sinfo_list):
|
|
"""Get Common directory from list of source code"""
|
|
source_list = [info.src for info in sinfo_list]
|
|
com_dir = get_common_dir(source_list)
|
|
return com_dir
|
|
|
|
@staticmethod
|
|
def get_including_set(sinfo_list):
|
|
"""Get including set from list of source code"""
|
|
include_set = set()
|
|
for sinfo in sinfo_list:
|
|
for inc in sinfo.include:
|
|
if inc != "":
|
|
include_set.add(inc)
|
|
return include_set
|
|
|
|
|
|
class IdeProject(object):
|
|
"""Base IDE project class.
|
|
|
|
make_src_nodes(self, source, group = None, parent_node = None):
|
|
make_include(self, sources, parent_node = None):
|
|
make_output_dir(self, target):
|
|
These functions need to override
|
|
|
|
Attributes:
|
|
root: root of its etree
|
|
ewp_ET: Its etree
|
|
settings: specific IDE setting
|
|
proj_dir: base directory, files path are often relative from this
|
|
rel_base: base directory symbol,
|
|
"""
|
|
|
|
def __init__(self, proj, settings, out_dir=None, use_gcc=False):
|
|
self.proj_dir = os.path.split(proj)[0]
|
|
if (out_dir is not None) and os.path.exists(out_dir):
|
|
self.proj_dir = out_dir
|
|
|
|
self.root = None
|
|
self.ewp_ET = None
|
|
self.settings = {}
|
|
self.rel_base = ""
|
|
if settings is not None:
|
|
self.settings = settings
|
|
self.use_gcc = use_gcc
|
|
|
|
self.rel_base = self.settings.get("rel_base", "")
|
|
self.saved_nodes = {} # some inside nodes need to save before clear all sources
|
|
try:
|
|
# Read template project xml structure
|
|
parser = ET.XMLParser(
|
|
remove_blank_text=True
|
|
) # use parser to make pretty print works
|
|
self.ewp_ET = ET.parse(proj, parser)
|
|
self.root = self.ewp_ET.getroot()
|
|
|
|
# Save some template nodes before clear
|
|
for tag in self.settings.get("saved_tags", []):
|
|
n = self.root.find(tag)
|
|
self.saved_nodes[tag] = deepcopy(n)
|
|
|
|
self.clear_src_nodes() # Clear all source node in template file
|
|
except Exception as e:
|
|
print("ERR: {0}".format(str(e)))
|
|
raise Exception("Can't init IdeProject object")
|
|
|
|
def get_relpath(self, dest):
|
|
"""Get relative path from its base directory"""
|
|
return self.rel_base + os.path.relpath(dest, self.proj_dir)
|
|
|
|
def get_output_dir(self):
|
|
""""""
|
|
out_paths = self.settings.get("output_path", {})
|
|
return out_paths.get("exe", "")
|
|
|
|
def get_obj_dir(self):
|
|
""""""
|
|
out_paths = self.settings.get("output_path", {})
|
|
return out_paths.get("obj", "")
|
|
|
|
def get_lst_dir(self):
|
|
""""""
|
|
out_paths = self.settings.get("output_path", {})
|
|
return out_paths.get("lst", "")
|
|
|
|
def write(self, ofile):
|
|
"""Write etree to file"""
|
|
self.ewp_ET.write(
|
|
ofile, pretty_print=True, xml_declaration=True, encoding="UTF-8"
|
|
)
|
|
|
|
def remove_nodes(self, element, remove_list):
|
|
"""Delete nodes in list from the xlm tree
|
|
|
|
Args:
|
|
element: root node of etree
|
|
remove_list: tuple of all node that need to remove
|
|
|
|
Returns:
|
|
Remove nodes from xlm tree
|
|
Raises:
|
|
None
|
|
"""
|
|
try:
|
|
for node in remove_list:
|
|
p = element.find(node)
|
|
while p is not None:
|
|
c = p.getparent()
|
|
c.remove(p)
|
|
p = element.find(node)
|
|
except Exception as e:
|
|
print(str(e))
|
|
|
|
def clear_src_nodes(self):
|
|
"""Remove all predefined node in settings from its etree"""
|
|
self.remove_nodes(self.root, self.settings.get("cleared_nodes", []))
|
|
|
|
def make_nodes(self, parent_node, tags, *args):
|
|
"""Create node(s) by using tag convention
|
|
Return most inner parent nodes
|
|
"""
|
|
if parent_node is None:
|
|
parent_node = self.root
|
|
|
|
head = None
|
|
root = None
|
|
# print "Create tags: ", tags
|
|
if len(tags) == 3:
|
|
|
|
# Check root, create if not exist
|
|
root_tag = tags[0]
|
|
if root_tag != "":
|
|
root = parent_node.find(root_tag)
|
|
if root is None:
|
|
root = ET.SubElement(parent_node, root_tag.split("/")[-1])
|
|
else:
|
|
root = parent_node
|
|
|
|
p_node = root
|
|
|
|
# Create middle sub nodes
|
|
sub_tags = tags[1]
|
|
if len(sub_tags) > 0:
|
|
head = ET.Element(sub_tags[0])
|
|
p_node = head
|
|
for tag in sub_tags[1:]:
|
|
p_node = ET.SubElement(p_node, tag)
|
|
|
|
# Create leaf node with input text
|
|
for src_tag, text in zip(tags[2], args):
|
|
e = ET.SubElement(p_node, src_tag)
|
|
e.text = text
|
|
|
|
if head is not None:
|
|
root.append(head)
|
|
else:
|
|
raise Exception("Wrong tag convention")
|
|
return p_node
|
|
|
|
def make_group(self, parent_node, *args):
|
|
"""Create group of source/lib tags
|
|
Tags info are get from settings
|
|
Args:
|
|
parent_node :
|
|
*args : nodes' text
|
|
return:
|
|
Return group node
|
|
"""
|
|
tags = self.settings.get("group_tags", [])
|
|
return self.make_nodes(parent_node, tags, *args)
|
|
|
|
def make_file(self, parent_node, *args):
|
|
"""Create group of source/lib tags
|
|
Tags info are get from settings
|
|
"""
|
|
tags = self.settings.get("file_tags", [])
|
|
return self.make_nodes(parent_node, tags, *args)
|
|
|
|
def make_src_nodes(self, source, group=None, parent_node=None):
|
|
"""Create xlm nodes for list of source file
|
|
|
|
Args:
|
|
sources: list of SourceInfo
|
|
group : group name that contains all of these source
|
|
parent_node : etree fake root node
|
|
Returns:
|
|
|
|
"""
|
|
pass
|
|
|
|
def make_include(self, sources, parent_node=None):
|
|
"""Create including nodes from source info for project
|
|
|
|
Args:
|
|
sources: list of SourceInfo
|
|
parent_node: etree fake root node
|
|
Returns:
|
|
|
|
"""
|
|
pass
|
|
|
|
def make_output_dir(self, target):
|
|
"""Update output directory setting for project
|
|
|
|
Args:
|
|
target: project output target name
|
|
Returns:
|
|
|
|
"""
|
|
pass
|
|
|
|
def add_misc(self, mtype, misc=""):
|
|
pass
|
|
|
|
def add_define(self, dtype, symbols):
|
|
pass
|
|
|
|
def set_link_libs(self, lib_dir, libs):
|
|
pass
|
|
|
|
def set_mcu(self, mcu):
|
|
pass
|
|
|
|
def set_core(self, core):
|
|
pass
|
|
|
|
@staticmethod
|
|
def factory(objtype, xml_file, out_dir=None):
|
|
"""Factory to create obj by derived type"""
|
|
return objtype(xml_file, out_dir=out_dir)
|
|
|
|
|
|
class IARWorkspace(IdeProject):
|
|
"""IAR workspace class.
|
|
|
|
Depend on its settings only.
|
|
Use default add node from base to add sub library project
|
|
|
|
Attributes:
|
|
"""
|
|
|
|
def __init__(self, proj, out_dir=None):
|
|
super(IARWorkspace, self).__init__(proj, IAR_WSP_SETTINGS, out_dir)
|
|
|
|
|
|
class IARProject(IdeProject):
|
|
"""IAR project class.
|
|
|
|
Add some specific logics to create source, include and output setting
|
|
|
|
"""
|
|
|
|
def __init__(self, proj, settings=IAR_PRJ_SETTINGS, out_dir=None):
|
|
super(IARProject, self).__init__(proj, settings, out_dir)
|
|
|
|
def make_include(self, sources, parent_node=None):
|
|
"""Create including nodes from source info for project
|
|
IAR sample including nodes
|
|
<option>
|
|
<name>CCIncludePath2</name>
|
|
<state>$PROJ_DIR$\nuttx\include\</state>
|
|
</option>
|
|
Args:
|
|
sources: list of SourceInfo
|
|
parent_node: etree fake root node
|
|
Returns:
|
|
|
|
"""
|
|
if parent_node is None:
|
|
parent_node = self.root
|
|
|
|
include_set = SourceInfo.get_including_set(sources)
|
|
|
|
# Adding dir to user include node, tags is from setting
|
|
include_nodes = self.settings["include_pnodes"]
|
|
for path in include_nodes:
|
|
for p in parent_node.iterfind(path): # ex: ".//*[name='CCIncludePath2']"
|
|
# print(n.tag, n.text)
|
|
for inc in include_set:
|
|
state = ET.SubElement(p, self.settings["include_tag"])
|
|
|
|
# In cygwin, we need to convert windows path to relative
|
|
if sys.platform == "cygwin":
|
|
inc = subprocess.check_output(["cygpath", "-u", inc])
|
|
inc = inc[:-1] # remove /n
|
|
|
|
state.text = self.get_relpath(inc)
|
|
|
|
def make_src_nodes(self, sources, group=None, parent_node=None):
|
|
"""Create nodes for list of source file
|
|
|
|
Args:
|
|
sources: list of SourceInfo
|
|
group: group name that contains all of these source
|
|
parent_node: etree fake root node
|
|
Returns:
|
|
|
|
"""
|
|
if parent_node is None:
|
|
parent_node = self.root
|
|
|
|
source_list = [info.src for info in sources]
|
|
|
|
com_dir = get_common_dir(source_list)
|
|
com_dir_name = os.path.split(com_dir)[1]
|
|
|
|
if group is None:
|
|
group = com_dir_name
|
|
|
|
# Create group node to contain all source files
|
|
group_node = self.make_group(parent_node, group)
|
|
|
|
# Add source files to group as sub node
|
|
for src in source_list:
|
|
fname = self.get_relpath(src) # make ref path from $PROJ_DIR$ to file
|
|
|
|
ext_remap = self.settings.get("ext_remap", {})
|
|
for ext, replacement in ext_remap.items():
|
|
fname = re.sub(ext, replacement, fname)
|
|
|
|
self.make_file(group_node, fname)
|
|
|
|
def make_output_dir(self, target):
|
|
"""Update output directory setting for IAR project
|
|
|
|
Args:
|
|
target: project's target name
|
|
Returns:
|
|
|
|
"""
|
|
sub_dir = "$PROJ_FNAME$"
|
|
exe_path = self.get_output_dir()
|
|
lst_path = self.get_output_dir()
|
|
obj_path = self.get_output_dir()
|
|
dirs = (exe_path, obj_path, lst_path)
|
|
tags = ('.//*[name="ExePath"]', './/*[name="ObjPath"]', './/*[name="ListPath"]')
|
|
|
|
for path, tag in zip(dirs, tags):
|
|
if path != "":
|
|
p = self.root.findall(tag)
|
|
for n in p:
|
|
self.remove_nodes(n, ("state",))
|
|
e = ET.SubElement(n, "state")
|
|
e.text = sub_dir + "/" + path
|
|
|
|
|
|
class UVisionWorkspace(IdeProject):
|
|
"""uVision workspace class.
|
|
|
|
Depend on its settings only.
|
|
Use default add node from base to add sub library project
|
|
|
|
Attributes:
|
|
"""
|
|
|
|
def __init__(self, proj, out_dir=None):
|
|
super(UVisionWorkspace, self).__init__(proj, UVISION_WSP_SETTINGS, out_dir)
|
|
|
|
|
|
class UVisionProject(IdeProject):
|
|
"""uVision project class.
|
|
|
|
Add some specific logics to create source, include and output setting
|
|
|
|
"""
|
|
|
|
def __init__(
|
|
self, proj, settings=UVISION_ARMCC_PRJ_SETTINGS, out_dir=None, use_gcc=False
|
|
):
|
|
super(UVisionProject, self).__init__(proj, settings, out_dir, use_gcc)
|
|
self.use_gcc = use_gcc
|
|
|
|
def make_include(self, sources, parent_node=None):
|
|
"""Create including nodes from source info for uVision project
|
|
uVision sample including nodes:
|
|
<VariousControls>
|
|
<IncludePath>../../../../apps/examples/hello;../../../../apps/examples/nsh>
|
|
</VariousControls>
|
|
Args:
|
|
sources: list of SourceInfo
|
|
parent_node: etree fake root node
|
|
Returns:
|
|
|
|
"""
|
|
if parent_node is None:
|
|
parent_node = self.root
|
|
|
|
include_set = SourceInfo.get_including_set(sources)
|
|
|
|
incs = []
|
|
for inc in include_set:
|
|
# In cygwin, we need to convert windows path to relative
|
|
if sys.platform == "cygwin":
|
|
inc = subprocess.check_output(["cygpath", "-u", inc])
|
|
inc = inc[:-1] # remove /n
|
|
|
|
inc = self.get_relpath(inc)
|
|
incs.append(inc)
|
|
|
|
inc_text = ";".join(incs)
|
|
|
|
# Adding dir to user include node (both ASM & CC)
|
|
for n in parent_node.iterfind(self.settings["include_pnodes"]):
|
|
n.text = inc_text
|
|
|
|
def make_src_nodes(self, sources, group=None, parent_node=None):
|
|
"""Create nodes for list of source file
|
|
Sample uVision file:
|
|
<Groups>
|
|
<Group>
|
|
<GroupName>board</GroupName>
|
|
<Files>
|
|
<File>
|
|
<FileName>stm32_boot.c</FileName>
|
|
<FileType>1</FileType>
|
|
<FilePath>../../../arch/arm/src/board/stm32_boot.c</FilePath>
|
|
</File>
|
|
</Files>
|
|
</Group>
|
|
</Groups>
|
|
|
|
Args:
|
|
sources: list of SourceInfo
|
|
group: group name that contains all of these source
|
|
parent_node: etree fake root node
|
|
Returns:
|
|
|
|
"""
|
|
if parent_node is None:
|
|
parent_node = self.root
|
|
|
|
source_list = [info.src for info in sources]
|
|
|
|
com_dir = get_common_dir(source_list)
|
|
com_dir_name = os.path.split(com_dir)[1]
|
|
|
|
if group is None:
|
|
group = com_dir_name
|
|
|
|
# Create group node to contain all source files
|
|
group_node = self.make_group(parent_node, group) # return <Group> node
|
|
|
|
# Add source files to group as sub node
|
|
for src in source_list:
|
|
fname = self.get_relpath(src)
|
|
ext = os.path.splitext(fname)[1]
|
|
|
|
# get uVison FileType
|
|
uv_file_type = self.settings.get("uv_file_type", {})
|
|
ftype = uv_file_type.get(ext, "0")
|
|
|
|
# Translate source to new format/location if need
|
|
ext_remap = self.settings.get("ext_remap", {})
|
|
for find, replacement in ext_remap.items():
|
|
fname = re.sub(find, replacement, fname)
|
|
|
|
name = os.path.split(fname)[1]
|
|
file_node = self.make_file(group_node, name, ftype, fname)
|
|
|
|
# Make exception for .S file (treat as C source with D__ASSEMBLY__)
|
|
if (self.use_gcc) and (ext in ASM_EXTS):
|
|
asm_opt_node = self.saved_nodes.get(".//FileOption")
|
|
if asm_opt_node is not None:
|
|
file_node.append(deepcopy(asm_opt_node))
|
|
|
|
def make_output_dir(self, target):
|
|
"""Update output directory setting for IAR project
|
|
|
|
Args:
|
|
target: project's target name
|
|
Returns:
|
|
|
|
"""
|
|
|
|
exe_path = self.get_output_dir()
|
|
if exe_path != "":
|
|
p = self.root.find(".//OutputDirectory")
|
|
if p is not None:
|
|
p.text = "\\".join((".", target, exe_path, ""))
|
|
|
|
lst_path = self.get_lst_dir()
|
|
if lst_path != "":
|
|
p = self.root.find(".//ListingPath")
|
|
if p is not None:
|
|
p.text = "\\".join((".", target, lst_path, ""))
|
|
|
|
p = self.root.find(".//OutputName")
|
|
if p is not None:
|
|
p.text = re.sub(r"^lib(.*)$", r"\g<1>", target) # prevent liblibapps.a
|
|
|
|
def add_misc(self, mtype, misc=""):
|
|
misc_info = self.settings.get(mtype)
|
|
if misc_info is not None:
|
|
tag, default = misc_info
|
|
if misc == "":
|
|
misc = default
|
|
n = self.root.find(tag)
|
|
if n is not None:
|
|
m = n.find(".//MiscControls")
|
|
if m is not None:
|
|
m.text = (m.text or "") + " " + misc
|
|
|
|
def add_define(self, dtype, symbols):
|
|
def_info = self.settings.get(dtype)
|
|
if def_info is not None:
|
|
tag, default = def_info
|
|
n = self.root.find(tag)
|
|
if n is not None:
|
|
m = n.find(".//Define")
|
|
if m is not None:
|
|
m.text = (m.text or "") + " " + symbols
|
|
|
|
def set_link_libs(self, libs, lib_dir=".\\lib"):
|
|
if self.use_gcc:
|
|
# need to add static lib in group so that linker does not throw errors
|
|
# http://eli.thegreenplace.net/2013/07/09/library-order-in-static-linking
|
|
mist_text = " -Wl,--start-group"
|
|
|
|
for sinfo in libs:
|
|
lib = os.path.split(sinfo.src)[1]
|
|
name, ext = os.path.splitext(lib)
|
|
mist_text += " -l" + name[3:] # remove lib in 'libAAA'
|
|
|
|
mist_text += " -Wl,--end-group"
|
|
|
|
misc_info = self.settings.get("ld_misc")
|
|
if misc_info is not None:
|
|
tag, default = misc_info
|
|
n = self.root.find(tag)
|
|
if n is not None:
|
|
m = n.find(".//Misc")
|
|
if m is not None:
|
|
m.text += mist_text
|
|
|
|
m = n.find(".//IncludeDir")
|
|
if m is not None:
|
|
m.text = lib_dir
|
|
|
|
def set_mcu(self, mcu):
|
|
# TODO:
|
|
pass
|
|
|
|
def set_core(self, core):
|
|
# TODO:
|
|
pass
|
|
|
|
|
|
class UVisionARMCCProject(UVisionProject):
|
|
"""uVision for ARMCC project class.
|
|
|
|
Add some specific logics to create source, include and output setting
|
|
|
|
"""
|
|
|
|
def __init__(self, proj, out_dir=None):
|
|
super(UVisionARMCCProject, self).__init__(
|
|
proj, UVISION_ARMCC_PRJ_SETTINGS, out_dir
|
|
)
|
|
|
|
|
|
class UVisionGCCProject(UVisionProject):
|
|
"""uVision for GCC project class.
|
|
|
|
Add some specific logics to create source, include and output setting
|
|
|
|
"""
|
|
|
|
def __init__(
|
|
self, proj, settings=UVISION_GCC_PRJ_SETTINGS, out_dir=None, use_gcc=True
|
|
):
|
|
super(UVisionGCCProject, self).__init__(proj, settings, out_dir, use_gcc)
|
|
|
|
|
|
def get_project_structure(lines):
|
|
"""Get project structure from make log file.
|
|
|
|
Loop through make log to figure the project structure
|
|
|
|
Args:
|
|
lines: A list of line from make log file (make V=1)
|
|
|
|
Returns:
|
|
A dict mapping library/target to its source list
|
|
|
|
{
|
|
'libc.a': [
|
|
{'src':/mynuttx/nuttx/libc/string/lib_strcat.c', 'include' : ['.', 'others include', ], 'flags': ''},
|
|
{'src':/mynuttx/nuttx/libc/string/lib_memcpy.c', 'include' : ['.', 'others include', ], 'flags': ''},
|
|
|
|
],
|
|
}
|
|
Source list is in full path form
|
|
Raises:
|
|
An error occurred when can't parse lib/target name
|
|
"""
|
|
|
|
group_dict = {}
|
|
src_list = []
|
|
make_path = ""
|
|
src_path = ""
|
|
ar_cmd = ""
|
|
cc_cmd = ""
|
|
|
|
for line in lines:
|
|
|
|
_lp = line[:PREFIX_LEN]
|
|
|
|
if _lp in COMPILE_PREFIX_LIST:
|
|
src_path = os.path.join(make_path, line[PREFIX_LEN:].strip())
|
|
cc_cmd = line.strip()
|
|
|
|
elif _lp in LIB_PREFIX_LIST:
|
|
ar_cmd = line.strip()
|
|
|
|
elif _lp in LINK_PREFIX_LIST:
|
|
match = re.search(_lp + r"(\w+)", line)
|
|
if match:
|
|
target = match.group(1)
|
|
if target not in group_dict:
|
|
group_dict[target] = []
|
|
|
|
for src in src_list:
|
|
group_dict[target].append(src)
|
|
|
|
elif MAKE_ENTER_DIR in line: # Get current make directory
|
|
match = re.search(r"'(.+)'\n$", line)
|
|
if match:
|
|
make_path = match.group(1)
|
|
elif cc_cmd != "": # Get include dirs and flags
|
|
incs = [make_path]
|
|
match = re.findall(r'(-I|-isystem) "(.+?)"', line)
|
|
if match:
|
|
incs += [p[1] for p in match]
|
|
|
|
# TODO: parse and other compile flags
|
|
|
|
src_info = SourceInfo(src_path, incs)
|
|
src_list.append(src_info)
|
|
|
|
cc_cmd = ""
|
|
src_path = ""
|
|
elif ar_cmd != "": # put all compiled files to library source list
|
|
match = re.search(r"(\w+?\.a)", line)
|
|
if match:
|
|
lib_name = match.group(1) # get library name
|
|
if lib_name not in group_dict:
|
|
group_dict[lib_name] = [] # create empty source info list
|
|
|
|
lib_objs = re.findall(
|
|
r"(\w+?)\.o", line
|
|
) # Get all obj name in libs (without ext)
|
|
# print("OBJ in .a: ", lib_objs)
|
|
remain_src_list = []
|
|
for sinfo in src_list:
|
|
obj = os.path.basename(sinfo.src)
|
|
obj = os.path.splitext(obj)[
|
|
0
|
|
] # Get the obj name (without ext) from source file name
|
|
|
|
# print("OBJ from file: ", obj)
|
|
if obj in lib_objs: # make sure the lib include this obj
|
|
group_dict[lib_name].append(sinfo)
|
|
# print('Put' + sinfo.src + "to lib: " + lib_name)
|
|
else:
|
|
remain_src_list.append(sinfo)
|
|
# print('Remain' + sinfo.src + " not in lib: " + lib_name)
|
|
|
|
src_list = remain_src_list
|
|
ar_cmd = ""
|
|
|
|
else:
|
|
raise AssertionError("Can't parse lib name ", line)
|
|
return group_dict
|
|
|
|
|
|
def dump_project_struct(project_structure):
|
|
"""Dump project structure
|
|
|
|
Print project structure
|
|
|
|
Args:
|
|
A dict mapping library/target to its source list
|
|
|
|
{
|
|
'libc.a': [
|
|
{'src':/mynuttx/nuttx/libc/string/lib_strcat.c', 'include' : ['.', 'others include', ], 'flags': ''},
|
|
{'src':/mynuttx/nuttx/libc/string/lib_memcpy.c', 'include' : ['.', 'others include', ], 'flags': ''},
|
|
|
|
],
|
|
}
|
|
|
|
Returns:
|
|
|
|
Raises:
|
|
None
|
|
"""
|
|
|
|
for lib, sinfo_list in project_structure.items():
|
|
print(lib)
|
|
for sinfo in sinfo_list:
|
|
print("\t" + sinfo.src)
|
|
|
|
|
|
IAR_EXPORT = {
|
|
"main": {"t": IARProject, "file": "template_nuttx_main.ewp"},
|
|
"lib": {"t": IARProject, "file": "template_nuttx_lib.ewp"},
|
|
"workspace": {"t": IARWorkspace, "file": "template_nuttx.eww"},
|
|
}
|
|
|
|
UVISION_ARMCC_EXPORT = {
|
|
"main": {"t": UVisionProject, "file": "template_nuttx_main.uvproj"},
|
|
"lib": {"t": UVisionProject, "file": "template_nuttx_lib.uvproj"},
|
|
"workspace": {"t": UVisionWorkspace, "file": "template_nuttx.uvmpw"},
|
|
}
|
|
|
|
UVISION_GCC_EXPORT = {
|
|
"main": {"t": UVisionGCCProject, "file": "template_nuttx_main.uvproj"},
|
|
"lib": {"t": UVisionGCCProject, "file": "template_nuttx_lib.uvproj"},
|
|
"workspace": {"t": UVisionWorkspace, "file": "template_nuttx.uvmpw"},
|
|
}
|
|
|
|
IDE_CONFIG_DICT = {
|
|
IAR: IAR_EXPORT,
|
|
UVISION_GCC: UVISION_GCC_EXPORT,
|
|
UVISION_ARMCC: UVISION_ARMCC_EXPORT,
|
|
}
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
parser = argparse.ArgumentParser(version="1.1")
|
|
|
|
parser.add_argument(
|
|
"build_log", help="Log file from make V=1", type=argparse.FileType("rt")
|
|
)
|
|
|
|
parser.add_argument(
|
|
"ide",
|
|
choices=[IAR, UVISION_GCC, UVISION_ARMCC],
|
|
help="The target IDE: iar, uvision_gcc, (uvision_armcc is experimental)",
|
|
)
|
|
|
|
parser.add_argument(
|
|
"template_dir", help="Directory that contains IDEs template projects"
|
|
)
|
|
|
|
parser.add_argument(
|
|
"-o", "--output", action="store", dest="out_dir", help="Output directory"
|
|
)
|
|
|
|
parser.add_argument(
|
|
"-d",
|
|
"--dump",
|
|
action="store_true",
|
|
dest="dump",
|
|
default=False,
|
|
help="Dump project structure tree",
|
|
)
|
|
|
|
options = parser.parse_args()
|
|
|
|
fmake_log = options.build_log
|
|
|
|
# read log file
|
|
lines = fmake_log.readlines()
|
|
fmake_log.close()
|
|
|
|
# get project structure
|
|
project = get_project_structure(lines)
|
|
|
|
if options.dump:
|
|
dump_project_struct(project)
|
|
|
|
templ_dir = options.template_dir
|
|
if not os.path.exists(templ_dir):
|
|
print(templ_dir + " does not exist")
|
|
exit(1)
|
|
|
|
prj_dir = templ_dir
|
|
if options.out_dir is not None:
|
|
prj_dir = os.path.abspath(options.out_dir)
|
|
|
|
try:
|
|
if not os.path.exists(prj_dir):
|
|
os.makedirs(prj_dir)
|
|
except Exception as e:
|
|
print("ERR: {0}".format(str(e)))
|
|
exit(1)
|
|
|
|
ide_config = IDE_CONFIG_DICT[options.ide]
|
|
xml_file = os.path.join(templ_dir, ide_config["workspace"]["file"])
|
|
ws_ext = os.path.splitext(xml_file)[1]
|
|
ws = IdeProject.factory(ide_config["workspace"]["t"], xml_file, prj_dir)
|
|
|
|
target = {"libs": [], "sources": []}
|
|
|
|
# Create nuttx iar library projects
|
|
for lib, group_src_list in project.items():
|
|
lib_name, lib_ext = os.path.splitext(lib)
|
|
if len(group_src_list) < 1:
|
|
print(lib_name, group_src_list)
|
|
elif lib_ext in LIB_EXTS:
|
|
xml_file = os.path.join(templ_dir, ide_config["lib"]["file"])
|
|
lib_prj = IdeProject.factory(ide_config["lib"]["t"], xml_file, prj_dir)
|
|
|
|
# print lib_name, group_src_list
|
|
lib_prj.make_src_nodes(group_src_list)
|
|
lib_prj.make_include(group_src_list)
|
|
lib_prj.make_output_dir(lib_name)
|
|
if lib_name == "libxx":
|
|
lib_prj.add_misc("cxx_misc")
|
|
else:
|
|
lib_prj.add_misc("c_misc")
|
|
|
|
# save main xml project to file
|
|
xml_ext = os.path.splitext(xml_file)[1]
|
|
prj_fname = os.path.join(prj_dir, lib_name + xml_ext)
|
|
lib_prj.write(prj_fname)
|
|
print("Exported " + prj_fname)
|
|
|
|
# Add library project to workspace
|
|
ws.make_file(ws.root, ws.get_relpath(prj_fname))
|
|
|
|
# Store output library file to ref from main project later
|
|
exe_dir = lib_prj.get_output_dir()
|
|
lib_fname = os.path.join(prj_dir, lib_name, exe_dir, lib)
|
|
target["libs"].append(SourceInfo(lib_fname))
|
|
else:
|
|
# Save name and source list for main project
|
|
target["name"] = lib
|
|
target["sources"] = group_src_list
|
|
|
|
# Create nuttx main project
|
|
xml_file = os.path.join(templ_dir, ide_config["main"]["file"])
|
|
main_prj = IdeProject.factory(ide_config["main"]["t"], xml_file, prj_dir)
|
|
|
|
main_prj.make_src_nodes(target["sources"])
|
|
main_prj.make_include(target["sources"])
|
|
|
|
if main_prj.use_gcc:
|
|
target["libs"].append(SourceInfo("libgcc.a")) # need add libgcc in ld
|
|
main_prj.set_link_libs(target["libs"])
|
|
else:
|
|
main_prj.make_src_nodes(target["libs"], group="libs")
|
|
|
|
main_prj.make_output_dir(target["name"])
|
|
|
|
# save main xml project to file
|
|
xml_ext = os.path.splitext(xml_file)[1]
|
|
prj_fname = os.path.join(prj_dir, target["name"] + "_main" + xml_ext)
|
|
main_prj.write(prj_fname)
|
|
print("Exported " + prj_fname)
|
|
|
|
# Add main project to workspace
|
|
ws.make_file(ws.root, ws.get_relpath(prj_fname))
|
|
|
|
# Write nuttx workspace
|
|
ww_fname = os.path.join(prj_dir, "nuttx" + ws_ext)
|
|
ws.write(ww_fname)
|
|
print("Exported " + ww_fname)
|