libinput-rs 0.3.1

100% drop-in Rust replacement for the libinput 1.31.3 C ABI and tools
Documentation
PREFIX ?= /usr
LIBDIR ?= $(PREFIX)/lib64
DESTDIR ?=
FC = gfortran
REFERENCE_LIBINPUT ?= /usr/lib64/libinput.so.10
RPM_RUNTIME ?=
RPM_TOPDIR ?= $(HOME)/rpmbuild
PACKAGE_NAME := $(shell rpmspec -q --srpm --qf '%{NAME}' libinput-rs.spec 2>/dev/null)
PACKAGE_VERSION := $(shell rpmspec -q --srpm --qf '%{VERSION}' libinput-rs.spec 2>/dev/null)
SOURCE_ARCHIVE := $(RPM_TOPDIR)/SOURCES/$(PACKAGE_NAME)-$(PACKAGE_VERSION).tar.gz
UPSTREAM_TOOLS_URL := $(shell rpmspec -P libinput-rs.spec 2>/dev/null | awk '/^Source1:/ { print $$2; exit }')
UPSTREAM_TOOLS_SHA256 := $(shell awk '$$1 == "%global" && $$2 == "libinput_tools_sha256" { print $$3; exit }' libinput-rs.spec)
UPSTREAM_TOOLS_ARCHIVE := $(RPM_TOPDIR)/SOURCES/$(notdir $(UPSTREAM_TOOLS_URL))
UPSTREAM_TOOLS_ROOT := target/upstream-tools
UPSTREAM_TOOLS_SOURCE_DIR := $(UPSTREAM_TOOLS_ROOT)/source
UPSTREAM_TOOLS_BUILD_DIR := $(UPSTREAM_TOOLS_ROOT)/build
UPSTREAM_TOOLS_STAGE_DIR := $(UPSTREAM_TOOLS_ROOT)/stage

.PHONY: all build shared check packaging-check crate-package-check main-crate-package-check source-archive upstream-tools-source upstream-tools srpm rpm-package-check test abi-check proofs proofs-strict install

all: build shared

build:
	CARGO_NET_OFFLINE=true CARGO_PROFILE_RELEASE_DEBUG=2 cargo build --frozen --release --bins

shared:
	CARGO_NET_OFFLINE=true CARGO_PROFILE_RELEASE_DEBUG=2 ./build-shared.sh

check: packaging-check
	cargo check --locked --workspace
	cargo clippy --locked --workspace --all-targets -- -D warnings
	cargo fmt --all -- --check

