A preliminary study of makefile

1. What are make commands and makefiles?

The make directive, as its name suggests, is used to make a file (make filename), or to automate the compilation, packaging, and generation of a file (executable or compressed) based on the Makefile target.

For example, if we want to synthesize output.txt from a.t_t and B.t_t files, we can write the following makefile:

output.txt: a.txt b.txt
	@# Synthesize output. TXT file from a.txt b.txt file
	@cat a.txt b.txt > output.txt
Copy the code
#Make the output. TXT file
make output.txt
Copy the code

Usually we use make to build C/C ++ projects, but we can also use make to build Go projects, Java projects, and Node.js projects.

For example, we compile the hello.cpp file with the make directive

#include <iostream>
using namespace std;
int main(a){
   cout << "Hello World!";
   return 0;
}
Copy the code

The makefile file:

hellocpp:	hello.o
	echo "Start compiling"
	g++ -o hello hello.o
	rm -f hello.o
	echo "End of compilation"
Copy the code

Execute make

#Execute hello.cpp using the makefile
make

#Execute the generated Hello file
./hello
> Hello World!
Copy the code

For those of you who haven’t played makefiles, you might think that a makefile is a lot like a shell script. Yes, a makefile can be used as a shell script (bushi). Here are some basic rules and common ways to write a makefile.

2. Makefile structure

The makefile is made up of a series of rules, each of which is written as follows:

<target> : <prerequisites> 
[tab]  <commands>
Copy the code

The part before the colon indicates the target, which indicates the action to be performed. The target can be a single file name (as in output.txt above) or multiple file names separated by Spaces. A target can be either a file name or a operation name, a makefile called a phony target, and a.phony target is used to declare the target operation.

If we want to do make clean, but there’s a clean file in the directory, then we’re going to do it without doing it, so we’re going to use a.phony pseudo-target declaration

.PHONY clean
clean: 
	rm -f *.o
Copy the code

We usually have some common goals in makefiles, such as:

  • Make all: compile all files
  • Make install: Installs the compiled application
  • Make clean: Clean applications, executable files, object files, etc.

The area after the colon indicates the prerequisites, which are separated by Spaces. The declared target specifies a precondition, and if there is no file that matches the precondition, the target can be executed only if the file is the one required by the precondition.

Commands indicate how to build the object file. Each command must start with a TAB and can be placed on the same line as your prerequisites, separated by a semicolon.

Preconditions and commands are not required, but if one is not written, the other must be.

A Makefile consists of five main things:

  • Definition of variables
  • Explicit rules: In the same way that target-enable-commands are written above, the rules are displayed
  • Cryptic rules: Because makefiles are automatically pushed, cryptic rules allow us to write makefiles cruder. Rules written using variables and functions built into makefiles are implicit rules.
  • File instructions: A makefile can be introduced nested with the include directive
  • annotation

Variables are usually defined as strings, similar to C macros, so we sometimes call variables in makefiles macros.

As with vue differential templates and shell and PHP variables, we usually use ${VARIABLE} or $(VARIABLE) to use a VARIABLE.

Variables can be declared in four ways in a Makefile:

VARIABLE = value # Lazy assignment, which extends at execution, can be recursively extended

VARIABLE := value # Assign immediatelyVARIABLE ? = value# Sets the value only if the variable is null

VARIABLE += value Append the value to the end of the variable
Copy the code

You can check Out Stack Overflow to see the differences between these four assignment methods, and since these differences are not the focus of this article, I won’t go into much detail.

In addition to user-declared variables, there are built-in and automatic variables in makefiles.

Built-in variables fall into two categories: variables that serve as program names (such as CC) and variables that contain program arguments (such as CFLAGS). See the official documentation for all of the makefile’s built-in variables.

The value of the automatic variable is related to the current rule.

Commonly used are the following:

  • $@: Current goal
  • $?: preconditions that are newer than the target
  • $<: The first precondition
  • $*: Matches the part of the wildcard
  • $^: All preconditions
  • $(@D)and$(@F):$@Directory name and file name
  • $(<D)and$(<F):$<Directory name and file name

At sign refers to the current target file, as in the following example, at sign refers to the current target file, as in the following example, at sign refers to the name of the a.tb and B.tb target files;

# The following two ways are equivalent
# write a
a.txt b.txt: 
    touch $@
# write two
a.txt:
    touch a.txt
b.txt:
    touch b.txt
Copy the code

< is the first precondition, as in this example, < is the first precondition, as in this example, < is the first precondition

# The following two ways are equivalent
# write a
a.txt: b.txt c.txt
    cp $< $@ 
# write two
a.txt: b.txt c.txt
    cp b.txt a.txt 
Copy the code

In addition to built-in and automatic variables, makefiles can also use built-in functions in the same way as variables. The official document lists a total of 14 functions, and the main commonly used functions are as follows:

  • Shell functions can execute shell commands. They are similar to pipes in the shell. For example,dir:=$(shell pwd)
  • Subst, used for text substitution, is as follows:$(subst from,to,text)
  • Patsubst, the patsubst function is used for pattern matching replacement, mainly used to replace wildcards. Usage for$(patsubst pattern,replacement,text). For example,$(patsubst %.c,%.o,a.c.c b.c)A.C.C and B.C can be replaced by A.C.O and B.O.
  • Wildcard, the wildcard function can use Spaces to separate a list of files that match this format. For example,$(wildcard *.c)Get a list of all *. C * files in the working directory.

