mbed-os/tools/resources/__init__.py

534 lines
17 KiB
Python
Raw Normal View History

# mbed SDK
# Copyright (c) 2011-2013 ARM Limited
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
# The scanning rules and Resources object.
A project in Mbed OS contains metadata in the file system as directory names.
These directory names adhere to a set of rules referred to as scanning rules.
The following are the English version of the scanning rules:
Directory names starting with "TEST_", "TARGET_", "TOOLCHAIN_" and "FEATURE_"
are excluded from a build unless one of the following is true:
* The suffix after "TARGET_" is a target label (see target.labels).
* The suffix after "TOOLCHAIN_" is a toolchain label, defined by the
inheritance hierarchy of the toolchain class.
* The suffix after "FEATURE_" is a member of `target.features`.
"""
from __future__ import print_function, division, absolute_import
import fnmatch
import re
from collections import namedtuple, defaultdict
from copy import copy
from itertools import chain
2018-06-19 18:50:19 +00:00
from os import walk, sep
2018-04-24 19:47:33 +00:00
from os.path import (join, splitext, dirname, relpath, basename, split, normcase,
abspath, exists)
from .ignore import MbedIgnoreSet, IGNORE_FILENAME
# Support legacy build conventions: the original mbed build system did not have
# standard labels for the "TARGET_" and "TOOLCHAIN_" specific directories, but
# had the knowledge of a list of these directories to be ignored.
LEGACY_IGNORE_DIRS = set([
2018-06-11 15:47:36 +00:00
# Legacy Targets
'LPC11U24',
'LPC1768',
'LPC2368',
'LPC4088',
'LPC812',
'KL25Z',
# Legacy Toolchains
'ARM',
'uARM',
'IAR',
'GCC_ARM',
'GCC_CS',
'GCC_CR',
'GCC_CW',
'GCC_CW_EWL',
'GCC_CW_NEWLIB',
'ARMC6',
# Tests, here for simplicity
'TESTS',
2018-01-23 10:26:14 +00:00
'TEST_APPS',
])
LEGACY_TOOLCHAIN_NAMES = {
2018-04-24 19:47:33 +00:00
'ARM_STD':'ARM',
'ARM_MICRO': 'uARM',
'GCC_ARM': 'GCC_ARM',
'GCC_CR': 'GCC_CR',
'IAR': 'IAR',
'ARMC6': 'ARMC6',
}
FileRef = namedtuple("FileRef", "name path")
class FileType(object):
C_SRC = "c"
CPP_SRC = "c++"
ASM_SRC = "s"
HEADER = "header"
INC_DIR = "inc"
LIB_DIR = "libdir"
LIB = "lib"
OBJECT = "o"
HEX = "hex"
BIN = "bin"
JSON = "json"
LD_SCRIPT = "ld"
LIB_REF = "libref"
BLD_REF = "bldref"
REPO_DIR = "repodir"
def __init__(self):
raise NotImplemented
2018-04-24 19:05:46 +00:00
class Resources(object):
ALL_FILE_TYPES = [
FileType.C_SRC,
FileType.CPP_SRC,
FileType.ASM_SRC,
FileType.HEADER,
FileType.INC_DIR,
FileType.LIB_DIR,
FileType.LIB,
FileType.OBJECT,
FileType.HEX,
FileType.BIN,
FileType.JSON,
FileType.LD_SCRIPT,
FileType.LIB_REF,
FileType.BLD_REF,
FileType.REPO_DIR,
]
def __init__(self, notify, collect_ignores=False):
# publicly accessible things
self.ignored_dirs = []
# Pre-mbed 2.0 ignore dirs
self._legacy_ignore_dirs = (LEGACY_IGNORE_DIRS)
# Primate parameters
self._notify = notify
self._collect_ignores = collect_ignores
# Storage for file references, indexed by file type
2018-06-19 18:50:19 +00:00
self._file_refs = defaultdict(set)
# Incremental scan related
self._label_paths = []
2018-07-30 14:17:53 +00:00
self._labels = {
"TARGET": [], "TOOLCHAIN": [], "FEATURE": [], "COMPONENT": []
}
self._prefixed_labels = set()
# Path seperator style (defaults to OS-specific seperator)
self._sep = sep
self._ignoreset = MbedIgnoreSet()
def ignore_dir(self, directory):
if self._collect_ignores:
self.ignored_dirs.append(directory)
def _collect_duplicates(self, dupe_dict, dupe_headers):
for filename in self.s_sources + self.c_sources + self.cpp_sources:
objname, _ = splitext(basename(filename))
dupe_dict.setdefault(objname, set())
dupe_dict[objname] |= set([filename])
for filename in self.headers:
headername = basename(filename)
dupe_headers.setdefault(headername, set())
dupe_headers[headername] |= set([headername])
return dupe_dict, dupe_headers
2018-06-11 18:27:05 +00:00
def detect_duplicates(self):
"""Detect all potential ambiguities in filenames and report them with
a toolchain notification
"""
count = 0
dupe_dict, dupe_headers = self._collect_duplicates(dict(), dict())
for objname, filenames in dupe_dict.items():
if len(filenames) > 1:
count+=1
self._notify.tool_error(
"Object file %s.o is not unique! It could be made from: %s"\
% (objname, " ".join(filenames)))
for headername, locations in dupe_headers.items():
if len(locations) > 1:
count+=1
self._notify.tool_error(
"Header file %s is not unique! It could be: %s" %\
(headername, " ".join(locations)))
return count
def win_to_unix(self):
self._sep = "/"
if self._sep != sep:
for file_type in self.ALL_FILE_TYPES:
v = [f._replace(name=f.name.replace(sep, self._sep)) for
f in self.get_file_refs(file_type)]
self._file_refs[file_type] = v
def __str__(self):
s = []
for (label, file_type) in (
('Include Directories', FileType.INC_DIR),
('Headers', FileType.HEADER),
('Assembly sources', FileType.ASM_SRC),
('C sources', FileType.C_SRC),
('C++ sources', FileType.CPP_SRC),
('Library directories', FileType.LIB_DIR),
('Objects', FileType.OBJECT),
('Libraries', FileType.LIB),
('Hex files', FileType.HEX),
('Bin files', FileType.BIN),
('Linker script', FileType.LD_SCRIPT)
):
resources = self.get_file_refs(file_type)
if resources:
s.append('%s:\n ' % label + '\n '.join(
"%s -> %s" % (name, path) for name, path in resources))
return '\n'.join(s)
def _add_labels(self, prefix, labels):
self._labels[prefix].extend(labels)
self._prefixed_labels |= set("%s_%s" % (prefix, label) for label in labels)
for path, base_path, into_path in self._label_paths:
if basename(path) in self._prefixed_labels:
self.add_directory(path, base_path, into_path)
self._label_paths = [(p, b, i) for p, b, i in self._label_paths
if basename(p) not in self._prefixed_labels]
def add_target_labels(self, target):
self._add_labels("TARGET", target.labels)
2018-08-06 14:39:24 +00:00
self._add_labels("COMPONENT", target.components)
self.add_features(target.features)
def add_features(self, features):
self._add_labels("FEATURE", features)
def add_toolchain_labels(self, toolchain):
for prefix, value in toolchain.get_labels().items():
self._add_labels(prefix, value)
self._legacy_ignore_dirs -= set(
2018-04-24 19:47:33 +00:00
[toolchain.target.name, LEGACY_TOOLCHAIN_NAMES[toolchain.name]])
def add_ignore_patterns(self, root, base_path, patterns):
real_base = relpath(root, base_path)
self._ignoreset.add_ignore_patterns(real_base, patterns)
def _not_current_label(self, dirname, label_type):
return (dirname.startswith(label_type + "_") and
dirname[len(label_type) + 1:] not in self._labels[label_type])
def add_file_ref(self, file_type, file_name, file_path):
if sep != self._sep:
ref = FileRef(file_name.replace(sep, self._sep), file_path)
2018-06-13 19:19:09 +00:00
else:
ref = FileRef(file_name, file_path)
2018-06-19 18:50:19 +00:00
self._file_refs[file_type].add(ref)
def get_file_refs(self, file_type):
"""Return a list of FileRef for every file of the given type"""
2018-06-19 18:50:19 +00:00
return list(self._file_refs[file_type])
def _all_parents(self, files):
2018-06-19 18:50:19 +00:00
for name in files:
components = name.split(self._sep)
start_at = 2 if components[0] in set(['', '.']) else 1
for index, directory in reversed(list(enumerate(components))[start_at:]):
if directory in self._prefixed_labels:
start_at = index + 1
break
2018-07-26 15:19:23 +00:00
for n in range(start_at, len(components)):
parent = self._sep.join(components[:n])
2018-06-19 18:50:19 +00:00
yield parent
def _get_from_refs(self, file_type, key):
if file_type is FileType.INC_DIR:
parents = set(self._all_parents(self._get_from_refs(
FileType.HEADER, key)))
parents.add(".")
else:
parents = set()
2018-06-28 15:43:40 +00:00
return sorted(
list(parents) + [key(f) for f in self.get_file_refs(file_type)]
)
2018-06-19 18:50:19 +00:00
def get_file_names(self, file_type):
2018-06-19 18:50:19 +00:00
return self._get_from_refs(file_type, lambda f: f.name)
2018-06-18 16:39:44 +00:00
def get_file_paths(self, file_type):
2018-06-19 18:50:19 +00:00
return self._get_from_refs(file_type, lambda f: f.path)
2018-06-18 16:39:44 +00:00
def add_files_to_type(self, file_type, files):
2018-06-13 19:19:09 +00:00
for f in files:
self.add_file_ref(file_type, f, f)
@property
def inc_dirs(self):
return self.get_file_names(FileType.INC_DIR)
@property
def headers(self):
return self.get_file_names(FileType.HEADER)
@property
def s_sources(self):
return self.get_file_names(FileType.ASM_SRC)
@property
def c_sources(self):
return self.get_file_names(FileType.C_SRC)
@property
def cpp_sources(self):
return self.get_file_names(FileType.CPP_SRC)
@property
def lib_dirs(self):
return self.get_file_names(FileType.LIB_DIR)
@property
def objects(self):
return self.get_file_names(FileType.OBJECT)
@property
def libraries(self):
return self.get_file_names(FileType.LIB)
@property
def lib_builds(self):
return self.get_file_names(FileType.BLD_REF)
@property
def lib_refs(self):
return self.get_file_names(FileType.LIB_REF)
@property
def linker_script(self):
options = self.get_file_names(FileType.LD_SCRIPT)
if options:
2018-07-19 18:44:19 +00:00
return options[0]
else:
return None
@property
def hex_files(self):
return self.get_file_names(FileType.HEX)
@property
def bin_files(self):
return self.get_file_names(FileType.BIN)
@property
def json_files(self):
return self.get_file_names(FileType.JSON)
def add_directory(
self,
path,
base_path=None,
into_path=None,
exclude_paths=None,
):
""" Scan a directory and include its resources in this resources obejct
Positional arguments:
path - the path to search for resources
Keyword arguments
base_path - If this is part of an incremental scan, include the origin
directory root of the scan here
into_path - Pretend that scanned files are within the specified
directory within a project instead of using their actual path
exclude_paths - A list of paths that are to be excluded from a build
"""
self._notify.progress("scan", abspath(path))
if base_path is None:
base_path = path
if into_path is None:
into_path = path
if self._collect_ignores and relpath(path, base_path) in self.ignored_dirs:
self.ignored_dirs.remove(path)
2018-06-11 16:04:51 +00:00
if exclude_paths:
self.add_ignore_patterns(
path, base_path, [join(e, "*") for e in exclude_paths])
for root, dirs, files in walk(path, followlinks=True):
# Check if folder contains .mbedignore
if IGNORE_FILENAME in files:
real_base = relpath(root, base_path)
self._ignoreset.add_mbedignore(
real_base, join(root, IGNORE_FILENAME))
root_path =join(relpath(root, base_path))
if self._ignoreset.is_ignored(join(root_path,"")):
self.ignore_dir(root_path)
dirs[:] = []
continue
for d in copy(dirs):
dir_path = join(root, d)
if d == '.hg' or d == '.git':
fake_path = join(into_path, relpath(dir_path, base_path))
self.add_file_ref(FileType.REPO_DIR, fake_path, dir_path)
if (any(self._not_current_label(d, t) for t
2018-07-31 15:37:07 +00:00
in self._labels.keys())):
self._label_paths.append((dir_path, base_path, into_path))
self.ignore_dir(relpath(dir_path, base_path))
dirs.remove(d)
elif (d.startswith('.') or d in self._legacy_ignore_dirs or
self._ignoreset.is_ignored(join(root_path, d, ""))):
self.ignore_dir(relpath(dir_path, base_path))
dirs.remove(d)
# Add root to include paths
root = root.rstrip("/")
for file in files:
file_path = join(root, file)
self._add_file(file_path, base_path, into_path)
_EXT = {
".c": FileType.C_SRC,
".cc": FileType.CPP_SRC,
".cpp": FileType.CPP_SRC,
".s": FileType.ASM_SRC,
".h": FileType.HEADER,
".hh": FileType.HEADER,
".hpp": FileType.HEADER,
".o": FileType.OBJECT,
".hex": FileType.HEX,
".bin": FileType.BIN,
".json": FileType.JSON,
".a": FileType.LIB,
".ar": FileType.LIB,
".sct": FileType.LD_SCRIPT,
".ld": FileType.LD_SCRIPT,
".icf": FileType.LD_SCRIPT,
".lib": FileType.LIB_REF,
".bld": FileType.BLD_REF,
}
2018-06-18 16:32:19 +00:00
_DIR_EXT = {
".a": FileType.LIB_DIR,
".ar": FileType.LIB_DIR,
}
def _add_file(self, file_path, base_path, into_path):
2018-06-11 16:04:51 +00:00
""" Add a single file into the resources object that was found by
scanning starting as base_path
"""
if (self._ignoreset.is_ignored(relpath(file_path, base_path)) or
basename(file_path).startswith(".")):
self.ignore_dir(relpath(file_path, base_path))
return
fake_path = join(into_path, relpath(file_path, base_path))
_, ext = splitext(file_path)
try:
file_type = self._EXT[ext.lower()]
self.add_file_ref(file_type, fake_path, file_path)
except KeyError:
pass
2018-06-18 16:32:19 +00:00
try:
dir_type = self._DIR_EXT[ext.lower()]
self.add_file_ref(dir_type, dirname(fake_path), dirname(file_path))
except KeyError:
pass
2018-04-24 19:05:46 +00:00
def scan_with_toolchain(self, src_paths, toolchain, dependencies_paths=None,
inc_dirs=None, exclude=True):
2018-04-24 19:05:46 +00:00
""" Scan resources using initialized toolcain
2018-04-24 19:05:46 +00:00
Positional arguments
src_paths - the paths to source directories
toolchain - valid toolchain object
Keyword arguments
2018-04-24 19:05:46 +00:00
dependencies_paths - dependency paths that we should scan for include dirs
inc_dirs - additional include directories which should be added to
the scanner resources
exclude - Exclude the toolchain's build directory from the resources
2018-04-24 19:05:46 +00:00
"""
self.add_toolchain_labels(toolchain)
for path in src_paths:
2018-04-24 19:47:33 +00:00
if exists(path):
into_path = relpath(path).strip(".\\/")
2018-04-24 19:47:33 +00:00
if exclude:
self.add_directory(
path,
into_path=into_path,
exclude_paths=[toolchain.build_dir]
)
2018-04-24 19:47:33 +00:00
else:
self.add_directory(path, into_path=into_path)
2018-04-24 19:05:46 +00:00
# Scan dependency paths for include dirs
if dependencies_paths is not None:
2018-04-24 19:47:33 +00:00
toolchain.progress("dep", dependencies_paths)
for dep in dependencies_paths:
lib_self = self.__class__(self._notify, self._collect_ignores)\
2018-04-24 19:47:33 +00:00
.scan_with_toolchain([dep], toolchain)
2018-04-24 19:05:46 +00:00
self.inc_dirs.extend(lib_self.inc_dirs)
# Add additional include directories if passed
if inc_dirs:
if isinstance(inc_dirs, list):
self.inc_dirs.extend(inc_dirs)
else:
self.inc_dirs.append(inc_dirs)
# Load self into the config system which might expand/modify self
# based on config data
toolchain.config.load_resources(self)
# Set the toolchain's configuration data
toolchain.set_config_data(toolchain.config.get_config_data())
2018-04-24 19:05:46 +00:00
return self
def scan_with_config(self, src_paths, config):
if config.target:
self.add_target_labels(config.target)
for path in src_paths:
if exists(path):
self.add_directory(path)
config.load_resources(self)
return self