<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Oz Tiram</title>
    <description>The latest articles on Forem by Oz Tiram (@oz123).</description>
    <link>https://forem.com/oz123</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F113372%2F03975c71-eeb8-4e2b-918b-34b229dd84d8.jpeg</url>
      <title>Forem: Oz Tiram</title>
      <link>https://forem.com/oz123</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/oz123"/>
    <language>en</language>
    <item>
      <title>Documenting Makefiles for DevOps teams and Software developmes using mh</title>
      <dc:creator>Oz Tiram</dc:creator>
      <pubDate>Thu, 25 Jul 2024 20:31:50 +0000</pubDate>
      <link>https://forem.com/oz123/documenting-makefiles-for-devops-teams-and-software-developmes-using-mh-13d2</link>
      <guid>https://forem.com/oz123/documenting-makefiles-for-devops-teams-and-software-developmes-using-mh-13d2</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcj692hhxpceyax8313tr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcj692hhxpceyax8313tr.png" alt="mh docs"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Modern tech stacks are complex and require one to remember hundreds of combinations of commands and parameters.&lt;br&gt;
As a full stack developer, I use many different tools in the command line.&lt;/p&gt;

&lt;p&gt;To name a few: go, python, sass, docker, terraform, kubectl, awscli, eksctl and more. &lt;/p&gt;

&lt;p&gt;Remembering the all the tasks requires some brain muscle, and context. Jumping from one project to another, I need to answer many questions before being able to start working on it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;do I push the build docker image to Y?&lt;/li&gt;
&lt;li&gt;Does this project use &lt;code&gt;webpack&lt;/code&gt; or &lt;code&gt;rollup&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;Does it use pipenv or poetry?&lt;/li&gt;
&lt;li&gt;Do I type yarn or npm to install js dependencies?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Jumping across teams, or coming back to a certain project after a while, becomes really hard.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Makefile&lt;/code&gt;s offer a really nice abstraction to all of these command and tools.&lt;/p&gt;

&lt;p&gt;For example, instead of typing:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

$ docker build -t $(git describe --always)-f  Dockerfile .


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;I can now just write &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

$ make build-img


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This not only saves time restructuring the correct command, it requires less typing, and prevents mistakes. It is also great for teams with various skill levels.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;make&lt;/code&gt; is a very old and boring technology. It is battle tested, and people already wrote mountains of words on &lt;a href="https://dev.to/matthewepler/using-makefiles-to-automate-workflows-acd"&gt;why you should adopt &lt;code&gt;Makefile&lt;/code&gt;s&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So, after you adopted &lt;code&gt;make&lt;/code&gt;, you now have a few &lt;code&gt;Makefile&lt;/code&gt;s, and when you come into a project you just need to read the &lt;code&gt;Makefile&lt;/code&gt; to see what it is doing, right?&lt;/p&gt;

&lt;p&gt;Well... not so fast...&lt;/p&gt;

&lt;p&gt;Unfortunately, &lt;code&gt;Makefile&lt;/code&gt;s have very cryptic syntax if you are not used to them. Also, they can be mixed with inline shell, and finally they can have a few hundred lines of code.&lt;br&gt;
Hence, understanding all the goals (build targets) is going to be tedious.&lt;br&gt;&lt;br&gt;
Some people already came with a good solution to this problem by integrating an &lt;code&gt;awk&lt;/code&gt; script in the &lt;code&gt;Makefile&lt;/code&gt; to parse the targets in the file and print a useful overview (&lt;a href="https://dev.to/alexbender/makefile-with-help-message-3d4h"&gt;here is just one example out of many&lt;/a&gt;). &lt;/p&gt;

&lt;p&gt;For almost a decade, I used a similar &lt;code&gt;awk&lt;/code&gt; script embedded in my &lt;code&gt;Makefile&lt;/code&gt;. Adding color, some more bells and whistles. With time, I wanted even more from that script. Most importantly, I wanted to document variables which affect different targets. &lt;/p&gt;

