# $Id: rust.mk,v 1.38 2025/08/09 22:42:24 sjg Exp $ # # @(#) Copyright (c) 2024, Simon J. Gerraty # # SPDX-License-Identifier: BSD-2-Clause # # Please send copies of changes and bug-fixes to: # sjg@crufty.net # ## # This makefile is used when a build includes one or more Rust projects. # # We first include local.rust.mk to allow for customization. # You can get very fancy - the logic/functionality here is minimal but # can be extended via local.rust.mk # # If RUST_PROJECT_DIR (where we find Cargo.toml) is not set, we will # make it ${.CURDIR:C,/src.*,,} actually we use # ${SRCTOP}/${RELDIR:C,/src.*,,} to ensure we don't confuse ${SRCTOP} # with ${RUST_PROJECT_DIR}/src. # # If ${.OBJDIR} is not ${.CURDIR} we will default CARGO_TARGET_DIR # to ${.OBJDIR}. # # First, if ${.CURDIR} is a subdir of ${RUST_PROJECT_DIR} (will happen # if an Emacs user does 'M-x compile' while visiting a src file) we # will need to adjust ${.OBJDIR} (and hence CARGO_TARGET_DIR). # # We assume that RUST_CARGO will be used to build Rust projects, # so we default RUST_CARGO_PROJECT_DIR to ${RUST_PROJECT_DIR} and # provide a _CARGO_USE that we automatically associate with # targets named 'cargo.*' the default is 'cargo.build'. # # _CARGO_USE will chdir to ${RUST_CARGO_PROJECT_DIR} and run # ${RUST_CARGO} with ENV, FLAGS and ARGS variables derived from # ${.TARGET:E:tu} so in the case of 'cargo.build' we get: # RUST_CARGO_BUILD_ENV, RUST_CARGO_BUILD_FLAGS and RUST_CARGO_BUILD_ARGS # # _CARGO_USE will "just work" for additional targets like # 'cargo.test', 'cargo.clippy', ... which will run '${RUST_CARGO} test', # '${RUST_CARGO} clippy' etc. # # If MK_META_MODE is "yes" 'cargo.build' will touch ${.TARGET} # so the default make rules will not consider it always out-of-date. # In META MODE, 'bmake' will know if anything changed that should # cause the target to be re-built. # # If MK_STAGING_RUST is "yes" we will stage the binary we # built to a suitable location under ${STAGE_OBJTOP}. # all: .MAIN: all # allow for customization .-include RUST_CARGO ?= cargo RUSTC ?= rustc .if ${.CURDIR} == ${SRCTOP} RELDIR ?= . .else RELDIR ?= ${.CURDIR:S,${SRCTOP}/,,} .endif .if empty(RUST_PROJECT_DIR) # we want this set correctly from anywhere within # using RELDIR avoids confusing ${SRCTOP} with ${RUST_PROJECT_DIR}/src RUST_PROJECT_DIR := ${SRCTOP}/${RELDIR:C,/src.*,,} .if ${RUST_PROJECT_DIR:T:Nsrc:N.} == "" RUST_PROJECT_DIR := ${RUST_PROJECT_DIR:H} .endif .endif .if ${.OBJDIR} != ${.CURDIR} .if ${.CURDIR:M${RUST_PROJECT_DIR}/*} != "" # Our .CURDIR is below RUST_PROJECT_DIR and thus our # .OBJDIR is likely not what we want either. # This can easily happen if in Emacs we do 'M-x compile' while # visiting a src file. # It is easily fixed. __objdir := ${.OBJDIR:S,${.CURDIR:S,${RUST_PROJECT_DIR},,},,} .OBJDIR: ${__objdir} .endif # tell cargo where to drop build artifacts CARGO_TARGET_DIR ?= ${.OBJDIR} .if !empty(OBJROOT) && exists(${OBJROOT}) CARGO_HOME_RELDIR ?= rust/cargo_home CARGO_HOME ?= ${OBJROOT}/common/${RUST_CARGO_HOME_RELDIR} .endif .elif ${.CURDIR} != ${RUST_PROJECT_DIR} .OBJDIR: ${RUST_PROJECT_DIR} .endif CARGO_TARGET_DIR ?= target .if ${MK_DIRDEPS_BUILD:Uno} == "no" || ${.MAKE.LEVEL} > 0 .export CARGO_HOME CARGO_TARGET_DIR RUST_PROJECT_DIR RUSTC all: cargo.build .if empty(RUST_PROJECT_FILES) RUST_PROJECT_FILES != find ${RUST_PROJECT_DIR} -type f \( \ -name '*.rs' -o \ -name Cargo.lock -o \ -name Cargo.toml \) | sort .endif RUST_CARGO_BUILD_DEPS += ${RUST_PROJECT_FILES:U} .endif RUST_CARGO_PROJECT_DIR ?= ${RUST_PROJECT_DIR} .if ${RUSTC:M/*} # make sure we find all the other toolchain bits in the same place RUST_CARGO_ENV += PATH=${RUSTC:H}:${PATH} # cargo clippy needs extra help finding the sysroot # https://github.com/rust-lang/rust-clippy/issues/3523 RUST_CARGO_CLIPPY_ENV += RUSTC_SYSROOT=${${RUSTC} --print sysroot:L:sh} .endif .if ${LDFLAGS:U:M-[BL]*} != "" # we may need to tell rustc where to find the native libs needed # rustc documents a space after -L so put it back RUST_LDFLAGS := ${LDFLAGS:C/(-[BL]) /\1/gW:M-[BL]*:S/-L/& /:S/-B/-C link-arg=&/} .endif .if !empty(RUST_LDFLAGS) RUSTFLAGS += ${RUST_LDFLAGS} .endif .if !empty(RUSTFLAGS) RUST_CARGO_BUILD_ENV += RUSTFLAGS="${RUSTFLAGS}" .endif _CARGO_USE: .USEBEFORE @(cd ${RUST_CARGO_PROJECT_DIR} && ${RUST_CARGO_ENV} \ ${RUST_CARGO_${.TARGET:E:tu}_ENV} \ ${RUST_CARGO} ${RUST_CARGO_${.TARGET:E:tu}_FLAGS:U${RUST_CARGO_FLAGS}} \ ${.TARGET:E} ${RUST_CARGO_${.TARGET:E:tu}_ARGS}) RUST_CARGO_TARGETS += cargo.build cargo.build: ${RUST_CARGO_BUILD_DEPS} .if ${.OBJDIR} != ${RUST_PROJECT_DIR} test ! -s Cargo.lock || cp -p Cargo.lock ${RUST_CARGO_PROJECT_DIR} .endif @${META_COOKIE_TOUCH} # handle cargo.{run,test,...} RUST_CARGO_TARGETS += ${.TARGETS:Mcargo.*} ${RUST_CARGO_TARGETS:O:u}: _CARGO_USE .if ${MK_DEBUG_RUST:Uno} == "no" && \ ${DEBUG_RUST_DIRS:Unone:@x@${RELDIR:M$x}@} == "" RUST_CARGO_BUILD_ARGS += --release .endif .if ${RUST_CARGO_BUILD_ARGS:U:M--release} != "" RUST_CARGO_TARGET = release .else RUST_CARGO_TARGET = debug .endif # do we want cargo.build to depend on cargo.fmt --check ? # if user did make cargo.fmt the target would exist by now .if ${MK_RUST_CARGO_FMT_CHECK:Uno} == "yes" && !target(cargo.fmt) RUST_CARGO_FMT_CHECK_ARGS ?= --check RUST_CARGO_FMT_ARGS += ${RUST_CARGO_FMT_CHECK_ARGS} cargo.fmt: _CARGO_USE cargo.build: cargo.fmt .endif # useful? defaults RUST_CARGO_CLIPPY_ARGS ?= -- -D warnings --no-deps # do we want cargo.clippy to be run after cargo.build? .if ${MK_RUST_CARGO_CLIPPY:Uno} == "yes" && !target(cargo.clippy) cargo.clippy: _CARGO_USE cargo.clippy: cargo.build all: cargo.clippy .endif .if !defined(RUST_LIBS) RUST_PROGS ?= ${RUST_PROJECT_DIR:T} .endif .if !empty(RUST_PROGS) BINDIR ?= ${prefix}/bin # there could be a target triple involved RUST_CARGO_TARGET_DIR ?= ${CARGO_TARGET_DIR} RUST_CARGO_OUTPUT_DIR ?= ${RUST_CARGO_TARGET_DIR}/${RUST_CARGO_TARGET} RUST_CARGO_BUILD_OUTPUT_LIST := ${RUST_PROGS:S,^,${RUST_CARGO_OUTPUT_DIR}/,} ${RUST_CARGO_BUILD_OUTPUT_LIST}: cargo.build .endif # for late customizations .-include