Thread: Making gcc return value other than 0 (or 1) when compiling with warnings.

  1. #1
    Registered User
    Join Date
    May 2013
    Posts
    228

    Making gcc return value other than 0 (or 1) when compiling with warnings.

    Hi.
    This is not so much C related as it is gcc related, I hope it's appropriate here.

    When you compile a C program with gcc, and the compilation fails, then gcc returns 1:

    test.c:
    Code:
    int main() {
    
        printf("x is: %d", x);
        return 0;
    }

    Code:
    #! /bin/bash
    
    $ gcc test.c
    test.c:8:21: error: ‘x’ undeclared (first use in this function)
      printf("x is: %d", x);
                         ^
    test.c:8:21: note: each undeclared identifier is reported only once for each function it appears in
    $ echo $?
    1
    $
    On the other hand, when it compiles without any errors, it'll return 0:

    Code:
    int main() {
    
        int x=5;
    
        printf("x is: %d", x);
        return 0;
    }

    Code:
    #! /bin/bash
    
    $ gcc test.c
    $ echo $?
    0
    $
    However, when the compiler issues a warning, it also returns 0:

    Code:
    int main() {
    
        int x=5;
    
        printf("x is: %d");
        return 0;
    }

    Code:
    #! /bin/bash
    
    $ gcc test.c
    test.c: In function ‘main’:
    test.c:10:2: warning: format ‘%d’ expects a matching ‘int’ argument [-Wformat=]
      printf("x is: %d");
      ^
    $ echo $?
    0
    $
    Can I somehow change this behaviour?
    More specifically, I'd like gcc to return 2 (or any other value other than 0 or 1) when it issues warning(s).
    Is it possible?

    The reason it will be useful to me is that I plan to write a shell (bash) script that will compile C files, and will react differently depending on if the compilation was successful, was erroneous, or with warnings.

    Any help would greatly appreciated.
    Regards.

  2. #2
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    O_o

    You could use a flag that tells "GCC" to treat all warnings as errors. You make combine two passes and get change the value yourself.

    You can use `awk` to search for "warning:" in the output.

    I don't think you can change the exit code for warnings.

    Soma
    “Salem Was Wrong!” -- Pedant Necromancer
    “Four isn't random!” -- Gibbering Mouther

  3. #3
    Registered User rstanley's Avatar
    Join Date
    Jun 2014
    Location
    New York, NY
    Posts
    1,111
    More specifically, I'd like gcc to return 2 (or any other value other than 0 or 1) when it issues warning(s).
    Is it possible?
    If gcc was written to either return 1 or 0, and no other value, then no, it is not possible. You could always examine the source code for gcc to confirm, but I wouldn't bother.

    The reason it will be useful to me is that I plan to write a shell (bash) script that will compile C files, and will react differently depending on if the compilation was successful, was erroneous, or with warnings.
    I would not recommend using a bash script to compile .c files. First of all, Makefiles are the way to compile multi-file programs. Secondly, the programmer should be examining the output from all compilations, rather than relying on a return value from gcc. Unless the bash script(s) are capturing the output from gcc for each compile, and writing to one or more log files, then you are losing this information!

    As I used to tell my students on more than one occasion per semester, "Doctor, Doctor! It hurts when I do this!" "Don't do it!" ;^)

  4. #4
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    Unless the bash script(s) are capturing the output from gcc for each compile, and writing to one or more log files, then you are losing this information.
    O_o

    Nonsense.

    Unless you are doing some sort of on-the-fly code generation, the code itself exists.

    You can see the information anytime you like just by running the code through the compiler.

    Soma
    “Salem Was Wrong!” -- Pedant Necromancer
    “Four isn't random!” -- Gibbering Mouther

  5. #5
    Registered User rstanley's Avatar
    Join Date
    Jun 2014
    Location
    New York, NY
    Posts
    1,111
    No, not nonsense.
    The reason it will be useful to me is that I plan to write a shell (bash) script that will compile C files, and will react differently depending on if the compilation was successful, was erroneous, or with warnings.
    So if the OP is compiling 100 source files by one run of the script, and NOT logging the text output of gcc, then how does the OP see the warnings and errors that are generated.

    Yes, the code exists, and at least some of the executables are generated. It was not clear if the script was to compile one file at a time, or 100 files in one run of the script. I assumed the latter.

    In either case, make and Makefiles are a better choice to a simple script.

  6. #6
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    No, not nonsense.
    O_o

    Following nonsense with nonsense isn't doing you any favors.

    So if the OP is compiling 100 source files by one run of the script, and NOT logging the text output of gcc, then how does the OP see the warnings and errors that are generated.
    Do you have any point? Sorry. Do you have any point which isn't nonsensical?

    The comment you quoted says that the original poster wants to change the behavior of a script depending on if the code has an error or a warning.

    The comment you quoted does not say that the original poster wants to ignore, redirect, or otherwise discard the "GCC" output.

    The command `gcc test.c && echo test.c >clean.txt || echo test.c >failed.txt` within a script will react differently depending on errors, but the output from "GCC" remains wherever the user of the script puts the output such as a file or terminal window.

    As I said, the statement "Unless the bash script(s) are capturing the output from gcc for each compile, and writing to one or more log files, then you are losing this information!" is nonsense.

    In either case, make and Makefiles are a better choice to a simple script.
    The right tool for the job depends on the actual goal which neither of use knows.

    Soma
    “Salem Was Wrong!” -- Pedant Necromancer
    “Four isn't random!” -- Gibbering Mouther

  7. #7
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    Hey use guys, let's look at a practical example instead.

    While I normally use Makefiles for my own code, I often use Bash scripts with ANSI-colored text output when compiling/porting existing code. Here is what I might use, if I wanted to differentiate between code that compiles in GCC without warnings, and code that does compile successfully but with warnings:
    Code:
    #!/bin/bash
    
    # Automatically removed temporary work directory.
    WORK="$(mktemp -d)" || exit 1
    trap "cd / ; rm -rf '$WORK'" EXIT
    
    # Default CFLAGS, if none specified.
    [[ -v CFLAGS -o -n "$CFLAGS" ]] || CFLAGS="-Wall -Wextra -O2"
    export CFLAGS
    
    # Default CC, if none specified. (Sometimes useful to set GCC version.)
    [[ -v CC -o -n "$CC" ]] || CC="gcc"
    export CC
    
    # Compile with warnings as errors.
    ( "$CC" "$CFLAGS" -Werrors "$@" 2>&1
      echo $? > "$WORK/status"
    ) | tee "$WORK/warnings.txt" >&2
    status=$(cat "$WORK/status" 2>/dev/null)
    if [[ -z "$status" ]]; then
        [[ -t 1 ]] || cat "$WORK/warnings.txt"
        printf '\n\033[1;31mAborted.\033[0m\n' >&2
        exit 3
    elif [[ "$status" = "0" ]]; then
        [[ -t 1 ]] || cat "$WORK/warnings.txt"
        printf '\n\033[1;32mCompiled without warnings.\033[0m\n' >&2
        exit 0
    else
        [[ -t 1 ]] || printf 'Compiling with warnings treated as errors:\n\n'
        [[ -t 1 ]] || cat "$WORK/warnings.txt"
        [[ -t 1 ]] || printf '\n\n\n\n\nRecompiling:\n\n'
        printf '\n\033[1;31mFailed; recompiling:\033[0m\n' >&2
    fi
    
    # Recompile.
    rm -f "$WORK/status"
    ( "$CC" "$CFLAGS" "$@" 2>&1
      echo $? > "$WORK/status"
    ) | tee "$WORK/errors.txt" >&2
    status=$(cat "$WORK/status" 2>/dev/null)
    [[ -t 1 ]] || cat "$WORK/errors.txt"
    if [[ -z "$status" ]]; then
        printf '\n\033[1;31mAborted.\033[0m\n' >&2
        exit 3
    elif [[ "$status" = "0" ]]; then
        printf '\n\033[1;33mCompiled with warnings.\033[0m\n' >&2
        exit 2
    else
        printf '\n\033[1;31mCompilation failed.\033[0m\n' >&2
        exit 1
    fi
    The idea above is that I prefer to have the compiler output running in that window, so I can tell with a single glance whether it is ready. The level of attention I give it does vary a lot.

    The [[ -t 1 ]] Bash test is true if standard output is a terminal. Thus, if I just run the script, I get the output on my terminal, with a colored line at end to tell me the end result. However, if I pipe standard output to a file (> log), I still see the same stuff in my terminal, but I will also get full output from the compiler into the file.

    I could even save the above as an executable in ~/bin/careful-gcc, with $HOME/bin in my PATH, so I could use it as a GCC wrapper. Even in Makefiles (via CC=careful-gcc).

    Instead of tee, I could instead use an awk script to highlight the parts that interest me. For example, warnings with one color (cyan-ish? purple?), and errors in another (red?). If I also drop the recompilation part, then I've just made a pretty useful GCC wrapper that I could use even in Makefiles.

    In other words, I don't understand why you are treating this as an either-or question. It is not. To me, it feels natural to write a shell script wrapper around a command used in Makefiles, to make the output conform to my workflow better.

  8. #8
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    Hey use guys, let's look at a practical example instead.
    In other words, I don't understand why you are treating this as an either-or question.
    O_o

    The script is fine, and I use a script that highlights different output, but I think you maybe didn't understand my complaint thus the argument.

    I honestly can't tell because "you" is rather ambiguous as I'm reading the post.

    My complaint was the assertion that supplemental files were a necessary requirement of the shell script without even knowing what the script is supposed to accomplish.

    You use supplemental files, as far as I understood from a casual look, to support the "always shows in a terminal" mechanic.

    I only do highlighting for terminals without needing supplemental files.

    Both are perfectly fine because we are solving different problems.

    To me, it feels natural to write a shell script wrapper around a command used in Makefiles, to make the output conform to my workflow better.
    I use the "Arch" distribution where the innumerable `PKGBUILD` are barely more than shell scripts wrapping make commands.

    Soma
    “Salem Was Wrong!” -- Pedant Necromancer
    “Four isn't random!” -- Gibbering Mouther

  9. #9
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    Quote Originally Posted by phantomotap View Post
    My complaint was the assertion that supplemental files were a necessary requirement of the shell script without even knowing what the script is supposed to accomplish.
    Okay, I kind of double-missed that. I mean, I don't understand how supplemental files have anything to do with anything in this thread, and I missed the assertion too.

    The need for both logging and saving the output to a file arises from huge projects, that take an hour or more to compile. Lets me see the progress with a glimpse, and run some greps and seds on the output afterwards to check for possible hidden issues. Works for me, without me having to write a complicated command line command. The habit started for me years ago, when I was active in Linux From Scratch; others' workflows are probably different.

    Thinking about this a bit further:

    To compile C programs, Makefiles are a very good tool, and I warmly suggest OP use those.

    One reason is that make is commonly available. Another reason is that you need to write very little code ("recipes") to compile even a complex project. My skeleton Makefile is more or less
    Code:
    CC      := gcc
    CFLAGS  := -Wall -Wextra -Wno-unused-parameter -O2 -fomit-frame-pointer
    LD      := $(CC)
    LDFLAGS := 
    PROGS   := example
    
    .PHONY: all clean
    
    all: clean $(PROGS)
    
    clean:
    	rm -f *.o $(PROGS)
    
    %.o: %.c
    	$(CC) $(CFLAGS) -c $^
    
    example: example.o
    	$(LD) $(LDFLAGS) $^ -o $@
    but note that the indentation uses tabs, not spaces, at the beginning of the indented lines. The variables set at the beginning can be overridden at the command line. The CFLAGS is just my own preference, for now. I only include the clean rule in the all rule for small projects, when it is better to always compile from scratch (as it takes longer to type even the clean in the command line).

    However, make stops the execution whenever a command exits with a nonzero exit status (unless the command is preceded with a dash). This means OP's idea about having a different exit status if warnings were issued, does not work with Makefiles.

    An additional wrinkle is that GCC output is localized, and to correctly detect the warnings, the locale should be set to a fixed one (C is always available).

    Combining the above, and some slight ANSI coloring, I would suggest the OP try a Bash wrapper script around gcc along the following lines:
    Code:
    #!/bin/bash
    [[ -v CFLAGS -o -n "$CFLAGS" ]] || CFLAGS="-Wall -Wextra -O2 -fomit-frame-pointer"
    [[ -v CC -o -n "$CC" ]] || CC="gcc"
    
    LANG=C LC_ALL=C
    export LANG LC_ALL
    
    [[ -t 2 ]] && printf '\033[1;37m%s \033[1;33m%s \033[1;37m%s\033[0m\n' "$CC" "$CFLAGS" "$*" >&2
    
    [[ -t 2 ]] && errtty=1 || errtty=0
    
    "$CC" "$CFLAGS" "$@" 2>&1 | awk -v errtty=$errtty 'BEGIN { RS="[\t\v\f ]*(\r\n|\n\r|\r|\n)" ; FS="[\t\v\f ]+" ; errs=0; warns=0 }
        $2=="error:" { errs++; if (errtty) $2="\033[1;31merror\033[0;31m:\033[0m"; }
        $2=="warning:" { warns++; if (errtty) $2="\033[1;33mwarning\033[0;33m:\033[0m"; }
        $2=="In" && $3=="function" && errtty { sub(/^./, "", $4); sub(/.:$/, "", $4); $4="\047\033[1;37m" $4 "\033[0m\047:" }
        $2=="note:" && errtty { $2="\033[1;32mnote\033[0;32m:\033[0m" }
        1
        END { if (errs>0) exit(1); if (warns>0) exit(31); exit(0) }'
    case "${PIPESTATUS[0]}:${PIPESTATUS[1]}" in
        0:0)
            [[ -t 2 ]] && printf '\033[1;37mSuccess\033[0m\n' >&2
            [[ -t 1 ]] || printf '=> Success\n'
            exit 0
            ;;
        0:31)
            [[ -t 2 ]] && printf '\033[1;37mSuccess\033[0;33m ,\033[1;33mwith warnings\033[0m\n' >&2
            [[ -t 1 ]] || printf '=> Success, with warnings\n'
            exit 0
            ;;
        *)
            [[ -t 2 ]] && printf '\033[1;31mFailure\033[0m\n' >&2
            [[ -t 1 ]] || printf '=> Failure\n'
            exit 1
            ;;
    esac
    Note that this one does not use any supplementary/temporary files; it is only a complicated wrapper around the command.

    If standard error is a terminal, certain keywords will be highlighted, plus a colored "Success", "Success, with warnings", or "Failure" will be printed at the end of output.

    If standard output is redirected to a file, it will add "=> Success", "=> Success, with warnings", or "=> Failure" at the end of output.

    If compilation was successful, with or without warnings, the exit status of the script will be zero, and thus won't stop the Makefile from executing.

    This should be useful for both command-line usage, as well as Makefile usage. I think it's a middling-good example of how to augment Makefiles/gcc via a shell script, without causing grief.

  10. #10
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    I think it's a middling-good example of how to augment Makefiles/gcc via a shell script, without causing grief.
    O_o

    I'd say reusing the `CC` environment variable isn't a good idea.

    I know why you reused the `CC` variable, but I can see the approach resulting in recursion when paired with make systems which limits the utility a bit.

    [Edit]
    You also have a bug in quoting the `CFLAGS` environment variable as passed in the "GCC" command.
    [/Edit]

    Soma
    “Salem Was Wrong!” -- Pedant Necromancer
    “Four isn't random!” -- Gibbering Mouther

  11. #11
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    Quote Originally Posted by phantomotap View Post
    reusing the `CC` environment variable isn't a good idea
    Yup, better use say NICEGCC instead. Avoids accidentally having the script call itself, just because CC was set in a Makefile.

    Could even add a check, say
    Code:
    NICEGCC_WRAPPED=$[$NICEGCC_WRAPPED+1]
    export NICEGCC_WRAPPED
    if [[ "$0" -ef "$NICEGCC" ]] || [[ "$0" -ef "$(which "$NICEGCC")" ]] || [ $NICEGCC_WRAPPED -gt 1 ]; then
        printf 'NICEGCC environment variable refers to this script!\n' >&2
        exit 9
    fi
    or something of that sort, to detect recursive situations.

    Quote Originally Posted by phantomotap View Post
    bug in quoting the `CFLAGS` environment variable
    Good catch. Should not be quoted at all, since it usually contains several parameters instead of just one.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. can't find what's making main return a non-zero value
    By spongefreddie in forum C++ Programming
    Replies: 11
    Last Post: 08-21-2011, 10:45 AM
  2. Replies: 4
    Last Post: 10-01-2009, 01:05 AM
  3. Warnings when compiling with command prompt (MSVC++)
    By knave in forum C++ Programming
    Replies: 6
    Last Post: 08-06-2003, 01:31 PM
  4. Replies: 4
    Last Post: 06-24-2003, 11:52 PM
  5. Making the Enter Key do a Return
    By BigSter in forum C++ Programming
    Replies: 7
    Last Post: 05-25-2002, 12:09 PM