pw_arduino_build: Remove duplicate c-flags from asm/cppflags

Teensy cores have cflags included in the asmflag and cppflag recipes.
This causes gn to effectively duplicate cflags when building asm and
cpp files. This CL adds --s-only-flags and --cpp-only-flags options
which remove cflags from each respectively.

Add --ld-lib-names option.
If --ld-libs returns: -larm_cortexM7lfsp_math -lm -lstdc++
Then --ld-lib-names returns: arm_cortexM7lfsp_math m stdc++

Add a --flash-only option to arduino_unit_test_runner.

Change-Id: I1906d6f846f12ceb8ca42c498a7b216e0e3ba7e2
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/23086
Reviewed-by: Armando Montanez <amontanez@google.com>
Commit-Queue: Anthony DiGirolamo <tonymd@google.com>
diff --git a/pw_arduino_build/py/pw_arduino_build/__main__.py b/pw_arduino_build/py/pw_arduino_build/__main__.py
index 16dfde7..503630a 100644
--- a/pw_arduino_build/py/pw_arduino_build/__main__.py
+++ b/pw_arduino_build/py/pw_arduino_build/__main__.py
@@ -22,6 +22,7 @@
 import shlex
 import subprocess
 import sys
+from collections import OrderedDict
 from typing import List
 
 from pw_arduino_build import core_installer, log
@@ -90,6 +91,15 @@
         print(flag_string)
 
 
+def subtract_flags(flag_list_a: List[str],
+                   flag_list_b: List[str]) -> List[str]:
+    """Given two sets of flags return flags in a that are not in b."""
+    flag_counts = OrderedDict()  # type: OrderedDict[str, int]
+    for flag in flag_list_a + flag_list_b:
+        flag_counts[flag] = flag_counts.get(flag, 0) + 1
+    return [flag for flag in flag_list_a if flag_counts.get(flag, 0) == 1]
+
+
 def run_command_lines(args, command_lines: List[str]):
     for command_line in command_lines:
         if not args.quiet:
@@ -185,16 +195,30 @@
         sflags = builder.get_s_flags()
         show_command_print_flag_string(args, sflags)
 
+    elif args.s_only_flags:
+        s_only_flags = subtract_flags(shlex.split(builder.get_s_flags()),
+                                      shlex.split(builder.get_c_flags()))
+        show_command_print_flag_string(args, " ".join(s_only_flags))
+
     elif args.cpp_flags:
         cppflags = builder.get_cpp_flags()
         show_command_print_flag_string(args, cppflags)
 
+    elif args.cpp_only_flags:
+        cpp_only_flags = subtract_flags(shlex.split(builder.get_cpp_flags()),
+                                        shlex.split(builder.get_c_flags()))
+        show_command_print_flag_string(args, " ".join(cpp_only_flags))
+
     elif args.ld_flags:
         ldflags = builder.get_ld_flags()
         show_command_print_flag_string(args, ldflags)
 
     elif args.ld_libs:
-        print(builder.get_ld_libs())
+        show_command_print_flag_string(args, builder.get_ld_libs())
+
+    elif args.ld_lib_names:
+        show_command_print_flag_string(args,
+                                       builder.get_ld_libs(name_only=True))
 
     elif args.ar_flags:
         ar_flags = builder.get_ar_flags()
@@ -464,10 +488,13 @@
     output_group.add_argument("--link", action="store_true")
     output_group.add_argument("--c-flags", action="store_true")
     output_group.add_argument("--s-flags", action="store_true")
+    output_group.add_argument("--s-only-flags", action="store_true")
     output_group.add_argument("--cpp-flags", action="store_true")
+    output_group.add_argument("--cpp-only-flags", action="store_true")
     output_group.add_argument("--ld-flags", action="store_true")
     output_group.add_argument("--ar-flags", action="store_true")
     output_group.add_argument("--ld-libs", action="store_true")
+    output_group.add_argument("--ld-lib-names", action="store_true")
     output_group.add_argument("--objcopy", help="objcopy step for SUFFIX")
     output_group.add_argument("--objcopy-flags",
                               help="objcopy flags for SUFFIX")
diff --git a/pw_arduino_build/py/pw_arduino_build/builder.py b/pw_arduino_build/py/pw_arduino_build/builder.py
index a59babe..43b5ac3 100755
--- a/pw_arduino_build/py/pw_arduino_build/builder.py
+++ b/pw_arduino_build/py/pw_arduino_build/builder.py
@@ -941,19 +941,13 @@
 
         return compile_line.strip()
 
