[
  {
    "path": "Dockerfile",
    "content": "FROM alpine\n\nRUN apk add --no-cache make\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2021 Uros Perisic\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "PREFIX?=/usr/local\n.PHONY=cat shell install test test-basic test-directory test-dockerfiles test-find-dockerfiles\n\ncat:\n\tcat /etc/os-release\n\nshell:\n\t/bin/sh\n\n# run naked\ntest: test-basic test-directory test-dockerfiles test-find-dockerfiles\n\ntest-basic:\n\t./cake\n\t./cake cat\n\ntest-directory:\n\t./cake -C subdir\n\t./cake --directory subdir\n\t./cake --directory=subdir\n\ntest-dockerfiles:\n\tCAKE_DOCKERFILES='subdir/Dockerfile' ./cake\n\tCAKE_DOCKERFILES='subdir/example.dockerfile subdir/Dockerfile' ./cake -C subdir\n\ntest-find-dockerfiles:\n\tCAKE_DOCKERFILES='subdir/' ./cake\n\ninstall:\n\t@mkdir -p ${DESTDIR}${PREFIX}/bin\n\tcp -f cake \"${DESTDIR}${PREFIX}/bin\" && \\\n\tchmod 755 \"${DESTDIR}${PREFIX}/bin/cake\"\n"
  },
  {
    "path": "README.md",
    "content": "<h1 align=\"center\"> 🍰 </h1>\n\n## What is Cake?\nCake is a *really* thin, drop-in replacement/wrapper around `make` that runs all\nof your targets inside of a development Docker/Podman container.\n\n### Vision\n- Though Cake supports more complex workflows, most projects that currently have\n  a `Makefile` at their root should also place a developer-focused `Dockerfile`\n  there for convenience and portability\n  - The `Makefile` is the single source of truth for the build process\n  - The `Dockerfile` is the single source of truth for the build environment\n- A container runtime should not be a hard dependency to build the project.\n- Choosing between containerized and \"naked\" builds should be as easy as typing\n  `make` or `cake` interchangeably \n- CI/CD pipelines should be able to reuse the instructions from the `Makefile`\n  in an ergonomic way without having to keep the build context in mind\n    \n## Why Cake?\nBecause I found myself constantly writing Makefiles that run their targets in a\ncontainer, then adding in add-hoc ways for people not to use the container\nthrough environment variables, followed by a half-hearted attempt at\noptimizations through bind-mounts and less frequent restarts, and some faulty\nlogic to avoid name and tag clashes. I figured it was time to extract this into\na script. Despite its simplicity, the script covers 99% of my use cases for\ntools like [act](https://github.com/nektos/act) without being tied to a specific\nforge.\n\n## How-To\nJust use `cake` instead of `make`. The defaults should fit most use cases.\n\nIf you really have to, you can specify additional `docker`/`podman` arguments\nusing `$CAKE_RUNTIME_ARGS`. I recommend placing these in your\n[.envrc](https://direnv.net/) if you need them to stick around due to the\nspecific needs of your project.\n\nIf you're building/testing your software against multiple environments, you can\nalways set `$CAKE_DOCKERFILES` (defaults to Make's `${PWD}/Dockerfile` - which\nis not necessarily the same as your shell's `${PWD}/Dockerfile`). This will run\nyour Make targets in one container per `Dockerfile`. If `$CAKE_DOCKERFILES` is a\ndirectory, all `Dockerfile`s in that directory (and all of its sub-directories)\nwill be used. This is the one area in which Cake diverges from Make. You have to\nspecify Cake-relevant environment variables before the command, not after. You\ncan take a look at some of my test cases for example invocations:\n\n``` sh\ncake\ncake all\ncake -C subdir\nCAKE_DOCKERFILES='subdir/' cake\nCAKE_DOCKERFILES='subdir/Dockerfile' cake\nCAKE_DOCKERFILES='subdir/one.dockerfile subdir/Dockerfile' cake\n```\n\n\n## Tips\n\nIf I want to debug my development container, I like to add a `shell` target\nto my `Makefile` like so:\n``` makefile\nshell:\n    /bin/sh\n```\nIt's more ergonomic then copying the container name.\n\n\nThe same goes for dealing with things like `./autogen.sh` and the `./configure`\nscript (often managed directly by the user). I tend to call those through a\n`Makefile` as well. Take this snippet from the `GNUMakefile` in the Emacs source\ntree as an example:\n\n``` makefile\nconfigure:\n\t@echo >&2 'There seems to be no \"configure\" file in this directory.'\n\t@echo >&2 Running ./autogen.sh ...\n\t./autogen.sh\n\t@echo >&2 '\"configure\" file built.'\n\nMakefile: configure\n\t@echo >&2 'There seems to be no Makefile in this directory.'\n\t@echo >&2 'Running ./configure ...'\n\t./configure\n\t@echo >&2 'Makefile built.'\n\n# 'make bootstrap' in a fresh checkout needn't run 'configure' twice.\nbootstrap: Makefile\n\t$(MAKE) -f Makefile all\n```\n\n### Why POSIX sh\nBecause additional dependencies are a problem, especially in corporate\nenvironments. just `curl`/copy this script into a directory on your `$PATH` and\nyou're good to go.\n\n## Completions\nI might provide them for convenience later, but in principle all you need to do\nis reuse existing make completions. In `zsh` that looks something like this:\n``` zsh\ncompdef _make cake\n```\n\n\n"
  },
  {
    "path": "cake",
    "content": "#!/bin/sh\n\nlog() {\n    if [ -t 1 ]; then\n        case $1 in\n            error) log_type=\"\\e[1mcake: \\e[31merror:\\e[0m\" ;;\n            warning) log_type=\"\\e[1mcake: \\e[33mwarning:\\e[0m\" ;;\n            info) log_type=\"\\e[1mcake: \\e[32minfo:\\e[0m\" ;;\n        esac\n    else\n        case $1 in\n            error) log_type=\"cake: error:\" ;;\n            warning) log_type=\"cake: warning:\" ;;\n            info) log_type=\"cake: info:\" ;;\n        esac\n    fi\n    shift\n    printf \"%b %s\\n\" \"$log_type\" \"$1\"\n}\n\nset_runtime() {\n    if type docker > /dev/null; then\n        log info 'using docker'\n        runtime=docker\n    elif type podman > /dev/null; then\n        log info 'using podman'\n        runtime=podman\n    else\n        log error 'cannot use docker or podman'\n        exit 1\n    fi\n}\n\nset_directory() {\n    # posixly parse first occurrence of -C dir/--directory dir/--directory=dir\n    # NOTE: consider removing the first directory argument and bind-mounting\n    # `$directory` instead of `$PWD` in the future - its slightly less tolerant\n    # but more consistent\n    while getopts \":C:-:\" o; do\n        # NOTE: we're passing unknown arguments through\n        # shellcheck disable=SC2220\n        case \"$o\" in\n            C) directory=\"$OPTARG\"; break;;\n            -) [ $OPTIND -ge 1 ] && optind=$((OPTIND - 1)) || optind=$OPTIND\n               eval option=\"\\$$optind\"\n               if [ \"${option#*=}\" != \"$option\" ]; then\n                   # --option=arg style\n                   optarg=$(echo \"$option\" | cut -d '=' -f 2)\n                   option=$(echo \"$option\" | cut -d '=' -f 1)\n                   if [ \"$option\" = '--directory' ]; then\n                       directory=\"$optarg\"\n                       break\n                   fi\n               else\n                   # --option arg style\n                   if [ \"$option\" = '--directory' ]; then\n                       optind=$((optind + 1))\n                       eval directory=\"\\$$optind\"\n                       break\n                   fi\n               fi;;\n        esac\n    done\n\n    if [ -z \"$directory\" ]; then\n        directory=\"$PWD\"\n    else\n        if [ -d \"$directory\" ]; then\n            directory=$(cd \"$directory\" && echo \"$PWD\")\n        else\n            log error \"'$directory' is not a directory\"\n            exit 1\n        fi\n    fi\n}\n\nset_dockerfiles() {\n    if [ -d \"$CAKE_DOCKERFILES\" ]; then\n        dockerfiles=$(find \"$CAKE_DOCKERFILES\" \\( -name Dockerfile -o -name '*.dockerfile' \\))\n    else\n        dockerfiles=\"${CAKE_DOCKERFILES:-${directory}/Dockerfile}\"\n    fi\n}\n\nrun_command() {\n    set_runtime\n    set_directory \"$@\"\n    set_dockerfiles\n\n    for dockerfile in $dockerfiles; do\n        # NOTE: it's not enough to just use the `$dockerfile` as a different build\n        # context could result in a different container\n        checksum=$(echo \"${directory}\" \"${dockerfile}\" | cksum | cut -d ' ' -f 1)\n        basename=$(basename \"${directory}\")\n        container=\"cake-${basename}-${checksum}\"\n\n        log info \"running 'make $*'\"\n        log info \"in container '$container'\"\n        log info \"using Dockerfile '$dockerfile'\"\n        log info \"in directory '$directory'\"\n\n        if [ -t 1 ]; then\n            tty_args='-it'\n        fi\n\n        if \"$runtime\" build -t \"$container\" -f \"$dockerfile\" \"$directory\" > /dev/null; then\n            # NOTE: we want `$CAKE_RUNTIME_ARGS` to be split and no $tty_args to be ignored\n            # shellcheck disable=SC2086\n            \"$runtime\" run -v \"${PWD}:${PWD}\" -w \"$PWD\" --rm \\\n                $tty_args $CAKE_RUNTIME_ARGS \\\n                \"$container\" make \"$@\"\n        fi\n    done\n}\n\nrun_command \"$@\"\n"
  },
  {
    "path": "subdir/Dockerfile",
    "content": "FROM alpine\n\nRUN apk add --no-cache make\n"
  },
  {
    "path": "subdir/Makefile",
    "content": ".PHONY=cat\n\ncat:\n\tcat /etc/os-release\n"
  },
  {
    "path": "subdir/example.dockerfile",
    "content": "FROM alpine\n\nRUN apk add --no-cache make\n"
  }
]