CMake如何检测更改的文件

the*_*ine 5 makefile cmake

我有一个“C”/C++ CMake 项目,运行良好。但是,我有时会在时间略有不同的远程集群上(重新)构建。这台机器运行 Linux,我正在使用make. 我想知道是否有一些 make/CMake 方法可以更改检测文件更改的方式,例如更改为 MD5 或 diff 而不是使用时间戳。否则我想我要么必须忍受恒定make clean/make -j周期,要么必须在每次使用该特定服务器时更改我的当地时间。

我正在查看 CMake 文档,看看是否有一个标志可以更改这些设置,但没有找到。这在没有 RTC 的平台(例如 Raspberry)上如何工作?

the*_*ine 1

是的,所以知道 CMake /make没有做我想要的事情,并且我不想将我的机器的时间同步到目标的麻烦,我想出了以下方法:

#!/bin/bash

touch src_hash.md5

echo -n make "$@" > mymake.sh

find `pwd`/../src `pwd`/../include -print0 | 
    while IFS= read -r -d $'\0' f; do 
        if [[ ! -d "$f" ]]; then
            MD5=`md5sum "$f" | awk -v fn="$f" '{ print "\"" fn "\" " $1; }'`
            echo $MD5 >> src_hash.md5.new

            OLDMD5=`grep -e "^\"$f\"" src_hash.md5`
            if [[ "$OLDMD5" == "" ]]; then
                echo "$MD5 -- [a new file]"
                continue # a new file, make can handle that well on its own
            fi

            HASH=`echo $MD5 | awk '{ print $2; }'`
            OLDHASH=`echo $OLDMD5 | awk '{ print $2; }'`
            if [[ "$HASH" != "$OLDHASH" ]]; then
                echo "$MD5 -- changed from $OLDHASH"
                echo -n " \"--what-if=${f}\"" >> mymake.sh
                # this is running elsewhere, can't pass stuff via variables
            fi
        fi
    done

touch src_hash.md5.new
mv src_hash.md5.new src_hash.md5

echo using: `cat mymake.sh`
echo >> mymake.sh # add a newline
chmod +x mymake.sh
./mymake.sh
rm -f mymake.sh
Run Code Online (Sandbox Code Playgroud)

这会保留源文件哈希值列表src_hash.md5,并且每次运行时都会将当前文件与这些哈希值进行比较(并相应地更新列表)。

最后,它调用make,将您提供给脚本的任何参数传递给脚本(例如-j)。它利用了--what-if=开关,告诉它make像给定的文件更改一样进行操作 - 这样可以优雅地处理构建目标对源/标头的依赖关系。

您可能还想将源/包含文件的路径作为参数传递,以便这些文件不会在内部进行硬编码。

或者对上述脚本进行再一次迭代,用于touch更改和恢复文件时间戳,以应对make非常顽固地不重建任何内容的情况:

#!/bin/bash

if [[ ! -d ../src ]]; then
    >&2 echo "error: ../src is not a directory or does not exist"
    exit -1
fi
if [[ ! -d ../include ]]; then
    >&2 echo "error: ../include is not a directory or does not exist"
    exit -1
fi

echo "Scanning for changed files in ../src and ../include"

touch src_hash.md5 # in case this runs for the first time

rm -f mymaketouch.sh
rm -f mymakerestore.sh
touch mymaketouch.sh
touch mymakerestore.sh

echo -n make "$@" > mymake.sh

CWD="`pwd`"
find ../src ../include -print0 | 
    while IFS= read -r -d $'\0' f; do 
        if [[ ! -d "$f" ]]; then
            fl=`readlink -f "$CWD/$f"`

            MD5=`md5sum "$fl" | awk -v fn="$fl" '{ print "\"" fn "\" " $1; }'`
            HASH=`echo $MD5 | awk '{ print $2; }'`
            echo $MD5 >> src_hash.md5.new

            OLDMD5=`grep -e "^\"$fl\"" src_hash.md5`
            OLDHASH=`echo $OLDMD5 | awk '{ print $2; }'`
            if [[ "$OLDMD5" == "" ]]; then
                echo "$f $HASH -- [a new file]"
                continue # a new file, make can handle that well on its own
            fi

            if [[ "$HASH" != "$OLDHASH" ]]; then
                echo "$f $HASH -- changed from $OLDHASH"

                echo "touch -m \"$fl\"" >> mymaketouch.sh # will touch it and change modification time
                stat "$fl" -c "touch -m -d \"%y\" \"%n\"" >> mymakerestore.sh # will restore it later on so that we do not run into problems when copying newer from a different system

                echo -n " \"--what-if=$fl\"" >> mymake.sh
                # this is running elsewhere, can't pass stuff via variables
            fi
        fi
    done

echo using: `cat mymake.sh`
echo >> mymake.sh # add a newline
echo 'exit $?' >> mymake.sh

chmod +x mymaketouch.sh
chmod +x mymakerestore.sh
chmod +x mymake.sh

