12 Make and CMake

Make

1. Introduction

  • What is Make?: Make is a build automation tool that automatically compiles source code into executable programs. It reads a file named Makefile to determine how to build a program.
  • Importance in Embedded Systems: In embedded systems, resources are limited and optimized code is critical. Make allows you to manage complex build pipelines efficiently, ensuring that only changed files are recompiled.

2. Basic Syntax

  • Makefile Structure: A Makefile consists mainly of "rules" that define how to derive a "target".
    • Target: The output or end goal, e.g., an executable file.
    • Prerequisites: Files needed to build the target. This can be source files, header files, or other targets, basically anything that, if changed, would require the target to be rebuilt.
    • Command: The shell command to create the target from the prerequisites.
target: prerequisites
    command
  • Rules, Targets, Prerequisites: Basic blocks of a Makefile. For example:
hello: hello.c
    gcc -o hello hello.c

Here, hello is the target, hello.c is the prerequisite, and gcc -o hello hello.c is the command to build the target.

3. Variables

  • Definition: Using = or :=.
CC = gcc
  • Usage: Refer to variables using $(VAR_NAME).
$(CC) -o hello hello.c

4. Functions

  • Commonly Used Functions: Make provides built-in functions for string manipulation, filename manipulation, etc.
    • $(wildcard pattern): Collects filenames matching a pattern.
    • $(patsubst pattern,replacement,text): Pattern-based string substitution.
SOURCES := $(wildcard *.c)
OBJECTS := $(patsubst %.c,%.o,$(SOURCES))

5. Conditional Statements

  • ifeq, ifndef, etc.: Conditional statements in Make allow you to alter the build process based on variables or operating environments.
  • ifeq (arg1, arg2): Executes the block if arg1 and arg2 are equal.
ifeq ($(CC),gcc)
    CFLAGS = -Wall
endif
  • ifndef (var): Executes the block if var is not defined.
ifndef DEBUG
    CFLAGS = -O2
endif

6. Phony Targets

  • .PHONY: A phony target is not a file but a label for a set of commands. Use .PHONY to indicate that the target doesn't correspond to a file. This is useful for targets like clean that don't produce an output file with the same name.
.PHONY: clean
clean:
    rm -f *.o
  • Why Phony Targets?: They help avoid conflicts between targets and filenames, and ensure the intended action gets executed even if there's a file with the same name as the target. Usually you should specify .PHONY for all targets that don't produce an output file, e.g., all, clean, install, etc.

7. Automatic Variables

  • What Are They?: Automatic variables are set by Make after a rule is matched. They save you from repeating filenames or directories.
    • $@: The target filename.
    • $^: The names of all the prerequisites, separated by spaces, and without duplicates.
    • $<: The name of the first prerequisite.
  • Usage: They are commonly used in generic rules.
%.o: %.c
    $(CC) $(CFLAGS) -c $< -o $@

In this example, $@ will be substituted with the .o file name, and $< will be replaced with the corresponding .c file name.

8. Common Makefile Patterns

  • Template to Compile Multiple Sources: You can use wildcards and automatic variables to compile multiple source files.
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)

all: $(OBJS)
  • Dependency Chains: Break down the build process into smaller steps with intermediary targets.
all: program

program: main.o util.o
    $(CC) -o program main.o util.o

main.o: main.c
    $(CC) -c main.c

util.o: util.c
    $(CC) -c util.c

9. Cross-Compilation

  • What is it?: Cross-compilation is the process of compiling code for a platform different from the one you are compiling on.
  • Setting Up Toolchain: Define the compiler and flags specific to the target platform.
CC = arm-none-eabi-gcc
CFLAGS = -mcpu=cortex-m3
  • Cross-Compile Example: A Makefile snippet to build for ARM.
all: target

target: target.c
    $(CC) $(CFLAGS) -o target target.c

10. Best Practices

  • Use .DEFAULT_GOAL: Explicitly set the default target if it's not the first one.
.DEFAULT_GOAL := all
  • Include Comments: Comment your Makefile for easier maintenance.
  • Utilize $(eval ...) Sparingly: This function is powerful but can make the Makefile hard to follow.
  • Keep It Simple: Makefiles can get very complex. Always aim for readability and maintainability.
  • Use .PHONY for Non-File Targets: As mentioned earlier, always specify .PHONY for targets that don't produce an output file.

11. Real-World Example

