Makefile - Tutorial and Tips & Tricks
Essential Makefile tips and tricks for automation. Learn to check file existence, run dependent targets, use conditional logic, suppress errors, and integrate with Docker in your scripts.
This article is over a year old but was reviewed on July 30, 2026 — the content is still accurate.

This is a large personal reference of Makefile tips accumulated over time: checking file/folder existence, chaining dependent targets, silencing echoed commands, conditional logic, ignoring errors, forcing Bash instead of /bin/sh, variable substitution, tabs-vs-spaces gotchas, named parameters, extending targets with ::, reading values from .env files, verbose-mode toggles, git helper targets, a vendor-freshness check for Composer projects, and a self-documenting make help target.
When I'm learning, I usually take notes. I find that it's one of the best ways of remembering what I've seen and being able to come back to it at any time.
Below is a note that I took and revised several times when I took the time to create my first .make files.
Two companion articles: Linux Makefile - When to use a makefile for the why, and Linux Makefile - Adding a help screen for the self-documenting make target that makes all these tips discoverable by your colleagues.
Don't have `make` installed yet? Click to expand.
Just run the following commands to install the Make executable on your host machine:
How to check if a file exists or not
You can use the test -s statement like below:
@test -s .config/phpunit.xml || echo "phpunit.xml didn't exist"
@test -s .config/phpunit.xml && echo "phpunit.xml exists"This makefile snippet checks whether a file called phpunit.xml exists in the .config directory and prints a message accordingly. The first line reports if the file is missing, and the third line confirms if it's present. The @ symbol suppresses the command itself from being printed during execution.
Unlike the ifeq statement, test should be indented.
We can use the { ... } notation if we need to run more than one command; f.i.:
@test -s .config/phpunit.xml || { echo "phpunit.xml file is missing, please take actions! Exiting..."; exit 1; }This is a bash test that checks whether a phpunit.xml configuration file exists and has content. If the file doesn't exist or is empty, it prints an error message and stops the script with an exit code of 1 to signal failure.
We can also do this before, f.i., includes an external file:
ifneq ("$(wildcard .env)","") include .envendifThis Makefile snippet checks whether a .env file exists in the current directory, and if it does, it includes (loads) that file so its variables are available to the rest of the Makefile. This is a common pattern for loading configuration from environment files.
Of course, we can also keep it simple i.e. just use the - before the command to ignore errors so, below, if the file didn't exist, no error will be raised and the script will continue.
-include .envThis is a Makefile snippet that includes a .env file if it exists. The minus sign tells Make to ignore errors if the file isn't found, so the build won't fail just because there's no .env file.
How to check if a folder exists or not
You can use the ifeq statement with a Linux shell command like below:
ifeq ($(shell test -e vendor/a/b/c || echo -n no),no) @echo "The folder didn't exist"endif
ifeq ($(shell test -e vendor/a/b/c && echo -n yes),yes) @echo "The folder exists"endifThis Makefile snippet checks whether a specific folder (vendor/a/b/c) exists using two different approaches. The first block echoes a message if the folder does NOT exist, while the second block echoes a message if the folder DOES exist. Both use shell commands to test for the folder's presence and compare the result.
Unlike the test statement, ifeq should be used without indentation. Directly at position column zero.
Running dependent targets
By running make php-cs-fixer, we'll first run vendor then update-them, finally php-cs-fixer i.e. we can define a list of dependent targets.
php-cs-fixer: vendor update-them @echo "And finally run php-cs-fixer" vendor/bin/php-cs-fixer
vendor: @echo "First get vendors"
update-them: @echo "Then update vendors"This is a Makefile that defines three build targets: 'vendor' prints a message about getting vendors, 'update-them' prints a message about updating vendors, and 'php-cs-fixer' runs those two targets in sequence and then executes the php-cs-fixer tool to automatically format PHP code according to style rules.
Stop the job if a target fails
If one of them fails, the script will stop. In the example below, php-cs-fixer will never print And finally run php-cs-fixer.
php-cs-fixer: vendor update-them @echo "And finally run php-cs-fixer" vendor/bin/php-cs-fixer
vendor: @echo "First get vendors"
update-them: @echo "Then update vendors" exit 2 && echo "Oups, an error has occurred"This is a Makefile that defines build targets for managing PHP dependencies and code formatting. The `php-cs-fixer` target runs a code style fixer tool, while `vendor` and `update-them` are helper targets that print messages (likely meant to be called in sequence). The exit statement on line 10 deliberately causes the update process to fail.
Running make php-cs-fixer will output this:
Using shell find option to get all dependencies
Imagine you've a lot of .md files in the directory. If one change, we'll concat them again. If nothing changes, nothing to do.
This can be done using a shell command for the dependence:
concat: $(shell find *.md -type f) $(shell concat.sh)This is a Makefile rule that finds all markdown files in the current directory and runs a shell script called concat.sh to process them. The 'concat' target depends on all .md files being present, so if any markdown files change, the rule will re-execute.
(source https://tech.davis-hansson.com/p/make/#specifying-inputs)
Don't echo the command
Running make helloWorld like below will output two lines on the console.
helloWorld: echo "Hello world"This is a simple shell script target (likely in a Makefile) that, when invoked, will print the text 'Hello world' to the console. It's a basic example of defining a named command that can be run by calling 'make helloWorld'.
And the output in the console:
To avoid the first one i.e. the output of the fired instruction, just prefix it with an at sign (@).
helloWorld: @echo "Hello world"This is a Makefile target named 'helloWorld' that, when invoked with the make command, will print the text 'Hello world' to the console. The @ symbol suppresses echoing the command itself, so you only see the output message, not the echo command that produced it.
Now, only the output will be echoed; no more the instruction itself.
Using conditional statements
We can make some conditional statement like this but, be careful on the indentation:
init:ifeq ($(shell expr $(SET_PHP_VERSION) \>= 8), 1) @printf "\e[1;${COLOR_YELLOW}m%s\e[0m\n\n" "Upgrade dependencies to PHP 8"else @printf "\e[1;${COLOR_YELLOW}m%s\e[0m\n\n" "Downgrade dependencies to PHP 7"endifThis is a Makefile snippet that checks the PHP version being used and prints an appropriate message to the console. If PHP 8 or higher is set, it tells the user to upgrade dependencies; otherwise, it says to downgrade to PHP 7. The message is printed in bold yellow text.
make init SET_PHP_VERSION=7.4 will display the downgrade message while make init SET_PHP_VERSION=8.1 the upgrade one.
Another example:
ifneq ("$(DB_TYPE)","") echo "The DB_TYPE variable is not emptyendif
ifeq ("$(DB_TYPE)","") echo "The DB_TYPE variable is emptyendifThis is a Makefile snippet that checks whether the DB_TYPE variable has a value or not, printing different messages depending on the result. It uses two conditional blocks—one that triggers if DB_TYPE is not empty, and another that triggers if it is empty.
Ignore error
Don't stop in case of error: add a - before the line like :-php --lint index.php
phplint: -php --lint index.php echo "Yes, this script will continue..."This is a Makefile target named 'phplint' that runs PHP's built-in linter on index.php to check for syntax errors, then prints a confirmation message if no errors stop execution.
In case of linting error in index.php don't stop the execution of the script and process the next command.
Note: makefile can perhaps display a message like make: [makefile:114: up] Error 1 (ignored) to inform the user an error has occurred but has been skipped. If you don't want it at all (silent output), this is how to do:
[...] @-docker network create -d bridge my_network >/dev/null 2>&1 || true[...]This Makefile target creates a Docker bridge network named 'my_network', suppressing any output and error messages. The '|| true' at the end ensures the command never fails, even if the network already exists, so the build continues without interruption.
This command will create a new Docker network called my_network and if case of error (the network already exists f.i.), we don't care and don't want to see any output.
Make sure you're using Bash
By default, make is using /bin/sh. This can be upgraded by adding this assignment at the top of the makefile:
SHELL:=bashThis line sets a Makefile variable called SHELL to the value 'bash', which tells Make to use the bash shell (instead of the default sh shell) when executing commands in recipe lines throughout the rest of the Makefile.
(source: https://tech.davis-hansson.com/p/make/#always-use-a-recent-bash)
Set the default target
By default, the first target defined in the file will be the default one i.e. the one fired when the user will just fire make on the command line.
default: hello
hello: @echo "Hello World"This is a Makefile that defines a simple target called 'hello' which prints 'Hello World' to the console. The 'default' target points to 'hello', so running 'make' without arguments will execute the hello target by default.
Substitution
Consider make convert INFILE=readme.md
outfile=$(INFILE:.md=.pdf)
COLOR_BLUE:=34
convert: @printf "\e[1;${COLOR_BLUE}m%s\e[0m\n\n" "INPUT FILE IS ${INFILE}; OUTPUT WILL BE ${outfile}"This Makefile snippet sets up a build target that converts a file from Markdown to PDF format. It defines a color constant for terminal output, creates a variable for the output filename by replacing the .md extension with .pdf, and then prints a colored message to the user showing what file will be converted and where it will go.
The syntax outfile=$(INFILE:.md=.pdf) will replace .md by .pdf so we can, in this example, derive the output file based on the input file.
Tab not space
The indentation to use when creating a makefile is the tabulation; not spaces. Using spaces will break the file.
helloWorld: echo "Hello World"This is a Makefile target named 'helloWorld' that, when invoked, executes a simple echo command to print 'Hello World' to the console. It's a basic example of how to define and run tasks in a Makefile.
However it is possible to adapt this behavior using .RECIPEPREFIX:
.RECIPEPREFIX = >
changeExt:### This is a comment> @printf "%s\n" "It works"This is a Makefile snippet that defines a custom recipe prefix and creates a simple target called 'changeExt'. When you run this target, it prints 'It works' to the output. The first line changes Makefile's recipe prefix from tab characters to the '>' character, which is unusual but valid.
(source: https://tech.davis-hansson.com/p/make/#dont-use-tabs)
Using parameters
Running a target with a parameter should be done using named parameters like this:
This will create a variable called firstname, we then can use it:
hello: @echo "Hi ${firstname}!"This is a Makefile target named 'hello' that prints a greeting message. When you run 'make hello', it echoes a message that includes a variable called 'firstname', which gets substituted with its actual value.
Make sure parameters are set
Imagine we want to run make runsql SQL='SELECT * FROM users LIMIT 10' i.e. the SQLargument should be defined otherwise we'll have a problem.
COLOR_RED:=31COLOR_YELLOW:=33
runsql: @[ "${SQL}" ] && printf "\e[1;${COLOR_YELLOW}m\n%s\e[0m\n\n" "Running ${SQL}" || ( printf "\e[1;${COLOR_RED}m\n%s\e[0m\n\n" "ERROR - Please set the SQL to execute; consult the help if needed"; exit 1 )This Makefile snippet defines a task called 'runsql' that checks whether a SQL variable has been set, then either prints a yellow message saying what SQL will run, or prints a red error message and exits if SQL wasn't provided. It's a safety check to prevent accidentally running undefined SQL commands.
Working with Docker
In a makefile, we can bail out early if we need a given Docker container to be running.
The if statement below will make sure the sonarqube container is running; if not, because it wasn't created yet or is stopped f.i., an error statement will be executed and the script will be stopped.
sonar-scan:ifeq ($(shell docker ps -a -q -f name=sonarqube -f status=running),) ### The sonarqube container didn't exist yet or not running (exit mode f.i.) @printf $(_RED) "ERROR - Please first run \"make sonar-server\"" exit 1endifThis Makefile target checks whether a SonarQube Docker container is already running before proceeding with a scan. If the container isn't found or isn't in a running state, it prints an error message and exits with failure status, preventing the scan from running against a non-existent service.
The if below will check if the container exists and if not, will create it.
The else statement knows thus that the container exists but will make sure it's running.
ifeq ($(shell docker ps -a -q -f name=sonarqube),) ### The sonarqube container didn't exist yet, create it. docker run -d --name sonarqube sonarqube:latestelse ### Make sure the container is running -@docker container start sonarqube > /dev/null 2>&1endifThis Makefile snippet checks whether a Docker container named 'sonarqube' already exists. If it doesn't, it creates a new one from the latest sonarqube image. If it does exist, it ensures the container is started, silencing any output or errors.
Configure Visual Studio Code
Add the makefile extension
https://marketplace.visualstudio.com/items?itemName=ms-vscode.makefile-tools
Add the .editorconfig file
Make sure your Makefile file is correctly formatted; add a file called .editorconfig in your root directory.
[*]end_of_line = lfinsert_final_newline = true
[Makefile]indent_style = tabindent_size = 4This is an EditorConfig file that sets code formatting rules for a project. It establishes baseline settings for all files (line endings and final newlines), then overrides those settings specifically for Makefiles to use tabs for indentation.
Some tips
How to extend a target
You've an existing target, let's say hello in our example, and you wish to extend it and add extra actions.
hello can be defined in the same makefile or in an included one but let's illustrate this with a basic example: we wish to add the Nice to meet you output.
hello: @echo "Hello world"
hello: @echo "Nice to meet you"This is a Makefile with two targets both named 'hello'. When you run 'make hello', only the second one executes because Makefile targets with duplicate names get overwritten — the second definition replaces the first. Each target uses @echo to print a message without showing the command itself.
If we run that file, here is the output.
The solution: use :: (this is called an explicit rule) and not a single : after the recipe; see the next sample:
hello:: @echo "Hello world"
hello:: @echo "Nice to meet you"
hello:: @echo "Do you have any plans for this weekend?"This Makefile defines a target called 'hello' three separate times, each with its own echo command. When you run 'make hello', all three rules will execute in sequence, printing three different greeting messages.
If we run that file, here is the output.
Recipes are just extended, the second one is appended to the first and so on so the order is important.
Getting the current directory into a variable
PWD:=$(shell pwd)
current_dir: @echo "The current directory is ${PWD}"This Makefile snippet defines a phony target called 'current_dir' that, when invoked, prints out the current working directory. The PWD variable is set at the beginning using a shell command to capture the actual directory path.
Get a list of files and initialize a variable
Let's take a real use case: scan a folder called .docker and retrieve the list of compose*.yaml files there.
The objective is to initialize an environment variable called COMPOSE_FILE (see https://docs.docker.com/compose/environment-variables/envvars/#compose_file) so, when running docker compose we can use all files at once (i.e. by not adding the --file file1.yml --file file2.yml and on)
So, getting the list of compose*.yaml can be done like this:
DOCKER_YAML_FILES := $(shell find ./.docker -name 'compose*.yaml')DOCKER_YAML_FILES := $(shell echo "$(DOCKER_YAML_FILES)" | tr ' ' ':')This Makefile snippet finds all Docker Compose YAML files in the `.docker` directory, then converts the space-separated list into a colon-separated list. This format is useful for passing multiple files as arguments to tools that expect paths delimited by colons.
The first line will return f.i. compose.yaml compose.override.yaml compose.mysql.yaml.
The second instruction will replace the space by a colon (:).
We'll thus obtain compose.yaml:compose.override.yaml:compose.mysql.yaml.
Now, to run docker compose config for instance, we just need to do the following i.e. first declare the COMPOSE_FILE environment variable then run the desired action.
.PHONY: configconfig: COMPOSE_FILE=${DOCKER_YAML_FILES} docker compose configThis Makefile target runs Docker Compose's config command to validate and display the final merged Docker Compose configuration. It uses a variable to specify which YAML files to include, allowing you to see what Docker will actually execute when you run your services.
Getting information from the .env file
Getting a value from a .env file is easy, just include it then use variables:
DOCKER_IMAGE=cavo789/my_imageThis script sets a variable called DOCKER_IMAGE to the string 'cavo789/my_image', which likely represents a Docker image identifier in the format of username/image-name. This is typically used later in the script to reference which container image to work with.
include .env
helloDocker: @echo "The name of the image is ${DOCKER_IMAGE}"This is a Makefile that includes environment variables from a .env file and defines a task called 'helloDocker' that prints out the name of a Docker image stored in the DOCKER_IMAGE variable.
This include tip will work with any file defining a variable and its value
We can perfectly have a file called Make.config, not .env
Define variable based on .env environment variables
Let's imagine you've a variable called APP_ENV in your .env file.
That variable can be set to local, test or whatever you want. Will be set to production when the application is running in the production environment.
So, based on that variable, we can define a variable like this:
ifeq ($(APP_ENV), production) CMD:=else CMD:=docker compose exec my_docker_imageendifThis Makefile snippet conditionally sets a CMD variable based on the APP_ENV environment variable. If we're in production, CMD stays empty (commands run directly), but in other environments like development, CMD becomes a docker compose prefix so commands run inside a container.
This means: if we're not running in production, every command will be fired inside our Docker container. If running in production, the command will be executed directly.
Here is an example:
composer-update: ${CMD} composer update --no-interactionThis is a Makefile target that runs the Composer dependency manager with an update command. When you invoke 'make composer-update', it will update all PHP project dependencies to their latest allowed versions without prompting for user input.
Use a default value if the variable is not defined
If the OS environment variable PHP_VERSION is not defined, set its default value to 8.1
PHP_VERSION := $(or $(PHP_VERSION),8.1)This is a Makefile variable assignment that sets PHP_VERSION to 8.1 if it hasn't already been defined. The `or` function checks if PHP_VERSION was passed in from elsewhere (like command-line arguments), and only uses the default value 8.1 if nothing was provided.
Another example can be: imagine you've a .env file with the DOCKER_APP_HOME variable. But, if the variable is not defined, by using the syntax below, you can set a default value.
echo $(or ${DOCKER_APP_HOME},/var/www/html)This line outputs a directory path, using a variable called DOCKER_APP_HOME if it's set, otherwise falling back to the default path /var/www/html. It's a common pattern for allowing configuration while providing a sensible default.
This will allow things like below i.e. target a custom version of a Docker image based on the selected PHP version.
DOCKER_PHPQA:=jakzal/phpqa:php${PHP_VERSION}-alpineThis line defines a Makefile variable called DOCKER_PHPQA that constructs a Docker image name by combining a base image path with a PHP version number. When this variable is used later in the Makefile, it will expand to the full image identifier needed to run PHP code quality analysis tools.
Override a variable
Even if the variable is still defined, you can override it by passing it on the command line:
This will start the yamllint target with PHP_VERSION set to 8.1 even if the variable is already defined and f.i. set to 7.4.
How to check if a variable starts with a given value?
The use case is: we have a variable called PHP_VERSION and we need to detect if we need to deal with PHP 7 code or PHP 8 or greater.
IS_PHP_7 := $(shell [[ $(PHP_VERSION) =~ 7[0-9.]+$$ ]] && echo "yes")
doThing:ifdef IS_PHP_7 @echo "Hey, it's an old version of PHP, why not migrate to PHP 8?"else @echo "Nice! You're using PHP 8 or greater"endifThis Makefile snippet checks the PHP version by using a regex pattern to detect if it's version 7, then stores the result in a variable. When the doThing target runs, it prints different messages depending on whether PHP 7 was detected or not.
Since Makefile didn't support regexes, we rely on the shell for running the regex and to return a non-empty string if the expression is matched. In that case, the IS_PHP_7 variable is defined and the ifdef construction will be verified.
Verbose mode
The idea: don't show informative message when running some targets.
The code below will check the presence of the --quiet argument / value in the $ARGS standard variable.
Sample code to demonstrate how to enable/disable verbose mode in a makefile.
Using the "--quiet" argument in ARGS.
- Verbose mode: run
make testmeon the command line - Silent mode: run
make testme ARGS="--quiet"on the command line
QUIET=$(if $(findstring --quiet,${ARGS}),true,false)
testme:ifeq ($(QUIET),false) @printf '\e[1;30m%s\n\e[m' "QUIET MODE NOT ENABLED - We'll show any informative text."endif @printf '\e[1;32m%s\n\n\e[m' "This is an important message"This Makefile snippet checks whether a `--quiet` flag was passed in the command arguments, then defines a `testme` target that conditionally prints an informational message about quiet mode, followed by an important message that always prints.
The code above define a global variable QUIET that will be set to true or false depending on the presence of the --quiet keyword in ARGS.
Then, use the ifeq conditional structure to show (or hide) informative message.
By running make testme ARGS="--quiet" only This is an important message will be displayed.
Working with git
Retrieve some important information
Retrieve some important variables from the shell:
GIT_CURRENT_BRANCH:=$(shell git rev-parse --abbrev-ref HEAD)
GIT_ROOT_DIR:=$(shell git rev-parse --show-toplevel)
GIT_URL_REPO:=$(shell git config --get remote.origin.url | sed -r 's:git@([^/]+)\:(.*\.git):https\://\1/\2:g' | grep -Po '.*(?=\.)')This Makefile snippet captures three pieces of information about the current Git repository: the branch name, the root directory path, and the repository URL. Each line runs a shell command and stores the result in a variable so it can be used later in build scripts or automation.
Some git targets
When variables have been initialized, we can do things like this:
git_open_repo: @sensible-browser ${GIT_URL_REPO}
git_open_pipeline: @sensible-browser ${GIT_URL_REPO}/pipelines
git_open_wiki: @sensible-browser ${GIT_URL_REPO}/-/wikis/homeThis Makefile defines three convenience targets that open different pages of a Git repository in your default web browser. Each target uses the sensible-browser command to launch a URL, with the base repository URL stored in the GIT_URL_REPO variable and different paths appended for the repo, pipelines, and wiki pages.
Git - Work in progress
Run make git_wip to quickly push your changes to the remote repository and skip the local hooks:
git_wip: git add . git commit -m "wip --no-verify git pushThis is a Makefile target that creates a quick checkpoint of work-in-progress code. When you run 'make git_wip', it stages all changes, commits them with a 'wip' message while skipping pre-commit hooks, and pushes to the remote repository — useful for saving your work without worrying about code quality checks.
Working with PHP project and vendors
Updating the vendor folder only when needed
Having a target like below (i.e. called vendor) will result in a check Should the vendor be upgraded or not?. This is done by first running the composer.lock target. The idea is then to compare the date/time of that file and composer.json. If there is a difference, the Make tool will run composer update and will generate a newer version of composer.lock and thus a newer version of vendor too.
If no changes have been made to the composer.json file, nothing has to be done since vendor is considered up to date.
Pretty easy.
composer.lock: composer.json ${CMD} composer update --no-interaction
vendor: composer.lock ${CMD} composer installThis is a Makefile snippet that defines two build targets: one to update PHP dependencies (composer.lock), and another to install them (vendor directory). The first target runs when composer.json changes, ensuring the lock file stays in sync; the second runs when the lock file changes, installing those specific versions into the vendor folder.
To run the scenario, just run make vendor.
PHP - Quality checks
The portion below can just be copied/pasted in your own Makefile to add quality controls features based on the https://github.com/jakzal/phpqa Docker image.
COLOR_BLUE:=34COLOR_CYAN:=36COLOR_YELLOW:=33
#### region - Implement `Quality Assurance` features; relying on the jakzal/phpqa Docker image)#### Define the Docker image to use for PHPQA (https://github.com/jakzal/phpqa)#! PHP_VERSION should be defined in the `.env` filePHP_VERSION:=$(or $(PHP_VERSION),8.1)
DOCKER_PHPQA:=jakzal/phpqa:php${PHP_VERSION}-alpine
#### The full "docker run" command to be able to run a command in the jakzal/phpqa containerDOCKER_PHPQA_ARGS:=-it --rm -v ${PWD}:/project -w /project -v ${PWD}/.output/tmp-phpqa:/tmp
#### Get the current user/group ID and define the docker "-u" argumentDOCKER_PHPQA_UID_GID:=-u $(shell id -u):$(shell id -g)
#### Finally, the final command for running a command in the jakzal/phpqa containerDOCKER_PHPQA_COMMAND:=docker run ${DOCKER_PHPQA_ARGS} ${DOCKER_PHPQA_UID_GID} ${DOCKER_PHPQA}
##### Run PHAN and report problemsphan: clear #### This is how to define a local variable $(eval COMMAND := $(shell echo "phan --progress-bar --config-file .config/phan.php")) @printf "\e[1;${COLOR_YELLOW}m%s\e[0m\n\n" "Report PHAN errors in your PHP files (no fix) using $(DOCKER_PHPQA)" @printf "\e[1;${COLOR_YELLOW}m%s\e[0m\n\n" "Running \"${COMMAND}\"" @printf "\e[1;${COLOR_BLUE}m%s\e[0m\n\n" "Note: make sure your /vendor folder is populated..." @docker run -it ${DOCKER_PHPQA_ARGS} ${DOCKER_PHPQA} phan --version @echo "" ${DOCKER_PHPQA_COMMAND} ${COMMAND}
##### Run PHP-CS-FIXER on the codebase IN A DRY-RUN MODEphp-cs-fixer-dry-run: clear #### This is how to define a local variable $(eval COMMAND := $(shell echo "php-cs-fixer fix --verbose --config=/project/.config/.php-cs-fixer.php --using-cache=no --diff")) @printf "\e[1;${COLOR_YELLOW}m%s\e[0m\n\n" "Report code formatting issues in your PHP files (no fix) using $(DOCKER_PHPQA)" @printf "\e[1;${COLOR_YELLOW}m%s\e[0m\n\n" "Running \"${COMMAND} --dry-run\"" ${DOCKER_PHPQA_COMMAND} ${COMMAND} --dry-run
##### Run PHP-CS-FIXER on the codebase and fix problemsphp-cs-fixer-fix: clear #### This is how to define a local variable $(eval COMMAND := $(shell echo "php-cs-fixer fix --verbose --config=/project/.config/.php-cs-fixer.php --using-cache=no --diff")) @printf "\e[1;${COLOR_YELLOW}m%s\e[0m\n\n" "Automatically fix formatting issues in your PHP files using $(DOCKER_PHPQA)" @printf "\e[1;${COLOR_YELLOW}m%s\e[0m\n\n" "Running \"${COMMAND}\"" ${DOCKER_PHPQA_COMMAND} ${COMMAND}
##### PHP - Check the syntax of PHP files and report syntax errors if anyphplint: clear @printf "\e[1;${COLOR_YELLOW}m%s\e[0m\n\n" "Check syntax validity of php files using $(DOCKER_PHPQA)" #### https://github.com/overtrue/phplint ${DOCKER_PHPQA_COMMAND} phplint --no-cache --exclude=vendor
##### YAML - Check the syntax of yaml files in the directory root folder and report syntax errors if anyyamllint: clear @printf "\e[1;${COLOR_YELLOW}m%s\e[0m\n\n" "Check syntax validity of yaml files using $(DOCKER_PHPQA)" #### https://github.com/j13k/yaml-lint #### (it seems yaml-lint can't search in all the tree structure and *.yml won't get .gitlab-ci.yml; strange) ${DOCKER_PHPQA_COMMAND} yaml-lint .*.yml *.yml .install/CI/*.yml#### endregionThis Makefile snippet sets up a quality assurance workflow using Docker containers to run PHP linting and code formatting tools. It defines color codes, configures a phpqa Docker image with the current PHP version, and then creates four Make targets (phan, php-cs-fixer-dry-run, php-cs-fixer-fix, phplint, yamllint) that run various code analysis and formatting commands inside isolated Docker containers while preserving file ownership.
Some functions
Clean folders
COLOR_YELLOW:=33
clean: @printf "\e[1;${COLOR_YELLOW}m%s\e[0m\n\n" "Remove node_modules and vendor folders" rm --recursive --force node_modules/ vendor/ @printf "\e[1;${COLOR_YELLOW}m\n%s\e[0m\n\n" "Empty .cache, .output and storage/logs" rm --recursive --force .cache/* .output/* storage/logs/*This is a Makefile snippet that defines a 'clean' target to remove build artifacts and temporary files from a project. When you run 'make clean', it deletes node_modules and vendor directories, then empties cache and log directories, with yellow colored status messages printed along the way.
We can also test for the existence of the folder first:
clean: test ! -e vendor || rm -rf vendorThis is a build target that safely removes the vendor directory if it exists. It's a cleanup step that would typically be run before a fresh installation or rebuild to ensure no stale dependencies remain.
Open a web browser
open-browser: @sensible-browser http://www.google.fr @echo "The site has been opened in your browser"This is a Makefile target that opens a French Google homepage in your default browser and prints a confirmation message. It's a convenient shortcut for developers who want to quickly launch a website during their build process.
This is pretty useful when you work with git repositories:
GIT_URL_REPO:=$(shell git config --get remote.origin.url | sed -r 's:git@([^/]+)\:(.*\.git):https\://\1/\2:g' | grep -Po '.*(?=\.)')
git_open_repo: @sensible-browser ${GIT_URL_REPO}This Makefile snippet defines a variable that extracts the GitHub repository URL from your local Git configuration and converts it to HTTPS format if needed, then provides a make target that opens that repository in your default web browser.
Self documenting makefile
You can use a tip for creating a target like help. The idea is to scan the current makefile and extract the list of all verbs and their description.

There are a few different ways to achieve this. I already have written a blog post for this: Linux Makefile - Adding a help screen.
The complexity comes when you're adding some files using include (like the .env file) and where the structure can be a bit different that your makefile.
The solution given here above is working for me.


