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 :-).