The corresponding section numbers are provided in the comments. Imagine you're developing firmware for an ARM-based device and have two source files: main.c and util.c.

# 1. Introduction
# Simple Makefile for an ARM-based embedded system project

# 3. Variables
CC = arm-none-eabi-gcc  # Compiler for ARM
CFLAGS = -Wall -mcpu=cortex-m3  # Compilation flags
SRCS = $(wildcard *.c)  # 4. Functions: Use wildcard to find all .c files
OBJS = $(patsubst %.c, %.o, $(SRCS))  # 4. Functions: String replacement

# 9. Cross-Compilation
# Set the default goal to 'all'. This is our custom target
# 10. Best Practices: Setting default goal
.DEFAULT_GOAL := all

# 2. Basic Syntax & 5. Conditional Statements
# Check if DEBUG is set for additional flags
ifeq ($(DEBUG), 1)
    CFLAGS += -g  # Add debugging flags
endif

# 8. Common Makefile Patterns
# Compile all .c files to .o
%.o: %.c
    $(CC) $(CFLAGS) -c $< -o $@  # 7. Automatic Variables

# 6. Phony Targets & 10. Best Practices
.PHONY: all clean  # Indicate that 'all' and 'clean' are phony targets

# 8. Common Makefile Patterns: Dependency Chains
# 'all' depends on 'firmware.elf'
all: firmware.elf

# 'firmware.elf' depends on all object files
firmware.elf: $(OBJS)
    $(CC) $(CFLAGS) $^ -o $@  # 7. Automatic Variables

# 6. Phony Targets: clean
# Remove all compiled files
clean:
    rm -f *.o firmware.elf

CMake

1. Introduction

  • What is CMake?: CMake is a cross-platform build-system generator. Instead of building your project, CMake generates build files based on configurations you provide. These generated files can then be processed with build tools like Make, Ninja, or even IDE-specific build systems.

  • Relevance in Embedded Systems: Yes, CMake is widely used in embedded systems development, especially as projects scale up in complexity. The ability to manage multiple targets, cross-compilation, and custom build steps makes it a powerful tool for embedded projects.

2. Installation and Setup

  • How to Install: You can typically install CMake through a package manager. For example, on Ubuntu:
sudo apt install cmake

On macOS:

brew install cmake

Or you can download it from the official website.

  • Initial Setup: After installing, you create a CMakeLists.txt file in your project directory. This file will contain all the CMake commands and configurations for your project.

3. Basic Commands

  • add_executable: Defines an executable target. It specifies the name of the executable and the source files needed.
add_executable(my_program main.cpp)
  • target_link_libraries: Specifies libraries to link against a target.
target_link_libraries(my_program PRIVATE some_library)
  • add_library: Defines a library target. You can specify STATIC (archive library) or SHARED (dynamic library) along with the source files.
add_library(my_library STATIC util.cpp)
  • find_package: Locates a package and sets variables to use it. Useful for third-party dependencies.
find_package(Threads REQUIRED)
  • set: Used to set a variable’s value, commonly used for compiler flags or options.
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")

4. Variables and Options

  • set: As mentioned earlier, the set command sets a variable's value. It's widely used for configuring build settings.
set(MY_VARIABLE "Some Value")
  • option: Allows you to provide a boolean option that the user can toggle on or off when running CMake.
option(ENABLE_DEBUG "Enable debug build" OFF)

Users can toggle this when configuring the project: cmake -DENABLE_DEBUG=ON.

  • Cache Variables: CMake has a concept of a cache where variables are stored. You can pre-populate the cache or read from it.
set(MY_CACHED_VAR "Default Value" CACHE STRING "An example cached variable")

5. Lists and Loops

  • list: The list command is used for list manipulation. You can append elements, find elements, sort the list, etc.
list(APPEND MY_LIST "Element1" "Element2")
  • foreach: Loops over a list of items, executing the loop body for each.
foreach(item IN LISTS MY_LIST)
message("Processing item: ${item}")
endforeach()
  • while: The while command lets you execute a set of commands as long as a condition is true.
set(counter 0)
while(counter LESS 10)
math(EXPR counter "${counter} + 1")
message("Counter value: ${counter}")
endwhile()

6. Toolchain Files

  • Defining Compilers and Flags: A toolchain file is a CMake script where you can set compilers and flags to be used for cross-compilation.
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_C_COMPILER arm-none-eabi-gcc)
set(CMAKE_CXX_COMPILER arm-none-eabi-g++)
  • Using a Toolchain File: You specify a toolchain file when you initially configure your CMake build.