packaging-check:
	grep -Eq '^Provides: *libinput( |%)' libinput-rs.spec
	grep -Eq '^Obsoletes: *libinput ' libinput-rs.spec
	grep -Eq '^Provides: *libinput-devel( |%)' libinput-rs.spec
	grep -Eq '^Obsoletes: *libinput-devel ' libinput-rs.spec
	! grep -q '^%package devel' libinput-rs.spec
	! grep -q '^%files devel' libinput-rs.spec
	! test -e systemd/libinput-rs.service
	! test -e systemd/90-libinput-rs.preset
	test -f systemd/libinput-rs-elan-resume.service
	test -f systemd/91-libinput-rs-elan.preset
	grep -q '%{_libdir}/libinput.so.10.13.0' libinput-rs.spec
	grep -q '%{_libdir}/libinput.so.10' libinput-rs.spec
	grep -q '%{_includedir}/libinput.h' libinput-rs.spec
	grep -q '%{_libdir}/pkgconfig/libinput.pc' libinput-rs.spec
	grep -q '%{_prefix}/lib/udev/libinput-fuzz-to-zero' libinput-rs.spec
	grep -q '%{_udevrulesdir}/90-libinput-fuzz-override.rules' libinput-rs.spec
	grep -q '%{_udevrulesdir}/90-libinput-rs-elantech-crc.rules' libinput-rs.spec
	grep -q '%{_unitdir}/libinput-rs-elan-resume.service' libinput-rs.spec
	grep -q '%{_presetdir}/91-libinput-rs-elan.preset' libinput-rs.spec
	grep -q '%{_datadir}/libinput/\*.quirks' libinput-rs.spec
	grep -q '%{_bindir}/libinput' libinput-rs.spec
	grep -q '%{_bindir}/libinput-rs-chwd' libinput-rs.spec
	grep -q '%{_libexecdir}/libinput/libinput-\*' libinput-rs.spec
	grep -q '^Source1:.*%{libinput_tools_commit}' libinput-rs.spec
	grep -q '^%global libinput_tools_commit 26191d396d74d505541d6311f0b4ae68d791b890' libinput-rs.spec
	grep -q '^%global libinput_tools_sha256 d5d8c8464f9cb24b0897c03edfe7d7c9e75ff5a91fe9b5b48791781aa9642858' libinput-rs.spec
	grep -q 'libinput-tool' libinput-rs.spec
	grep -q 'libinput-replay' libinput-rs.spec
	test -f packaging/libinput-1.31.3-meson-0.63.patch
	grep -q 'libinput-1.31.3-meson-0.63.patch' libinput-rs.spec
	grep -Eq '^BuildRequires: +patch' libinput-rs.spec
	! test -e packaging/libinput.1
	! test -e packaging/_libinput
	grep -Eq '^Requires: +python3-libevdev' libinput-rs.spec
	grep -Eq '^Requires: +python3-pyudev' libinput-rs.spec
	grep -Eq '^Requires: +python3-pyyaml' libinput-rs.spec
	grep -Eq '^install .*%\{_libdir\}/libinput\.so\.10' libinput-rs.spec
	! grep -Eq '^BuildRequires: *(Agda|idris2)' libinput-rs.spec
	grep -Eq '^BuildRequires: *gcc-gfortran' libinput-rs.spec
	grep -Fq 'default = []' Cargo.toml
	grep -Fq 'libwacom = []' Cargo.toml
	grep -Fq '#[cfg(feature = "libwacom")]' src/backend.rs
	grep -Fq 'native_libraries+=(-lwacom)' build-shared.sh
	grep -Fq '%if 0%{?fedora}' libinput-rs.spec
	grep -Fq '%global cargo_features --features libwacom' libinput-rs.spec
	grep -Fq 'BuildRequires:  libwacom-devel >= 2.18' libinput-rs.spec
	test "$(PACKAGE_VERSION)" = "$$(awk '/^\[package\]/{package=1; next} package && /^version = /{gsub(/[" ]/, "", $$3); print $$3; exit}' Cargo.toml)"
	test -f packaging/libinput.h
	test -f packaging/libinput-rs.pc.in
	test -f packaging/libinput-rs-smoke.c
	test -f packaging/rpmlintrc
	test -x scripts/verify-rpm-package.sh

crate-package-check:
	cargo metadata --locked --offline --no-deps >/dev/null
	! grep -q '^publish = false' Cargo.toml
	grep -Eq '^evdev-upstream = \{ package = "evdev", version = "=0\.13\.2" \}$$' Cargo.toml
	cargo metadata --locked --offline --no-deps --format-version 1 | \
		grep -Eq '"workspace_members":\["[^"]*libinput-rs#[^"]*"\]'

main-crate-package-check:
	cd /tmp && cargo package --locked --no-verify --allow-dirty --package libinput-rs \
		--manifest-path "$(CURDIR)/Cargo.toml"
	cd /tmp && ! cargo package --locked --list --allow-dirty --package libinput-rs \
		--manifest-path "$(CURDIR)/Cargo.toml" | grep -Eq '/(vendor|\.cargo|rpmbuild)'

source-archive:
	mkdir -p "$(RPM_TOPDIR)/SOURCES"
	tar --exclude='./target' --exclude='./rpmbuild' --exclude='./rpmbuild2' \
		--exclude='./proofs/fortran/build' --exclude='./.git' \
		--exclude='./audit.py' --exclude='./fix_compile.py' \
		--transform='s|^\./|$(PACKAGE_NAME)-$(PACKAGE_VERSION)/|' \
		-czf "$(SOURCE_ARCHIVE)" .

upstream-tools-source:
	mkdir -p "$(RPM_TOPDIR)/SOURCES"
	@if ! echo "$(UPSTREAM_TOOLS_SHA256)  $(UPSTREAM_TOOLS_ARCHIVE)" | sha256sum -c --status 2>/dev/null; then \
		curl --fail --location --retry 3 --output "$(UPSTREAM_TOOLS_ARCHIVE).part" "$(UPSTREAM_TOOLS_URL)"; \
		echo "$(UPSTREAM_TOOLS_SHA256)  $(UPSTREAM_TOOLS_ARCHIVE).part" | sha256sum -c -; \
		mv "$(UPSTREAM_TOOLS_ARCHIVE).part" "$(UPSTREAM_TOOLS_ARCHIVE)"; \
	fi
	echo "$(UPSTREAM_TOOLS_SHA256)  $(UPSTREAM_TOOLS_ARCHIVE)" | sha256sum -c -