&lt;p&gt;So, I rewrote it in Python, which seemed easier than &lt;code&gt;awk&lt;/code&gt;. When it grew to about 30 lines of code, I extracted it as a Python package called &lt;a href="https://pypi.org/project/make-help-helper/" rel="noopener noreferrer"&gt;make-help-helper&lt;/a&gt;.&lt;br&gt;
As much as I love Python, building and distributing binaries for this &lt;code&gt;Makefile&lt;/code&gt; helper was not so easy, and letting people copying an inline script from one &lt;code&gt;Makefile&lt;/code&gt; to another seemed like a very bad way to distribute software ...&lt;/p&gt;

&lt;h2&gt;
  
  
  enter mh
&lt;/h2&gt;

&lt;p&gt;With the limited scoped of this script, I decided it was a good and fun project to exercise my C programming skills. And so the program &lt;code&gt;mh&lt;/code&gt; was born. In the spirit of &lt;a href="https://unix.stackexchange.com/a/440140" rel="noopener noreferrer"&gt;old UNIX programs, it has a two letter name&lt;/a&gt;, which stands for &lt;strong&gt;m&lt;/strong&gt;akes &lt;strong&gt;h&lt;/strong&gt;elp&lt;br&gt;
I'm using it across multiple projects for more than 3 years, and recently I gave it some more polish, and decided to release it to the world, in the hope more people will find it useful.  &lt;/p&gt;

&lt;p&gt;So without further ado, here is a demo of what &lt;code&gt;mh&lt;/code&gt; does.&lt;br&gt;
Running &lt;code&gt;make&lt;/code&gt; or &lt;code&gt;make help&lt;/code&gt; in a project directory with a specially crafted &lt;code&gt;Makefile&lt;/code&gt; you will see a colored output similar to this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foyovj07q7pb4ixa7qyag.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foyovj07q7pb4ixa7qyag.png" alt="mh project output"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The above output was generated from a &lt;code&gt;Makefile&lt;/code&gt; with the following content:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;

&lt;span class="nl"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;test coverage all watch clean docker-build docker-run save cp-to-server &lt;/span&gt;\
&lt;span class="nf"&gt;   import-on-server run-on-server disable-on-server&lt;/span&gt;

&lt;span class="nv"&gt;SHELL&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; /bin/bash
&lt;span class="nv"&gt;.DEFAULT_GOAL&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;help&lt;/span&gt;

&lt;span class="nl"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;help&lt;/span&gt;
&lt;span class="nl"&gt;help&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="p"&gt;@&lt;/span&gt;mh &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;MAKEFILE_LIST&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;target&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Please install mh from https://github.com/oz123/mh/releases/latest"&lt;/span&gt;
&lt;span class="k"&gt;ifndef&lt;/span&gt; &lt;span class="nv"&gt;target&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;which mh &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null 2&amp;gt;&amp;amp;1 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;Use &lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="s2"&gt;make help target=foo&lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="s2"&gt; to learn more about foo."&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;endif&lt;/span&gt;

&lt;span class="k"&gt;ifneq&lt;/span&gt; &lt;span class="nv"&gt;(,$(wildcard ./.env))&lt;/span&gt;
&lt;span class="k"&gt;include&lt;/span&gt;&lt;span class="sx"&gt; .env&lt;/span&gt;
    &lt;span class="err"&gt;export&lt;/span&gt;
&lt;span class="k"&gt;endif&lt;/span&gt;

&lt;span class="nl"&gt;run-server&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="c"&gt;##&lt;/span&gt;&lt;span class="nf"&gt; run a local server&lt;/span&gt;
    python3 sweet.py serve &lt;span class="nt"&gt;-p&lt;/span&gt; 8080 &amp;amp;

&lt;span class="nl"&gt;test&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="nf"&gt;run-server &lt;/span&gt;&lt;span class="c"&gt;##&lt;/span&gt;&lt;span class="nf"&gt; run the test suite with pytest&lt;/span&gt;
    python &lt;span class="nt"&gt;-m&lt;/span&gt; pytest