cmake -DCMAKE_TOOLCHAIN_FILE=path/to/toolchainfile.cmake ..
  • Example for Embedded Systems: Here's a minimal example tailored for an ARM Cortex-M3 processor.
# toolchain-arm-cortex-m3.cmake
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_C_COMPILER arm-none-eabi-gcc)
set(CMAKE_CXX_COMPILER arm-none-eabi-g++)
add_definitions(-mcpu=cortex-m3)

7. Find Modules and Packages

  • find_package: This command attempts to find a package configuration file that CMake provides or that you have installed manually. It sets several variables to indicate whether the package was found and provides relevant details.
find_package(OpenCV REQUIRED)
  • find_library: Finds a library and stores the path as a variable. This is useful for linking against system or custom libraries.
find_library(MY_LIB mylib)
  • Custom Find Modules: For packages that don’t offer CMake support, you can write a custom Find<PackageName>.cmake module to locate the libraries and headers, and then use find_package as usual.

8. Exporting and Installing

  • install: The install command specifies how the built files should be packaged and where they should be placed on the system.
install(TARGETS my_program DESTINATION bin)
  • CPack: This is CMake's packaging tool, allowing you to create tarballs, DEBs, RPMs, NSIS installers, and more. You configure it in your CMakeLists.txt:
set(CPACK_PACKAGE_NAME "MyProgram")
set(CPACK_PACKAGE_VERSION "1.0")
include(CPack)
  • Exporting Targets: You can also export targets (like libraries) so that other CMake projects can easily import and use them.
install(EXPORT MyLibraryTargets
        FILE MyLibraryTargets.cmake
        DESTINATION lib/cmake/MyLibrary)

9. Best Practices

  • Out-of-Source Builds: Always create a separate directory for your build files (mkdir build && cd build && cmake ..). This keeps your source directory clean.

  • Use target_* Commands: Prefer using target_include_directories, target_compile_definitions, etc., over their global counterparts (include_directories, add_definitions, etc.) for setting properties. This makes your CMakeLists more modular and easier to manage.

  • Version Check: Always specify the minimum required CMake version for better compatibility.

cmake_minimum_required(VERSION 3.10)
  • Don't Hardcode Paths: Instead of hardcoding paths, use variables and generator expressions to keep things flexible.

  • Documentation and Comments: Just like with code, documenting your CMakeLists.txt files helps maintainability.

10. Real-World Example

# 3. Basic Commands
cmake_minimum_required(VERSION 3.10)  # 9. Best Practices: Version Check

# 4. Variables and Options
set(PROJECT_NAME "EmbeddedProject")
project(${PROJECT_NAME})

# 7. Toolchain Files: Assume this is set via -DCMAKE_TOOLCHAIN_FILE=path/to/toolchainfile.cmake
# Example content for toolchainfile.cmake:
#   set(CMAKE_SYSTEM_NAME Generic)
#   set(CMAKE_C_COMPILER arm-none-eabi-gcc)
#   set(CMAKE_CXX_COMPILER arm-none-eabi-g++)

# 5. Lists and Loops
list(APPEND SOURCES
    "main.c"
    "util.c"
)

# 3. Basic Commands
add_executable(${PROJECT_NAME} ${SOURCES})

# 7. Find Modules and Packages
find_package(SomeLibrary REQUIRED)

# 3. Basic Commands
target_link_libraries(${PROJECT_NAME} PRIVATE SomeLibrary::SomeLibrary)

# 4. Variables and Options
option(ENABLE_DEBUG "Enable Debugging" OFF)
if(ENABLE_DEBUG)
    target_compile_options(${PROJECT_NAME} PRIVATE -g)
endif()

# 8. Exporting and Installing
install(TARGETS ${PROJECT_NAME} DESTINATION bin)
set(CPACK_PACKAGE_NAME ${PROJECT_NAME})
set(CPACK_PACKAGE_VERSION "1.0")
include(CPack)

# 9. Best Practices
# Documentation and Comments
# No hard-coded paths used

11. Q&A

1. Question:
What is the primary purpose of a Makefile in a C/C++ project?

Answer:
The primary purpose of a Makefile is to direct the make tool on how to compile and link a program. It specifies the relationships among files in your program and provides commands to update them.


2. Question:
How would you specify a compiler to use in a Makefile?