$(UPSTREAM_TOOLS_SOURCE_DIR)/.stamp: | upstream-tools-source
	mkdir -p "$(UPSTREAM_TOOLS_SOURCE_DIR)"
	tar -xzf "$(UPSTREAM_TOOLS_ARCHIVE)" --strip-components=1 -C "$(UPSTREAM_TOOLS_SOURCE_DIR)"
	patch -d "$(UPSTREAM_TOOLS_SOURCE_DIR)" -p1 < packaging/libinput-1.31.3-meson-0.63.patch
	touch "$@"

$(UPSTREAM_TOOLS_BUILD_DIR)/.stamp: $(UPSTREAM_TOOLS_SOURCE_DIR)/.stamp
	meson setup "$(UPSTREAM_TOOLS_BUILD_DIR)" "$(UPSTREAM_TOOLS_SOURCE_DIR)" \
		--buildtype=release --prefix="$(PREFIX)" --libdir="$(notdir $(LIBDIR))" \
		-Dtests=false -Ddocumentation=false -Ddebug-gui=false \
		-Dlibwacom=false -Dlua-plugins=disabled
	meson compile -C "$(UPSTREAM_TOOLS_BUILD_DIR)"
	touch "$@"

$(UPSTREAM_TOOLS_STAGE_DIR)/.stamp: $(UPSTREAM_TOOLS_BUILD_DIR)/.stamp
	mkdir -p "$(UPSTREAM_TOOLS_STAGE_DIR)"
	DESTDIR="$(abspath $(UPSTREAM_TOOLS_STAGE_DIR))" \
		meson install -C "$(UPSTREAM_TOOLS_BUILD_DIR)" --no-rebuild
	touch "$@"

upstream-tools: $(UPSTREAM_TOOLS_STAGE_DIR)/.stamp

srpm: source-archive upstream-tools-source
	mkdir -p "$(RPM_TOPDIR)/SRPMS"
	rpmbuild -bs libinput-rs.spec --define "_topdir $(RPM_TOPDIR)"

rpm-package-check:
	test -n "$(RPM_RUNTIME)"
	scripts/verify-rpm-package.sh "$(RPM_RUNTIME)"

test:
	cargo test --locked --workspace

abi-check: shared
	scripts/check-abi.sh "$(REFERENCE_LIBINPUT)" target/release/libinput.so

proofs:
	cd proofs/agda && agda ButtonLifecycle.agda
	cd proofs/agda && agda FailOpen.agda
	cd proofs/agda && agda ResourceLifecycle.agda
	cd proofs/agda && agda RestrictedDiscovery.agda
	cd proofs/agda && agda --safe HwDetect.agda
	cd proofs/agda && agda --safe ProfileSelection.agda
	cd proofs/idris && idris2 --check ButtonLifecycle.idr
	cd proofs/idris && idris2 --check FailOpen.idr
	cd proofs/idris && idris2 --check ResourceLifecycle.idr
	cd proofs/idris && idris2 --check RestrictedDiscovery.idr
	cd proofs/idris && idris2 --check HwSpec.idr
	cd proofs/idris && idris2 --check ProfileSelection.idr
	mkdir -p proofs/fortran/build
	$(FC) -std=f2018 -Wall -Wextra -Werror -fcheck=all \
		-J proofs/fortran/build -o proofs/fortran/build/fail-open \
		proofs/fortran/fail_open.f90
	proofs/fortran/build/fail-open
	$(FC) -std=f2018 -Wall -Wextra -Werror -fcheck=all \
		-J proofs/fortran/build -o proofs/fortran/build/resource-lifecycle \
		proofs/fortran/resource_lifecycle.f90
	proofs/fortran/build/resource-lifecycle
	$(FC) -std=f2018 -Wall -Wextra -Werror -fcheck=all \
		-J proofs/fortran/build -o proofs/fortran/build/restricted-discovery \
		proofs/fortran/restricted_discovery.f90
	proofs/fortran/build/restricted-discovery
	$(FC) -std=f2018 -Wall -Wextra -Werror -fcheck=all \
		-J proofs/fortran/build -o proofs/fortran/build/button-lifecycle \
		proofs/fortran/button_lifecycle.f90
	proofs/fortran/build/button-lifecycle

proofs-strict:
	command -v agda >/dev/null
	command -v idris2 >/dev/null
	command -v "$(FC)" >/dev/null
	$(MAKE) proofs FC="$(FC)"

