pw_arduino_build: Support Multiple Library Paths

- `--library-path` can take multiple paths in order of increasing
  precedence similar to Arduino IDE behavior. This is useful for
  including the 'libraries' directory included in an Arduino core
  and a separate user library folder.

- Remove serial wait loop from pw_sys_io_arduino

- Fix raised ValueError if arduino core install prefix is not
  relative to os.getcwd()

Change-Id: I6d68b319d2a6a4bfc6a7a599bf4c86f4bea96e39
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/24840
Reviewed-by: Keir Mierle <keir@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 4a17072..9efed18 100644
--- a/pw_arduino_build/py/pw_arduino_build/__main__.py
+++ b/pw_arduino_build/py/pw_arduino_build/__main__.py
@@ -300,7 +300,9 @@
         default=project_source_path,
         help="Project directory. Default: '{}'".format(project_source_path))
     parser.add_argument("--library-path",
-                        default="libraries",
+                        default=["libraries"],
+                        nargs="+",
+                        type=str,
                         help="Path to Arduino Library directory.")
     parser.add_argument(
         "--build-project-name",
diff --git a/pw_arduino_build/py/pw_arduino_build/builder.py b/pw_arduino_build/py/pw_arduino_build/builder.py
index fcd77e4..b8c5221 100755
--- a/pw_arduino_build/py/pw_arduino_build/builder.py
+++ b/pw_arduino_build/py/pw_arduino_build/builder.py
@@ -89,10 +89,13 @@
         self.compiler_path_override = compiler_path_override
         self.variant_includes = ""
         self.build_variant_path = False
-        if library_names and library_path:
-            self.library_names = library_names
-            self.library_path = os.path.realpath(
-                os.path.expanduser(os.path.expandvars(library_path)))
+        self.library_names = library_names
+        self.library_path = library_path
+        if library_path:
+            self.library_path = [
+                os.path.realpath(os.path.expanduser(
+                    os.path.expandvars(l_path))) for l_path in library_path
+            ]
 
         self.compiler_path_override_binaries = []
         if self.compiler_path_override:
@@ -967,24 +970,25 @@
         if not self.library_names or not self.library_path:
             return []
 
-        library_path = self.library_path
         folder_patterns = ["*"]
         if self.library_names:
             folder_patterns = self.library_names
 
-        library_folders = file_operations.find_files(library_path,
-                                                     folder_patterns,
-                                                     directories_only=True)
-        library_source_root_folders = []
-        for lib in library_folders:
-            lib_dir = os.path.join(library_path, lib)
-            src_dir = os.path.join(lib_dir, "src")
-            if os.path.exists(src_dir) and os.path.isdir(src_dir):
-                library_source_root_folders.append(src_dir)
-            else:
-                library_source_root_folders.append(lib_dir)
+        library_folders = OrderedDict()
+        for library_dir in self.library_path:
+            found_library_names = file_operations.find_files(
+                library_dir, folder_patterns, directories_only=True)
+            _LOG.debug("Found Libraries %s: %s", library_dir,
+                       found_library_names)
+            for lib_name in found_library_names:
+                lib_dir = os.path.join(library_dir, lib_name)
+                src_dir = os.path.join(lib_dir, "src")
+                if os.path.exists(src_dir) and os.path.isdir(src_dir):
+                    library_folders[lib_name] = src_dir
+                else:
+                    library_folders[lib_name] = lib_dir
 
-        return library_source_root_folders
+        return list(library_folders.values())
 
     def library_include_dirs(self):
         return [Path(lib).as_posix() for lib in self.library_folders()]
diff --git a/pw_arduino_build/py/pw_arduino_build/file_operations.py b/pw_arduino_build/py/pw_arduino_build/file_operations.py
index 61b728d..df68331 100644
--- a/pw_arduino_build/py/pw_arduino_build/file_operations.py
+++ b/pw_arduino_build/py/pw_arduino_build/file_operations.py
@@ -84,6 +84,15 @@
     return True
 
 
+def relative_or_absolute_path(file_string: str):
+    """Return a Path relative to os.getcwd(), else an absolute path."""
+    file_path = Path(file_string)
+    try:
+        return file_path.relative_to(os.getcwd())
+    except ValueError:
+        return file_path.resolve()
+
+
 def download_to_cache(url: str,
                       expected_md5sum=None,
                       expected_sha256sum=None,
@@ -99,8 +108,7 @@
         urllib.request.urlretrieve(url, filename=downloaded_file)
 
     if os.path.exists(downloaded_file):
-        _LOG.info("Downloaded: %s",
-                  Path(downloaded_file).relative_to(os.getcwd()))
+        _LOG.info("Downloaded: %s", relative_or_absolute_path(downloaded_file))
         if expected_sha256sum:
             verify_file_checksum(downloaded_file,
                                  expected_sha256sum,
@@ -148,7 +156,7 @@
                                     "." + os.path.basename(archive_file))
     os.makedirs(temp_extract_dir, exist_ok=True)
 
-    _LOG.info("Extracting: %s", Path(archive_file).relative_to(os.getcwd()))
+    _LOG.info("Extracting: %s", relative_or_absolute_path(archive_file))
     if zipfile.is_zipfile(archive_file):
         extract_zipfile(archive_file, temp_extract_dir)
     elif tarfile.is_tarfile(archive_file):
@@ -157,7 +165,7 @@
         _LOG.error("Unknown archive format: %s", archive_file)
         return sys.exit(1)
 
-    _LOG.info("Installing into: %s", Path(dest_dir).relative_to(os.getcwd()))
+    _LOG.info("Installing into: %s", relative_or_absolute_path(dest_dir))
     path_to_extracted_files = temp_extract_dir
 
     extracted_top_level_files = os.listdir(temp_extract_dir)
diff --git a/pw_sys_io_arduino/sys_io_arduino.cc b/pw_sys_io_arduino/sys_io_arduino.cc
index de6c344..fbbc7bc 100644
--- a/pw_sys_io_arduino/sys_io_arduino.cc
+++ b/pw_sys_io_arduino/sys_io_arduino.cc
@@ -20,12 +20,7 @@
 #include "pw_preprocessor/compiler.h"
 #include "pw_sys_io/sys_io.h"
 
-extern "C" void pw_sys_io_Init() {
-  Serial.begin(115200);
-  // Wait for serial port to be available
-  while (!Serial) {
-  }
-}
+extern "C" void pw_sys_io_Init() { Serial.begin(115200); }
 
 namespace pw::sys_io {