|
| 1 | +# Developer Guide: Bash and Make |
| 2 | + |
| 3 | +- [Developer Guide: Bash and Make](#developer-guide-bash-and-make) |
| 4 | + - [Using Make](#using-make) |
| 5 | + - [Using Bash](#using-bash) |
| 6 | + - [Make and Bash working together](#make-and-bash-working-together) |
| 7 | + - [Conventions](#conventions) |
| 8 | + - [Debugging](#debugging) |
| 9 | + - [Scripts](#scripts) |
| 10 | + - [TODO](#todo) |
| 11 | + |
| 12 | +## Using Make |
| 13 | + |
| 14 | +Sample make target definition: |
| 15 | + |
| 16 | +```makefile |
| 17 | +some-target: # Short target description - mandatory: foo=[description]; optional: baz=[description, default is 'qux'] |
| 18 | + # Recipe implementation... |
| 19 | +``` |
| 20 | + |
| 21 | +Run make target from a terminal: |
| 22 | + |
| 23 | +```shell |
| 24 | +foo=bar make some-target # Environment variable is passed to the make target execution process |
| 25 | +make some-target foo=bar # Make argument is passed to the make target execution process |
| 26 | +``` |
| 27 | + |
| 28 | +By convention we use uppercase variables for global settings that you would ordinarily associate with environment variables. We use lower-case variables as arguments to call functions or make targets, in this case. |
| 29 | + |
| 30 | +All make targets should be added to the `${VERBOSE}.SILENT:` section of the `make` file, which prevents `make` from printing commands before executing them. The `${VERBOSE}` prefix on the `.SILENT:` special target allows toggling it if needed. If you explicitly want output from a certain line, use `echo`. |
| 31 | + |
| 32 | +It is worth noting that by default, `make` creates a new system process to execute each line of a recipe. This is not the desired behaviour for us and the entire content of a make recipe (a target) should be run in a single shell invocation. This has been configured in this repository by setting the [`.ONESHELL:`](https://www.gnu.org/software/make/manual/html_node/One-Shell.html) special target in the `scripts/init.mk` file. |
| 33 | + |
| 34 | +## Using Bash |
| 35 | + |
| 36 | +When working in the command-line ensure the environment variables are reset to their initial state. This can be done by reloading shell using the `env -i $SHELL` command. |
| 37 | + |
| 38 | +Sample Bash function definition: |
| 39 | + |
| 40 | +```shell |
| 41 | +# Short function description |
| 42 | +# Arguments (provided as environment variables): |
| 43 | +# foo=[description] |
| 44 | +# baz=[description, default is 'qux'] |
| 45 | +function some-shell-function() { |
| 46 | + # Function implementation... |
| 47 | +``` |
| 48 | +
|
| 49 | +Run Bash function from a terminal: |
| 50 | +
|
| 51 | +```shell |
| 52 | +source scripts/a-suite-of-shell-functions |
| 53 | +foo=bar some-shell-function # Environment variable is accessible by the function executed in the same operating system process |
| 54 | +``` |
| 55 | +
|
| 56 | +```shell |
| 57 | +source scripts/a-suite-of-shell-functions |
| 58 | +foo=bar |
| 59 | +some-shell-function # Environment variable is still accessible by the function |
| 60 | +``` |
| 61 | +
|
| 62 | +Run Bash script from a terminal, bear in mind that executing a script creates a child operating system process: |
| 63 | +
|
| 64 | +```shell |
| 65 | +# Environment variable has to be exported to be passed to a child process, DO NOT use this pattern |
| 66 | +export foo=bar |
| 67 | +scripts/a-shell-script |
| 68 | +``` |
| 69 | +
|
| 70 | +```shell |
| 71 | +# or to be set in the same line before creating a new process, prefer this pattern over the previous one |
| 72 | +foo=bar scripts/a-shell-script |
| 73 | + |
| 74 | +# or when multiple variables are required |
| 75 | +foo=bar \ |
| 76 | +baz=qux \ |
| 77 | + scripts/a-shell-script |
| 78 | +``` |
| 79 | +
|
| 80 | +By convention we use uppercase variables for global settings that you would ordinarily associate with environment variables. We use lower-case variables as arguments to be passed into specific functions we call, usually on the same line, right before the function name. |
| 81 | +
|
| 82 | +The command `set -euo pipefail` is commonly used in the Bash scripts, to configure the behavior of the script in a way that makes it more robust and easier to debug. Here is a breakdown of each option switch: |
| 83 | +
|
| 84 | +- `-e`: Ensures that the script exits immediately if any command returns a non-zero exit status. |
| 85 | +- `-u`: Makes the script exit if there is an attempt to use an uninitialised variable. |
| 86 | +- `-o pipefail`: ensures that if any command in a pipeline fails (i.e., returns a non-zero exit status), then the entire pipeline will return that non-zero status. By default, a pipeline returns the exit status of the last command. |
| 87 | +
|
| 88 | +## Make and Bash working together |
| 89 | +
|
| 90 | +Sample make target calling a Bash function. Notice that `baz` is going to be accessible to the function as it is executed in the same operating system process: |
| 91 | +
|
| 92 | +```makefile |
| 93 | +some-target: # Run shell function - mandatory: foo=[description] |
| 94 | + source scripts/a-suite-of-shell-function |
| 95 | + baz=qux |
| 96 | + some-shell-function # 'foo' and 'baz' are accessible by the function |
| 97 | +``` |
| 98 | +
|
| 99 | +Sample make target calling another make target. In this case a parameter `baz` has to be passed as a variable to the make target, which is executed in a child process: |
| 100 | +
|
| 101 | +```makefile |
| 102 | +some-target: # Call another target - mandatory: foo=[description] |
| 103 | + baz=qux \ |
| 104 | + make another-target # 'foo' and 'baz' are passed to the make target |
| 105 | +``` |
| 106 | +
|
| 107 | +Run it from a terminal: |
| 108 | +
|
| 109 | +```shell |
| 110 | +foo=bar make some-target |
| 111 | +``` |
| 112 | +
|
| 113 | +## Conventions |
| 114 | +
|
| 115 | +### Debugging |
| 116 | +
|
| 117 | +To assist in investigating scripting issues, the `VERBOSE` variable is available for both Make and Bash scripts. If it is set to `true` or `1`, it prints all the commands that the script executes to the standard output. Here is how to use it: |
| 118 | +
|
| 119 | +for Make targets |
| 120 | +
|
| 121 | +```shell |
| 122 | +VERBOSE=1 make docker-example-build |
| 123 | +``` |
| 124 | +
|
| 125 | +for Bash scripts |
| 126 | +
|
| 127 | +```shell |
| 128 | +VERBOSE=1 scripts/shellscript-linter.sh |
| 129 | +``` |
| 130 | +
|
| 131 | +### Scripts |
| 132 | +
|
| 133 | +Most scripts provided with this repository template can utilise tools installed on your `PATH` if they are available or run them from within a Docker container. To force a script to use Docker, the `FORCE_USE_DOCKER` variable is provided. Here is an example of how to use it: |
| 134 | +
|
| 135 | +```shell |
| 136 | +FORCE_USE_DOCKER=1 scripts/shellscript-linter.sh |
| 137 | +``` |
| 138 | +
|
| 139 | +You can combine it with the `VERBOSE` flag to see the details of the execution flow: |
| 140 | +
|
| 141 | +```shell |
| 142 | +VERBOSE=1 FORCE_USE_DOCKER=1 scripts/shellscript-linter.sh |
| 143 | +``` |
| 144 | +
|
| 145 | +## TODO |
| 146 | +
|
| 147 | +- Use of CLI tools installed and available on `$PATH` |
| 148 | +- Commands run in Docker containers when a CLI tool is not installed |
| 149 | +- Explain the concept of modules in this repository |
| 150 | +- Make is used as an orchestrator and tool to integrate development processes with the CI/CD pipeline |
0 commit comments