tools: Add presubmit checks

- Remove some unused BUILD.gn cruft related to arduino and mimxrt595
- default_build presubmit step that compiles with no display (and no
  need for GLFW or ImGui packages).
- Fix application deps to remove unused pw_system:work_queue and add
  pw_system:target_hooks.
- Add a missing Common::EndOfFrameCallback() implementation to
  common_host_null.cc to match common_host_imgui.cc and common_pico.cc
- Switch 'pw presubmit' command to call kudzu_tools.presubmit_checks

Bug: b/303300305
Change-Id: I35073d9b1d04d8259e3a6b862fdc3fff6f801d56
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/kudzu/+/176950
Commit-Queue: Anthony DiGirolamo <tonymd@google.com>
Reviewed-by: Rob Mohr <mohrr@google.com>
diff --git a/applications/32blit_demo/BUILD.gn b/applications/32blit_demo/BUILD.gn
index 0c1c984..8c2202c 100644
--- a/applications/32blit_demo/BUILD.gn
+++ b/applications/32blit_demo/BUILD.gn
@@ -36,7 +36,7 @@
     "$dir_pw_spin_delay",
     "$dir_pw_string",
     "$dir_pw_sys_io",
-    "$dir_pw_system:work_queue",
+    "$dir_pw_system:target_hooks",
     "$dir_pw_thread:thread",
     "$pw_dir_third_party_32blit:32blit",
     "//applications/app_common",
@@ -49,9 +49,7 @@
     remove_configs += [ "$dir_pw_toolchain/host_clang:linux_sysroot" ]
   }
 
-  if (pw_build_EXECUTABLE_TARGET_TYPE == "arduino_executable" ||
-      pw_build_EXECUTABLE_TARGET_TYPE == "pico_executable" ||
-      pw_build_EXECUTABLE_TARGET_TYPE == "mimxrt595_executable") {
+  if (pw_build_EXECUTABLE_TARGET_TYPE == "pico_executable") {
     ldflags = [ "-Wl,--print-memory-usage" ]
   }
 }
diff --git a/applications/32blit_demo/main.cc b/applications/32blit_demo/main.cc
index 4f21bce..2243f5b 100644
--- a/applications/32blit_demo/main.cc
+++ b/applications/32blit_demo/main.cc
@@ -32,7 +32,6 @@
 #include "pw_string/string_builder.h"
 #include "pw_sys_io/sys_io.h"
 #include "pw_system/target_hooks.h"
-#include "pw_system/work_queue.h"
 #include "pw_thread/detached_thread.h"
 
 using pw::color::color_rgb565_t;
diff --git a/applications/app_common_impl/common_host_null.cc b/applications/app_common_impl/common_host_null.cc
index 1a05e4b..d82344f 100644
--- a/applications/app_common_impl/common_host_null.cc
+++ b/applications/app_common_impl/common_host_null.cc
@@ -40,6 +40,8 @@
 }  // namespace
 
 // static
+Status Common::EndOfFrameCallback() { return pw::OkStatus(); }
+
 Status Common::Init() { return s_display_driver.Init(); }
 
 // static
diff --git a/applications/badge/BUILD.gn b/applications/badge/BUILD.gn
index 223d5b1..3eb7243 100644
--- a/applications/badge/BUILD.gn
+++ b/applications/badge/BUILD.gn
@@ -45,7 +45,7 @@
     "$dir_pw_spin_delay",
     "$dir_pw_string",
     "$dir_pw_sys_io",
-    "$dir_pw_system:work_queue",
+    "$dir_pw_system:target_hooks",
     "$dir_pw_thread:thread",
     "$pw_dir_third_party_32blit:32blit",
     "//applications/app_common",
@@ -58,9 +58,7 @@
     remove_configs += [ "$dir_pw_toolchain/host_clang:linux_sysroot" ]
   }
 