Answer:
You can specify a compiler by setting the CC variable. For instance, to use gcc, you'd write:

CC=gcc

3. Question:
In a Makefile, what is a target, and how is it defined?

Answer:
A target is typically the name of the file that is generated by a program; it could be an executable or an object file. It's defined in the Makefile followed by a colon and then the files it depends on. Underneath the target, you define the commands to run, typically indented.

target: dependencies
    commands

4. Question:
What is CMake and how does it differ from a traditional Makefile?

Answer:
CMake is a cross-platform build-system generator. Users can write simple configuration files in a non-specific language, and CMake will produce Makefiles (or other build scripts) tailored to the system. While a Makefile directly instructs the make tool, CMake abstracts away the generation of these instructions.


5. Question:
How would you instruct CMake to use a specific compiler?

Answer:
You can specify a compiler for CMake by setting the CC and CXX environment variables before invoking CMake. For example:

export CC=gcc
export CXX=g++
cmake path_to_source

6. Question:
In CMake, what does the add_executable function do?

Answer:
The add_executable function defines an executable target built from source files. For example:

add_executable(my_program main.c utils.c)

This tells CMake to generate build rules to create an executable named "my_program" from the source files main.c and utils.c.


7. Question:
What's the purpose of the find_package command in CMake?

Answer:
find_package is used to locate external libraries and tools in a system. If the package is found, it sets certain variables, usually including include directories and library names, which can then be used in subsequent CMake commands.


8. Question:
In a Makefile, how would you define a variable to hold compiler flags?

Answer:
You can define a variable in a Makefile with an assignment. For example, to hold compiler flags, you might use:

CFLAGS=-Wall -O2

9. Question:
How do you specify a required minimum version of CMake in a CMakeLists.txt file?

Answer:
You use the cmake_minimum_required command. For example, to specify a minimum CMake version of 3.10, you'd write:

cmake_minimum_required(VERSION 3.10)

10. Question:
How can you conditionally include files or set flags in CMake based on the platform being targeted?

Answer:
CMake provides various variables that define the platform, like WIN32, UNIX, APPLE, etc. You can use these in conditional statements. For example:

if(WIN32)
    # Do something specific for Windows
elseif(APPLE)
    # Do something specific for macOS
else()
    # Other UNIX-like systems
endif()

11. Question:
In a Makefile, what does the special variable $@ represent?

Answer:
In a Makefile, $@ represents the name of the target of the rule.


12. Question:
How do you add include directories in CMake?

Answer:
You can use the include_directories() function. For instance:

include_directories(/path/to/includes)

13. Question:
What is the purpose of the add_subdirectory command in CMake?

Answer:
The add_subdirectory command adds a sub-directory to the build. CMake will descend into the given directory and process the CMakeLists.txt file in that directory.


14. Question:
In a Makefile, how do you define a rule that is not associated with a file?

Answer:
You can define a "phony" target using the .PHONY directive. This tells make that the target isn't a real file. For example:

.PHONY: clean

clean:
    rm -rf *.o my_program

15. Question:
How would you specify a required library for your project in CMake?

Answer:
You can use the target_link_libraries() function. After using find_package() to locate the library, you'd typically do something like:

find_package(MyLibrary REQUIRED)
add_executable(my_program main.c)
target_link_libraries(my_program MyLibrary::MyLibrary)

16. Question:
How can you specify different build types, like Debug or Release, in CMake?

Answer:
You can set the CMAKE_BUILD_TYPE variable. For example:

cmake -DCMAKE_BUILD_TYPE=Release path_to_source

17. Question:
In a Makefile, what does the special variable $^ represent?

Answer:
In a Makefile, $^ represents the names of all the prerequisites, separated by spaces, with duplicates removed.


18. Question:
How do you generate an install target using CMake?

Answer:
You can use the install() command. For instance, to install an executable target:

add_executable(my_program main.c)
install(TARGETS my_program DESTINATION bin)

19. Question:
In a Makefile, how would you handle conditional compilation based on the value of a variable?

Answer:
You can use conditional directives like ifeq, ifneq, etc. For example:

DEBUG = 1

ifeq ($(DEBUG),1)
CFLAGS=-g
else
CFLAGS=-O2
endif

20. Question:
How can you group source files into a static library using CMake?

Answer:
You can use the add_library() function with the STATIC keyword. For instance:

add_library(my_lib STATIC source1.c source2.c)

21. Question:
In CMake, how would you propagate compile options only to consumers of a library, without affecting the library itself?

