{"version":"https://jsonfeed.org/version/1.1","title":"Vic Demuzere - Development","home_page_url":"https://vic.demuzere.be/articles/tags/development/","feed_url":"https://vic.demuzere.be/articles/tags/development/feed.json","description":"Blog posts tagged Development by Vic Demuzere","language":"en","items":[{"id":"https://vic.demuzere.be/articles/bots-crawlers-and-geoblocks/","content_html":"<p>The last few years, web traffic from bots has increased rapidly.\nSome 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.\nThis is even more pronounced on small websites like mine, as I don't receive many human visitors.</p>\n<p>The table below shows traffic on this website for the last few days:</p>\n<table>\n<thead>\n<tr>\n<th align=\"center\"></th>\n<th>Path</th>\n<th align=\"right\">Hits</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td align=\"center\">1</td>\n<td>/wp-login.php</td>\n<td align=\"right\">2751</td>\n</tr>\n<tr>\n<td align=\"center\">2</td>\n<td>/wp-admin/index.php</td>\n<td align=\"right\">2707</td>\n</tr>\n<tr>\n<td align=\"center\">3</td>\n<td>/wp-admin/edit.php</td>\n<td align=\"right\">2354</td>\n</tr>\n<tr>\n<td align=\"center\">4</td>\n<td>/wp-admin/profile.php</td>\n<td align=\"right\">2353</td>\n</tr>\n<tr>\n<td align=\"center\">5</td>\n<td>/wp-admin/plugins.php</td>\n<td align=\"right\">2351</td>\n</tr>\n<tr>\n<td align=\"center\">6</td>\n<td>/robots.txt</td>\n<td align=\"right\">1131</td>\n</tr>\n<tr>\n<td align=\"center\">7</td>\n<td>/</td>\n<td align=\"right\">994</td>\n</tr>\n<tr>\n<td align=\"center\">8</td>\n<td>/articles/feed.rss</td>\n<td align=\"right\">989</td>\n</tr>\n<tr>\n<td align=\"center\">9</td>\n<td>/articles/how-i-almost-lost-my-backups/</td>\n<td align=\"right\">314</td>\n</tr>\n<tr>\n<td align=\"center\">10</td>\n<td>/wp-content/themes/vic/style.css</td>\n<td align=\"right\">299</td>\n</tr>\n</tbody>\n</table>\n<p>This clearly shows two things:</p>\n<ul>\n<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>\n<li>My stylesheet has a low number of hits compared to the frontpage and my most recently published article.\nOne would expect each unique visitor to download the stylesheet at least once.</li>\n</ul>\n<p>That is to say, my own statistics show that I'm mainly serving our robot overlords.</p>\n<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.\nIt's a single-user instance, and most projects are private, but I do publish a bunch of open-source projects there.\nRecent bot traffic on applications like these is causing significant load on my server.</p>\n<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>\n<p>Previously, the majority of crawlers were relatively decent.\nThey would crawl your website slowly, making sure not to cause too much load.\nTheir main purpose was serving search results to people looking for the content you publish.\nBringing down your website would only result in their own users landing on error pages.</p>\n<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.\nMultiple companies are now racing to collect as much data as possible.\nThey no longer care what happens with the websites they're crawling.\nOnce they have collected enough content, their GenAI agents provide users with answers directly.\nThe source website is treated as irrelevant.\nObviously, 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>\n<p>So I went through the standard ways of blocking them:</p>\n<ol>\n<li>Asking nicely in <a href=\"https://www.robotstxt.org/\" title=\"The Web Robots Pages\"><code>robots.txt</code></a>.</li>\n<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>\n<li>Blocking IP ranges from known data centers and cloud providers.</li>\n</ol>\n<p>None of these methods work.</p>\n<p>GenAI companies frequently ignore <code>robots.txt</code> files.\nThey also usually disguise themselves as normal browsers instead of identifying as crawlers.\nMore recently, I've also noticed them increasingly using consumer IP space.\nCombined with rapidly changing addresses, this makes it impossible to block them by IP address alone.</p>\n<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>\n<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>\n<p>Making sure websites stay online is part of my job.\nI don't want to have to go through the same hassle for my own web apps when I get home.\nI'm tired, and the few things I'm hosting just aren't worth it.</p>\n<p>So, I decided to take two different approaches:</p>\n<ol>\n<li>\n<p>Websites that are <strong>meant to be public</strong>, like the one you're reading now.\nI've always optimized these for high traffic, and will continue to do so.\nThese are built to withstand much more than the AI crawlers I'm currently seeing.\n<strong>I'm no longer blocking any bots on public websites.</strong></p>\n</li>\n<li>\n<p>Websites (and web apps) that are <strong>primarily meant for myself</strong>.\nThese are <strong>now geoblocked</strong> and only accept traffic from Belgium.\nAs these are mainly open source applications that I don't maintain myself, they are harder to optimize.\nThis includes my <a href=\"https://forgejo.org/\" title=\"Forgejo is a self-hosted lightweight software forge.\">Forgejo</a> instance.\nIt's sad that part of my open source code disappears behind a firewall.\nI hope to open it up again once AI companies start behaving more responsibly.</p>\n</li>\n</ol>\n<p>My geoblock page contains contact information.\nSo 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>\n<section class=\"footnotes\" data-footnotes>\n<ol>\n<li id=\"fn-1\">\n<p>Every article I've found claiming this reads like marketing material for a web security firm.\nFor that reason, I decided not to link to any of them.\nBased 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>\n</li>\n<li id=\"fn-2\">\n<p>I do read robots.txt on some sites I visit.\nIt's a treasure trove of information about the website you're visiting.\nBut 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>\n</li>\n<li id=\"fn-3\">\n<p>I don't really mind AI crawlers, as long as they behave.\nThey 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>\n</li>\n<li id=\"fn-4\">\n<p>Anubis is great!\nEspecially 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>\n</li>\n</ol>\n</section>\n<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>","url":"https://vic.demuzere.be/articles/bots-crawlers-and-geoblocks/","date_published":"2026-05-14T13:41:51Z","date_modified":"2026-05-14T13:41:51Z","language":"en"},{"id":"https://vic.demuzere.be/articles/generating-uuids-in-sqlite/","content_html":"<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>\n<p>Before we continue, it's important to note that you should verify whether SQLite's random functions are adequate for your usecase.\nThey may not return truly random data.\nHowever, 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;.\nAnd since we all follow that to the letter, it doesn't really matter.. right?</p>\n<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>\n<blockquote>\n<p>Hint: applications can generate globally unique identifiers using this function together with hex() and/or lower().</p>\n</blockquote>\n<p>Combining the <code>randomblob(N)</code> function with <code>hex(N)</code> does indeed create something that vaguely resembles a UUID.\nFor 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>:\nvalid 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>\n<p>We can do better!</p>\n<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>\n<p>The binary representation of a version 4 UUID consists of:</p>\n<ul>\n<li>48 bits (6 bytes) random data</li>\n<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>\n<li>6 bits (1.5 bytes) random data</li>\n<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>\n<li>60 bits (7.5 bytes) random data</li>\n</ul>\n<p>As far as I know, we can't modify individual bytes in a blob using the available functions in SQLite.\nHowever, we do have access to a lot of string functions.\nTaking a detour using HEX encoding provides a solution.\nWe'll first concatenate a HEX string containing the data we want, then convert it back to a binary blob.\nNote that each byte is represented by two hexadecimal characters.</p>\n<p>The first 48 random bits are easy:</p>\n<pre><code class=\"language-sqlite\">sqlite&gt; select HEX(RANDOMBLOB(6));\r\n4004714237AF\r\n</code></pre>\n<p>The first 4 bits of the next byte need to be set to <code>0100</code> (or <code>4</code> in hex).\nAs every byte is represented by two characters in hexadecimal, we can just use a literal <code>4</code> for the next character.</p>\n<pre><code class=\"language-sqlite\">sqlite&gt; select '4';\r\n4\r\n</code></pre>\n<p>Next, we need 6 bits of random data.\nWe can only generate random bytes using <code>RANDOMBLOB(N)</code>, but we need 1.5 bytes.\nLuckily, because we are working with a hexadecimal string, we can generate 2 bytes (4 hex characters) and take the first three characters.</p>\n<pre><code>sqlite&gt; select HEX(RANDOMBLOB(2));\r\n7B4A\r\n\r\nsqlite&gt; select SUBSTR(HEX(RANDOMBLOB(2)),0,4);\r\n5BD\r\n</code></pre>\n<p>The next one is a little more complex.\nThe <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.\nFor our version 4 UUID, we need to set the first two bits to <code>10</code>, the others can be random.\nThat 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>\n<pre><code>select FORMAT('%X', 8 + ABS(RANDOM() % 4));\r\nA\r\n</code></pre>\n<p>After this, we need 60 more random bits.\nSince this again includes a half byte again (7.5 bytes), we're doing the same <code>SUBSTR</code> trick as before:</p>\n<pre><code>sqlite&gt; select SUBSTR(HEX(RANDOMBLOB(8)),0,16);\r\nE83EF2136842130\r\n</code></pre>\n<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>\n<p>Using <code>CONCAT(X, ...)</code> we create a single string:</p>\n<pre><code>sqlite&gt; select CONCAT(\r\n(x1...&gt;   HEX(RANDOMBLOB(6)),\r\n(x1...&gt;   '4',\r\n(x1...&gt;   SUBSTR(HEX(RANDOMBLOB(2)),0,4),\r\n(x1...&gt;   FORMAT('%X', 8 + ABS(RANDOM() % 4)),\r\n(x1...&gt;   SUBSTR(HEX(RANDOMBLOB(8)),0,16)\r\n(x1...&gt; );\r\n\r\n64DF281823BE47249BB2CA8D6450066B\r\n</code></pre>\n<p>Then use <code>UNHEX(X)</code> to convert it back into a binary blob:</p>\n<pre><code>select UNHEX(CONCAT(\r\n  HEX(RANDOMBLOB(6)),\r\n  '4',\r\n  SUBSTR(HEX(RANDOMBLOB(2)),0,4),\r\n  FORMAT('%X', 8 + ABS(RANDOM() % 4)),\r\n  SUBSTR(HEX(RANDOMBLOB(8)),0,16)\r\n));\r\n</code></pre>\n<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>\n<p>The SQL above can be used as the default value for a blob column:</p>\n<pre><code>CREATE TABLE &quot;animals&quot; (\r\n  &quot;id&quot; INTEGER PRIMARY KEY NOT NULL,\r\n  &quot;uuid&quot; BLOB UNIQUE NOT NULL\r\n    DEFAULT (UNHEX(CONCAT(\r\n      HEX(RANDOMBLOB(6)), '4',\r\n      SUBSTR(HEX(RANDOMBLOB(2)),0,4),\r\n      FORMAT('%X', 8 + ABS(RANDOM() % 4)),\r\n      SUBSTR(HEX(RANDOMBLOB(8)),0,16))))\r\n    UNIQUE CHECK(LENGTH(&quot;uuid&quot;) = 16),\r\n   &quot;name&quot; TEXT NOT NULL\r\n) STRICT;\r\n</code></pre>\n<p>This will generate a UUID if we don't specify one on insert:</p>\n<pre><code>sqlite&gt; INSERT INTO &quot;animals&quot; (&quot;name&quot;) VALUES ('Fluffy');\r\n\r\nsqlite&gt; SELECT * FROM &quot;animals&quot;;\r\n1|[blob]|Fluffy\r\n\r\nsqlite&gt; SELECT &quot;id&quot;, HEX(&quot;uuid&quot;), &quot;name&quot; FROM &quot;animals&quot;;\r\n1|85671F7120F14AA6802B8B4E9213B8BE|Fluffy\r\n</code></pre>\n<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>\n<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>\n<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.\n<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>\n<pre><code>sqlite&gt; select CONCAT(\r\n  HEX(RANDOMBLOB(4)),\r\n  '-',\r\n  HEX(RANDOMBLOB(2)),\r\n  '-',\r\n  '4',\r\n  SUBSTR(HEX(RANDOMBLOB(2)),0,4),\r\n  '-',\r\n  FORMAT('%X', 8 + ABS(RANDOM() % 4)),\r\n  SUBSTR(HEX(RANDOMBLOB(2)),0,4),\r\n  '-',\r\n  SUBSTR(HEX(RANDOMBLOB(8)),0,13)\r\n);\r\n\r\n199ED82A-3102-42DB-B59B-8C356B6F3FF7\r\n</code></pre>\n<p>Voila!</p>\n<section class=\"footnotes\" data-footnotes>\n<ol>\n<li id=\"fn-1\">\n<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>\n</li>\n</ol>\n</section>\n<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>","url":"https://vic.demuzere.be/articles/generating-uuids-in-sqlite/","date_published":"2025-12-24T15:28:41Z","date_modified":"2026-01-04T17:26:44Z","language":"en"},{"id":"https://vic.demuzere.be/articles/printer-friendly-website/","content_html":"<p>Certain visitors of your blog may want to print your articles.\nBy default, most browsers will present a <strong>simplified version</strong> of your website for printing.\nBackground images may be hidden, saving a lot of ink.\nBut we can do better: usually people just want to <strong>print your content</strong>, not the entire website.</p>\n<p>Printing things is not that common,\nso it's really not worth it to spend much time worrying about how your website looks on paper.\nThere are a few simple things that make a lot of difference, though!</p>\n<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>\n<p>Modern browsers <a href=\"http://caniuse.com/#feat=css-queries\" title=\"Media Queries at CanIUse.com\">support media queries</a>,\na way to limit part of your stylesheet to devices that match certain criteria.\nWhile this feature is most used to scale your website to the size of multiple devices,\nwe can also use it to define <strong>print styles</strong>.</p>\n<pre><code>@media print {\n\t/* insert CSS for printing here */\n}\n</code></pre>\n<p>We want these styles at the very <strong>bottom of our stylesheet</strong>:\nThey have to override everything that we have defined before.</p>\n<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>\n<p>In case you want to support Internet Explorer 8\nand lower (or prefer your print styles in a separate file),\nthe alternative is using a <code>link</code> tag with <code>media</code> attribute:</p>\n<pre><code>&lt;link rel=&quot;stylesheet&quot; href=&quot;print-all-the-things.css&quot; media=&quot;print&quot; /&gt;\n</code></pre>\n<p>When using a separate stylesheet, you no longer have to use the media queries in the examples below.</p>\n<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>\n<p>Usually you'll want to get rid of everything except the content of your website.\nNo header, footer, sidebar, sharing widgets, advertisements..\nMost of this should be possible <em>by changing your stylesheet only</em>.\nYou might want to introduce a few new classes to manage visibility, that'll come in handy!</p>\n<pre><code>.print {\n\tdisplay: none;\n}\n@media print {\n\t#header, #footer, #sidebar, .share, .ads, .no-print {\n\t\tdisplay: none;\n\t}\n\t.print {\n\t\tdisplay: block;\n\t}\n}\n</code></pre>\n<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>\n<p>Another really simple tweak is the page margin.\nI prefer margins <em>a bit wider than the defaults</em> that Chrome gave me.\nNote that most browsers add some information in the page header and footer;\nMaking the top and bottom margins a bit bigger helps separating your content from the page info.</p>\n<pre><code>@media print {\n\t@page {\n\t\tmargin: 2cm 2cm 2cm 1.5cm;\n\t}\n}\n</code></pre>\n<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>","url":"https://vic.demuzere.be/articles/printer-friendly-website/","date_published":"2016-06-18T14:34:00Z","date_modified":"2025-03-09T10:03:36Z","language":"en"},{"id":"https://vic.demuzere.be/articles/golang-makefile-crosscompile/","content_html":"<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.\nThis can easily be done in a shell script, but Make brings a bonus: it can run multiple builds at the same time.</p>\n<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>\n<p>In case you've worked with make before, skip this section!\nIf not: let's see what a basic makefile looks like.</p>\n<p>Make defines <strong>targets</strong>, these are things that need to be done.\nUsually a target represents a file that has to be created.\nEvery target contains a list of commands that have to be executed, we call that the <strong>recipe</strong>.\nMake will only execute the recipe if a file with the same name as the target does not exist.\nIf your target does not represent a file, it's a <em>phony target</em>.</p>\n<pre><code>mytarget:\n\techo Hello world.\n\nspace: mytarget foo\n\techo &quot;I'm in space.&quot;\n\nfoo:\n\techo I am run only once\n\ttouch foo\n\n.PHONY: mytarget space\n</code></pre>\n<p>The above makefile contains 3 targets: <code>mytarget</code>, <code>foo</code> and <code>space</code>.\nThe 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>\n<p>At the bottom of the file we define a special .PHONY target: Here we simply tell make which targets are phony targets.\nThose do not represent a file on disk.\nAs we usually don't compile separate Go files, most of our targets will be phony targets.</p>\n<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>\n<pre><code>$ make space  \necho Hello world.\nHello world.\necho I am run only once\nI am run only once\necho &quot;I'm in space.&quot;\nI'm in space.\n</code></pre>\n<p>Notice that make always tells us what command it is executing.\nYou can hide a command by prefixing it with @.\nAs we'll be executing multiple targets concurrently, it may be useful to see which commands are running.</p>\n<p>Run <code>make space</code> again.\nYou'll see that the <code>foo</code> target is not run anymore.\nThat's because we created a file named foo and <code>foo</code> is not a phony target.</p>\n<h3 id=\"variables\">Variables<a href=\"#variables\" aria-label=\"Link to heading 'Variables'\" data-heading-content=\"Variables\" class=\"anchor\"></a></h3>\n<p>GNU Make has two types of variables.\nWhen we assign to a variable with <code>:=</code> the contents is evaluated at the time the variable is declared.\nIf we use just <code>=</code> the contents is evaluated every time we use the variable.</p>\n<pre><code>MYVAR := Hello there! I am currently in $(shell pwd)\nPWD = $(shell pwd)\n</code></pre>\n<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>\n<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>\n<p>Make supports running multiple targets at the same time.\nThis means that we need a separate target for every GOOS and GOARCH in order to take advantage of the concurrency.</p>\n<p>A simple way to do this would be:</p>\n<pre><code>linux:\n\tGOOS=linux GOARCH=amd64 go build ...\n\nwindows:\n\tGOOS=windows GOARCH=amd64 go build ...\n\nrelease: windows linux\n\n.PHONY release windows linux\n</code></pre>\n<p>This builds both linux and windows binaries when we call <code>make release</code>.\nWe can improve this Makefile by looping over the platforms and architectures we want to build:</p>\n<pre><code>PLATFORMS := linux/amd64 windows/amd64\n\nrelease: $(PLATFORMS)\n\n$(PLATFORMS):\n\techo Hello, I am $@\n\n.PHONY release $(PLATFORMS)\n</code></pre>\n<p>What we do here is create a target that contains all possible platforms.\nEvery platform is a separate target, just like it was in the previous Makefile.\nThat also means that we have to add every platform to the phony list.\nLuckily, 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>\n<p>The next part is a bit harder, we need to separate OS and ARCH.\nRemember that variables can be evaluated at runtime?\nWe'll use that feature to create an <code>$(os)</code> and <code>$(arch)</code> var.</p>\n<pre><code>temp = $(subst /, ,$@)\nos = $(word 1, $(temp))\narch = $(word 2, $(temp))\n</code></pre>\n<p>String manipulation functions in make are limited, so we have to be a bit creative here.\nWe'll use these variables inside the <code>$(PLATFORM)</code> target.\nRemember that <code>$@</code> contains the name of the current target?\nWhen we replace the slash in this string we can use the <code>word</code> function to get the first and last part.</p>\n<p>Combined with our previous Makefile, this becomes:</p>\n<pre><code>PLATFORMS := linux/amd64 windows/amd64\n\ntemp = $(subst /, ,$@)\nos = $(word 1, $(temp))\narch = $(word 2, $(temp))\n\nrelease: $(PLATFORMS)\n\n$(PLATFORMS):\n\tGOOS=$(os) GOARCH=$(arch) go build -o '$(os)-$(arch)' mypackage\n\n.PHONY release $(PLATFORMS)\n</code></pre>\n<p>Note that variables can only be set outside the recipe.\nAs the contents is only evaluated when we use them, it still contains the correct value.</p>\n<h2 id=\"concurrency\">Concurrency<a href=\"#concurrency\" aria-label=\"Link to heading 'Concurrency'\" data-heading-content=\"Concurrency\" class=\"anchor\"></a></h2>\n<p>We now have a Makefile that creates binaries for the platforms we listed in the <code>$(PLATFORMS)</code> variable.\nWhen we call <code>make release</code> it builds everything we asked, one by one.\nThe 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>\n<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>","url":"https://vic.demuzere.be/articles/golang-makefile-crosscompile/","date_published":"2016-06-06T21:47:00Z","date_modified":"2024-11-11T18:06:57Z","language":"en"}]}