+#!/bin/bash
+# gast (Gcc Automatically -Save-Temps) : wrapper script for gcc to save temporary file.
+#
+# Copyright (C) 2009 Tadashi Koike
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+#
+#----------------------------------------------------------------------
+# (I am weak in English. Please modify English comment more suitable. :T.K)
+#
+# [ Explanation of environment variable ]
+# - GAST_ENABLE
+# When this environment variable is defined (however excludes its
+# value is "no"), this script works to generate compiler's temporary
+# file (.i file) automatically, and stores it.
+# When this environment variable is not defined, this script behaves
+# itself like just same as an original gcompiler binary.
+#
+# - GAST_SAVE_DIR
+# When this environment variable is defined and its value can be
+# recognized a directory name, a genrerated temporary file (.i file)
+# by compiler is moved to that directory.
+# When that directory is not defined , this script attempt to create
+# that directory.
+#
+# - GAST_SOURCE_DIR
+# When this environment variable is defined and its value can be
+# recognized a directory name, this script tries to get a relative
+# path-name from a source file path-name by removing a directory
+# name which this environment means. And that relative path-name
+# applies under the GAST_SAVE_DIR directory to moved a temporary
+# file (.i file).
+# When this environment variable is not defined or the value does
+# not match a header of source file path-name, this script get a
+# relative path-name by removing "/" from a top of source file
+# path-name.
+#
+# ** NOTE **
+# When this environment variable is not defined but this script
+# is executed under a build process by 'rpmbuild' command, this
+# script sets this environment variable automatically with a
+# value of RPM_BUILD_DIR.
+#
+# - GAST_CONTENT_WITHOUT_H
+# When this environment variable is defined (however excludes its
+# value is "no"), created temporary file (.i file) is modified to
+# remove contents which are expanded from header files, and stored
+# as a substitute for an original temporary file (.i file).
+#
+# - GAST_CONTENT_UNIQUE
+# By default, when a C source file is compiled repeatedly with
+# different compile options each time, this script store each
+# temporary files.
+# When this environment variable is defined (however excludes its
+# value is "no"), If compile options are different but content of
+# temporary file is same as one of some previous stored files,
+# this script does not store temporary file at this time.
+
+VERSION="0.1.0"
+ORIGINAL_NAME="gast"
+CWD=`pwd`
+SCRIPT_NAME=${0##*/}
+SCRIPT_PATH=${0%/*}
+
+WORK_DIR="/tmp"
+
+# If this script is executed under the rpmbuild command, GAST_SOURCE_DIR is
+# forced to set value by RPM_BUILD_DIR environment variable's one.
+[ -n "${RPM_BUILD_DIR}" ] && GAST_SOURCE_DIR="${RPM_BUILD_DIR}"
+[ -n "${GAST_SOURCE_DIR}" ] && GAST_SOURCE_DIR=${GAST_SOURCE_DIR%/}
+
+# When this script is executed as 'gast', this script creates symbolic link
+# file name as compiler alias name and exit.
+if [ "${SCRIPT_NAME}" = "${ORIGINAL_NAME}" ]; then
+ for arg in "$@"
+ do
+ case "${arg}" in
+ '--version'|'-v')
+ echo "${ORIGINAL_NAME} ${VERSION}"
+ exit 0
+ ;;
+ '--help'|'-h')
+ cat <<EOL
+usage : ${ORIGINAL_NAME} [-v|--version] [-h|--help]
+
+ -h, --help display this help and exit
+ -v, --version display version information and exit
+
+ When this script is executed as '${ORIGINAL_NAME}' with no option, this script
+ creates symbolic link toward itself which name is from compiler (e.g. gcc/cc).
+
+ When this script is executed as compiler, this script executes original
+ compiler with additional -save-temps option (If GAST_ENABLE is defined), and
+ stores temporary files.
+
+[ environment variables to control storing temporary files ]
+ GAST_ENABLE : add '-save-temps' option automatically
+ in compiling when this variable is defined.
+ GAST_SAVE_DIR=<DIR> : directory name temporary file is stored in.
+ GAST_SOURCE_DIR=<DIR> : top directory of source tree.
+ GAST_CONTENT_WITHOUT_H : slim temporary files when this variable
+ is defined.
+EOL
+ exit 0
+ ;;
+ *)
+ echo "usage : ${ORIGINAL_NAME} [--version|-v] [--help|-h]"
+ exit 1
+ ;;
+ esac
+ done
+
+ cd "${SCRIPT_PATH}"
+ for cmd in gcc cc
+ do
+ if [ -e "${cmd}" ]; then
+ file "${cmd}" 2>/dev/null | egrep 'symbolic link' >/dev/null 2>&1
+ if [ "$?" -ne 0 ]; then
+ echo "${ORIGINAL_NAME} : '${cmd}' exists as normal file or directory. Could not create symbolic link." >&2
+ continue
+ fi
+ fi
+ ln -sf "${ORIGINAL_NAME}" "${cmd}"
+ if [ "$?" -eq 0 ]; then
+ echo "${ORIGINAL_NAME} : Create symbolic link '${cmd}'" >&2
+ else
+ echo "${ORIGINAL_NAME} : Couldn't create symbolic link '${cmd}'" >&2
+ fi
+ done
+ exit 0
+fi
+
+# get a directory path (full-path) of this script
+case "${SCRIPT_PATH}" in
+ '.') SCRIPT_PATH="${CWD}" ;;
+ \./*) SCRIPT_PATH="${CWD}/${SCRIPT_PATH#*/}" ;;
+ '~') SCRIPT_PATH="${HOME}" ;;
+ \~/*) SCRIPT_PATH="${HOME}/${SCRIPT_PATH#*/}" ;;
+ \/*) : ;;
+ *) SCRIPT_PATH="${CWD}/${SCRIPT_PATH}" ;;
+esac
+
+# leaves out a script path name from PATH environment variable because of
+# executing original compiler binary file in this script.
+path_tmp=
+for dir in ${PATH//:/ }
+do
+ [ "${dir}" = "${SCRIPT_PATH}" ] && continue
+ [ "${dir}" = '.' ] && dir="${CWD}"
+
+ if [ -z "${path_tmp}" ]; then
+ path_tmp="${dir}"
+ else
+ path_tmp="${path_tmp}:${dir}"
+ fi
+done
+export PATH=${path_tmp}
+unset path_tmp
+
+# When GAST_ENABLE does't exist, execute an original compiler and exit.
+if [ "${GAST_ENABLE=no}" = "no" ]; then
+ exec "${SCRIPT_NAME}" "$@"
+fi
+
+# Check whether C source file(s) is/are specified.
+# And create new commandline arguments.
+asm_notdelete_flg=
+pipe_flg=
+idx=0
+idx_src=0
+for arg in "$@"
+do
+ new_args[$idx]="${arg}"
+
+ case "${arg}" in
+ *.c) src_list[$idx_src]="${arg}"
+ let idx_src+=1
+ ;;
+ '-S') asm_notdelete_flg="YES"
+ ;;
+ '-pipe') pipe_flg="YES"
+ new_args[$idx]="-save-temps"
+ ;;
+ esac
+ let idx+=1
+done
+
+# Compiling
+if [ -z "${src_list[*]}" ]; then
+ # No C source file was specified. -save-temps option does not need.
+ exec "${SCRIPT_NAME}" "$@"
+else
+ # C source file(s) is/are specified. -save-temp option is set to argument.
+ if [ -z "${pipe_flg}" ]; then
+ "${SCRIPT_NAME}" -save-temps "$@"
+ else
+ "${SCRIPT_NAME}" "${new_args[@]}"
+ fi
+ RET=$?
+fi
+
+
+# modify a value of GAST_SOURCE_DIR environment variable to full-path name.
+# When GAST_SOURCE_DIR doesn't exist, define it and set null.
+case "${GAST_SOURCE_DIR}" in
+ '') GAST_SOURCE_DIR="/" ;;
+ '.') GAST_SOURCE_DIR="${CWD}" ;;
+ \./*) GAST_SOURCE_DIR="${CWD}/${GAST_SOURCE_DIR#*/}" ;;
+ '~') GAST_SOURCE_DIR="${HOME}" ;;
+ \~/*) GAST_SOURCE_DIR="${HOME}/${GAST_SOURCE_DIR#*/}" ;;
+ \/*) : ;;
+ *) GAST_SOURCE_DIR="${CWD}/${GAST_SOURCE_DIR}" ;;
+esac
+GAST_SOURCE_DIR="${GAST_SOURCE_DIR%/}"
+# NOTE : In above process, when user set GAST_SOURCE_DIR as "/" or
+# GAST_SOURCE_DIR doesn't exists, value of GAST_SOURCE_DIR is set to "".
+
+# When GAST_CONTENT_WITHOUT_H environment variable exist, define a filter
+# function.
+HAS_PERL=`which perl 2>/dev/null`
+
+if [ "${GAST_CONTENT_WITHOUT_H=no}" != "no" ]; then
+ if [ -n "${HAS_PERL}" ]; then
+ function filter_tempfile_perl () {
+ perl -e '
+#----------(start temporary perl script)-------------------------
+BEGIN {
+ $prnt_flg=-1;
+ $fname="'$1'";
+}
+while (<>) {
+ if (/^#\s+(\d+)\s+"([^"]+)".*$/) {
+ $line_no = $1;
+ $cfile = $2;
+ $cfile =~ s{^.*/}{};
+ if ($cfile eq $fname) {
+ print;
+ $prnt_flg = $line_no if ($prnt_flg >= 0);
+ } elsif ($cfile =~ /^</) {
+ print;
+ $prnt_flg = 0 if ($cfile eq "<built-in>");
+ } else {
+ if ($prnt_flg == -1) {
+ print;
+ } elsif ($line_no == 1) {
+ print;
+ $prnt_flg = 0 if ($prnt_flg >= 0);
+ } else {
+ $prnt_flg = 0;
+ }
+ }
+ } elsif ($prnt_flg > 0) {
+ print;
+ }
+}
+#----------(end temporary perl script)---------------------------
+'
+ }
+
+ function delete_added_info_perl () {
+ perl -e '
+#----------(start temporary perl script)-------------------------
+BEGIN {
+ $prnt_flg=0;
+}
+while (<>) {
+ if ($prnt_flg != 0) {
+ print;
+ } elsif (/^#\s+(\d+)\s+"<compile options>/) {
+ $prnt_flg=1;
+ }
+}
+#----------(end temporary perl script)---------------------------
+'
+ }
+
+ elif [ ${BASH_VERSION%%.*} -ge 3 ]; then
+ function filter_tempfile_bash () {
+ prnt_flg=-1
+ fname="$1"
+ IFS_SAVE=$IFS
+ IFS=
+ while read line
+ do
+ if [[ "${line}" =~ "^#[ \t]+([0-9]+)[ \t]+\"([^\"]+)\".*$" ]]; then
+ line_no=${BASH_REMATCH[1]}
+ cfile=${BASH_REMATCH[2]}
+ cfile=${cfile##*/}
+ #echo "$line_no : $cfile"
+ if [ "${cfile}" = "${fname}" ]; then
+ echo "${line}"
+ [ "${prnt_flg}" -ge 0 ] && prnt_flg="${line_no}"
+ elif [ "${cfile:0:1}" = "<" ]; then
+ echo "${line}"
+ [ "${cfile}" = "<built-in>" ] && prnt_flg=0
+ else
+ if [ "${prnt_flg}" -eq -1 ]; then
+ echo "${line}"
+ elif [ "${line_no}" -eq 1 ]; then
+ echo "${line}"
+ [ "${prnt_flg}" -ge 0 ] && prnt_flg=0
+ else
+ prnt_flg=0
+ fi
+ fi
+ else
+ [ "${prnt_flg}" -gt 0 ] && echo "${line}"
+ fi
+ done
+ IFS=$IFS_SAVE
+ }
+
+ function delete_added_info_bash () {
+ prnt_flg=0
+ IFS_SAVE=$IFS
+ IFS=
+ while read line
+ do
+ if [ "${prnt_flg}" -ne 0 ]; then
+ echo "${line}"
+ elif [[ "${line}" =~ "^#[ \t]+[0-9]+[ \t]+\"<compile options" ]]; then
+ prnt_flg=1
+ fi
+ done
+ IFS=$IFS_SAVE
+ }
+ fi
+fi
+
+# processes the compiler's temporary file
+for src_file in "${src_list[@]}"
+do
+ # get full-path name of a C source file.
+ fullname=
+ case "${src_file}" in
+ \/*) fullname="${src_file}" ;;
+ \~/*) fullname="${HOME}/${src_file#*/}" ;;
+ \./*) fullname="${CWD}/${src_file#*/}" ;;
+ *) fullname="${CWD}/${src_file}" ;;
+ esac
+
+ # Get relative-path of C source file from GAST_SOURCE_DIR directory.
+ basename="${fullname##*/}" # C source filename
+ dirname="${fullname%/*}" # directory name of C source file (full-path)
+ target="${basename/%.c/.i}" # Compiler's temporary filename (.i file)
+ asmfile="${basename/%.c/.s}" # Compiler's temporary filename (.s file)
+ src_relative_dir= # directory name of C source file (relative-path from GAST_SOURCE_DIR)
+
+ case "${fullname}" in
+ ${GAST_SOURCE_DIR}/*) # contain GAST_SOURCE_DIR=""
+ if [ "${dirname}" = "${GAST_SOURCE_DIR}" ]; then
+ src_relative_dir="."
+ else
+ src_relative_dir="${dirname#${GAST_SOURCE_DIR}/}"
+ fi
+ ;;
+ *)
+ # source file is not under the GAST_SOURCE_DIR directory.
+ # Set the relative-path from '/' directory.
+ src_relative_dir="${dirname#/}"
+ ;;
+ esac
+ [ "${src_relative_dir:0:1}" = '/' ] && src_relative_dir="${src_relative_dir#/}"
+ # Delete assembler temporary file (except that compiler is executed with -S option)
+ [ -e "${asmfile}" -a -z "${asm_notdelete_flg}" ] && rm -f "${asmfile}" > /dev/null 2>&1
+
+ if [ -e "${target}" ]; then
+ if [ "${target}" = "conftest.i" ]; then
+ # temporary file is created by 'configure' script as test.
+ rm -f "${target}"
+ continue
+ fi
+
+ # Add an information in the header of temporary file.
+ # When filtering temporary file was specified, try it.
+ tmpfile_org=
+ until [ -n "${tmpfile_org}" -a ! -e "${tmpfile_org}" ]
+ do
+ tmpfile_org="${target}.${RANDOM}${RANDOM}${RANDOM}"
+ done
+ trap "rm -rf ${tmpfile_org} ${target} 2>/dev/null" HUP INT QUIT ABRT TERM XCPU
+
+ mv "${target}" "${tmpfile_org}"
+ if [ $? -eq 0 ]; then
+ echo "# 1 \"<compile options> $@\"" > "${target}"
+ #if [ -z "${pipe_flg}" ]; then
+ # echo "# 1 \"<modified compile options> -save-temps $@\"" >> "${target}"
+ #else
+ # echo "# 1 \"<modified compile options> ${new_args[@]}\"" >> "${target}"
+ #fi
+
+ if [ "${GAST_CONTENT_WITHOUT_H=no}" = "no" ]; then
+ cat "${tmpfile_org}" >> "${target}"
+ else
+ #echo "# 1 \"<GAST_CONTENT_WITHOUT_H>\"" >> "${target}"
+ if [ -n "${HAS_PERL}" ]; then
+ filter_tempfile_perl "${basename}" < "${tmpfile_org}" >> "${target}"
+ elif [ ${BASH_VERSION%%.*} -ge 3 ]; then
+ filter_tempfile_bash "${basename}" < "${tmpfile_org}" >> "${target}"
+ else
+ cat "${tmpfile_org}" >> "${target}"
+ fi
+ fi
+ fi
+
+ # Check whether the GAST_SAVE_DIR has a value or not.
+ # When it has a value, that is recognized as a directory name.
+ # When such directory does not exist, try to create.
+ if [ -n "${GAST_SAVE_DIR}" ]; then
+ GAST_SAVE_DIR="${GAST_SAVE_DIR%/}"
+ if [ ! -d "${GAST_SAVE_DIR}" ]; then
+ if [ -e "${GAST_SAVE_DIR}" ]; then
+ echo "${ORIGINAL_NAME} : ${GAST_SAVE_DIR} is not a directory." >&2
+ GAST_SAVE_DIR=
+ else
+ mkdir -p "${GAST_SAVE_DIR}"
+ if [ $? -ne 0 ]; then
+ echo "${ORIGINAL_NAME} : Couldn't create ${GAST_SAVE_DIR} directory." >&2
+ GAST_SAVE_DIR=
+ fi
+ fi
+ fi
+ fi
+
+ # define a directory path-name of temporary file to resotre.
+ temp_dirname=
+ if [ -n "${GAST_SAVE_DIR}" ]; then
+ # resolve a directory path-name of a temporary file to move.
+
+ if [ "${src_relative_dir}" = "." ]; then
+ temp_dirname="${GAST_SAVE_DIR}"
+ else
+ temp_dirname="${GAST_SAVE_DIR}/${src_relative_dir}"
+ fi
+
+ # Check a directory to move a temporary file.
+ if [ -e "${temp_dirname}" ]; then
+ if [ ! -d "${temp_dirname}" ]; then
+ echo "${ORIGINAL_NAME}(${SCRIPT_NAME}): ${temp_dirname} is not a directory. Couldn't move ${target} to ${temp_dirname} ." >&2
+ temp_dirname=
+ fi
+ else
+ # create a directory
+ mkdir -p ${temp_dirname}
+ if [ $? -ne 0 ]; then
+ echo "${ORIGINAL_NAME}(${SCRIPT_NAME}): Couldn't create ${temp_dirname} directory, so couldn't move ${target} to ${temp_dirname} ." >&2
+ temp_dirname=
+ fi
+ fi
+ else
+ temp_dirname=
+ fi
+
+ # set destination filename(full-path) to move a temporary file.
+ loop_count=0
+ skip_moving=
+ dest_name=
+ dest_file=
+ if [ -n "${temp_dirname}" ]; then
+ dest_name=${target}
+ dest_file="${temp_dirname}/${dest_name}"
+ else
+ # When destination directory to store the temporary file doesn't
+ # exist, try to rename temporary file as '*****.0.i', because
+ # if some C source file is compiled over and over, old temporary
+ # file is overwrited by new one. We wish to avoid that.
+ dest_name="${target/%.i/.${loop_count}.i}"
+ dest_file="${dest_name}"
+ fi
+
+ while [ -e "${dest_file}" ]
+ do
+ let loop_count+=1
+ # software loop limit
+ if [ "${loop_count}" -ge 500 ]; then
+ echo "${ORIGINAL_NAME}(${SCRIPT_NAME}): loop abort. give up to move ${target} to ${temp_dirname} ." >&2
+ skip_moving="YES"
+ break
+ fi
+
+ # destination file already exists. compare a content.
+ cmp_result=
+ if [ "${GAST_CONTENT_UNIQUE=no}" != "no" ]; then
+ # comparison excluding "<compile option>" line.
+ tmpfile_prev=
+ until [ -n "${tmpfile_prev}" -a ! -e "${tmpfile_prev}" ]
+ do
+ tmpfile_prev="${WORK_DIR}/${destname}.${RANDOM}${RANDOM}${RANDOM}"
+ tmpfile_cur="${WORK_DIR}/${target}.${RANDOM}${RANDOM}${RANDOM}"
+ trap "rm -rf ${tmpfile_prev} ${tmpfile_cur} 2>/dev/null" HUP INT QUIT ABRT TERM XCPU
+ done
+
+ if [ -n "${HAS_PERL}" ]; then
+ delete_added_info_perl < "${dest_file}" > "${tmpfile_prev}"
+ delete_added_info_perl < "${target}" > "${tmpfile_cur}"
+ elif [ ${BASH_VERSION%%.*} -ge 3 ]; then
+ delete_added_info_bash < "${dest_file}" > "${tmpfile_prev}"
+ delete_added_info_bash < "${target}" > "${tmpfile_cur}"
+ fi
+ if [ -e "${tmpfile_prev}" -a -e "${tmpfile_cur}" ]; then
+ diff --brief "${tmpfile_prev}" "${tmpfile_cur}" > /dev/null 2>&1
+ cmp_result=$?
+ else
+ # failed to compare.
+ cmp_result=1
+ fi
+ rm -f "${tmpfile_prev}" "${tmpfile_cur}" > /dev/null 2>&1
+ trap - HUP INT QUIT ABRT TERM XCPU
+ else
+ # comparison including "<compile option>" line.
+ diff --brief "${dest_file}" ${target} > /dev/null 2>&1
+ cmp_result=$?
+ fi
+
+ if [ "${cmp_result}" -eq 0 ]; then
+ # delete current temporary file.
+ inode_from=`ls -i "${target}" | sed -e 's/\([0-9][0-9]*\)[^0-9]*/\1/'`
+ inode_to=`ls -i "${dest_file}" | sed -e 's/\([0-9][0-9]*\)[^0-9]*/\1/'`
+ [ "${inode_from}" != "${inode_to}" ] && rm -f "${target}"
+
+ skip_moving="YES"
+ break
+ else
+ # same file already exists and contents is different.
+ # try to store this temporary file with another filename
+ # by using ${loop_count} number.
+ dest_name="${target/%.i/.${loop_count}.i}"
+ if [ -n "${temp_dirname}" ]; then
+ dest_file="${temp_dirname}/${dest_name}"
+ else
+ dest_file="${dest_name}"
+ fi
+ fi
+ done
+
+ # move a temporary file to a save-directory.
+ if [ -z "${skip_moving}" ]; then
+ mv -f "${target}" "${dest_file}" 2>/dev/null
+ [ $? -ne 0 ] && echo "${ORIGINAL_NAME}(${SCRIPT_NAME}): Couldn't move ${target} to ${dest_file} ." >&2
+ fi
+
+ # Delete original temporary file.
+ rm -f "${tmpfile_org}"
+ trap - HUP INT QUIT ABRT TERM XCPU
+
+ fi #if [ -e "${target}" ]; then
+done
+
+# return with compiler's return code
+exit "${RET}"