install: all upstream-tools
	install -Dm755 target/release/libinput $(DESTDIR)$(PREFIX)/bin/libinput
	ln -sf libinput $(DESTDIR)$(PREFIX)/bin/libinput-rs
	install -Dm755 target/release/libinput-rs-chwd $(DESTDIR)$(PREFIX)/bin/libinput-rs-chwd
	install -d $(DESTDIR)$(PREFIX)/libexec/libinput
	install -Dm755 $(UPSTREAM_TOOLS_STAGE_DIR)$(PREFIX)/bin/libinput \
		$(DESTDIR)$(PREFIX)/libexec/libinput/libinput-tool
	for helper in $(UPSTREAM_TOOLS_STAGE_DIR)$(PREFIX)/libexec/libinput/libinput-*; do \
		test "$$(basename "$$helper")" = libinput-test && continue; \
		install -Dm755 "$$helper" $(DESTDIR)$(PREFIX)/libexec/libinput/"$$(basename "$$helper")"; \
	done
	for helper in libinput-analyze-buttons libinput-analyze-per-slot-delta \
		libinput-analyze-recording libinput-analyze-touch-down-state \
		libinput-list-kernel-devices libinput-measure-fuzz \
		libinput-measure-touch-size libinput-measure-touchpad-pressure \
		libinput-measure-touchpad-size libinput-measure-touchpad-tap \
		libinput-replay; do \
		sed -i '1s|^#!/usr/bin/env python3$$|#!/usr/bin/python3|' \
			$(DESTDIR)$(PREFIX)/libexec/libinput/"$$helper"; \
	done
	install -Dm755 target/release/libinput-device-group $(DESTDIR)$(PREFIX)/lib/udev/libinput-device-group
	install -Dm755 target/release/libinput-fuzz-extract $(DESTDIR)$(PREFIX)/lib/udev/libinput-fuzz-extract
	install -Dm755 target/release/libinput-fuzz-to-zero $(DESTDIR)$(PREFIX)/lib/udev/libinput-fuzz-to-zero
	install -Dm644 packaging/80-libinput-device-groups.rules $(DESTDIR)$(PREFIX)/lib/udev/rules.d/80-libinput-device-groups.rules
	install -Dm644 packaging/90-libinput-fuzz-override.rules $(DESTDIR)$(PREFIX)/lib/udev/rules.d/90-libinput-fuzz-override.rules
	install -Dm644 packaging/90-libinput-rs-elantech-crc.rules $(DESTDIR)$(PREFIX)/lib/udev/rules.d/90-libinput-rs-elantech-crc.rules
	install -Dm644 systemd/libinput-rs-elan-resume.service $(DESTDIR)$(PREFIX)/lib/systemd/system/libinput-rs-elan-resume.service
	install -Dm644 systemd/91-libinput-rs-elan.preset $(DESTDIR)$(PREFIX)/lib/systemd/system-preset/91-libinput-rs-elan.preset
	install -d $(DESTDIR)$(PREFIX)/share/libinput
	install -m644 quirks/*.quirks $(DESTDIR)$(PREFIX)/share/libinput/
	install -Dm755 target/release/libinput.so $(DESTDIR)$(LIBDIR)/libinput.so.10.13.0
	ln -sf libinput.so.10.13.0 $(DESTDIR)$(LIBDIR)/libinput.so.10
	ln -sf libinput.so.10 $(DESTDIR)$(LIBDIR)/libinput.so
	install -Dm644 packaging/libinput.h $(DESTDIR)$(PREFIX)/include/libinput.h
	install -d $(DESTDIR)$(LIBDIR)/pkgconfig
	sed 's|@LIBDIR@|$(LIBDIR)|g' packaging/libinput-rs.pc.in > $(DESTDIR)$(LIBDIR)/pkgconfig/libinput.pc
	install -Dm644 packaging/libinput-rs.8 $(DESTDIR)$(PREFIX)/share/man/man8/libinput-rs.8
	install -Dm644 packaging/libinput-rs-chwd.8 $(DESTDIR)$(PREFIX)/share/man/man8/libinput-rs-chwd.8
	for manpage in $(UPSTREAM_TOOLS_STAGE_DIR)$(PREFIX)/share/man/man1/libinput*.1; do \
		test "$$(basename "$$manpage")" = libinput-test.1 && continue; \
		install -Dm644 "$$manpage" $(DESTDIR)$(PREFIX)/share/man/man1/"$$(basename "$$manpage")"; \
	done
	install -Dm644 $(UPSTREAM_TOOLS_STAGE_DIR)$(PREFIX)/share/zsh/site-functions/_libinput \
		$(DESTDIR)$(PREFIX)/share/zsh/site-functions/_libinput