bashmicrochipmplabgit-for-windowsmplab-x

Microchip MPLAB X IDE: configure and run a shared pre-build-step Bash script that runs in both Windows and Linux as part of the build process


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:

enter image description 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.


Solution

  • After much effort, I figured it out. One very functional and customizatable 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 (and be sure to check the box), on both Windows and Linux:

    "C:\Program Files\Git\git-bash.exe" ../preBuildSteps.sh && echo "Pre-build output logged to 'autogenerated/logs/'"
    

    This assumes that:

    1. You have the Git Bash terminal installed in Windows and it is located at Windows path "C:\Program Files\Git\git-bash.exe".
    2. You have a bash script called preBuildSteps.sh one level up from the *.X MPLAB X project directory (see the full project structure/file tree further below in the answer). So, relative to the *.X project directory, it is located at ../preBuildSteps.sh.
    3. 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 Files\Git\git-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 Files\Git\git-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 Files\Git\git-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 via the symlink
    # we just created above
    'C:\Program Files\Git\git-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: https://stackoverflow.com/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====================\n\n"
    }
    
    # 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: https://stackoverflow.com/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
    ├── preBuildSteps.sh
    ├── myProject.X
    │   └── ...MPLAB 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: https://stackoverflow.com/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 Files\Git\git-bash.exe" ../preBuildSteps.sh && echo "Pre-build output logged to 'autogenerated/logs/'"
    

    On Windows, this opens a Git Bash terminal via "C:\Program Files\Git\git-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 Files\Git\git-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 Files\Git\git-bash.exe" is to create a bash wrapper script instead. Such a script would still be located at path ~/bin/"C:\Program Files\Git\git-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 Files\Git\git-bash.exe"
    
    
    # Make the file executable
    chmod +x ~/bin/"C:\Program Files\Git\git-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, like this, for example, for myBuildConfiguration1:

    "C:\Program Files\Git\git-bash.exe" ../preBuildSteps.sh --myBuildConfiguration1 && echo "Pre-build output logged to 'autogenerated/logs/'"
    

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

    1. The only chars forbidden in Linux filenames are / (forward slash) and \0 (null char, or binary zero). See:
      1. What characters are forbidden in Windows and Linux directory names?
      2. Are there any invalid linux filenames?
      3. Wikipedia: Filename.
    2. My instructions to install the Git Bash terminal in Windows: Installing Git For Windows.
    3. 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 Files\Git\git-bash.exe
    4. My answer: How do I get the directory where a Bash script is located from within the script itself?
    5. Server Fault: How can I fully log all bash scripts actions?
    6. My answer: How to detect the OS from a Bash script?
    7. My answer: How to UNignore some select contents (files or folders) within an ignored folder
    8. MPLAB X IDE XC32 compiler and license issues:
      1. My answer: How to renew your paid Microchip XC32 Compiler Pro license when it has expired or is about to expire
      2. My answer: How do I make my Microchip MPLAB X IDE project use the free version of the XC32 compiler?
      3. My project to build a license-free version of the compiler yourself on both Linux and Windows: https://github.com/ElectricRCAircraftGuy/Microchip_XC32_Compiler
    9. My answer on configuring Git Bash in Windows to allow running Python as python3 (like in Linux), and to run python scripts as ./myProgram.py (like in Linux) while using Linux-style hash-bangs such as #!/usr/bin/env python3 at the top of your Windows and Linux Python scripts: Python not working in the command line of git bash