Windows Subsystem for Linux is a lightweight VM that allows you to run a Linux environment directly and easily on Windows without any other software such as VMWare or VirtualBox. It runs with a true Linux kernel (written by Microsoft and interacing with the VM) and a true ext4 filesystem (which exists as a vhdx file in the AppData folder of your C drive).
Note: This article is about WSL2, not WSL1.
Simply put, I'm a fan of Windows. There are a lot of UI decisions that just rub me the wrong way with Mac OS. Even without WSL, cross-platform support for Windows is highly evolved. Path naming conventions, POSIX support, and use of the cmd.exe shell are not the big obstacles people make them out to be. Typically, there's either a lack of familiarity with the Windows platform, or there are issues with project setup that counteract any cross-platform support the project may have had.
All that being said, you can use WSL in your daily workflow with little to no friction. As far as POSIX-compatibility is concerned, I consider this better than Mac. There are differences with Mac OS that show themselves in projects. This can be due to using zsh instead of Bash, the differences between Apple versions of UNIX CLI utilities, differences between APFS and ext4 filesystems, issues with Apple Clang masquarading as gcc, or other nuances of being on a Mac.
IMO, seeing something work correctly in WSL is a far better litmus test that something runs correctly in CI and in containers than seeing it work correctly on a Mac.
Memfault has already touched on this, so I won't say too much more here. What I will say is that the "How do I connect to hardware?" question is far more straightforward with WSL than it is with anything like Dev Containers or GitPod.
Note: I am aware of the ability of cloud based IDEs (such as Keil Studio and Arduino Create) to connect with hardware. This is done via an agent installed on the user's machine that interacts with the browser. This article assumes no such special software in setting up a workflow.
The primary benefit is knowing that you're operating in a true Linux environment including a Linux kernel, an ext4 filesystem, and GNU-compliant Bash shell and command-line utilities. Considering that anything running in CI is likely to be a Linux-based Docker image, this is just a cleaner way to go. I have caught many issues where something worked fine on Mac but not on Linux due to a WSL-based workflow.
The other benefit is faster build times. I have experienced much faster build times in WSL as compared to Windows. For a typical bare-metal project with a CMake/Ninja based build, a pristine build may be under 10 seconds.
TODO: Add graph of example project with build-time results for different environments.
I won't cover getting started with WSL in this article, but Medium has a good article here. The only difference to it I would make is to say that Ubuntu 20.04 is outdated. Go with Ubuntu 22.04 instead.
Let's move on to the main question: How to effectively and seamlessly integrate WSL into the development workflow. As I stated earlier, I'm not going to give a single prescription. I believe developers should have autonomy in creating their workflow. I will simply list all the ways that WSL facilitates workflow integration.
If you are wondering how to access files, the good news is that Windows has you covered in both directions.
- Accessing Windows files from WSL
- All Windows letter drives are mounted in the
/mnt
directory of the WSL instance.
E.g.C:\Users\username\Documents
becomes/mnt/c/Users/username/Documents
- All Windows letter drives are mounted in the
- Accessing WSL files from Windows
Being able to access files within the WSL instance through a network share is powerful, but it does have some limitations. Some apps will work well with that (e.g. Balena Etcher) and others will not. To get around this, it's necessary to map the WSL instance to a drive letter. Right click on the top-level folder (e.g. "Ubuntu-22.04") and select "Map network drive...". For the drive letter, I typically choose "Y:" or "Z:". Now you should have no problem opening IDEs, editors, etc. natively in Windows to the contents of a WSL instance.
Drive letter remapping also makes it easy to navigate to files in the WSL instance from a Windows-side command shell. This is a useful trick for running flash and debug scripts. The following example shows Windows Terminal split between two views, both are pointing at the same folder. The left side is in the Ubuntu Bash shell and is used to run the build natively in WSL. The right side is in Git Bash in Windows and has cd
ed to the drive in WSL thanks to drive letter mapping. The right side is used to flash the device.
Note: This image does not show the full output of the right-hand side.
Windows creates a Hyper-V Virtual Ethernet Adapter for WSL. Primarily this allows access to the Internet from WSL, but it also allows Windows-side and WSL-side applications to talk to each other via TCP/IP. This is similar to how containerized applications can communicate with the outside world via exposed ports.
To take advantage of this, you will want to know the IP address assigned to the WSL adapter from within the WSL instance. Add the following line to your .bashrc
to assign this address to an environment variable.
export HOST_IP=`ip route|awk '/^default/{print $3}'`
One use for this is to connect a WSL-side GDB client to a Windows side GDB server. More details on this below.
Type echo $PATH
in your WSL Bash shell and you will see something interesting. After all of the paths native to the WSL instance (/bin
, /usr/local/bin
, etc.), you will see all of the paths from your Windows PATH variable. The only difference is that the paths are modified to work from WSL. (e.g. C:\
is changed to /mnt/c/
, all ;
separators changed to :
etc.). The reason for this is that the WSL Linux kernel is designed to forward any invocation of a Windows-native application to the Windows host. This means any application accessible from the Windows command shell is also accessible from the WSL Bash shell.
Examples:
- Type
explorer.exe .
and a new instance of File Explorer will open showing the contents of the current WSL directory. - Type
code .
and a new instance of VS Code will appear and open the current folder as a remote connection.
The wsl.exe utility is used for managing WSL instances, but it can also be used for invoking scripts within a WSL instance directly from Windows. This can be used to invoke a build script, or any other script, from an IDE residing natively in Windows. Just pass the Bash command line you want to run as an argument to wsl.exe
. There are two options you may need before the Bash command.
-d
to select which distro (i.e. WSL instance) you wish to use. You only need this if the WSL instance you use for development is not already the default distro.--cd
to select the working directory inside the WSL instance .
Here is an example using Eclipse to edit and build a project in WSL. Eclipse has access to the files thanks to drive letter mapping. The project is set up as a Makefile build and the build command is passed through wsl.exe
to run it directly in Ubuntu Bash. The build shown in the CDT Build Console was started by clicking the hammer button.
Note: Setting up the debug configuration is easy here because the gdb server and client can both run natively from Windows and Eclipse has easy access to the ELF file.
One of the major questions people have about WSL is how to directly access their USB devices. Microsoft provides instructions on how to map USB devices into a WSL instance using a tool called USB/IP. USB-over-IP is already a supported feature in Linux distributions. With usbipd-win, it takes advantage of the Hyper-V Virtual Ethernet Adapter to bridge devices into WSL. These show up as normal devices in /dev
.
Using the command-line tools to set up usbipd-win correctly isn't very user friendly. Fortunately there's a GUI to make things easy. Just go through this tutorial from Goloith and you'll be up and running in no time.
Here is an example of what this looks like:
JLink probe forwarded to WSL | Use of JLink Commander from WSL |
---|---|
As noted previously, VS Code will automatically establish a remote connection for any folder opened from a WSL instance. The remote connection icon in the lower left corner will denote the distro you are connected to.
Since VS Code is remote connected, the terminal it opens will be your WSL Bash shell and any commands run will occur natively inside the WSL instance. This is incredibly convenient as it negates the need to pass any build commands through wsl.exe
.
The easiest way to perform flash and debug operations is to have your debug tools installed directly into WSL and to map the corresponding USB devices into WSL using WSL USB GUI. However, you do not have to do things this way.
As noted above, it is possible to run the GDB server natively from Windows and bridge to it from the GDB client running in WSL. For this to work automatically as a VS Code launch configuration, several things have to be set up properly.
JLinkGDBServerCL.exe
needs to be in the path visible to WSL.- The
HOST_IP
env var needs to be in your.bashrc
as decribed above. - The
Cortex Debug
launch configuration needs the server type set toexternal
. - The launch configuration should have invoke a pre-launch task, defined in
tasks.json
, to invokeJLinkGDBServerCL.exe
. JLinkGDBServerCL.exe
needs to be supplied with the-nolocalhostonly
option so that it can be accessed from WSL's virtual Ethernet adapter.- The gdb client needs the
target remote
as one of its startup commands with the correct IP address ($HOST_IP
) and port (usually2331
) to connect to the GDB server.
More details and discussion on setting this up can be found here here. Note that I and many others have experienced problems with this bridging setup. You may find that the GDB client randomly disconnects from the server or that you're not able to step very far in the code base before the debug setup becomes unstable.
I strongly recommend WSL USB GUI over attempts to run the GDB server natively in Windows.