-  if (pw_build_EXECUTABLE_TARGET_TYPE == "arduino_executable" ||
-      pw_build_EXECUTABLE_TARGET_TYPE == "pico_executable" ||
-      pw_build_EXECUTABLE_TARGET_TYPE == "mimxrt595_executable") {
+  if (pw_build_EXECUTABLE_TARGET_TYPE == "pico_executable") {
     ldflags = [ "-Wl,--print-memory-usage" ]
   }
 }
diff --git a/applications/badge/main.cc b/applications/badge/main.cc
index cce77de..669a8a8 100644
--- a/applications/badge/main.cc
+++ b/applications/badge/main.cc
@@ -43,7 +43,6 @@
 #include "pw_string/string_builder.h"
 #include "pw_sys_io/sys_io.h"
 #include "pw_system/target_hooks.h"
-#include "pw_system/work_queue.h"
 #include "pw_thread/detached_thread.h"
 #include "pw_touchscreen/touchscreen.h"
 
diff --git a/applications/terminal_display/BUILD.gn b/applications/terminal_display/BUILD.gn
index a49c726..1fe897b 100644
--- a/applications/terminal_display/BUILD.gn
+++ b/applications/terminal_display/BUILD.gn
@@ -50,7 +50,7 @@
     "$dir_pw_spin_delay",
     "$dir_pw_string",
     "$dir_pw_sys_io",
-    "$dir_pw_system:work_queue",
+    "$dir_pw_system:target_hooks",
     "$dir_pw_thread:thread",
     "//applications/app_common",
     "//lib/framecounter",
diff --git a/applications/terminal_display/main.cc b/applications/terminal_display/main.cc
index db05bde..d5e3c8c 100644
--- a/applications/terminal_display/main.cc
+++ b/applications/terminal_display/main.cc
@@ -42,7 +42,6 @@
 #include "pw_string/string_builder.h"
 #include "pw_sys_io/sys_io.h"
 #include "pw_system/target_hooks.h"
-#include "pw_system/work_queue.h"
 #include "pw_thread/detached_thread.h"
 #include "pw_touchscreen/touchscreen.h"
 #include "text_buffer.h"
diff --git a/pigweed.json b/pigweed.json
index 33f9972..3fc10a0 100644
--- a/pigweed.json
+++ b/pigweed.json
@@ -11,7 +11,7 @@
           "function": "watch_project"
         },
         "presubmit": {
-          "module": "pigweed_experimental_tools.presubmit_checks",
+          "module": "kudzu_tools.presubmit_checks",
           "function": "main"
         },
         "heap-viewer": {
@@ -64,4 +64,3 @@
     }
   }
 }
