Our team has a mix of developers on both Windows and Linux, and a shared code base that has to be able to compile on both, in the Microchip MPLAB X IDE v6.20, for PIC32 microcontrollers in our case.
I see in the project properties (right-click the project name –> Properties) there is an option for adding a pre-build step, here:
This could be used for auto-generating C code .h header files, for instance. How can we get a common pre-build script here to run on both Windows and Linux in the MPLAB X IDE?
I’m having a lot of challenges because the bash commands available in the MPLAB X IDE are inconsistent and have missing options and commands.
After much effort, I figured it out. The best (most versatile) way to do it is to use a common .sh bash script which will run on both Linux and Windows. This bash script can then do whatever pre-build steps you want. It could call other executables, other bash scripts, Python scripts, or just do the work directly.
How to configure a preBuildSteps.sh
Bash script that runs as part of the pre-build process in the MPLAB X IDE on both Windows and Linux
Add this line into that "Execute this line before build"
box, on both Windows and Linux:
"C:Program FilesGitgit-bash.exe" ../preBuildSteps.sh && echo "Pre-build output logged to 'autogenerated/logs/'"
This assumes that:
- You have the Git Bash terminal installed in Windows and it is located at Windows path
"C:Program FilesGitgit-bash.exe"
. - You have a bash script called
preBuildSteps.sh
one level up from the*.X
MPLAB X project directory. So, relative to the*.X
project directory, it is located at../preBuildSteps.sh
. - You’d like to log your pre-build output to
autogenerated/logs/
in the root of your project.
On Windows, install Git for Windows, which contains the Git Bash terminal. I recommend you follow my instructions here: Installing Git For Windows.
On Linux, create an executable symlink to bash
, with a symlink filename of C:Program FilesGitgit-bash.exe
(this is a legal filename in Linux), and place it into your ~/bin
directory, as follows:
# Ensure this directory exists
mkdir -p ~/bin
# Now create the symlink
ln -si $(which bash) ~/bin/"C:Program FilesGitgit-bash.exe"
Now, ensure that ~/bin
is in your PATH. If on Linux Ubuntu, the following should already be in the bottom of your ~/.profile
file:
# set PATH so it includes user's private bin if it exists
if [ -d "$HOME/bin" ] ; then
PATH="$HOME/bin:$PATH"
fi
If it’s not, then add that to the bottom of your ~/.profile
file (preferred) or ~/.bashrc
file and then re-source (import) it by running:
. ~/.profile
. ~/.bashrc
Now, you should be able to run 'C:Program FilesGitgit-bash.exe'
as an executable alias to bash
from your Linux terminal. Test it by running the following, and you should see the exact same output from each:
# Check your Linux bash version
bash --version
# Also check the exact same Linux bash version
'C:Program FilesGitgit-bash.exe' --version
Now, create a bash script called preBuildSteps.sh
in the root of your project, and place your pre-build steps into it. Here is a very thorough example of what it might look like:
myProject/preBuildSteps.sh
:
#!/usr/bin/env bash
# This script contains pre-build steps to run at the start of the build in
# MPLAB X IDE.
# Get paths. See my answer: /a/60157372/4561887
FULL_PATH_TO_SCRIPT="$(realpath "${BASH_SOURCE[-1]}")"
SCRIPT_DIRECTORY="$(dirname "$FULL_PATH_TO_SCRIPT")"
SCRIPT_FILENAME="$(basename "$FULL_PATH_TO_SCRIPT")"
LOG_DIR="$SCRIPT_DIRECTORY/autogenerated/logs"
LOG_FILE="$LOG_DIR/$SCRIPT_FILENAME.log"
mkdir -p "$LOG_DIR"
# Change to the script's directory so all relative paths work correctly.
# From this point on, all paths below can be relative to the script's directory.
cd "$SCRIPT_DIRECTORY"
# Function to print a separator in the terminal between commands.
print_separator() {
printf "n====================nn"
}
# start of output log; see: https://serverfault.com/a/103509/357116
((
echo -e "nRunning $SCRIPT_FILENAME..."
echo "Logging output to "$LOG_FILE"."
print_separator
# ------------------------- START OF PRE-BUILD STEPS ---------------------
# Run these prebuild scripts you may have
# Autogenerate at header file at:
# 1. autogenerated/autogenerated/MyAutogeneratedHeader1.h
# 2. autogenerated/autogenerated/MyAutogeneratedHeader2.h
# 3. autogenerated/autogenerated/MyAutogeneratedHeader3.h
./autogenerated/scripts/make_MyAutogeneratedHeader1.h.sh; print_separator
./autogenerated/scripts/make_MyAutogeneratedHeader2.h.sh; print_separator
./autogenerated/scripts/make_MyAutogeneratedHeader3.h.sh; print_separator
# Now handle running a **Windows** binary executable on Linux, by running it
# with `wine`.
# See my answer: /a/78480875/4561887
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
# OS is Linux, so use wine to run the Windows executable.
wine ./autogenerated/bin/myWindowsExecutable.exe arg1 arg2 arg3
elif [[ "$OSTYPE" == "msys" ]]; then
# OS is Windows (Git Bash), so run the Windows executable directly.
./autogenerated/bin/myWindowsExecutable.exe arg1 arg2 arg3
fi
print_separator
# ------------------------- END OF PRE-BUILD STEPS -----------------------
# end of output log
) 2>&1) | tee "$LOG_FILE"
Lastly, you should ignore your autogenerated files and logs via your .gitignore
file. Assuming you have this project structure (as shown by tree -a
):
myProject
├── autogenerated
│ ├── autogenerated
│ │ ├── MyAutogeneratedHeader1.h
│ │ ├── MyAutogeneratedHeader2.h
│ │ └── MyAutogeneratedHeader3.h
│ ├── bin
│ │ └── myWindowsExecutable.exe
│ ├── .gitignore
│ ├── logs
│ │ └── preBuildSteps.sh.log
│ ├── README.md
│ └── scripts
│ ├── make_MyAutogeneratedHeader1.h.sh
│ ├── make_MyAutogeneratedHeader1.h.sh
│ └── make_MyAutogeneratedHeader1.h.sh
├── myProject.X
│ └── ...project configuration files and build output...
├── ...other project files...
…then here is an example of what your myProject/autogenerated/.gitignore
file shown above might look like to ignore all autogenerated files and logs, except for a README.md
file in each directory, if one exists:
myProject/autogenerated/.gitignore
:
# Ignore all files herein; see: /a/67551691/4561887
/autogenerated/*
# Except this file if it exists
!/autogenerated/README.md
# Ignore all files herein
/logs/*
# Except this file if it exists
!/logs/README.md
That’s it! Now, when you build your project in MPLAB X, it will run the prebuild command of:
"C:Program FilesGitgit-bash.exe" ../preBuildSteps.sh && echo "Pre-build output logged to 'autogenerated/logs/'"
On Windows, this opens a Git Bash terminal via "C:Program FilesGitgit-bash.exe"
, and then runs your ../preBuildSteps.sh
Bash script within it. At the end, it prints Pre-build output logged to 'autogenerated/logs/'
so that you can see that inside your MPLAB X IDE build output window. This is helpful because the Git Bash popup window terminal will have just closed and you’ll have missed all the output it printed, so you need a way to review the output in the log file.
On Linux, you have a magical symlink to bash
, in your PATH at ~/bin/"C:Program FilesGitgit-bash.exe"
, so it simply runs the ../preBuildSteps.sh
Bash script directly inside of a new bash
sub-shell, and then of course also prints Pre-build output logged to 'autogenerated/logs/'
at the end.
The preBuildSteps.sh
script does whatever you want it to do. In my example above, I have it call other scripts to autogenerate some C header files, and I have it handle a unique case where it needs to run a Windows executable on Linux through wine
. I make it log all output to autogenerated/logs/preBuildSteps.sh.log
, which gets ignored by your .gitignore
file shown in the project tree above.
Additional notes
An alternative to creating the bash symlink on Linux via ln -si $(which bash) ~/bin/"C:Program FilesGitgit-bash.exe"
is to create a bash wrapper script instead. Such a script would still be located at path ~/bin/"C:Program FilesGitgit-bash.exe"
, but it would contain the following contents:
#!/usr/bin/env bash
# This is a wrapper to simulate Windows's Git Bash terminal in Linux
bash "$@"
It could be created by running these commands:
# Create the file (copy and paste this whole chunk into your terminal all
# at once, from here to the `echo` line below)
file_contents=$(cat << 'EOF'
#!/usr/bin/env bash
# This is a wrapper to simulate Windows's Git Bash terminal in Linux
bash "$@"
EOF
)
echo "$file_contents" > ~/bin/"C:Program FilesGitgit-bash.exe"
# Make the file executable
chmod +x ~/bin/"C:Program FilesGitgit-bash.exe"
The end result would be the exact same as using the symlink to bash
, but the symlink is easier. The wrapper script would be better if you need to have it do additional things before running bash.
Handling multiple build configurations with one ../preBuildSteps.sh
script
If you have multiple build configurations in MPLAB X, rather than creating a separate ../preBuildSteps.sh
script for each one, you can simply pass in the build configuration name as an argument to the script, and then have the script do different things based on the build configuration name. For example, add this to your ../preBuildSteps.sh
script:
# ------------------------- START OF PRE-BUILD STEPS ---------------------
# Do common build configuration stuff here
# ...
# Do build configuration-specific stuff here
if [[ "$1" == "--myBuildConfiguration1" ]]; then
# Do unique stuff for Build Configuration 1
elif [[ "$1" == "--myBuildConfiguration2" ]]; then
# Do unique stuff for Build Configuration 2
elif [[ "$1" == "--myBuildConfiguration3" ]]; then
# Do unique stuff for Build Configuration 3
fi
# ------------------------- END OF PRE-BUILD STEPS -----------------------
References and additional resources
- The only chars forbidden in Linux filenames are
/
(forward slash) and(null char, or binary zero). See:
- What characters are forbidden in Windows and Linux directory names?
- Are there any invalid linux filenames?
- Wikipedia: Filename.
- My instructions to install the Git Bash terminal in Windows: Installing Git For Windows.
- My bug report: VSCode cannot open files with backslashes in their names on Linux: Cannot open or edit valid files on Linux with backslashes in names; example of a text filename on Linux:
C:Program FilesGitgit-bash.exe
- My answer: How do I get the directory where a Bash script is located from within the script itself?
- Server Fault: How can I fully log all bash scripts actions?
- My answer: How to detect the OS from a Bash script?
- My answer: How to UNignore some select contents (files or folders) within an ignored folder
- MPLAB X IDE XC32 compiler and license issues:
- My answer: How to renew your paid Microchip XC32 Compiler Pro license when it has expired or is about to expire
- My answer: How do I make my Microchip MPLAB X IDE project use the free version of the XC32 compiler?
- My project to build a license-free version of the compiler yourself on both Linux and Windows: https://github.com/ElectricRCAircraftGuy/Microchip_XC32_Compiler