Working Rust code insight in CLion
CLion is a fantastic IDE. And I say this as a lifelong emacs evangelist, so you know it pains me to say that. On top of being a fantastic IDE, it also has first-class support for Rust, so it’s my daily driver these days.
There has been one minor issue I’ve experienced though - in my AVR projects
code insight and some features (like detecting which #[cfg(...)]
directives
are enabled) don’t work properly from CLion. With the invaluable help
of Anna at JetBrains I’ve managed to work out what the problem is, and
a workaround.
The Problem⌗
The problem is fundamentally a bug in Rust, rather than with CLion per se.
CLion uses the cargo rustc --print cfg
command to identify the current
build configuration for a project. Unfortunately, when you use a custom
JSON target specification, like this in .cargo/config.toml
…
[build]
target = "target-specs/avr-atmega4809.json"
…that cargo rustc --print cfg
command fails, like so:
timwa% cargo rustc -Z unstable-options --print cfg
error: Error loading target specification: Could not find specification for target "avr-atmega4809". Run `rustc --print target-list` for a list of built-in targets
It seems the --print cfg
command doesn’t properly parse our config file.
The Solution⌗
Fortunately, there is a workaround. If we set the RUST_TARGET_PATH
to the
directory containing our target JSON file, then it will work:
timwa% setenv RUST_TARGET_PATH target-specs
timwa% cargo rustc -Z unstable-options --print cfg
debug_assertions
panic="unwind"
target_abi=""
target_arch="avr"
target_endian="little"
target_env=""
target_has_atomic_equal_alignment="8"
target_has_atomic_load_store="8"
target_os="unknown"
target_pointer_width="16"
target_vendor="unknown"
Of course, now that we know the workaround, we want a way to set that environment variable on a per-project basis (not all my projects use a custom target.json, and even if they did it might not always be in the same place…)
Looking at the documentation for .cargo/config.toml
might suggest we can
use an [env]
section. Unfortunately, that doesn’t work for some reason
(the documented environment variable substitution doesn’t seem to take place
when rustc
is invoked directly with cargo rustc
.)
What does work though is providing a small wrapper-script that will wrap
the rustc
invocation.
Permanent fix⌗
Modify your .cargo/config.toml
like so:
[build]
target = "target-specs/avr-atmega4809.json"
rustc-wrapper = "./rustc-wrapper.sh"
Create a wrapper script that looks something like this:
#!/bin/sh
# rustc-wrapper
#
# Use this script in the `[build]`,`rustc-wrapper` section of your
# `.cargo/config.toml` configuration file to correctly set the
# `RUST_TARGET_PATH` environment variable before running rustc.
#
# This is to workaround a bug whereby `rustc --print cfg` fails when you
# use a custom `target.json` file, unless `RUST_TARGET_PATH` is set to the
# directory containing the target description.
# `cargo` passes "rustc " as the first parameter to the wrapper; pop it off
shift
# Extract the target config JSON from the `config.toml`
#
# NOTE: We assume that this wrapper script is in the root of your project, and
# that the `build.target` value is relative to the location of the same.
#
TARGET_JSON_FILE=$(cargo config -Z unstable-options get --format json-value build.target 2>/dev/null || echo "")
if [ ! -z "${TARGET_JSON_FILE}" ]; then
TARGET_JSON_DIR=$(dirname "${TARGET_JSON_FILE//\"/}")
# Work out where we are and which rustup we're wrapping
BASEDIR=$(dirname "$0")
# Set the env variable
export RUST_TARGET_PATH="${BASEDIR}/${TARGET_JSON_DIR}"
fi
RUSTC=$(rustup which rustc)
# Now let rustc do its thing
"${RUSTC}" $@
And, hey presto - working code insights in CLion :-).