Fix for x86_64 build fail
[platform/upstream/connectedhomeip.git] / third_party / pigweed / repo / pw_arduino_build / py / pw_arduino_build / __main__.py
1 #!/usr/bin/env python3
2 # Copyright 2020 The Pigweed Authors
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 # use this file except in compliance with the License. You may obtain a copy of
6 # the License at
7 #
8 #     https://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 # License for the specific language governing permissions and limitations under
14 # the License.
15 """Command line interface for arduino_builder."""
16
17 import argparse
18 import json
19 import logging
20 import os
21 import pprint
22 import shlex
23 import subprocess
24 import sys
25 from collections import OrderedDict
26 from pathlib import Path
27 from typing import List
28
29 from pw_arduino_build import core_installer, log
30 from pw_arduino_build.builder import ArduinoBuilder
31 from pw_arduino_build.file_operations import decode_file_json
32
33 _LOG = logging.getLogger(__name__)
34
35 _pretty_print = pprint.PrettyPrinter(indent=1, width=120).pprint
36 _pretty_format = pprint.PrettyPrinter(indent=1, width=120).pformat
37
38
39 class MissingArduinoCore(Exception):
40     """Exception raised when an Arduino core can not be found."""
41
42
43 def list_boards_command(unused_args, builder):
44     # list-boards subcommand
45     # (does not need a selected board or default menu options)
46
47     # TODO(tonymd): Print this sorted with auto-ljust columns
48     longest_name_length = 0
49     for board_name, board_dict in builder.board.items():
50         if len(board_name) > longest_name_length:
51             longest_name_length = len(board_name)
52     longest_name_length += 2
53
54     print("Board Name".ljust(longest_name_length), "Description")
55     for board_name, board_dict in builder.board.items():
56         print(board_name.ljust(longest_name_length), board_dict['name'])
57     sys.exit(0)
58
59
60 def list_menu_options_command(args, builder):
61     # List all menu options for the selected board.
62     builder.select_board(args.board)
63
64     print("All Options")
65     all_options, all_column_widths = builder.get_menu_options()
66     separator = "-" * (all_column_widths[0] + all_column_widths[1] + 2)
67     print(separator)
68
69     for name, description in all_options:
70         print(name.ljust(all_column_widths[0] + 1), description)
71
72     print("\nDefault Options")
73     print(separator)
74
75     menu_options, unused_col_widths = builder.get_default_menu_options()
76     for name, description in menu_options:
77         print(name.ljust(all_column_widths[0] + 1), description)
78
79
80 def show_command_print_string_list(args, string_list: List[str]):
81     if string_list:
82         join_token = "\n" if args.delimit_with_newlines else " "
83         print(join_token.join(string_list))
84
85
86 def show_command_print_flag_string(args, flag_string):
87     if args.delimit_with_newlines:
88         flag_string_with_newlines = shlex.split(flag_string)
89         print("\n".join(flag_string_with_newlines))
90     else:
91         print(flag_string)
92
93
94 def subtract_flags(flag_list_a: List[str],
95                    flag_list_b: List[str]) -> List[str]:
96     """Given two sets of flags return flags in a that are not in b."""
97     flag_counts = OrderedDict()  # type: OrderedDict[str, int]
98     for flag in flag_list_a + flag_list_b:
99         flag_counts[flag] = flag_counts.get(flag, 0) + 1
100     return [flag for flag in flag_list_a if flag_counts.get(flag, 0) == 1]
101
102
103 def run_command_lines(args, command_lines: List[str]):
104     for command_line in command_lines:
105         if not args.quiet:
106             print(command_line)
107         # TODO(tonymd): Exit with sub command exit code.
108         command_line_args = shlex.split(command_line)
109         process = subprocess.run(command_line_args,
110                                  stdout=subprocess.PIPE,
111                                  stderr=subprocess.STDOUT)
112         if process.returncode != 0:
113             _LOG.error('Command failed with exit code %d.', process.returncode)
114             _LOG.error('Full command:')
115             _LOG.error('')
116             _LOG.error('  %s', command_line)
117             _LOG.error('')
118             _LOG.error('Process output:')
119             print(flush=True)
120             sys.stdout.buffer.write(process.stdout)
121             print(flush=True)
122             _LOG.error('')
123
124
125 def run_command(args, builder):
126     """Run sub command function.
127
128     Runs Arduino recipes.
129     """
130
131     if args.run_prebuilds:
132         run_command_lines(args, builder.get_prebuild_steps())
133
134     if args.run_link:
135         line = builder.get_link_line()
136         archive_file_path = args.run_link[0]  # pylint: disable=unused-variable
137         object_files = args.run_link[1:]
138         line = line.replace("{object_files}", " ".join(object_files), 1)
139         run_command_lines(args, [line])
140
141     if args.run_objcopy:
142         run_command_lines(args, builder.get_objcopy_steps())
143
144     if args.run_postbuilds:
145         run_command_lines(args, builder.get_postbuild_steps())
146
147     if args.run_upload_command:
148         command = builder.get_upload_line(args.run_upload_command,
149                                           args.serial_port)
150         run_command_lines(args, [command])
151
152
153 # pylint: disable=too-many-branches
154 def show_command(args, builder):
155     """Show sub command function.
156
157     Prints compiler info and flags.
158     """
159     if args.cc_binary:
160         print(builder.get_cc_binary())
161
162     elif args.cxx_binary:
163         print(builder.get_cxx_binary())
164
165     elif args.objcopy_binary:
166         print(builder.get_objcopy_binary())
167
168     elif args.ar_binary:
169         print(builder.get_ar_binary())
170
171     elif args.size_binary:
172         print(builder.get_size_binary())
173
174     elif args.c_compile:
175         print(builder.get_c_compile_line())
176
177     elif args.cpp_compile:
178         print(builder.get_cpp_compile_line())
179
180     elif args.link:
181         print(builder.get_link_line())
182
183     elif args.objcopy:
184         print(builder.get_objcopy(args.objcopy))
185
186     elif args.objcopy_flags:
187         objcopy_flags = builder.get_objcopy_flags(args.objcopy_flags)
188         show_command_print_flag_string(args, objcopy_flags)
189
190     elif args.c_flags:
191         cflags = builder.get_c_flags()
192         show_command_print_flag_string(args, cflags)
193
194     elif args.s_flags:
195         sflags = builder.get_s_flags()
196         show_command_print_flag_string(args, sflags)
197
198     elif args.s_only_flags:
199         s_only_flags = subtract_flags(shlex.split(builder.get_s_flags()),
200                                       shlex.split(builder.get_c_flags()))
201         show_command_print_flag_string(args, " ".join(s_only_flags))
202
203     elif args.cpp_flags:
204         cppflags = builder.get_cpp_flags()
205         show_command_print_flag_string(args, cppflags)
206
207     elif args.cpp_only_flags:
208         cpp_only_flags = subtract_flags(shlex.split(builder.get_cpp_flags()),
209                                         shlex.split(builder.get_c_flags()))
210         show_command_print_flag_string(args, " ".join(cpp_only_flags))
211
212     elif args.ld_flags:
213         ldflags = builder.get_ld_flags()
214         show_command_print_flag_string(args, ldflags)
215
216     elif args.ld_libs:
217         show_command_print_flag_string(args, builder.get_ld_libs())
218
219     elif args.ld_lib_names:
220         show_command_print_flag_string(args,
221                                        builder.get_ld_libs(name_only=True))
222
223     elif args.ar_flags:
224         ar_flags = builder.get_ar_flags()
225         show_command_print_flag_string(args, ar_flags)
226
227     elif args.core_path:
228         print(builder.get_core_path())
229
230     elif args.prebuilds:
231         show_command_print_string_list(args, builder.get_prebuild_steps())
232
233     elif args.postbuilds:
234         show_command_print_string_list(args, builder.get_postbuild_steps())
235
236     elif args.upload_command:
237         print(builder.get_upload_line(args.upload_command, args.serial_port))
238
239     elif args.upload_tools:
240         tools = builder.get_upload_tool_names()
241         for tool_name in tools:
242             print(tool_name)
243
244     elif args.library_include_dirs:
245         show_command_print_string_list(args, builder.library_include_dirs())
246
247     elif args.library_includes:
248         show_command_print_string_list(args, builder.library_includes())
249
250     elif args.library_c_files:
251         show_command_print_string_list(args, builder.library_c_files())
252
253     elif args.library_s_files:
254         show_command_print_string_list(args, builder.library_s_files())
255
256     elif args.library_cpp_files:
257         show_command_print_string_list(args, builder.library_cpp_files())
258
259     elif args.core_c_files:
260         show_command_print_string_list(args, builder.core_c_files())
261
262     elif args.core_s_files:
263         show_command_print_string_list(args, builder.core_s_files())
264
265     elif args.core_cpp_files:
266         show_command_print_string_list(args, builder.core_cpp_files())
267
268     elif args.variant_c_files:
269         vfiles = builder.variant_c_files()
270         if vfiles:
271             show_command_print_string_list(args, vfiles)
272
273     elif args.variant_s_files:
274         vfiles = builder.variant_s_files()
275         if vfiles:
276             show_command_print_string_list(args, vfiles)
277
278     elif args.variant_cpp_files:
279         vfiles = builder.variant_cpp_files()
280         if vfiles:
281             show_command_print_string_list(args, vfiles)
282
283
284 def add_common_parser_args(parser, serial_port, build_path, build_project_name,
285                            project_path, project_source_path):
286     """Add command line options common to the run and show commands."""
287     parser.add_argument(
288         "--serial-port",
289         default=serial_port,
290         help="Serial port for flashing. Default: '{}'".format(serial_port))
291     parser.add_argument(
292         "--build-path",
293         default=build_path,
294         help="Build directory. Default: '{}'".format(build_path))
295     parser.add_argument(
296         "--project-path",
297         default=project_path,
298         help="Project directory. Default: '{}'".format(project_path))
299     parser.add_argument(
300         "--project-source-path",
301         default=project_source_path,
302         help="Project directory. Default: '{}'".format(project_source_path))
303     parser.add_argument("--library-path",
304                         default=[],
305                         nargs="+",
306                         type=str,
307                         help="Path to Arduino Library directory.")
308     parser.add_argument(
309         "--build-project-name",
310         default=build_project_name,
311         help="Project name. Default: '{}'".format(build_project_name))
312     parser.add_argument("--board",
313                         required=True,
314                         help="Name of the board to use.")
315     # nargs="+" is one or more args, e.g:
316     #   --menu-options menu.usb.serialhid menu.speed.150
317     parser.add_argument(
318         "--menu-options",
319         nargs="+",
320         type=str,
321         metavar="menu.usb.serial",
322         help="Desired Arduino menu options. See the "
323         "'list-menu-options' subcommand for available options.")
324     parser.add_argument("--set-variable",
325                         action="append",
326                         metavar='some.variable=NEW_VALUE',
327                         help="Override an Arduino recipe variable. May be "
328                         "specified multiple times. For example: "
329                         "--set-variable 'serial.port.label=/dev/ttyACM0' "
330                         "--set-variable 'serial.port.protocol=Teensy'")
331
332
333 def check_for_missing_args(args):
334     if args.arduino_package_path is None:
335         raise MissingArduinoCore(
336             "Please specify the location of an Arduino core using "
337             "'--arduino-package-path' and '--arduino-package-name'.")
338
339
340 # TODO(tonymd): These defaults don't make sense anymore and should be removed.
341 def get_default_options():
342     defaults = {}
343     defaults["build_path"] = os.path.realpath(
344         os.path.expanduser(
345             os.path.expandvars(os.path.join(os.getcwd(), "build"))))
346     defaults["project_path"] = os.path.realpath(
347         os.path.expanduser(os.path.expandvars(os.getcwd())))
348     defaults["project_source_path"] = os.path.join(defaults["project_path"],
349                                                    "src")
350     defaults["build_project_name"] = os.path.basename(defaults["project_path"])
351     defaults["serial_port"] = "UNKNOWN"
352     return defaults
353
354
355 def load_config_file(args):
356     """Load a config file and merge with command line options.
357
358     Command line takes precedence over values loaded from a config file."""
359
360     if args.save_config and not args.config_file:
361         raise FileNotFoundError(
362             "'--save-config' requires the '--config-file' option")
363
364     if not args.config_file:
365         return
366
367     default_options = get_default_options()
368
369     commandline_options = {
370         # Global option
371         "arduino_package_path": args.arduino_package_path,
372         "arduino_package_name": args.arduino_package_name,
373         "compiler_path_override": args.compiler_path_override,
374         # These options may not exist unless show or run command
375         "build_path": getattr(args, "build_path", None),
376         "project_path": getattr(args, "project_path", None),
377         "project_source_path": getattr(args, "project_source_path", None),
378         "build_project_name": getattr(args, "build_project_name", None),
379         "board": getattr(args, "board", None),
380         "menu_options": getattr(args, "menu_options", None),
381     }
382
383     # Decode JSON config file.
384     json_file_options, config_file_path = decode_file_json(args.config_file)
385
386     # Merge config file with command line options.
387     merged_options = {}
388     for key, value in commandline_options.items():
389         # Use the command line specified option by default
390         merged_options[key] = value
391
392         # Is this option in the config file?
393         if json_file_options.get(key, None) is not None:
394             # Use the json defined option if it's not set on the command
395             # line (or is a default value).
396             if value is None or value == default_options.get(key, None):
397                 merged_options[key] = json_file_options[key]
398
399     # Update args namespace to matched merged_options.
400     for key, value in merged_options.items():
401         setattr(args, key, value)
402
403     # Write merged_options if --save-config.
404     if args.save_config:
405         encoded_json = json.dumps(merged_options, indent=4)
406         # Create parent directories
407         os.makedirs(os.path.dirname(config_file_path), exist_ok=True)
408         # Save json file.
409         with open(config_file_path, "w") as jfile:
410             jfile.write(encoded_json)
411
412
413 def _parse_args() -> argparse.Namespace:
414     """Setup argparse and parse command line args."""
415     def log_level(arg: str) -> int:
416         try:
417             return getattr(logging, arg.upper())
418         except AttributeError:
419             raise argparse.ArgumentTypeError(
420                 f'{arg.upper()} is not a valid log level')
421
422     def existing_directory(input_string: str):
423         """Argparse type that resolves to an absolute path."""
424         input_path = Path(os.path.expandvars(input_string)).absolute()
425         if not input_path.exists():
426             raise argparse.ArgumentTypeError(
427                 "'{}' is not a valid directory.".format(str(input_path)))
428         return input_path.as_posix()
429
430     parser = argparse.ArgumentParser()
431     parser.add_argument("-q",
432                         "--quiet",
433                         help="hide run command output",
434                         action="store_true")
435     parser.add_argument('-l',
436                         '--loglevel',
437                         type=log_level,
438                         default=logging.INFO,
439                         help='Set the log level '
440                         '(debug, info, warning, error, critical)')
441
442     default_options = get_default_options()
443
444     # Global command line options
445     parser.add_argument("--arduino-package-path",
446                         type=existing_directory,
447                         help="Path to the arduino IDE install location.")
448     parser.add_argument("--arduino-package-name",
449                         help="Name of the Arduino board package to use.")
450     parser.add_argument("--compiler-path-override",
451                         type=existing_directory,
452                         help="Path to arm-none-eabi-gcc bin folder. "
453                         "Default: Arduino core specified gcc")
454     parser.add_argument("-c", "--config-file", help="Path to a config file.")
455     parser.add_argument("--save-config",
456                         action="store_true",
457                         help="Save command line arguments to the config file.")
458
459     # Subcommands
460     subparsers = parser.add_subparsers(title="subcommand",
461                                        description="valid subcommands",
462                                        help="sub-command help",
463                                        dest="subcommand",
464                                        required=True)
465
466     # install-core command
467     install_core_parser = subparsers.add_parser(
468         "install-core", help="Download and install arduino cores")
469     install_core_parser.set_defaults(func=core_installer.install_core_command)
470     install_core_parser.add_argument("--prefix",
471                                      required=True,
472                                      help="Path to install core files.")
473     install_core_parser.add_argument(
474         "--core-name",
475         required=True,
476         choices=core_installer.supported_cores(),
477         help="Name of the arduino core to install.")
478
479     # list-boards command
480     list_boards_parser = subparsers.add_parser("list-boards",
481                                                help="show supported boards")
482     list_boards_parser.set_defaults(func=list_boards_command)
483
484     # list-menu-options command
485     list_menu_options_parser = subparsers.add_parser(
486         "list-menu-options",
487         help="show available menu options for selected board")
488     list_menu_options_parser.set_defaults(func=list_menu_options_command)
489     list_menu_options_parser.add_argument("--board",
490                                           required=True,
491                                           help="Name of the board to use.")
492
493     # show command
494     show_parser = subparsers.add_parser("show",
495                                         help="Return compiler information.")
496     add_common_parser_args(show_parser, default_options["serial_port"],
497                            default_options["build_path"],
498                            default_options["build_project_name"],
499                            default_options["project_path"],
500                            default_options["project_source_path"])
501     show_parser.add_argument("--delimit-with-newlines",
502                              help="Separate flag output with newlines.",
503                              action="store_true")
504     show_parser.add_argument("--library-names", nargs="+", type=str)
505
506     output_group = show_parser.add_mutually_exclusive_group(required=True)
507     output_group.add_argument("--c-compile", action="store_true")
508     output_group.add_argument("--cpp-compile", action="store_true")
509     output_group.add_argument("--link", action="store_true")
510     output_group.add_argument("--c-flags", action="store_true")
511     output_group.add_argument("--s-flags", action="store_true")
512     output_group.add_argument("--s-only-flags", action="store_true")
513     output_group.add_argument("--cpp-flags", action="store_true")
514     output_group.add_argument("--cpp-only-flags", action="store_true")
515     output_group.add_argument("--ld-flags", action="store_true")
516     output_group.add_argument("--ar-flags", action="store_true")
517     output_group.add_argument("--ld-libs", action="store_true")
518     output_group.add_argument("--ld-lib-names", action="store_true")
519     output_group.add_argument("--objcopy", help="objcopy step for SUFFIX")
520     output_group.add_argument("--objcopy-flags",
521                               help="objcopy flags for SUFFIX")
522     output_group.add_argument("--core-path", action="store_true")
523     output_group.add_argument("--cc-binary", action="store_true")
524     output_group.add_argument("--cxx-binary", action="store_true")
525     output_group.add_argument("--ar-binary", action="store_true")
526     output_group.add_argument("--objcopy-binary", action="store_true")
527     output_group.add_argument("--size-binary", action="store_true")
528     output_group.add_argument("--prebuilds",
529                               action="store_true",
530                               help="Show prebuild step commands.")
531     output_group.add_argument("--postbuilds",
532                               action="store_true",
533                               help="Show postbuild step commands.")
534     output_group.add_argument("--upload-tools", action="store_true")
535     output_group.add_argument("--upload-command")
536     output_group.add_argument("--library-includes", action="store_true")
537     output_group.add_argument("--library-include-dirs", action="store_true")
538     output_group.add_argument("--library-c-files", action="store_true")
539     output_group.add_argument("--library-s-files", action="store_true")
540     output_group.add_argument("--library-cpp-files", action="store_true")
541     output_group.add_argument("--core-c-files", action="store_true")
542     output_group.add_argument("--core-s-files", action="store_true")
543     output_group.add_argument("--core-cpp-files", action="store_true")
544     output_group.add_argument("--variant-c-files", action="store_true")
545     output_group.add_argument("--variant-s-files", action="store_true")
546     output_group.add_argument("--variant-cpp-files", action="store_true")
547
548     show_parser.set_defaults(func=show_command)
549
550     # run command
551     run_parser = subparsers.add_parser("run", help="Run Arduino recipes.")
552     add_common_parser_args(run_parser, default_options["serial_port"],
553                            default_options["build_path"],
554                            default_options["build_project_name"],
555                            default_options["project_path"],
556                            default_options["project_source_path"])
557     run_parser.add_argument("--run-link",
558                             nargs="+",
559                             type=str,
560                             help="Run the link command. Expected arguments: "
561                             "the archive file followed by all obj files.")
562     run_parser.add_argument("--run-objcopy", action="store_true")
563     run_parser.add_argument("--run-prebuilds", action="store_true")
564     run_parser.add_argument("--run-postbuilds", action="store_true")
565     run_parser.add_argument("--run-upload-command")
566
567     run_parser.set_defaults(func=run_command)
568
569     return parser.parse_args()
570
571
572 def main():
573     """Main command line function.
574
575     Dispatches command line invocations to sub `*_command()` functions.
576     """
577     # Parse command line arguments.
578     args = _parse_args()
579     _LOG.debug(_pretty_format(args))
580
581     log.install(args.loglevel)
582
583     # Check for and set alternate compiler path.
584     if args.compiler_path_override:
585         # Get absolute path
586         compiler_path_override = os.path.realpath(
587             os.path.expanduser(os.path.expandvars(
588                 args.compiler_path_override)))
589         args.compiler_path_override = compiler_path_override
590
591     load_config_file(args)
592
593     if args.subcommand == "install-core":
594         args.func(args)
595     elif args.subcommand in ["list-boards", "list-menu-options"]:
596         check_for_missing_args(args)
597         builder = ArduinoBuilder(args.arduino_package_path,
598                                  args.arduino_package_name)
599         builder.load_board_definitions()
600         args.func(args, builder)
601     else:  # args.subcommand in ["run", "show"]
602         check_for_missing_args(args)
603         builder = ArduinoBuilder(
604             args.arduino_package_path,
605             args.arduino_package_name,
606             build_path=args.build_path,
607             build_project_name=args.build_project_name,
608             project_path=args.project_path,
609             project_source_path=args.project_source_path,
610             library_path=getattr(args, 'library_path', None),
611             library_names=getattr(args, 'library_names', None),
612             compiler_path_override=args.compiler_path_override)
613         builder.load_board_definitions()
614         builder.select_board(args.board, args.menu_options)
615         if args.set_variable:
616             builder.set_variables(args.set_variable)
617         args.func(args, builder)
618
619     sys.exit(0)
620
621
622 if __name__ == '__main__':
623     main()