Answer:
You can use target_compile_options() with the INTERFACE keyword.

target_compile_options(my_lib INTERFACE -DSOME_DEFINITION)

22. Question:
In a Makefile, what's the purpose of the - prefix before a command, like -rm -f *.o?

Answer:
The - prefix tells make to continue and not exit even if the command results in an error.


23. Question:
What's the difference between CMAKE_SOURCE_DIR and CMAKE_CURRENT_SOURCE_DIR in CMake?

Answer:
CMAKE_SOURCE_DIR refers to the top level source directory, while CMAKE_CURRENT_SOURCE_DIR refers to the source directory currently being processed.


24. Question:
In a Makefile, how would you define a variable whose value is the output of a shell command?

Answer:
You can use shell function. For example:

FILES := $(shell ls *.c)

25. Question:
How do you ensure in CMake that a custom command is always executed, even if the output file already exists?

Answer:
Use the ALWAYS argument in the add_custom_command().

add_custom_command(OUTPUT output.txt COMMAND some_command ALWAYS)

26. Question:
What does the add_dependencies() command do in CMake?

Answer:
It adds custom target-level dependencies. If you specify that target A depends on target B, B will be built before A.


27. Question:
In CMake, if you wanted to conditionally compile a section of your CMakeLists.txt based on the presence of a specific library, which command would you use?

Answer:
You would use the find_package() command in conjunction with an if statement.

find_package(SomeLibrary)
if(SomeLibrary_FOUND)
  # Compile the conditional section
endif()

28. Question:
In a Makefile, how would you set a variable conditionally only if it hasn't been set already?

Answer:
You can use the ?= assignment. For example:

CFLAGS ?= -O2

29. Question:
How do you set a CMake variable that's cached and available for subsequent runs of CMake?

Answer:
You can use set() with the CACHE option.

set(MY_VARIABLE "SomeValue" CACHE STRING "My cached variable")

30. Question:
In CMake, what's the difference between add_custom_command() and add_custom_target()?

Answer:
add_custom_command() defines a new command and the circumstances under which it will be executed. add_custom_target() creates a new target, and the command associated with it will always be executed when the target is built.


31. Question:
Given the following Makefile line:

all: my_app.c
    gcc -o my_app

What's the issue here?

Answer:
The gcc command doesn't specify the source file to compile. It should be gcc -o my_app my_app.c.


32. Question:
Consider this CMake snippet:

add_executable(my_program main.c)
target_link_libraries(my_program PRIVATE SomeLibrary)

If SomeLibrary wasn't defined anywhere else in the project, what would be the problem?

Answer:
CMake would throw an error because it doesn't know what SomeLibrary is. Before using SomeLibrary, you typically need to use find_package() or add_library() to define or locate it.


33. Question:
In the Makefile below, what's wrong?

CFLAGS=-I./include
my_app: main.o utils.o

Answer:
There's no rule provided to link main.o and utils.o to produce the my_app target.


34. Question:
Given this CMakeLists.txt line:

set(CMAKE_CXX_FLAGS "-Wall")

What could be an unintended consequence?

Answer:
This line will override any other flags that are set elsewhere for the C++ compiler, which might not be the intended behavior. It's often safer to append flags using something like list(APPEND CMAKE_CXX_FLAGS "-Wall").


35. Question:
Consider the Makefile snippet:

all:
    @echo "Building..."
    -rm -f *.o
    gcc main.c -o mainApp

What's the potential problem here?

Answer:
The -rm -f *.o will delete all .o files, even if they weren't generated by this Makefile. If there are other object files in the directory for different purposes, they will be unintentionally removed.


36. Question:
Review this CMake snippet:

add_library(MyLib STATIC my_source.c)
add_executable(MyApp app_source.c)
target_link_libraries(MyApp MyLib)

If app_source.c doesn't actually use anything from my_source.c, what's the problem?

Answer:
While not a syntax error, it's unnecessary and potentially misleading to link MyApp with MyLib if MyApp doesn't use anything from MyLib.


37. Question:
In this Makefile rule, what could go wrong?

%.o: %.c
    gcc -c $< -o $@ 
clean:
    rm *.o app

Answer:
The clean rule does not have any dependencies, but if no .o files or app exist in the directory, rm will throw an error. To suppress errors for a command in a Makefile, you can prefix it with -, like -rm *.o app.


38. Question:
Given this CMakeLists.txt line:

