You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
238 lines
6.5 KiB
238 lines
6.5 KiB
#!/bin/bash |
|
|
|
src=$(realpath $(dirname $BASH_SOURCE)/../../) |
|
base=$src/libraries |
|
declare -A header_dirs |
|
|
|
arg_verbose=false |
|
arg_create_commits=false |
|
|
|
usage(){ |
|
cat <<EOF |
|
Usage: $(basename $BASH_SOURCE) [OPTIONS] [--] [<pathspec>...] |
|
|
|
Fix includes of libraries headers in source files to be as the following: |
|
|
|
- If the header is in the same directory the source belongs to, then the |
|
notation #include "" is used with the path relative to the directory |
|
containing the source. |
|
|
|
- If the header is outside the directory containing the source, then we use |
|
the notation #include <> with the path relative to libraries folder. |
|
|
|
If pathspec is given then it's an argument passed directly to git-grep. See |
|
git-grep(1) for more information on its format. In this case the changes will |
|
apply only to files that match the pathspec. Otherwise changes will be made to |
|
the entire repository. |
|
|
|
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 <<EOF |
|
$commit_base: standardize inclusion of libaries headers |
|
|
|
This commit changes the way libraries headers are included in source files: |
|
|
|
- If the header is in the same directory the source belongs to, so the |
|
notation '#include ""' is used with the path relative to the directory |
|
containing the source. |
|
|
|
- If the header is outside the directory containing the source, then we use |
|
the notation '#include <>' 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 $? |
|
;; |
|
--) |
|
# remaining args are pathspecs |
|
shift |
|
break |
|
;; |
|
-*) |
|
usage >&2 |
|
exit 1 |
|
;; |
|
*) |
|
# this and the remaining args are pathspecs |
|
break |
|
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 </tmp/headers |
|
echo |
|
|
|
total=${#header_dirs[@]} |
|
i=0 |
|
for header in "${!header_dirs[@]}"; do |
|
regex="#\s*include\s*[<\"]\(.*/\)\?$header[>\"]" |
|
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
|
|
|