diff --git a/Tools/scripts/fix_libraries_includes.sh b/Tools/scripts/fix_libraries_includes.sh new file mode 100755 index 0000000000..d78638778b --- /dev/null +++ b/Tools/scripts/fix_libraries_includes.sh @@ -0,0 +1,225 @@ +#!/bin/bash + +src=$(realpath $(dirname $BASH_SOURCE)/../../) +base=$src/libraries +declare -A header_dirs + +arg_verbose=false +arg_create_commits=false + +usage(){ + cat < with the path relative to libraries folder. + +The output is a log of the process. + +Options: + -h,--help + Display this help message. + + -v,--verbose + Not only log errors and warnings but also substitutions. + + -c,--create-commits + Create commits in the end. + + --commit + Assume that the user have run the substitutions beforehand - only + create the commits. +EOF +} + +create_commits(){ + for f in $(git diff-files --name-only); do + if [[ ${f%%/*} == "libraries" ]]; then + echo $f | sed "s,\(libraries/[^/]*\)/.*,\1," + else + echo ${f%%/*} + fi + done | uniq | while read d; do + if [[ $d == libraries/* ]]; then + commit_base=${d#libraries/} + else + commit_base=$d + fi + cat >/tmp/commit_msg <' with the path relative to libraries folder. + +Some of the advantages of such approach: + + - Only one search path for libraries headers. + + - OSs like Windows may have a better lookup time. +EOF + git add -u $d + git commit -F /tmp/commit_msg + done +} + +replace_include(){ + local file=$1 + local n=$2 + local new_path=$3 + local old_path=$4 + local regex="\(#\s*include\s*\)[<\"].\+[>\"]" + + [[ $new_path == $old_path ]] && return + + $arg_verbose && echo "$file:$n: $old_path --> $new_path" + if ! sed -i "${n}s,$regex,\1$new_path," $file; then + echo Error on executing command: sed -i "${n}s,$regex,\1$new_path," $file >&2 + kill -SIGINT $$ + fi +} + +fix_includes(){ + local file=$1 + local header=$2 + local dirs=(${header_dirs[$header]}) + local num_dirs=${#dirs[@]} + local regex="^\s*#\s*include\s*[<\"]\(.*/\)\?$header[>\"]" + + grep -ahno $regex $file | while IFS=":" read n match; do + path=$(echo $match | sed "s/^\s*#\s*include\s*//g") + delim=${path:0:1} + path=${path:1:(${#path}-2)} + file_dir=$(realpath $(dirname $file)) + + if [[ $delim == "\"" ]]; then + localpath=$file_dir/$path + if [[ -f $localpath ]]; then + # verify if file is under to the file dir + localpath=$(realpath $localpath) + [[ $localpath == $file_dir* ]] && continue + + # if not under file dir, check if $localpath is under $base + if [[ $localpath == $base* ]]; then + new_path=${localpath#$base/} + replace_include $file $n \<$new_path\> \"$path\" + continue + fi + fi + fi + + match_count=0 + possible_paths=() + for dir in "${dirs[@]}"; do + if [[ $dir/$header == *$path ]]; then + ((match_count++)) + new_path=$dir/$header + possible_paths[${#possible_paths[@]}]=$new_path + fi + done + + if [[ $match_count -eq 0 ]]; then + echo "$file:$n: couldn't find a match for inclusion of $path" + elif [[ $match_count -eq 1 ]]; then + # check if included header is under file dir + if [[ -f $file_dir/$path ]]; then + new_path=\"$(realpath $file_dir/$path --relative-to $file_dir)\" + else + new_path=\<$new_path\> + fi + if [[ $delim == '"' ]]; then path=\"$path\"; else path=\<$path\>; fi + replace_include $file $n $new_path $path + else + echo "$file:$n: more than one match for inclusion of $path" + echo " possible paths:" + for p in "${possible_paths[@]}"; do + echo " $p" + done + fi + done +} + +trap_reset_tree(){ + echo + echo Process killed or interrupted! Reseting tree... + git -C $src reset --hard + exit 1 +} + +# parse args +while [[ -n $1 ]]; do + case "$1" in + -h|--help) + usage + exit 0 + ;; + -v|--verbose) + arg_verbose=true + ;; + -c|--create-commits) + arg_create_commits=true + ;; + --commit) + create_commits + exit $? + ;; + *) + usage >&2 + exit 1 + ;; + esac + shift +done + +trap trap_reset_tree SIGINT SIGKILL + +if ! git -C $src diff-files --quiet --exit-code; then + echo You have unstaged changes, please commit or stash them beforehand >&2 + exit 1 +fi + +pushd $src > /dev/null + +# collect all headers +git -C $base ls-files *.h > /tmp/headers +total=$(cat /tmp/headers | wc -l) +header_max_len=0 +while read f; do + header=$(basename $f) + dir=$(dirname $f) + if [[ -z ${header_dirs[$header]} ]]; then + header_dirs[$header]=$dir + else + header_dirs[$header]+=" $dir" + fi + printf "\rCollecting header files paths... $((++i))/$total" >&2 + [[ ${#header} -gt $header_max_len ]] && header_max_len=${#header} +done \"]" + printf "\r($((++i))/$total) Fixing includes for header %-${header_max_len}s" $header >&2 + + # for each file that includes $header + git grep -l $regex | while read f; do + fix_includes $f $header + done +done + +$arg_create_commits && create_commits + +popd > /dev/null