1# GNU Make Extensions
  2
  3Makext is a collection of useful extensions for Makefiles, aimed
  4at simplifying and enhancing the functionality of Make-based
  5projects. These extensions provide additional features and
  6convenience functions to improve the overall usage of [GNU
  7Make](https://www.gnu.org/software/make/) as a task runner.
  8
  9All the extensions are written in GNU Make so no other languages are used
 10making this very embedable and has zero dependencies other than GNU Make.
 11
 12Other than `makext.mk` all the other files in the repository are either
 13license or readme files or just testing data that is/are not needed to
 14use it.
 15
 16> [!NOTE]
 17> These extensions are abusing GNU Make in some sense since it was not
 18> meant to really be a task runner. Keep that in mind. However, despite
 19> that, I constantly find myself using it as such.
 20
 21## Extensions
 22
 23| Extension   | Description                                         |
 24|-------------|-----------------------------------------------------|
 25| **help**        | Displays all targets with a comment in help format. |
 26| **assure**      | Check for the existence of programs on a machine.   |
 27| **environment** | Loads environmental variables from other files.     |
 28
 29Additional features:
 30
 31- Automatic description inclusion in help message.
 32- Automatic license inclusion in help message.
 33
 34Tested on:
 35
 36- GNU Linux Debian 12 with GNU Make 4.3
 37- macOS Sonoma 14.4.1 with GNU Make 3.81
 38- Windows 10/11 with GNU Make 4.4.1 for Windows32
 39
 40If you have an idea for a new feature [open a new
 41issue](https://github.com/mitjafelicijan/makext/issues/new).
 42
 43## How to use
 44
 45> [!IMPORTANT]
 46> If you intend to use this script on Windows machine
 47> make sure you have all the [prerequisites](#windows-specific-instructions) installed.
 48
 49First you will need to download `makext.mk` file from the repository to
 50the same directory where you have `Makefile`.
 51
 52```sh
 53wget -O makext.mk https://github.com/mitjafelicijan/makext/raw/master/makext.mk
 54```
 55
 56Now you can include it in your `Makefile`.
 57
 58```make
 59include makext.mk
 60
 61help: .help
 62```
 63
 64> [!IMPORTANT]
 65> Make sure you create first target `help: .help` before any other
 66> targets in your `Makefile`. GNU Make will execute first target if
 67> no target provided as an argument when calling `make`.
 68
 69### Windows specific instructions
 70
 71On Windows OS'es you need the following:
 72
 73- Git Bash for Windows
 74- make
 75
 76All of the above can be conveniently installed via [Scoop](https://scoop.sh/):
 77
 78```sh
 79scoop install main/git main/make
 80```
 81
 82## Help extension
 83
 84One of the extensions is `.help` which displays all the targets in the
 85`Makefile` and their descriptions which are provided as comments next
 86to the target definition.
 87
 88Lets check how and example `Makefile` would look like. It
 89is recommended to use `.PHONY` for targets that are not
 90actual files. In the example below I am not doing that
 91though, but it is wise to follow that rule. Check [4.6 Phony
 92Targets](https://www.gnu.org/software/make/manual/make.html#Phony-Targets)
 93section in the GNU Make manual.
 94
 95```make
 96include makext.mk
 97
 98help: .help
 99
100build-app: clean-cache # Build the application
101	@echo "Building the application..."
102
103clean-cache: # Clean the cache
104	@echo "Cleaning the cache..."
105
106deploy-prod: # Deploy to production
107	@echo "Deploying to production..."
108
109run-tests:
110	@echo "Running tests..."
111```
112
113This will give us the following result when we execute command `make`
114without any arguments.
115
116```text
117$ make
118Targets:
119  build-app                 Build the application
120  clean-cache               Clean the cache
121  deploy-prod               Deploy to production
122```
123
124- Targets without defined comment next to the target will be ignored
125  from help list. In this case `run-tests` is missing from the list.
126- Targets that start with `.` will also be ignored.
127- Prerequisites in targets will be omitted from the result. See how
128  `clean-cache` is missing from `build-app` target.
129
130## Description & License information
131
132You can provide description for the project that will be displayed
133together with `help`. To do this provide this information in the
134`MEX_DESCRIPTION` variable.
135
136Same goes for license information. Provide this information by creating
137`MEX_LICENSE` variable.
138
139If these variables are not present this information will not be displayed
140in the help.
141
142Description and license information is also formatted to max 75 characters
143per row.
144
145> [!IMPORTANT]
146> Variables `MEX_DESCRIPTION` and `MEX_LICENSE` must be defined before you
147> include `makext.mk` to your `Makefile`. This is needed because the way
148> GNU Make is parsing Makefiles.
149
150```make
151MEX_DESCRIPTION="This provides some additional tools for this project."
152MEX_LICENSE="Released under the BSD two-clause license, see the LICENSE file for more information."
153
154include makext.mk
155
156help: .help
157
158build-app: clean-cache # Build the application
159	@echo "Building the application..."
160
161clean-cache: # Clean the cache
162	@echo "Cleaning the cache..."
163```
164
165The following example will produce the following result.
166
167```text
168$ make
169This provides some additional tools for this project.
170
171Targets:
172  build-app                 Build the application
173  clean-cache               Clean the cache
174
175Released under the BSD two-clause license, see the LICENSE file for
176more information.
177```
178
179## Assure extension
180
181Often times project uses multiple programs and to ensure that these
182programs are already installed before recipes are executed `.assure` can
183be used. If programs are missing recipes can only partially be executed
184leaving project in a potentially broken state.
185
186```make
187MEX_ASSURE="python3 ls tree clang"
188
189include makext.mk
190
191build-app: .assure
192	@echo "Building the application..."
193```
194
195`.assure` prerequisite will loop over the list of programs defined in
196`MEX_ASSURE` variable and in case one is missing will exit `make` with
197error status code 1. This will stop executing the recipe and therefore
198not execute anything in target `build-app`.
199
200## Environment extension
201
202This extension helps loading of additional environmental files in your
203project. The files should have environmental variables defined in the
204usual way. Separate each file by a space and that is about it.
205
206If a file is missing this will break the execution of make and exit with
207status code 1.
208
209```env
210API_KEY=abc123
211SECRET_KEY=def456
212```
213
214By defining `MEX_ENVIRONMENT` variable you can provide additional files
215and they will be loaded automatically.
216
217```make
218MEX_ENVIRONMENT="local.env second.env"
219
220include makext.mk
221
222demo-envars:
223	@echo "Environment variables"
224	@echo "  HOME: $(HOME)"
225	@echo "  TERM: $(TERM)"
226	@echo "  ENV: $(MEX_ENVIRONMENT)"
227	@echo "  AUDIO_BUCKET: $(AUDIO_BUCKET)"
228	@echo "  DB_HOST: $(DB_HOST)"
229```
230
231After that they can be used in your recipes like all the other variables
232you have. They will however override variables the shell already has
233defined.
234
235## Alternative tools
236
237- https://github.com/rocky/remake
238- https://github.com/casey/just
239- https://github.com/xonixx/makesure
240- https://github.com/ruby/rake
241- https://github.com/taskctl/taskctl
242- https://github.com/go-task/task
243- https://github.com/pydoit/doit
244
245## Acknowledgment
246
247- https://stackoverflow.com/a/59087509
248
249## License
250
251[makext](https://github.com/mitjafelicijan/makext) was written by [Mitja
252Felicijan](https://mitjafelicijan.com) and is released under the BSD
253two-clause license, see the LICENSE file for more information.