-
diff --git a/tools/BUILD.gn b/tools/BUILD.gn
index 9b894ec..ab199a1 100644
--- a/tools/BUILD.gn
+++ b/tools/BUILD.gn
@@ -24,6 +24,7 @@
   sources = [
     "kudzu_tools/__init__.py",
     "kudzu_tools/build_project.py",
+    "kudzu_tools/presubmit_checks.py",
   ]
   python_deps = [
     "$dir_pw_build/py",
diff --git a/tools/kudzu_tools/presubmit_checks.py b/tools/kudzu_tools/presubmit_checks.py
new file mode 100755
index 0000000..5b0159a
--- /dev/null
+++ b/tools/kudzu_tools/presubmit_checks.py
@@ -0,0 +1,171 @@
+# Copyright 2023 The Pigweed Authors
+#
+# 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
+#
+#     https://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.
+"""Kudzu presubmit check script."""
+
+import argparse
+import logging
+import os
+from pathlib import Path
+import re
+import sys
+
+try:
+    import pw_cli.log
+except ImportError:
+    print(
+        'ERROR: Activate the environment before running presubmits!',
+        file=sys.stderr,
+    )
+    sys.exit(2)
+
+import pw_presubmit
+from pw_presubmit import (
+    build,
+    cli,
+    cpp_checks,
+    format_code,
+    git_repo,
+    inclusive_language,
+    install_hook,
+    keep_sorted,
+    python_checks,
+)
+from pw_presubmit.presubmit_context import PresubmitContext, PresubmitFailure
+
+_LOG = logging.getLogger(__name__)
+
+# Set up variables for key project paths.
+try:
+    PROJECT_ROOT = Path(os.environ['KUDZU_ROOT'])
+except KeyError:
+    print(
+        'ERROR: The presubmit checks must be run in Kudzu\'s root directory',
+        file=sys.stderr,
+    )
+    sys.exit(2)
+
+PIGWEED_ROOT = PROJECT_ROOT / 'third_party' / 'pigweed'
+
+#
+# Presubmit checks
+#
+default_build = build.GnGenNinja(
+    name='default_build',
+    doc='',
+    ninja_targets=('default', ),
+)
+
+
+def check_for_git_changes(ctx: PresubmitContext):
+    """Checks that repositories have all changes committed."""
+    checked_repos = (PIGWEED_ROOT, *ctx.repos)
+    changes = [r for r in checked_repos if git_repo.has_uncommitted_changes(r)]
+    for repo in changes:
+        _LOG.error('There are uncommitted changes in the %s repo!', repo.name)
+    if changes:
+        _LOG.warning(
+            'Commit or stash pending changes before running the presubmit.')
+        raise PresubmitFailure
+
+
+# Avoid running some checks on certain paths.
+PATH_EXCLUSIONS = (
+    re.compile(r'^pcb/'),
+    re.compile(r'^third_party/'),
+)
+
+#
+# Presubmit check programs
+#
+OTHER_CHECKS = (build.gn_gen_check, )
+
+QUICK = (
+    default_build,
+    format_code.presubmit_checks(
+        code_formats=format_code.CODE_FORMATS_WITH_BLACK),
+)
+
+LINTFORMAT = (
+    # keep-sorted: start
+    cpp_checks.pragma_once,
+    format_code.presubmit_checks(),
+    inclusive_language.presubmit_check,
+    keep_sorted.presubmit_check,
+    python_checks.gn_python_lint,
+    # keep-sorted: end
+)
+
+FULL = (
+    QUICK,  # Add all checks from the 'quick' program
+    LINTFORMAT,
+    # Use the upstream Python checks, with custom path filters applied.
+    python_checks.gn_python_check,
+)
+
+PROGRAMS = pw_presubmit.Programs(
+    # keep-sorted: start
+    ci_cq=FULL,
+    full=FULL,
+    lintformat=LINTFORMAT,
+    other_checks=OTHER_CHECKS,
+    quick=QUICK,
+    # keep-sorted: end
+)
+
+
+def run(install: bool, exclude: list, **presubmit_args) -> int:
+    """Process the --install argument then invoke pw_presubmit."""
+
+    # Install the presubmit Git pre-push hook, if requested.
+    if install:
+        install_hook.install_git_hook(
+            'pre-push',
+            [
+                'python',
+                '-m',
+                'kudzu_tools.presubmit_checks',
+                '--base',
+                'origin/main..HEAD',
+                '--program',
+                'quick',
+            ],
+        )
+        return 0
+
+    exclude.extend(PATH_EXCLUSIONS)
+    repos = git_repo.discover_submodules(superproject_dir=PROJECT_ROOT)
+    return cli.run(root=PROJECT_ROOT,
+                   repositories=repos,
+                   exclude=exclude,
+                   **presubmit_args)
+
+
+def main() -> int:
+    """Run the presubmit checks for this repository."""
+    parser = argparse.ArgumentParser(description=__doc__)
+    cli.add_arguments(parser, PROGRAMS, 'quick')
+
+    # Define an option for installing a Git pre-push hook for this script.
+    parser.add_argument(
+        '--install',
+        action='store_true',
+        help='Install the presubmit as a Git pre-push hook and exit.',
+    )
+
+    return run(**vars(parser.parse_args()))
+
+
+if __name__ == '__main__':
+    pw_cli.log.install(logging.INFO)
+    sys.exit(main())