<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Acarg</title><link>https://www.acarg.ch/</link><description>Recent content on Acarg</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Wed, 13 Dec 2023 00:19:36 +0100</lastBuildDate><atom:link href="https://www.acarg.ch/index.xml" rel="self" type="application/rss+xml"/><item><title>My email setup with Aerc and Git</title><link>https://www.acarg.ch/posts/aerc-email-setup/</link><pubDate>Wed, 13 Dec 2023 00:19:36 +0100</pubDate><guid>https://www.acarg.ch/posts/aerc-email-setup/</guid><description>&lt;h2 id="motivation">
&lt;a class="Heading-link u-clickable" href="https://www.acarg.ch/posts/aerc-email-setup/#motivation">Motivation&lt;/a>
&lt;/h2>
&lt;p>Ever since GMail in the early 2000s&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup> I have
been interacting with emails through the browser, in a webmail. It may sound
stupid, but I only recently realised that I was missing out on some pretty cool
features of email, such as:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Offline access:&lt;/strong> Webmails require to be online. Not only is it convenient
to read/write emails offline, but it is also faster because there is no need
to re-download the emails every time.&lt;/li>
&lt;li>&lt;strong>No lock-in:&lt;/strong> Without owning the domain, it is hard to change provider
because it means changing the address and updating it everywhere. Email makes
it easy to own the domain, separating the address from the provider.&lt;/li>
&lt;li>&lt;strong>Easy backup:&lt;/strong> Now that I fetch my emails and keep them offline, I have
a &lt;code>mail/&lt;/code> folder that I can easily backup.&lt;/li>
&lt;li>&lt;strong>The right UI for the job:&lt;/strong> On my phone, I want an graphical app that saves
my recent emails so that I can access my cinema ticket offline. On my desktop,
I want to backup all my emails, and I want an easy way to forward patches from
a mailing list into git (I like TUIs like Aerc or Mutt). Sometimes I want to
access my emails from a device I don&amp;rsquo;t own, and the webmail is convenient for
that.&lt;/li>
&lt;/ul>
&lt;p>I therefore spent some time setting up an email environment that works well for
me. Everyone is different, so what is ergonomic for me may not be for others.
But it doesn&amp;rsquo;t hurt to share my setup and - who knows - it may help someone,
someday. You will also find other resources describing similar setups
&lt;a href="#annex-other-resources">below&lt;/a>.&lt;/p>
&lt;h2 id="my-setup">
&lt;a class="Heading-link u-clickable" href="https://www.acarg.ch/posts/aerc-email-setup/#my-setup">My setup&lt;/a>
&lt;/h2>
&lt;p>I am using the following programs:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://aerc-mail.org">Aerc&lt;/a> as my TUI email client (there are others like
&lt;a href="http://www.mutt.org">Mutt&lt;/a>). It &lt;a href="https://drewdevault.com/2022/07/25/Code-review-with-aerc.html">integrates well&lt;/a>
with the git email workflow.&lt;/li>
&lt;li>Git with the email workflow, sending patches with &lt;code>git send-email&lt;/code>.&lt;/li>
&lt;li>&lt;a href="https://sourceforge.net/projects/isync/">isync/mbsync&lt;/a>&lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup> to
synchronize my mailbox (it downloads my emails into a &lt;code>mail/&lt;/code> folder that
Aerc accesses).&lt;/li>
&lt;li>&lt;a href="https://www.opensmtpd.org/">OpenSMTPD&lt;/a> as my SMTP relay, which queues the
emails I send while I am offline and sends them later. An alternative would
have been &lt;a href="https://marlam.de/msmtp/">msmtp&lt;/a> (which is &amp;ldquo;just&amp;rdquo; an SMTP client
and may therefore be more lightweight) together with the
&lt;a href="https://git.marlam.de/gitweb/?p=msmtp.git;a=blob_plain;f=scripts/msmtpq/msmtpq;hb=HEAD">msmtpq&lt;/a>
script (this setup is described
&lt;a href="https://bence.ferdinandy.com/2023/07/20/email-in-the-terminal-a-complete-guide-to-the-unix-way-of-email/">here&lt;/a>).&lt;/li>
&lt;/ul>
&lt;p>Also note that I am currently using Fastmail as a provider (different providers
may require slightly different configurations). I know that JMAP is a thing, but
I haven&amp;rsquo;t found an alternative to isync/mbsync for JMAP yet.&lt;/p>
&lt;h3 id="aerc">
&lt;a class="Heading-link u-clickable" href="https://www.acarg.ch/posts/aerc-email-setup/#aerc">Aerc&lt;/a>
&lt;/h3>
&lt;p>Aerc can be used &amp;ldquo;standalone&amp;rdquo; (without isync/mbsync and OpenSMTPD), with a
simple config like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-toml" data-lang="toml">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># ./config/aerc/accounts.conf&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">Fastmail&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">source&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">imaps&lt;/span>&lt;span class="err">://&lt;/span>&lt;span class="nx">my-user&lt;/span>&lt;span class="err">%&lt;/span>&lt;span class="mi">40&lt;/span>&lt;span class="nx">my-domain&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">com&lt;/span>&lt;span class="err">:&lt;/span>&lt;span class="nx">my-password&lt;/span>&lt;span class="err">@&lt;/span>&lt;span class="nx">imap&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">fastmail&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">com&lt;/span>&lt;span class="err">:&lt;/span>&lt;span class="mi">993&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">outgoing&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">smtp&lt;/span>&lt;span class="err">://&lt;/span>&lt;span class="nx">my-user&lt;/span>&lt;span class="err">%&lt;/span>&lt;span class="mi">40&lt;/span>&lt;span class="nx">my-domain&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">com&lt;/span>&lt;span class="err">:&lt;/span>&lt;span class="nx">my-password&lt;/span>&lt;span class="err">@&lt;/span>&lt;span class="nx">smtp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">fastmail&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">com&lt;/span>&lt;span class="err">:&lt;/span>&lt;span class="mi">587&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">from&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">My&lt;/span> &lt;span class="nx">User&lt;/span> &lt;span class="err">&amp;lt;&lt;/span>&lt;span class="nx">my-user&lt;/span>&lt;span class="err">@&lt;/span>&lt;span class="nx">my-domain&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">com&lt;/span>&lt;span class="err">&amp;gt;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>Where:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>my-user@my-domain.com&lt;/strong> is my address and Fastmail username.&lt;/li>
&lt;li>&lt;strong>my-password&lt;/strong> is the token I got from Fastmail.&lt;/li>
&lt;/ul>
&lt;p>But in this setup, Aerc will only work online: you will not have access to the
emails when offline, and you will not be able to send emails either.&lt;/p>
&lt;p>That is why we will configure isync/mbsync to fetch and store the emails on
disk (here in &lt;code>~/mail/&lt;/code>), such that Aerc can access them. We can therefore
update the source in Aerc:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-toml" data-lang="toml">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">source&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">maildir&lt;/span>&lt;span class="err">://~/&lt;/span>&lt;span class="nx">mail&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>In order to send emails while we are offline, we will send them with OpenSMTPD
which will queue them and retry sending them when we are back online. We make
Aerc leverage this by setting the &lt;code>outgoing&lt;/code> param to OpenSMTPD&amp;rsquo;s &lt;code>sendmail&lt;/code>
executable:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-toml" data-lang="toml">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">outgoing&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="err">/&lt;/span>&lt;span class="nx">usr&lt;/span>&lt;span class="err">/&lt;/span>&lt;span class="nx">sbin&lt;/span>&lt;span class="err">/&lt;/span>&lt;span class="nx">sendmail&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>The final &lt;code>./config/aerc/accounts.conf&lt;/code> looks like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-toml" data-lang="toml">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># ./config/aerc/accounts.conf&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">Fastmail&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">source&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">maildir&lt;/span>&lt;span class="err">://~/&lt;/span>&lt;span class="nx">mail&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">outgoing&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="err">/&lt;/span>&lt;span class="nx">usr&lt;/span>&lt;span class="err">/&lt;/span>&lt;span class="nx">sbin&lt;/span>&lt;span class="err">/&lt;/span>&lt;span class="nx">sendmail&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">default&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">INBOX&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">from&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">My&lt;/span> &lt;span class="nx">User&lt;/span> &lt;span class="err">&amp;lt;&lt;/span>&lt;span class="nx">my-user&lt;/span>&lt;span class="err">@&lt;/span>&lt;span class="nx">my-domain&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">com&lt;/span>&lt;span class="err">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">copy-to&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">Sent&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">check-mail-cmd&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s1">&amp;#39;mbsync -c ~/.config/isync/mbsync.conf primary&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">check-mail&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="nx">m&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;ul>
&lt;li>&lt;strong>source:&lt;/strong> the mail folder (synchronized with Fastmail by isync/mbsync).&lt;/li>
&lt;li>&lt;strong>outgoing:&lt;/strong> the &lt;code>sendmail&lt;/code> executable that passes the email to OpenSMTPD.&lt;/li>
&lt;li>&lt;strong>check-mail-cmd:&lt;/strong> the command run by Aerc to check for new emails. Note that
it will only be called while Aerc is running. If you want to fetch emails
regularly independently from Aerc, consider running the &lt;code>mbsync&lt;/code> command in a
cron job.&lt;/li>
&lt;li>&lt;strong>check-mail:&lt;/strong> we tell Aerc to run &lt;code>check-mail-cmd&lt;/code> every minute.&lt;/li>
&lt;/ul>
&lt;h3 id="git">
&lt;a class="Heading-link u-clickable" href="https://www.acarg.ch/posts/aerc-email-setup/#git">Git&lt;/a>
&lt;/h3>
&lt;p>Just like for Aerc, we can configure Git to send emails without OpenSMTPD (see
&lt;a href="https://git-send-email.io/">this guide&lt;/a> for more details):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-toml" data-lang="toml">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># ~/.gitconfig&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">user&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">email&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">my-user&lt;/span>&lt;span class="err">@&lt;/span>&lt;span class="nx">my-domain&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">com&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">My&lt;/span> &lt;span class="nx">User&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">sendemail&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">smtpserver&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">smtp&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">fastmail&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">com&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">smtpuser&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">my-user&lt;/span>&lt;span class="err">@&lt;/span>&lt;span class="nx">my-domain&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">com&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">smtpencryption&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">ssl&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">smtpserverport&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="mi">465&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">smtppass&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">my-password&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>But again, sending emails will only work online. The good news is that with
OpenSMTPD configured, we can just drop the whole &lt;code>[sendemail]&lt;/code> section and the
emails will be handled by it:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-toml" data-lang="toml">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># ~/.gitconfig&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">[&lt;/span>&lt;span class="nx">user&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">email&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">my-user&lt;/span>&lt;span class="err">@&lt;/span>&lt;span class="nx">my-domain&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">com&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">name&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="nx">My&lt;/span> &lt;span class="nx">User&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>Now &lt;code>git send-email&lt;/code> will work offline, too, with the outgoing emails being
queued in OpenSMTPD.&lt;/p>
&lt;h3 id="isyncmbsync">
&lt;a class="Heading-link u-clickable" href="https://www.acarg.ch/posts/aerc-email-setup/#isyncmbsync">isync/mbsync&lt;/a>
&lt;/h3>
&lt;p>We have configured Aerc to fetch emails from &lt;code>~/mail/&lt;/code> above, now we need
isync/mbsync to actually put them there. Here is my &lt;code>~/.config/isync/mbsync.conf&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-toml" data-lang="toml">&lt;span class="line">&lt;span class="cl">&lt;span class="c"># ~/.config/isync/mbsync.conf&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">CopyArrivalDate&lt;/span> &lt;span class="nx">yes&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">Create&lt;/span> &lt;span class="nx">Near&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">Expunge&lt;/span> &lt;span class="nx">Both&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">IMAPAccount&lt;/span> &lt;span class="nx">fastmail&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">Host&lt;/span> &lt;span class="nx">imap&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">fastmail&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">com&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">User&lt;/span> &lt;span class="nx">my-user&lt;/span>&lt;span class="err">@&lt;/span>&lt;span class="nx">my-domain&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">com&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">Pass&lt;/span> &lt;span class="nx">my-password&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">SSLType&lt;/span> &lt;span class="nx">IMAPS&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">MaildirStore&lt;/span> &lt;span class="nx">local&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">Path&lt;/span> &lt;span class="err">~/&lt;/span>&lt;span class="nx">mail&lt;/span>&lt;span class="err">/&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">Inbox&lt;/span> &lt;span class="err">~/&lt;/span>&lt;span class="nx">mail&lt;/span>&lt;span class="err">/&lt;/span>&lt;span class="nx">INBOX&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">SubFolders&lt;/span> &lt;span class="nx">Verbatim&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">IMAPStore&lt;/span> &lt;span class="nx">fastmail&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">Account&lt;/span> &lt;span class="nx">fastmail&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">Channel&lt;/span> &lt;span class="nx">primary&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">Far&lt;/span> &lt;span class="err">:&lt;/span>&lt;span class="nx">fastmail&lt;/span>&lt;span class="err">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">Near&lt;/span> &lt;span class="err">:&lt;/span>&lt;span class="nx">local&lt;/span>&lt;span class="err">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nx">Patterns&lt;/span> &lt;span class="err">*&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>A few notes here:&lt;/p>
&lt;ul>
&lt;li>&lt;code>User&lt;/code> replaces the &lt;code>smtpuser&lt;/code> we had in &lt;code>.gitconfig&lt;/code> and the user we had in
&lt;code>accounts.conf&lt;/code>.&lt;/li>
&lt;li>&lt;code>Pass&lt;/code> replaces the &lt;code>smtppass&lt;/code> we had in &lt;code>.gitconfig&lt;/code> and the password we had
in &lt;code>accounts.conf&lt;/code>.&lt;/li>
&lt;li>I do have issues with Fastmail and &lt;code>CopyArrivalDate yes&lt;/code> because isync fails
to correctly parse the dates with a 1-digit day (so it fails at the beginning
of each month). It turns out that Fastmail (Postfix actually, which is used by
Fastmail) is fine and isync is the one parsing incorrectly. There is a
&lt;a href="https://sourceforge.net/p/isync/isync/ci/e70c300f7446ba6ec1259f459a0f0e1d2d592ed9/">fix upstream&lt;/a>
but it hasn&amp;rsquo;t made it to a release yet. In my case I compile isync from
sources for that reason.&lt;/li>
&lt;/ul>
&lt;p>With this configuration, we can synchronize with the Fastmail server by
running the following command manually:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">mbsync -c ~/.config/isync/mbsync.conf primary&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>But that&amp;rsquo;s just for testing, I usually don&amp;rsquo;t call it directly. Remember:
this is the command that Aerc will run every minute as part of the
&lt;code>check-mail-cmd&lt;/code> mechanism (see &lt;a href="#aerc">the Aerc setup above&lt;/a>).&lt;/p>
&lt;h3 id="opensmtpd">
&lt;a class="Heading-link u-clickable" href="https://www.acarg.ch/posts/aerc-email-setup/#opensmtpd">OpenSMTPD&lt;/a>
&lt;/h3>
&lt;p>With isync/mbsync, Aerc will fetch and store the emails on the disk for offline
use. Now we will setup OpenSMTPD as a relay to send messages, so that they get
queued and sent when an Internet connection is available.&lt;/p>
&lt;p>Note that OpenSMTPD runs as a service and therefore has to be enabled. Changing
the &lt;a href="https://github.com/OpenSMTPD/OpenSMTPD/blob/master/usr.sbin/smtpd/smtpd.conf">default smtpd.conf&lt;/a>
to support my use-case was pretty straightforward (it is even described in the
&amp;ldquo;examples&amp;rdquo; section of the &lt;code>smtpd.conf&lt;/code> manpage). First, I created a new
&lt;code>/etc/smtpd/secrets&lt;/code> file with the following content:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># /etc/smtpd/secrets&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">me my-user@my-domain.com:my-password&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>Then I had to add the following two lines to &lt;code>/etc/smtpd/smtpd.conf&lt;/code> in order to
enable the relay:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># /etc/smtpd/smtpd.conf&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">table secrets file:/etc/smtpd/secrets
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="o">[&lt;/span>...&lt;span class="o">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">action &lt;span class="s2">&amp;#34;relay&amp;#34;&lt;/span> relay host smtp+tls://me@smtp.fastmail.com:587 auth &amp;lt;secrets&amp;gt;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>And that was it! The final &lt;code>/etc/smtpd/smtpd.conf&lt;/code> file looks like this (I
highlighted the two lines I had to add to the default file):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># /etc/smtpd/smtpd.conf&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># This is the smtpd server system-wide configuration file.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># See smtpd.conf(5) for more information.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">table aliases file:/etc/smtpd/aliases
&lt;/span>&lt;/span>&lt;span class="line hl">&lt;span class="cl">table secrets file:/etc/smtpd/secrets
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># To accept external mail, add something like: listen on eth0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">#&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">listen on lo
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">action &lt;span class="s2">&amp;#34;local&amp;#34;&lt;/span> maildir &lt;span class="nb">alias&lt;/span> &amp;lt;aliases&amp;gt;
&lt;/span>&lt;/span>&lt;span class="line hl">&lt;span class="cl">action &lt;span class="s2">&amp;#34;relay&amp;#34;&lt;/span> relay host smtp+tls://me@smtp.fastmail.com:587 auth &amp;lt;secrets&amp;gt;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Uncomment the following to accept external mail for domain &amp;#34;example.org&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">#&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># match from any for domain &amp;#34;example.org&amp;#34; action &amp;#34;local&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">match &lt;span class="k">for&lt;/span> &lt;span class="nb">local&lt;/span> action &lt;span class="s2">&amp;#34;local&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">match from &lt;span class="nb">local&lt;/span> &lt;span class="k">for&lt;/span> any action &lt;span class="s2">&amp;#34;relay&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;h2 id="conclusion">
&lt;a class="Heading-link u-clickable" href="https://www.acarg.ch/posts/aerc-email-setup/#conclusion">Conclusion&lt;/a>
&lt;/h2>
&lt;p>We have configured isync/mbsync to synchronise a local folder (&lt;code>~/mail&lt;/code>) with
the remote mail server, and OpenSMTPD to relay emails sent with
&lt;code>/usr/sbin/sendmail&lt;/code> (also to the mail server). This enables Aerc to show the
emails and both Aerc and Git to send emails - even when offline.&lt;/p>
&lt;p>I really like this setup for my &amp;ldquo;normal&amp;rdquo; email conversations, but I believe
it really shines when used with the git email workflow where Aerc and Git can
be used (possibly offline) to review and send patches. And if you do not know
about the git email workflow, I recommend you read
&lt;a href="https://drewdevault.com/2018/07/02/Email-driven-git.html">some&lt;/a>
&lt;a href="https://drewdevault.com/2022/07/25/Code-review-with-aerc.html">of&lt;/a>
&lt;a href="https://begriffs.com/posts/2018-06-05-mailing-list-vs-github.html">these&lt;/a>
&lt;a href="https://web.archive.org/web/20180522180815/https://dpc.pw/blog/2017/08/youre-using-git-wrong/">resources&lt;/a>.&lt;/p>
&lt;h2 id="annex-other-resources">
&lt;a class="Heading-link u-clickable" href="https://www.acarg.ch/posts/aerc-email-setup/#annex-other-resources">Annex: other resources&lt;/a>
&lt;/h2>
&lt;p>In the process of setting up my system, I found the following resources very
helpful:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://drewdevault.com/2021/05/17/aerc-with-mbsync-postfix.html">Drew Devault&amp;rsquo;s post&lt;/a>
using Aerc, isync/mbsync and postfix&lt;/li>
&lt;li>&lt;a href="https://bence.ferdinandy.com/2023/07/20/email-in-the-terminal-a-complete-guide-to-the-unix-way-of-email">Bence Ferdinandy&amp;rsquo;s very detailed post&lt;/a>
using Aerc, isync/mbsync, msmtp(q), notmuch and even more (for authentication,
automation and address book).&lt;/li>
&lt;li>&lt;a href="https://www.seanh.cc/2021/08/22/backup-fastmail-and-gmail-with-isync/">Sean Hammond&amp;rsquo;s post&lt;/a>
explaining how to use isync/mbsync with Fastmail and GMail, and using Mutt.&lt;/li>
&lt;li>&lt;a href="https://man.sr.ht/~rjarry/aerc/integrations/notmuch.md">Aerc wiki&lt;/a>
describing a setup with Aerc, isync/mbsync and notmuch for ProtonMail.&lt;/li>
&lt;/ul>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>Or was it even Hotmail, before that?&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:2">
&lt;p>Note that I write &amp;ldquo;isync/mbsync&amp;rdquo; everywhere because the tool is
called &amp;ldquo;isync&amp;rdquo;, but the command to call it is &amp;ldquo;mbsync&amp;rdquo;.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>CMake superbuild</title><link>https://www.acarg.ch/posts/cmake-superbuild/</link><pubDate>Fri, 10 Mar 2023 00:37:56 +0100</pubDate><guid>https://www.acarg.ch/posts/cmake-superbuild/</guid><description>&lt;p>A superbuild is an approach in which a build system not only builds the main
project, but also its dependencies, as part of the same build process.
This post builds on top of the &lt;a href="https://www.acarg.ch/posts/cmake-deps/">previous one&lt;/a>
and explores ways to achieve this with CMake.&lt;/p>
&lt;blockquote>
&lt;p>TL;DR: In my opinion, a superbuild should act as a wrapper around a project
and not impose any specific dependency management approach on users. However,
with CMake I find that it introduces unnecessary indirections and overhead,
and therefore superbuilds should not be used. I provide a small example of a
CMake superbuild &lt;a href="https://github.com/acarg/cmake-deps-example-grpc-superbuild">here&lt;/a>.&lt;/p>
&lt;/blockquote>
&lt;h2 id="you-may-already-have-a-superbuild">
&lt;a class="Heading-link u-clickable" href="https://www.acarg.ch/posts/cmake-superbuild/#you-may-already-have-a-superbuild">You may already have a superbuild&lt;/a>
&lt;/h2>
&lt;p>If you manage your dependencies with &lt;code>add_subdirectory&lt;/code>, it is already a
superbuild. But it has downsides, as explained
&lt;a href="https://www.acarg.ch/posts/cmake-deps/#second-strategy-add_subdirectory">in the previous post&lt;/a>.
What if a dependency does not support CMake?
What if the user wants to use a package manager instead of building the
dependencies? Not to mention that a clean build will require rebuilding all
the dependencies.&lt;/p>
&lt;p>The &lt;a href="https://www.acarg.ch/posts/cmake-deps/#first-strategy-find_package">&lt;code>find_package&lt;/code>&lt;/a>
strategy, however, is not a superbuild itself (that&amp;rsquo;s the point, after all: it should
delegate the dependency management). But it can be used in a superbuild; let&amp;rsquo;s see how.&lt;/p>
&lt;h2 id="treat-your-project-like-a-dependency">
&lt;a class="Heading-link u-clickable" href="https://www.acarg.ch/posts/cmake-superbuild/#treat-your-project-like-a-dependency">Treat your project like a dependency&lt;/a>
&lt;/h2>
&lt;p>The idea is simple: write a script that will build the dependencies
(in the right order), and then the main project. At the point where the
main project is being built, CMake will find the dependencies that were
just built with &lt;code>find_package&lt;/code>.
Because it stays completely out of the main project, it can be written
in any language: shell, Python, or CMake.&lt;/p>
&lt;h3 id="with-a-shell-script">
&lt;a class="Heading-link u-clickable" href="https://www.acarg.ch/posts/cmake-superbuild/#with-a-shell-script">With a shell script&lt;/a>
&lt;/h3>
&lt;p>Let us consider a simple example where our main project (&lt;code>main&lt;/code>) has
two dependencies: &lt;code>depA&lt;/code> (using Automake) and &lt;code>depB&lt;/code> (using CMake).
Our main project depends on both, and &lt;code>depB&lt;/code> depends on &lt;code>depA&lt;/code>.&lt;/p>
&lt;p>The superbuild shell script (let&amp;rsquo;s call it &lt;code>build.sh&lt;/code>) would live in the
parent directory w.r.t. &lt;code>main&lt;/code>, like so:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── build.sh
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└── main
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └── CMakeLists.txt&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>Note that &lt;code>main&lt;/code> is a completely standard, standalone CMake project.
Assuming that the dependencies are installed on the system, it could
be built normally (from the parent directory) with:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">cmake -Bmain/build -Smain
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cmake --build main/build&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>For a superbuild, we want to build the dependencies instead of relying
on the system libraries. So we want &lt;code>build.sh&lt;/code> to:&lt;/p>
&lt;ol>
&lt;li>Fetch and build the dependencies&lt;/li>
&lt;li>Build the main project&lt;/li>
&lt;/ol>
&lt;p>Let&amp;rsquo;s first fetch the dependencies in order to better visualize the
directory tree. The script could start like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#!/bin/sh
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git clone https://git-repo-1.com/depA
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git clone https://git-repo-2.com/depB&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>Which would result in the following tree:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── build.sh
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── depA
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│   └── configure
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│   └── Makefile
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── depB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│   └── CMakeLists.txt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└── main
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └── CMakeLists.txt&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>Then it would need to build the projects in order, using
the right tooling (note how &lt;code>depA&lt;/code> uses Automake):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">&lt;span class="cp">#!/bin/sh
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="cp">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git clone https://example.com/depA
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">git clone https://example.com/depB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Build depA and install it in ./install&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">pushd&lt;/span> depA
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">./configure --prefix ../install
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">make install
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nb">popd&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Build depB and install it in ./install, too&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Note that depB depends on depA, so we point CMAKE_PREFIX_PATH&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># to ./install (where depA is installed)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cmake -DCMAKE_PREFIX_PATH&lt;span class="o">=&lt;/span>&lt;span class="k">$(&lt;/span>&lt;span class="nb">pwd&lt;/span>&lt;span class="k">)&lt;/span>/install -DCMAKE_INSTALL_PREFIX&lt;span class="o">=&lt;/span>install -BdepB/build -SdepB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cmake --build depB/build --target install
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Build our main project, also pointing CMAKE_PREFIX_PATH to ./install&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cmake -DCMAKE_PREFIX_PATH&lt;span class="o">=&lt;/span>&lt;span class="k">$(&lt;/span>&lt;span class="nb">pwd&lt;/span>&lt;span class="k">)&lt;/span>/install -Bmain/build -Smain
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cmake --build main/build&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>That&amp;rsquo;s it, we have our superbuild! Without changing anything in
the main CMakeLists.txt of the main project. Now the user can either build
&lt;code>./main/CMakeLists.txt&lt;/code> like a normal CMake project (that will try to find the
dependencies on the system), or call &lt;code>./build.sh&lt;/code> that will build and install
the dependencies locally (in &lt;code>./install&lt;/code>) and will then build the main
project with them.&lt;/p>
&lt;p>Though it works, this shell script is probably not cross-platform.
If we used CMake instead, then it would be better in that
regard&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>. Let&amp;rsquo;s see how we
can leverage our CMake helpers from the previous post.&lt;/p>
&lt;h3 id="with-cmake">
&lt;a class="Heading-link u-clickable" href="https://www.acarg.ch/posts/cmake-superbuild/#with-cmake">With CMake&lt;/a>
&lt;/h3>
&lt;p>We can achieve the same result as the &lt;code>build.sh&lt;/code> above with a CMake
script. It will look like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── CMakeLists.txt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└── main
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └── CMakeLists.txt&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>I find it important to realise that &lt;code>./CMakeLists.txt&lt;/code> and
&lt;code>./main/CMakeLists.txt&lt;/code> are &lt;em>two different CMake projects&lt;/em> (just like
&lt;code>build.sh&lt;/code> and &lt;code>./main/CMakeLists.txt&lt;/code> were two separate things)!
In other words, &lt;code>./CMakeLists.txt&lt;/code> &lt;strong>will not&lt;/strong> call &lt;code>add_subdirectory(main)&lt;/code>.
That is not the typical setup (with consequences that we will discuss later):
usually, there is a root CMakeLists that calls sub-CMakeLists with
&lt;code>add_subdirectory&lt;/code>, making a tree where all CMakeLists belong to the same
project. Here we really have two different CMake projects: the superbuild, and
the main project. But the superbuild will build multiple separate projects
(i.e. the main project and its dependencies).&lt;/p>
&lt;p>The superbuild CMakeLists (i.e. &lt;code>./CMakeLists.txt&lt;/code>) could look like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-cmake" data-lang="cmake">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cmake_minimum_required&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">VERSION&lt;/span> &lt;span class="s">3.10.2&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">project&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">superbuild&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">ExternalProject_Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">depA&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">PREFIX&lt;/span> &lt;span class="s">depA&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">GIT_REPOSITORY&lt;/span> &lt;span class="s">https://example.com/depA&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">CONFIGURE_COMMAND&lt;/span> &lt;span class="s">&amp;lt;SOURCE_DIR&amp;gt;/configure&lt;/span> &lt;span class="s">--prefix=&lt;/span>&lt;span class="o">${&lt;/span>&lt;span class="nv">CMAKE_INSTALL_PREFIX&lt;/span>&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">BUILD_COMMAND&lt;/span> &lt;span class="o">${&lt;/span>&lt;span class="nv">CMAKE_MAKE_PROGRAM&lt;/span>&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">INSTALL_COMMAND&lt;/span> &lt;span class="o">${&lt;/span>&lt;span class="nv">CMAKE_MAKE_PROGRAM&lt;/span>&lt;span class="o">}&lt;/span> &lt;span class="s">install&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">ExternalProject_Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">depB&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">PREFIX&lt;/span> &lt;span class="s">depA&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">GIT_REPOSITORY&lt;/span> &lt;span class="s">https://example.com/depB&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">CMAKE_CACHE_ARGS&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">-DCMAKE_INSTALL_PREFIX=&lt;/span>&lt;span class="o">${&lt;/span>&lt;span class="nv">CMAKE_INSTALL_PREFIX&lt;/span>&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">-DCMAKE_PREFIX_PATH=&lt;/span>&lt;span class="o">${&lt;/span>&lt;span class="nv">CMAKE_PREFIX_PATH&lt;/span>&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">DEPENDS&lt;/span> &lt;span class="s">depA&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">ExternalProject_Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">main&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">PREFIX&lt;/span> &lt;span class="s">main&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">URL&lt;/span> &lt;span class="o">${&lt;/span>&lt;span class="nv">CMAKE_CURRENT_SOURCE_DIR&lt;/span>&lt;span class="o">}&lt;/span>&lt;span class="s">/main&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">CMAKE_CACHE_ARGS&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">-DCMAKE_INSTALL_PREFIX=&lt;/span>&lt;span class="o">${&lt;/span>&lt;span class="nv">CMAKE_INSTALL_PREFIX&lt;/span>&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">-DCMAKE_PREFIX_PATH=&lt;/span>&lt;span class="o">${&lt;/span>&lt;span class="nv">CMAKE_PREFIX_PATH&lt;/span>&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">DEPENDS&lt;/span> &lt;span class="s">depA&lt;/span> &lt;span class="s">depB&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>And it would be called like so:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">cmake -DCMAKE_INSTALL_PREFIX&lt;span class="o">=&lt;/span>install -DCMAKE_PREFIX_PATH&lt;span class="o">=&lt;/span>&lt;span class="k">$(&lt;/span>&lt;span class="nb">pwd&lt;/span>&lt;span class="k">)&lt;/span>/install -Bbuild -S.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cmake --build build&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>This is now equivalent to the superbuild setup we had with &lt;code>build.sh&lt;/code>, except
that instead of a shell script, we now use a CMake script. Again, the user can
choose to run the superbuild or to directly build the main project (and use the
dependencies installed on the system).&lt;/p>
&lt;p>A real-life example of a one-file superbuild can be found
&lt;a href="https://github.com/grpc/grpc/blob/v1.46.4/examples/cpp/helloworld/cmake_externalproject/CMakeLists.txt">here in the gRPC examples&lt;/a>.&lt;/p>
&lt;h3 id="with-cmake-and-helper-scripts">
&lt;a class="Heading-link u-clickable" href="https://www.acarg.ch/posts/cmake-superbuild/#with-cmake-and-helper-scripts">With CMake and helper scripts&lt;/a>
&lt;/h3>
&lt;p>Both superbuild scripts above look quite simple, but they don&amp;rsquo;t involved many
dependencies, platforms and options. In reality, that file will tend to grow and
become harder to maintain. That is why I would advise using a structure similar
to &lt;a href="https://www.acarg.ch/posts/cmake-deps/#the-helper-subproject">my previous post&lt;/a>,
but adapted for the superbuild. With the CMake superbuild script, it looks
like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-sh" data-lang="sh">&lt;span class="line">&lt;span class="cl">.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── CMakeLists.txt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── dependencies
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│   ├── depA
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│   │   ├── Makefile
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│   │   └── configure
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│   └── depB
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│   └── CMakeLists.txt
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└── main
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> └── CMakeLists.txt&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>The superbuild CMakeLists may now look like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-cmake" data-lang="cmake">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cmake_minimum_required&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">VERSION&lt;/span> &lt;span class="s">3.10.2&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">project&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">superbuild&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">add_subdirectory&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">dependencies&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">ExternalProject_Add&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">main&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">PREFIX&lt;/span> &lt;span class="s">main&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">URL&lt;/span> &lt;span class="o">${&lt;/span>&lt;span class="nv">CMAKE_CURRENT_SOURCE_DIR&lt;/span>&lt;span class="o">}&lt;/span>&lt;span class="s">/main&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">CMAKE_CACHE_ARGS&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">-DCMAKE_INSTALL_PREFIX=&lt;/span>&lt;span class="o">${&lt;/span>&lt;span class="nv">CMAKE_INSTALL_PREFIX&lt;/span>&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">-DCMAKE_PREFIX_PATH=&lt;/span>&lt;span class="o">${&lt;/span>&lt;span class="nv">CMAKE_PREFIX_PATH&lt;/span>&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">DEPENDS&lt;/span> &lt;span class="s">depA&lt;/span> &lt;span class="s">depB&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>Now the superbuild calls the &lt;code>dependencies&lt;/code> project with &lt;code>add_subdirectory()&lt;/code>,
which in turn will call each dependency (also with &lt;code>add_subdirectory()&lt;/code>), which
will eventually call &lt;code>ExternalProject_Add()&lt;/code> for each dependency. And finally it
will call &lt;code>ExternalProject_Add()&lt;/code> for the main project.&lt;/p>
&lt;p>I do believe that this is the best structure for a superbuild, because:&lt;/p>
&lt;ul>
&lt;li>It doesn&amp;rsquo;t pollute the main project, which remains a standalone CMake project
that can be built manually with the system libraries. You can even have your
CI test that building with the system libraries keeps working.&lt;/li>
&lt;li>It is just a wrapper around the dependencies, which can still be built without
the superbuild scheme (again, the CI can test it).&lt;/li>
&lt;li>It doesn&amp;rsquo;t use any exotic/advanced CMake features (and &lt;code>ExternalProject_Add&lt;/code>
has been supported for many years, it is virtually always available).&lt;/li>
&lt;li>The superbuild script does not have to be written with CMake. It can be done
with shell scripts (by adapting the &lt;code>build.sh&lt;/code> example above) or any
language you want.&lt;/li>
&lt;/ul>
&lt;p>At the beginning of this post, I advised against superbuilds. Let&amp;rsquo;s now look at
the disadvantages of this design&lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup>.&lt;/p>
&lt;h2 id="disadvantages">
&lt;a class="Heading-link u-clickable" href="https://www.acarg.ch/posts/cmake-superbuild/#disadvantages">Disadvantages&lt;/a>
&lt;/h2>
&lt;p>A superbuild is an abstraction on top of the build, and the goal of an
abstraction is to make something either easier or faster to use. In this case,
it tries to make it easier for users to build the project. But it comes at a
cost.&lt;/p>
&lt;h3 id="abstractions-reduce-flexibility">
&lt;a class="Heading-link u-clickable" href="https://www.acarg.ch/posts/cmake-superbuild/#abstractions-reduce-flexibility">Abstractions reduce flexibility&lt;/a>
&lt;/h3>
&lt;p>By definition, an abstraction cannot offer all the features of the lower-level
interface. Therefore, choices have to be made for every feature that gets
abstracted away.
For instance with the helper scripts, the user can decide where the
built dependencies should be installed (with &lt;code>CMAKE_INSTALL_PREFIX&lt;/code>). They can
choose to install the dependencies in some place, and the main project in
another place (for instance in order to cache the dependencies instead of
rebuilding them over and over).&lt;/p>
&lt;p>If we want to keep this flexibility in the superbuild, we will probably have
to add an option like &lt;code>-DDEPENDENCIES_INSTALL_PREFIX&lt;/code> and document it. It may
add complexity: when &lt;code>-DCMAKE_INSTALL_PREFIX&lt;/code> is specified alone, should it
impact both the main project and the dependencies, or only the main project? Or
should we add another option, e.g. &lt;code>-DMAIN_INSTALL_PREFIX&lt;/code>? But then what do we
do with &lt;code>-DCMAKE_INSTALL_PREFIX&lt;/code>?&lt;/p>
&lt;p>For every option we may want to add to the superbuild, we will have to think
about how it impacts the dependencies, how it impacts the main project, and how
to forward the option properly. And it is likely that from time to time,
some user of the superbuild will want to add an option in order to
access a lower-level feature that got abstracted away. And as a maintainer,
you will have to decide whether you want to keep adding features (which will
progressively destroy your abstraction) or to refuse to do it (which may piss
off the users). That is a situation I personally do not like to be in.&lt;/p>
&lt;h3 id="abstractions-need-to-be-maintained">
&lt;a class="Heading-link u-clickable" href="https://www.acarg.ch/posts/cmake-superbuild/#abstractions-need-to-be-maintained">Abstractions need to be maintained&lt;/a>
&lt;/h3>
&lt;p>Adding an abstraction should - as much as possible - not prevent advanced users
from using the lower-level interface. That is why we use &lt;code>find_package&lt;/code> instead
of &lt;code>add_subdirectory&lt;/code> in the first place: a package maintainer (e.g. for a Linux
distribution) will always want to use the system libraries. And as we saw above,
a user may want to build the dependencies separately instead of using the
superbuild.&lt;/p>
&lt;p>Our design allows all of that, but it means that we have to maintain three ways
of building the project (system libraries, helper scripts, superbuild). This is
extra work, and maintainers usually have enough to do already.&lt;/p>
&lt;h3 id="abstractions-keep-users-ignorant">
&lt;a class="Heading-link u-clickable" href="https://www.acarg.ch/posts/cmake-superbuild/#abstractions-keep-users-ignorant">Abstractions keep users ignorant&lt;/a>
&lt;/h3>
&lt;p>It is not always a bad thing; I could not use my computer without abstractions.
But it is a tradeoff, and it should not be abused. In my experience with
superbuilds, I have seen users recompiling all the dependencies every time they
needed a clean build in the main project, and then complaining that the build
&amp;ldquo;took forever&amp;rdquo;. And I honestly could not blame them, because the build system
was not obvious to those unfamiliar with CMake. Whereas without the superbuild
script, it is suddenly obvious that the &lt;code>CMakeLists.txt&lt;/code> at the root is the main
project, and that &lt;code>./dependencies/CMakeLists.txt&lt;/code> is for the dependencies.&lt;/p>
&lt;p>Beginners still have to learn how to call CMake, but they are C++ developers:
they will have to learn that anyway. I may as well teach them how to do it with
my project rather than putting effort into maintaining a superbuild abstraction.&lt;/p>
&lt;h2 id="conclusion">
&lt;a class="Heading-link u-clickable" href="https://www.acarg.ch/posts/cmake-superbuild/#conclusion">Conclusion&lt;/a>
&lt;/h2>
&lt;p>I have designed and used superbuilds myself, and I used to think that it was a
good idea. But my experience is that they are usually&lt;sup id="fnref1:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup> an
unnecessary abstraction that requires maintenance work and risks making the
build system convoluted.&lt;/p>
&lt;p>I did my best to explain how I believe it should be done, but my opinion is that
it should not be done. Instead I would recommend delegating the dependency
management to the user (by using the system libraries with &lt;code>find_package&lt;/code>),
possibly providing &lt;a href="https://www.acarg.ch/posts/cmake-deps/#the-helper-subproject">helper scripts&lt;/a>
if there is a need to build the dependencies from source (I typically do that
when I need to cross-compile for Android or iOS).&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>In my experience, it still requires some care,
e.g. when applying patches in &lt;code>ExternalProject_Add&lt;/code> on Windows. But other than
that it works quite well.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:2">
&lt;p>At least with CMake.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&amp;#160;&lt;a href="#fnref1:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>Handling dependencies with CMake</title><link>https://www.acarg.ch/posts/cmake-deps/</link><pubDate>Sun, 10 Jul 2022 22:13:47 +0200</pubDate><guid>https://www.acarg.ch/posts/cmake-deps/</guid><description>&lt;p>Over the years, I have seen mainly two strategies (or variants
of them) being used to handle
dependencies in CMake projects. Because I only like one of them, this
post is about explaining why my preferred strategy is better.&lt;/p>
&lt;blockquote>
&lt;p>TL;DR: I strongly believe that a CMake project should use &lt;code>find_package&lt;/code>
instead of &lt;code>add_subdirectory&lt;/code>. Feel free to try building
&lt;a href="https://github.com/acarg/cmake-deps-example-proxygen/">this Proxygen example&lt;/a> or
&lt;a href="https://github.com/acarg/cmake-deps-example-grpc/">this gRPC example&lt;/a>
before reading if you want to.&lt;/p>
&lt;/blockquote>
&lt;h2 id="first-strategy-find_package">
&lt;a class="Heading-link u-clickable" href="https://www.acarg.ch/posts/cmake-deps/#first-strategy-find_package">First strategy: &lt;code>find_package&lt;/code>&lt;/a>
&lt;/h2>
&lt;p>The first strategy is about realising that CMake is not a package manager,
and therefore delegating package management to the user (who can install
the dependency using their preferred way). This is typically the favoured
solution when starting a small project.&lt;/p>
&lt;p>For instance:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-cmake" data-lang="cmake">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cmake_minimum_required&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">VERSION&lt;/span> &lt;span class="s">3.10.2&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">project&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">hello_world&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">find_package&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">OpenSSL&lt;/span> &lt;span class="s">REQUIRED&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">add_executable&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">hello_world&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">main.cpp&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">target_link_libraries&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">hello_world&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">OpenSSL::SSL&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>Here, we do rely on &lt;code>find_package&lt;/code> to &amp;ldquo;find&amp;rdquo; OpenSSL, following some rules
(&lt;a href="https://cmake.org/cmake/help/latest/command/find_package.html#config-mode-search-procedure">documented here&lt;/a>), which implies that the user
installed OpenSSL before (for instance using &lt;code>apt install&lt;/code>).
If OpenSSL is not to be found, CMake will complain during the configure
step.&lt;/p>
&lt;p>&amp;ldquo;Well, &lt;code>apt install libssl-dev&lt;/code> is nice, but what if &lt;code>apt&lt;/code> does not
provide my dependency, or not the version
of the dependency I want?&amp;rdquo;, you ask. That is where I believe the
second strategy becomes (wrongfully) popular.&lt;/p>
&lt;h2 id="second-strategy-add_subdirectory">
&lt;a class="Heading-link u-clickable" href="https://www.acarg.ch/posts/cmake-deps/#second-strategy-add_subdirectory">Second strategy: &lt;code>add_subdirectory&lt;/code>&lt;/a>
&lt;/h2>
&lt;p>As a project grows, there often comes a need for a dependency (or a
version of it) that is not provided by the system package manager.
At this point it is not enough to just &lt;code>apt install &amp;lt;dependency&amp;gt;&lt;/code>.
There are many ways to handle that, but it seems like adding the
dependency as a git submodule and building it inside the project with
&lt;code>add_subdirectory&lt;/code> has become popular.&lt;/p>
&lt;p>As an example, say we do not want to use GoogleTest (&lt;code>libgtest&lt;/code>) from
the system.
We can add it as a git submodule, say &lt;code>gtest/&lt;/code>, and use it from CMake:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-cmake" data-lang="cmake">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cmake_minimum_required&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">VERSION&lt;/span> &lt;span class="s">3.10.2&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">project&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">hello_world&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">add_executable&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">hello_world&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">main.cpp&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">add_subdirectory&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">gtest&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">target_include_directories&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">hello_world&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">SYSTEM&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">PRIVATE&lt;/span> &lt;span class="o">${&lt;/span>&lt;span class="nv">PROJECT_SOURCE_DIR&lt;/span>&lt;span class="o">}&lt;/span>&lt;span class="s">/gtest/googletest/include&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">target_link_libraries&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">hello_world&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">gtest&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">gtest_main&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>There are alternatives to the git submodule, such as CMake&amp;rsquo;s own
&lt;a href="https://cmake.org/cmake/help/latest/module/FetchContent.html">FetchContent&lt;/a> and of course wrappers on top of that like
&lt;a href="https://github.com/cpm-cmake/CPM.cmake">CPM&lt;/a>. But that is not
fundamentally different; at the end of the day, this strategy builds the
dependency with &lt;code>add_subdirectory&lt;/code>.&lt;/p>
&lt;p>&lt;strong>I argue that handling dependencies with &lt;code>add_subdirectory&lt;/code> is flawed:&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>What happens if your dependency does not support CMake? Say it uses
Autotools, Meson or a custom build system instead? OpenSSL and Boost do
not support CMake, for instance.&lt;/li>
&lt;li>How does it work with transitive dependencies? Say your project runs
&lt;code>add_subdirectory(libA)&lt;/code> &lt;em>and&lt;/em> &lt;code>add_subdirectory(libB)&lt;/code>, but &lt;code>libA&lt;/code> also
runs &lt;code>add_subdirectory(libB)&lt;/code>.
What happens then? Which version of &lt;code>libB&lt;/code> is being used?&lt;/li>
&lt;li>Every clean build (e.g. in the CI) has to recompile all your
dependencies. Isn&amp;rsquo;t it nicer (and potentially much faster) when the
library can just be installed somewhere (or copied from a cache) once and
for all?&lt;/li>
&lt;li>You force the consumers of your library to use your strategy.
What if I want to use your library, but I do not want you to include
OpenSSL through &lt;code>add_subdirectory(openssl)&lt;/code> (maybe I want to use LibreSSL)?&lt;/li>
&lt;/ol>
&lt;p>The &lt;code>find_package&lt;/code> strategy does not have those problems. So why are so
many projects using &lt;code>add_subdirectory&lt;/code>? I believe that the answer is that
&lt;em>it feels simpler&lt;/em>.&lt;/p>
&lt;h2 id="delegate-but-assist">
&lt;a class="Heading-link u-clickable" href="https://www.acarg.ch/posts/cmake-deps/#delegate-but-assist">Delegate, but assist&lt;/a>
&lt;/h2>
&lt;p>Ok, so delegating the package management to the user (i.e. using
&lt;code>find_package&lt;/code>) is nicer, but &amp;ldquo;copying&amp;rdquo; the dependencies inside the
project (i.e. using &lt;code>add_subdirectory&lt;/code>) is simpler.&lt;/p>
&lt;p>I believe that the solution is to use &lt;code>find_package&lt;/code>, but to provide
helpers if necessary, so that we get the best of both worlds:
those who know what they are doing can handle dependencies the
way they like, and those who don&amp;rsquo;t can run the helper script.&lt;/p>
&lt;p>But before getting to the helper script, I would like to review different
ways one can handle dependencies.&lt;/p>
&lt;h3 id="using-the-system-package-manager">
&lt;a class="Heading-link u-clickable" href="https://www.acarg.ch/posts/cmake-deps/#using-the-system-package-manager">Using the system package manager&lt;/a>
&lt;/h3>
&lt;p>As mentioned above, the easiest way to get a dependency is to install
it globally on the system using the system package manager. For instance,
&lt;code>apt install libssl-dev&lt;/code> for the corresponding &lt;code>find_package(OpenSSL)&lt;/code>.&lt;/p>
&lt;p>The problem is that you may rely on a library that is not provided by
the system package manager. Another issue arises when you need to
cross-compile your library (and its dependencies), because typically the
system package manager only provides packages for the host architecture.&lt;/p>
&lt;h3 id="manually-building-and-installing-_globally_">
&lt;a class="Heading-link u-clickable" href="https://www.acarg.ch/posts/cmake-deps/#manually-building-and-installing-_globally_">Manually building and installing &lt;em>globally&lt;/em>&lt;/a>
&lt;/h3>
&lt;p>Instead of using the system package manager (which may not have your
dependency), you can install the dependency manually.&lt;/p>
&lt;p>For instance, for a CMake library:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">cmake -Bbuild -S.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo cmake --build build --target install&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>Using Autotools, it could look like this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">./configure
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">make
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">sudo make install&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>And with Meson:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">meson builddir &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nb">cd&lt;/span> builddir
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">meson compile
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">meson install&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>That should result in the dependency being installed on your system,
similar to what the system package manager would do. Except that the
package manager cannot know about this, and it could create conflicts.
I don&amp;rsquo;t like this way myself (and I would discourage the use of it), but
I admittedly used to do it years ago, when I was blindly following
instructions saying stuff like &amp;ldquo;now run &lt;code>make &amp;amp;&amp;amp; sudo make install&lt;/code>&amp;rdquo;.
I still see a lot of READMEs doing that, so I&amp;rsquo;m pretty sure it is still
used extensively.&lt;/p>
&lt;h3 id="manually-building-and-installing-_locally_">
&lt;a class="Heading-link u-clickable" href="https://www.acarg.ch/posts/cmake-deps/#manually-building-and-installing-_locally_">Manually building and installing &lt;em>locally&lt;/em>&lt;/a>
&lt;/h3>
&lt;p>Less known is the fact that one can install a project locally, by
specifying an install path in the CMake configure step:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">cmake -DCMAKE_INSTALL_PREFIX&lt;span class="o">=&lt;/span>/path/to/local/install -Bbuild -S.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cmake --build build --target install&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>Using Autotools, we can specify the installation prefix at
configure time:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">./configure --prefix&lt;span class="o">=&lt;/span>/path/to/local/install
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">make
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">make install&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>Similarly with Meson, we can use &lt;code>--prefix&lt;/code> in the configure step:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">meson builddir &lt;span class="o">&amp;amp;&amp;amp;&lt;/span> &lt;span class="nb">cd&lt;/span> builddir
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">meson configure --prefix /path/to/local/install
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">meson compile
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">meson install&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>The trick with locally-installed dependencies is that we need to
inform CMake about the installation path (how would &lt;code>find_package&lt;/code> guess
otherwise?). This is done
using &lt;code>CMAKE_PREFIX_PATH&lt;/code>. So when you build your &lt;em>project&lt;/em> (the lines
above built the &lt;em>dependency&lt;/em>), you have to specify this path in the CMake
configure step:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">cmake -DCMAKE_PREFIX_PATH&lt;span class="o">=&lt;/span>/path/to/local/install -Bbuild -S.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cmake --build build&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>Let me repeat that:&lt;/p>
&lt;ul>
&lt;li>Build the dependency with &lt;code>-DCMAKE_INSTALL_PREFIX&lt;/code> (CMake) or &lt;code>--prefix&lt;/code> (Autoconf, Meson) to specify &lt;em>where it should be installed&lt;/em>.&lt;/li>
&lt;li>Build the project with &lt;code>-DCMAKE_PREFIX_PATH&lt;/code> to tell CMake &lt;em>where it
can find the dependencies&lt;/em>.&lt;/li>
&lt;/ul>
&lt;p>Spoiler alert: this is my favourite, because I can keep the dependencies
per project, and it&amp;rsquo;s super easy to clean them (to me it feels close to
using a venv in Python). Also it is very convenient when cross-compiling.
For these reasons, that is what the helper script will leverage.&lt;/p>
&lt;h3 id="using-another-package-manager">
&lt;a class="Heading-link u-clickable" href="https://www.acarg.ch/posts/cmake-deps/#using-another-package-manager">Using another package manager&lt;/a>
&lt;/h3>
&lt;p>Instead of relying on your system package manager, there are alternatives
out there, like Conan. I am not familiar with them, but as long as they
are compatible with the &lt;code>find_package&lt;/code> syntax, I don&amp;rsquo;t see a problem with
them. For instance Conan provides a &lt;a href="https://docs.conan.io/en/latest/integrations/build_system/cmake/cmake_find_package_generator.html">find_package_generator&lt;/a>.&lt;/p>
&lt;p>The one requirement I have for such a package manager is that it
should be totally optional. For instance the &lt;code>find_package_generator&lt;/code> of
Conan is compatible with &lt;code>CMAKE_PREFIX_PATH&lt;/code>. As a user, I want to have the
choice to use Conan (and set &lt;code>CMAKE_PREFIX_PATH&lt;/code> accordingly) or to use
whatever system I want (even build the dependencies manually).&lt;/p>
&lt;p>I am not a huge fan of &lt;a href="https://hunter.readthedocs.io/en/latest/index.html">Hunter&lt;/a> because it requires changes in the CMakeLists. But again, as long
as it is made optional and I am free to handle the dependencies my way,
I am fine with it.&lt;/p>
&lt;p>Finally, the problem with &amp;ldquo;third party&amp;rdquo; package managers is that you may
need to maintain your own repository (if your dependencies are not
available in the official repo). This is not only costly, but you cannot
really expect users of your library to rely on your package manager and
your repository. Which brings us to my main point again: use a package
manager as a helper if you want to, but make it optional: your CMakeLists
should not know about it at all, and instead rely on &lt;code>find_package&lt;/code>.&lt;/p>
&lt;h3 id="the-helper-subproject">
&lt;a class="Heading-link u-clickable" href="https://www.acarg.ch/posts/cmake-deps/#the-helper-subproject">The helper subproject&lt;/a>
&lt;/h3>
&lt;p>As we have seen above, the goal is to use &lt;code>find_package()&lt;/code> for all the
dependencies in our CMakeLists, and to let the user make sure that they
are found (by using a package manager or building and installing them manually). But nothing prevents us from providing a helper script that
fetches, builds and installs all our dependencies locally.&lt;/p>
&lt;p>I have been mentioning a script, but the way I do it is actually more
of a CMake subproject. I like it because it can be cross-platform, but the
point is really that it builds and installs dependencies locally somewhere.&lt;/p>
&lt;h4 id="using-the-helper">
&lt;a class="Heading-link u-clickable" href="https://www.acarg.ch/posts/cmake-deps/#using-the-helper">Using the helper&lt;/a>
&lt;/h4>
&lt;p>Let&amp;rsquo;s first see how the helper is being used:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Run the helper script, installing the dependencies in `./dependencies/install`&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cmake -DCMAKE_INSTALL_PREFIX&lt;span class="o">=&lt;/span>dependencies/install -Bdependencies/build -Sdependencies
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cmake --build dependencies/build
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Build the project, telling `find_package()` where to find dependencies&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cmake -DCMAKE_PREFIX_PATH&lt;span class="o">=&lt;/span>&lt;span class="k">$(&lt;/span>&lt;span class="nb">pwd&lt;/span>&lt;span class="k">)&lt;/span>/dependencies/install -Bbuild -S.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cmake --build build&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>For a user of your project, that&amp;rsquo;s all it takes: running two CMake
projects:&lt;/p>
&lt;ol>
&lt;li>The first one builds the dependencies and installs them in a location
&lt;em>chosen by the user&lt;/em> (here &lt;code>./dependencies/install&lt;/code>).&lt;/li>
&lt;li>The second one builds the project, using the dependencies installed in
step 1 (specified with &lt;code>CMAKE_PREFIX_PATH&lt;/code>).&lt;/li>
&lt;/ol>
&lt;p>From the user perspective, is that really more difficult than handling git submodules? I don&amp;rsquo;t think so.&lt;/p>
&lt;h4 id="writing-the-helper">
&lt;a class="Heading-link u-clickable" href="https://www.acarg.ch/posts/cmake-deps/#writing-the-helper">Writing the helper&lt;/a>
&lt;/h4>
&lt;p>The helper script builds and installs all our dependencies locally
by leveraging an old CMake feature: &lt;a href="https://cmake.org/cmake/help/latest/module/ExternalProject.html">ExternalProject&lt;/a>.&lt;/p>
&lt;p>It is extremely easy; the syntax looks like this (there are more options
nicely described in
&lt;a href="https://cmake.org/cmake/help/latest/module/ExternalProject.html">the documentation&lt;/a>):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-cmake" data-lang="cmake">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cmake_minimum_required&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">VERSION&lt;/span> &lt;span class="s">3.10.2&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">project&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">my-dependency&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">include&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">ExternalProject&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">ExternalProject_add&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;lt;dependency&lt;/span> &lt;span class="s">name&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">URL&lt;/span> &lt;span class="s">&amp;lt;link&lt;/span> &lt;span class="s">to&lt;/span> &lt;span class="s">sources&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">PREFIX&lt;/span> &lt;span class="s">&amp;lt;build&lt;/span> &lt;span class="s">directory&lt;/span> &lt;span class="s">for&lt;/span> &lt;span class="s">this&lt;/span> &lt;span class="s">dependency&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">CONFIGURE_COMMAND&lt;/span> &lt;span class="s">&amp;lt;configure&lt;/span> &lt;span class="s">command&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">BUILD_COMMAND&lt;/span> &lt;span class="s">&amp;lt;build&lt;/span> &lt;span class="s">command&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">CMAKE_ARGS&lt;/span> &lt;span class="s">&amp;lt;args&lt;/span> &lt;span class="s">passed&lt;/span> &lt;span class="s">to&lt;/span> &lt;span class="s">the&lt;/span> &lt;span class="s">cmake&lt;/span> &lt;span class="s">commands&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="s">if&lt;/span> &lt;span class="s">relevant&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="s">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>The beauty of it is that while it uses CMake to build and install the
dependency, it does not require the final project to use CMake. You could
use this to install dependencies locally, and then use them from a Meson
or Autotools-based project! It also means that instead of using CMake with
&lt;code>ExternalProject_add&lt;/code>, you are free to write your helper in shell, or
python, or whatever you want; the whole point is that this is completely
transparent to your main CMake project.&lt;/p>
&lt;p>Let&amp;rsquo;s see a few examples below (with many more
&lt;a href="https://github.com/acarg/cmake-deps">available here&lt;/a>).&lt;/p>
&lt;h5 id="a-cmake-dependency-re2">
&lt;a class="Heading-link u-clickable" href="https://www.acarg.ch/posts/cmake-deps/#a-cmake-dependency-re2">A CMake dependency: re2&lt;/a>
&lt;/h5>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-cmake" data-lang="cmake">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cmake_minimum_required&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">VERSION&lt;/span> &lt;span class="s">3.1&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">project&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">external-re2&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">include&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">ExternalProject&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">list&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">APPEND&lt;/span> &lt;span class="s">CMAKE_ARGS&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;-DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_INSTALL_PREFIX}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;-DCMAKE_POSITION_INDEPENDENT_CODE=ON&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;-DBUILD_SHARED_LIBS=OFF&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">ExternalProject_add&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">re2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">URL&lt;/span> &lt;span class="s">https://github.com/google/re2/archive/2022-04-01.tar.gz&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">PREFIX&lt;/span> &lt;span class="s">re2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">CMAKE_ARGS&lt;/span> &lt;span class="s2">&amp;#34;${CMAKE_ARGS}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>Feel free to try building it with:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">cmake -DCMAKE_INSTALL_PREFIX&lt;span class="o">=&lt;/span>install -Bbuild -S.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cmake --build build&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>The build step will run &lt;code>ExternalProject_Add&lt;/code> which will fetch, configure,
build and install &lt;code>re2&lt;/code> into &lt;code>./install&lt;/code>. See how I passed
&lt;code>CMAKE_INSTALL_PREFIX&lt;/code> through &lt;code>CMAKE_ARGS&lt;/code>? &lt;code>CMAKE_ARGS&lt;/code> is also an
opportunity to pass all the CMake options you want for this dependency,
e.g. &lt;code>-DBUILD_TESTING=OFF&lt;/code>, &lt;code>-DBUILD_SHARED_LIBS=OFF&lt;/code>, &lt;code>-DSOME_OPTION=ON&lt;/code>,
you name it.&lt;/p>
&lt;p>Also note the use of &lt;code>URL&lt;/code> to download a tarball containing the sources.
This can be replaced by &lt;code>GIT_REPOSITORY&lt;/code> and &lt;code>GIT_TAG&lt;/code> (see for instance &lt;a href="https://github.com/acarg/cmake-deps/blob/main/dependencies/jsoncpp/CMakeLists.txt#L32-L33">jsoncpp here&lt;/a>).&lt;/p>
&lt;h5 id="an-autotools-dependency-libsodium">
&lt;a class="Heading-link u-clickable" href="https://www.acarg.ch/posts/cmake-deps/#an-autotools-dependency-libsodium">An Autotools dependency: libsodium&lt;/a>
&lt;/h5>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-cmake" data-lang="cmake">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cmake_minimum_required&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">VERSION&lt;/span> &lt;span class="s">3.10.2&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">project&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">external-sodium&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">include&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">ExternalProject&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">ExternalProject_add&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">sodium&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">URL&lt;/span> &lt;span class="s">https://download.libsodium.org/libsodium/releases/libsodium-1.0.18-stable.tar.gz&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">PREFIX&lt;/span> &lt;span class="s">sodium&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">CONFIGURE_COMMAND&lt;/span> &lt;span class="s">&amp;lt;SOURCE_DIR&amp;gt;/configure&lt;/span> &lt;span class="s">--prefix=&lt;/span>&lt;span class="o">${&lt;/span>&lt;span class="nv">CMAKE_INSTALL_PREFIX&lt;/span>&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">BUILD_COMMAND&lt;/span> &lt;span class="s">make&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>It can be run with the same commands as above:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">cmake -DCMAKE_INSTALL_PREFIX&lt;span class="o">=&lt;/span>install -Bbuild -S.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cmake --build build&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>And it will result in &lt;code>libsodium&lt;/code> being installed in &lt;code>./install&lt;/code>. Only
this time it will have used Autotools (see the &lt;code>CONFIGURE_COMMAND&lt;/code> and the
&lt;code>BUILD_COMMAND&lt;/code>).&lt;/p>
&lt;h5 id="a-custom-build-system-boost">
&lt;a class="Heading-link u-clickable" href="https://www.acarg.ch/posts/cmake-deps/#a-custom-build-system-boost">A custom build system: boost&lt;/a>
&lt;/h5>
&lt;p>&lt;code>ExternalProject&lt;/code> is very versatile, as can be seen when building Boost
using their custom build system:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-cmake" data-lang="cmake">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cmake_minimum_required&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">VERSION&lt;/span> &lt;span class="s">3.10.2&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">project&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">external-boost&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">include&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">ExternalProject&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">ExternalProject_add&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">boost&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">URL&lt;/span> &lt;span class="s">https://boostorg.jfrog.io/artifactory/main/release/1.80.0/source/boost_1_80_0.tar.gz&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">PREFIX&lt;/span> &lt;span class="s">boost&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">CONFIGURE_COMMAND&lt;/span> &lt;span class="s">&amp;lt;SOURCE_DIR&amp;gt;/bootstrap.sh&lt;/span> &lt;span class="s">--prefix=&lt;/span>&lt;span class="o">${&lt;/span>&lt;span class="nv">CMAKE_INSTALL_PREFIX&lt;/span>&lt;span class="o">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">BUILD_COMMAND&lt;/span> &lt;span class="s">&amp;lt;SOURCE_DIR&amp;gt;/b2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">BUILD_IN_SOURCE&lt;/span> &lt;span class="s">TRUE&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">INSTALL_COMMAND&lt;/span> &lt;span class="s">&amp;lt;SOURCE_DIR&amp;gt;/b2&lt;/span> &lt;span class="s">install&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;h5 id="all-together">
&lt;a class="Heading-link u-clickable" href="https://www.acarg.ch/posts/cmake-deps/#all-together">All together&lt;/a>
&lt;/h5>
&lt;p>The examples above show how to build one dependency using
&lt;code>ExternalProject&lt;/code>, and all that remains now is to group them into one,
by just calling all of them from one parent CMakeLists:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-cmake" data-lang="cmake">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cmake_minimum_required&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">VERSION&lt;/span> &lt;span class="s">3.10.2&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">project&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">dependencies&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">add_subdirectory&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">re2&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">add_subdirectory&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">sodium&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">add_subdirectory&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">boost&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>One more note: in the case of transitive dependencies, we need to install
the dependencies in the right order and let new ones know about the install
path. So typically the dependencies are built like this (when there are
transitive dependencies):&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">cmake -DCMAKE_PREFIX_PATH&lt;span class="o">=&lt;/span>&lt;span class="k">$(&lt;/span>&lt;span class="nb">pwd&lt;/span>&lt;span class="k">)&lt;/span>/install -DCMAKE_INSTALL_PREFIX&lt;span class="o">=&lt;/span>install -Bbuild -S.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">cmake --build build&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>This just means that dependencies will be installed into &lt;code>./install&lt;/code>
(as per &lt;code>CMAKE_INSTALL_PREFIX&lt;/code>), &lt;em>and&lt;/em> CMake will look for dependencies
in &lt;code>$(pwd)/install&lt;/code> (as per &lt;code>CMAKE_PREFIX_PATH&lt;/code>) when it encounters a
&lt;code>find_package&lt;/code> instruction.&lt;/p>
&lt;h2 id="conclusion">
&lt;a class="Heading-link u-clickable" href="https://www.acarg.ch/posts/cmake-deps/#conclusion">Conclusion&lt;/a>
&lt;/h2>
&lt;p>CMake is not a package manager, and therefore a proper CMake project should
delegate the package management to the user. The way to do this is to use
&lt;code>find_package&lt;/code> for dependencies (and &lt;em>not&lt;/em> &lt;code>add_subdirectory&lt;/code>, which has many downsides).&lt;/p>
&lt;p>Optionally, one can provide a helper script to build and install
the dependencies &lt;em>locally&lt;/em>, as suggested in this post.
Of course, it can be done differently (as long as it works with
&lt;code>find_package&lt;/code> through &lt;code>CMAKE_PREFIX_PATH&lt;/code>).
At Facebook, for instance, they seem to
&lt;a href="https://github.com/facebook/proxygen/tree/main/build">have&lt;/a>
&lt;a href="https://github.com/facebook/proxygen/blob/main/getdeps.sh">their&lt;/a>
&lt;a href="https://github.com/facebook/proxygen/blob/main/build/fbcode_builder/getdeps.py">way&lt;/a>.&lt;/p>
&lt;p>I wrote a small project illustrating the helper strategy
&lt;a href="https://github.com/acarg/cmake-deps-example-proxygen">here&lt;/a>
(using Facebook/Proxygen because it has a few transitive dependencies),
and another one &lt;a href="https://github.com/acarg/cmake-deps-example-grpc">using gRPC&lt;/a>.
I also provide more dependency scripts
&lt;a href="https://github.com/acarg/cmake-deps">here&lt;/a> for inspiration.&lt;/p>
&lt;h2 id="annex">
&lt;a class="Heading-link u-clickable" href="https://www.acarg.ch/posts/cmake-deps/#annex">Annex&lt;/a>
&lt;/h2>
&lt;h3 id="what-if-the-dependency-does-not-play-well-with-find_package">
&lt;a class="Heading-link u-clickable" href="https://www.acarg.ch/posts/cmake-deps/#what-if-the-dependency-does-not-play-well-with-find_package">What if the dependency does not play well with &lt;code>find_package&lt;/code>?&lt;/a>
&lt;/h3>
&lt;p>If the dependency is a CMake project, it &lt;em>should&lt;/em> support &lt;code>find_package&lt;/code>.
In case it does not, consider contributing it, or at least ask the
maintainer to add it. If they don&amp;rsquo;t want to, and you can&amp;rsquo;t contribute it,
maybe it is a good time to wonder whether you want to depend on that
library, really.&lt;/p>
&lt;p>This said, what about dependencies that are &lt;em>not&lt;/em> CMake projects?
It does make sense for a non-CMake library to not provide a CMake
package configuration file (&lt;code>&amp;lt;PackageName&amp;gt;Config.cmake&lt;/code>) indeed.
Fortunately that usually happens in one of the following two scenarios:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>The library supports &lt;code>pkg-config&lt;/code>. The good news is that
&lt;a href="https://cmake.org/cmake/help/latest/module/FindPkgConfig.html">CMake supports pkg-config&lt;/a>,
too (I like to hide that into a CMake Find Module, but that&amp;rsquo;s a longer
story). For instance, gstreamer uses Meson, but supports &lt;code>pkg-config&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-cmake" data-lang="cmake">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">find_package&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">PkgConfig&lt;/span> &lt;span class="s">REQUIRED&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">&lt;/span>&lt;span class="nb">pkg_search_module&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">GST&lt;/span> &lt;span class="s">REQUIRED&lt;/span> &lt;span class="s">gstreamer-1.0&amp;gt;=1.4&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;/li>
&lt;li>
&lt;p>The library is so popular that CMake &lt;a href="https://cmake.org/cmake/help/latest/manual/cmake-modules.7.html#find-modules">provides a Find Module&lt;/a>.
That&amp;rsquo;s the case for &lt;a href="https://cmake.org/cmake/help/latest/module/FindOpenSSL.html">OpenSSL&lt;/a> and &lt;a href="https://cmake.org/cmake/help/latest/module/FindBoost.html">Boost&lt;/a>,
for instance; they do not support CMake, but &lt;code>find_package(OpenSSL)&lt;/code> and
&lt;code>find_package(Boost)&lt;/code> both works because &lt;em>CMake supports them&lt;/em>.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>Worst case, you can always write a Find Module manually, but I never really
had to myself (except to hide a call to &lt;code>pkg_search_module&lt;/code>, as mentioned
above).&lt;/p>
&lt;h3 id="what-if-i-actually-need-add_subdirectory">
&lt;a class="Heading-link u-clickable" href="https://www.acarg.ch/posts/cmake-deps/#what-if-i-actually-need-add_subdirectory">What if I actually need &lt;code>add_subdirectory&lt;/code>?&lt;/a>
&lt;/h3>
&lt;p>There is exactly one reason I can think of where one would want to add a
dependency using &lt;code>add_subdirectory&lt;/code>, and that&amp;rsquo;s when the author of the
main project is also the author of the dependency, and wants to develop
both in parallel. In that case, I would still prefer &lt;code>ExternalProject_add&lt;/code>
(i.e. using the helper script) which supports local paths.
However, if your IDE really needs
&lt;code>add_subdirectory&lt;/code>, then you can still support both,
&lt;a href="https://github.com/grpc/grpc/blob/v1.46.4/CMakeLists.txt#L69-L89">like gRPC does&lt;/a>
(though I personally find this slightly convoluted).&lt;/p></description></item></channel></rss>