Initial commit

Initial commit for rules_libusb that provides a release version module
extension that simplifies an external dependency on libusb.

Change-Id: I4f61890b74f975e792a6b159fb8a6c8e953c14a5
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d6631ca
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+bazel-*
+
+# Ignore until https://github.com/bazelbuild/bazel/issues/20369 is fixed.
+MODULE.bazel.lock
diff --git a/BUILD.bazel b/BUILD.bazel
new file mode 100644
index 0000000..a21c90e
--- /dev/null
+++ b/BUILD.bazel
@@ -0,0 +1,42 @@
+# Copyright 2024 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
+#
+#     http://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.
+
+package(default_visibility = ["//visibility:public"])
+
+cc_library(
+    name = "libusb_linux_config",
+    hdrs = ["linux/config.h"],
+)
+
+cc_library(
+    name = "default_libusb_config",
+    hdrs = ["include/config.h"],
+    includes = ["include"],
+    deps = select({
+        "@platforms//os:windows": ["@libusb_sources//:libusb_msvc_config"],
+        "@platforms//os:macos": ["@libusb_sources//:libusb_macos_config"],
+        "@platforms//os:linux": ["//:libusb_linux_config"],
+        "//conditions:default": [],
+    }),
+)
+
+alias(
+    name = "libusb",
+    actual = "@libusb_sources//:libusb",
+)
+
+alias(
+    name = "libusb_dynamic",
+    actual = "@libusb_sources//:libusb_dynamic",
+)
diff --git a/MODULE.bazel b/MODULE.bazel
new file mode 100644
index 0000000..7b3ca35
--- /dev/null
+++ b/MODULE.bazel
@@ -0,0 +1,14 @@
+module(
+    name = "rules_libusb",
+    version = "0.1.0-rc1",
+)
+
+bazel_dep(name = "platforms", version = "0.0.8")
+
+# If your project has versioning requirements for libusb, you'll need to specify
+# this in your project's MODULE.bazel.
+libusb = use_extension("@rules_libusb//:extensions.bzl", "libusb")
+libusb.source_release(min_version = "1.0.20-rc1")
+
+# rules_libusb actually needs to be able to reference @libusb_sources.
+use_repo(libusb, "libusb_sources")
diff --git a/README.md b/README.md
index c5ae3d3..7b54ce0 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,43 @@
-Pigweed Open Source Template Repository
-=======================================
+# rules_libusb
 
