<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Max Howell on Medium]]></title>
        <description><![CDATA[Stories by Max Howell on Medium]]></description>
        <link>https://medium.com/@mxcl?source=rss-87f057c6e50c------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*TuQf4Qe601QnUiTNRDvLEg.jpeg</url>
            <title>Stories by Max Howell on Medium</title>
            <link>https://medium.com/@mxcl?source=rss-87f057c6e50c------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Mon, 13 Jul 2026 10:40:06 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@mxcl/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[dev.diary — tea is env++]]></title>
            <link>https://medium.com/teaxyz/dev-diary-tea-is-env-ef9fd7322de1?source=rss-87f057c6e50c------2</link>
            <guid isPermaLink="false">https://medium.com/p/ef9fd7322de1</guid>
            <category><![CDATA[package-manager]]></category>
            <category><![CDATA[package-management]]></category>
            <category><![CDATA[devtools]]></category>
            <category><![CDATA[dev]]></category>
            <dc:creator><![CDATA[Max Howell]]></dc:creator>
            <pubDate>Tue, 31 Jan 2023 13:07:31 GMT</pubDate>
            <atom:updated>2023-01-31T13:07:31.249Z</atom:updated>
            <content:encoded><![CDATA[<h3>dev.diary — tea is env++</h3><p>A few aspects of the shape of tea’s CLI have been bothering me.</p><p>While building out the pre-release tea began to take shape as a “universal interpreter”. I have long thought package managers were depressingly basic in operation, a strange thing for such a (potentially) powerful part of the stack. It seemed somehow backwards that if I obtain a script, I first must figure out what package runs it, and then possibly even install a bunch of dependencies in order for it to run. Why can’t the package manager figure this stuff out for you? And thus tea script.ext did just that.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/500/1*7F_695_rMJ8JNn7Sa_pR9g.jpeg" /><figcaption>want <a href="https://charm.sh">https://charm.sh</a>’s amazing vhs terminal recorder? Just type `vhs`</figcaption></figure><p>While iterating around this I realized that it wasn’t laziness that was holding me back from implementing tea install , it was a desire to find something <em>better;</em> a better paradigm for using open source; a better paradigm than <em>managing it</em>. So I held off. My coworkers complained that they still needed brew even though we were building its spiritual successor.</p><p>Yet I held off because I know that if I make parts of my workflow tedious, I will come up with a solution. I suffer a little now to reap the rewards later.</p><p>I would joke with people that “tea wasn’t a package manager”. This was tongue in cheek—of course tea was and is a package manager. But it didn’t seem much like one. I wanted people to stop thinking about the package manager altogether. I wanted to make something where managing packages stops being a part of your workflow at all. You have apps to build, and I want you to build them with tools like mine getting out of your way entirely.</p><p>In pursuit of being a universal interpreter without installing packages one must build environments that create a kind of container for the software. It seemed useful to make those environments available more generally.</p><p>With tea you build an environment by <em>adding packages </em>with +pkg.com syntax. With the environment assembled you then execute a command. Plus package syntax was a wonderful discovery allowing entirely new categories of workflows.</p><p>What was tea? It seemed to have become a tool for assembling environments of open source software which you then apply to other tooling. If we can figure out those environments for you, we do, if we cannot we need you to tell us.</p><p><em>tea was a</em> <em>universal interpreter</em>. So I looked to other interpreters to see if the similarities should go further.</p><p>It’s typical with interpreters for just typing them to start a REPL (a <a href="https://en.wikipedia.org/wiki/Read–eval–print_loop">read eval print loop</a>). You exchange your Bash prompt for a Python, Ruby, etc. prompt. Should tea do this? We tried it, so tea +deno.land would start a new shell with deno available. It was somewhat a good feature and it was idiomatic with the concept that tea was a kind of interpreter.</p><p>But something about it irked me.</p><p>tea described itself as a unified package manager, a universal interpreter, a universal environment manager. It was all these things, but you can’t sell a product if you can’t put your finger on what the product <em>*is*</em>.</p><p><a href="https://medium.com/teaxyz/dev-diary-finding-the-fun-1b351431fcac">Last week</a> I talked about how we resolved the messaging by more clearly seeing what tea was. tea is a magical package manager, it’s <em>invisible and powerful</em>. Stop thinking about installing stuff—get on with your work instead. If you don’t need to even think about managing packages entirely you can use new tools and workflows effortlessly, but also other people who make tools, scripts or workflows can know you, the end user for their efforts, have the entire open source ecosystem at your fingertips too. They don’t need to tell you how to install a bunch of stuff, with a bunch of different versions, for a bunch of different platforms in their README—you have tea.</p><p>This week I realized that during the history of UNIX there was another innovation in this area. As developers for UNIX like systems exploded, new tools began to appear all over. Each UNIX had different conventions and preferences for where such tools may be installed. This would be problematic for the <strong><em>shebang</em></strong>.</p><h4>Shebangs</h4><p>Perl was one of the first interpreted languages. It grabbed the minds of all UNIX users since it was fast to dev like shell scripting but powerful like “proper” languages (eg. C). It supported being “shebanged”, the #! at the front of a text file that told your shell who interprets the file you want it to run:</p><pre>#!/usr/bin/perl</pre><p>The problem is: perl was new and not POSIX with an associated “this is where it should be installed” path. It might be installed anywhere! Shebangs were not designed to search PATH (for security reasons presumably) but this impeded the possibility of distributing a perl script to other communities.</p><p>So/usr/bin/env was built. It was designed to construct environments† and then execute commands inside those environments searching the specified PATH for the command to run. POSIX declared its path to be /usr/bin/env so now shebangs could be:</p><pre>#!/usr/bin/env perl</pre><p>Thus alleviating the location problem.</p><p>However the user still needed to install perl themselves.</p><blockquote>† though not commonly used env FOO=bar baz and other environment management features are a primary part of env’s CLI.</blockquote><h3>tea is env++</h3><pre>#!/usr/bin/env -S tea perl<br><br>## see `man env` to understand the `-S`</pre><p>Like env we construct an environment for perl and run it, but as supplemental functionality we install perl and its dependencies first.</p><p>One day perhaps we’ll be POSIX and we can reduce this to:</p><pre>#!/usr/bin/tea perl</pre><p>One can only dream…</p><h4>Adapting tea/cli Based on this Realization</h4><p>tea is a more capable env. Should this be something we document? Should this adapt how we introduce it and talk about it? I began exploring that.</p><p>Firstly, our REPL like feature tea +rust-lang.org starts a new shell that contains the environment for rust. Should instead we output the environment like env does?</p><p>I tried it and it felt right. We already use this functionality for tea’s magic, so now instead of that being a special mode it was a idiomatic use of tea as a CLI.</p><p>However the REPL functionality is neat, so do we really dare lose it?</p><p>Fortuantely an idiomatic way to do the REPL presented itself to me, simply append sh, eg. tea +rust-lang.org sh. This is all our REPL was doing underneath anyway. Now tea doesn’t have any special modes, we are communicating to the user intuitively that tea installs packages, creates environments and executes commands within them. To see the environment that will be created, stop typing (eg tea +rust-lang.org). To use the environment, add commands.</p><p>Typing tea by itself dumps the current supplemented environment. If you have our magic installed then inside a developer environment you can see what tea did to create that environment.</p><p>I’m super happy with these changes as now I feel tea is close to being very well scoped, intuitive to understand, composable, flexible and useful to many different tasks along the lines of its utility proposition. <em>We are both more powerful than all other package managers while also being more intuitive.</em></p><p>I foresee a v1 in the near future.</p><h3>Breaking Change</h3><p>This is a breaking change, but we are pre v1. After v1 we will not break the CLI API unless we bump the major version.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ef9fd7322de1" width="1" height="1" alt=""><hr><p><a href="https://medium.com/teaxyz/dev-diary-tea-is-env-ef9fd7322de1">dev.diary — tea is env++</a> was originally published in <a href="https://medium.com/teaxyz">teaxyz</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[dev.diary—Finding the Fun]]></title>
            <link>https://medium.com/teaxyz/dev-diary-finding-the-fun-1b351431fcac?source=rss-87f057c6e50c------2</link>
            <guid isPermaLink="false">https://medium.com/p/1b351431fcac</guid>
            <category><![CDATA[devtools]]></category>
            <category><![CDATA[open-source]]></category>
            <category><![CDATA[dev]]></category>
            <dc:creator><![CDATA[Max Howell]]></dc:creator>
            <pubDate>Mon, 23 Jan 2023 15:39:48 GMT</pubDate>
            <atom:updated>2023-01-23T15:39:48.356Z</atom:updated>
            <content:encoded><![CDATA[<p>When building any kind of product it takes a little time before you figure out its shape. Famous games developer Sid Meier described this process as “finding the fun”. He would iterate compulsively on his ideas until those little changes in the little details would uncover something sufficiently new and engaging that he had the basis for a new game.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/512/1*Y25rtCmwlRMmcRlef-mlFA.png" /></figure><p>This is the approach I take with my products. From my childhood writing toys in BBC Basic, to working on the amaroK music player for KDE in the early 2000s, to making the Android version of TweetDeck in 2010, to how I approached making Homebrew and now with its spiritual successor: tea.</p><p>We released <a href="https://github.com/teaxyz/cli">tea/cli</a> a couple months ago. At that point I felt the product was “ready enough” for general consumption. We had a number of compelling features coupled with a good mission statement and together to me that seemed enough to present it to the community and start gathering wider ideas and interest.</p><p>Which is a key differentiator between developing most products and developing open source: you bring your userbase in <strong><em>before</em></strong> you’ve “found the fun”.</p><p>Before launch I jokingly would say that <em>tea was not a package manager</em>. I was being flippant. But also I wasn&#39;t being flippant. Package Managers as a class of tool haven’t evolved since their inception in the 90s. They are very much in the what I call “egotistical tool” category: that being: you *<em>will</em>* learn how it works thank you very much because the package manager is a<strong> very important tool </strong>and you are lucky to have it. I’ve not a fan of this attitude.</p><p>I believe tools are a special class of utility. A well written tool completely eradicates entire classes of tedium enabling its users to reach new heights in productivity. A well written tool enables functionality that before was either too tricky to accomplish or completely impossible.</p><p>At launch we said tea was a universal interpreter, a universal environment manager, a unified dependency manager and it could run executable markdown.</p><p><a href="https://changelog.com/news/491E">Changelog rightly called me out on this</a>, saying “might Max and the team be thinking <em>too</em> big this time around?”</p><p>The truth is we hadn’t yet <em>quite</em> found the fun.</p><p>Two months of passionate early adopter usage later and a few things were becoming apparent. tea’s real super powers were the underlying rethinking about the nature of package management, specifically that with tea the package manager disappeared. So I decided to experiment with doubling down on that.</p><p>At launch you could type tea -X npm startand tea would look for npm and run start , but if it didn’t find npm it would fetch it first. This was in fact a very near launch addition by me but it was a truly wonderful reduction in friction for using the open source ecosystem. And fundamentally what this feature said was: <strong>the package manager needs to get out the way.</strong></p><blockquote>tea doesn’t <em>install packages</em>—at least not in a conventional sense. In the above example npm is not available unless you type tea -X npm otherwise it is “stowed” in ~/.tea . This is important since I want using tea to complement your system. tea is not an operating system, it is a complement to whatever system (Mac, Linux, Windows, etc.) you are using.</blockquote><p>Soon after a passionate early adopter of tea suggested that symlinks to tea should behave as though tea -X foo bar was called. So we implemented that and immediately it was apparent that this was a seriously cool feature. I used this to create a symlink from tea to bpb (a tool that makes signing your git commits <em>trivially easy</em>) and immediately knew that forever more I would not have to piss about for an hour getting my git-signing set up whenever it stopped working or a had to set myself up on a new machine, remote server or when clean installing my OS.</p><p>A developer at tea realized that you could make the shell’s command-not-found handler call tea -X . Command not found handlers have been used by the package manager before. For example, Ubuntu uses it to suggest packages that may satisfy whatever you typed. The idea made me laugh because it seemed so ill-advised! But we added it to the README as a hack that users may want to try out—<em>for fun</em>.</p><p>But I got to wondering… <em>had we in fact</em> <strong><em>found the fun</em></strong>? I decided to add the command-not-found handler to my shell and see how I liked it.</p><p>After a few days I stopped laughing. I started getting excited. This hack wasn’t a hack. <em>It was awesome</em>. It completely delivered on tea’s promise and significantly simplified our narrative.</p><p>How do you use tea? You don’t. You use the tools that the open source ecosystem provides. If you read a blog post about a new tool then just type the commands you read into your Terminal and try it out. There’s no “figure out the package name and then install it step”. There’s no “will installing this break other stuff on my system” concerns. If you don’t like it then don’t worry about it—it’s already gone.</p><pre># want to see if bun will run your node project?<br>$ bun run start<br>tea: stowing bun^0.4<br>bun: start<br><br>$ which bun<br>bun not found<br># ^^ it’s not in your PATH!<br><br>$ bun --version<br>0.4.0<br># ^^ but tea surfaces it for you</pre><p>tea still is a “universal interpreter” but that just falls under the general banner; we don’t have to explicitly name this feature; tea just knows how to make what you throw at it work. Be that a command or a script, tea makes it possible for you to get on with what matters: making your app.</p><p>And so I dropped executable markdown completely. Part of the reason I added exe/md was the desire for tea to be a tool that makes development a dream. And we have achieved this because now if you want to use executable markdown to run the script build you just type xc build and tea installs <a href="https://github.com/joerdav/xc">xc</a> transparently. Whatever “task runner” your project needs is transparently available. Having this feature in tea itself no longer made sense.</p><p>When it comes to making great products you need to be able to drop entire features without ego or the sunk-cost fallacy holding you back. What matters is the entire product vision no matter how cool you think something is. I still think exe/md is a cool idea, and didn’t know about xc before writing it. I’m very glad someone else thought the same and built it. UNIX tools should do one thing and one thing well.</p><p><strong>tea 0.19</strong> lives up to the UNIX philosophy and thanks to Changelog for reminding me about that. I revisited every part of the CLI and retooled it towards this purpose. Check out the new README for the deets, the caveats and what comes next:</p><p><a href="https://github.com/teaxyz/cli">https://github.com/teaxyz/cli</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1b351431fcac" width="1" height="1" alt=""><hr><p><a href="https://medium.com/teaxyz/dev-diary-finding-the-fun-1b351431fcac">dev.diary—Finding the Fun</a> was originally published in <a href="https://medium.com/teaxyz">teaxyz</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Something new is brewing]]></title>
            <link>https://medium.com/teaxyz/tea-brew-478a9e736638?source=rss-87f057c6e50c------2</link>
            <guid isPermaLink="false">https://medium.com/p/478a9e736638</guid>
            <dc:creator><![CDATA[Max Howell]]></dc:creator>
            <pubDate>Wed, 23 Mar 2022 11:35:44 GMT</pubDate>
            <atom:updated>2022-03-23T19:31:13.143Z</atom:updated>
            <cc:license>http://creativecommons.org/publicdomain/zero/1.0/</cc:license>
            <content:encoded><![CDATA[<p>I created Homebrew nearly 13 years ago out of sheer necessity. I found the tooling available to developers at the time haphazard and difficult to tease into shape. After complaining about it at the pub one evening, I was met with an exasperated “Max, quit whining and do something about it”. So I did. (Thanks Jono).</p><p>From the start, I designed it to be delightful where other tools were not. I have always firmly believed when building anything, especially open source, that the tooling should get out of your way because you have much more important work to be doing.</p><p>I also intentionally designed it to <em>go viral</em>. I was lazy, you see, in the way all the best developers are. I needed you — <em>all of you</em> — to help me build it. I did this so well that Homebrew became one of the biggest open source projects of all time.</p><p>Not long after starting, I quit my job to work on this project I felt I was put on the world to make. However… within a few months, the task was long from complete, and I found myself skint. I still remember going to the bank and asking what would happen if I overdrew the next day to pay for rent. They didn’t care much.</p><p>Thus began a steady rotation between jobs and open source. I wanted to work on open source full time, believing it would help the world become a better place. But that remained an elusive dream.</p><p>Eventually I burned out and quit maintaining Homebrew. I handed the reins to the community that had emerged from the trail I had laid out. I’m proud to say the project has done very well without me.</p><p>A few times a year someone would ask me if I would make a brew2. I always said no. What would be the point? brew is good, and I’m not going to reinvent the wheel.</p><h3>A Wild Timothy Appears</h3><p>I met Timothy in Chicago in late 2013. We clicked and worked together on many projects. We were both passionate about what people can build in open environments that encourage and incentivize contribution. Eventually I moved away but the friendship stuck. Timothy caught the wave of crypto while I moved on to become a top mobile developer.</p><p>Every year or so he’d hit me up and talk to me about what was new in crypto, trying to get me to bite. I always declined. I admired the bitcoin white paper but I felt crypto was just money and, for me, money was the least interesting consequence of work.</p><h3>A Change of Heart</h3><p>My partner and I were trying for a baby last year and in September, she showed me the positive test. We were overjoyed, but the next morning I woke up in a cold sweat. How was I going to provide for my family? <em>Open source was not paying the bills.</em></p><p>I went back over all my old ideas looking for a startup idea I could turn into a business. Eventually I came back to my ream of notes about “brew2”. Despite previously snubbing the idea of making another, I am an obsessive note taker and had been recording my thoughts as they came to me for years.</p><p>I moved on, at the time still convinced open source could not pay the bills.</p><p>Timothy phoned me to catch up and once more pointed me to what he was now calling “web3”.</p><p>I sniffed around the top of the rabbit hole.</p><p>While learning about web3 I bought and sold a few NFTs. The process was mostly uninteresting except for when I sold one and saw the automated, unavoidable 10% royalty enforced by a digital contract (with no need for a legal structure) that compensated the original creator for secondary sales<em>. </em>I felt the sting of inspiration.</p><p><em>web3 enables indirect compensation.</em></p><p>I wondered if we could apply this concept to helping distribute value to open source.</p><p>I dove down the rabbit hole.</p><h3>The Nebraska Problem</h3><p>It’s a well-known problem. Developers make fundamental improvements to the nature of the Internet. Their contributions are grabbed by the community with both hands and inserted into the tower:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/385/0*TTqemZPUKyxK8mgo" /><figcaption><a href="https://xkcd.com/2347/">https://xkcd.com/2347/</a></figcaption></figure><p>Once a block is placed it’s rarely removed. The maintainers become charitable volunteers.</p><p>Solutions like sponsorship and bounties exist. Both unfortunately reward only the top of the tower, the visible packages. Beneath them lie tens of thousands of vital packages that most people don’t even know exist.</p><p>Recently Log4J<a href="https://www.cnet.com/news/privacy/log4j-software-bug-cisa-issues-emergency-directive-to-federal-agencies/"> hit the news</a> with a zero day that impacted most of the Internet. They fixed the bug and endured abuse while pointing out that they receive no funding from the myriad of unicorns that use them. They probably still don’t. Most of these hyper profitable corporations didn’t even know that Log4J was a vital brick in their tower of open source built by people they don’t even know and have never even paid.</p><p>CoreJS is another famous example, downloaded 30 million times a week. Their<a href="https://github.com/zloirock/core-js/blob/master/README.md"> README</a> is depressingly laced with <em>genuine need</em> for funding to help them keep every Node.js application that exists afloat.</p><h3>Let’s Brew a Fresh Pot</h3><p>Tools like Homebrew lie beneath all development tools, assisting developers to actually get development done. We know the graph of all open source, which means we’re uniquely placed to innovate in interesting and exciting ways. This is exactly what <a href="https://tea.xyz">tea</a> will do. We’re taking our knowledge of how to make development more efficient and throwing innovations nobody has ever really considered before. Package managers haven’t been sexy. Until now.</p><p>Most importantly, we’re moving the package registry on-chain (relax, we’ll use a low-energy proof of stake chain). This has numerous benefits due to the inherent benefits of blockchain technology:</p><ul><li>Packages will be immutable (no more<a href="https://www.theregister.com/2016/03/23/npm_left_pad_chaos/"> left-pad incidents</a>)</li><li>Packages will always be available (we’ll use decentralized storage)</li><li>Releases will be signed by the maintainers themselves (rather than a middleman you are told you can trust)</li><li>Tools can be built to fundamentally verify the integrity of your app’s open source constitution</li><li>Token can flow through the graph</li></ul><p>Token flowing is where things get <em>really </em>interesting, but first let’s set things straight: we’re not changing the nature of open source. It’s still free. web3 has enabled novel new ways to distribute value, and with our system people who care about the health of the open source ecosystem buy some token and stake it. Periodically, we reward this staking because it is securing our token network. We give a portion of these rewards to the staker and a portion to packages of their choice <em>along with all the dependencies of those packages</em>.</p><p>Note that no portion goes to <em>us</em>. We’re not like the other app stores.</p><h3>What is tea?</h3><p>Founded by Max Howell, the creator of Homebrew and Timothy Lewis, famous web3 evangelist:</p><p><strong>tea</strong> is a delightful product suite that you will adore.</p><p><strong>tea</strong> is the home to a DAO that will ensure the open source maintainers that keep the Internet running are rewarded as they deserve.</p><p><strong>tea</strong> is our revolution against a failing system.</p><p>Stay in touch: <a href="https://tea.xyz">https://tea.xyz</a> &amp; <a href="https://linktr.ee/teaxyz">https://linktr.ee/teaxyz</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=478a9e736638" width="1" height="1" alt=""><hr><p><a href="https://medium.com/teaxyz/tea-brew-478a9e736638">Something new is brewing</a> was originally published in <a href="https://medium.com/teaxyz">teaxyz</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Maintaining a Homebrew Tap for Swift projects]]></title>
            <link>https://medium.com/@mxcl/maintaining-a-homebrew-tap-for-swift-projects-7287ed379324?source=rss-87f057c6e50c------2</link>
            <guid isPermaLink="false">https://medium.com/p/7287ed379324</guid>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[brew]]></category>
            <dc:creator><![CDATA[Max Howell]]></dc:creator>
            <pubDate>Sun, 09 Feb 2020 20:37:15 GMT</pubDate>
            <atom:updated>2020-02-10T01:20:03.511Z</atom:updated>
            <content:encoded><![CDATA[<h3>Maintaining a Homebrew Tap for Swift Projects</h3><p>I read somebody recently complaining that installing Swift projects was hard because SwiftPM has no install command. Avoiding going into my opinion on that (I don’t want SwiftPM to go there), I disagree that maintaining a Homebrew tap is hard. I do it for <a href="https://github.com/mxcl/swift-sh">swift-sh</a>, and I even have Travis set up to automatically update the tap for me so I don’t have to do anything.</p><p>For example installing swift-sh is this easy:</p><pre>brew install mxcl/made/swift-sh</pre><p>And Homebrew will keep it up-to-date.</p><h3>Why Should I Do This?</h3><p>I maintain that a super large hindrance to the success of your work is adoption pain. You <em>need</em> to reduce adoption pain as much as possible if you want your work to be successful, it’s that simple.</p><h3>How To Make a Tap</h3><ol><li>Tag a release for your project ✻</li><li>Create a new repository on GitHub, name it homebrew-tap†</li><li>Add a file called your-project.rb, adapting the following:</li></ol><pre>class ProjectName &lt; Formula<br>  desc &quot;Describe your project&quot;<br>  homepage &quot;<a href="https://github.com/your/project">https://github.com/your/project</a>&quot;<br>  url &quot;<a href="https://github.com/your/project/archive/1.16.0.tar.gz">https://github.com/your/project/archive/1.2.3.tar.gz</a>&quot;</pre><pre>  def install<br>    system &quot;swift&quot;, &quot;build&quot;,<br>        &quot;--configuration&quot;, &quot;release&quot;,<br>        &quot;--disable-sandbox&quot;</pre><pre>    bin.install &#39;.build/release/your-project&#39;<br>  end<br>end</pre><p>4. Install it:brew install ./your-project.rb, you do this to test your formula, but also because brew will helpfully tell you the SHA256, which you then must add to the formula:</p><pre>class ProjectName &lt; Formula<br>  desc &quot;…&quot;<br>  homepage &quot;<a href="https://github.com/your/project">…</a>&quot;<br>  url &quot;<a href="https://github.com/your/project/archive/1.16.0.tar.gz">…</a>&quot;</pre><pre>  sha256 &quot;INSERT-SHA256-HERE&quot;</pre><pre>  def install<br>    # …<br>  end<br>end</pre><p>5. Commit and push</p><p>6. Try it: brew install your-github-username/tap/your-project --force ‡</p><p>That’s it.</p><blockquote>✻ SwiftPM requires that projects be tagged with a semantic version, eg.1.2.3 or v1.2.3.</blockquote><blockquote>† You can pick a different name than homebrew-tap, the homebrew prefix is required though.</blockquote><blockquote>‡ --force is <strong>only required for <em>you</em></strong> because you <strong>already installed</strong> your formula in step 4.</blockquote><h3>Automating Formula Updates</h3><p>Maintaining metadata like this is tedious for every release, so automate it with the following Travis configuration (in your Swift project, not the tap):</p><pre>os: osx<br>language: swift<br>osx_image: xcode10.2<br>env:<br>  - HOMEBREW_NO_AUTO_UPDATE=1<br>  - HOMEBREW_NO_INSTALL_CLEANUP=1</pre><pre>deploy:<br>  provider: script<br>  on:<br>    all_branches: true<br>    condition: $TRAVIS_BRANCH =~ ^v?\d+\.\d+\.\d+$<br>  script:<br>    - USER=$(dirname $TRAVIS_REPO_SLUG)<br>    - PROJ=$(basename $TRAVIS_REPO_SLUG)<br>    - URL=&quot;<a href="https://github.com/$TRAVIS_SLUG/archive/$TRAVIS_TAG.tar.gz">https://github.com/$TRAVIS_SLUG/archive/$TRAVIS_TAG.tar.gz</a>&quot;<br>    - ORIGIN=&quot;<a href="https://$GITHUB_TOKEN@github.com/$TRAVIS_SLUG.git">https://$GITHUB_TOKEN@github.com/$USER/homebrew-tap.git</a>&quot;</pre><pre>    # grab the tap<br>    - brew tap $USER/homebrew-tap<br>    - cd /usr/local/Homebrew/Library/Taps/$USER/homebrew-tap<br>        <br>    # update the formula URL<br>    - sed -E -i &#39;&#39; &#39;s~^  url &quot;.+&quot;~  url &quot;&#39;$URL\&quot;~ ./swift-sh.rb<br>    - brew fetch $PROJ || true<br>    # ^^ fails because SHA is wrong, hence || true<br>    <br>    # figure out the sha<br>    - SHA256=$(shasum --algorithm 256 $(brew --cache --build-from-source $PROJ) | awk &#39;{print $1}&#39;)<br>    <br>    # update the formula SHA<br>    - sed -E -i &#39;&#39; &#39;s/^  sha256 &quot;.+&quot;/  sha256 &quot;&#39;$SHA256\&quot;/ $PROJ.rb</pre><pre>    # commit to the tap<br>    - git remote set-url origin &quot;$ORIGIN&quot;<br>    - git add $PROJ.rb<br>    - git config user.name &quot;Travis&quot;<br>    - git config user.email &quot;<a href="mailto:bot@travis-ci.com">bot@travis-ci.com</a>&quot;<br>    - git commit -m &quot;$PROJ $TRAVIS_TAG&quot;<br>    - git push origin master</pre><p>You may need to adapt the variables at the top depending on how you named everything.</p><p><a href="https://docs.travis-ci.com/user/deployment/pages/">You also need to add a GitHub token to your Travis account</a> (consumed above with $GITHUB_TOKEN).</p><h3>Automatic Bottling</h3><p>Bottles are brew’s binary downloads, which makes your users happier, but this is entirely optional for you.</p><p>Automating this took a lot of trial and error since the documentation for bottling is somewhat lacking. So please adapt what I did:</p><ul><li><a href="https://github.com/mxcl/homebrew-made/blob/master/swift-sh.rb">Formula</a></li><li><a href="https://github.com/mxcl/swift-sh/blob/master/.travis.yml">.travis.yml</a></li></ul><p>Also notably the bottles only work for Swift ≥ 5 and macOS ≥ 10.14.4 due to ABI being a thing. My formula detects if the bottle is ok to pour.</p><h3>Optional Extras</h3><p>You’ll see I do a little more work in <a href="https://github.com/mxcl/homebrew-made/blob/master/swift-sh.rb">my formula</a> in order to statically link the standard library on macOS &lt; 10.14.4, but this is optional if you only support Swift ≥ 5, and even then it’s optional (your users will need to reinstall the formula if they upgrade to Xcode 11, and probably only a small minority of people are not on the latest Xcode already).</p><h3>Add Travis for Your Tap</h3><p>If you’re feeling keen you can verify that your tap actually builds and that the bottle works. <a href="https://github.com/mxcl/homebrew-made/blob/master/.travis.yml">Here’s mine</a>. You can see if your package works on Linux for example (yes, Homebrew supports Linux).</p><h3>An Idea</h3><p>Still, it’s work, and not everyone can be bothered. One idea would be for someone to do the above for all Swift projects that create binaries:</p><ol><li>Scan GitHub periodically for new Package.swift which build binaries</li><li>Create formula for those repos (via a template obv.)</li><li>Periodically scan for new releases for those repos and update the formula</li></ol><p>Maybe <em>you</em> should build this idea?</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7287ed379324" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Workbench: Seamless, Automatic, “dotfile” Sync to iCloud]]></title>
            <link>https://medium.com/@mxcl/workbench-seamless-automatic-dotfile-sync-to-icloud-e5529e2d30a0?source=rss-87f057c6e50c------2</link>
            <guid isPermaLink="false">https://medium.com/p/e5529e2d30a0</guid>
            <category><![CDATA[icloud]]></category>
            <category><![CDATA[open-source]]></category>
            <category><![CDATA[swift]]></category>
            <dc:creator><![CDATA[Max Howell]]></dc:creator>
            <pubDate>Wed, 20 Feb 2019 15:24:42 GMT</pubDate>
            <atom:updated>2019-02-20T16:41:42.231Z</atom:updated>
            <content:encoded><![CDATA[<p>As part of my <a href="https://medium.com/@mxcl/massively-contributing-to-swift-open-source-in-2019-7f104edd9468">one‐week‐one‐open‐source series</a> this year I have released <a href="https://github.com/mxcl/Workbench">Workbench</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/900/1*5oA5SpMC9hVGq9InsvBdBA.png" /></figure><p>Workbench is (currently) a small app that monitors changes in your “dotfiles”, uploading them to your private iCloud account when changed.</p><p>There are many “dotfile” backup solutions out there, but I found they all were limited or tedious in some manner. Hence I made my own.</p><p>Use of iCloud for services like Workbench is something I’d like to see more of from other developers. It is truly zero‐conf, giving your apps sign‐in‐less access to secure and private cloud‐data storage.</p><p>I have already tested it for clean-installs, and it works a treat.</p><p>Check it out!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e5529e2d30a0" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Massively Contributing to Swift Open Source in 2019]]></title>
            <link>https://medium.com/@mxcl/massively-contributing-to-swift-open-source-in-2019-7f104edd9468?source=rss-87f057c6e50c------2</link>
            <guid isPermaLink="false">https://medium.com/p/7f104edd9468</guid>
            <category><![CDATA[one-week-one-project-2019]]></category>
            <category><![CDATA[swift]]></category>
            <dc:creator><![CDATA[Max Howell]]></dc:creator>
            <pubDate>Sat, 16 Feb 2019 16:37:44 GMT</pubDate>
            <atom:updated>2019-03-04T15:53:30.228Z</atom:updated>
            <content:encoded><![CDATA[<p>This year I will release a new open source project <strong>EVERY WEEK</strong>.</p><p>I’m already at five projects this year:</p><ul><li><a href="https://github.com/mxcl/swift-sh">swift-sh</a>: trivially run Swift scripts with third party dependencies.</li><li><a href="https://github.com/mxcl/Path.swift">Path.swift</a> a delightful, robust, cross-platform and chainable file-pathing library.</li><li><a href="https://github.com/mxcl/LegibleError">LegibleError</a> a tool for displaying Swift errors to the user that are <strong>less</strong> <em>The operation couldn’t be completed. (ThirdPartyModule.(unknown context at 0xx10d6b4a44).SomeError error 0.)</em> and <strong>more</strong> legible and useful.</li><li><a href="https://github.com/mxcl/Version">Version</a> a µ‐framework for representing, comparing, encoding and utilizing <a href="https://semver.org/">semantic versions</a>.</li><li><a href="https://github.com/mxcl/AppUpdater">AppUpdater</a> a µ‐framework for updating your open source macOS app based on your repo’s GitHub releases.</li></ul><p>Update, now eight:</p><p><a href="https://github.com/mxcl/Workbench">Workbench</a>, <a href="https://github.com/mxcl/Chalk">Chalk</a>, <a href="https://github.com/mxcl/Cake">Cake</a>.</p><h3>Who are you exactly?</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1015/1*72b-jhf7btgqVXvl6_bkKQ.jpeg" /></figure><p>I’m Max Howell, the creator of Homebrew and an Apple fanboy since I switched from working on Linux open source 13 years ago. I am a <a href="https://github.com/mxcl">prolific and famous creator of open source</a>, but also a renowned developer of <a href="https://mxcl.github.io/#career">high profile apps</a>. I love making developer tools because:</p><blockquote>Developer tools are the foundation of the modern world</blockquote><p>Nothing makes me happier than working on vital tooling for our community and helping Swift take over the world.</p><h3>Will you help?</h3><p>Will you help me fill in the gaps in the Swift toolkit with 52 modular, well-designed, <strong>high-quality</strong> Swift packages this year? Please support my patreon:</p><p><a href="https://patreon.com/mxcl">https://patreon.com/mxcl</a></p><p><a href="https://patreon.com/mxcl">Max Howell is creating Open Source | Patreon</a></p><p>All my projects are announced on my Patreon first, giving you the opportunity to talk about them before anyone else. Higher tiers also give access to my invite only Discord where you can ask me for help with API design or Swift in general.</p><h3>Tell Me More!</h3><p>I have a long list of ideas that I’ve been assembling for years, and I want to give them to the Swift community. All my projects have 95%+ code coverage, 100% documentation, excellent READMEs and are under liberal licenses (I favor the <a href="https://unlicense.org">Unlicense</a> nowadays).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/992/1*wLPq-44dyc1ozzCKU-qokg.png" /></figure><p>I am meticulous with my API design; at the forefront of my mind is always the trade-offs of simplicity, elegance and power. A tool should be easy to learn, easy to master and <strong>provide power</strong> to those who master it. I care about your developer experience, supporting all the package managers in the ecosystem, updating documentation automatically and thoroughly testing in CI for all the platforms Swift supports (yes, especially Linux). I adhere religiously to semantic versioning, I never, ever want my libraries to break when you update them, or when you update Swift (or Xcode) itself.</p><p>I am careful with pull requests, I care about my products and resist feature bloat: libraries should be lean and tight and know their responsibility boundaries.</p><p>I have been working with Apple tooling for 15 years, am an accomplished mobile app developer for all platforms and worked at Apple on the beginnings of the Swift Package Manager. I maintain the top-100 pod <a href="https://github.com/mxcl/PromiseKit">PromiseKit</a> used by many of the worlds top apps. Oh, and I also created <a href="https://brew.sh">Homebrew</a>.</p><p>I understand the intricacies of developing Swift applications, the process, the difficulties, the hurdles and the areas developers <em>like you</em> need improvements.</p><h4>Community</h4><p>I am a prolific creator of open source, have managed some of the biggest communities open source has ever had and contributed to hundreds of projects. If I’m full‐time on open source I can be a real boon to our community. Managing open source is like nothing else, being that it is composed of volunteers, but developers are like nobody else, we build our own tooling and we revel in trying out new ideas and ways of working. Producing diamonds from this environment is something I excel at.</p><h3>What will I make?</h3><p>I intend to provide:</p><ul><li><strong>Tools</strong>. I have three, one of which is mostly finished that aid and facilitate development.</li><li><strong>Libraries.</strong> Micro, small, large, model-layer and UI-layer.</li><li><strong>Organizations</strong>. I have two ideas for community programs, formed to facilitate and moderate aspects of the Swift packaging community.</li></ul><h4>Ask mxcl anything</h4><p>Questions? <a href="https://github.com/mxcl/AmA">I have an AmA</a>, go wild!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7f104edd9468" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Version.swift]]></title>
            <link>https://medium.com/@mxcl/version-swift-3f6728c9eb1a?source=rss-87f057c6e50c------2</link>
            <guid isPermaLink="false">https://medium.com/p/3f6728c9eb1a</guid>
            <category><![CDATA[semver]]></category>
            <category><![CDATA[swift]]></category>
            <dc:creator><![CDATA[Max Howell]]></dc:creator>
            <pubDate>Mon, 11 Feb 2019 21:07:15 GMT</pubDate>
            <atom:updated>2019-02-11T21:15:20.010Z</atom:updated>
            <content:encoded><![CDATA[<p>I have released <a href="https://github.com/mxcl/Version">Version</a>.</p><p>A real version object is essential as soon as you have to parse one. And this is often enough, I mean, every app you make has one in its Info.plist.</p><p>I have for years used the Version.swift that is part of the Swift Package Manager, and this library is that code, extracted, documented, tested and enhanced. Relative to the original implementation it has:</p><ol><li>A more compact Codable implementation †</li><li>It implements LosslessStringConvertible ‡</li><li>Is not a massive-single-source-file (MSSF)</li><li>Has <a href="https://mxcl.github.io/Version/Structs/Version.html">online documentation</a> (100% documented)</li><li>Extensions for Bundle and ProcessInfo</li><li>Removal of the potentially fatal ExpressibleByStringLiteral conformance</li><li>A “tolerant” initializer for user input like 10.0 or 3</li><li>Idiomatic implementations for Range&lt;Version&gt;</li><li>99% code coverage</li></ol><p>I feel ok releasing this as my own, partly because I wrote the original implementation when I was at Apple 😏. And I maintained the license.</p><p>The version library has implementations for Equatable, Comparable, Hashable, Codable and Range. It fully conforms to the Semantic Version RFC, which includes support for prerelease identifiers and build identifiers, <strong>and</strong> we know how to sort and compare these properly (2.0.0-alpha is <strong>less than</strong> 2.0.0 and 1.0.0..&lt;2.0.0 does <strong>not</strong> contain it).</p><h3>Hey there!</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*abOrO2qgFk4lI4nugVfhqA.jpeg" /></figure><p>I’m Max Howell and I write open source <strong>all day, every day</strong>. I’ve been doing open source for more than 15 years, and you probably already use some of it (Homebrew anyone?). But, I need your help, currently I’m earning less than minimum wage. Don’t let me need apply to Google again!</p><p><a href="https://patreon.com/mxcl">https://patreon.com/mxcl</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3f6728c9eb1a" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[New Open Source: LegibleError]]></title>
            <link>https://medium.com/@mxcl/new-open-source-legibleerror-a90749df40c7?source=rss-87f057c6e50c------2</link>
            <guid isPermaLink="false">https://medium.com/p/a90749df40c7</guid>
            <category><![CDATA[open-source]]></category>
            <category><![CDATA[swift]]></category>
            <dc:creator><![CDATA[Max Howell]]></dc:creator>
            <pubDate>Thu, 07 Feb 2019 17:21:06 GMT</pubDate>
            <atom:updated>2019-02-07T17:21:06.715Z</atom:updated>
            <content:encoded><![CDATA[<p>LegibleError’s goal is to prevent you showing the user a string like this:</p><blockquote>The operation couldn’t be completed. (ThirdPartyModule.(unknown context at 0xx10d6b4a44).SomeError error 0.)</blockquote><p>That string is the default localizedDescription for a Swift Error. Instead use LegibleError and you’ll get something more like this:</p><blockquote>The operation couldn’t be completed. (ThirdPartyModule.SomeError.networkFailure)</blockquote><p>This way both you and the user are less confused and more informed.</p><p>LegibleError also provides a method to give better strings for logging and debug, providing all the information we can get from the runtime.</p><p><a href="https://github.com/mxcl/LegibleError">Check it out</a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6kgERc3nxIBq-zprvlFthA.jpeg" /></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a90749df40c7" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Keeping Swift Tests on Linux Sync’d]]></title>
            <link>https://medium.com/@mxcl/keeping-swift-tests-on-linux-syncd-7dac70f3d6e8?source=rss-87f057c6e50c------2</link>
            <guid isPermaLink="false">https://medium.com/p/7dac70f3d6e8</guid>
            <category><![CDATA[continuous-integration]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[linux]]></category>
            <dc:creator><![CDATA[Max Howell]]></dc:creator>
            <pubDate>Fri, 25 Jan 2019 16:45:22 GMT</pubDate>
            <atom:updated>2019-01-25T16:58:03.006Z</atom:updated>
            <content:encoded><![CDATA[<p>If your Swift project uses swift test on Linux then you are aware that you need to run swift test --generate-linuxmain in order to get for free what you get on Apple platforms, ie. for XCTest to know which tests it runs. The command generates several extra files that are only used on Linux and must be committed to your repository if you want test your product on Linux in CI.</p><p>A pain point in Swift’s early life for sure, but for now it’s just extra boilerplate. Well almost, since you have to re-run that command <em>every time </em>you add new tests, it’s easy to forget and then you aren’t testing on Linux properly.</p><p>So let’s add a CI hook to do it. Using Travis’s build stages it’s pretty easy to add a single stage that all tests depend on:</p><pre># these defaults are used unless we override them in individual jobs<br>os: osx<br>language: swift<br>osx_image: xcode10.1<br>  <br>jobs:<br>  include:<br>    - stage: pretest<br>      name: Check Linux tests are sync’d<br>      install: swift test --generate-linuxmain<br>      script: git diff --exit-code</pre><pre>    - stage: test<br>      name: macOS / Swift 4.2.1<br>      script: swift test --parallel</pre><pre>    - env: SWIFT_VERSION=4.2.1<br>      os: linux<br>      name: Linux / Swift 4.2.1<br>      language: generic<br>      install: |<br>        eval &quot;$(curl -sL <a href="https://swiftenv.fuller.li/install.sh">https://swiftenv.fuller.li/install.sh</a>)&quot;<br>      script: swift test --parallel</pre><p>The pretest is simple, we run swift test --generate-linuxmain and then ask git if there’s a diff. If there’s any differences then we (or a contributor) forgot to run swift test --generate-linuxmain before committing.</p><p>It <em>would</em> be neat to automate running swift test --generate-linuxmain and then committing those changes, and perhaps <a href="https://github.com/features/actions">GitHub Actions</a> (when final) will get us there, but until then having our CI commit to the repo it is testing against is a really bad idea (infinite CI cycles are not a future I want for you). So adding a check is the best we can get.</p><p>We are using <a href="https://github.com/kylef">Kyle Fuller</a>’s excellent <a href="https://github.com/kylef/swiftenv">Swift Version Manager</a> to install a specific version of Swift on Linux. The image Travis provides does not have Swift available via APT .</p><p>This gives us a matrix that will look similar to this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yFO6LSXnU33WzZDfQZMr9g.png" /><figcaption>This is the matrix for <a href="https://github.com/mxcl/Path.swift">Path.swift</a></figcaption></figure><p>If the pretest fails, the tests don’t run and the pull-request will be blocked.</p><h4>Hey there!</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*abOrO2qgFk4lI4nugVfhqA.jpeg" /></figure><p>I’m Max Howell and I want to be full-time making, writing and being all about open source. I’ve been doing open source for more than 15 years, and you probably already use some of it (Homebrew anyone?). I need your help to continue, any contribution is most welcome. Thanks so much.</p><p><a href="https://patreon.com/mxcl">https://patreon.com/mxcl</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7dac70f3d6e8" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Server-side Swift: Making Canopy (5/6)]]></title>
            <link>https://medium.com/@mxcl/server-side-swift-making-canopy-5-6-6b7de0d0feac?source=rss-87f057c6e50c------2</link>
            <guid isPermaLink="false">https://medium.com/p/6b7de0d0feac</guid>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[push-notification]]></category>
            <category><![CDATA[apn]]></category>
            <category><![CDATA[server-side-swift]]></category>
            <dc:creator><![CDATA[Max Howell]]></dc:creator>
            <pubDate>Tue, 22 Jan 2019 03:06:22 GMT</pubDate>
            <atom:updated>2019-01-25T16:46:42.811Z</atom:updated>
            <content:encoded><![CDATA[<p><a href="https://medium.com/@mxcl/server-side-swift-making-canopy-4-6-9212285a4462">Last time</a> I talked about deploying and maintaining a Linux server. This time we’re talking about how to commuincate with the APNs communication portal.</p><h4>What is Canopy?</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8Axr0XfuBHi9ABuhsPDlaA.png" /></figure><p>Canopy has apps for macOS and iOS, and it’s <a href="https://codebasesaga.com/canopy/">available here</a>.</p><h3>APNs</h3><p>The Apple Push Notification service. Why is the “s” lowercase? Nobody knows.</p><p>The service is a black box on the Internet. Your server connects to it securely and sends small JSON payloads, each for a specific device; Apple then send that to the device. If the device isn’t powered they will “hold” it for a little, if it has power it arrives pretty much instantly.</p><p>The connection is a persistent HTTP2 connection, HTTP2 is still pretty new and not much of the Internet is using it. Strictly URLSession supports it, but it will not keep the connection open. While some people <em>do</em> use URLSession for APNs, you should be careful: Apple promise that they consider this (opening and closing many connections in succession) a literal <em>attack</em> and will ban you for it.</p><h4>Implementing APNs</h4><p>First thing, go to the Apple provisioning portal and get yourself an APNs key. It comes as a file, transfer it to your server. Make sure it’s not exposed to the web somehow, if anyone received it they could send your users notifications as your app, which could be embarrassing for you.</p><p>Secondly you need to send your server the device tokens that Apple gives users of your client-apps. You’ve seen this code before, and now you understand better what it’s for.</p><p>However unless you plan to send the same notifications to everybody you also need some manner of associating that token with the user-id system you are using on your server. So send this information as well. Don’t forget you can use the same Codable struct on both ends! (See Part 1)</p><h4>Perfect Notifications</h4><p>Perfect provides <a href="https://github.com/PerfectlySoft/Perfect-Notifications">Perfect-Notifications</a>, so to start with I used that. It was easy to setup and use:</p><pre>import PerfectNotifications</pre><pre>let configurationName = &quot;configurationName&quot;<br>let apnsKeyIdentifier = &quot;AB90CD56XY&quot;   // from provisioning portal<br>let apnsTeamIdentifier = &quot;YX65DC09BA&quot;  // from provisioning portal<br>let apnsPrivateKeyFilePath = &quot;./APNsAuthKey_AB90CD56XY.p8&quot;</pre><pre>NotificationPusher.addConfigurationAPNS(<br>    name: configurationName, <br>    production: false,  // set to `true` when you go live!<br>    keyId: apnsKeyIdentifier, <br>    teamId: apnsTeamIdentifier, <br>    privateKeyPath: apnsPrivateKeyFilePath)</pre><p>Then you can send a notification:</p><pre>NotificationPusher(apnsTopic: &quot;your.bundle.id&quot;).pushAPNS(<br>    configurationName: configurationName, <br>    deviceTokens: [deviceToken], <br>    notificationItems: [.alertBody(&quot;Hello!&quot;), .sound(&quot;default&quot;)]) {<br>        responses in<br>        print(&quot;\(responses)&quot;)<br>}</pre><p>If this is a test, grab your token from your phone app with a print statement and hardcode it. After testing though you will need to start storing and grabbing your tokens from your database. You’ll need to add a route to your server (see part 2) and send the token to that endpoint where you then store that token for that user in your database (see part 4).</p><h4>Troubles</h4><p>After a couple of weeks I noticed that if many events happened on GitHub in quick succession the Perfect notification engine would continuously send push notifications without end and I’d have to kill the server.</p><p>I reported the bug and the Perfect maintainer was very fast to commit a fix, unfortunately it was not a complete fix and I could still experience it. After that the maintainer stopped talking to me. I looked into fixing it myself but figuring out the cause was beyond me, so I started looking into other options.</p><p>Vapor have a APNs layer, but using it would mean pulling in most of Vapor. By this point I knew how communicating with APNs worked quite well and I realized I could just write my own because I only needed to implement a subset of the full requirements. For example APNs supports having multiple “pipes” inside the same HTTP2 connection, this number is limited and you have to support knowing how many to use at a time. Canopy doesn’t need this yet and probably won’t for some time, instead I can only send one notification at a time, but my tests showed I can still send almost 600 per second this way. (Apple claim you can send more than 5,000 per second if your code is working efficiently). Also advanced APNs engines will make new connections to Apple as needed, I wouldn’t need more than one.</p><h4>Reinventing The Wheel</h4><p>RtW is not that bad; you learn the most when you do it yourself.</p><p>Firstly I needed a HTTP2 library. Perfect does it themselves. Vapor uses libcurl. The libcurl I needed though was a newer version than comes with Ubuntu 16.04, so I had to configure, make and make install my own to /usr/local.</p><p>Perfect already uses libcurl for other things, so importing this newer version was in fact automatic (also because Linux has no rpath so it will use whichever library is in the library-path first, and /usr/local is there).</p><p>Here’s how we prepare the connection:</p><pre>let curlHandle: UnsafeMutableRawPointer<br>var url: String<br>if production {<br>    url = &quot;<a href="https://api.push.apple.com/3/device/">https://api.push.apple.com/3/device/</a>&quot;<br>} else {<br>    url = &quot;<a href="https://api.sandbox.push.apple.com/3/device/">https://api.sandbox.push.apple.com/3/device/</a>&quot;<br>}<br>curlHandle = curl_easy_init()<br><br>url.withCString {<br>    var str = UnsafeMutablePointer(mutating: $0)<br>    curlHelperSetOptString(curlHandle, CURLOPT_URL, str)<br>}</pre><pre>curlHelperSetOptInt(curlHandle, CURLOPT_HTTP_VERSION, <br>    CURL_HTTP_VERSION_2_0)<br>curlHelperSetOptInt(curlHandle, CURLOPT_PORT, 443)<br>curlHelperSetOptBool(curlHandle, CURLOPT_FOLLOWLOCATION, CURL_TRUE)<br>curlHelperSetOptBool(curlHandle, CURLOPT_POST, CURL_TRUE)<br>curlHelperSetOptBool(curlHandle, CURLOPT_HEADER, CURL_TRUE)</pre><p>Then to send something:</p><pre>var json: [String: Any] = //…<br>json.append(0)<br>json.withUnsafeMutableBytes {<br>    _ = curlHelperSetOptString(curlHandle, CURLOPT_POSTFIELDS, $0)<br>}<br>curlHelperSetOptInt(curlHandle, CURLOPT_POSTFIELDSIZE, json.count - 1)</pre><pre>// headers<br>var curlHeaders: UnsafeMutablePointer&lt;curl_slist&gt;?<br>curlHeaders = curl_slist_append(curlHeaders, &quot;Authorization: bearer \(jwt)&quot;)<br>curlHeaders = curl_slist_append(curlHeaders, &quot;User-Agent: Canopy, Codebase LLC&quot;)<br>curlHeaders = curl_slist_append(curlHeaders, &quot;apns-topic: \(topic)&quot;)<br>curlHeaders = curl_slist_append(curlHeaders, &quot;Accept: application/json&quot;)<br>curlHeaders = curl_slist_append(curlHeaders, &quot;Content-Type: application/json; charset=utf-8&quot;)<br>curlHelperSetOptHeaders(curlHandle, curlHeaders)<br>defer {<br>    curlHeaders.map(curl_slist_free_all)<br>}</pre><pre>// send<br>curl_easy_perform(curlHandle)</pre><p>Yikes. And that’s not all. The topic is your app’s bundle id, which for Canopy varies (macOS and iOS), but you may be able to hardcode it if you have one. The json is weird, but <a href="https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/generating_a_remote_notification">documented</a>. The jwt is a unique key that you have to generate once every hour. Here’s my code:</p><pre>let now = Date()<br>let payload: [String: Any] = [&quot;iss&quot;: teamId, &quot;iat&quot;: Int(now.timeIntervalSince1970)]<br>let jwt = JWTCreator(payload: payload)!<br>let pem = try! PEMKey(pemPath: &quot;./AuthKey_5354D789X6.p8&quot;)<br>return try! jwt.sign(alg: JWT.Alg.es256, key: pem, headers: [&quot;kid&quot;: &quot;5354D789X6&quot;])</pre><p>Perfect provides the JWTCreator and PEMKey objects. The bangs are safe since any nil here would be a development error.</p><p>Now be sure that you don’t create new jwts too often, Apple will close your connection. Also keep the same curl_handle, don’t close it <em>or</em> reopen it. You want a persistent connection. libcurl will automatically reopen it for you should Apple close it which is very handy.</p><p>You also have to do more, you need to listen to the responses from Apple, if they send certain HTTP response codes you need to delete those tokens from your database. The Internet thinks that if you ignore these messages Apple start to throttle your connection or worse.</p><p>Having done all this my APNs system has stayed up for weeks. I was surprised, but really I have the libcurl people to thank for it all, they did the hard work.</p><h4>Don’t Write Your Own</h4><p>If you need APNs pick Vapor.</p><h3>Hey there!</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*abOrO2qgFk4lI4nugVfhqA.jpeg" /></figure><p>I’m Max Howell and I want to be full-time making, writing and being all about open source. I’ve been doing open source for more than 15 years, and you probably already use some of it (Homebrew anyone?). I need your help to continue, any contribution is most welcome. Thanks so much.</p><p><a href="https://patreon.com/mxcl">https://patreon.com/mxcl</a></p><h3>Part 6 Coming Soon!</h3><p>Next week: final thoughts, conclusions and hindsight.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6b7de0d0feac" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>