{"id":13596066,"url":"https://github.com/xonixx/makesure","last_synced_at":"2026-01-03T15:14:48.898Z","repository":{"id":44535918,"uuid":"316841633","full_name":"xonixx/makesure","owner":"xonixx","description":"Simple task/command runner with declarative goals and dependencies","archived":false,"fork":false,"pushed_at":"2025-04-01T23:26:54.000Z","size":858,"stargazers_count":346,"open_issues_count":6,"forks_count":6,"subscribers_count":6,"default_branch":"main","last_synced_at":"2025-04-02T00:26:51.555Z","etag":null,"topics":["awk","bash","build-automation","build-system","build-tool","make","makefile","shell","task-runner"],"latest_commit_sha":null,"homepage":"https://makesure.dev","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/xonixx.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-11-28T23:37:37.000Z","updated_at":"2025-04-01T23:26:58.000Z","dependencies_parsed_at":"2025-01-20T00:23:02.598Z","dependency_job_id":"b60ba816-d549-4550-a70c-03879cd26c50","html_url":"https://github.com/xonixx/makesure","commit_stats":null,"previous_names":[],"tags_count":24,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xonixx%2Fmakesure","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xonixx%2Fmakesure/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xonixx%2Fmakesure/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xonixx%2Fmakesure/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/xonixx","download_url":"https://codeload.github.com/xonixx/makesure/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248049854,"owners_count":21039282,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["awk","bash","build-automation","build-system","build-tool","make","makefile","shell","task-runner"],"created_at":"2024-08-01T16:02:07.092Z","updated_at":"2026-01-03T15:14:48.855Z","avatar_url":"https://github.com/xonixx.png","language":"Shell","readme":"[![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner-direct-single.svg)](https://stand-with-ukraine.pp.ua)\n\n# makesure\n\n[![Run tests](https://github.com/xonixx/makesure/workflows/Run%20tests/badge.svg)](https://github.com/xonixx/makesure/actions?query=workflow%3A%22Run+tests%22)\n![coverage](coverage.svg)\n\nSimple task/command runner inspired by `make` with declarative goals and dependencies.\n\nThe simplest way to think of this tool is to have a way to have \"shortcuts\" (aka goals) to some pieces of scripts. This way allows to call them easily without the need to call long shell one-liners instead.\n\nExample `Makesurefile`:\n\n```\n@goal downloaded\n@reached_if [[ -f code.tar.gz ]]\n  wget http://domain/code.tar.gz\n  \n@goal extracted\n@depends_on downloaded\n  tar xzf code.tar.gz \n\n@goal built\n@depends_on extracted\n  npm install\n  npm run build\n\n@goal deployed\n@depends_on built\n  scp -C -r build/* user@domain:~/www\n\n@goal default\n@depends_on deployed\n```\n\nNow to run the whole build you just issue `./makesure` command in a folder with `Makesurefile` (`default` goal will be called). \n\nYou can as well call single goal explicitly, example `./makesure built`. \n\nAlso pay attention to `@reached_if` directive. This one allows skipping goal if it's already satisfied. This allows to speedup subsequent executions.\n\nBy default, all scripts inside goals are executed with `bash`. If you want to use `sh` just add `@shell sh` directive at start of the `Makesurefile`.  \n\n## Features\n\n- [Zero-install](#installation)\n- [Very portable](#os)\n- Very simple, only bare minimum of truly needed features. You don’t need to learn a whole new programming language to use the tool! Literally it’s goals + dependencies + handful of directives + bash/shell.\n- Much saner and simpler `make` analog.\n- A bunch of useful built-in facilities: timing the goal's execution, listing goals in a build file, a [means](#reached_if) to speed-up repeated builds.\n- The syntax of a build file is also a [valid bash/shell](Makesurefile) (though semantics is different). This can to some extent be in use for editing in IDE.\n\n## Usage\n\n```\n$ ./makesure -h\nmakesure ver. 0.9.24\nUsage: makesure [options...] [-f buildfile] [goals...]\n -f,--file buildfile\n                 set buildfile to use (default Makesurefile)\n -l,--list       list all available non-@private goals\n -la,--list-all  list all available goals\n -d,--resolved   list resolved dependencies to reach given goals\n -D \"var=val\",--define \"var=val\"\n                 override @define values\n -s,--silent     silent mode - only output what goals output\n -t,--timing     display execution times for goals and total\n -x,--tracing    enable tracing in bash/sh via `set -x`\n -v,--version    print version and exit\n -h,--help       print help and exit\n -U,--selfupdate update makesure to latest version\n```\n\n## Installation\n\nSince `makesure` is a tiny utility represented by a single file, the recommended installation strategy is to keep it local to a project where it's used (this means in code repository). Not only this eliminates the need for repetitive installation for every dev on a project, but also allows using separate `makesure` version per project and update only as needed.\n\n```sh\nwget \"https://raw.githubusercontent.com/xonixx/makesure/v0.9.24/makesure\" -Omakesure \u0026\u0026 \\\nchmod +x makesure \u0026\u0026 echo \"makesure $(./makesure -v) installed\"\n```\nor\n```sh\ncurl \"https://raw.githubusercontent.com/xonixx/makesure/v0.9.24/makesure\" -o makesure \u0026\u0026 \\\nchmod +x makesure \u0026\u0026 echo \"makesure $(./makesure -v) installed\"\n```\n\n### Update\n\nUpdates `makesure` executable to latest available version in-place:\n\n```sh\n./makesure -U\n```\n\n## Prerequisites\n\n### OS    \n\n`makesure` will run on any environment with POSIX shell available. [Tested](https://github.com/xonixx/makesure/actions) and officially supported are:\n \n- Linux\n- macOS\n- FreeBSD\n- Windows (via Git Bash)\n      \n## Concepts\n\n- Build file is a text file named `Makesurefile`.\n- Build file uses [directives](#directives).\n- Build file consists of a set of goals.\n- A [goal](#goal) is a labeled piece of shell.\n- A goal can declare [dependencies](#depends_on) on other goals. During execution each referenced dependency will run only once despite the number of occurrences in dependency tree. Dependencies will run in proper sequence according to the inferred topological order. Dependency loops will be reported as error.\n- Goal bodies are executed in separate shell invocations. It means, you can’t easily pass variables from one goal to another. This is done on purpose to enforce declarative style.\n- By default, goals are run with `bash`. You can change to `sh` with `@shell sh` directive specified before all goals.\n- For convenience in all shell invocations the current directory is automatically set to the one of `Makesurefile`. Typically, this is the root of the project. This allows using relative paths without bothering of the way the build is run.\n- Goal can declare `@reached_if` directive ([link](#reached_if)). This allows skipping goal execution if it's already satisfied.\n\n## Directives\n   \n### @options\n\nOnly valid: in prelude (meaning before any `@goal` declaration).\n\nValid options: `timing`, `tracing`, `silent`\n\n```\n@options timing\n```\nWill measure and log each goal execution time + total time.\n\nExample `Makesurefile`:\n```sh\n@options timing\n\n@goal a\n@depends_on b\n  echo \"Executing goal 'a' ...\"\n  sleep 1\n@goal b\n  echo \"Executing goal 'b' ...\"\n  sleep 2\n```\n\nRunning:\n```\n$ ./makesure a\n  goal 'b' ...\nExecuting goal 'b' ...\n  goal 'b' took 2.003 s\n  goal 'a' ...\nExecuting goal 'a' ...\n  goal 'a' took 1.003 s\n  total time 3.006 s\n```\n\n*Small issue exists with this option on macOS.* Due to BSD's `date` not supporting `+%N` formatting option, the default precision of timings is 1 sec. To make it 1 ms precise (if this is important) just install Gawk (`brew install gawk`). In this case Gawk built-in `gettimeofday` function will be used. \n```\n@options tracing\n```\nWill trace the executed shell script. This activates `set -x` shell option under the hood.\n```\n@options silent\n```\nBy default `makesure` logs the goals being executed. Use this option if this is not desired (you only need the output of your own code in goals).\n\n### @define\n\nUse this directive to declare global variable (visible to all goals).\nThe variable will be declared as environment variable (via `export`).\n\nExample:\n\n```sh\n@define A hello\n@define B \"${A} world\"\n@define C 'hello world'\n```\n\nThis directive is valid [in any place](tests/24_define_everywhere.sh) in `Makesurefile`. However, we recommend:\n- place frequently changed variables (like versions) to the top of `Makesurefile`\n- place infrequently changed variables closer to the goals/libs that use them\n\nVariable defined with `@define` can be overridden with a variable passed in invocation via `-D` parameter. \n\nOverall the precedence for variables resolution is (higher priority top):\n\n- `./makesure -D VAR=1`\n- `@define VAR 2` in `Makesurefile`\n- `VAR=3 ./makesure`\n\nThe precedence priorities are designed like this on purpose, to prevent accidental override of `@define VAR='value'` definition in file by the environment variable with the same name. However, sometimes this is the desired behavior. In this case you can use:\n\n```sh\n@define VAR  \"${VAR}\"                      # using the same name, or\n@define VAR1 \"${ENV_VAR}\"                  # using different name, or\n@define VAR2 \"${VAR_NAME:-default_value}\"  # if need the default value when not set  \n```\n\nThis allows to use environment variables `VAR`, `ENV_VAR`, and `VAR_NAME` to set the value of `VAR`, `VAR1` and `VAR2`. \n\nPlease note, the parser of `makesure` is somewhat stricter here than shell's one:\n```sh\n@define HW  ${HELLO}world    # makesure won't accept  \n@define HW \"${HELLO}world\"   # OK  \n```\n\n### @shell\n\nOnly valid: in prelude.\n\nValid options: `bash` (default), `sh`\n\nSets the shell interpreter to be used for execution of goal bodies and `@reached_if` conditions.\n\nExample:\n\n```\n@shell sh\n```\n\n### @goal\n\n#### Simple goal\n```\n@goal goal_name [ @private ]\n```\n\nDefines a goal. `@private` modifier is optional. When goal is private, it won't show in `./makesure -l`. To list all goals including private use `./makesure -la`.\n\nLines that go after this declaration line (but before next `@goal` declaration line) will be treated as a shell script for the body of the goal. Example:\n\n```sh\n@goal hello\n  echo \"Hello world\" \n```\n\nHaving the above in `Makesurefile` will produce next output when ran with `./makesure hello`\n```\nhello world\n```\n\nIndentation in goal body is optional, unlike `make`, so below is perfectly valid:\n\n```sh\n@goal hello\necho \"Hello world\" \n```\n\nInvoking `./makesure` without arguments will attempt to call the goal named `default`:\n\n```sh\n@goal default\n  echo \"I'm default goal\"\n```\n\n#### Glob goal\n```\n@goal [ goal_name ] @glob \u003cglob pattern\u003e [ @private ]\n```\n\nThis one is easy to illustrate with an example:\n\n```sh\n@goal process_file @glob '*.txt' \n echo $ITEM $INDEX $TOTAL\n```\n\nIs equivalent to declaring three goals\n\n```sh\n@goal process_file@a.txt @private\n echo a.txt 0 2\n\n@goal process_file@b.txt @private\n echo b.txt 1 2\n \n@goal process_file\n@depends_on process_file@a.txt   \n@depends_on process_file@b.txt   \n```\niff\n```\n$ ls\na.txt b.txt\n```\n\nFor convenience, you can omit name in case of glob goal:\n```sh\n@goal @glob '*.txt'\n echo $ITEM $INDEX $TOTAL\n```\nas equivalent for\n```sh\n@goal a.txt @private\n echo a.txt 0 2\n\n@goal b.txt @private\n echo b.txt 1 2\n \n@goal '*.txt'\n@depends_on a.txt \n@depends_on b.txt \n```\n\nSo essentially one glob goal declaration expands to multiple goal declarations based on files present in project that match the glob pattern. Shell glob expansion mechanism applies. \n\nThe useful use case here would be to represent a set of test files as a set of goals. The example could be found in the project's own [build file](https://github.com/xonixx/makesure/blob/3be738d771bf855b5a6d3cd08cbc38dc977bed76/Makesurefile#L91).\n\nWhy this may be useful? Imagine in your nodejs application you have `test1.js`, `test2.js`, `test3.js`.\nNow you can use this `Makesurefile`\n\n```sh\n@goal @glob 'test*.js'\n  echo \"running test file $INDEX out of $TOTAL ...\"\n  node $ITEM\n```\n\nto be able to run each test individually (`./makesure test2.js` for example) and all together (`./makesure 'test*.js'`).\n\nIn case if you need to glob the files with spaces in their names, please check the [naming rules section](#naming-rules) below.\n\n#### Parameterized goal\n\nMake code easier to reuse.\n\n\u003cins\u003eDeclaration syntax (using `@params`):\u003c/ins\u003e\n```sh\n@goal goal_name @params A B C\n```\n\u003cins\u003eUsage syntax (using `@args`):\u003c/ins\u003e\n```sh\n@goal other_goal @params PARAM\n@depends_on goal_name @args 'value1' 'value 2' PARAM\n```\n                           \nThe idea of using two complementary keywords `@params` + `@args` was inspired by `async` + `await` from JavaScript.\n\nExample:\n\n```sh\n@goal file_downloaded @params FILE_NAME\n  echo \"Downloading $FILE_NAME...\"\n  \n@goal file_processed @params FILE_NAME\n@depends_on file_downloaded @args FILE_NAME\n  echo \"Processing $FILE_NAME...\"\n  \n@goal all_files_processed\n@depends_on file_processed @args 'file1' \n@depends_on file_processed @args 'file2' \n@depends_on file_processed @args 'file3' \n```\n\nHaving the above in `Makesurefile` will produce next output when ran with `./makesure all_files_processed`:\n```\n  goal 'file_downloaded@file1' ...\nDownloading file1...\n  goal 'file_processed@file1' ...\nProcessing file1...\n  goal 'file_downloaded@file2' ...\nDownloading file2...\n  goal 'file_processed@file2' ...\nProcessing file2...\n  goal 'file_downloaded@file3' ...\nDownloading file3...\n  goal 'file_processed@file3' ...\nProcessing file3...\n  goal 'all_files_processed' [empty].\n```\n\nWhen listing goals, you'll see \"instantiated\" goals there:\n```\n$ ./makesure -l\nAvailable goals:\n  all_files_processed\n  file_processed@file1\n  file_downloaded@file1\n  file_processed@file2\n  file_downloaded@file2\n  file_processed@file3\n  file_downloaded@file3\n```\n\nAnd you can even call such \"instantiated\" goal: \n```\n$ ./makesure file_processed@file2\n  goal 'file_downloaded@file2' ...\nDownloading file2...\n  goal 'file_processed@file2' ...\nProcessing file2...\n```\n\nYou can also take a look at an [example from a real project](https://github.com/xonixx/intellij-awk/blob/68bd7c5eaa5fefbd7eaa9f5f5a4b77b69dcd8779/Makesurefile#L126).\n\nNote, the goal's body parameter values will appear as environment variables (as if defined via `export`).\n\nNote, you can reference the `@define`-ed variables in the arguments of the parameterized goals:\n\n```sh\n@define HELLO 'hello'\n\n@goal parameterized_goal @params ARG\n  echo \"ARG=$ARG\"\n  \n@goal goal1\n@depends_on parameterized_goal @args HELLO          # reference by name\n@depends_on parameterized_goal @args \"$HELLO world\" # interpolated inside string\n```\n\nHaving the above in `Makesurefile` will produce next output when ran with `./makesure goal1`:\n```\n  goal 'parameterized_goal@hello' ...\nARG=hello\n  goal 'parameterized_goal@hello world' ...\nARG=hello world\n  goal 'goal1' [empty].\n```\n\nYou can also rely on parameterized goals [parameters interpolation](https://github.com/xonixx/makesure/issues/153).\n\nAlso, it's possible for a `@glob` goal [to be parameterized](https://github.com/xonixx/makesure/issues/155).\n\nPlease find a more real-world example [here](https://github.com/xonixx/fhtagn/blob/e7161f92731c13b5afbc09c7d738c1ff4882906f/Makesurefile#L70).\n\nFor more technical consideration regarding this feature see [parameterized_goals.md](docs/parameterized_goals.md).\n\n#### Naming rules\n\nIt's *recommended* that you name your goals using alphanumeric chars + underscore.\n\nHowever, it's possible to name a goal any way you want provided that you apply proper escaping:\n\n```sh\n@goal 'name with spaces' # all chars between '' have literal meaning, same as in shell, ' itself is not allowed in it\n\n@goal $'name that contains \\' single quote' # if you need to have ' in a string, use dollar-strings and escape it\n\n@goal usual_name  \n```\n\nNow `./makesure -l` gives:\n```\nAvailable goals:\n  'name with spaces'\n  $'name that contains \\' single quote'\n  usual_name\n```\n\nNote, how goal names are already escaped in output. This is to make it easier for you to call it directly:\n```sh\n./makesure $'name that contains \\' single quote'\n```\n\nSame naming rules apply to other directives (like `@doc`).\n\nUsually you won't need this escaping tricks often, but they can be especially in use for `@glob` goals if the relevant files have spaces in them:\n\n```sh\n@goal @glob 'file\\ with\\ spaces*.txt'\n@goal other\n  @depends_on 'file with spaces1.txt'\n```\n\nMore info on this topic is covered in the [issue](https://github.com/xonixx/makesure/issues/63).\n\n### @doc\n\nOnly valid: inside `@goal`.\n                  \nProvides a description for a goal.\n\nExample `Makesurefile`:\n\n```sh\n@goal build\n@doc builds the project \n  echo \"Building ...\"\n  \n@goal test\n@doc tests the project\n  echo \"Testing ...\"\n```\n\nRunning `./makesure -l` will show\n\n```\nAvailable goals:\n  build : builds the project\n  test  : tests the project\n```\n\n### @depends_on\n\nOnly valid: inside `@goal`.\n                 \nSyntax:\n```\n@depends_on goal1 [ goal2 [ goal3 [...] ] ]\n```\nDeclares a dependency on other goal. \n\nExample `Makesurefile`:\n\n```sh\n@goal a\n  echo a\n  \n@goal b\n@depends_on a\n  echo b\n```\n\nRunning `./makesure b` will show\n \n```\n  goal 'a' ...\na\n  goal 'b' ...\nb\n```\n\nYou can declare multiple dependencies for a goal:\n\n```sh\n@goal a\n  echo a\n\n@goal b\n@depends_on a\n  echo b\n\n@goal c\n  echo c\n\n@goal d\n@depends_on b c\n  echo d\n```\n\nRunning `./makesure d` will show\n```\n  goal 'a' ...\na\n  goal 'b' ...\nb\n  goal 'c' ...\nc\n  goal 'd' ...\nd\n```\n\nCircular dependency will cause an error:\n\n```sh\n@goal a\n@depends_on b\n\n@goal b\n@depends_on c\n\n@goal c\n@depends_on a\n```\n\nRunning `./makesure a` will show\n```\nThere is a loop in goal dependencies via a -\u003e c\n```\n\n### @calls\n\nOnly valid: inside `@goal`.\n                 \nSyntax:\n```\n@calls goal1 [ goal2 [ goal3 [...] ] ]\n```\nImperative (non-declarative) counterpart of [@depends_on](#depends_on).\n\nThe need for `@calls` may be not obvious, but the use-case is presented [here](https://github.com/xonixx/makesure/issues/171). \n\nDifferences to `@depends_on`:\n- `@calls` doesn't favor run-once semantics\n- `@calls` defers the `@reached_if` processing of a goal being called to the invocation time (`@depens_on` calculates all `@reached_if` conditions at start) \n\nOperationally `@calls` desugars to a nested `makesure` invocation:\n\n```sh\n@goal a\n  echo a\n  \n@goal b\n@calls a\n```\ndesugars to \n```sh\n@goal a\n  echo a\n  \n@goal b\n  ./makesure a # actual command passes other arguments (for --file, --define, etc.)\n```\n\nExample `Makesurefile`:\n\n```sh\n@goal a\n  echo a\n  \n@goal b\n@calls a\n@calls a\n```\n\nRunning `./makesure b` will show\n \n```\n  goal 'b' ...\n  goal 'a' ...\na\n  goal 'a' ...\na\n```\n\nYou can declare multiple `@calls` targets for a goal:\n\n```sh\n@goal a\n@calls b c d\n```\n\nCircular dependency (both `@depends_on` and `@calls` considered) will cause an error:\n\n```sh\n@goal a\n@depends_on b\n\n@goal b\n@calls c\n\n@goal c\n@calls a\n```\n\nRunning `./makesure a` will show\n```\nThere is a loop in goal dependencies via a -\u003e c\n```\n\nYou can use `@calls` to invoke a [parameterized goal](#parameterized-goal):\n\n```sh\n@goal hello @params WHO\n  echo \"Hello $WHO!\"\n\n@goal a\n@calls hello @args 'world'\n@calls hello @args 'hacker'\n```\n\nRunning `./makesure a` will show\n```\n  goal 'a' ...\n  goal 'hello@world' ...\nHello world!\n  goal 'hello@hacker' ...\nHello hacker!\n```\n\nYou can mix `@calls` and `@depends_on` but please note, that _depended-on_ goal will be invoked before the _called_ one.\nSo this is valid:\n\n```sh\n@goal a\n@calls b\n@depends_on c\n```\nbut you better write it as:\n```sh\n@goal a\n@depends_on c\n@calls b\n```\n \nYou can find more information on how the directive interacts with the other directives in [@calls.md](docs/@calls.md).\n\n### @reached_if\n\nOnly valid: inside `@goal`.\n     \nSyntax:\n```\n@reached_if \u003ccondition\u003e\n```\n\nAllows skipping goal execution if it's already satisfied. This allows to speedup subsequent executions. Only one per goal allowed. The goal will be considered fulfilled (and thus will not run) if `condition` executed as a shell script returns exit code `0`. Any `condition` evaluation is done only once.\n\nExample `Makesurefile`:\n\n```sh\n@goal file_created\n@reached_if [[ -f ./file.txt ]]\n  echo \"Creating file ...\"\n  echo \"hello world\" \u003e ./file.txt\n```\n\nIf you run `./makesure file_created` the first time:\n```\n  goal 'file_created' ...\nCreating file ...\n```\n\nIf you run `./makesure file_created` the second time:\n```\n  goal 'file_created' [already satisfied].\n```\n\nIt is a good practice to name goals that declare `@reached_if` in past tense.\n\n`@reached_if` should only rely on condition that is changed by the owning goal ([why?](https://github.com/xonixx/makesure/issues/105#issuecomment-1031571263)).\n\n### @lib\n\nSyntax:\n```\n@lib [ lib_name ]\n```\n\nHelps with code reuse. Occasionally you need to run similar code in multiple goals. The most obvious approach would be to place a code into `shared.sh` and invoke it in both goals. The downside is that now you need an additional file(s) and the build file is no more self-contained. `@lib` to the resque!\n\nThe usage is simple:\n\n```sh\n@lib lib_name\n  a() { \n    echo Hello $1  \n  }\n\n@goal hello_world\n@use_lib lib_name\n  a World\n```\n\nFor simplicity can omit name:\n       \n```sh\n@lib\n  a() {\n    echo Hello $1  \n  }\n\n@goal hello_world\n@use_lib\n  a World\n```\n     \nOperationally `@use_lib` is just substituted by content of a corresponding `@lib`'s body, as if the above goal is declared like:\n```sh\n@goal hello_world\n  a() {\n    echo Hello $1  \n  }\n  a World\n```\n\n### @use_lib\n\nOnly valid: inside `@goal`.\n\nOnly single `@use_lib` per goal is allowed.\n\n## Bash completion\n        \nInstall Bash completion for `./makesure` locally\n```sh\n[[ ! -f ~/.bash_completion ]] \u0026\u0026 touch ~/.bash_completion\ngrep makesure ~/.bash_completion \u003e/dev/null || echo '. ~/.makesure_completion.bash' \u003e\u003e ~/.bash_completion\ncurl \"https://raw.githubusercontent.com/xonixx/makesure/v0.9.24/completion.bash\" -o ~/.makesure_completion.bash  \necho 'Please reopen the shell to activate completion.'\n```\n\n## Design principles\n\n- Convention over configuration.\n- Minimalistic. Bare minimum of features that compose good with each other.\n- There should be one way to do the thing.\n- Overall [Zen of Python](https://www.python.org/dev/peps/pep-0020/#the-zen-of-python).\n- Think hard before adding new feature. Think of a damage it could cause used improperly. Think of cognitive complexity it introduces. Only add a feature generic enough to cover lots of useful cases instead of just some corner cases. Let's better have a list of recipes for the latter.\n- Do not introduce unjustified complexity. User should not be forced to learn a whole new programming language to work with a tool. Instead, the tool is based on limited set of simple concepts, like goals + dependencies + handful of directives + familiar shell language (bash/sh).\n- [Worse is better](https://en.wikipedia.org/wiki/Worse_is_better).\n- [Principle of least surprise](https://en.wikipedia.org/wiki/Principle_of_least_astonishment).\n- Tests coverage is a must.\n\n## Omitted features\n\n- Calling goals with arguments, like in [just](https://github.com/casey/just#recipe-parameters) \n  - We deliberately don't support this feature. The idea is that the build file should be self-contained, so have all the information to run in it, no external arguments should be required. It should be much easier for the final user to run a build. The tool however has limited parameterization capabilities via `./makesure -D VAR=value`.\n- Includes\n  - This is a considerable complication to the tool. Also, it makes the build file not self-contained.  \n- Shells other than `bash`/`sh`\n  - Less portable build.\n  - If you need to use, say, python for a goal body, it's unclear why you even need `makesure` at all. Besides, you always can just use `python -c \"script\"`. \n- Custom own programming language, like `make` has\n  - We think that this would be unjustified complexity.\n  - We believe that the power of shell is enough.\n- Parallel execution\n  - `makesure` is a task runner, not a full-fledged build tool, like `make`, `ninja` or `bazel`. So if you need one, just use a proper build tool of your choice. \n\n## Developer notes\n\nFind some contributor instructions in [DEVELOPER.md](docs/DEVELOPER.md).\n\n### AWK\n\nThe core of this tool is implemented in [AWK](https://en.wikipedia.org/wiki/AWK).\nAlmost all major implementations of AWK will work. Tested and officially supported are [Gawk](https://www.gnu.org/software/gawk/), [BWK](https://github.com/onetrueawk/awk), [mawk](https://invisible-island.net/mawk/). This means that the default AWK implementation in your OS will work.\n\nDeveloped in [xonixx/intellij-awk](https://github.com/xonixx/intellij-awk).\n\n## Articles\n\n- [Makesure vs Just command runners on examples](https://maximullaris.com/makesure-vs-just.html) (December 2023)\n- [makesure v0.9.21 – what’s new?](https://maximullaris.com/revamp_define.html) (October 2023)\n- [Adding parameterized goals to makesure](https://maximullaris.com/parameterized_goals.html) (March 2023)\n- [makesure vs make](https://maximullaris.com/makesure-vs-make.html) (March 2023)\n- [makesure – make with a human face](https://maximullaris.com/makesure.html) (February 2023)\n\n## Similar tools\n\n- **just** [https://github.com/casey/just](https://github.com/casey/just) `Rust`\n  - just is a handy way to save and run project-specific commands\n- **Taskfile** [https://github.com/adriancooney/Taskfile](https://github.com/adriancooney/Taskfile) `Bash`\n  - A Taskfile is a bash \\[...] script that follows a specific format \\[...], sits in the root of your project \\[...] and contains the tasks to build your project.\n- **Task** [https://github.com/go-task/task](https://github.com/go-task/task) `Go`\n  - Task is a task runner / build tool that aims to be simpler and easier to use than, for example, GNU Make.\n- **mmake** [https://github.com/tj/mmake](https://github.com/tj/mmake) `Go`\n  - Modern Make is a small program which wraps `make` to provide additional functionality\n- **Robo** [https://github.com/tj/robo](https://github.com/tj/robo) `Go`\n  - Simple Go / YAML-based task runner for the team\n- **haku** [https://github.com/VladimirMarkelov/haku](https://github.com/VladimirMarkelov/haku) `Rust` \n  - A task/command runner inspired by 'make'\n- **Invoke-Build** [https://github.com/nightroman/Invoke-Build](https://github.com/nightroman/Invoke-Build) `PowerShell`\n  - Build Automation in PowerShell\n- **make** [https://www.gnu.org/software/make/](https://www.gnu.org/software/make/) `C`\n  - \\[...] a tool which controls the generation of executables and other non-source files of a program from the program's source files.\n","funding_links":[],"categories":["bash","Shell"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxonixx%2Fmakesure","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fxonixx%2Fmakesure","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxonixx%2Fmakesure/lists"}