In a project, during the later stages of software development, the leadership proposed new requirements based on Client A’s requests to adjust some display information, while the overall logic remained largely unchanged. After accommodating these new requirements, Client B also raised new requests, requiring some customization of logic and display information. At this point, someone suggested copying the project code twice to specifically develop solutions for the custom needs of Client A and Client B.
However, after some development time, several common bugs were discovered during testing. Additionally, Clients C, D, E, and others came along, and synchronizing the solutions for these common bugs across all branches of code increased maintenance costs significantly. Developers faced a daunting task of maintaining multiple versions, which was time-consuming and labor-intensive.
So, is there a simpler way? Can we maintain just one set of code that can accommodate the different requirements of various clients?
To meet the above requirements, modifications need to be made to the source code and the corresponding Makefile, as shown below:
#include <stdio.h>
int main(){ printf("AAA\n");
return 0;}
The corresponding Makefile is as follows:
CC = gcc
STRIP = strip
CFLAGS += -g -Werror
OBJECST = test1.o
TARGET = test1
all: $(TARGET)
$(TARGET): $(OBJECST) $(CC) -o $(TARGET) $(OBJECST) $(CFLAGS) $(STRIP) --strip-all $(TARGET)
.c.o: $(CC) $(CFLAGS) -c -o $@ $<
.PHONY: clean
clean: rm -rf $(OBJECST) $(TARGET)
After executing the make command, the output is as follows:
test@ubuntu:~$ make
gcc -g -Werror -c -o test1.o test1.c
gcc -o test1 test1.o -g -Werror
strip --strip-all test1
test@ubuntu:~$ ./test1
AAA
At this point, Client B needs to print “BBB”, while Client C needs to print “CCC”, with other logic remaining unchanged (of course, this example code has no real logic; the actual project logic is much more complex). So what should we do? The source code needs to be modified as follows:
#include <stdio.h>
int main(){
#ifdef FLAG_A
printf("AAA\n");
#endif
#ifdef FLAG_B
printf("---BBB\n");
#endif
#ifdef FLAG_C
printf("---CCC\n");
#endif
return 0;
}
The Makefile is modified as follows:
CC = gcc
STRIP = strip
CFLAGS += -g -Werror
OBJECST = test1.o
TARGET = test1
all: $(TARGET)
$(TARGET): $(OBJECST) $(CC) -o $(TARGET) $(OBJECST) $(CFLAGS) $(STRIP) --strip-all $(TARGET)
.c.o: $(CC) $(CFLAGS) -c -o $@ $< -D$(MYFLAG)
.PHONY: clean
clean: rm -rf $(OBJECST) $(TARGET)
If we need to compile the code for Client B, we execute the make command with the following results:
test@ubuntu:~$ make MYFLAG=FLAG_B
gcc -g -Werror -c -o test1.o test1.c -DFLAG_B
gcc -o test1 test1.o -g -Werror
strip --strip-all test1
test@ubuntu:~$ ./test1
---BBB
If we need to compile the code for Client C, we execute the following:
test@ubuntu:~$ make MYFLAG=FLAG_C
gcc -g -Werror -c -o test1.o test1.c -DFLAG_C
gcc -o test1 test1.o -g -Werror
strip --strip-all test1
test@ubuntu:~$ ./test1
---CCC
The following example demonstrates how to pass parameters to code via Makefile, which is commonly used in large projects!
#include <stdio.h>
const char* data = STRING_DATA;
int main(){ printf("%s\n", data); printf("AAA\n");
return 0;
}
The corresponding Makefile is as follows:
CC = gcc
STRIP = strip
CFLAGS += -g -Werror
OBJECST = test1.o
TARGET = test1
all: $(TARGET)
MY_DATA="default_data"
$(TARGET): $(OBJECST) $(CC) -o $(TARGET) $(OBJECST) $(CFLAGS) $(STRIP) --strip-all $(TARGET)
.c.o: $(CC) $(CFLAGS) -DSTRING_DATA="$(MY_DATA)" -c -o $@ $<
.PHONY: clean
clean: rm -rf $(OBJECST) $(TARGET)
When executing the make command without parameters, the result is as follows:
test@ubuntu:~$ make
gcc -g -Werror -DSTRING_DATA="default_data" -c -o test1.o test1.c
gcc -o test1 test1.o -g -Werror
strip --strip-all test1
test@ubuntu:~$ ./test1
default_data
AAA
When executing the make command with parameters, the result is as follows:
test@ubuntu:~$ make MY_DATA="my_own_data"
gcc -g -Werror -DSTRING_DATA="my_own_data" -c -o test1.o test1.c
gcc -o test1 test1.o -g -Werror
strip --strip-all test1
test@ubuntu:~$ ./test1
my_own_data
AAA
Isn’t it simple? By cleverly using the parameter passing feature of Makefile, you can easily handle the different requirements of various clients, giving you more time to read books and enjoy tea.