Add flag for linux backend

Adds a --@rules_libusb//:linux_backend flag to allow selection of
udev/netlink as the backend for libusb. This prevents header-based
autodetection, which isn't really hermetic, and also ensures -ludev is
passed to the linker when the udev backend is enabled.

Bug: b/358095928
Change-Id: Id27911b2587c94bd457fd5e68ecc27c87a2c9f1c
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/rules_libusb/+/275954
Reviewed-by: Ted Pudlik <tpudlik@google.com>
Presubmit-Verified: CQ Bot Account <pigweed-scoped@luci-project-accounts.iam.gserviceaccount.com>
Lint: Lint 🤖 <android-build-ayeaye@system.gserviceaccount.com>
Commit-Queue: Auto-Submit <auto-submit@pigweed-service-accounts.iam.gserviceaccount.com>
Pigweed-Auto-Submit: Armando Montanez <amontanez@google.com>
diff --git a/BUILD.bazel b/BUILD.bazel
index ccfcf72..fd2d929 100644
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -12,13 +12,18 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-load("@bazel_skylib//rules:common_settings.bzl", "bool_flag")
+load("@bazel_skylib//rules:common_settings.bzl", "bool_flag", "string_flag")
+load("@rules_cc//cc:cc_library.bzl", "cc_library")
 
 package(default_visibility = ["//visibility:public"])
 
 cc_library(
     name = "libusb_linux_config",
     hdrs = ["linux/config.h"],
+    defines = select({
+        ":use_udev": ["_RULES_LIBUSB_USE_UDEV=1"],
+        "//conditions:default": [],
+    }),
 )
 
 cc_library(
@@ -45,3 +50,22 @@
     name = "force_dynamic_linkage_enabled",
     flag_values = {":force_dynamic_linkage": "True"},
 )
+
+string_flag(
+    name = "linux_backend",
+    build_setting_default = "netlink",
+    values = [
+        "udev",
+        "netlink",
+    ],
+)
+
+config_setting(
+    name = "use_udev",
+    flag_values = {":linux_backend": "udev"},
+)
+
+config_setting(
+    name = "use_netlink",
+    flag_values = {":linux_backend": "netlink"},
+)
diff --git a/MODULE.bazel b/MODULE.bazel
index 8cdba89..8d2aea9 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -4,6 +4,7 @@
 )
 
 bazel_dep(name = "platforms", version = "0.0.8")
+bazel_dep(name = "rules_cc", version = "0.0.9")
 bazel_dep(name = "rules_license", version = "0.0.8")
 bazel_dep(name = "bazel_skylib", version = "1.5.0")
 
diff --git a/examples/basic_usage/BUILD.bazel b/examples/basic_usage/BUILD.bazel
index 6517265..a33f914 100644
--- a/examples/basic_usage/BUILD.bazel
+++ b/examples/basic_usage/BUILD.bazel
@@ -12,9 +12,11 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+load("@rules_cc//cc:cc_test.bzl", "cc_test")
+
 # This is enumerated as a test to ensure it is run and doesn't crash/fail.
 cc_test(
     name = "example",
     srcs = ["main.cc"],
-    deps = ["@libusb//:libusb"],
+    deps = ["@libusb"],
 )
diff --git a/examples/basic_usage/MODULE.bazel b/examples/basic_usage/MODULE.bazel
index f98ebee..ce4337c 100644
--- a/examples/basic_usage/MODULE.bazel
+++ b/examples/basic_usage/MODULE.bazel
@@ -1,5 +1,7 @@
 module(name = "rules_libusb_basic_example")
 
+bazel_dep(name = "rules_cc", version = "0.0.9")
+
 bazel_dep(name = "rules_libusb")
 
 # DO NOT copy this into your project. This allows this repository to reference
diff --git a/libusb.BUILD b/libusb.BUILD
index 87bd2e0..7a20766 100644
--- a/libusb.BUILD
+++ b/libusb.BUILD
@@ -12,6 +12,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+load("@rules_cc//cc:cc_library.bzl", "cc_library")
+load("@rules_cc//cc:cc_shared_library.bzl", "cc_shared_library")
 load("@rules_license//rules:license.bzl", "license")
 
 package(
@@ -65,13 +67,17 @@
 cc_library(
     name = "libusb_core",
     srcs = glob(["libusb/*.c"]),
-    visibility = ["//visibility:private"],
+    linkopts = select({
+        "@platforms//os:windows": ["-DEFAULTLIB:AdvAPI32.Lib"],
+        "//conditions:default": [],
+    }),
     target_compatible_with = select({
         "@platforms//os:linux": [],
         "@platforms//os:macos": [],
         "@platforms//os:windows": [],
         "//conditions:default": ["@platforms//:incompatible"],
     }),
+    visibility = ["//visibility:private"],
     deps = select({
         "@platforms//os:linux": [":libusb_linux"],
         "@platforms//os:macos": [":libusb_macos"],
@@ -81,10 +87,6 @@
         ":libusb_config",
         ":libusb_headers",
     ],
-    linkopts = select({
-        "@platforms//os:windows": ["-DEFAULTLIB:AdvAPI32.Lib"],
-        "//conditions:default": [],
-    }),
 )
 
 label_flag(
@@ -144,24 +146,6 @@
     ],
 )
 
-# TODO: Consider upstreaming a build-system-independent method for detecting
-# udev vs netlink.
-_GENERATED_UDEV_OR_NETLINK = """
-#include "config.h"
-
-#if HAVE_LIBUDEV
-#include "libusb/os/linux_udev.c"
-#else
-#include "libusb/os/linux_netlink.c"
-#endif  // HAVE_LIBUDEV
-"""
-
-genrule(
-    name = "autodetect_udev",
-    outs = ["udev_or_netlink.c"],
-    cmd = "echo '{}' > $@".format(_GENERATED_UDEV_OR_NETLINK),
-)
-
 cc_library(
     name = "libusb_linux",
     srcs = glob(
@@ -170,24 +154,26 @@
             "libusb/os/linux_netlink.c",
             "libusb/os/linux_udev.c",
         ],
-    ) + [
-        "udev_or_netlink.c",
-    ],
-    hdrs = glob(["libusb/os/linux_*.h"]) + [
-        # These are listed as `hdrs` because they're selected by
-        # an `#include` in the generated `udev_or_netlink.c`.
-        "libusb/os/linux_netlink.c",
-        "libusb/os/linux_udev.c",
-    ],
+    ) + select({
+        "@rules_libusb//:use_udev": ["libusb/os/linux_udev.c"],
+        "@rules_libusb//:use_netlink": ["libusb/os/linux_netlink.c"],
+    }),
+    hdrs = glob(["libusb/os/linux_*.h"]),
+    # linux_netlink.c passes a buffer pointer (`unsigned char*`) to a function
+    # expecting `char*`.
+    copts = ["-Wno-pointer-sign"],
+    linkopts = select({
+        "@rules_libusb//:use_udev": ["-ludev"],
+        # When using netlink, `-lnl` doesn't appear to be necessary (adding it
+        # makes gcc complain the library can't be found).
+        "//conditions:default": [],
+    }),
     visibility = ["//visibility:private"],
     deps = [
         ":libusb_config",
         ":libusb_headers",
         ":libusb_posix",
     ],
-    # linux_netlink.c passes a buffer pointer (`unsigned char*`) to a function
-    # expecting `char*`.
-    copts = ["-Wno-pointer-sign"],
 )
 
 cc_library(
diff --git a/linux/config.h b/linux/config.h
index 6fdfa0d..6a6bb72 100644
--- a/linux/config.h
+++ b/linux/config.h
@@ -40,7 +40,7 @@
 
 #define PLATFORM_POSIX 1
 
-/* Required for libusb < 1.0.24. */
+// Required for libusb < 1.0.24.
 #define THREADS_POSIX 1
 #define POLL_NFDS_TYPE nfds_t
 #define USBI_TIMERFD_AVAILABLE 1
@@ -58,10 +58,20 @@
 
 #define _GNU_SOURCE 1
 
-// Explicitly define HAVE_LIBUDEV to prevent auto-detection.
-#ifndef HAVE_LIBUDEV
-#if __has_include(<libudev.h>)
+// Infer UDEV support from the Bazel build defines.
+#if defined(_RULES_LIBUSB_USE_UDEV) && _RULES_LIBUSB_USE_UDEV
 #define HAVE_LIBUDEV 1
 #define USE_UDEV 1
-#endif  // __has_include(<libudev.h>)
-#endif  // HAVE_LIBUDEV
+#endif  // defined(_RULES_LIBUSB_USE_UDEV) && _RULES_LIBUSB_USE_UDEV
+
+#if defined(HAVE_LIBUDEV) && HAVE_LIBUDEV
+// Check that libudev header can be found.
+#if !__has_include(<libudev.h>)
+#error "--@rules_libusb//:linux_backend=udev enabled, but no <libudev.h> header found."
+#endif  // !__has_include(<libudev.h>)
+#else
+// Check that netlink header can be found.
+#if !__has_include(<linux/netlink.h>)
+#error "--@rules_libusb//:linux_backend=netlink enabled, but no <linux/netlink.h> header found."
+#endif  // !__has_include(<libudev.h>)
+#endif  // defined(HAVE_LIBUDEV) && HAVE_LIBUDEV