A library build is not finished when files appear under lib/. It is finished when a consumer can find the header, link the correct ABI, load the intended shared object, enumerate hardware, handle errors, and release every reference. libusb makes that journey pleasantly small—provided package ownership and USB permissions are kept explicit.

What libusb does

libusb is a user-space C library for portable access to USB devices across Linux, Windows, macOS, BSD-family systems, Haiku, Solaris, and WebUSB targets. It gives applications device discovery, descriptor access, control/bulk/interrupt/isochronous transfers, interface claiming, hotplug callbacks, and asynchronous I/O without exposing one application API per host operating system.

Kernel, permissions, and process boundaries

  • The kernel owns host-controller and USB device drivers. libusb communicates through the operating system’s user-space backend; it is not a replacement kernel driver.

  • Enumeration and descriptor reading may work when opening an interface does not. On Linux, udev permissions and a bound kernel driver commonly determine access.

  • Claiming an interface can conflict with a kernel driver. Detach only when the device design and application lifecycle justify it, and reattach/clean up responsibly.

  • A successful transfer also depends on endpoint direction/type, maximum packet size, timeout policy, device state, and protocol—not only vendor/product IDs.

Path A: install Ubuntu’s development package

Terminalbash
sudo apt update
sudo apt install build-essential pkg-config libusb-1.0-0-dev

Risk level: caution. Review the command before running it.

What APT installs

  • libusb-1.0-0-dev provides the public header, linker metadata, and development symlink matching Ubuntu’s packaged runtime library.

  • build-essential supplies the native compiler/build baseline; pkg-config resolves consumer flags from libusb-1.0.pc.

  • APT owns these files and can update them consistently with the operating system. Review the proposed package changes before confirming.

  • The package version may trail upstream by design. That is often preferable for stable distro integration unless the application requires a newer API or fix.

Verify the packaged development interface

Terminalbash
pkg-config --modversion libusb-1.0
pkg-config --cflags libusb-1.0
pkg-config --libs libusb-1.0
dpkg -L libusb-1.0-0-dev | grep -E 'libusb\.h|libusb-1\.0\.pc'

Read the metadata rather than hard-coding paths

  • The pkg-config module name is libusb-1.0, while the header is conventionally included as <libusb-1.0/libusb.h>.

  • --cflags returns required compiler include flags and --libs returns linker flags such as -lusb-1.0.

  • dpkg -L proves which files belong to Ubuntu’s development package. It does not prove which shared object a finished process loads.

  • Build systems should query pkg-config or an appropriate imported target rather than assuming /usr/include and /usr/lib.

Path B: build a reviewed upstream release

Upstream’s officially supported build system is Autotools. Use an official release tarball when possible because upstream notes that its release .tar.bz assets contain generated configure, while GitHub-generated source archives are snapshots without it. The Git-tag workflow below intentionally runs autogen.sh.

Install source-build prerequisites

Terminalbash
sudo apt update
sudo apt install git build-essential autoconf automake libtool pkg-config libudev-dev

Risk level: caution. Review the command before running it.

Why each build dependency exists

  • Autoconf, Automake, and Libtool generate/configure the portable upstream build from a Git checkout.

  • libudev-dev supports libusb’s normal Linux device-discovery integration.

  • pkg-config is used both while configuring dependencies and later by consumer programs.

  • Documentation/examples can have optional dependencies; read the selected release’s README and configure summary instead of installing unrelated packages blindly.

Clone a tag, not a moving branch

Terminalbash
git clone --branch v1.0.30 --depth 1 https://github.com/libusb/libusb.git
cd libusb
git describe --tags --always
git status --short

Pinning makes the build explainable

  • --branch v1.0.30 selects a concrete reviewed release and --depth 1 avoids downloading unrelated history. Substitute a newer stable tag only after reviewing upstream releases.

  • git describe records the source identity used by the build.

  • An empty git status --short confirms no local source changes before configuration.

  • For supply-chain-sensitive builds, verify the release artifact/signature or commit through your organization’s trusted process; HTTPS and a tag name alone are not a complete provenance policy.

Configure an isolated install prefix

libusbbash
./autogen.sh --prefix="$PWD/_install"
make -j"$(nproc)"
make check
make install

Why the local prefix matters

  • autogen.sh generates the Autotools files from a Git checkout and passes the prefix to configuration. Inspect its final feature summary.

  • make -j"$(nproc)" parallelizes compilation across detected processors; reduce concurrency on memory-limited builders.

  • make check runs the tests available for that configuration. Hardware/backend coverage may remain limited, so passing tests are necessary evidence—not complete validation.

  • make install writes under the repository’s _install directory without root access, keeping the experiment separate from APT-managed files.

Inspect and activate the custom prefix

libusbbash
find _install -maxdepth 3 -type f -o -type l
export PKG_CONFIG_PATH="$PWD/_install/lib/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}"
pkg-config --modversion libusb-1.0
pkg-config --variable=prefix libusb-1.0

