Skip to content

Commit

Permalink
[Add Content] Build Systems (SRA-VJTI#80)
Browse files Browse the repository at this point in the history
* Removed all python files, and added basic cpp files and refined file structure

* Removed all python files, and added basic cpp files and refined file structure

* Refined file structure according to SRA-VJTI#48

* Fixed OS detection for debain and arch

* Added all file structure files

* Suggested changes in PR SRA-VJTI#64 made

- Added `1_cpp_basics` folder.
- Added readme for makefile.
- Renamed all directories as per directory structure.

* Adding documentation for build systems and compiler

* Added Makefile full explanation

* Renamed files for uniform structure

* Fixed issue with makefile

* Making the suggested changes in 2_build_systems.md

* Resolving typo error

* Using appropriate flags for generating an object file

* Correcting the placement of semicolon

---------

Co-authored-by: PritK99 <pritpjk@gmail.com>
Co-authored-by: Prit Kanadiya <103832825+PritK99@users.noreply.github.com>
  • Loading branch information
3 people authored and Alqama Shaikh committed Mar 23, 2023
1 parent 3d55034 commit 6ee3062
Show file tree
Hide file tree
Showing 3 changed files with 272 additions and 7 deletions.
265 changes: 265 additions & 0 deletions 2_build_systems/2_build_systems.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
## Compilers

### What is a compiler?

A compiler is a special program that translates a programming language's source code into machine code, bytecode or another programming language. The source code is typically written in a high-level, human-readable language such as C/C++.

### Compiler Directives

Compiler directives refer to statement written in the source code of a program that lets the programmer instruct the compiler to perform a specific operation within the compilation phase itself.

For example, the ```#include``` directive incorporates the contents of a header file to the compilation. Similarly, The ```#define``` directive is used to define a macro.

### Different stages of compilation

<p align="center">
<img src="../assets/images/Compilation_Stages.png" width="800" />
</p>

The compilation process transforms a human-readable code into a machine-readable format. For a programming language, it happens before a program starts executing to check the syntax and semantics of the code. The compilation process involves four steps: pre-processing, compiling, assembling, and linking then, we run the obtained executable file to get an output on the screen.

1) Pre-Processing : Common tasks accomplished by preprocessing are macro substitution, testing for conditional compilation directives, and file inclusion.

2) Compiling : The code which is expanded by the preprocessor is passed to the compiler. The compiler converts this code into assembly code. Or we can say that the C compiler converts the pre-processed code into assembly code.

3) Assembling : The assembly code is converted into object code by using an assembler. The name of the object file generated by the assembler is the same as the source file.

4) Linking : The main working of the linker is to combine the object code of library files with the object code of our program.

### Different types of files during compilation

During the stages of compilation, there are several types of files involved.

* Source(.c):
These files contain function definitions, and the entire program logics. These files are human readable and by convention their names ends with ```.c```, ```.cpp```, ```.py```, etc.

* Assembly(.s):
Assembly Language mainly consists of mnemonic processor instructions or data and other statements or instructions.

Flag : -S

eg. ```gcc -S foo.c```

This command will generate ```foo.s``` as output.

* Object(.o):
These files are produced as the output of the compiler. They consist of function definitions in binary form, but they are not executable by themselves and by convention their names end with ```.o```.

Flag : -c

eg. ```gcc -c foo.c```

This command will generate ```foo.o``` as output.

## Build Systems

### What is a build system ?

All build systems have a straightforward purpose: they transform the source code written by humans into executable binaries that can be read by machines.

Build systems aren't just for human-authored code; they also allow machines to create builds automatically, whether for testing or for releases to production. In an organization with thousands of engineers, it's common that most builds are triggered automatically rather than directly by engineers.

### Why do we need build systems ?

The need for a build system might not be immediately obvious. As long as all the source code is in the same directory, a command like this works fine:

```c
gcc main.c
```
This instructs the compiler to take convert main.c into a binary class file. In the simplest case, this is we you need.

However, as soon as code expands, the complications begin. Our compiler has no way of finding code stored in other parts of the filesystem (perhaps a library shared by several projects).