-    def get_ld_libs(self):
+    def get_ld_libs(self, name_only=False):
         compile_line = self.get_link_line()
-        _, compile_line = ArduinoBuilder.split_binary_from_arguments(
-            compile_line)
-        # TODO(tonymd): This replacement is teensy specific
-        compile_line = compile_line.replace(
-            "-o \"{build.path}/{build.project_name}.elf\" "
-            "{object_files} \"-L{build.path}\"", "", 1)
-        libs = re.findall(r'(-l[^ ]+ ?)', compile_line)
-        for lib in libs:
-            compile_line = compile_line.replace(lib, "", 1)
-        libs = [lib.strip() for lib in libs]
-
+        libs = re.findall(r'(?P<arg>-l(?P<name>[^ ]+) ?)', compile_line)
+        if name_only:
+            libs = [lib_name.strip() for lib_arg, lib_name in libs]
+        else:
+            libs = [lib_arg.strip() for lib_arg, lib_name in libs]
         return " ".join(libs)
 
     def library_folders(self):
diff --git a/pw_arduino_build/py/pw_arduino_build/unit_test_runner.py b/pw_arduino_build/py/pw_arduino_build/unit_test_runner.py
index 8463f58..0ef5d8f 100755
--- a/pw_arduino_build/py/pw_arduino_build/unit_test_runner.py
+++ b/pw_arduino_build/py/pw_arduino_build/unit_test_runner.py
@@ -79,9 +79,13 @@
     parser.add_argument('--verbose',
                         '-v',
                         dest='verbose',
-                        action="store_true",
+                        action='store_true',
                         help='Output additional logs as the script runs')
 
+    parser.add_argument('--flash-only',
+                        action='store_true',
+                        help="Don't check for test output after flashing.")
+
     # arduino_builder arguments
     # TODO(tonymd): Get these args from __main__.py or elsewhere.
     parser.add_argument("-c",
@@ -212,7 +216,7 @@
     _LOG.info('Test passed!')
 
 
-def run_device_test(binary, port, baud, test_timeout, upload_tool,
+def run_device_test(binary, flash_only, port, baud, test_timeout, upload_tool,
                     arduino_package_path, test_runner_args) -> bool:
     """Flashes, runs, and checks an on-device test binary.
 
@@ -258,6 +262,8 @@
         # this serial port.
         flash_device(test_runner_args, upload_tool)
         wait_for_port(port)
+        if flash_only:
+            return True
         result.append(read_serial(port, baud, test_timeout))
         if result:
             handle_test_results(result[0])
@@ -337,6 +343,7 @@
             arduino_builder_args += ["--set-variable", var]
 
     if run_device_test(binary.as_posix(),
+                       args.flash_only,
                        args.port,
                        args.baud,
                        args.test_timeout,
diff --git a/targets/arduino/BUILD.gn b/targets/arduino/BUILD.gn
index 07d84a6..146e784 100644
--- a/targets/arduino/BUILD.gn
+++ b/targets/arduino/BUILD.gn
@@ -49,12 +49,13 @@
       cflags = filter_exclude(_cflags, _exclude_flags)
 
       asmflags = exec_script(arduino_builder_script,
-                             arduino_show_command_args + [ "--s-flags" ],
+                             arduino_show_command_args + [ "--s-only-flags" ],
                              "list lines")
 
-      _cflags_cc = exec_script(arduino_builder_script,
-                               arduino_show_command_args + [ "--cpp-flags" ],
-                               "list lines")
+      _cflags_cc =
+          exec_script(arduino_builder_script,
+                      arduino_show_command_args + [ "--cpp-only-flags" ],
+                      "list lines")
       cflags_cc = filter_exclude(_cflags_cc, _exclude_flags)
 
       _ldflags = exec_script(arduino_builder_script,
@@ -75,6 +76,12 @@
                            # Remove the Arduino {object_files} variable
                            "{object_files}",
                          ])
+
+      # TODO(tonymd): Determine if libs are needed.
+      #   Teensy4 core recipe uses: '-larm_cortexM7lfsp_math -lm -lstdc++'
+      # libs = exec_script(arduino_builder_script,
+      #     arduino_show_command_args + [ "--ld-lib-names" ],
+      #     "list lines")
     }
 
     pw_source_set("pre_init") {