<?xml version="1.0" encoding="UTF-8"?><feed xmlns="http://www.w3.org/2005/Atom"><id>https://vic.demuzere.be/articles/tags/development/atom.xml</id><title>Vic Demuzere - Development</title><link href="https://vic.demuzere.be/articles/tags/development/atom.xml" rel="self" type="application/atom+xml" /><link href="https://vic.demuzere.be/articles/tags/development/" rel="alternate" type="text/html" /><updated>2026-05-14T13:41:51+00:00</updated><author><name>Vic Demuzere</name></author><entry><id>https://vic.demuzere.be/articles/bots-crawlers-and-geoblocks/</id><title>Bots are dominating the web</title><link href="https://vic.demuzere.be/articles/bots-crawlers-and-geoblocks/" rel="alternate" /><updated>2026-05-14T13:41:51+00:00</updated><published>2026-05-14T13:41:51+00:00</published><content type="html"><![CDATA[<p>The last few years, web traffic from bots has increased rapidly.
Some sources<sup class="footnote-ref"><a href="#fn-1" id="fnref-1" data-footnote-ref>1</a></sup> claim that automated activity now accounts for over half of all web traffic.
This is even more pronounced on small websites like mine, as I don't receive many human visitors.</p>
<p>The table below shows traffic on this website for the last few days:</p>
<table>
<thead>
<tr>
<th align="center"></th>
<th>Path</th>
<th align="right">Hits</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">1</td>
<td>/wp-login.php</td>
<td align="right">2751</td>
</tr>
<tr>
<td align="center">2</td>
<td>/wp-admin/index.php</td>
<td align="right">2707</td>
</tr>
<tr>
<td align="center">3</td>
<td>/wp-admin/edit.php</td>
<td align="right">2354</td>
</tr>
<tr>
<td align="center">4</td>
<td>/wp-admin/profile.php</td>
<td align="right">2353</td>
</tr>
<tr>
<td align="center">5</td>
<td>/wp-admin/plugins.php</td>
<td align="right">2351</td>
</tr>
<tr>
<td align="center">6</td>
<td>/robots.txt</td>
<td align="right">1131</td>
</tr>
<tr>
<td align="center">7</td>
<td>/</td>
<td align="right">994</td>
</tr>
<tr>
<td align="center">8</td>
<td>/articles/feed.rss</td>
<td align="right">989</td>
</tr>
<tr>
<td align="center">9</td>
<td>/articles/how-i-almost-lost-my-backups/</td>
<td align="right">314</td>
</tr>
<tr>
<td align="center">10</td>
<td>/wp-content/themes/vic/style.css</td>
<td align="right">299</td>
</tr>
</tbody>
</table>
<p>This clearly shows two things:</p>
<ul>
<li>The first useful page for a real visitor is in sixth place<sup class="footnote-ref"><a href="#fn-2" id="fnref-2" data-footnote-ref>2</a></sup>.</li>
<li>My stylesheet has a low number of hits compared to the frontpage and my most recently published article.
One would expect each unique visitor to download the stylesheet at least once.</li>
</ul>
<p>That is to say, my own statistics show that I'm mainly serving our robot overlords.</p>
<p>The situation is worse on other kinds of web apps, like my <a href="https://forgejo.org/" title="Forgejo is a self-hosted lightweight software forge.">Forgejo</a> instance.
It's a single-user instance, and most projects are private, but I do publish a bunch of open-source projects there.
Recent bot traffic on applications like these is causing significant load on my server.</p>
<h2 id="keeping-the-bots-out">Keeping the bots out<a href="#keeping-the-bots-out" aria-label="Link to heading 'Keeping the bots out'" data-heading-content="Keeping the bots out" class="anchor"></a></h2>
<p>Previously, the majority of crawlers were relatively decent.
They would crawl your website slowly, making sure not to cause too much load.
Their main purpose was serving search results to people looking for the content you publish.
Bringing down your website would only result in their own users landing on error pages.</p>
<p>The recent <a href="https://en.wikipedia.org/wiki/Generative_AI" title="Generative AI on Wikipedia">Generative AI</a> (GenAI) craze changed this completely.
Multiple companies are now racing to collect as much data as possible.
They no longer care what happens with the websites they're crawling.
Once they have collected enough content, their GenAI agents provide users with answers directly.
The source website is treated as irrelevant.
Obviously, as a website owner, I do not want these thankless robots on my property<sup class="footnote-ref"><a href="#fn-3" id="fnref-3" data-footnote-ref>3</a></sup>!</p>
<p>So I went through the standard ways of blocking them:</p>
<ol>
<li>Asking nicely in <a href="https://www.robotstxt.org/" title="The Web Robots Pages"><code>robots.txt</code></a>.</li>
<li>Denying requests based on the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/User-Agent" title="User-Agent HTTP header on Mozilla Developer Network">User-Agent</a> header.</li>
<li>Blocking IP ranges from known data centers and cloud providers.</li>
</ol>
<p>None of these methods work.</p>
<p>GenAI companies frequently ignore <code>robots.txt</code> files.
They also usually disguise themselves as normal browsers instead of identifying as crawlers.
More recently, I've also noticed them increasingly using consumer IP space.
Combined with rapidly changing addresses, this makes it impossible to block them by IP address alone.</p>
<p>There are some promising new tools like <a href="https://anubis.techaro.lol/" title="Anubis">Anubis</a> that work<sup class="footnote-ref"><a href="#fn-4" id="fnref-4" data-footnote-ref>4</a></sup>, for now.</p>
<h2 id="giving-up">Giving up<a href="#giving-up" aria-label="Link to heading 'Giving up'" data-heading-content="Giving up" class="anchor"></a></h2>
<p>Making sure websites stay online is part of my job.
I don't want to have to go through the same hassle for my own web apps when I get home.
I'm tired, and the few things I'm hosting just aren't worth it.</p>
<p>So, I decided to take two different approaches:</p>
<ol>
<li>
<p>Websites that are <strong>meant to be public</strong>, like the one you're reading now.
I've always optimized these for high traffic, and will continue to do so.
These are built to withstand much more than the AI crawlers I'm currently seeing.
<strong>I'm no longer blocking any bots on public websites.</strong></p>
</li>
<li>
<p>Websites (and web apps) that are <strong>primarily meant for myself</strong>.
These are <strong>now geoblocked</strong> and only accept traffic from Belgium.
As these are mainly open source applications that I don't maintain myself, they are harder to optimize.
This includes my <a href="https://forgejo.org/" title="Forgejo is a self-hosted lightweight software forge.">Forgejo</a> instance.
It's sad that part of my open source code disappears behind a firewall.
I hope to open it up again once AI companies start behaving more responsibly.</p>
</li>
</ol>
<p>My geoblock page contains contact information.
So if people <em>really want to go through the hassle</em> of requesting access (which I very much doubt), they can be allowed in.</p>
<section class="footnotes" data-footnotes>
<ol>
<li id="fn-1">
<p>Every article I've found claiming this reads like marketing material for a web security firm.
For that reason, I decided not to link to any of them.
Based on my own traffic stats though, I don't doubt their numbers. <a href="#fnref-1" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="1" aria-label="Back to reference 1">↩</a></p>
</li>
<li id="fn-2">
<p>I do read robots.txt on some sites I visit.
It's a treasure trove of information about the website you're visiting.
But that's probably not normal human behaviour. <a href="#fnref-2" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="2" aria-label="Back to reference 2">↩</a></p>
</li>
<li id="fn-3">
<p>I don't really mind AI crawlers, as long as they behave.
They usually don't. <a href="#fnref-3" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="3" aria-label="Back to reference 3">↩</a></p>
</li>
<li id="fn-4">
<p>Anubis is great!
Especially since it lets through the bots that clearly identify as a bot. <a href="#fnref-4" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="4" aria-label="Back to reference 4">↩</a></p>
</li>
</ol>
</section>
<div class="footer"><hr><p>Thanks for subscribing to my feed!</p><p>Feel free to e-mail comments or corrections to <a href="mailto:vic@demuzere.be?subject=RE%3A%20Bots%20are%20dominating%20the%20web">vic@demuzere.be</a>.</p></div>]]></content></entry><entry><id>https://vic.demuzere.be/articles/generating-uuids-in-sqlite/</id><title>Generating UUIDs in SQLite</title><link href="https://vic.demuzere.be/articles/generating-uuids-in-sqlite/" rel="alternate" /><updated>2026-01-04T17:26:44+00:00</updated><published>2025-12-24T15:28:41+00:00</published><content type="html"><![CDATA[<p>SQLite does not have built-in UUID functions, but by getting creative with the <code>randomblob(N)</code> function we can generate version 4 (random) UUIDs.</p>
<p>Before we continue, it's important to note that you should verify whether SQLite's random functions are adequate for your usecase.
They may not return truly random data.
However, as <a href="https://www.rfc-editor.org/rfc/rfc9562.html#name-uuid-format" title="RFC 9562 section 4">the RFC</a> notes, &quot;they (UUIDs) MUST NOT be used as security capabilities&quot;.
And since we all follow that to the letter, it doesn't really matter.. right?</p>
<p>The <a href="https://sqlite.org/lang_corefunc.html#randomblob" title="randomblob(N) function at SQLite manual">excellent SQLite manual</a> gives us a starting point:</p>
<blockquote>
<p>Hint: applications can generate globally unique identifiers using this function together with hex() and/or lower().</p>
</blockquote>
<p>Combining the <code>randomblob(N)</code> function with <code>hex(N)</code> does indeed create something that vaguely resembles a UUID.
For some purposes this may suffice, but the result isn't actually a valid UUID<sup class="footnote-ref"><a href="#fn-1" id="fnref-1" data-footnote-ref>1</a></sup>:
valid UUIDs must have <a href="https://www.rfc-editor.org/rfc/rfc9562.html#name-uuid-format" title="RFC 9562 section 4">specific bits set</a> to indicate their version and variant.</p>
<p>We can do better!</p>
<h2 id="as-binary-blob">As binary blob<a href="#as-binary-blob" aria-label="Link to heading 'As binary blob'" data-heading-content="As binary blob" class="anchor"></a></h2>
<p>The binary representation of a version 4 UUID consists of:</p>
<ul>
<li>48 bits (6 bytes) random data</li>
<li>4 bits (0.5 bytes) <a href="https://www.rfc-editor.org/rfc/rfc9562.html#name-version-field" title="RFC 9562 section 4.2: Version Field">version field</a></li>
<li>6 bits (1.5 bytes) random data</li>
<li>4 bits (0.5 bytes) <a href="https://www.rfc-editor.org/rfc/rfc9562.html#name-variant-field" title="RFC 9562 section 4.1: Variant Field">variant field</a></li>
<li>60 bits (7.5 bytes) random data</li>
</ul>
<p>As far as I know, we can't modify individual bytes in a blob using the available functions in SQLite.
However, we do have access to a lot of string functions.
Taking a detour using HEX encoding provides a solution.
We'll first concatenate a HEX string containing the data we want, then convert it back to a binary blob.
Note that each byte is represented by two hexadecimal characters.</p>
<p>The first 48 random bits are easy:</p>
<pre><code class="language-sqlite">sqlite&gt; select HEX(RANDOMBLOB(6));
4004714237AF
</code></pre>
<p>The first 4 bits of the next byte need to be set to <code>0100</code> (or <code>4</code> in hex).
As every byte is represented by two characters in hexadecimal, we can just use a literal <code>4</code> for the next character.</p>
<pre><code class="language-sqlite">sqlite&gt; select '4';
4
</code></pre>
<p>Next, we need 6 bits of random data.
We can only generate random bytes using <code>RANDOMBLOB(N)</code>, but we need 1.5 bytes.
Luckily, because we are working with a hexadecimal string, we can generate 2 bytes (4 hex characters) and take the first three characters.</p>
<pre><code>sqlite&gt; select HEX(RANDOMBLOB(2));
7B4A

