OSDN Git Service

Update version number 0.1.0 --> 0.1.1
[gast/master.git] / gast
1 #!/bin/bash
2 #   gast (Gcc Automatically -Save-Temps) : wrapper script for gcc to save temporary file.
3 #
4 #   Copyright (C) 2009 Tadashi Koike
5 #
6 #   This program is free software; you can redistribute it and/or modify
7 #   it under the terms of the GNU General Public License as published by
8 #   the Free Software Foundation; either version 2 of the License, or
9 #   (at your option) any later version.
10 #
11 #   This program is distributed in the hope that it will be useful,
12 #   but WITHOUT ANY WARRANTY; without even the implied warranty of
13 #   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 #   GNU General Public License for more details.
15 #
16 #   You should have received a copy of the GNU General Public License
17 #   along with this program; if not, write to the Free Software
18 #   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  
19 #   02110-1301, USA.
20 #
21 #----------------------------------------------------------------------
22 # (I am weak in English. Please modify English comment more suitable. :T.K)
23 #
24 # [ Explanation of environment variable ]
25 #  - GAST_ENABLE
26 #        When this environment variable is defined (however excludes its 
27 #        value is "no"), this script works to generate compiler's temporary
28 #        file (.i file) automatically, and stores it.
29 #        When this environment variable is not defined, this script behaves
30 #        itself like just same as an real compiler.
31 #
32 #  - GAST_SAVE_DIR
33 #        When this environment variable is defined and its value can be
34 #        recognized a directory name, a genrerated temporary file (.i file)
35 #        by compiler is moved to this directory.
36 #        When that directory is not defined , this script attempt to create 
37 #        this directory.
38 #
39 #  - GAST_SOURCE_DIR
40 #        When this environment variable is defined and its value can be 
41 #        recognized a directory name, this script tries to get a relative 
42 #        path-name from a source file path-name by removing a directory 
43 #        name which this environment means. And that relative path-name 
44 #        applies under the GAST_SAVE_DIR directory to moved a temporary 
45 #        file (.i file).
46 #        When this environment variable is not defined or the value does
47 #        not match a header of source file path-name, this script get a 
48 #        relative path-name by removing "/" from a top of source file 
49 #        path-name.
50 #
51 #        [ NOTICE ]
52 #        When this environment variable is not defined and this script 
53 #        is executed under a build process by 'rpmbuild' command, this 
54 #        script tries to define GAST_SOURCE_DIR automatically with a 
55 #        value of RPM_BUILD_DIR.
56 #
57 #  - GAST_CONTENT_WITHOUT_H
58 #        When this environment variable is defined (however excludes its 
59 #        value is "no"), created temporary file (.i file) is modified.
60 #        Most of all the contents from header files are removed.
61 #
62 #        [NOTICE]
63 #        Only a track of including header file is left.
64 #
65 #  - GAST_CONTENT_UNIQUE
66 #        When this environment variable is not defined, If C source file 
67 #        is compiled any number of times with different compile options,
68 #        this script preserve each temporary files by adding number.
69 #
70 #        When this environment variable is defined (however excludes its
71 #        value is "no"), If compile options are different but content of
72 #        temporary file is same as one of some previous stored files, 
73 #        this script does preserve current temporary file.
74
75 VERSION="0.1.1"
76 ORIGINAL_NAME="gast"
77 CWD=`pwd`
78 SCRIPT_NAME=${0##*/}
79 SCRIPT_PATH=${0%/*}
80
81 WORK_DIR="/tmp"
82
83 # If this script is executed under the rpmbuild command, GAST_SOURCE_DIR is
84 # forced to set value by RPM_BUILD_DIR environment variable's one.
85 [ -n "${RPM_BUILD_DIR}" ] && GAST_SOURCE_DIR="${RPM_BUILD_DIR}"
86 [ -n "${GAST_SOURCE_DIR}" ] && GAST_SOURCE_DIR=${GAST_SOURCE_DIR%/}
87
88 # When this script is executed as 'gast', this script creates symbolic link
89 # file name as compiler alias name and exit.
90 if [ "${SCRIPT_NAME}" = "${ORIGINAL_NAME}" ]; then
91     for arg in "$@"
92     do
93         case "${arg}" in
94         '--version'|'-v')
95             echo "${ORIGINAL_NAME} ${VERSION}"
96             exit 0
97             ;;
98         '--help'|'-h')
99             cat <<EOL
100 usage : ${ORIGINAL_NAME} [-v|--version] [-h|--help]
101
102   -h, --help              display this help and exit
103   -v, --version           display version information and exit
104
105   When this script is executed as '${ORIGINAL_NAME}' with no option, this script 
106   creates symbolic link toward itself which name is from compiler (e.g. gcc/cc).
107
108   When this script is executed as compiler, this script executes original 
109   compiler with additional -save-temps option (If GAST_ENABLE is defined), and
110   stores temporary files.
111
112 [ environment variables to control storing temporary files ]
113     GAST_ENABLE              : add '-save-temps' option automatically
114                                in compiling when this variable is defined.
115     GAST_SAVE_DIR=<DIR>      : directory name temporary file is stored in.
116     GAST_SOURCE_DIR=<DIR>    : top directory of source tree.
117     GAST_CONTENT_WITHOUT_H : slim temporary files when this variable 
118                                is defined.
119 EOL
120             exit 0
121             ;;
122         *)
123             echo "usage : ${ORIGINAL_NAME} [--version|-v] [--help|-h]"
124             exit 1
125             ;;
126         esac
127     done
128
129     cd "${SCRIPT_PATH}"
130     for cmd in gcc cc
131     do
132         if [ -e "${cmd}" ]; then
133             file "${cmd}" 2>/dev/null | egrep 'symbolic link' >/dev/null 2>&1
134             if [ "$?" -ne 0 ]; then
135                 echo "${ORIGINAL_NAME} : '${cmd}' exists as normal file or directory. Could not create symbolic link." >&2
136                 continue
137             fi
138         fi
139         ln -sf "${ORIGINAL_NAME}" "${cmd}"
140         if [ "$?" -eq 0 ]; then
141             echo "${ORIGINAL_NAME} : Create symbolic link '${cmd}'" >&2
142         else
143             echo "${ORIGINAL_NAME} : Couldn't create symbolic link '${cmd}'" >&2
144         fi
145     done
146     exit 0
147 fi
148
149 # get a directory path (full-path) of this script
150 case "${SCRIPT_PATH}" in
151     '.')        SCRIPT_PATH="${CWD}" ;;
152     \./*)       SCRIPT_PATH="${CWD}/${SCRIPT_PATH#*/}" ;;
153     '~')        SCRIPT_PATH="${HOME}" ;;
154     \~/*)       SCRIPT_PATH="${HOME}/${SCRIPT_PATH#*/}" ;;
155     \/*)                :       ;;
156     *)          SCRIPT_PATH="${CWD}/${SCRIPT_PATH}" ;;
157 esac
158
159 # leaves out a script path name from PATH environment variable because of
160 # executing original compiler binary file in this script.
161 path_tmp=
162 for dir in ${PATH//:/ }
163 do
164     [ "${dir}" = "${SCRIPT_PATH}" ] && continue
165     [ "${dir}" = '.' ] && dir="${CWD}"
166
167     if [ -z "${path_tmp}" ]; then
168         path_tmp="${dir}"
169     else
170         path_tmp="${path_tmp}:${dir}"
171     fi
172 done
173 export PATH=${path_tmp}
174 unset path_tmp
175
176 # When GAST_ENABLE does't exist, execute an original compiler and exit.
177 if [ "${GAST_ENABLE=no}" = "no" ]; then
178     exec "${SCRIPT_NAME}" "$@"
179 fi
180
181 # Check whether C source file(s) is/are specified.
182 # And create new commandline arguments.
183 asm_notdelete_flg=
184 pipe_flg=
185 idx=0
186 idx_src=0
187 for arg in "$@"
188 do
189     new_args[$idx]="${arg}"
190
191     case "${arg}" in
192     *.c)        src_list[$idx_src]="${arg}"
193                 let idx_src+=1
194         ;;
195     '-S')       asm_notdelete_flg="YES"
196         ;;
197     '-pipe')    pipe_flg="YES"
198                 new_args[$idx]="-save-temps"
199         ;;
200     esac
201     let idx+=1
202 done
203
204 # Compiling
205 if [ -z "${src_list[*]}" ]; then
206     # No C source file was specified. -save-temps option does not need.
207     exec "${SCRIPT_NAME}" "$@"
208 else
209     # C source file(s) is/are specified. -save-temp option is set to argument.
210     if [ -z "${pipe_flg}" ]; then
211         "${SCRIPT_NAME}" -save-temps "$@"
212     else
213         "${SCRIPT_NAME}" "${new_args[@]}"
214     fi
215     RET=$?
216 fi
217
218
219 # modify a value of  GAST_SOURCE_DIR environment variable to full-path name.
220 # When GAST_SOURCE_DIR doesn't exist, define it and set null.
221 case "${GAST_SOURCE_DIR}" in
222     '')         GAST_SOURCE_DIR="/" ;;
223     '.')        GAST_SOURCE_DIR="${CWD}" ;;
224     \./*)       GAST_SOURCE_DIR="${CWD}/${GAST_SOURCE_DIR#*/}" ;;
225     '~')        GAST_SOURCE_DIR="${HOME}" ;;
226     \~/*)       GAST_SOURCE_DIR="${HOME}/${GAST_SOURCE_DIR#*/}" ;;
227     \/*)                :       ;;
228     *)  GAST_SOURCE_DIR="${CWD}/${GAST_SOURCE_DIR}" ;;
229 esac
230 GAST_SOURCE_DIR="${GAST_SOURCE_DIR%/}"
231 # NOTE : In above process, when user set GAST_SOURCE_DIR as "/" or
232 #         GAST_SOURCE_DIR doesn't exists, value of GAST_SOURCE_DIR is set to "".
233
234 # When GAST_CONTENT_WITHOUT_H environment variable exist, define a filter 
235 # function.
236 HAS_PERL=`which perl 2>/dev/null`
237
238 if [ "${GAST_CONTENT_WITHOUT_H=no}" != "no" ]; then
239     if [ -n "${HAS_PERL}" ]; then
240         function filter_tempfile_perl () {
241             perl -e '
242 #----------(start temporary perl script)-------------------------
243 BEGIN {
244   $prnt_flg=-1;
245   $fname="'$1'";
246 }
247 while (<>) {
248   if (/^#\s+(\d+)\s+"([^"]+)".*$/) {
249       $line_no = $1;
250       $cfile = $2;
251       $cfile =~ s{^.*/}{};
252       if ($cfile !~ /\.h$/) {
253           print;
254           $prnt_flg = $line_no if ($prnt_flg >= 0);
255       } elsif ($cfile =~ /\.h$/) {
256           print if ($line_no == 1);
257           $prnt_flg = 0;
258       } elsif ($cfile =~ /^</) {
259           print;
260           $prnt_flg = 0  if ($cfile eq "<built-in>");
261       } else {
262           if  ($prnt_flg == -1) {
263               print;
264           } elsif  ($line_no == 1) {
265               print;
266               $prnt_flg = 0 if ($prnt_flg >= 0);
267           } else {
268               $prnt_flg = 0;
269           }
270       }
271   } elsif ($prnt_flg > 0) {
272       print;
273   }
274 }
275 #----------(end temporary perl script)---------------------------
276 '
277         }
278
279         function delete_added_info_perl () {
280             perl -e '
281 #----------(start temporary perl script)-------------------------
282 BEGIN {
283   $prnt_flg=0;
284 }
285 while (<>) {
286   if ($prnt_flg != 0) {
287       print;
288   } elsif (/^#\s+(\d+)\s+"<compile-options>/) {
289       $prnt_flg=1;
290   }
291 }
292 #----------(end temporary perl script)---------------------------
293 '
294         }
295
296     elif [ ${BASH_VERSION%%.*} -ge 3 ]; then
297         function filter_tempfile_bash () {
298             prnt_flg=-1
299             fname="$1"
300             IFS_SAVE=$IFS
301             IFS=
302             while read line
303             do
304                 if [[ "${line}" =~ "^#[ \t]+([0-9]+)[ \t]+\"([^\"]+)\".*$" ]]; then
305                     line_no=${BASH_REMATCH[1]}
306                     cfile=${BASH_REMATCH[2]}
307                     cfile=${cfile##*/}
308                     #echo "$line_no : $cfile"
309                     if [[ ! "${cfile}" =~ "\.h$" ]]; then
310                         echo "${line}"
311                         [ "${prnt_flg}" -ge 0 ] && prnt_flg="${line_no}"
312                     elif [[ "${cfile}" =~ "\.h$" ]]; then
313                         [ "${line_no}" -eq 1 ] && echo "${line}"
314                         prnt_flg=0
315                     elif [ "${cfile:0:1}" = "<" ]; then
316                         echo "${line}"
317                         [ "${cfile}" = "<built-in>" ] && prnt_flg=0
318                     else
319                         if [ "${prnt_flg}" -eq -1 ]; then
320                             echo "${line}"
321                         elif [ "${line_no}" -eq 1 ]; then
322                             echo "${line}"
323                             [ "${prnt_flg}" -ge 0 ] && prnt_flg=0
324                         else
325                             prnt_flg=0
326                         fi
327                     fi
328                 else
329                     [ "${prnt_flg}" -gt 0 ] && echo "${line}"
330                 fi
331             done
332             IFS=$IFS_SAVE
333         }
334
335         function delete_added_info_bash () {
336             prnt_flg=0
337             IFS_SAVE=$IFS
338             IFS=
339             while read line
340             do
341                 if [ "${prnt_flg}" -ne 0 ]; then
342                     echo "${line}"
343                 elif [[ "${line}" =~ "^#[ \t]+[0-9]+[ \t]+\"<compile-options" ]]; then
344                     prnt_flg=1
345                 fi
346             done
347             IFS=$IFS_SAVE
348         }
349     fi
350 fi
351
352 # processes the compiler's temporary file
353 for src_file in "${src_list[@]}"
354 do
355     # get full-path name of a C source file.
356     fullname=
357     case "${src_file}" in
358     \/*)        fullname="${src_file}" ;;
359     \~/*)       fullname="${HOME}/${src_file#*/}" ;;
360     \./*)       fullname="${CWD}/${src_file#*/}" ;;
361     *)          fullname="${CWD}/${src_file}" ;;
362     esac
363
364     # Get relative-path of C source file from GAST_SOURCE_DIR directory.
365     basename="${fullname##*/}"  # C source filename
366     dirname="${fullname%/*}"    # directory name of C source file (full-path)
367     target="${basename/%.c/.i}" # Compiler's temporary filename (.i file)
368     asmfile="${basename/%.c/.s}"        # Compiler's temporary filename (.s file)
369     src_relative_dir=           # directory name of C source file (relative-path from GAST_SOURCE_DIR)
370
371     case "${fullname}" in
372     ${GAST_SOURCE_DIR}/*)       # contain GAST_SOURCE_DIR=""
373         if [ "${dirname}" = "${GAST_SOURCE_DIR}" ]; then
374             src_relative_dir="."
375         else
376             src_relative_dir="${dirname#${GAST_SOURCE_DIR}/}"
377         fi
378         ;;
379     *)
380         # source file is not under the GAST_SOURCE_DIR directory.
381         # Set the relative-path from '/' directory.
382         src_relative_dir="${dirname#/}"
383         ;;
384     esac
385     [ "${src_relative_dir:0:1}" = '/' ] && src_relative_dir="${src_relative_dir#/}"
386     # Delete assembler temporary file (except that compiler is executed with -S option)
387     [ -e "${asmfile}" -a -z "${asm_notdelete_flg}" ] && rm -f "${asmfile}" > /dev/null 2>&1
388
389     if [ -e "${target}" ]; then
390         if [ "${target}" = "conftest.i" ]; then
391             # temporary file is created by 'configure' script as test.
392             rm -f "${target}"
393             continue
394         fi
395
396         # Add an information in the header of temporary file.
397         # When filtering temporary file was specified, try it.
398         tmpfile_org=
399         until [ -n "${tmpfile_org}" -a ! -e "${tmpfile_org}" ]
400         do
401             tmpfile_org="${target}.${RANDOM}${RANDOM}${RANDOM}"
402         done
403         trap "rm -rf ${tmpfile_org} ${target} 2>/dev/null" HUP INT QUIT ABRT TERM XCPU
404
405         mv "${target}" "${tmpfile_org}"
406         if [ $? -eq 0 ]; then
407             echo "# 1 \"<compile-options> $@\"" > "${target}"
408             #if [ -z "${pipe_flg}" ]; then
409             #    echo "# 1 \"<modified compile-options> -save-temps $@\"" >> "${target}"
410             #else
411             #    echo "# 1 \"<modified compile-options> ${new_args[@]}\"" >> "${target}"
412             #fi
413
414             if [ "${GAST_CONTENT_WITHOUT_H=no}" = "no" ]; then
415                 cat "${tmpfile_org}" >> "${target}"
416             else
417                 #echo "# 1 \"<GAST_CONTENT_WITHOUT_H>\"" >> "${target}"
418                 if [ -n "${HAS_PERL}" ]; then
419                     filter_tempfile_perl "${basename}" < "${tmpfile_org}" >> "${target}"
420                 elif [ ${BASH_VERSION%%.*} -ge 3 ]; then
421                     filter_tempfile_bash "${basename}" < "${tmpfile_org}" >> "${target}"
422                 else
423                     cat "${tmpfile_org}" >> "${target}"
424                 fi
425             fi
426         fi
427
428         # Check whether the GAST_SAVE_DIR has a value or not.
429         # When it has a value, that is recognized as a directory name.
430         # When such directory does not exist, try to create.
431         if [ -n "${GAST_SAVE_DIR}" ]; then
432             GAST_SAVE_DIR="${GAST_SAVE_DIR%/}"
433             if [ ! -d "${GAST_SAVE_DIR}" ]; then
434                 if [ -e "${GAST_SAVE_DIR}" ]; then
435                     echo "${ORIGINAL_NAME} : ${GAST_SAVE_DIR} is not a directory." >&2
436                     GAST_SAVE_DIR=
437                 else
438                     mkdir -p "${GAST_SAVE_DIR}"
439                     if [ $? -ne 0 ]; then
440                         echo "${ORIGINAL_NAME} : Couldn't create ${GAST_SAVE_DIR} directory." >&2
441                         GAST_SAVE_DIR=
442                     fi
443                 fi
444             fi
445         fi
446
447         # define a directory path-name of temporary file to resotre.
448         temp_dirname=
449         if [ -n "${GAST_SAVE_DIR}" ]; then
450             # resolve a directory path-name of a temporary file to move.
451
452             if [ "${src_relative_dir}" = "." ]; then
453                 temp_dirname="${GAST_SAVE_DIR}"
454             else
455                 temp_dirname="${GAST_SAVE_DIR}/${src_relative_dir}"
456             fi
457
458             # Check a directory to move a temporary file.
459             if [ -e "${temp_dirname}" ]; then
460                 if [ ! -d "${temp_dirname}" ]; then
461                     echo "${ORIGINAL_NAME}(${SCRIPT_NAME}): ${temp_dirname} is not a directory. Couldn't move ${target} to ${temp_dirname} ." >&2
462                     temp_dirname=
463                 fi
464             else
465                 # create a directory
466                 mkdir -p ${temp_dirname}
467                 if [ $? -ne 0 ]; then
468                     echo "${ORIGINAL_NAME}(${SCRIPT_NAME}): Couldn't create ${temp_dirname} directory, so couldn't move ${target} to ${temp_dirname} ." >&2
469                     temp_dirname=
470                 fi
471             fi
472         else
473             temp_dirname=
474         fi
475
476         # set destination filename(full-path) to move a temporary file.
477         loop_count=0
478         skip_moving=
479         dest_name=
480         dest_file=
481         if [ -n "${temp_dirname}" ]; then
482             dest_name=${target}
483             dest_file="${temp_dirname}/${dest_name}"
484         else
485             # When destination directory to store the temporary file doesn't
486             # exist, try to rename temporary file as '*****.0.i', because
487             # if some C source file is compiled over and over, old temporary
488             # file is overwrited by new one. We wish to avoid that.
489             dest_name="${target/%.i/.${loop_count}.i}"
490             dest_file="${dest_name}"
491         fi
492
493         while [ -e "${dest_file}" ]
494         do
495             let loop_count+=1
496             # software loop limit
497             if [ "${loop_count}" -ge 500 ]; then
498                 echo "${ORIGINAL_NAME}(${SCRIPT_NAME}): loop abort.  give up to move ${target} to ${temp_dirname} ." >&2
499                 skip_moving="YES"
500                 break
501             fi
502
503             # destination file already exists. compare a content.
504             cmp_result=
505             if [ "${GAST_CONTENT_UNIQUE=no}" != "no" ]; then
506                 # comparison excluding "<compile-option>" line.
507                 tmpfile_prev=
508                 until [ -n "${tmpfile_prev}" -a ! -e "${tmpfile_prev}" ]
509                 do
510                     tmpfile_prev="${WORK_DIR}/${destname}.${RANDOM}${RANDOM}${RANDOM}"
511                     tmpfile_cur="${WORK_DIR}/${target}.${RANDOM}${RANDOM}${RANDOM}"
512                     trap "rm -rf ${tmpfile_prev} ${tmpfile_cur} 2>/dev/null" HUP INT QUIT ABRT TERM XCPU
513                 done
514
515                 if [ -n "${HAS_PERL}" ]; then
516                     delete_added_info_perl < "${dest_file}" > "${tmpfile_prev}"
517                     delete_added_info_perl < "${target}" > "${tmpfile_cur}"
518                 elif [ ${BASH_VERSION%%.*} -ge 3 ]; then
519                     delete_added_info_bash < "${dest_file}" > "${tmpfile_prev}"
520                     delete_added_info_bash < "${target}" > "${tmpfile_cur}"
521                 fi
522                 if [ -e "${tmpfile_prev}" -a -e "${tmpfile_cur}" ]; then
523                     diff --brief "${tmpfile_prev}" "${tmpfile_cur}" > /dev/null 2>&1
524                     cmp_result=$?
525                 else
526                     # failed to compare. 
527                     cmp_result=1
528                 fi
529                 rm -f "${tmpfile_prev}" "${tmpfile_cur}" > /dev/null 2>&1
530                 trap - HUP INT QUIT ABRT TERM XCPU
531             else
532                 # comparison including "<compile-option>" line.
533                 diff --brief "${dest_file}" ${target} > /dev/null 2>&1
534                 cmp_result=$?
535             fi
536
537             if [ "${cmp_result}" -eq 0 ]; then
538                 # delete current temporary file.
539                 inode_from=`ls -i "${target}" | sed -e 's/\([0-9][0-9]*\)[^0-9]*/\1/'`
540                 inode_to=`ls -i "${dest_file}" | sed -e 's/\([0-9][0-9]*\)[^0-9]*/\1/'`
541                 [ "${inode_from}" != "${inode_to}" ] && rm -f "${target}"
542
543                 skip_moving="YES"
544                 break
545             else
546                 # same file already exists and contents is different.
547                 # try to store this temporary file with another filename
548                 # by using ${loop_count} number.
549                 dest_name="${target/%.i/.${loop_count}.i}"
550                 if [ -n "${temp_dirname}" ]; then
551                     dest_file="${temp_dirname}/${dest_name}"
552                 else
553                     dest_file="${dest_name}"
554                 fi
555             fi
556         done
557
558         # move a temporary file to a save-directory.
559         if [ -z "${skip_moving}" ]; then
560             mv -f "${target}" "${dest_file}" 2>/dev/null
561             [ $? -ne 0 ] && echo "${ORIGINAL_NAME}(${SCRIPT_NAME}): Couldn't move ${target} to ${dest_file} ." >&2
562         fi
563
564         # Delete original temporary file.
565         rm -f "${tmpfile_org}"
566         trap - HUP INT QUIT ABRT TERM XCPU
567
568     fi  #if [ -e "${target}" ]; then
569 done
570
571 # return with compiler's return code
572 exit "${RET}"