Keep build-time and runtime lookup separate

  • find shows the installed headers, libraries, symlinks, and metadata without assuming an exact library filename. Parentheses would be needed if adding more predicates/actions to a complex find expression.

  • Prepending PKG_CONFIG_PATH makes the compiler query resolve the isolated .pc file for this shell.

  • The reported prefix should end in the checkout’s _install; otherwise the system package is still winning.

  • pkg-config controls compilation/link flags. The dynamic loader uses its own search rules at runtime; solve deployment with packaging, an intentional rpath, or administrator-managed loader configuration—not a permanent ad-hoc environment variable.

A complete libusb enumeration program

list_usb.cc
#include <inttypes.h>
#include <stdio.h>
#include <libusb-1.0/libusb.h>
 
int main(void) {
    libusb_context *ctx = NULL;
    libusb_device **devices = NULL;
 
    int rc = libusb_init(&ctx);
    if (rc < 0) {
        fprintf(stderr, "libusb_init: %s\n", libusb_error_name(rc));
        return 1;
    }
 
    ssize_t count = libusb_get_device_list(ctx, &devices);
    if (count < 0) {
        fprintf(stderr, "get_device_list: %s\n",
                libusb_error_name((int)count));
        libusb_exit(ctx);
        return 1;
    }
 
    for (ssize_t i = 0; i < count; ++i) {
        struct libusb_device_descriptor descriptor;
        rc = libusb_get_device_descriptor(devices[i], &descriptor);
        if (rc < 0) {
            fprintf(stderr, "descriptor: %s\n", libusb_error_name(rc));
            continue;
        }
 
        printf("bus %03u device %03u: %04" PRIx16 ":%04" PRIx16 "\n",
               libusb_get_bus_number(devices[i]),
               libusb_get_device_address(devices[i]),
               descriptor.idVendor, descriptor.idProduct);
    }
 
    libusb_free_device_list(devices, 1);
    libusb_exit(ctx);
    return 0;
}

Initialize libusb, enumerate descriptors, release the device list, and shut down the context.

The ownership rules are the real lesson

  • libusb_init(&ctx) creates an explicit library context and returns zero or a negative libusb_error code.

  • libusb_get_device_list returns a signed count or a negative error and gives each listed device a reference.

  • libusb_get_device_descriptor reads the standard descriptor without opening a device handle; the program continues past a per-device error.

  • libusb_free_device_list(devices, 1) frees the list and unreferences its device objects. The final libusb_exit(ctx) releases context resources on both success and error paths.

Compile, inspect, and run the test

Terminalbash
cc -std=c11 -Wall -Wextra -Wpedantic \
  list_usb.c -o list_usb \
  $(pkg-config --cflags --libs libusb-1.0)
ldd ./list_usb | grep -E 'libusb|not found'
./list_usb

Three different failures, three different layers

  • A missing header is a compiler/include-path problem. Confirm pkg-config --cflags and the selected prefix.

  • An undefined reference is a link command/order problem. Place the source/object before library flags and inspect pkg-config --libs.

  • ldd reporting not found is a runtime loader problem, even if compilation succeeded. For a temporary isolated-prefix test, use a scoped LD_LIBRARY_PATH; production should package/configure the loader deliberately.

  • LIBUSB_ERROR_ACCESS after later opening a device is a permissions/driver issue, not proof that the library was linked incorrectly.

Give a non-root application device access with udev

99-example-usb.rulestext
SUBSYSTEM=="usb", ATTR{idVendor}=="1234", ATTR{idProduct}=="5678", GROUP="plugdev", MODE="0660"

Example narrow udev rule; replace identifiers and group with values approved for the target device.

Treat the rule as an access-control change

  • Replace the example IDs with values verified from lsusb/descriptors. A vendor-only wildcard can grant access to unrelated devices.

  • MODE="0660" grants owner/group read-write access without making the device world-writable.

  • Choose an existing managed group and explicitly enroll only authorized users; group membership commonly takes effect at a new login session.

  • Rules are evaluated when a device is added. Reloading rules does not necessarily reapply ownership to an already-connected device; reconnect or trigger it deliberately and verify with udevadm info/filesystem permissions.

Cross-platform realities

  • On Windows, a compatible user-space driver such as WinUSB may need to bind to the interface; changing a production device’s driver can break its normal application.

  • On macOS, entitlement, sandbox, interface ownership, and platform driver behavior differ from Linux udev.

  • On Android, applications normally use platform USB host APIs and permissions; native libusb integration depends on file descriptors/build design and is not equivalent to desktop root access.

  • Hotplug support and backend behavior vary by platform and libusb build options. Feature-test and exercise the exact target OS rather than assuming source portability guarantees operational parity.

Troubleshooting checklist

  • `autogen.sh: command not found` or macro errors: verify the Git checkout and Autotools prerequisites; official release tarballs already contain configure.

  • `libudev.h` missing: install the distro’s libudev development package or make an intentional documented backend choice.

  • Wrong libusb version: compare pkg-config --variable=prefix, compile flags, ldd, and loader cache; do not trust PATH.

  • Program works only with sudo: stop using root as the workaround; create a narrow udev rule and verify device ownership/group access.

  • Device is busy: identify the bound kernel/process owner and understand the protocol before detaching a driver or killing a service.

  • Examples fail to link: ensure they use the same build/prefix metadata as the library and avoid mixing source-tree and installed headers.

  • Static link fails: request pkg-config --static --libs libusb-1.0 and satisfy private dependencies/license/deployment requirements.

Primary references