&lt;span class="nl"&gt;coverage&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="c"&gt;##&lt;/span&gt;&lt;span class="nf"&gt; run the tests and collect metrics&lt;/span&gt;
    python &lt;span class="nt"&gt;-m&lt;/span&gt; pytest &lt;span class="nt"&gt;-vv&lt;/span&gt; &lt;span class="nt"&gt;--cov&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;coldsweet tests

&lt;span class="nl"&gt;all&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;

&lt;span class="nl"&gt;update&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;CSS_FILES ?= ./static/stylesheets/all.scss:./static/stylesheets/all.css &lt;/span&gt;&lt;span class="c"&gt;#&lt;/span&gt;&lt;span class="nf"&gt;? the css files to update&lt;/span&gt;
&lt;span class="nl"&gt;update&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="c"&gt;##&lt;/span&gt;&lt;span class="nf"&gt; update css files from sass sources&lt;/span&gt;
    sass &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; compressed &lt;span class="nt"&gt;--update&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;CSS_FILES&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nl"&gt;watch&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="c"&gt;##&lt;/span&gt;&lt;span class="nf"&gt; rebuild css on changes to sass sources&lt;/span&gt;
    sass &lt;span class="nt"&gt;--watch&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;CSS_FILES&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nl"&gt;clean&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="c"&gt;##&lt;/span&gt;&lt;span class="nf"&gt; remove sass cache&lt;/span&gt;
    &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; ./.sass-cache

&lt;span class="nv"&gt;REGISTRY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;registry.acme.org  &lt;span class="c"&gt;#? the docker registry&lt;/span&gt;
&lt;span class="nv"&gt;ORG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;oz123   &lt;span class="c"&gt;#? the organization &lt;/span&gt;
&lt;span class="nv"&gt;IMG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;coldsweet   &lt;span class="c"&gt;#? the image name&lt;/span&gt;