Large systems often involve different pieces written in a variety of programming languages with several dependencies among those pieces, meaning no compiler for a single language can possibly build the entire system.

The compiler also doesn’t know anything about how to handle external dependencies. Without a build system, it's difficult to maintain the updates, versions, and source of these external dependencies.

This is why we need build systems.

## GNU Make Build System

Makefiles are useful for determining which parts of a large program need recompilation. They are commonly used to compile C and C++ files but other languages have similar tools. Make can also be used to run a series of instructions when specific files change.

### Syntax of Makefile

Makefile consists of a set of rules.

```makefile
target: prerequisites
command_1
command_2
command_3
```

- **Targets** are file names, which are separated by spaces.
- **Commands** are a series of specific steps that need to be executed in order to create the target files. These commands must start with a tab character.
- Each target may have one or more **prerequisites** that need to exist before the target can be built.
- Prerequisites are also files, and they must be created before the target file can be generated.

### Example 1

Let's create a very simple Makefile. First, create an `example.c` file:

```c
int main() {return 0;}
```

Then, let's create our first `Makefile`. Create the file (`touch Makefile`) and add a target `hello`.

```makefile
hello: example.c
gcc example.c -o hello
```

Now, when we run `make` in the terminal, we see that a new file is created, called `hello`. This file is the compiled executable of `example.c`, which we can execute. If we run `make` again, nothing happens, and it says `make: 'example.c' is up to date`.

Let's make a small change in `example.c`, and observe the response of Make.

```c
#include <stdio.h>
int main() { printf("Hello, world!"); return 0; }
```

Now, when we run `make`, we can observe that the file is recompiled. This is because `example.c` is a dependency of `hello`, therefore when the dependency is changed the target is built again.

How does make do this? It uses the file-system timestamps to determine if something has changed. If the dependencies of a target are changed, the target is regenerated.

### Variables in Makefile

Makefile supports variables, but only strings.

```makefile
file_name = example.c
executable_name = hello

$(executable_name): $(file_name)
gcc $(file_name) -o $(executable_name)
```

Here, the target name can be modified as per our requirement.

We must remember that even though everything is a string, single and double quotes have no meaning to Make.

```makefile
a := one two # a is assigned to the string "one two"
b := 'one two' # Not recommended. b is assigned to the string "'one two'"
```
We can reference variables using either `$()` or `${}`.
### Example 2
Let’s try something more. Create two files `greeting.c` and `hello.c`.
```c
//hello.c
#include <stdio.h>
int main() { printf("Hello, world!"); return 0; }
```

```c
//greeting.c
#include <stdio.h>
int main() { printf("Welcome!"); return 0; }
```
Let’s create a Makefile that shall compile both of these files.
```makefile
hello: hello.c
gcc hello.c -o hello
greeting: greeting.c
gcc greeting.c -o greeting
```

As seen before, running either `make hello` or `make greeting` will compile the `hello.c` or the `greeting.c`.

> What do you think happens when simply `make` is run?
>
But, what if the requirement arises to run both of the targets at once? We can create an `all` target, that will run both the rules.

```makefile
all: hello greeting

hello: hello.c
gcc hello.c -o hello
greeting: greeting.c
gcc greeting.c -o greeting
```

## Automatic Variables and Wildcards in Makefile

### Automatic Variables

Here are some automatic variables which can be used in Makefile:

- `$@` returns the filename representing the target.
- `$<` returns the filename of the first prerequisite.
- `$^` returns the names of all the prerequisites, separated by spaces
- `$?` returns the names of only the prerequisites that are newer than the target, separated by spaces.

### The `*` wildcard

The `*` wildcard searches your file-system for matching filenames. In the previous example, we can list out all the C files with it’s help

```makefile
print: $(wildcard *.c)
ls -la $^
```

### The `%` wildcard

The `%` wildcard is used to match any string. It is useful when our files follow a certain pattern. The `%` character matches any string, and that string is used as a variable in the rule definition.

For example, let's say we have several source files with the extension `.c`, and we want to compile them into object files with the extension `.o`. We could use the `%` wildcard to match any file name and then substitute the `.c` extension with `.o` to create the object file name.