There are a few other syntax points to note for makefiles:

Echo: @

Normally, make prints every command during execution, including comments, a phenomenon known in makefiles as echoes. If you do not want to print the echo, you can use the @ operator to turn off the echo. Such as:

@# Close comments
test:
	@echo "Compiling..."
	@npm run dev
Copy the code

Wildcard

Make’s wildcards are the same as shell’s. .

Pattern matching

Make’s main pattern matching operator is %, which allows regular-like matching of filenames

annotation

The makefile comment is the same as the shell script L: #

Loop and judge instructions

Like shell scripts, makefiles have the following types of looping commands:

  • Ifeq (if EQAUL) command. It contains two arguments, separated by commas and surrounded by parentheses. The variable substitution is performed on both parameters and then compared. If the two arguments match, the command line following ifeq is followed. Otherwise it will be ignored.
  • Ifneq (if not EQAUL). It contains two arguments, separated by commas and surrounded by parentheses. The variable substitution is performed on both parameters and then compared. If the two arguments do not match, the makefile line following ifNEq is followed; Otherwise it will be ignored.
  • The ifdef (if defined) directive. It contains a single parameter. The condition is true if the given argument is true.
  • The ifnDEF (if not defined) directive. It contains a single parameter. The condition is true if the given parameter is false.
  • The else instructions.
  • The statement that ends with the endif directive. Each if condition must end with endif.
  • The for – in – do – done, cycle

The include directive

The include directive can introduce other Makefiles. Syntax is as follows

include <filename>
# Filenames can contain file name matches in shell format. Extra Spaces are allowed and ignored at the beginning of a line, but the TAB (\t) character is not allowed

-include <filename>
Do not report any errors in the # include process. The above directive will report an error if it cannot find the include target file
Copy the code

Override the instructions

If you want to reassign a variable, you use the Override directive. Such as

override VARIABLE = value
Copy the code

Use make to build JavaScript code

Now that we’ve briefly covered the use of make and some of the rules for makefiles, let’s look at how to compress JavaScript code with make.

Without further ado, go straight to the code:

src_files := $(shell find src -name '*.js')
dist_files := $(patsubst src/%.js, dist/%.min.js, $(src_files))

node_modules: package.json package-lock.json
	@npm i uglifyjs 

$(dist_files): $(src_files)
	@rm -rf dist
	@mkdir dist
	@npx uglifyjs $^ -cmo $@

all: node_modules $(dist_files) 

.PHONY: all
Copy the code

For testing purposes, we use zero width space to test whether the file was compressed successfully.

Create a SRC directory under the root directory and create an app.js file with the following code:

​​​​​​​​​​​​​​​​​​a​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​
Copy the code

Although only one character a is displayed in the editor, the string is 221 in length and the file size is 601 bytes. This is caused by the zero-width character.

So what is a zero-width space?

Characters expressed in multiple bytes are called wide characters, and our common Unicode encoding is an implementation of wide characters, but wide characters are not necessarily Unicode.

A zero-width character is, as the name implies, a character of width 0. The zero-width character is not visible in the browser environment, but it is real and occupies the length when obtaining the character length. Often used to prevent crawlers, data steganography, can also be used for DOS attacks.

The following special characters are common in browsers:

Zero-width Spaces (ZWSP) are used where a line feed may be needed.Unicode: U+200B  HTML: &#8203; A zero-width non-joiner (ZWNJ) is placed between two characters in the electronic text to suppress the hyphen that would have occurred. Instead, the text is drawn with the original character of the two characters.Unicode: U+200C  HTML: &#8204; A Zero-width Joiner (ZWJ) is a control character. It is placed between two characters in some languages (such as Arabic and Hindi) that require complex typeset requirements. The ZWJ and ZWJ characters that are not hyphenated are hyphenated.Unicode: U+200D  HTML: &#8205; A Left-to-right mark (LRM) is a control character used in computer typesetting in both directions.Unicode: U+200E HTML: &lrm; &#x200E; Or & #8206; A right-to-left mark (RLM) is a control character used in computer typesetting of bidirectional documents.Unicode: U+200F HTML: &rlm; &#x200F; Or & #8207; Bytes-order marks (BOM) are often used to indicate that a file is UTF-8, the UTF -16Or UTF -32Coded markup.Unicode: U+FEFF
Copy the code

Now that we know what zero-width characters mean, let’s test JavaScript compression.

Enter the make all command in the terminal, open the generated dist directory, and check the app.min.js file. It is found that the size has been compressed to 2 bytes.

This is just a simple example of building a front-end project with a Makefile. In actual development, we can use make -j to start multithreading to speed up our build, but in modern front-end builds like WebPack, Rollup, We can also use multiple processes to speed up our packaging (such as thread-loader). Therefore, it is recommended that in a real development scenario, it is best to build our project with isomorphic code.

The code will be sent to Github later in the day.