#!/bin/bash projectPattern="*.uvprojx"; if [ "$#" -gt 1 ] then 1>&2 echo "Error: Invalid number of arguments: $#"; fi; if [ "$#" -ne 1 ] || [ "$1" == "-h" ] || [ "$1" == "--help" ] then echo "Usage: ${BASH_SOURCE[0]} "; exit; fi; function handle_status() { if [ $1 -ne 0 ]; then echo "Error: $2 (status code: $1)"; exit $1; fi } function regex_escape() { echo "$1" | sed "s/[^\^]/[&]/g; s/\^/\0/g"; } function normalize_path() { echo "$1" | sed "s/\\\\/\\//g"; } function getTargetXPath() { index="$1"; echo -n "Project/Targets/Target"; if [ ! -z "$index" ] then echo -n "[$index]"; fi; echo ""; } function getFileXPath() { index="$1"; echo "$(echo -n "$(getTargetXPath "$index")")/Groups/Group/Files/File/FilePath"; } function getOptionPath() { index="$1"; echo "$(echo -n "$(getTargetXPath "$index")")/TargetOption/TargetCommonOption"; } function find_files() { local -n files=$1; arguments=("${@:2}"); readarray -d '' files < <(find "${arguments[@]}" -print0); return; } function find_project() { file="$1"; currentDir="$(dirname "$file")"; find_files projectFiles "$currentDir" -name "$projectPattern"; while true; do for projectFile in "${projectFiles[@]}" do if containsFile "$file" "$projectFile" then echo "$projectFile"; return; fi; done; if [ "$currentDir" != "/" ]; then currentDir="$(dirname "$currentDir")"; find_files projectFiles "$currentDir" -maxdepth 1 -name "$projectPattern"; else break; fi; done; } function containsFile() { local i; file="$1"; projectFile="$2"; targetIndex="$3"; xpath="$(getFileXPath "$targetIndex")"; relativePath="$(realpath --relative-to "$(dirname "$projectFile")" "$(dirname "$file")")"; filePattern="$(regex_escape "$(basename "$file")")"; while [ "$relativePath" != "." ]; do filePattern="$(regex_escape "$(basename "$relativePath")")"'[/\\\\]'"$filePattern"; relativePath="$(dirname "$relativePath")"; done; fileCount="$(xmlstarlet select --template --copy-of "count($xpath)" "$projectFile")"; for i in $(seq 1 $fileCount) do currentFileName="$(xmlstarlet select --template --match "($xpath)[$i]" --value-of . "$projectFile")"; currentFileName="$(normalize_path "$currentFileName")"; currentFileName="$(realpath --relative-to=. -m "$currentFileName")"; if echo "$currentFileName" | grep "^$filePattern$" > /dev/null then true; return; fi; done; false; } function chooseTarget() { local i; projectFile="$1"; file="$2"; targetCandidates=(); targetCount="$(xmlstarlet select --template --copy-of "count($(getTargetXPath))" "$projectFile")"; if [ $targetCount -gt 0 ] then for i in $(seq 1 $targetCount) do if containsFile "$file" "$projectFile" "$i" then targetCandidates+=("$i"); fi; done; if [ "${#targetCandidates[@]}" = 0 ] then targetCandidates=($(seq 1 "$targetCount")); fi; while [ ! "${#targetCandidates[@]}" = 1 ] do echo "Which Target Would You Like to Build?"; for j in $(seq 1 "${#targetCandidates[@]}") do index="${targetCandidates["$(expr $j - 1)"]}"; targetName="$(xmlstarlet select --template --match "$(getTargetXPath "$index")/TargetName" --value-of . "$projectFile")"; echo "$(printf "%${#targetCount}s" "$index"): \"$targetName\""; done; read -p "Enter your choice: " choice; if [ $choice -gt 0 ] && [ $choice -le "${#targetCandidates[@]}" ] then targetCandidates=("${targetCandidates[$(expr $choice - 1)]}"); fi; done; return "${targetCandidates[0]}"; else handle_status 1 "The current project '$projectFile' doesn't contain any targets!"; fi; } fileName="$(realpath "$1")"; test -f "$fileName"; handle_status "$?" "The specified file \"$1\" does not exist!"; if [[ "$fileName" =~ \.uvprojx$ ]] then projectFile="$fileName"; else projectFile="$(find_project "$fileName")"; fi if [ ! -z "$projectFile" ]; then projectRoot="$(dirname "$projectFile")"; cd "$projectRoot"; tempFile="$(mktemp)"; cp -f "$projectFile" "$tempFile"; chooseTarget "$projectFile" "$fileName"; targetIndex="$?"; targetPath="$(getTargetXPath "$targetIndex")"; optionPath="$(getOptionPath "$targetIndex")"; fileCount="$(xmlstarlet select --template --copy-of "count($(getFileXPath))" "$projectFile")"; targetName="$(xmlstarlet select --template --match "$targetPath/TargetName" --value-of . "$projectFile")"; buildPath="$(xmlstarlet select --template --match "$optionPath/OutputDirectory" --value-of . "$projectFile")"; outputName="$(xmlstarlet select --template --match "$optionPath/OutputName" --value-of . "$projectFile")"; buildPath="$(normalize_path "$buildPath")"; outputName="$(normalize_path "$outputName")"; echo "Building Target \"$targetName\""; echo ""; for i in $(seq 1 $fileCount) do xpath="($(getFileXPath))[$i]" currentFileName="$(xmlstarlet select --template --match "$xpath" --value-of . "$projectFile")"; currentFileName="$(normalize_path "$currentFileName")"; xmlstarlet edit --inplace --update "$xpath" --value "$currentFileName" "$projectFile"; done; logFile="$(mktemp)"; xvfb-run bash -c "dwm & wine \"C:\Keil_v5\UV4\UV4.exe\" -j0 -b \"$projectFile\" -t \"$targetName\" -l \"$logFile\"" > /dev/null 2>&1; mv -f "$tempFile" "$projectFile"; errorPatterns=() errorPatterns+=("\(\)\(.*\)\(([[:digit:]]\+): \(warning\|note\|error\): \([A-Z0-9]\+: \)\?.*\)$") errorPatterns+=("\(\"\)\(.*\)\(\", line [[:digit:]]\+\( (column [[:digit:]]\+)\)\?: \(Warning\|Error\): [A-Z0-9]\+: .*\)$"); for errorPattern in "${errorPatterns[@]}" do echo "$errorPattern"; sed -i \ -e "/$errorPattern/{" \ -e "h;" \ -e "s/$errorPattern/\1\n\3/" \ -e "x; s/$errorPattern/realpath \"\2\"/" \ -e "e" \ -e "x; G; h;" \ -e "s/\([^\n]*\)\n//" \ -e "s/\([^\n]*\)\n\(.*\)/\2\1/" \ -e "x; s/\([^\n]*\)\n\(.*\)$/\1/" \ -e "G; s/\n//" \ -e "}" "$logFile"; done; cat "$logFile"; if grep -q " 0 Error(s)" "$logFile"; then outputName="$(realpath "$projectRoot/$buildPath/$outputName")"; outputFile="$outputName.axf"; echo "The File Has Been Built to \"$outputFile\"."; pyocd list | { grep '^No available' && { echo "Skpping flash." } || { pyocd flash --target stm32f439xi "$outputFile"; handle_status $? "Flashing failed"; }; }; else handle_status 1 "The project could not be built!"; fi; else handle_status 1 "No project file for \"${fileName}\" could be found." fi;