sqlite&gt; select SUBSTR(HEX(RANDOMBLOB(2)),0,4);
5BD
</code></pre>
<p>The next one is a little more complex.
The <a href="https://www.rfc-editor.org/rfc/rfc9562.html#name-variant-field" title="RFC 9562 section 4.1: Variant Field">variant field</a> consists of a variable number of bits.
For our version 4 UUID, we need to set the first two bits to <code>10</code>, the others can be random.
That gives us a possible range between <code>1000</code> and <code>1011</code>, or a hexadecimal character between <code>8</code> and <code>B</code> inclusive.</p>
<pre><code>select FORMAT('%X', 8 + ABS(RANDOM() % 4));
A
</code></pre>
<p>After this, we need 60 more random bits.
Since this again includes a half byte again (7.5 bytes), we're doing the same <code>SUBSTR</code> trick as before:</p>
<pre><code>sqlite&gt; select SUBSTR(HEX(RANDOMBLOB(8)),0,16);
E83EF2136842130
</code></pre>
<h3 id="stitching-it-together">Stitching it together<a href="#stitching-it-together" aria-label="Link to heading 'Stitching it together'" data-heading-content="Stitching it together" class="anchor"></a></h3>
<p>Using <code>CONCAT(X, ...)</code> we create a single string:</p>
<pre><code>sqlite&gt; select CONCAT(
(x1...&gt;   HEX(RANDOMBLOB(6)),
(x1...&gt;   '4',
(x1...&gt;   SUBSTR(HEX(RANDOMBLOB(2)),0,4),
(x1...&gt;   FORMAT('%X', 8 + ABS(RANDOM() % 4)),
(x1...&gt;   SUBSTR(HEX(RANDOMBLOB(8)),0,16)
(x1...&gt; );

64DF281823BE47249BB2CA8D6450066B
</code></pre>
<p>Then use <code>UNHEX(X)</code> to convert it back into a binary blob:</p>
<pre><code>select UNHEX(CONCAT(
  HEX(RANDOMBLOB(6)),
  '4',
  SUBSTR(HEX(RANDOMBLOB(2)),0,4),
  FORMAT('%X', 8 + ABS(RANDOM() % 4)),
  SUBSTR(HEX(RANDOMBLOB(8)),0,16)
));
</code></pre>
<h3 id="generating-uuids-automatically">Generating UUIDs automatically<a href="#generating-uuids-automatically" aria-label="Link to heading 'Generating UUIDs automatically'" data-heading-content="Generating UUIDs automatically" class="anchor"></a></h3>
<p>The SQL above can be used as the default value for a blob column:</p>
<pre><code>CREATE TABLE &quot;animals&quot; (
  &quot;id&quot; INTEGER PRIMARY KEY NOT NULL,
  &quot;uuid&quot; BLOB UNIQUE NOT NULL
    DEFAULT (UNHEX(CONCAT(
      HEX(RANDOMBLOB(6)), '4',
      SUBSTR(HEX(RANDOMBLOB(2)),0,4),
      FORMAT('%X', 8 + ABS(RANDOM() % 4)),
      SUBSTR(HEX(RANDOMBLOB(8)),0,16))))
    UNIQUE CHECK(LENGTH(&quot;uuid&quot;) = 16),
   &quot;name&quot; TEXT NOT NULL
) STRICT;
</code></pre>
<p>This will generate a UUID if we don't specify one on insert:</p>
<pre><code>sqlite&gt; INSERT INTO &quot;animals&quot; (&quot;name&quot;) VALUES ('Fluffy');

sqlite&gt; SELECT * FROM &quot;animals&quot;;
1|[blob]|Fluffy

sqlite&gt; SELECT &quot;id&quot;, HEX(&quot;uuid&quot;), &quot;name&quot; FROM &quot;animals&quot;;
1|85671F7120F14AA6802B8B4E9213B8BE|Fluffy
</code></pre>
<h2 id="in-human-readable-hexadecimal-form">In human readable (hexadecimal) form<a href="#in-human-readable-hexadecimal-form" aria-label="Link to heading 'In human readable (hexadecimal) form'" data-heading-content="In human readable (hexadecimal) form" class="anchor"></a></h2>
<p>Depending on your use case, you <a href="https://www.rfc-editor.org/rfc/rfc9562.html#name-dbms-and-database-considera" title="RFC 9562 section 6.13: Database Considerations">may want to store UUIDs in a <code>TEXT</code> column</a>.</p>
<p>We were close to a &quot;hex-and-dash&quot; string format with the SQL above, until we call the <code>UNHEX(X)</code> function.
<a href="https://www.rfc-editor.org/rfc/rfc9562.html#name-uuid-format" title="RFC 9562 section 4">RFC 9562</a> specifies that dashes are required, so we'll add them in the right places.</p>
<pre><code>sqlite&gt; select CONCAT(
  HEX(RANDOMBLOB(4)),
  '-',
  HEX(RANDOMBLOB(2)),
  '-',
  '4',
  SUBSTR(HEX(RANDOMBLOB(2)),0,4),
  '-',
  FORMAT('%X', 8 + ABS(RANDOM() % 4)),
  SUBSTR(HEX(RANDOMBLOB(2)),0,4),
  '-',
  SUBSTR(HEX(RANDOMBLOB(8)),0,13)
);

199ED82A-3102-42DB-B59B-8C356B6F3FF7
</code></pre>
<p>Voila!</p>
<section class="footnotes" data-footnotes>
<ol>
<li id="fn-1">
<p>As technically correct is the best kind of correct: if you get <em>really</em> lucky with the generated random data, they might actually be valid. <a href="#fnref-1" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="1" aria-label="Back to reference 1">↩</a></p>
</li>
</ol>
</section>
<div class="footer"><hr><p>Thanks for subscribing to my feed!</p><p>Feel free to e-mail comments or corrections to <a href="mailto:vic@demuzere.be?subject=RE%3A%20Generating%20UUIDs%20in%20SQLite">vic@demuzere.be</a>.</p></div>]]></content></entry><entry><id>https://vic.demuzere.be/articles/printer-friendly-website/</id><title>Making your blog printer-friendly</title><link href="https://vic.demuzere.be/articles/printer-friendly-website/" rel="alternate" /><updated>2025-03-09T10:03:36+00:00</updated><published>2016-06-18T14:34:00+00:00</published><content type="html"><![CDATA[<p>Certain visitors of your blog may want to print your articles.
By default, most browsers will present a <strong>simplified version</strong> of your website for printing.
Background images may be hidden, saving a lot of ink.
But we can do better: usually people just want to <strong>print your content</strong>, not the entire website.</p>
<p>Printing things is not that common,
so it's really not worth it to spend much time worrying about how your website looks on paper.
There are a few simple things that make a lot of difference, though!</p>
<h2 id="style-for-printing">Style for printing<a href="#style-for-printing" aria-label="Link to heading 'Style for printing'" data-heading-content="Style for printing" class="anchor"></a></h2>
<p>Modern browsers <a href="http://caniuse.com/#feat=css-queries" title="Media Queries at CanIUse.com">support media queries</a>,
a way to limit part of your stylesheet to devices that match certain criteria.
While this feature is most used to scale your website to the size of multiple devices,
we can also use it to define <strong>print styles</strong>.</p>
<pre><code>@media print {
	/* insert CSS for printing here */
}
</code></pre>
<p>We want these styles at the very <strong>bottom of our stylesheet</strong>:
They have to override everything that we have defined before.</p>
<h3 id="older-browsers">Older browsers<a href="#older-browsers" aria-label="Link to heading 'Older browsers'" data-heading-content="Older browsers" class="anchor"></a></h3>
<p>In case you want to support Internet Explorer 8
and lower (or prefer your print styles in a separate file),
the alternative is using a <code>link</code> tag with <code>media</code> attribute:</p>
<pre><code>&lt;link rel=&quot;stylesheet&quot; href=&quot;print-all-the-things.css&quot; media=&quot;print&quot; /&gt;
</code></pre>
<p>When using a separate stylesheet, you no longer have to use the media queries in the examples below.</p>
<h2 id="hiding-unnecessary-parts-of-the-page">Hiding unnecessary parts of the page<a href="#hiding-unnecessary-parts-of-the-page" aria-label="Link to heading 'Hiding unnecessary parts of the page'" data-heading-content="Hiding unnecessary parts of the page" class="anchor"></a></h2>
<p>Usually you'll want to get rid of everything except the content of your website.
No header, footer, sidebar, sharing widgets, advertisements..
Most of this should be possible <em>by changing your stylesheet only</em>.
You might want to introduce a few new classes to manage visibility, that'll come in handy!</p>
<pre><code>.print {
	display: none;
}
@media print {
	#header, #footer, #sidebar, .share, .ads, .no-print {
		display: none;
	}
	.print {
		display: block;
	}
}
</code></pre>
<h2 id="page-margins">Page margins<a href="#page-margins" aria-label="Link to heading 'Page margins'" data-heading-content="Page margins" class="anchor"></a></h2>
<p>Another really simple tweak is the page margin.
I prefer margins <em>a bit wider than the defaults</em> that Chrome gave me.
Note that most browsers add some information in the page header and footer;
Making the top and bottom margins a bit bigger helps separating your content from the page info.</p>
<pre><code>@media print {
	@page {
		margin: 2cm 2cm 2cm 1.5cm;
	}
}
</code></pre>
<div class="footer"><hr><p>Thanks for subscribing to my feed!</p><p>Feel free to e-mail comments or corrections to <a href="mailto:vic@demuzere.be?subject=RE%3A%20Making%20your%20blog%20printer-friendly">vic@demuzere.be</a>.</p></div>]]></content></entry><entry><id>https://vic.demuzere.be/articles/golang-makefile-crosscompile/</id><title>Cross compiling Go applications with Make</title><link href="https://vic.demuzere.be/articles/golang-makefile-crosscompile/" rel="alternate" /><updated>2024-11-11T18:06:57+00:00</updated><published>2016-06-06T21:47:00+00:00</published><content type="html"><![CDATA[<p>When a new version of one of my Go apps is ready, I usually want to build and package the binary for multiple platforms.
This can easily be done in a shell script, but Make brings a bonus: it can run multiple builds at the same time.</p>
<h2 id="makefile-101">Makefile 101<a href="#makefile-101" aria-label="Link to heading 'Makefile 101'" data-heading-content="Makefile 101" class="anchor"></a></h2>
<p>In case you've worked with make before, skip this section!
If not: let's see what a basic makefile looks like.</p>
<p>Make defines <strong>targets</strong>, these are things that need to be done.
Usually a target represents a file that has to be created.
Every target contains a list of commands that have to be executed, we call that the <strong>recipe</strong>.
Make will only execute the recipe if a file with the same name as the target does not exist.
If your target does not represent a file, it's a <em>phony target</em>.</p>
<pre><code>mytarget:
	echo Hello world.

space: mytarget foo
	echo &quot;I'm in space.&quot;

foo:
	echo I am run only once
	touch foo

.PHONY: mytarget space
</code></pre>
<p>The above makefile contains 3 targets: <code>mytarget</code>, <code>foo</code> and <code>space</code>.
The second target tells us that it needs both other targets, so when you call <code>make space</code>, make will also execute <code>mytarget</code> and <code>foo</code>.</p>
<p>At the bottom of the file we define a special .PHONY target: Here we simply tell make which targets are phony targets.
Those do not represent a file on disk.
As we usually don't compile separate Go files, most of our targets will be phony targets.</p>
<p>Let's see what this does: save the file as <code>Makefile</code> and run <code>make space</code> in the same directory:</p>
<pre><code>$ make space  
echo Hello world.
Hello world.
echo I am run only once
I am run only once
echo &quot;I'm in space.&quot;
I'm in space.
</code></pre>
<p>Notice that make always tells us what command it is executing.
You can hide a command by prefixing it with @.
As we'll be executing multiple targets concurrently, it may be useful to see which commands are running.</p>
<p>Run <code>make space</code> again.
You'll see that the <code>foo</code> target is not run anymore.
That's because we created a file named foo and <code>foo</code> is not a phony target.</p>
<h3 id="variables">Variables<a href="#variables" aria-label="Link to heading 'Variables'" data-heading-content="Variables" class="anchor"></a></h3>
<p>GNU Make has two types of variables.
When we assign to a variable with <code>:=</code> the contents is evaluated at the time the variable is declared.
If we use just <code>=</code> the contents is evaluated every time we use the variable.</p>
<pre><code>MYVAR := Hello there! I am currently in $(shell pwd)
PWD = $(shell pwd)
</code></pre>
<p>The <code>pwd</code> shell command will be executed every time we use variable <code>$(PWD)</code> in the makefile, while <code>$(MYVAR)</code> will always contain the same value.</p>
<h2 id="cross-compiling">Cross compiling<a href="#cross-compiling" aria-label="Link to heading 'Cross compiling'" data-heading-content="Cross compiling" class="anchor"></a></h2>
<p>Make supports running multiple targets at the same time.
This means that we need a separate target for every GOOS and GOARCH in order to take advantage of the concurrency.</p>
<p>A simple way to do this would be:</p>
<pre><code>linux:
	GOOS=linux GOARCH=amd64 go build ...

windows:
	GOOS=windows GOARCH=amd64 go build ...

release: windows linux

.PHONY release windows linux
</code></pre>
<p>This builds both linux and windows binaries when we call <code>make release</code>.
We can improve this Makefile by looping over the platforms and architectures we want to build:</p>
<pre><code>PLATFORMS := linux/amd64 windows/amd64

release: $(PLATFORMS)

$(PLATFORMS):
	echo Hello, I am $@

.PHONY release $(PLATFORMS)
</code></pre>
<p>What we do here is create a target that contains all possible platforms.
Every platform is a separate target, just like it was in the previous Makefile.
That also means that we have to add every platform to the phony list.
Luckily, we can do that using the same variable. Inside the recipe we can use the special <code>$@</code> that contains the name of the current target.</p>
<p>The next part is a bit harder, we need to separate OS and ARCH.
Remember that variables can be evaluated at runtime?
We'll use that feature to create an <code>$(os)</code> and <code>$(arch)</code> var.</p>
<pre><code>temp = $(subst /, ,$@)
os = $(word 1, $(temp))
arch = $(word 2, $(temp))
</code></pre>
<p>String manipulation functions in make are limited, so we have to be a bit creative here.
We'll use these variables inside the <code>$(PLATFORM)</code> target.
Remember that <code>$@</code> contains the name of the current target?
When we replace the slash in this string we can use the <code>word</code> function to get the first and last part.</p>
<p>Combined with our previous Makefile, this becomes:</p>
<pre><code>PLATFORMS := linux/amd64 windows/amd64

temp = $(subst /, ,$@)
os = $(word 1, $(temp))
arch = $(word 2, $(temp))

release: $(PLATFORMS)

$(PLATFORMS):
	GOOS=$(os) GOARCH=$(arch) go build -o '$(os)-$(arch)' mypackage

.PHONY release $(PLATFORMS)
</code></pre>
<p>Note that variables can only be set outside the recipe.
As the contents is only evaluated when we use them, it still contains the correct value.</p>
<h2 id="concurrency">Concurrency<a href="#concurrency" aria-label="Link to heading 'Concurrency'" data-heading-content="Concurrency" class="anchor"></a></h2>
<p>We now have a Makefile that creates binaries for the platforms we listed in the <code>$(PLATFORMS)</code> variable.
When we call <code>make release</code> it builds everything we asked, one by one.
The main reason to use make is the easy concurrency: simply call make with the <code>-j n</code> argument, where n is the number of simultaneous jobs.</p>
<div class="footer"><hr><p>Thanks for subscribing to my feed!</p><p>Feel free to e-mail comments or corrections to <a href="mailto:vic@demuzere.be?subject=RE%3A%20Cross%20compiling%20Go%20applications%20with%20Make">vic@demuzere.be</a>.</p></div>]]></content></entry></feed>