<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>make on Kshitij Patil</title><link>https://ksh.fyi/tags/make/</link><description>Recent content in make on Kshitij Patil</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Sat, 03 Oct 2026 20:40:00 +0530</lastBuildDate><atom:link href="https://ksh.fyi/tags/make/index.xml" rel="self" type="application/rss+xml"/><item><title>Makefile basics for a small C project</title><link>https://ksh.fyi/notes/makefile-basics/</link><pubDate>Sat, 03 Oct 2026 20:40:00 +0530</pubDate><guid>https://ksh.fyi/notes/makefile-basics/</guid><description>&lt;p&gt;The Makefile for a one-binary C project, explained piece by piece.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-make" data-lang="make"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;CC&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; gcc
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;CFLAGS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; -std&lt;span class="o"&gt;=&lt;/span&gt;gnu23 -Og -g3 -Wall -Werror -Wextra -Wwrite-strings
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;OUT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; out
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;BIN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;$(&lt;/span&gt;OUT&lt;span class="k"&gt;)&lt;/span&gt;/tinybox
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;BIN&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Any out/&amp;lt;name&amp;gt; from &amp;lt;name&amp;gt;.c, e.g. `make out/scratch`
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;$(OUT)/%&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; %.&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="n"&gt;Makefile&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;OUT&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;$(&lt;/span&gt;CC&lt;span class="k"&gt;)&lt;/span&gt; &lt;span class="k"&gt;$(&lt;/span&gt;CFLAGS&lt;span class="k"&gt;)&lt;/span&gt; -o &lt;span class="nv"&gt;$@&lt;/span&gt; $&amp;lt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;$(OUT)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mkdir -p &lt;span class="nv"&gt;$@&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;tinybox&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;BIN&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;scratch&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;OUT&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;/&lt;span class="n"&gt;scratch&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;BIN&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ./&lt;span class="k"&gt;$(&lt;/span&gt;BIN&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;clean&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; rm -rf &lt;span class="k"&gt;$(&lt;/span&gt;OUT&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="n"&gt;clean&lt;/span&gt; &lt;span class="n"&gt;tinybox&lt;/span&gt; &lt;span class="n"&gt;scratch&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="rules"&gt;Rules&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-make" data-lang="make"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;target&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prerequisites&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; recipe
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;make rebuilds &lt;code&gt;target&lt;/code&gt; if it doesn&amp;rsquo;t exist or &lt;strong&gt;any prerequisite is newer&lt;/strong&gt;
(by file modification time). That&amp;rsquo;s the whole algorithm.&lt;/li&gt;
&lt;li&gt;The recipe lines must start with a &lt;strong&gt;tab&lt;/strong&gt;. Spaces give
&lt;code&gt;*** missing separator. Stop.&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;A rule with no recipe (&lt;code&gt;tinybox: $(BIN)&lt;/code&gt;) just says &amp;ldquo;to make this, make that&amp;rdquo;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="variables"&gt;Variables&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;CC&lt;/code&gt;, &lt;code&gt;CFLAGS&lt;/code&gt; are conventional names (make&amp;rsquo;s built-in rules use them too).
Use with &lt;code&gt;$(NAME)&lt;/code&gt;. Override from the command line: &lt;code&gt;make CFLAGS=-O2&lt;/code&gt;.&lt;/p&gt;</description><content:encoded><![CDATA[<p>The Makefile for a one-binary C project, explained piece by piece.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-make" data-lang="make"><span class="line"><span class="cl"><span class="nv">CC</span>     <span class="o">=</span> gcc
</span></span><span class="line"><span class="cl"><span class="nv">CFLAGS</span> <span class="o">=</span> -std<span class="o">=</span>gnu23 -Og -g3 -Wall -Werror -Wextra -Wwrite-strings
</span></span><span class="line"><span class="cl"><span class="nv">OUT</span>    <span class="o">=</span> out
</span></span><span class="line"><span class="cl"><span class="nv">BIN</span>    <span class="o">=</span> <span class="k">$(</span>OUT<span class="k">)</span>/tinybox
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">all</span><span class="o">:</span> <span class="k">$(</span><span class="nv">BIN</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c"># Any out/&lt;name&gt; from &lt;name&gt;.c, e.g. `make out/scratch`
</span></span></span><span class="line"><span class="cl"><span class="nf">$(OUT)/%</span><span class="o">:</span> %.<span class="n">c</span> <span class="n">Makefile</span> <span class="p">|</span> <span class="k">$(</span><span class="nv">OUT</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">	<span class="k">$(</span>CC<span class="k">)</span> <span class="k">$(</span>CFLAGS<span class="k">)</span> -o <span class="nv">$@</span> $&lt;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">$(OUT)</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">	mkdir -p <span class="nv">$@</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">tinybox</span><span class="o">:</span> <span class="k">$(</span><span class="nv">BIN</span><span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">scratch</span><span class="o">:</span> <span class="k">$(</span><span class="nv">OUT</span><span class="k">)</span>/<span class="n">scratch</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">run</span><span class="o">:</span> <span class="k">$(</span><span class="nv">BIN</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">	./<span class="k">$(</span>BIN<span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">clean</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">	rm -rf <span class="k">$(</span>OUT<span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">.PHONY</span><span class="o">:</span> <span class="n">all</span> <span class="n">run</span> <span class="n">clean</span> <span class="n">tinybox</span> <span class="n">scratch</span>
</span></span></code></pre></div><h2 id="rules">Rules</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-make" data-lang="make"><span class="line"><span class="cl"><span class="nf">target</span><span class="o">:</span> <span class="n">prerequisites</span>
</span></span><span class="line"><span class="cl">	recipe
</span></span></code></pre></div><ul>
<li>make rebuilds <code>target</code> if it doesn&rsquo;t exist or <strong>any prerequisite is newer</strong>
(by file modification time). That&rsquo;s the whole algorithm.</li>
<li>The recipe lines must start with a <strong>tab</strong>. Spaces give
<code>*** missing separator.  Stop.</code></li>
<li>A rule with no recipe (<code>tinybox: $(BIN)</code>) just says &ldquo;to make this, make that&rdquo;.</li>
</ul>
<h2 id="variables">Variables</h2>
<p><code>CC</code>, <code>CFLAGS</code> are conventional names (make&rsquo;s built-in rules use them too).
Use with <code>$(NAME)</code>. Override from the command line: <code>make CFLAGS=-O2</code>.</p>
<h2 id="automatic-variables">Automatic variables</h2>
<p>Inside a recipe:</p>
<ul>
<li><code>$@</code>: the target (<code>out/tinybox</code>)</li>
<li><code>$&lt;</code>: the <strong>first</strong> prerequisite (<code>tinybox.c</code>)</li>
<li><code>$^</code>: all prerequisites</li>
</ul>
<h2 id="the-default-target-and-why-its-called-all">The default target, and why it&rsquo;s called <code>all</code></h2>
<p>Plain <code>make</code> builds the <strong>first</strong> target in the file, whatever its name.
Calling it <code>all</code> is a convention (GNU Coding Standards), not a requirement.</p>
<h2 id="pattern-rules">Pattern rules</h2>
<p><code>$(OUT)/%: %.c</code> means &ldquo;<code>out/&lt;anything&gt;</code> is built from <code>&lt;anything&gt;.c</code>&rdquo;. One
rule covers <code>out/tinybox</code>, <code>out/scratch</code>, and any future file. Some IDEs
(e.g. CLion) only list named targets, which is why <code>tinybox:</code> and <code>scratch:</code>
exist as aliases.</p>
<h2 id="the-makefile-as-a-prerequisite">The Makefile as a prerequisite</h2>
<p>If only <code>tinybox.c</code> is listed, changing <code>CFLAGS</code> doesn&rsquo;t trigger a rebuild:
make says <code>Nothing to be done</code> and your new warning flag silently does
nothing. Listing <code>Makefile</code> fixes that. (<code>$&lt;</code> still picks <code>tinybox.c</code>,
since it&rsquo;s first.)</p>
<h2 id="order-only-prerequisites--out">Order-only prerequisites: <code>| $(OUT)</code></h2>
<p>Everything after <code>|</code> must <strong>exist</strong> before the recipe runs, but its timestamp
is ignored. That matters for directories: a directory&rsquo;s mtime changes
whenever a file inside it is added or removed, so as a normal prerequisite it
would make targets look out of date constantly.</p>
<h2 id="phony"><code>.PHONY</code></h2>
<p>Marks targets that are names, not files. Without it:</p>
<ul>
<li>A file called <code>clean</code> in the directory would make <code>make clean</code> think it&rsquo;s
up to date.</li>
<li>make goes looking for <strong>built-in rules</strong>. It has one for <code>foo</code> from
<code>foo.c</code>, so without <code>.PHONY</code>, <code>make tinybox</code> would build <code>out/tinybox</code> and
<em>then</em> run <code>gcc $(CFLAGS) tinybox.c out/tinybox -o tinybox</code>, building a stray
<code>./tinybox</code> and feeding the binary in as an extra input. Phony targets skip
the built-in rule search.</li>
</ul>
<p>(Fun fact: with no Makefile at all, <code>make scratch</code> works: the built-in rule
runs <code>cc scratch.c -o scratch</code>.)</p>
<h2 id="handy-flags">Handy flags</h2>
<ul>
<li><code>make -n</code>: dry run, print the commands without running them</li>
<li><code>make -B</code>: rebuild everything regardless of timestamps</li>
<li><code>make -p</code>: dump the database, including all built-in rules</li>
</ul>
<p>References: <code>info make</code> (the GNU make manual), especially &ldquo;Rule Syntax&rdquo;,
&ldquo;Pattern Rules&rdquo;, &ldquo;Automatic Variables&rdquo;, &ldquo;Phony Targets&rdquo;, &ldquo;Prerequisite Types&rdquo;.</p>
]]></content:encoded></item></channel></rss>