gem.snowgoons.ro

CLion[1] is a fantastic IDE. And I say this as a lifelong emacs[2] 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[3] 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 :-).

1: https://www.jetbrains.com/clion/

2: https://www.gnu.org/software/emacs/

3: https://toolbox-support.jetbrains.com/hc/en-us/profiles/1283061991-Anna-Filippova

--------------------

Home - gem.snowgoons.ro