```makefile
%.o: %.c
gcc -c $< -o $@
```

This rule tells make that any file ending in `.o` can be created by compiling the corresponding `.c` file. The `$<` automatic variable is used to get the name of the first prerequisite, which in this case is the `.c` file. The `$@` variable is used to get the name of the target, which is the `.o` file.

### Example 3

Let's say we have three source files called `file1.c`, `file2.c`, and `file3.c`. We want to create three corresponding object files called `file1.o`, `file2.o`, and `file3.o`. We can use the `%` wildcard to match the file names and create a rule that compiles any `.c` file into a corresponding `.o` file.

```makefile
%.o: %.c
gcc -c $< -o $@

all: file1.o file2.o file3.o
```

This rule tells make that any file ending in `.o` can be created by compiling the corresponding `.c` file. The `$<` automatic variable is used to get the name of the first prerequisite, which in this case is the `.c` file. The `$@` variable is used to get the name of the target, which is the `.o` file.

When we run `make all`, it will compile all three source files into object files using the same rule.

## Commands and execution

Makefile executes all commands under the target. The commands under the target are executed in the order they appear in the Makefile.

The printing of each command at execution can be stopped by the use of `@` .

```makefile
all:
@echo "This make line will not be printed"
echo "But this will"
```

Each command in a target is run in a new shell. To run commands in the same shell, one can either use semicolons or forward slashes.

```makefile
all:
cd ..
echo `pwd`
#the above command are run in seperate shells.
cd ..; echo `pwd`;
#these commands are run in the same shell.
cd ..; \
echo `pwd`
#these commands are run in the same shell as well.
```

---
14 changes: 7 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
# Variables
CC = g++
CFLAGS = #-Wall -std=c++11 -shared
LDFLAGS = `pkg-config opencv4 --cflags --libs`
# LDFLAGS = ` -lm -lstdc++ -lsqlite3 pkg-config opencv4 --cflags --libs`
LDFLAGS = `pkg-config opencv4 --cflags --libs` -lsqlite3
BIN_DIR = bin
#command to get the OS
UNAME_S := $(shell uname -r)
Expand Down Expand Up @@ -47,7 +48,7 @@ build:
@cp $(FOLDER)/*.cpp $(FOLDER)/build && cp $(FOLDER)/*.hpp $(FOLDER)/build
@find $(FOLDER)/build -name "*.cpp" -type f -exec sed -i 's@PROJECT_SOURCE_DIR@$(shell pwd)@g' {} \;
@find $(FOLDER)/build -name "*.hpp" -type f -exec sed -i 's@PROJECT_SOURCE_DIR@$(shell pwd)@g' {} \;
@$(CC) $(CFLAGS) $(FOLDER)/build/$(FOLDER).cpp -o $(BIN_DIR)/$(FOLDER) -I $(FOLDER)/build $(LDFLAGS)
@$(CC) $(CFLAGS) $(FOLDER)/build/$(notdir $(shell basename $(FOLDER))).cpp -o $(BIN_DIR)/$(notdir $(shell basename $(FOLDER))) -I $(FOLDER)/build $(LDFLAGS)
endif


Expand All @@ -56,13 +57,13 @@ endif
ifeq ($(FOLDER),)
clean:
@echo "Cleaning..."
@rm -rf */build
@rm -rf **/build && rm -rf */*/build
@rm -rf $(BIN_DIR)
else
clean:
@echo "Cleaning..."
@rm -rf $(FOLDER)/build
@rm -rf $(BIN_DIR)/$(FOLDER)
@rm -rf $(BIN_DIR)/$(notdir $(shell basename $(FOLDER)))
endif

# the command make run FOLDER=<subfolder> should run the executable in the subfolder
Expand All @@ -72,6 +73,5 @@ run:
else
run:
@echo "Running..."
@./$(BIN_DIR)/$(FOLDER)
endif

@./$(BIN_DIR)/$(notdir $(shell basename $(FOLDER)))
endif
Binary file added assets/images/Compilation_Stages.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 6ee3062

Please sign in to comment.