set(MY_FILES main.c util.c)

Later in the CMakeLists, there's a reference to ${MY_FILEZ}. What's the problem?

Answer:
There's a typo. The variable is defined as MY_FILES, but it's being referenced as ${MY_FILEZ}. This would result in an empty value for ${MY_FILEZ}.


39. Question:
Consider this Makefile snippet:

CC_FLAGS=-O2

all: mainApp

mainApp:
    gcc $(CC_FLAGS) main.c -o mainApp

What issue might arise?

Answer:
If main.c hasn't changed, calling make again will still recompile mainApp, even though there's no need. To fix this, a dependency on main.c should be added to the mainApp target.


40. Question:
In this CMake snippet, what could be the issue?

add_library(LibA STATIC a.c)
add_library(LibB STATIC b.c)
target_link_libraries(LibA PUBLIC LibB)

Answer:
LibA and LibB are both static libraries. In CMake, directly linking static libraries to other static libraries doesn't combine them. This might cause confusion or linking errors if someone expects LibA to also contain the contents of LibB when used.


41. Question:
Given the following Makefile:

all: prog

prog: main.c
    gcc main.c -o prog -DDEBUG

If you don't want to compile with the DEBUG flag, how can this Makefile be problematic?

Answer:
Each time you run make, even if main.c hasn't changed, the program will recompile due to the hard-coded -DDEBUG flag. To avoid unnecessary recompilations, flags like this should be parameterized.


42. Question:
In this CMakeLists.txt:

option(ENABLE_DEBUG "Enable debug mode" ON)
if(ENABLE_DEBUG)
    add_definitions(-DDEBUG)
endif()

What might be misleading?

Answer:
The ENABLE_DEBUG option defaults to ON, which might unintentionally enable debug mode for users unaware of this default setting.


43. Question:
In the Makefile below, what's the potential issue?

CFILES = $(wildcard *.c)
OFILES = $(CFILES:.c=.o)

prog: $(OFILES)
    gcc -o prog $(OFILES)

Answer:
While it captures all .c files in the current directory, any new .c files added to the directory will automatically be included. This might unintentionally compile undesired or test files.


44. Question:
Consider this CMakeLists.txt:

set(SOURCES main.c)
add_executable(prog ${SOURCES})
install(TARGETS prog DESTINATION /usr/local/bin)

What could be a potential pitfall?

Answer:
Hardcoding the destination path as /usr/local/bin can be problematic on systems where this isn't the standard location, or where the user doesn't have write permissions. It's often better to let the user override default installation paths.


45. Question:
Given this Makefile snippet:

all: debug release

debug:
    gcc main.c -o main -DDEBUG

release:
    gcc main.c -o main

What's a potential problem when switching between building debug and release?

Answer:
There's no clean step, so object files from a previous build can persist and interfere with the next build. This can lead to a "debug" build containing release object files or vice versa.


46. Question:
Given the CMake line:

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall")

What happens if CMAKE_C_FLAGS already contains -Wall?

Answer:
The -Wall flag will be duplicated. While it doesn't hurt the compilation, it makes the flag string messy and harder to debug or read.


47. Question:
In this Makefile:

prog: helper.o
    gcc main.c helper.o -o prog

helper.o: helper.c helper.h
    gcc -c helper.c

What's missing?

Answer:
main.c is not listed as a dependency for the prog target. As a result, if changes are made to main.c, the program won't recompile.


48. Question:
For this CMakeLists.txt snippet:

add_executable(prog main.c)
set_property(TARGET prog PROPERTY C_STANDARD 99)

What might go wrong?

Answer:
The set_property command sets the C standard to C99. However, if the code in main.c (or any other linked source) uses features from a newer C standard, there will be compilation errors.


49. Question:
In the Makefile:

all: 
    gcc -o prog src/*.c

What could be an issue?

Answer:
By using the wildcard (*), the Makefile will compile all .c files in the src directory. If any unrelated or test .c files are added, they will also be compiled, possibly causing errors or unexpected behaviors.


50. Question:
Given this CMakeLists.txt:

file(GLOB SOURCES "src/*.c")
add_executable(prog ${SOURCES})

Why might this approach be discouraged?

Answer:
Using file(GLOB ...) to specify sources can be problematic. If a new .c file is added to the directory, CMake won't automatically know to re-run. You would have to manually re-run CMake to recognize the new source file. This can cause confusion and lead to build issues.