control_c() # run if user hits control-c
{
    echo -en "\nrestoring modification times\n"
    ./mymakerestore.sh
    rm -f mymaketouch.sh
    rm -f mymakerestore.sh
    rm -f mymake.sh
    rm -f src_hash.md5.new 
    exit -1
}

trap control_c SIGINT

./mymaketouch.sh
./mymake.sh
RETVAL=$?
./mymakerestore.sh
rm -f mymaketouch.sh
rm -f mymakerestore.sh
rm -f mymake.sh

touch src_hash.md5.new # in case there was nothing new
mv src_hash.md5.new src_hash.md5
# do it now in case someone hits ctrl+c mid-build and not all files are built

exit $RETVAL
Run Code Online (Sandbox Code Playgroud)

或者,如果您正在构建一个大型项目,甚至可以并行运行哈希:

#!/bin/bash

if [[ ! -d ../src ]]; then
    >&2 echo "error: ../src is not a directory or does not exist"
    exit -1
fi
if [[ ! -d ../include ]]; then
    >&2 echo "error: ../include is not a directory or does not exist"
    exit -1
fi

echo "Scanning for changed files in ../src and ../include"

touch src_hash.md5 # in case this runs for the first time

rm -f mymaketouch.sh
rm -f mymakerestore.sh
touch mymaketouch.sh
touch mymakerestore.sh

echo -n make "$@" > mymake.sh

CWD="`pwd`"
rm -f src_hash.md5.new # will use ">>", make sure to remove the file
find ../src ../include -print0 |
    while IFS= read -r -d $'\0' f; do
        if [[ ! -d "$f" ]]; then
            fl="$CWD/$f"
            (echo `md5sum "$f" | awk -v fn="$fl" '{ print "\"" fn "\" " $1; }'` ) & # parallel, echo is atomic (http://stackoverflow.com/questions/9926616/is-echo-atomic-when-writing-single-lines)
            # run in parallel (remove the ampersand if you run into trouble)
        fi
    done >> src_hash.md5.new # >> is atomic but > wouldn't be
# this is fast

cat src_hash.md5 > src_hash.md5.diff
echo separator >> src_hash.md5.diff
cat src_hash.md5.new >> src_hash.md5.diff
# make a compound file for awk (could also read the other file in awk but this seems simpler right now)

cat src_hash.md5.diff | awk 'BEGIN { FS="\""; had_sep = 0; }
    {
        if(!had_sep && $1 == "separator")
            had_sep = 1;
        else {
            sub(/[[:space:]]/, "", $3);
            if(!had_sep)
                old_hashes[$2] = $3;
            else {
                f = $2;
                if((idx = index(f, "../")) != 0)
                    f = substr(f, idx, length(f) - idx + 1);
                if($2 in old_hashes) {
                    if(old_hashes[$2] != $3)
                        print "\"" f "\" " $3 " -- changed from " old_hashes[$2];
                } else
                    print "\"" f "\" -- a new file " $3;
            }
        }
    }'
# print verbose for the user only

cat src_hash.md5.diff | awk 'BEGIN { FS="\""; had_sep = 0; }
    {
        if(!had_sep && $1 == "separator")
            had_sep = 1;
        else {
            sub(/[[:space:]]/, "", $3);
            if(!had_sep)
                old_hashes[$2] = $3;
            else {
                if($2 in old_hashes) {
                    if(old_hashes[$2] != $3)
                        printf($2 "\0"); /* use \0 as a line separator for the below loop */
                }
            }
        }
    }' |
    while IFS= read -r -d $'\0' fl; do
        echo "touch -m \"$fl\"" >> mymaketouch.sh # will touch it and change modification time
        stat "$fl" -c "touch -m -d \"%y\" \"%n\"" >> mymakerestore.sh # will restore it later on so that we do not run into problems when copying newer from a different system

        echo -n " \"--what-if=$fl\"" >> mymake.sh
        # this is running elsewhere, can't pass stuff via variables
    done
# run again, handle files that require change

rm -f src_hash.md5.diff

echo using: `cat mymake.sh`
echo >> mymake.sh # add a newline
echo 'exit $?' >> mymake.sh

chmod +x mymaketouch.sh
chmod +x mymakerestore.sh
chmod +x mymake.sh

control_c() # run if user hits control-c
{
    echo -en "\nrestoring modification times\n"
    ./mymakerestore.sh
    rm -f mymaketouch.sh
    rm -f mymakerestore.sh
    rm -f mymake.sh
    rm -f src_hash.md5.new 
    exit -1
}

trap control_c SIGINT

./mymaketouch.sh
./mymake.sh
RETVAL=$?
./mymakerestore.sh
rm -f mymaketouch.sh
rm -f mymakerestore.sh
rm -f mymake.sh

touch src_hash.md5.new # in case there was nothing new
mv src_hash.md5.new src_hash.md5
# do it now in case someone hits ctrl+c mid-build and not all files are built

exit $RETVAL
Run Code Online (Sandbox Code Playgroud)