&lt;span class="nl"&gt;docker-push&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="c"&gt;##&lt;/span&gt;&lt;span class="nf"&gt; push the built image to the repository&lt;/span&gt;
    docker push &lt;span class="p"&gt;$(&lt;/span&gt;REGISTRY&lt;span class="p"&gt;)&lt;/span&gt;/&lt;span class="p"&gt;$(&lt;/span&gt;ORG&lt;span class="p"&gt;)&lt;/span&gt;/&lt;span class="p"&gt;$(&lt;/span&gt;IMG&lt;span class="p"&gt;)&lt;/span&gt;:&lt;span class="p"&gt;$(&lt;/span&gt;shell git describe&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nl"&gt;docker-build&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="c"&gt;##&lt;/span&gt;&lt;span class="nf"&gt; build a docker image&lt;/span&gt;
    docker build &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;REGISTRY&lt;span class="p"&gt;)&lt;/span&gt;/&lt;span class="p"&gt;$(&lt;/span&gt;ORG&lt;span class="p"&gt;)&lt;/span&gt;/&lt;span class="p"&gt;$(&lt;/span&gt;IMG&lt;span class="p"&gt;)&lt;/span&gt;:&lt;span class="p"&gt;$(&lt;/span&gt;shell git describe&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; docker/Dockerfile-py312 .


&lt;span class="nl"&gt;docker-run&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;CMD ?= &lt;/span&gt;
&lt;span class="nl"&gt;docker-run&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;TAG ?= $(shell git describe)&lt;/span&gt;
&lt;span class="nl"&gt;docker-run&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="c"&gt;##&lt;/span&gt;&lt;span class="nf"&gt; run the docker image locally&lt;/span&gt;
    docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;COLDSWEET_DEBUG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;COLDSWEET_INSTALL_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/run/coldsweet &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;COLDSWEET_CONFIG_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/etc/coldsweet/config &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;CURDIR&lt;span class="p"&gt;)&lt;/span&gt;/coldsweet/templates:/run/coldsweet/coldsweet/templates &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:8080 &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 9001:9001 &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;CURDIR&lt;span class="p"&gt;)&lt;/span&gt;/data:/var/lib/coldsweet/db &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;CURDIR&lt;span class="p"&gt;)&lt;/span&gt;/docker/:/etc/coldsweet/ &lt;span class="nt"&gt;-w&lt;/span&gt; /run/coldsweet &lt;span class="p"&gt;$(&lt;/span&gt;REGISTRY&lt;span class="p"&gt;)&lt;/span&gt;/&lt;span class="p"&gt;$(&lt;/span&gt;ORG&lt;span class="p"&gt;)&lt;/span&gt;/&lt;span class="p"&gt;$(&lt;/span&gt;IMG&lt;span class="p"&gt;)&lt;/span&gt;:&lt;span class="p"&gt;$(&lt;/span&gt;TAG&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;CMD&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nl"&gt;docker-save&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="c"&gt;##&lt;/span&gt;&lt;span class="nf"&gt; saves the image to a localfile&lt;/span&gt;
    &lt;span class="nb"&gt;sudo &lt;/span&gt;docker save &lt;span class="p"&gt;$(&lt;/span&gt;ORG&lt;span class="p"&gt;)&lt;/span&gt;/&lt;span class="p"&gt;$(&lt;/span&gt;IMG&lt;span class="p"&gt;)&lt;/span&gt;:&lt;span class="p"&gt;$(&lt;/span&gt;TAG&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;ORG&lt;span class="p"&gt;)&lt;/span&gt;.&lt;span class="p"&gt;$(&lt;/span&gt;IMG&lt;span class="p"&gt;)&lt;/span&gt;.&lt;span class="p"&gt;$(&lt;/span&gt;TAG&lt;span class="p"&gt;)&lt;/span&gt;.img

&lt;span class="nl"&gt;deploy-k8s&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    make &lt;span class="nt"&gt;-C&lt;/span&gt; k8s apply

&lt;span class="nl"&gt;cp-to-server&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="c"&gt;##&lt;/span&gt;&lt;span class="nf"&gt; upload the image to a server&lt;/span&gt;
    scp &lt;span class="p"&gt;$(&lt;/span&gt;ORG&lt;span class="p"&gt;)&lt;/span&gt;.&lt;span class="p"&gt;$(&lt;/span&gt;IMG&lt;span class="p"&gt;)&lt;/span&gt;.&lt;span class="p"&gt;$(&lt;/span&gt;TAG&lt;span class="p"&gt;)&lt;/span&gt;.img &lt;span class="p"&gt;$(&lt;/span&gt;SERVER&lt;span class="p"&gt;)&lt;/span&gt;:~/


&lt;span class="nl"&gt;import-on-server&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="c"&gt;##&lt;/span&gt;&lt;span class="nf"&gt; imports the image on the serve&lt;/span&gt;
    ssh &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;SERVER&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"echo &lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;SUDOPASSWORD&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; | sudo -S docker load --input  &lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;ORG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;IMG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;TAG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;.img"&lt;/span&gt;

&lt;span class="nl"&gt;run-on-server&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="c"&gt;##&lt;/span&gt;&lt;span class="nf"&gt; runs the docker image on a remote server&lt;/span&gt;
    ssh &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;SERVER&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"echo &lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;SUDOPASSWORD&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; | sudo -S docker run --name &lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;CONTAINERNAME&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
        -d --restart always&lt;/span&gt;
        &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="s2"&gt;v &lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;BASEDIR&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;:/var/lib/coldsweet/db &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
        -p &lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;HOST&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;:9001:9001 &lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;ORG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;IMG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;TAG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nl"&gt;disable-on-server&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="c"&gt;##&lt;/span&gt;&lt;span class="nf"&gt; stops the container on a remote server&lt;/span&gt;
    ssh &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;SERVER&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"echo &lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;SUDOPASSWORD&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; | sudo -S docker rm --force &lt;/span&gt;&lt;span class="p"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;CONTAINERNAME&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nl"&gt;compile-sass&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;cd &lt;/span&gt;static/stylesheets &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; sassc all.scss all.css

&lt;span class="nl"&gt;minify-sass&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;cd &lt;/span&gt;static/stylesheets &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; sassc &lt;span class="nt"&gt;--sourcemap&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; compressed all.scss all.min.css


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The biggest addition to the simple AWK script or the Python package is the ability to document global variables and target local variables.&lt;/p&gt;

&lt;p&gt;Targets are documented with:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;

&lt;span class="nl"&gt;foo&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="c"&gt;##&lt;/span&gt;&lt;span class="nf"&gt; foo does bar&lt;/span&gt;
   &lt;span class="err"&gt;echo&lt;/span&gt; &lt;span class="err"&gt;bar&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;local variables are documented with:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;

&lt;span class="nv"&gt;NAME&lt;/span&gt; &lt;span class="o"&gt;?=&lt;/span&gt; world &lt;span class="c"&gt;#? great who&lt;/span&gt;
&lt;span class="nl"&gt;greet&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
     &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="p"&gt;$(&lt;/span&gt;HELLO&lt;span class="p"&gt;)&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;global variables are documented with:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;

&lt;span class="nv"&gt;REGISTRY&lt;/span&gt; &lt;span class="o"&gt;?=&lt;/span&gt; docker.io &lt;span class="c"&gt;#? where should images be pushed&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In addition to having global variables inside the &lt;code&gt;Makefile&lt;/code&gt;, I found it useful to have the Makefile read variables from a &lt;code&gt;.env&lt;/code&gt; a la &lt;a href="https://12factor.net/" rel="noopener noreferrer"&gt;12-Factor applications&lt;/a&gt;.&lt;br&gt;
That is done by the following code in the above &lt;code&gt;Makefile&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;

&lt;span class="k"&gt;ifneq&lt;/span&gt; &lt;span class="nv"&gt;(,$(wildcard ./.env))&lt;/span&gt;
&lt;span class="k"&gt;include&lt;/span&gt;&lt;span class="sx"&gt; .env&lt;/span&gt;
    &lt;span class="err"&gt;export&lt;/span&gt;
&lt;span class="k"&gt;endif&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now, if your directory contains a &lt;code&gt;.env&lt;/code&gt; file, &lt;code&gt;mh&lt;/code&gt; will read the variables and their description and show them on the console:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8m7tifxetoiw2xwsonug.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8m7tifxetoiw2xwsonug.png" alt="show .env vars and description"&gt;&lt;/a&gt;&lt;br&gt;
Usually, my repositories will contain a file called &lt;code&gt;dot.envexample&lt;/code&gt;, which will show other developers what can go into this file.&lt;br&gt;
The format which is understood by &lt;code&gt;mh&lt;/code&gt; is:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

FOO=BAR #? control the foo
MY_SECRET_API_KEY=123456 #? the secret api key


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You can learn more about a specific target with &lt;code&gt;make help target=&amp;lt;name&amp;gt;&lt;/code&gt;, for example:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhgg0brw837ieo2ca3xvh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhgg0brw837ieo2ca3xvh.png" alt="target help"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you found it so far useful, you can get a version of &lt;code&gt;mh&lt;/code&gt; as a &lt;a href="https://github.com/oz123/mh/releases/latest" rel="noopener noreferrer"&gt;statically compiled binary for Linux or Dynamically compiled Mac OSX binary in the GitHub release page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Of course, you can &lt;a href="https://github.com/oz123/mh/" rel="noopener noreferrer"&gt;study the code&lt;/a&gt;, or provide &lt;a href="https://github.com/oz123/mh/issues" rel="noopener noreferrer"&gt;feedback&lt;/a&gt; and &lt;a href="https://github.com/oz123/mh/pulls" rel="noopener noreferrer"&gt;contributions&lt;/a&gt; too.&lt;/p&gt;

</description>
      <category>makefile</category>
      <category>software</category>
      <category>development</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
