Guides on getting started with Godot-Rust are a bit sparse, and if you're building on Windows, you may have noticed that the gdnative bindings will fail to build unless you're running cargo from within the appropriate Visual Studio Command Prompt.

In my case, I'm running 64-bit Windows and wish to build a 64-bit library, so I need to run cargo from within the x64 Native Tools Command Prompt for VS shell.

While IntelliJ IDEA offers the ability to change the shell used for its built-in terminal, it's a global setting that affects all projects and it does not affect the build commands. This means the regular build commands will fail for us in this situation, and we need an alternative to manually launching the correct command-prompt and running cargo ourselves.

Fortunately, there is a solution:

Shell Script is a plugin bundled with IntelliJ IDEA, and as the name would suggest, it allows us to create run/debug configurations that execute a shell script in either the currently configured or a custom specified shell.

With this, we can leverage a batch file to setup the desired VS shell environment for us, and perform a series of operations to simplify the build process, without the need to modify our global IDE settings.

We have two main steps we need to take, to properly integrate our custom build system.

  1. Write a batch file that will perform all the tasks we require, with easy to use and understand arguments that we can use to define build commands..
  2. Setup IntelliJ with custom run/debug configurations.

We also must make some assumptions about the development environment; deviating from these assumptions will require manually modifying our build system to account for the differences.

  1. You have IntelliJ IDEA installed, with the Rust plugin.
  2. Your dev environment is already properly configured for rust-godot (see here).
  3. The project hierarchy is setup so that your main project folder has at least two sub-directories: project which contains your Godot project, and a second directory (you can name it anything you like) that contains your Rust IntelliJ project. It's also assumed that your build targets go into the default IntelliJ build locations.

The Batch Script

Given those assumptions, our first major step is going to be writing our batch file.

A batch file is not necessarily our only option here; we could have also done it in bash (assuming you have WSL installed), and I'll even include a bash example later for those that would like to experiment with different setups. However, given how the VS command prompts work, this is one of those occasions where a batch file is the superior option.

@echo off
setlocal enableextensions

rem :TERMINAL COLORS
rem :More colors: https://stackoverflow.com/a/38617204/1131788
set RED=
set MAGENTA=
set CYAN=

rem :NO COLOR; resets the color drawn to the terminal.
set NC=

rem :Read the first argument passed to the script as the intended 
rem :build target.
set build_target=%1

rem :CLEAN TARGETS
rem :If passed `clean`, we naively remove the entire build target dir.
rem :Then jump to :echo_log_and_exit, to cleanly exit the script.
if %build_target%==clean (
	echo %RED%Cleaning targets%NC%
	RD /S /Q target

	goto :echo_log_and_exit
)

rem :Launch the appropriate VS Command Prompt; you may need to change this 
rem :depending on your intended architecture. Could alternatively be made 
rem :friendlier by supporting a second script argument that selects the  
rem :proper command prompt, based on the intended architecture.
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat"

rem :BUILD RELEASE TARGET
rem :If passed `release`, we build a release build of the lib. Under 
rem :default settings, this will go to target/release/<project_name>.dll
if %build_target%==release (
	echo Building %MAGENTA%release%NC% lib ...
	echo %MAGENTA%cargo build --release%NC%

	cargo build --release

rem :BUILD DEBUG TARGET
rem :If passed anything else, we assume a debug target. Under default 
rem :settings, this will go to target/debug/<project_name>.dll
) else (
	echo Building %CYAN%debug%NC% lib ...
	echo %CYAN%cargo build%NC%

	cargo build
)

rem :COPY BUILD TARGETS
rem :Copies our build output into our Godot project folder, so we don't 
rem :need to update them manually.
echo %RED%Copying output lib(s) ...%NC%
xcopy /Y target\%build_target%\*.dll ..\project\gdnative\%build_target%\

rem :Provides a clean and user-friendly way of exiting the script.
:echo_log_and_exit

echo Press any key to exit ...
pause > nul
exit 0

This batch file is largely straightforward. It accepts a single command-line argument, which it uses to determine our intended build target. For now, we only support three targets: release, debug and clean. All are pretty self-explanatory–release will create a release build, debug a debug build, and clean will remove all build target output.

We make sure to include some minor quality-of-life features to make using the script easier, such as terminal output colors to help identify commands, and a clean exit, that allows the user to review the output when the build finishes.

Save this batch file into the root of your IntelliJ project directory; you can name it anything you like.

Custom Run/Debug Configurations

Our next step is to setup IntelliJ, so that our script is incorporated in a way that it behaves like different build configurations. Now, as far as I can tell, IntelliJ lacks actual build configuration support for Rust, but it does support custom run-debug configurations. Other than a change of supported hot-keys to launch them, it effectively behaves the same for our purposes.

