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