• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2021 The Pigweed Authors
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may not
4# use this file except in compliance with the License. You may obtain a copy of
5# the License at
6#
7#     https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations under
13# the License.
14"""Finds files for a given product."""
15
16from typing import Any, List, Optional, Set, Tuple
17
18import pathlib
19import re
20
21
22def parse_product_str(product_str: str) -> Tuple[str, Set[str], str]:
23    """Parses provided product string.
24
25    Args:
26        product_str: target supplied product string.
27
28    Returns:
29        (family, defines, name) where
30            `family` is the stm32 product family (ex. 'stm32l5xx')
31            `defines` is a list of potential product defines for the HAL.
32                There can be multiple because some products use a subfamily
33                for their define.
34                (ex. The only stm32f411 define is `STM32F411xE`)
35                The correct define can be validated using `select_define()`
36            `name` is the standardized name for the product string.
37                (ex. product_str = 'STM32F429', name = 'stm32f429xx')
38                This is the product name expected by the filename matching
39                functions (`match_filename()`, etc.)
40
41    Raises:
42        ValueError if the product string does not start with 'stm32' or specify
43            at least the chip model (9 chars).
44    """
45    product_name = product_str.lower()
46    if not product_name.startswith('stm32'):
47        raise ValueError("Product string must start with 'stm32'")
48
49    if len(product_name) < 9:
50        raise ValueError(
51            "Product string too short. Must specify at least the chip model.")
52
53    family = product_name[:7] + 'xx'
54    name = product_name
55
56    # Pad the full name with 'x' to reach the max expected length.
57    name = product_name.ljust(11, 'x')
58
59    # This generates all potential define suffixes for a given product name
60    # This is required because some boards have more specific defines
61    # ex. STM32F411xE, while most others are generic, ex. STM32F439xx
62    # So if the user specifies `stm32f207zgt6u`, this should generate the
63    # following as potential defines
64    #  STM32F207xx, STM32F207Zx, STM32F207xG, STM32F207ZG
65    define_suffixes = ['xx']
66    if name[9] != 'x':
67        define_suffixes.append(name[9].upper() + 'x')
68    if name[10] != 'x':
69        define_suffixes.append('x' + name[10].upper())
70    if name[9] != 'x' and name[10] != 'x':
71        define_suffixes.append(name[9:11].upper())
72
73    defines = set(map(lambda x: product_name[:9].upper() + x, define_suffixes))
74    return (family, defines, name)
75
76
77def select_define(defines: Set[str], family_header: str) -> str:
78    """Selects valid define from set of potential defines.
79
80    Looks for the defines in the family header to pick the correct one.
81
82    Args:
83        defines: set of defines provided by `parse_product_str`
84        family_header: `{family}.h` read into a string
85
86    Returns:
87        A single valid define
88
89    Raises:
90        ValueError if exactly one define is not found.
91    """
92    valid_defines = list(
93        filter(
94            lambda x: f'defined({x})' in family_header or f'defined ({x})' in
95            family_header, defines))
96
97    if len(valid_defines) != 1:
98        raise ValueError("Unable to select a valid define")
99
100    return valid_defines[0]
101
102
103def match_filename(product_name: str, filename: str):
104    """Matches linker and startup filenames with product name.
105
106    Args:
107        product_name: the name standardized by `parse_product_str`
108        filename: a linker or startup filename
109
110    Returns:
111        True if the filename could be associated with the product.
112        False otherwise.
113    """
114    stm32_parts = list(
115        filter(lambda x: x.startswith('stm32'),
116               re.split(r'\.|_', filename.lower())))
117
118    if len(stm32_parts) != 1:
119        return False
120
121    pattern = stm32_parts[0].replace('x', '.')
122
123    return re.match(pattern, product_name) is not None
124
125
126def find_linker_files(
127    product_name: str, files: List[str], stm32cube_path: pathlib.Path
128) -> Tuple[Optional[pathlib.Path], Optional[pathlib.Path]]:
129    """Finds linker file for the given product.
130
131    This searches `files` for linker scripts by name.
132
133    Args:
134        product_name: the name standardized by `parse_product_str`
135        files: list of file paths
136        stm32cube_path: the root path that `files` entries are relative to
137
138    Returns:
139        (gcc_linker, iar_linker) where gcc_linker / iar_linker are paths to a
140            linker file or None
141
142    Raises:
143        ValueError if `product_name` matches with no linker files, or with
144            multiple .ld/.icf files.
145    """
146    linker_files = list(
147        filter(
148            lambda x:
149            (x.endswith('.ld') or x.endswith('.icf')) and '_flash.' in x.lower(
150            ), files))
151    matching_linker_files = list(
152        filter(lambda x: match_filename(product_name,
153                                        pathlib.Path(x).name), linker_files))
154
155    matching_ld_files = list(
156        filter(lambda x: x.endswith('.ld'), matching_linker_files))
157    matching_icf_files = list(
158        filter(lambda x: x.endswith('.icf'), matching_linker_files))
159
160    if len(matching_ld_files) > 1 or len(matching_icf_files) > 1:
161        raise ValueError(
162            f'Too many linker file matches for {product_name}.' +
163            ' Provide a more specific product string or your own linker script'
164        )
165    if not matching_ld_files and not matching_icf_files:
166        raise ValueError(f'No linker script matching {product_name} found')
167
168    return (stm32cube_path /
169            matching_ld_files[0] if matching_ld_files else None,
170            stm32cube_path /
171            matching_icf_files[0] if matching_icf_files else None)
172
173
174def find_startup_file(product_name: str, files: List[str],
175                      stm32cube_path: pathlib.Path) -> pathlib.Path:
176    """Finds startup file for the given product.
177
178    Searches for gcc startup files.
179
180    Args:
181        product_name: the name standardized by `parse_product_str`
182        files: list of file paths
183        stm32cube_path: the root path that `files` entries are relative to
184
185    Returns:
186        Path to matching startup file
187
188    Raises:
189        ValueError if no / > 1 matching startup files found.
190    """
191    # ST provides startup files for gcc, iar, and arm compilers. They have the
192    # same filenames, so this looks for a 'gcc' folder in the path.
193    matching_startup_files = list(
194        filter(
195            lambda f: '/gcc/' in f and f.endswith('.s') and match_filename(
196                product_name, f), files))
197
198    if not matching_startup_files:
199        raise ValueError(f'No matching startup file found for {product_name}')
200    if len(matching_startup_files) == 1:
201        return stm32cube_path / matching_startup_files[0]
202
203    raise ValueError(
204        f'Multiple matching startup files found for {product_name}')
205
206
207_INCLUDE_DIRS = [
208    'hal_driver/Inc',
209    'hal_driver/Inc/Legacy',
210    'cmsis_device/Include',
211    'cmsis_core/Include',
212    'cmsis_core/DSP/Include',
213]
214
215
216def get_include_dirs(stm32cube_path: pathlib.Path) -> List[pathlib.Path]:
217    """Get HAL include directories."""
218    return list(map(lambda f: stm32cube_path / f, _INCLUDE_DIRS))
219
220
221def get_sources_and_headers(
222        files: List[str],
223        stm32cube_path: pathlib.Path) -> Tuple[List[str], List[str]]:
224    """Gets list of all sources and headers needed to build the stm32cube hal.
225
226    Args:
227        files: list of file paths
228        stm32cube_path: the root path that `files` entries are relative to
229
230    Returns:
231        (sources, headers) where
232            `sources` is a list of absolute paths to all core (non-template)
233                sources needed for the hal
234            `headers` is a list of absolute paths to all needed headers
235    """
236    source_files = filter(
237        lambda f: f.startswith('hal_driver/Src') and f.endswith('.c') and
238        'template' not in f, files)
239
240    header_files = filter(
241        lambda f: (any(f.startswith(dir)
242                       for dir in _INCLUDE_DIRS)) and f.endswith('.h'), files)
243
244    rebase_path = lambda f: str(stm32cube_path / f)
245    return list(map(rebase_path,
246                    source_files)), list(map(rebase_path, header_files))
247
248
249def parse_files_txt(stm32cube_path: pathlib.Path) -> List[str]:
250    """Reads files.txt into list."""
251    with open(stm32cube_path / 'files.txt', 'r') as files:
252        return list(
253            filter(lambda x: not x.startswith('#'),
254                   map(lambda f: f.strip(), files.readlines())))
255
256
257def _gn_str_out(name: str, val: Any):
258    """Outputs scoped string in GN format."""
259    print(f'{name} = "{val}"')
260
261
262def _gn_list_str_out(name: str, val: List[Any]):
263    """Outputs list of strings in GN format with correct escaping."""
264    list_str = ','.join('"' + str(x).replace('"', r'\"').replace('$', r'\$') +
265                        '"' for x in val)
266    print(f'{name} = [{list_str}]')
267
268
269def find_files(stm32cube_path: pathlib.Path, product_str: str, init: bool):
270    """Generates and outputs the required GN args for the build."""
271    file_list = parse_files_txt(stm32cube_path)
272
273    include_dirs = get_include_dirs(stm32cube_path)
274    sources, headers = get_sources_and_headers(file_list, stm32cube_path)
275    (family, defines, name) = parse_product_str(product_str)
276
277    family_header_path = list(
278        filter(lambda p: p.endswith(f'/{family}.h'), headers))[0]
279
280    with open(family_header_path, 'rb') as family_header:
281        family_header_str = family_header.read().decode('utf-8',
282                                                        errors='ignore')
283
284    define = select_define(defines, family_header_str)
285
286    _gn_str_out('family', family)
287    _gn_str_out('product_define', define)
288    _gn_list_str_out('sources', sources)
289    _gn_list_str_out('headers', headers)
290    _gn_list_str_out('include_dirs', include_dirs)
291
292    if init:
293        startup_file_path = find_startup_file(name, file_list, stm32cube_path)
294        gcc_linker, iar_linker = find_linker_files(name, file_list,
295                                                   stm32cube_path)
296
297        _gn_str_out('startup', startup_file_path)
298        _gn_str_out('gcc_linker', gcc_linker if gcc_linker else '')
299        _gn_str_out('iar_linker', iar_linker if iar_linker else '')
300