We have one major decision to make in how we setup these configurations. If you use cmd or powershell as your default IntelliJ terminal shell, we can ignore some configuration settings, and our custom builds will be fully and nicely integrated. If you use bash (WSL) or another shell as your default terminal shell, everything will still function properly, but we won't be able to support the execute in terminal option, which means our integration won't be as clean. Namely, after the script completes, you'll need to manually end the command process created to run the script. This is a flaw with IntelliJ and I don't know a way around it.

cmd/powershell

There are many guides on how to create custom run/debug configurations in IntelliJ, but it essentially boils down to going into the Run menu, and selecting Edit Configurations...

In the window that opens, add a new configuration by pressing the + button and selecting a Shell Script template. Inevitably we'll be creating three separate configurations, but they're similar enough that we can create one, and simply duplicate it and make our (minor) changes to create the other two.

Common configuration when IntelliJ uses cmd or powershell as its terminal shell.

When using cmd or powershell, the configuration is relatively simple. The shell script can be executed inside the IntelliJ terminal, so an interpreter doesn't need to be defined. Our main configuration options here, are pointing to our batch script and providing arguments to it. In this example, release to create a release build.

Once configured, we can duplicate this and just change the script options to debug and clean for the other two configurations. Make sure you don't forget to name all the configurations something descriptive.

<other shells>

If you're using a shell other than cmd/powershell, such as bash through WSL, then we need an alternative configuration. We'll no longer be able to execute our command through the integrated terminal. Uncheck Execute in terminal; this will make IntelliJ launch the shell defined in Interpreter path and attach it as a new process. The downside to this is that when the process ends, we need to manually close out the window IntelliJ opened for the process. Our script can end execution, but it no longer has direct control over the terminal window itself.

In this example configuration, you can see that we configured it to launch cmd.exe and are passing it the /C launch option, which normally would make the terminal window close automatically when execution is complete. Unfortunately this still won't automatically close the process window IntelliJ opens.

Common configuration when IntelliJ uses bash or other linux shell as its terminal shell.

Whichever configuration setup you go with, you can also check the Store as project file option, which makes these configurations portable, and can be included in version control. Although IntelliJ shows absolute paths, the paths are actually stored relative to the IntelliJ project directory, so these can easily be used in other Godot-Rust projects.

If you intend to include these configurations in version-control, you should modify the .gitignore file in your IntelliJ project directory, with these additional two lines:

.idea/*
!/.idea/runConfigurations

The dark arts alternative: bash through WSL

I did promise earlier that I would provide a bash script example, although a batch file is the superior alternative in this situation. This script functions perfectly, although a word of caution: IntelliJ doesn't like this alternative. It will pass the Windows path to this script to WSL, which then cannot find it, because it's expecting the Linux path. If you go this route, you'll need to develop a solution on your own.

#!/bin/bash
# Godot-Rust Build Script

RED='\033[0;31m'
PURPLE='\033[0;35m'
CYAN='\033[0;36m'
NC='\033[0m'		# No Color

build_mode="${1}"

#	bash is a nightmare when dealing with double-quotations and whether 
#	or not you want them escaped. Trying to directly pass commands that 
#	require double-quotations to cmd.exe is simply not going to work
#	properly, as bash will not pass them in a way that cmd.exe can 
#	interpret. Instead, this is the next-best solution: output the 
#	commands we'd like to pass into a temporary batch file, and then 
#	have cmd.exe run the batch file. We just have to remember to delete 
#	this file when we're done.
function create_temp_batch_file () {
	echo "@echo off" > temp_batch.bat
	echo -e "call \"C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Auxiliary\\Build\\\vcvars64.bat\"\n" >> temp_batch.bat
	echo -e "cargo build ${1}\n" >> temp_batch.bat
}

function remove_temp_batch_file {
	rm -f temp_batch.bat
}

function copy_build_results () {
	mkdir --parents -v "../project/gdnative/${1}/"
	cp --verbose "target/${1}/*.dll" "../project/gdnative/${1}/"
}

function press_key_to_exit {
	read -n 1 -s -r -p "Press any key to exit ..."
	echo
	exit 0
}

function launch_vs_shell () {	
	create_temp_batch_file $1
	cmd.exe /c temp_batch.bat

	remove_temp_batch_file
}

if [ "${build_mode}" = "clean" ];
then
	echo -e "${RED}Cleaning targets ...${NC}"
	rm target -f -r

	press_key_to_exit
fi

if [ "${build_mode}" = "release" ];
then
	echo -e "cargo build ${PURPLE}release${NC}"
	launch_vs_shell --release

	copy_build_results release
	press_key_to_exit
else
	echo -e "cargo build ${CYAN}debug${NC}"
	launch_vs_shell

	copy_build_results debug
	press_key_to_exit
fi

Final thoughts

As I continue to work with Godot-Rust, I'm sure I'll continue to streamline my development process. It's also a young project with teething issues, that will naturally resolve over time. As I discover new ways of easing development, I'll make sure to post about the findings.