-This repository is a template that we will use when creating new open source
-repositories for Pigweed.
+A small wrapper Bazel module that builds libusb from sources. Supports Windows,
+macOS, and Linux.
+
+## Getting Started
+
+At this time, rules_libusb only supports bzlmod projects. Legacy `WORKSPACE`
+projects are not explicitly supported.
+
+### blzmod
+
+Add rules_libusb to your `MODULE.bazel` file:
+```
+bazel_dep(name = "rules_libusb", version="0.1.0-rc1")
+```
+
+Then add `@libusb//:libusb` in `dynamic_deps` to the tool that uses it:
+```
+cc_binary(
+    name = "my_tool",
+    srcs = ["main.cpp"],
+    deps = ["@rules_libusb//:libusb"],
+    dynamic_deps = ["@rules_libusb//:libusb_dynamic"],
+)
+```
+
+If you have explicit libusb versioning requirements, you may add them to your
+`MODULE.bazel`:
+```
+libusb = use_extension("@rules_libusb//:extensions.bzl", "libusb")
+libusb.source_release(min_version = "1.0.27")
+```
+
+Note: `source_release` constraints follow bzlmod behavior of minimal version
+selection.
+
+## Building this repo
+
+To build this repo, run:
+```
+bazel build //:libusb
+```
diff --git a/extensions.bzl b/extensions.bzl
new file mode 100644
index 0000000..d782758
--- /dev/null
+++ b/extensions.bzl
@@ -0,0 +1,98 @@
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+
+# This just silences warnings about canonical reproducible forms.
+_KNOWN_RELEASE_HASHES = {
+    "1.0.20-rc1": "914c4c66f4d6582fb19afc92aa8274f11e0745a93a02593e3bcc2ee7c6048e23",
+    "1.0.20": "cb057190ba0a961768224e4dc6883104c6f945b2bf2ef90d7da39e7c1834f7ff",
+    "1.0.21": "7dce9cce9a81194b7065ee912bcd55eeffebab694ea403ffb91b67db66b1824b",
+    "1.0.22": "75aeb9d59a4fdb800d329a545c2e6799f732362193b465ea198f2aa275518157",
+    "1.0.23": "db11c06e958a82dac52cf3c65cb4dd2c3f339c8a988665110e0d24d19312ad8d",
+    "1.0.24": "7efd2685f7b327326dcfb85cee426d9b871fd70e22caa15bb68d595ce2a2b12a",
+    "1.0.25": "8a28ef197a797ebac2702f095e81975e2b02b2eeff2774fa909c78a74ef50849",
+    "1.0.26": "12ce7a61fc9854d1d2a1ffe095f7b5fac19ddba095c259e6067a46500381b5a5",
+    "1.0.27": "ffaa41d741a8a3bee244ac8e54a72ea05bf2879663c098c82fc5757853441575",
+}
+
+def _version_parts(ver_string):
+    """Converts version parts into a list of integers.
+
+    Every version integer list will be four parts to ensure that release
+    candidates are properly handled. Final releases are akin to a
+    release candidate 9999999999.
+    """
+    parts = ver_string.split(".")
+    if "-" in parts[-1]:
+        rc = parts[-1].split("-")
+        if len(rc) > 2:
+            fail("Unknown version string format:", ver_string)
+        parts[-1] = rc[0]
+        parts.append(rc[1].lower().lstrip("rc"))
+    else:
+        parts.append(9999999999)
+    return [int(p) for p in parts]
+
+def _version_less_than(a, b):
+    a_parts = _version_parts(a)
+    b_parts = _version_parts(b)
+    for i in range(len(a_parts)):
+        if a_parts[i] == b_parts[i]:
+            continue
+        return a_parts[i] < b_parts[i]
+
+    # Must be equal.
+    return False
+
+def _libusb_impl(module_ctx):
+    """ Fetches libusb sources as requested by Bazel modules.
+
+    Intended behavior:
+        The minimum supported version is selected, per usual bzlmod behavior.
+    """
+    root_module = True
+    min_versions = {}
+    max_versions = {}
+    for module in module_ctx.modules:
+        for release in module.tags.source_release:
+            if release.min_version:
+                if release.min_version not in min_versions:
+                    min_versions[release.min_version] = []
+                min_versions[release.min_version].append(module.name)
+            if release.max_version:
+                if release.max_version not in max_versions:
+                    max_versions[release.max_version] = []
+                max_versions[release.max_version].append(module.name)
+
+    version_to_use = None
+    for v, m in min_versions.items():
+        if version_to_use == None or _version_less_than(version_to_use, v):
+            version_to_use = v
+
+    for v, m in max_versions.items():
+        if _version_less_than(v, version_to_use):
+            fail("Minimum supported libusb version for", min_versions[version_to_use], "is", version_to_use, "but", max_versions[v], "require at most", v)
+
+    http_archive(
+        name = "libusb_sources",
+        urls = ["https://github.com/libusb/libusb/releases/download/v{}/libusb-{}.tar.bz2".format(version_to_use, version_to_use)],
+        build_file = "@rules_libusb//:libusb.BUILD",
+        strip_prefix = "libusb-" + version_to_use,
+        sha256 = _KNOWN_RELEASE_HASHES.get(version_to_use, None),
+    )
+
+libusb = module_extension(
+    doc = "Bzlmod extension for pulling in libusb sources.",
+    implementation = _libusb_impl,
+    tag_classes = {
+        "source_release": tag_class(
+            doc = "Controls the source code version to use when building libusb.",
+            attrs = {
+                "min_version": attr.string(
+                    doc = "Minimum supported version of libusb required",
+                ),
+                "max_version": attr.string(
+                    doc = "Maximum supported version of libusb required",
+                ),
+            },
+        ),
+    },
+)
diff --git a/include/config.h b/include/config.h
new file mode 100644
index 0000000..38b5f11
--- /dev/null
+++ b/include/config.h
@@ -0,0 +1,41 @@
+// Copyright 2024 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
+//
+//     http://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.
+
+#if defined(_WIN32) | defined(_WIN64)
+
+#if defined(_MSC_VER)
+#include "msvc/config.h"
+#endif // defined(_MSC_VER )
+
+// TODO: MinGW-W64 and clang support.
+
+#define _RULES_LIBUSB_OS_SUPPORTED 1
+
+#elif defined(__linux__)
+
+#include "linux/config.h"
+
+#define _RULES_LIBUSB_OS_SUPPORTED 1
+
+#elif defined(__APPLE__)
+
+#include "Xcode/config.h"
+
+#define _RULES_LIBUSB_OS_SUPPORTED 1
+
+#endif  // defined(_WIN32) | defined(_WIN64)
+
+#if !defined(_RULES_LIBUSB_OS_SUPPORTED) || !_RULES_LIBUSB_OS_SUPPORTED
+#error Unsupported configuration, either contribute to rules_libusb, or build your own `config.h`.
+#endif  // !defined(_RULES_LIBUSB_OS_SUPPORTED)
diff --git a/libusb.BUILD b/libusb.BUILD
new file mode 100644
index 0000000..959275c
--- /dev/null
+++ b/libusb.BUILD
@@ -0,0 +1,154 @@
+# Copyright 2024 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
+#
+#     http://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.
+
+cc_library(
+    name = "libusb",
+    srcs = glob(["libusb/*.c"]),
+    visibility = ["//visibility:public"],
+    deps = select({
+        "@platforms//os:linux": [":libusb_linux"],
+        "@platforms//os:macos": [":libusb_macos"],
+        "@platforms//os:windows": [":libusb_windows"],
+        "//conditions:default": ["@platforms//:incompatible"],
+    }) + [
+        ":libusb_config",
+        ":libusb_headers",
+    ],
+)
+
+cc_shared_library(
+    name = "libusb_dynamic",
+    # Always use exactly this name.
+    shared_lib_name = select({
+        "@platforms//os:macos": "libusb-1.0.dylib",
+        "@platforms//os:windows": "libusb-1.0.dll",
+        "//conditions:default": "libusb-1.0.so",
+    }),
+    visibility = ["//visibility:public"],
+    win_def_file = select({
+        "@platforms//os:windows": "libusb/libusb-1.0.def",
+        "//conditions:default": None,
+    }),
+    deps = [":libusb"],
+)
+
+label_flag(
+    name = "libusb_config",
+    build_setting_default = "@rules_libusb//:default_libusb_config",
+)
+
+# To use this `config.h`, include it as `msvc/config.h` from your actual
+# `config.h`.
+cc_library(
+    name = "libusb_msvc_config",
+    hdrs = ["msvc/config.h"],
+    visibility = ["//visibility:public"],
+)
+
+# To use this `config.h`, include it as `Xcode/config.h` from your actual
+# `config.h`.
+cc_library(
+    name = "libusb_macos_config",
+    hdrs = ["Xcode/config.h"],
+    visibility = ["//visibility:public"],
+)
+
+cc_library(
+    name = "libusb_headers",
+    hdrs = glob(["libusb/*.h"]),
+    includes = ["libusb"],
+    visibility = ["//visibility:private"],
+    deps = [":libusb_config"],
+)
+
+cc_library(
+    name = "libusb_windows",
+    srcs = glob(["libusb/os/*_windows.c"]),
+    hdrs = glob(["libusb/os/*_windows.h"]),
+    visibility = ["//visibility:private"],
+    deps = [
+        ":libusb_config",
+        ":libusb_headers",
+    ],
+)
+
+cc_library(
+    name = "libusb_posix",
+    srcs = glob(["libusb/os/*_posix.c"]),
+    hdrs = glob(["libusb/os/*_posix.h"]),
+    visibility = ["//visibility:private"],
+    deps = [
+        ":libusb_config",
+        ":libusb_headers",
+    ],
+)
+
+# 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(
+        ["libusb/os/linux_*.c"],
+        exclude = [
+            "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",
+    ],
+    visibility = ["//visibility:private"],
+    deps = [
+        ":libusb_config",
+        ":libusb_headers",
+        ":libusb_posix",
+    ],
+)
+
+cc_library(
+    name = "libusb_macos",
+    srcs = glob(["libusb/os/darwin_*.c"]),
+    hdrs = glob(["libusb/os/darwin_*.h"]),
+    linkopts = [
+        "-framework IOKit",
+        "-framework Security",
+    ],
+    visibility = ["//visibility:private"],
+    deps = [
+        ":libusb_config",
+        ":libusb_headers",
+        ":libusb_posix",
+    ],
+)
diff --git a/linux/config.h b/linux/config.h
new file mode 100644
index 0000000..3c87c02
--- /dev/null
+++ b/linux/config.h
@@ -0,0 +1,53 @@
+// Copyright 2024 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
+//
+//     http://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.
+
+#define DEFAULT_VISIBILITY __attribute__ ((visibility ("default")))
+#define ENABLE_LOGGING 1
+#define HAVE_ASM_TYPES_H 1
+#define HAVE_CLOCK_GETTIME 1
+#define HAVE_DECL_EFD_CLOEXEC 1
+#define HAVE_DECL_EFD_NONBLOCK 1
+#define HAVE_DECL_TFD_CLOEXEC 1
+#define HAVE_DECL_TFD_NONBLOCK 1
+#define HAVE_DLFCN_H 1
+#define HAVE_EVENTFD 1
+#define HAVE_INTTYPES_H 1
+#define HAVE_NFDS_T 1
+#define HAVE_PIPE2 1
+#define HAVE_PTHREAD_CONDATTR_SETCLOCK 1
+#define HAVE_PTHREAD_SETNAME_NP 1
+#define HAVE_STDINT_H 1
+#define HAVE_STDIO_H 1
+#define HAVE_STDLIB_H 1
+#define HAVE_STRINGS_H 1
+#define HAVE_STRING_H 1
+#define HAVE_SYS_STAT_H 1
+#define HAVE_SYS_TIME_H 1
+#define HAVE_SYS_TYPES_H 1
+#define HAVE_TIMERFD 1
+#define HAVE_UNISTD_H 1
+
+#define PLATFORM_POSIX 1
+
+#define PRINTF_FORMAT(a, b) __attribute__ ((__format__ (__printf__, a, b)))
+#define STDC_HEADERS 1
+
+#define _GNU_SOURCE 1
+
+// Explicitly define HAVE_LIBUDEV to prevent auto-detection.
+#ifndef HAVE_LIBUDEV
+#if __has_include(<libudev.h>)
+#define HAVE_LIBUDEV 1
+#endif  // __has_include(<libudev.h>)
+#endif  // HAVE_LIBUDEV