Help running a Makefile from within a .sh script?

Hi there! I am a undergraduate student and recently submitted an assignment for my coursework - however there was one function I could not get to work properly before the due date. Although I don't need to complete this work anymore I would still like to in order to know what was going wrong. If this needs to be in the student/homework forum I am sorry! This is my first post so I'm new to the site and unsure.

The problem:
I have a function that is meant to compile any .c files that it finds in the current directory. It allows the user to either use their own Makefile (where the error is) - or use the Makefile within the .sh script which works. The Makefile is created with this function:

# Populate makefile with example code 
populateMFiles () {
echo '    clean:
          rm -rf cfile.c cfile.out
      build: 
          gcc -g -Wall -pedantic -Wextra cfile.c -w -o cfile.out
    all: cfile.c
        clean build'
}

And a .c file that is created with this function:

# Populate .c files with example code so that they can compile 
populateCFiles () {
echo '    #include <stdio.h>
    int main()
    {
           printf("Hello, World!");
           return 0;
    }'
}

Both of these functions are then used to fill two files as shown below:

populateCFiles > cfile.c
populateMFiles > Makefile

Both files are populated with the data correctly. I know the .c file can be compiled as it will do so if I compile it using the terminal. However, whenever I try to use the Makefile from within my bash script it returns the error message "ERROR: Makefile is incorrect - will not compile." which is defined in the method. The method in question is:

#Check repository for .c files
#Compile all .c files within the current directory
compileRepository () {
    tput cup $[$lineCount] 5 ; echo "============================================================"
    if [ -f $currentPath/*Makefile* ]; then # Detect makefiles in repository and allow user to use if desired
        tput cup $[1+$lineCount] 5 ; echo "Is there an existing make file within the repository" 
        tput cup $[2+$lineCount] 5 ; echo "you would like to use? (y/n) "
        read useMakefile
        if [[ $useMakefile == Y || $useMakefile == y ]]; then 
            cd $currentPath/
            make all
            if [ $? -eq 0 ]; then
                clear
                let lineCount=3
                tput cup 2 5 ; echo -e "${GREEN}           The .c files have succesfully compiled!          ${NC}"
                displayFileMenu
            else
                clear
                let lineCount=3
                tput cup 2 5 ; echo -e "${RED}      ERROR: Makefile is incorrect - will not compile.      ${NC}"
                displayFileMenu
            fi
    
        fi
    fi
    if [ -f $currentPath/*.c ]; then
        for everyFile in *.c ; do # Locate all .c files in directory         
            gcc -g -Wall -pedantic -Wextra "$everyFile" -w -o "$currentPath/${everyFile%.c}.out" # Acts as makefile for all .c files in repository
            if [ $? -eq 0 ]; then    # Checks return value from compiling files
                   clear
                let lineCount=3
                tput cup 2 5 ; echo -e "${GREEN}           The .c files have succesfully compiled!          ${NC}"
                displayFileMenu
                   else
                clear
                let lineCount=3
                   tput cup 2 5 ; echo -e "${RED}        ERROR: Code is incorrect - will not compile.        ${NC}"
                displayFileMenu
                   fi
        done  
    else
        clear
        let lineCount=3
        tput cup 2 5 ; echo -e "${RED}            ERROR: No .c files in the directory.            ${NC}"    
        displayFileMenu
    fi
}  

As you can see, the function will attempt to detect a Makefile already in the current directory and if there is it will ask the user if they wish to use it. If the user does decide to use it the function calls the make all command which will always throw up the error described previously. Why does it not work correctly?

Further information:
The $currentPath variable stores which directory the user is currently accessing and takes the form:
/tmp/Assignment1/Repository/Repository_Alpha
I thought the error could be with the Makefile itself so I've tried countless different Makefile formats taken from other forums/website - so I don't think the problem is with that. I've also tried calling the Makefile commands without the 'all' suffix i.e. just make but still returns the same error message.

Any help would be greatly appreciated, thank you!

  • cherryTango

Hi.

Judging by populateMFiles, which prints the contents of the makefile, the indentation is wrong. Perhaps removing leading spaces from the targets, and replacing the leading spaces of the commands with a tab would help?

With the code altered to read:

# Populate makefile with example code 
populateMFiles () {
echo 'clean:
    rm -rf cfile.c cfile.out
build: 
    gcc -g -Wall -pedantic -Wextra cfile.c -w -o cfile.out
all: cfile.c
    clean build'
}

It still returns the same error! Is there anything else I can try?

Are those spaces or a TAB character before rm ..., gcc ... and clean ...? Changing your function as-is as I suggested, it works fine. It needs to be a TAB, spaces won't work. If I click to edit your post, just see exactly what you pasted, I see spaces, not a TAB.

They are all tab characters. Still returning same error message. Also have changed the Makefile so that 'clean' does not remove the cfile.c before it is compiled:

clean:
    rm -rf cfile.out
build: 
    gcc -g -Wall -pedantic -Wextra cfile.c -w -o cfile.out
all: cfile.c
    clean build

It will now only remove any previous compilations of the file (I think?) - if I manage to get it working!

Is your editor set to replace a horizontal TAB with spaces?

Can you post the output of:

populateMFiles | od -c

Running the

populateMFiles | od -c

returned:

0000000   c   l   e   a   n   :  \n                   r   m       -   r
0000020   f       c   f   i   l   e   .   o   u   t  \n   b   u   i   l
0000040   d   :      \n                   g   c   c       -   g       -
0000060   W   a   l   l       -   p   e   d   a   n   t   i   c       -
0000100   W   e   x   t   r   a       c   f   i   l   e   .   c       -
0000120   w       -   o       c   f   i   l   e   .   o   u   t  \n   a
0000140   l   l   :       c   f   i   l   e   .   c  \n                
0000160   c   l   e   a   n       b   u   i   l   d  \n
0000174

What does this mean?

---------- Post updated at 11:48 AM ---------- Previous update was at 11:41 AM ----------

UPDATE: That was after copy and pasting the makefile code from this forum, when using the original code the output of that command was:

0000000   c   l   e   a   n   :  \n  \t   r   m       -   r   f       c
0000020   f   i   l   e   .   o   u   t  \n   b   u   i   l   d   :    
0000040  \n  \t   g   c   c       -   g       -   W   a   l   l       -
0000060   p   e   d   a   n   t   i   c       -   W   e   x   t   r   a
0000100       c   f   i   l   e   .   c       -   w       -   o       c
0000120   f   i   l   e   .   o   u   t  \n   a   l   l   :       c   f
0000140   i   l   e   .   c  \n  \t   c   l   e   a   n       b   u   i
0000160   l   d  \n
0000163

It means there are no TABs

After:

c   l   e   a   n   :  \n

Should be:

\t

But there are, instead only spaces.

I don't think your all: rule is working either, but it's that long since I've written a makefile, have forgotten more than I know :smiley: It doesn't help that your script is obfuscating the actual make error with its own. If you run just the function on its own, you would see the actual make error.

Okay, so after playing around with it I have changed the way the Makefile is created to:

populateMFiles () {
echo '
clean:    
    rm -rf cfile.out
build:    
    gcc -g -Wall -pedantic -Wextra cfile.c -w -o cfile.out
all:    cfile.c
    clean build'
}

There are TABs after clean: build: and all: and there are TABS before rm, gcc and clean. I have also changed the compileRepository method to:

#Check repository for .c files
#Compile all .c files within the current directory
compileRepository () {
    tput cup $[$lineCount] 5 ; echo "============================================================"
    if [ -f $currentPath/*Makefile* ]; then # Detect makefiles in repository and allow user to use if desired
        tput cup $[1+$lineCount] 5 ; echo "Is there an existing make file within the repository" 
        tput cup $[2+$lineCount] 5 ; echo "you would like to use? (y/n) "
        read useMakefile
        if [[ $useMakefile == Y || $useMakefile == y ]]; then 
            cd $currentPath/
            make
            exit

So it will just attempt to make it - then exit. This leaves the output on the screen which was an error message until I removed the 'all' from the make command. Now it outputs:

rm -rf cfile.out

Which is the code in the clean function of the Makefile. Have I set up my Makefile incorrectly do you think?

Arghh... really doesn't help that I had a file called makefile in my directory, which make was using instead of Makefile :smiley:

populateMFiles() {
#echo '.PHONY: all clean build
echo 'clean:
        rm -rf cfile.c cfile.out
build:
        gcc -g -Wall -pedantic -Wextra cfile.c -w -o cfile.out
all: clean build'
}

This does work fine, as far as I can see, so it would help to see the actual make error, not the one your script is displaying.

Saying nothing as to the rest of your code because I haven't read it, it would be slightly cleaner using cat instead of echo to print the Makefile and C file.

e.g.

populateMFiles() {
cat << EOF
clean:
        rm -rf cfile.c cfile.out
build:
        gcc -g -Wall -pedantic -Wextra cfile.c -w -o cfile.out
all: clean build
EOF
}
$ functions
populateMFiles() {
cat << EOF
clean:
	rm -rf cfile.out
build:
	gcc -g -Wall -pedantic -Wextra cfile.c -w -o cfile.out
all: clean build
EOF
}

$ cat cfile.c
int main( void ) {
  printf( "%s", "Hello world!\n" );
  return 1;
}

$ populateMFiles > Makefile

$ cat Makefile
clean:
	rm -rf cfile.out
build:
	gcc -g -Wall -pedantic -Wextra cfile.c -w -o cfile.out
all: clean build

$ make all
rm -rf cfile.out
gcc -g -Wall -pedantic -Wextra cfile.c -w -o cfile.out

$ ls cfile.out
cfile.out

$ ./cfile.out
Hello world!

Your makefile may remove the cfile.c file, so you need to run "populateCFiles" every time before you run make (as clean is the first rule), make all or make clean.

1 Like

After making the changes you suggested to my program the first error I recieved was:

Makefile:2: *** missing separator (did you mean TAB instead of 8 spaces?). Stop.

This was because I copy and pasted directly from the forum.. after correcting it and re-running the program I then recieved:

rm -rf cfile.c cfile.out
gcc -g -Wall -pedantic -Wextra cfile.c -w -o cfile.out
gcc: error: cfile.c: No such file or directory
gcc: fatal error: no input files
compilation terminated.
Makefile:4: recipe for target 'build' failed
make: *** [build] Error 1

Originally I thought this meant I was in the wrong directory to call the makefile but clearly not if it can actually interpret the lines from the file. I realised I had been deleting the .c file before compiling it so there was nothing to compile. After changing the clean statement to:

rm -rf cfile.out

Running the make all command returned:

rm -rf cfile.out
gcc -g -Wall -pedantic -Wextra cfile.c -w -o cfile.out

With no errors! However there is no cfile.out in the directory or any compiled c file! What else am I missing?

---------- Post updated at 01:05 PM ---------- Previous update was at 01:03 PM ----------

UPDATE: It is in the directory I just couldn't see it. Thank you so much for your help! Problem solved!!!

What if you run the gcc command directly (from the directory where the .c file is)?

gcc -g -Wall -pedantic -Wextra cfile.c -w -o cfile.out

Show your makefile:

cat Makefile

(and just to not make the same mistake I did, make sure there are no other makefiles there!)

UPDATE: OK, cool. You're welcome :slight_smile: