<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Harsh Shandilya</title>
    <description>The latest articles on Forem by Harsh Shandilya (@msfjarvis).</description>
    <link>https://forem.com/msfjarvis</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F83424%2Fea542571-bb61-4d75-a3ba-b5491dc170a0.jpeg</url>
      <title>Forem: Harsh Shandilya</title>
      <link>https://forem.com/msfjarvis</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/msfjarvis"/>
    <language>en</language>
    <item>
      <title>Tips and tricks for using Renovate</title>
      <dc:creator>Harsh Shandilya</dc:creator>
      <pubDate>Tue, 17 Jan 2023 19:32:18 +0000</pubDate>
      <link>https://forem.com/msfjarvis/tips-and-tricks-for-using-renovate-4gl6</link>
      <guid>https://forem.com/msfjarvis/tips-and-tricks-for-using-renovate-4gl6</guid>
      <description>&lt;p&gt;&lt;a href="https://www.mend.io/free-developer-tools/renovate/"&gt;Mend Renovate&lt;/a&gt; is a free to use dependency update management service powered by the open-source &lt;a href="https://github.com/renovatebot/renovate"&gt;renovate&lt;/a&gt;, and is a compelling alternative to GitHub’s blessed solution for this problem space: &lt;a href="https://docs.github.com/en/code-security/dependabot"&gt;Dependabot&lt;/a&gt;. Renovate offers a significantly larger suite of supported language ecosystems compared to Dependabot as well as fine-grained control over where it finds dependencies, how it chooses updated versions, and a lot more. TL;DR: Renovate is a massive upgrade over Dependabot and you should evaluate it if &lt;em&gt;any&lt;/em&gt; aspect of Dependabot has caused you grief, there’s a good chance Renovate does it better.&lt;/p&gt;

&lt;p&gt;I’m collecting some tips here about “fancy” things I’ve done using Renovate that may be helpful to other folks. You’ll be able to find more details about all of these in their very high quality docs at &lt;a href="https://docs.renovatebot.com/"&gt;docs.renovatebot.com&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Disabling updates for individual packages
&lt;/h2&gt;

&lt;p&gt;There are times where you’re sticking with an older version of a package (temporarily or otherwise) and you just don’t want to see PRs bumping it, wasting CI resources for an upgrade that will probably fail and is definitely not going to be merged. Renovate offers a convenient way to do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"packageRules"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"managers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"gradle"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"packagePatterns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"^com.squareup.okhttp3"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Grouping updates together
&lt;/h2&gt;

&lt;p&gt;Renovate already includes preset configurations for &lt;a href="https://github.com/renovatebot/renovate/blob/b4d1ad8e5210017a3550c9da4342b0953a70330a/lib/config/presets/internal/monorepo.ts"&gt;monorepos&lt;/a&gt; that publish multiple packages with identical versions, but you can also easily add more of your own. As an example, here’s how you can combine updates of the serde crate and its derive macro.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"packageRules"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"managers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"cargo"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"matchPackagePatterns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"serde"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"serde_derive"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"groupName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"serde"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Set a semver range for upgrades
&lt;/h2&gt;

&lt;p&gt;Sometimes there are cases where you may need to set an upper bound on a package dependency to avoid breaking changes or regressions. Renovate offers intuitive support for the same.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"packageRules"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"matchPackageNames"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"com.android.tools.build:gradle"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"allowedVersions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;=7.4.0"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Supporting non-standard dependency declarations
&lt;/h2&gt;

&lt;p&gt;Dependency versions are sometimes specified without their package names, for example in config files. These cannot be automatically detected by Renovate, but you can use a regular expression to teach it how to identify these dependencies.&lt;/p&gt;

&lt;p&gt;For example, you can specify the version of Hugo to build your Netlify site with in the &lt;code&gt;netlify.toml&lt;/code&gt; file in your repository.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[build.environment]&lt;/span&gt;
  &lt;span class="py"&gt;HUGO_VERSION&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.109.0"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is how the relevant configuration might look like with Renovate&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"regexManagers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Update Hugo version in Netlify config"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"fileMatch"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;".toml$"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"matchStrings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"HUGO_VERSION = &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;(?&amp;lt;currentValue&amp;gt;.*?)&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"depNameTemplate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gohugoio/hugo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"datasourceTemplate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"github-releases"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can read more about Regex Managers &lt;a href="https://docs.renovatebot.com/modules/manager/regex/"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making your GitHub Actions usage more secure
&lt;/h2&gt;

&lt;p&gt;According to GitHub’s &lt;a href="https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-third-party-actions"&gt;official recommendations&lt;/a&gt;, you should be using exact commit SHAs instead of tags for third-party actions. However, this is a pain to do manually. Instead, allow Renovate to manage it for you!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"extends"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"config:base"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"helpers:pinGitHubActionDigests"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="err"&gt;﻿&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;﻿&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Automatically merging compatible updates
&lt;/h2&gt;

&lt;p&gt;Every person with a JavaScript project has definitely loved getting 20 PRs from Dependabot about arbitrary transitive dependencies that they didn’t even realise they had. With Renovate, that pain can also be automated away if you have a robust enough test suite to permit automatic merging of minor updates.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"automergeType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"branch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"packageRules"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Automerge non-major updates"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"matchUpdateTypes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"minor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"patch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"digest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"lockFileMaintenance"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"automerge"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this configuration, Renovate will push compatible updates to &lt;code&gt;renovate/$depName&lt;/code&gt; branches and merge it automatically to your main branch if CI runs on the branch and passes. To make that happen, you will also need to update your GitHub Actions workflows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt; name: Run tests
 on:
   pull_request:
     branches:
       - main
&lt;span class="gi"&gt;+ push:
+ branches:
+ - renovate/**
&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Closing notes
&lt;/h2&gt;

&lt;p&gt;This list currently consists exclusively of things I’ve used in my own projects. There is way more you can achieve with Renovate, and I recommend going through the docs at &lt;a href="https://docs.renovatebot.com/"&gt;docs.renovatebot.com&lt;/a&gt; to find any useful knobs for the language ecosystem you wish to use it with. If you come across something interesting not covered here, let me know either below or on Mastodon at &lt;a href="https://androiddev.social/@msfjarvis"&gt;@msfjarvis@androiddev.social&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>dependencies</category>
      <category>renovate</category>
    </item>
    <item>
      <title>Writing your own Nix Flake checks</title>
      <dc:creator>Harsh Shandilya</dc:creator>
      <pubDate>Sun, 18 Dec 2022 00:00:00 +0000</pubDate>
      <link>https://forem.com/msfjarvis/writing-your-own-nix-flake-checks-1aen</link>
      <guid>https://forem.com/msfjarvis/writing-your-own-nix-flake-checks-1aen</guid>
      <description>&lt;h2&gt;
  
  
  Preface
&lt;/h2&gt;

&lt;p&gt;Ever since discovering &lt;a href="https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-flake-check.html"&gt;nix(3) flake check&lt;/a&gt; from &lt;a href="https://github.com/ipetkov/crane"&gt;crane&lt;/a&gt; (wonderful tool btw, highly recommend it if you’re building Rust things), I’ve wanted to be able to quickly write my own flake checks. Unfortunately, as with everything Nix, dummy-friendly documentation was hard to come by so I started trying out a bunch of things until I ended up with something that worked, which I’ll share below.&lt;/p&gt;

&lt;h2&gt;
  
  
  The premise
&lt;/h2&gt;

&lt;p&gt;I had been using a basic shell script with a &lt;code&gt;nix-shell&lt;/code&gt; shebang for a while to run formatters on my scripts repo and while it worked, &lt;code&gt;nix-shell&lt;/code&gt; startup is fairly slow and it just wasn’t cutting it for me. So I decided to try porting it to &lt;code&gt;nix flake check&lt;/code&gt; which would benefit from evaluation caching and be faster while removing the overhead of &lt;code&gt;nix-shell&lt;/code&gt; from the utility script.&lt;/p&gt;

&lt;h2&gt;
  
  
  The thing you’re here for
&lt;/h2&gt;

&lt;p&gt;Like everything in Nix, the checks needed to be derivations that Nix will build and run the respective &lt;code&gt;checkPhase&lt;/code&gt; of. So naively, I put together this to run the &lt;a href="https://github.com/kamadorueda/alejandra"&gt;alejandra&lt;/a&gt; Nix formatter, &lt;a href="https://github.com/mvdan/sh"&gt;shfmt&lt;/a&gt; to format shell scripts and &lt;a href="https://shellcheck.net/"&gt;shellcheck&lt;/a&gt; to lint them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="nv"&gt;outputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;nixpkgs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;flake-utils&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}:&lt;/span&gt;
  &lt;span class="nv"&gt;flake-utils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;eachDefaultSystem&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;system&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt;
    &lt;span class="nv"&gt;pkgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="nv"&gt;nixpkgs&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kn"&gt;inherit&lt;/span&gt; &lt;span class="nv"&gt;system&lt;/span&gt;&lt;span class="p"&gt;;};&lt;/span&gt;
    &lt;span class="nv"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;concatStringsSep&lt;/span&gt; &lt;span class="s2"&gt;" "&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="c"&gt;# Individual shell scripts from the repository&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nv"&gt;fmt-check&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;stdenv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;mkDerivation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nv"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"fmt-check"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nv"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sx"&gt;./.&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nv"&gt;nativeBuildInputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kn"&gt;with&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;alejandra&lt;/span&gt; &lt;span class="nv"&gt;shellcheck&lt;/span&gt; &lt;span class="nv"&gt;shfmt&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
      &lt;span class="nv"&gt;checkPhase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;        shfmt -d -s -i 2 -ci &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;files&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;        alejandra -c .&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;        shellcheck -x &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;files&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;      ''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="kn"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;checks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kn"&gt;inherit&lt;/span&gt; &lt;span class="nv"&gt;fmt-check&lt;/span&gt;&lt;span class="p"&gt;;};&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I needed a space separated list of my shell scripts to pass to shfmt and shellcheck, so I used a library function from nixpkgs called &lt;code&gt;concatStringsSep&lt;/code&gt; that takes a list, and concatenates it together with the given separator. That’s the &lt;code&gt;files&lt;/code&gt; binding declared in the snippet above.&lt;/p&gt;

&lt;p&gt;Here I ran into my first problem: Nix expects every derivation to generate an output which meant this doesn’t actually build.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜ nix flake check
error: flake attribute 'checks.fmt-check.outPath' is not a derivation

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There’s been &lt;a href="https://github.com/NixOS/nixpkgs/issues/16182"&gt;some discussion&lt;/a&gt; about this but the TL;DR is that &lt;code&gt;mkDerivation&lt;/code&gt; must produce an output. So I tried to cheat around this requirement by faking an output.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git flake.nix flake.nix
index b7fef3b99110..a531a30ad88e 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- flake.nix
&lt;/span&gt;&lt;span class="gi"&gt;+++ flake.nix
&lt;/span&gt;&lt;span class="p"&gt;@@ -18,6 +18,7 @@&lt;/span&gt;
       ];
       fmt-check = pkgs.stdenv.mkDerivation {
         name = "fmt-check";
&lt;span class="gi"&gt;+ dontBuild = true;
&lt;/span&gt;         src = ./.;
         nativeBuildInputs = with pkgs; [alejandra shellcheck shfmt];
         checkPhase = ''
&lt;span class="p"&gt;@@ -25,6 +26,11 @@&lt;/span&gt;
           alejandra -c .
           shellcheck -x ${files}
         '';
&lt;span class="gi"&gt;+ installPhase = ''
+ mkdir "$out"
+ '';
&lt;/span&gt;       };
     in {
       checks = {inherit fmt-check;};

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;dontBuild&lt;/code&gt; does exactly what you’d think and makes Nix not execute the &lt;code&gt;buildPhase&lt;/code&gt; of the derivation, and the &lt;code&gt;mkdir $out&lt;/code&gt; in the &lt;code&gt;installPhase&lt;/code&gt; generates the output directory Nix was looking for which is still valid even if completely empty.&lt;/p&gt;

&lt;p&gt;You can make this slightly faster by using a smaller stdenv that won’t pull in a compiler toolchain or be rebuilt when said toolchain is updated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git flake.nix flake.nix
index 7ce7a2ba80f8..b69db13fbc6d 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- flake.nix
&lt;/span&gt;&lt;span class="gi"&gt;+++ flake.nix
&lt;/span&gt;&lt;span class="p"&gt;@@ -16,7 +16,7 @@&lt;/span&gt;
       files = pkgs.lib.concatStringsSep " " [
         # bunch of shell scripts since I didn't have an extension I could glob against
       ];
&lt;span class="gd"&gt;- fmt-check = pkgs.stdenv.mkDerivation {
&lt;/span&gt;&lt;span class="gi"&gt;+ fmt-check = pkgs.stdenvNoCC.mkDerivation {
&lt;/span&gt;         name = "fmt-check";
         dontBuild = true;
         src = ./.;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The final result
&lt;/h2&gt;

&lt;p&gt;This is what the flake looked like for me after all this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nix"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"A very basic flake"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nv"&gt;inputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;nixpkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"github:nixos/nixpkgs/nixpkgs-unstable"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;flake-utils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"github:numtide/flake-utils/master"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nv"&gt;outputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;nixpkgs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;flake-utils&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}:&lt;/span&gt;
    &lt;span class="nv"&gt;flake-utils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;eachDefaultSystem&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;system&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt;
      &lt;span class="nv"&gt;pkgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="nv"&gt;nixpkgs&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kn"&gt;inherit&lt;/span&gt; &lt;span class="nv"&gt;system&lt;/span&gt;&lt;span class="p"&gt;;};&lt;/span&gt;
      &lt;span class="nv"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;concatStringsSep&lt;/span&gt; &lt;span class="s2"&gt;" "&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="c"&gt;# Individual shell scripts from the repository&lt;/span&gt;
      &lt;span class="p"&gt;];&lt;/span&gt;
      &lt;span class="nv"&gt;fmt-check&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;stdenvNoCC&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;mkDerivation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"fmt-check"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;dontBuild&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sx"&gt;./.&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;nativeBuildInputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kn"&gt;with&lt;/span&gt; &lt;span class="nv"&gt;pkgs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;alejandra&lt;/span&gt; &lt;span class="nv"&gt;shellcheck&lt;/span&gt; &lt;span class="nv"&gt;shfmt&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="nv"&gt;checkPhase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;          shfmt -d -s -i 2 -ci &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;files&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;          alejandra -c .&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;          shellcheck -x &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;files&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;        ''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;installPhase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;          mkdir "$out"&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;        ''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="kn"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nv"&gt;checks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kn"&gt;inherit&lt;/span&gt; &lt;span class="nv"&gt;fmt-check&lt;/span&gt;&lt;span class="p"&gt;;};&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It’s probably not idiomatic Nix (for some definition of idiomatic) but the entire thing has been a trial and error anyway so 🤷&lt;/p&gt;

&lt;p&gt;I’m very much a noob when it comes to Nix so any feedback is very welcome and appreciated!&lt;/p&gt;

</description>
      <category>nix</category>
    </item>
    <item>
      <title>Mastodon on your own domain without hosting a server, Netlify edition</title>
      <dc:creator>Harsh Shandilya</dc:creator>
      <pubDate>Wed, 16 Nov 2022 00:00:00 +0000</pubDate>
      <link>https://forem.com/msfjarvis/mastodon-on-your-own-domain-without-hosting-a-server-netlify-edition-1o24</link>
      <guid>https://forem.com/msfjarvis/mastodon-on-your-own-domain-without-hosting-a-server-netlify-edition-1o24</guid>
      <description>&lt;h2&gt;
  
  
  Preface
&lt;/h2&gt;

&lt;p&gt;I recently came across &lt;a href="https://blog.maartenballiauw.be/post/2022/11/05/mastodon-own-donain-without-hosting-server.html"&gt;a blog post&lt;/a&gt; from &lt;a href="https://mastodon.online/@maartenballiauw"&gt;Maarten Balliauw&lt;/a&gt; that explained how they had managed to create an ActivityPub compatible identity for themselves, without hosting Mastodon or any other ActivityPub server.&lt;/p&gt;

&lt;p&gt;I recommend going to their blog and reading the whole thing, but here’s a TL;DR&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://activitypub.rocks/"&gt;ActivityPub&lt;/a&gt; has the notion of an “actor” that sends messages&lt;/li&gt;
&lt;li&gt;This “actor” must be discoverable via a protocol called &lt;a href="https://webfinger.net"&gt;WebFinger&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;WebFinger is ridiculously easy to implement&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For all practical purposes, WebFinger is essentially a JSON document that is served at &lt;code&gt;/.well-known/webfinger&lt;/code&gt; from a domain and is used to identify “actors” across the Fediverse.&lt;/p&gt;

&lt;p&gt;Maarten’s approach to implementing this was to simply place the JSON document at &lt;code&gt;/.well-known/webfinger&lt;/code&gt; on their domain &lt;code&gt;balliauw.be&lt;/code&gt;, which allowed &lt;code&gt;@maarten@balliauw.be&lt;/code&gt; to become a WebFinger-compatible identity that can be searched for on Mastodon and will return their actual &lt;code&gt;@maartenballiauw.be@mastodon.online&lt;/code&gt; profile.&lt;/p&gt;

&lt;p&gt;Maarten did however note that since they’re relying on static hosting, they’re unable to restrict what identities they can enforce as valid, and thus a search for &lt;code&gt;@anything@balliauw.be&lt;/code&gt; will also return their &lt;code&gt;mastodon.online&lt;/code&gt; identity.&lt;/p&gt;

&lt;h2&gt;
  
  
  The implementation
&lt;/h2&gt;

&lt;p&gt;I wanted to also set up something like this, but without the limitation Maarten had run into. Since my website runs on Netlify, I decided to try out using an &lt;a href="https://docs.netlify.com/edge-functions/overview/"&gt;Edge Function&lt;/a&gt; to build this up.&lt;/p&gt;

&lt;p&gt;Similar to Maarten, I first obtained my current Fediverse identity from the Mastodon server I am on: &lt;a href="https://androiddev.social"&gt;androiddev.social&lt;/a&gt; (incredible props to &lt;a href="https://androiddev.social/@friendlymike"&gt;Mikhail&lt;/a&gt; for making it a reality).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜ curl -s https://androiddev.social/.well-known/webfinger?resource=acct:msfjarvis@androiddev.social | jq .
{
  "subject": "acct:msfjarvis@androiddev.social",
  "aliases": [
    "https://androiddev.social/@msfjarvis",
    "https://androiddev.social/users/msfjarvis"
  ],
  "links": [
    {
      "rel": "http://webfinger.net/rel/profile-page",
      "type": "text/html",
      "href": "https://androiddev.social/@msfjarvis"
    },
    {
      "rel": "self",
      "type": "application/activity+json",
      "href": "https://androiddev.social/users/msfjarvis"
    },
    {
      "rel": "http://ostatus.org/schema/1.0/subscribe",
      "template": "https://androiddev.social/authorize_interaction?uri={uri}"
    }
  ]
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this in hand, now we can get started on wiring this up into our website.&lt;/p&gt;

&lt;p&gt;First, create an Edge Function using the Netlify CLI. Here’s the options I chose.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜ yarn exec ntl functions:create --name webfinger
? Select the type of function you'd like to create: Edge function (Deno)
? Select the language of your function: TypeScript
? Pick a template: typescript-json
? Name your function: webfinger
◈ Creating function webfinger
◈ Created netlify/edge-functions/webfinger/webfinger.ts
? What route do you want your edge function to be invoked on?: /.well-known/webfinger
◈ Function 'webfinger' registered for route `/.well-known/webfinger`. To change, edit your `netlify.toml` file.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, add the following code to the TypeScript file just created for you. I’ve added comments inline to explain what each part of the code does so you can customize it according to your needs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Netlify Edge Functions run on Deno (https://deno.land), so imports use URLs rather than package names.&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Status&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://deno.land/std@0.136.0/http/http_status.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Context&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://edge.netlify.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// We obtain the value of the 'resource' query parameter so that we&lt;/span&gt;
  &lt;span class="c1"&gt;// can ensure a response is only sent for the identity we want.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resourceParam&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;resource&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resourceParam&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;No 'resource' query parameter was provided&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BadRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// I want to be searchable as `@harsh@msfjarvis.dev`, so I only&lt;/span&gt;
    &lt;span class="c1"&gt;// allow requests that set the resource query param to this value.&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resourceParam&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;acct:harsh@msfjarvis.dev&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;An invalid identity was requested&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BadRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Here's the JSON object we got earlier&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;acct:msfjarvis@androiddev.social&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;aliases&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://androiddev.social/@msfjarvis&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://androiddev.social/users/msfjarvis&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;links&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://webfinger.net/rel/profile-page&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text/html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://androiddev.social/@msfjarvis&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;self&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/activity+json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://androiddev.social/users/msfjarvis&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://ostatus.org/schema/1.0/subscribe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://androiddev.social/authorize_interaction?uri={uri}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that’s it! You can test it out as below to verify things work as expected.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜ curl -s https://msfjarvis.dev/.well-known/webfinger | jq .
{
  "error": "No 'resource' query parameter was provided"
}

➜ curl -s https://msfjarvis.dev/.well-known/webfinger?resource=acct:anything@msfjarvis.dev | jq .
{
  "error": "An invalid identity was requested"
}

➜ curl -s https://msfjarvis.dev/.well-known/webfinger?resource=acct:harsh@msfjarvis.dev | jq .
{
  "subject": "acct:msfjarvis@androiddev.social",
  "aliases": [
    "https://androiddev.social/@msfjarvis",
    "https://androiddev.social/users/msfjarvis"
  ],
  "links": [
    {
      "rel": "http://webfinger.net/rel/profile-page",
      "type": "text/html",
      "href": "https://androiddev.social/@msfjarvis"
    },
    {
      "rel": "self",
      "type": "application/activity+json",
      "href": "https://androiddev.social/users/msfjarvis"
    },
    {
      "rel": "http://ostatus.org/schema/1.0/subscribe",
      "template": "https://androiddev.social/authorize_interaction?uri={uri}"
    }
  ]
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thanks again to Maarten for doing the initial research for this and writing about it!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Writing Paparazzi tests for your Kotlin Multiplatform projects</title>
      <dc:creator>Harsh Shandilya</dc:creator>
      <pubDate>Sun, 26 Jun 2022 00:00:00 +0000</pubDate>
      <link>https://forem.com/msfjarvis/writing-paparazzi-tests-for-your-kotlin-multiplatform-projects-ka4</link>
      <guid>https://forem.com/msfjarvis/writing-paparazzi-tests-for-your-kotlin-multiplatform-projects-ka4</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/cashapp/paparazzi" rel="noopener noreferrer"&gt;Paparazzi&lt;/a&gt; is a Gradle plugin and library that enables writing UI tests for Android screens that run entirely on the JVM, without needing a physical device or emulator. This is massive, since it significantly increases the speed of UI tests as well as allows them to run on any CI system, not just ones using macOS or Linux with KVM enabled.&lt;/p&gt;

&lt;p&gt;Unfortunately, Paparazzi does not directly work with Kotlin Multiplatform projects so you cannot apply it to a KMP + Android module and start putting your tests in the &lt;code&gt;androidTest&lt;/code&gt; source set (not to be confused with &lt;code&gt;androidAndroidTest&lt;/code&gt;. Yes, I know). Why would you want to do this in the first place? Like everything cool and new in Android land, &lt;a href="https://d.android.com/jetpack/compose" rel="noopener noreferrer"&gt;Compose&lt;/a&gt;! Specifically, &lt;a href="https://github.com/jetbrains/compose-jb" rel="noopener noreferrer"&gt;compose-jb&lt;/a&gt;, JetBrains’ redistribution of Jetpack Compose optimised for Kotlin Multiplatform.&lt;/p&gt;

&lt;p&gt;I’ve &lt;a href="https://github.com/cashapp/paparazzi/pull/450" rel="noopener noreferrer"&gt;sent a PR&lt;/a&gt; to Paparazzi that will resolve this issue, and in the mean time we can workaround this limitation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting things up
&lt;/h2&gt;

&lt;p&gt;To begin, we’ll need a new Gradle module for our Paparazzi tests. Since Paparazzi doesn’t understand Kotlin Multiplatform yet, we’re gonna hide that aspect of our project and present it a pure Android library project. Set up the module like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// paparazzi-tests/build.gradle.kts&lt;/span&gt;
&lt;span class="nf"&gt;plugins&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"com.android.library"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"app.cash.paparazzi"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;android&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;buildFeatures&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;compose&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, add dependencies in this module to the modules that contain the composables you’d like to test. As you might have guessed, this approach currently limits you to only being able to test public composables. However, if you’re trying to test the UI exposed by a “common” module like I am, that might not be such a big deal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// paparazzi-tests/build.gradle.kts&lt;/span&gt;
&lt;span class="nf"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;testImplementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;common&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that’s pretty much it! You can now be off to the races and start writing your tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// paparazzi-tests/src/test/kotlin/UserProfileTest.kt&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserProfileTest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nc"&gt;Rule&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;paparazzi&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Paparazzi&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="nd"&gt;@Test&lt;/span&gt;
  &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;light_mode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;paparazzi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;snapshot&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nc"&gt;MaterialTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;colorScheme&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LightThemeColors&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;UserProfile&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nd"&gt;@Test&lt;/span&gt;
  &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;dark_mode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;paparazzi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;snapshot&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nc"&gt;MaterialTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;colorScheme&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DarkThemeColors&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;UserProfile&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Consult the &lt;a href="https://cashapp.github.io/paparazzi" rel="noopener noreferrer"&gt;Paparazzi documentation&lt;/a&gt; for the Gradle tasks reference and customization options.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recipes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Disable release build type for test module
&lt;/h3&gt;

&lt;p&gt;If you use &lt;code&gt;./gradlew check&lt;/code&gt; in your CI, our new module will be tested in both release and debug build types. This is fairly redundant, so you can disable the release build type altogether:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// paparazzi-tests/build.gradle.kts&lt;/span&gt;
&lt;span class="nf"&gt;androidComponents&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;beforeVariants&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;variant&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enable&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;buildType&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"debug"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Running with JDK 12+
&lt;/h3&gt;

&lt;p&gt;You will run into &lt;a href="https://github.com/cashapp/paparazzi/issues/409" rel="noopener noreferrer"&gt;this issue&lt;/a&gt; if you use JDK 12 or above to run Paparazzi-backed tests. I’ve &lt;a href="https://github.com/cashapp/paparazzi/pull/474" rel="noopener noreferrer"&gt;started working&lt;/a&gt; on a fix for it upstream, in the mean time it can be worked around by forcing the test tasks to run with JDK 11.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// paparazzi-tests/build.gradle.kts&lt;/span&gt;
&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;withType&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Test&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;().&lt;/span&gt;&lt;span class="nf"&gt;configureEach&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;javaLauncher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;javaToolchains&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launcherFor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;languageVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;JavaLanguageVersion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Testing with multiple themes easily
&lt;/h3&gt;

&lt;p&gt;Using an enum and Google’s &lt;a href="https://github.com/google/TestParameterInjector" rel="noopener noreferrer"&gt;TestParameterInjector&lt;/a&gt; you can write a single test and have it run against all your themes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// paparazzi-tests/src/test/kotlin/Theme.kt&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;androidx.compose.material3.ColorScheme&lt;/span&gt;

&lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Theme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ColorScheme&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;Light&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;LightThemeColors&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nc"&gt;Dark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;DarkThemeColors&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="c1"&gt;// paparazzi-tests/src/test/kotlin/UserProfileTest.kt&lt;/span&gt;
&lt;span class="nd"&gt;@RunWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TestParameterInjector&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserProfileTest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nc"&gt;Rule&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;paparazzi&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Paparazzi&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="nd"&gt;@Test&lt;/span&gt;
  &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@TestParameter&lt;/span&gt; &lt;span class="n"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Theme&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;paparazzi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nc"&gt;MaterialTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;colorScheme&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;UserProfile&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>android</category>
      <category>jetpackcompose</category>
    </item>
    <item>
      <title>Converting Gradle convention plugins to binary plugins</title>
      <dc:creator>Harsh Shandilya</dc:creator>
      <pubDate>Sun, 17 Apr 2022 00:00:00 +0000</pubDate>
      <link>https://forem.com/msfjarvis/converting-gradle-convention-plugins-to-binary-plugins-gbc</link>
      <guid>https://forem.com/msfjarvis/converting-gradle-convention-plugins-to-binary-plugins-gbc</guid>
      <description>&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;Gradle’s &lt;a href="https://docs.gradle.org/current/samples/sample_convention_plugins.html" rel="noopener noreferrer"&gt;convention plugins&lt;/a&gt; are a powerful feature that allow creating simple, reusable Gradle plugins that can be used across your multi-module projects to ensure all modules of a certain type are configured the same way. As an example, if you want to enforce that none of your Android library projects contain a &lt;code&gt;BuildConfig&lt;/code&gt; class then the convention plugin for it could look something like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;com.example.android-library.gradle.kts&lt;/code&gt;&lt;/p&gt;


&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;plugins&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"com.android.library"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;android&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;buildFeatures&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;buildConfig&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;p&gt;Then in your modules, you can use this plugin like so:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;library-module/build.gradle.kts&lt;/code&gt;&lt;/p&gt;


&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;plugins&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;id&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"com.example.android-library"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Setting up convention plugins in your project
&lt;/h2&gt;

&lt;p&gt;Gradle’s official sample linked above mentions &lt;code&gt;buildSrc&lt;/code&gt; as the location for your convention plugins. I’m inclined to disagree, &lt;code&gt;buildSrc&lt;/code&gt; has historically had issues with IDE support and it’s special status within Gradle’s project handling means any change within &lt;code&gt;buildSrc&lt;/code&gt; invalidates caches for your &lt;strong&gt;entire&lt;/strong&gt; project resulting in incredible amounts of time lost during incremental builds.&lt;/p&gt;

&lt;p&gt;The solution to all of these problems is &lt;a href="https://docs.gradle.org/current/userguide/composite_builds.html" rel="noopener noreferrer"&gt;composite builds&lt;/a&gt;, and &lt;a href="https://proandroiddev.com/stop-using-gradle-buildsrc-use-composite-builds-instead-3c38ac7a2ab3" rel="noopener noreferrer"&gt;Josef Raska has a fantastic article&lt;/a&gt; that thoroughly explains the shortcomings of &lt;code&gt;buildSrc&lt;/code&gt; and how composite builds solve them.&lt;/p&gt;

&lt;p&gt;A full explainer on the topic is slightly out of scope for this post, but I can wholeheartedly endorse Jendrik Johannes’ &lt;a href="https://github.com/jjohannes/idiomatic-gradle" rel="noopener noreferrer"&gt;idiomatic-gradle&lt;/a&gt; repository as an example of setting up the Gradle build of a real-world project while leveraging features introduced in recent versions of Gradle. I highly recommend also checking out their ‘Understanding Gradle’ &lt;a href="https://github.com/jjohannes/understanding-gradle#readme" rel="noopener noreferrer"&gt;video series&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why would you want to make binary plugins out of convention plugins
&lt;/h2&gt;

&lt;p&gt;First, let’s answer this: what is a binary plugin?&lt;/p&gt;

&lt;p&gt;A Gradle plugin that is resolved as a dependency rather than compiled from source is a binary plugin. Binary plugins are cool because the next best thing after a cached compilation task is one that doesn’t exist in the first place.&lt;/p&gt;

&lt;p&gt;For most use cases, convention plugins will need to be updated very infrequently. This means that having each developer execute the plugin build as part of their development process is needlessly wasteful, and we can instead just distribute them as maven dependencies.&lt;/p&gt;

&lt;p&gt;This also makes it significantly easier to share convention plugins between projects without resorting to historically painful solutions like Git submodules or just straight up copy-pasting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Publishing your convention plugins
&lt;/h2&gt;

&lt;p&gt;To their credit, Gradle supports this ability very well and you can actually publish all plugins within a build/project with minimal configuration. The changes required to publish &lt;a href="https://msfjarvis.dev/aps" rel="noopener noreferrer"&gt;Android Password Store&lt;/a&gt;’s convention plugins for Android are:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;build-logic/android-plugins/build.gradle.kts&lt;/code&gt;&lt;/p&gt;


&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;-plugins { `kotlin-dsl` }
&lt;/span&gt;&lt;span class="gi"&gt;+plugins {
+   `kotlin-dsl`
+   id("maven-publish")
+}
+
+group = "com.github.android-password-store"
+
+version = "1.0.0"
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;p&gt;After that you can run &lt;code&gt;gradle -p build-logic publishToMavenLocal&lt;/code&gt; and it will Just Work™️. You can configure additional publishing repositories in similar fashion to how you’d do it for a library project.&lt;/p&gt;

&lt;p&gt;If like me you need to publish these to &lt;a href="https://search.maven.org/" rel="noopener noreferrer"&gt;Maven Central&lt;/a&gt;, you’ll need slightly more setup since it enforces multiple security and publishing related best practices. Here’s how I use &lt;a href="https://github.com/vanniktech/gradle-maven-publish-plugin" rel="noopener noreferrer"&gt;gradle-maven-publish-plugin&lt;/a&gt; to configure the same (&lt;code&gt;gradle.properties&lt;/code&gt; changes omitted for brevity, the GitHub repository explains what you need):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;build-logic/settings.gradle.kts&lt;/code&gt;&lt;/p&gt;


&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gi"&gt;+pluginManagement {
+   repositories {
+     mavenCentral()
+     gradlePluginPortal()
+   }
+   plugins {
+     id("com.vanniktech.maven.publish.base") version "0.19.0"
+   }
+}
+
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;&lt;code&gt;build-logic/android-plugins/build.gradle.kts&lt;/code&gt;&lt;/p&gt;


&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gi"&gt;+import com.vanniktech.maven.publish.JavadocJar
+import com.vanniktech.maven.publish.JavaLibrary
+import com.vanniktech.maven.publish.MavenPublishBaseExtension
+import com.vanniktech.maven.publish.SonatypeHost
+import org.gradle.kotlin.dsl.provideDelegate
+
&lt;/span&gt;&lt;span class="gd"&gt;-plugins { `kotlin-dsl` }
&lt;/span&gt;&lt;span class="gi"&gt;+plugins {
+   `kotlin-dsl`
+   id("com.vanniktech.maven.publish.base")
+   id("signing")
+}
+
+configure&amp;lt;MavenPublishBaseExtension&amp;gt; {
+   group = requireNotNull(project.findProperty("GROUP"))
+   version = requireNotNull(project.findProperty("VERSION_NAME"))
+   publishToMavenCentral(SonatypeHost.DEFAULT)
+   signAllPublications()
+   configure(JavaLibrary(JavadocJar.Empty()))
+   pomFromGradleProperties()
+}
+
+ afterEvaluate {
+   signing {
+     val signingKey: String? by project
+     val signingPassword: String? by project
+     useInMemoryPgpKeys(signingKey, signingPassword)
+   }
+ }
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;p&gt;This will populate your POM files with the properties required by Maven Central and sign all artifacts with PGP.&lt;/p&gt;

&lt;h2&gt;
  
  
  Consuming your new binary plugins
&lt;/h2&gt;

&lt;p&gt;With your convention plugins converted to shiny new binary plugins, you might be inclined to start using them like so:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;autofill-parser/build.gradle.kts&lt;/code&gt;&lt;/p&gt;


&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;plugins {
&lt;/span&gt;&lt;span class="gd"&gt;- id("com.github.android-password-store.published-android-library")
- id("com.github.android-password-store.kotlin-android")
- id("com.github.android-password-store.kotlin-library")
- id("com.github.android-password-store.psl-plugin")
&lt;/span&gt;&lt;span class="gi"&gt;+ id("com.github.android-password-store.published-android-library") version "1.0.0"
+ id("com.github.android-password-store.kotlin-android") version "1.0.0"
+ id("com.github.android-password-store.kotlin-library") version "1.0.0"
+ id("com.github.android-password-store.psl-plugin") version "1.0.0"
&lt;/span&gt;}
&lt;span class="err"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;p&gt;However, this fails because &lt;code&gt;kotlin-android&lt;/code&gt; and &lt;code&gt;kotlin-library&lt;/code&gt; plugins resolve to the same binary JAR that encompasses all plugins from the &lt;code&gt;build-logic/kotlin-plugins&lt;/code&gt; module and results in a classpath conflict. To better understand how this resolution works, check out the docs on &lt;a href="https://docs.gradle.org/current/userguide/plugins.html#sec:plugin_markers" rel="noopener noreferrer"&gt;plugin markers&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The way to resolve this problem is to define the plugin versions in your &lt;code&gt;settings.gradle.kts&lt;/code&gt; file, where these classpath conflicts will be resolved automatically by Gradle:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;settings.gradle.kts&lt;/code&gt;&lt;/p&gt;


&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;@@ -14,6 +14,25 @@&lt;/span&gt; pluginManagement {
  mavenCentral()
  gradlePluginPortal()
}
&lt;span class="gi"&gt;+ plugins {
+   id("com.github.android-password-store.kotlin-android") version "1.0.0"
+   id("com.github.android-password-store.kotlin-library") version "1.0.0"
+   id("com.github.android-password-store.psl-plugin") version "1.0.0"
+   id("com.github.android-password-store.published-android-library") version "1.0.0"
+ }
&lt;/span&gt;}
&lt;span class="err"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;p&gt;And you’re off to the races!&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing notes
&lt;/h2&gt;

&lt;p&gt;This post was motivated by my goal of sharing a common set of Gradle configurations across my projects such as &lt;a href="https://msfjarvis.dev/aps" rel="noopener noreferrer"&gt;Android Password Store&lt;/a&gt; and &lt;a href="https://msfjarvis.dev/g/compose-lobsters" rel="noopener noreferrer"&gt;Claw&lt;/a&gt;, which maintain a nearly identical set of convention plugins shared between the projects that I manually copy-paste back and forth. I’ve extracted the &lt;code&gt;build-logic&lt;/code&gt; subproject of APS to a separate &lt;a href="https://msfjarvis.dev/g/aps-build-logic" rel="noopener noreferrer"&gt;aps-build-logic&lt;/a&gt; repository, set it up for standalone development and configured publishing support. My goal is to supplement this with a continuous deployment workflow where an automatic version bump + release happens after each commit to the main, after which I can migrate my projects to it.&lt;/p&gt;

</description>
      <category>gradle</category>
    </item>
    <item>
      <title>Backing up your content from Google Photos</title>
      <dc:creator>Harsh Shandilya</dc:creator>
      <pubDate>Mon, 04 Apr 2022 00:00:00 +0000</pubDate>
      <link>https://forem.com/msfjarvis/backing-up-your-content-from-google-photos-87g</link>
      <guid>https://forem.com/msfjarvis/backing-up-your-content-from-google-photos-87g</guid>
      <description>&lt;p&gt;Google Photos has established itself as one of the most popular photo storage services, and like a typical Google service, it makes it impressively difficult to get your data back out of it :D&lt;/p&gt;

&lt;p&gt;There are many good reasons why you’d want to archive your pictures outside of Google Photos, having an extra backup never hurts, maybe you want an offline copy for reasons, or you just want to get your stuff out so you can switch away from Google Photos entirely.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to archive your images from Google Photos
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;You can use &lt;a href="https://takeout.google.com/"&gt;Takeout&lt;/a&gt;, except it &lt;strong&gt;always&lt;/strong&gt; strips the EXIF metadata of your images, and often generates incomplete archives. Losing EXIF metadata is a deal-breaker, because you can no longer organize images automatically based on properties like date, location, and camera type.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You can download directly from &lt;a href="https://photos.google.com/"&gt;photos.google.com&lt;/a&gt; which preserves metadata, but is embarassingly manual and basically impossible to use if you’re trying archive a few years of history.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So, what’s the solution?&lt;/p&gt;

&lt;h3&gt;
  
  
  gphotos-cdp
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/perkeep/gphotos-cdp"&gt;gphotos-cdp&lt;/a&gt; is a tool that uses the nearly-perfect method number 2 and makes it automated. It does so by using the &lt;a href="https://chromedevtools.github.io/devtools-protocol/"&gt;Chrome DevTools Protocol&lt;/a&gt; to drive an instance of the Google Chrome browser, and emulates all the manual actions you’d take as a human to ensure you get copies of your pictures with all the EXIF metadata retained.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up gphotos-cdp
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Disclaimer: I’ve only tested this on Linux. This &lt;em&gt;should&lt;/em&gt; be doable on other platforms, but it’s not relevant to my needs so I will not be investigating that.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ideally you’d want to run this tool on a schedule on a NAS or a server to keep archiving images automatically as they get added to your Google Photos. I personally run this inside a hosted VM on a daily schedule.&lt;/p&gt;

&lt;p&gt;For gphotos-cdp to run in a non-interactive manner, it requires your browser data directory with your Google login cookies. You can easily create this with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;google-chrome \
  --user-data-dir=gphotos-cdp \
  --no-first-run \
  --password-store=basic \
  --use-mock-keychain \
  https://photos.google.com/

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will launch Google Chrome with a brand new profile. Login to Photos, and then close the browser. Optionally, re-run the command to ensure that you do not need to login again.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The flags passed to google-chrome are extracted from the default set of parameters used by gphotos-cdp. I wish I could explain why each flag is necessary, but all I know is that it does the trick. I got them from &lt;a href="https://github.com/perkeep/gphotos-cdp/issues/1#issuecomment-567378082"&gt;this GitHub comment&lt;/a&gt; on the issue tracker for gphotos-cdp.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once done, you’ll have a &lt;code&gt;gphotos-cdp&lt;/code&gt; directory that you’ll need to move to the &lt;code&gt;/tmp&lt;/code&gt; directory of whichever machine you wish to run gphotos-cdp on.&lt;/p&gt;

&lt;p&gt;gphotos-cdp is written in &lt;a href="https://go.dev"&gt;Golang&lt;/a&gt; so you’ll need to install it first. Once done, run the following command to install the latest version of gphotos-cdp&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;go install github.com/perkeep/gphotos-cdp@latest

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you can go ahead and start using gphotos-cdp, as given below&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/go/bin/gphotos-cdp \ # go install puts things in ~/go/bin by default
  -v \ # Enable verbose logging
  -dev \ # Enable dev mode which always uses /tmp/gphotos-cdp as the profile directory
  -headless \ # Run Chrome in headless mode so it works on servers and such
  -dldir ~/photos # Download everything to ~/photos

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The automation techniques used are not completely reliable and can often fail. You’ll want to implement some kind of retry-on-failure logic to ensure this is run a few times every day.&lt;/p&gt;

&lt;h3&gt;
  
  
  Monitoring
&lt;/h3&gt;

&lt;p&gt;With anything built on such a brittle foundation, it’s useful to be able to constantly monitor that things are working as they should.&lt;/p&gt;

&lt;p&gt;Using &lt;a href="https://healthchecks.io"&gt;healthchecks.io&lt;/a&gt; you can easily set up alerts that notify you of failures running the tool or unintentional gaps in the schedule you run the gphotos-cdp on. I use my &lt;a href="https://msfjarvis.dev/g/healthchecks-rs"&gt;healthchecks-monitor&lt;/a&gt; CLI in a &lt;a href="https://man7.org/linux/man-pages/man5/crontab.5.html"&gt;cron&lt;/a&gt; job to run gphotos-cdp every day, and healthchecks.io notifies me via Telegram when it fails. The script running in cron looks like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;

&lt;span class="nv"&gt;HEALTHCHECKS_CHECK_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;UUID &lt;span class="k"&gt;for &lt;/span&gt;check as given on healthchecks&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;HEALTHCHECKS_USERAGENT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;crontab &lt;span class="se"&gt;\&lt;/span&gt;
~/bin/healthchecks-monitor &lt;span class="nt"&gt;--retries&lt;/span&gt; 3 &lt;span class="se"&gt;\ &lt;/span&gt;&lt;span class="c"&gt;# Try running the command thrice before giving up&lt;/span&gt;
  &lt;span class="nt"&gt;--timer&lt;/span&gt; &lt;span class="se"&gt;\ &lt;/span&gt;&lt;span class="c"&gt;# Start off a server-side timer on healthchecks&lt;/span&gt;
  &lt;span class="nt"&gt;--logs&lt;/span&gt; &lt;span class="se"&gt;\ &lt;/span&gt;&lt;span class="c"&gt;# Record execution logs on healthchecks in case of failure, to help with debugging&lt;/span&gt;
  &lt;span class="nt"&gt;--exec&lt;/span&gt; &lt;span class="s2"&gt;"~/go/bin/gphotos-cdp -v -dev -headless -dldir ~/photos"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;As evident, it’s not an easy task to automatically archive your pictures from Google Photos. The setup is tedious and prone to breakage when any authentication related change happens, such as you accidentally logging out the “device” being used by gphotos-cdp or changing your password, in which case you will need to create the &lt;code&gt;gphotos-cdp&lt;/code&gt; directory with Chrome again.&lt;/p&gt;

&lt;p&gt;Also, the technique in this post could easily stop working at any time if Google chooses to break it. That being said, gphotos-cdp was last updated in 2020 and still continues to function as-is so there is some degree of hope that it can be used for quite a bit more.&lt;/p&gt;

&lt;p&gt;Hopefully this setup causes you minimal grief and allows you to back up your precious memories without relying only on Google :)&lt;/p&gt;

</description>
      <category>backup</category>
      <category>googlephotos</category>
    </item>
    <item>
      <title>Building static Rust binaries for Linux</title>
      <dc:creator>Harsh Shandilya</dc:creator>
      <pubDate>Sun, 17 Oct 2021 16:31:45 +0000</pubDate>
      <link>https://forem.com/msfjarvis/building-static-rust-binaries-for-linux-56a</link>
      <guid>https://forem.com/msfjarvis/building-static-rust-binaries-for-linux-56a</guid>
      <description>&lt;p&gt;Rust has supported producing statically linked binaries since &lt;a href="https://github.com/rust-lang/rfcs/pull/1721"&gt;RFC #1721&lt;/a&gt; which proposed the &lt;code&gt;target-feature=+crt-static&lt;/code&gt; flag to statically link the platform C library into the final binary. This was initially only supported for Windows MSVC and the MUSL C library. While MUSL works for &lt;em&gt;most&lt;/em&gt; people, it&lt;br&gt;
has many problems by virtue of being a work-in-progress such as &lt;a href="https://www.reddit.com/r/rust/comments/a6pna3/why_rust_uses_glibc_and_not_musl_by_default_for/ebzpzld/"&gt;unpredictable performance&lt;/a&gt; and many unimplemented features which programs tend to assume are present due to glibc being ubiquitous. In lieu of these concerns, support was added to Rust in 2019 to be able to &lt;a href="https://github.com/rust-lang/rust/issues/65447"&gt;statically link against glibc&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Unfortunately, if you try to directly use it with &lt;code&gt;RUSTFLAGS='-C target-feature=+crt-static' cargo build&lt;/code&gt; there is a good chance you'll run into an error similar to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cannot produce proc-macro for `async-trait v0.1.51` as the target `x86_64-unknown-linux-gnu` does not support these crate types
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a bit of a head scratcher, because the target (your host machine) &lt;em&gt;definitely&lt;/em&gt; supports proc-macro crates. Turns out, even Rust contributors &lt;a href="https://github.com/rust-lang/rust/issues/78210"&gt;were confused by this&lt;/a&gt;. The "fix" for this is apparently to pass in the &lt;code&gt;--target&lt;/code&gt; explicitly. The reason behind this seems to be a bug with cargo, where the &lt;code&gt;RUSTFLAGS&lt;/code&gt; are applied to the target platform only when &lt;code&gt;--target&lt;/code&gt; is explicitly provided. Without it, &lt;code&gt;RUSTFLAGS&lt;/code&gt; values are set for the host only which results in the errors we see. More details are available &lt;a href="https://github.com/rust-lang/rust/issues/78210#issuecomment-714776007"&gt;Rust issue #78210&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Therefore, the correct way to build a statically linked glibc executable for an x86_64 machine is this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;RUSTFLAGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'-C target-feature=+crt-static'&lt;/span&gt; cargo build &lt;span class="nt"&gt;--release&lt;/span&gt; &lt;span class="nt"&gt;--target&lt;/span&gt; x86_64-unknown-linux-gnu
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Other potential problems
&lt;/h2&gt;

&lt;p&gt;You may be unable to statically link your binary even after all this, due to dependencies that &lt;em&gt;mandate&lt;/em&gt; dynamic linking. In some cases this is avoidable, such as using &lt;a href="https://github.com/rustls/rustls"&gt;rustls&lt;/a&gt; in place of OpenSSL for cryptography, and &lt;a href="https://hyper.rs"&gt;hyper&lt;/a&gt; in place of bindings to cURL for HTTP, not so much in others. Thanks to the convention of native-linking crates using the &lt;code&gt;-sys&lt;/code&gt; suffix in their name it is fairly simple to find if your build has dependencies that dynamically link to libraries. Using &lt;code&gt;cargo&lt;/code&gt;'s native &lt;code&gt;tree&lt;/code&gt; subcommand and &lt;code&gt;grep&lt;/code&gt;ing (or &lt;a href="https://github.com/BurntSushi/ripgrep"&gt;ripgrep&lt;/a&gt;ing for me), you can locate native dependencies. Running &lt;code&gt;cargo tree | rg -- -sys&lt;/code&gt; against &lt;a href="https://msfjarvis.dev/g/androidx-release-watcher"&gt;androidx-release-watcher&lt;/a&gt;'s &lt;code&gt;v4.1.0&lt;/code&gt; release gives us this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;cargo tree | rg &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;-sys&lt;/span&gt;
│   │   │   │   ├── curl-sys v0.4.45+curl-7.78.0
│   │   │   │   │   ├── libnghttp2-sys v0.1.6+1.43.0
│   │   │   │   │   ├── libz-sys v1.1.3
│   │   │   │   │   └── openssl-sys v0.9.66
│   │   │   │   ├── openssl-sys v0.9.66 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
│   │   │   ├── curl-sys v0.4.45+curl-7.78.0 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
│   └── web-sys v0.3.53
│       ├── js-sys v0.3.53
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This indicates curl, zlib, openssl, and libnghttp2 as well as a bunch of WASM-related things are being dynamically linked into my executable. To resolve this, I looked at the build features exposed by &lt;a href="https://github.com/http-rs/surf"&gt;surf&lt;/a&gt; and found that it selects the &lt;code&gt;"curl_client"&lt;/code&gt; feature by default, which can be turned off and replaced with &lt;code&gt;"h1-client-rustls"&lt;/code&gt; which uses an HTTP client backed by &lt;a href="https://github.com/rustls/rustls"&gt;rustls&lt;/a&gt; and &lt;a href="https://github.com/async-rs/async-std"&gt;async-std&lt;/a&gt; and no dynamically linked libraries. Enabling &lt;a href="https://msfjarvis.dev/g/androidx-release-watcher/b67a212106d8"&gt;this build feature&lt;/a&gt; removed all &lt;code&gt;-sys&lt;/code&gt; dependencies from &lt;a href="https://msfjarvis.dev/g/androidx-release-watcher"&gt;androidx-release-watcher&lt;/a&gt;, allowing me to build static executables of it.&lt;/p&gt;

</description>
      <category>rust</category>
    </item>
    <item>
      <title>Tips and Tricks for GitHub Actions</title>
      <dc:creator>Harsh Shandilya</dc:creator>
      <pubDate>Mon, 04 Jan 2021 05:36:23 +0000</pubDate>
      <link>https://forem.com/msfjarvis/tips-and-tricks-for-github-actions-26op</link>
      <guid>https://forem.com/msfjarvis/tips-and-tricks-for-github-actions-26op</guid>
      <description>&lt;p&gt;GitHub Actions has grown at a rapid pace, and has become the CI platform of choice for most open source projects. The recent changes to Travis CI's pricing for open source is certainly bound to accelerate this even more.&lt;/p&gt;

&lt;p&gt;Due to it being a first-party addition to GitHub, Actions has nearly infinite potential to run jobs in reaction to changes on GitHub. You can automatically set labels to newly opened pull requests, greet first time contributors, and more.&lt;/p&gt;

&lt;p&gt;Let's go over some things that you can do with Actions, and we'll end it with some safety related tips to ensure that your workflows are secure from both rogue action authors as well as rogue pull requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running workflows based on a cron trigger
&lt;/h2&gt;

&lt;p&gt;GitHub Actions can trigger the execution of a workflow in response to a large list of events as given &lt;a href="https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows"&gt;here&lt;/a&gt;, one of them being a cron schedule. Let's see how we can use the schedule feature to automate repetitive tasks.&lt;/p&gt;

&lt;p&gt;For &lt;a href="https://msfjarvis.dev/aps"&gt;Android Password Store&lt;/a&gt;, we maintain a list of known &lt;a href="https://publicsuffix.org/"&gt;public suffixes&lt;/a&gt; to be able efficiently detect the 'base' domain of the website we're autofilling into. This list changes frequently, and we typically sync our repository with the latest copy on a weekly basis. Actions enables us to do this automatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Update Publix Suffix List data&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;6'&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;update-publicsuffix-data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# The actual workflow doing the update job&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Putting the cron expression into &lt;a href="https://crontab.guru/#0_*_*_*_6"&gt;crontab guru&lt;/a&gt;, you can see that it executes at 12AM on every Saturday. Going through the merged pull requests in APS, you will also notice that the &lt;a href="https://github.com/android-password-store/Android-Password-Store/pulls?q=is%3Apr+is%3Amerged+sort%3Aupdated-desc+label%3APSL"&gt;publicsuffixlist pull requests&lt;/a&gt; indeed happen no sooner than 7 days apart.&lt;/p&gt;

&lt;p&gt;Mine is a very naive example of how you can use cron triggers to automate parts of your workflow. The &lt;a href="https://github.com/rust-lang"&gt;Rust&lt;/a&gt; project uses these same triggers to implement a significantly more important aspect of their daily workings. Rust maintains a repository called &lt;a href="https://github.com/rust-lang/glacier"&gt;glacier&lt;/a&gt; which contains a list of internal compiler errors (ICEs) and code fragments to reproduce each of them. Using a similar cron trigger, this repository checks each new nightly release of Rust to see if any of these compiler crashes were resolved silently by a refactor. When it comes across a ICE that was fixed (compiles correctly or fails with errors rather than crashing the compiler), it files a &lt;a href="https://github.com/rust-lang/glacier/pulls?q=is%3Apr+author%3Aapp%2Fgithub-actions+sort%3Aupdated-desc"&gt;pull request&lt;/a&gt; moving the reproduction file to the &lt;code&gt;fixed&lt;/code&gt; pile.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running jobs based on commit message
&lt;/h2&gt;

&lt;p&gt;Continuous delivery is great, but sometimes you want slightly more control. Rather than run a deployment task on each push to your repository, what if you want it to only run when a specific keyword is in the commit message? Actions has support for this natively, and the deployment pipeline of this very site relies on this feature:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to Cloudflare Workers Sites&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy-main&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;contains(github.event.head_commit.message,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'[deploy]')"&lt;/span&gt;
    &lt;span class="c1"&gt;# Set up wrangler and push to the production environment&lt;/span&gt;

  &lt;span class="na"&gt;deploy-staging&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;contains(github.event.head_commit.message,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'[staging]')"&lt;/span&gt;
    &lt;span class="c1"&gt;# Set up wrangler and push to the staging environment&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This snippet defines a job that is only executed when the top commit of the push contains the text &lt;code&gt;[deploy]&lt;/code&gt; in its message, and another that only runs when the commit message contains &lt;code&gt;[staging]&lt;/code&gt;. Together, these let me control if I want a change to not be immediately deployed, deployed to either the main or staging site, or to both at the same time. So now I can update a draft post without a full re-deployment of the main site, or make a quick edit to a published post that doesn't need to be reflected in the staging environment.&lt;/p&gt;

&lt;p&gt;The core logic of this operation is composed of three parts. The &lt;a href="https://docs.github.com/en/free-pro-team@latest/actions/reference/context-and-expression-syntax-for-github-actions#github-context"&gt;github context&lt;/a&gt;, the &lt;a href="https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#jobsjob_idif"&gt;if conditional&lt;/a&gt; and the &lt;a href="https://docs.github.com/en/free-pro-team@latest/actions/reference/context-and-expression-syntax-for-github-actions#contains"&gt;contains&lt;/a&gt; method. The linked documentation for each does a great job at explaining them, and has further references to allow you to fulfill even more advanced use cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing across multiple configurations in parallel
&lt;/h2&gt;

&lt;p&gt;Jobs in a workflow run in parallel by default, and GitHub comes with an amazing matrix functionality that can automatically generate multiple jobs for you from a single definition. Take this specific example:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Windows&lt;/th&gt;
&lt;th&gt;MacOS&lt;/th&gt;
&lt;th&gt;Ubuntu&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Stable&lt;/td&gt;
&lt;td&gt;Windows + Stable&lt;/td&gt;
&lt;td&gt;MacOS + Stable&lt;/td&gt;
&lt;td&gt;Ubuntu + Stable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Beta&lt;/td&gt;
&lt;td&gt;Windows + Beta&lt;/td&gt;
&lt;td&gt;MacOS + Beta&lt;/td&gt;
&lt;td&gt;Ubuntu + Beta&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nightly&lt;/td&gt;
&lt;td&gt;Windows + Nightly&lt;/td&gt;
&lt;td&gt;MacOS + Nightly&lt;/td&gt;
&lt;td&gt;Ubuntu + Nightly&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;This particular matrix is for Rust, to test a codebase across Windows, Ubuntu, and macOS using the Rust stable, beta, and nightly toolchains.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In GitHub Actions, we can simply provide the platforms (Windows, MacOS and, Ubuntu) and the Rust channels (Stable, Beta, and Nightly) inside a single job and let it figure out how to make the permutations and create separate jobs for them. To configure such a matrix, we write something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;check-rust-code&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# Defines a matrix strategy&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Sets the OSes we want to run jobs on&lt;/span&gt;
        &lt;span class="na"&gt;os&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;windows-latest&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;macOS-latest&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="c1"&gt;# Sets the Rust channels we want to test against&lt;/span&gt;
        &lt;span class="na"&gt;rust&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;stable&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;beta&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;nightly&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="c1"&gt;# Make the job run on the OS picked by the matrix&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.os }}&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions-rs/toolchain@v1&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;profile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;minimal&lt;/span&gt;
        &lt;span class="na"&gt;components&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rustfmt, clippy&lt;/span&gt;
        &lt;span class="c1"&gt;# Installs the Rust toolchain for the channel picked by the matrix&lt;/span&gt;
        &lt;span class="na"&gt;toolchain&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.rust }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will automatically generate 9 (3 platforms * 3 Rust channels) parallel jobs to test this entire configuration, without requiring us to manually define each of them. &lt;a href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself"&gt;DRY&lt;/a&gt; at its finest :)&lt;/p&gt;

&lt;h2&gt;
  
  
  Make a job run after another
&lt;/h2&gt;

&lt;p&gt;By default, jobs defined in a workflow file run in parallel. However, we might need a more sequential order of execution for some cases, and GHA does include support for this case. Let's try another real world example!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/square/leakcanary"&gt;LeakCanary&lt;/a&gt; has a &lt;a href="https://github.com/square/leakcanary/blob/f5343aca6e019994f7e69a28fac14ca18e071b88/.github/workflows/main.yml"&gt;checks job&lt;/a&gt; that runs on each push to the main branch and on each pull request. They wanted to add support for snapshot deployment, in order to finally retire Travis CI. To make this happen, I simply added a &lt;a href="https://github.com/square/leakcanary/pull/2044/commits/a6f6c204559396120836b27c0b2a46d3e444c728"&gt;new job&lt;/a&gt; to the same workflow, having it run only on push events and have a dependency on the checks job. This ensures that there won't be a snapshot deployment until all tests are passing on the main branch. The relevant parts of the workflow configuration are here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;checks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Runs automated unit and instrumentation tests&lt;/span&gt;

  &lt;span class="na"&gt;snapshot-deployment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Only run if the push event triggered this workflow run&lt;/span&gt;
  &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;github.event_name&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;==&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'push'"&lt;/span&gt;
  &lt;span class="c1"&gt;# Run after the 'checks' job has passed&lt;/span&gt;
  &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;checks&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Mitigating security concerns with Actions
&lt;/h1&gt;

&lt;p&gt;GitHub Actions benefits from a vibrant ecosystem of user-authored actions, which opens it up to equal opportunities for abuse. It is relatively easy to work around the common ones, and I'm going to outline them here. I'm no authority on security, and these recommendations are based on a combination of my reading and understanding. These &lt;em&gt;should&lt;/em&gt; be helpful, but this list is not exhaustive, and you should exercise all the caution you can.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use exact commit hashes rather than tags
&lt;/h2&gt;

&lt;p&gt;Tags are moving qualifiers, and can be &lt;a href="https://julienrenaux.fr/2019/12/20/github-actions-security-risk/"&gt;force pushed at any moment&lt;/a&gt;. If the repository for an Action you use in your workflows is compromised, the tag you use could be force pushed with a malicious version that can send your repository secrets to a third-party server. Auditing the source of a repository at a given tag, then using the SHA1 commit hash it currently points to as the version addresses that concern due to it being nearly impossible to fake a new commit with the exact hash.&lt;/p&gt;

&lt;p&gt;To get the commit hash for a specific tag, head to the Releases page of the repository, then click the short SHA1 hash below the tag name and copy the full hash from the URL.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LT4Lq97F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://msfjarvis.dev/uploads/actions_tips_tricks_commit_hash.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LT4Lq97F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://msfjarvis.dev/uploads/actions_tips_tricks_commit_hash.webp" alt="A tag along with its commit hash" width="880" height="311"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Here, the commit hash is feb985e. Ideally, you want to click that link and copy the full hash from the URL&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;job:
&lt;/span&gt;  checks:
&lt;span class="gd"&gt;-   - uses: burrunan/gradle-cache-actions@v1.6
&lt;/span&gt; +  - uses: burrunan/gradle-cache-actions@feb985ecf49f57f54f31920821a50d0394faf122
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Alternate solution
&lt;/h3&gt;

&lt;p&gt;A more extreme fix for this problem is to &lt;a href="https://stackoverflow.com/questions/26217488/what-is-vendoring"&gt;vendor&lt;/a&gt; each third-party action you use into your own repository, and then use the local copy as the source. This puts you in charge of manually syncing the source to each version, but allows you to restrict the allowed Actions to ones in your repository thereby greatly increasing security. However, having to manually sync can get tedious if your workflows involve a lot of third-party actions. However, the same manual sync also gives you slightly better visibility into the changes between versions since they'd be available in a single PR diff.&lt;/p&gt;

&lt;p&gt;To use an Action from a local directory, replace the &lt;code&gt;uses:&lt;/code&gt; line with the relative path to the local copy in the repository.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;job:
&lt;/span&gt;  checks:
    - name: Checkout repository
    # Assuming the copy of actions/checkout is at .github/actions/checkout
&lt;span class="gd"&gt;-   - uses: actions/checkout@v2
&lt;/span&gt;&lt;span class="gi"&gt;+   - uses: ./.github/actions/checkout
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Replace &lt;code&gt;pull_request_target&lt;/code&gt; with &lt;code&gt;pull_request&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows#pull_request_target"&gt;&lt;code&gt;pull_request_target&lt;/code&gt;&lt;/a&gt; grants a PR access to a github token that can write to your repository, exposing your code to modification by a malicious third-party who simply needs to open a PR against your repository. Most people will already be using the safe &lt;a href="https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows#pull_request"&gt;&lt;code&gt;pull_request&lt;/code&gt;&lt;/a&gt; event, but if you are not, audit your requirements for &lt;code&gt;pull_request_target&lt;/code&gt; and make the switch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;-on: [push, pull_request_target]
&lt;/span&gt;&lt;span class="gi"&gt;+on: [push, pull_request]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;p&gt;I'm still learning about Actions, and there is a lot that I did not cover here. I highly encourage readers to refer the GitHub docs for &lt;a href="https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions"&gt;Workflow syntax&lt;/a&gt; and &lt;a href="https://docs.github.com/en/free-pro-team@latest/actions/reference/context-and-expression-syntax-for-github-actions"&gt;Context and expressions syntax&lt;/a&gt; to gain more knowledge of the workflow configuration capabilities. Let me know if you find something cool that I did not cover here!&lt;/p&gt;

</description>
      <category>github</category>
    </item>
    <item>
      <title>Improvements to inline classes in Kotlin 1.4.30</title>
      <dc:creator>Harsh Shandilya</dc:creator>
      <pubDate>Tue, 22 Dec 2020 00:00:00 +0000</pubDate>
      <link>https://forem.com/msfjarvis/improvements-to-inline-classes-in-kotlin-1-4-30-fn6</link>
      <guid>https://forem.com/msfjarvis/improvements-to-inline-classes-in-kotlin-1-4-30-fn6</guid>
      <description>&lt;h2&gt;
  
  
  What are inline classes?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://kotlinlang.org/docs/reference/inline-classes.html#inline-classes"&gt;Inline classes&lt;/a&gt; are a Kotlin language feature introduced in Kotlin 1.3 that allow creating no-cost 'wrapper' classes. This is best understood with an example.&lt;/p&gt;

&lt;p&gt;Imagine that your project requires an EmailAddress type.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EmailAddress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is &lt;em&gt;fine&lt;/em&gt;, but there are hidden performance costs. Classes are initialized on the heap, and fields are much cheaper, fields of primitive types even more so. We only really need to be able to call some &lt;code&gt;String&lt;/code&gt;s an &lt;code&gt;EmailAddress&lt;/code&gt;, so we can make this class &lt;code&gt;inline&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;inline&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EmailAddress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will result in the EmailAddress wrapper being erased at runtime into direct access of the underlying &lt;code&gt;value&lt;/code&gt; field. The rules around this are not as simple as "&lt;code&gt;inline&lt;/code&gt; means it will always be inlined", and I recommend reading the &lt;a href="https://kotlinlang.org/docs/reference/inline-classes.html#representation"&gt;representation of inline classes&lt;/a&gt; documentation to get a better idea of what particular situations the compiler can optimize.&lt;/p&gt;

&lt;h2&gt;
  
  
  Upcoming improvements for inline classes
&lt;/h2&gt;

&lt;p&gt;With that small refresher on inline classes, let's talk about the restrictions that are being lifted in 1.4.30 and how they are helpful for us as developers.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;init {}&lt;/code&gt; blocks are now allowed
&lt;/h3&gt;

&lt;p&gt;This is pretty straightforward: you can now have initialization logic in inline classes. Let's re-use our email example but now try to perform some validation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;inline&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EmailAddress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 🎉 New in Kotlin 1.4.30&lt;/span&gt;
  &lt;span class="nf"&gt;init&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isNotEmpty&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"empty email address does not make sense"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Kotlin 1.4.20, this will fail to compile with &lt;code&gt;Inline class cannot have an initializer block&lt;/code&gt;. This change is valuable because ultimately most of us will be wrapping primitive types into these classes and will frequently have restrictions on what is valid, so being able to validate is extremely helpful to catch invalid values early.&lt;/p&gt;

&lt;h3&gt;
  
  
  Primary constructor can be made non-public
&lt;/h3&gt;

&lt;p&gt;The primary constructor of an inline class can now be made &lt;code&gt;internal&lt;/code&gt; or &lt;code&gt;private&lt;/code&gt;, which was previously impossible.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Sample courtesy https://youtrack.jetbrains.com/issue/KT-28056#focus=Comments-27-4357288.0-0&lt;/span&gt;
&lt;span class="k"&gt;inline&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EmailAddress&lt;/span&gt; &lt;span class="nd"&gt;@PublishedApi&lt;/span&gt;
&lt;span class="cm"&gt;/* 🎉 New in Kotlin 1.4.30 */&lt;/span&gt;
&lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="k"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
  &lt;span class="k"&gt;companion&lt;/span&gt; &lt;span class="k"&gt;object&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;EmailAddress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;'@'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"invalid email address."&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;EmailAddress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Weeeeeeeeeeeeeeeeeeeeeeeeeee&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Yeah. What's happening here is that we've made the constructor of &lt;code&gt;EmailAddress&lt;/code&gt; &lt;code&gt;internal&lt;/code&gt; to restrict its use to the current module, but annotated it with &lt;a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-published-api/"&gt;&lt;code&gt;@PublishedApi&lt;/code&gt;&lt;/a&gt; which makes it legal to use in public inline functions. Read the docs for the &lt;a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-published-api/"&gt;&lt;code&gt;@PublishedApi&lt;/code&gt;&lt;/a&gt; annotation to get a better idea about what this means for you, since it's out of scope here.&lt;/p&gt;

&lt;p&gt;Doing this allows us to have an &lt;code&gt;EmailAddress&lt;/code&gt; type that can be used to verify that a &lt;code&gt;String&lt;/code&gt; is a valid email and then have that information be carried downstream as the value's type. Why is having the type important? Allow me to refer you to "&lt;a href="https://fasterthanli.me/articles/aiming-for-correctness-with-types"&gt;Aiming for correctness with types&lt;/a&gt;", provided you have a free hour or so. It's worth it.&lt;/p&gt;

</description>
      <category>kotlin</category>
    </item>
    <item>
      <title>Why upgrade Android?</title>
      <dc:creator>Harsh Shandilya</dc:creator>
      <pubDate>Thu, 23 Jul 2020 00:00:00 +0000</pubDate>
      <link>https://forem.com/msfjarvis/why-upgrade-android-557f</link>
      <guid>https://forem.com/msfjarvis/why-upgrade-android-557f</guid>
      <description>&lt;p&gt;A couple days ago I tweeted this out, partly in response to a security conscious user who was quick to point out why a particular feature had to be added to APS, but failed to realise the fact that the problem wouldn't even exist if they were running the latest version of Android (we'll talk about the behavior change that fixed it later here).&lt;/p&gt;


&lt;blockquote class="ltag__twitter-tweet"&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--dIOsQ9fd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/1267324027627991040/4fjGteUf_normal.jpg" alt="Harsh Shandilya profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        Harsh Shandilya
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        @msf_jarvis
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ir1kO05j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-f95605061196010f91e64806688390eb1a4dbc9e913682e043eb8b1e06ca484f.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      Something that I shouldn't have to be saying: Being concerned about your security and then using a 4 year old version of Android can't really go hand in hand.
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      19:41 PM - 21 Jul 2020
    &lt;/div&gt;


    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=1285661151104323584" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fFnoeFxk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-reply-action-238fe0a37991706a6880ed13941c3efd6b371e4aefe288fe8e0db85250708bc4.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=1285661151104323584" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k6dcrOn8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-retweet-action-632c83532a4e7de573c5c08dbb090ee18b348b13e2793175fea914827bc42046.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/like?tweet_id=1285661151104323584" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SRQc9lOp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-like-action-1ea89f4b87c7d37465b0eb78d51fcb7fe6c03a089805d7ea014ba71365be5171.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;


&lt;p&gt;I completely stand by what I said, and for good reason. Android upgrades bring massive changes to the platform, improving security against both known and unknown threats. You sign off that benefit when you buy into an incompetent OEM's cheap phones, and it has become a bit too 'normal' than anybody would prefer.&lt;/p&gt;

&lt;p&gt;That's not what we're going to talk about, though. This post is going to be purely about privacy, and how it has changed, nay, improved, over the years of Android. My apps support a minimum of Android 6, so I will begin with the next version, Android 7, and go through Google's release notes, singling out privacy related changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Android 7
&lt;/h2&gt;

&lt;p&gt;Android 7 had a very passing focus on privacy and thus did not have a lot of obvious or concrete changes around it. Background execution limits introduced in Android 6 were improved in Android 7 to apply even more restrictions after devices became stationary, which can be loosely interpreted as 'bad' for data exfiltration SDKs that apps ship but in reality didn't do much.&lt;/p&gt;

&lt;h2&gt;
  
  
  Android 8
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Locking down background location access
&lt;/h3&gt;

&lt;p&gt;In Android 8, access to background location was &lt;a href="https://developer.android.com/about/versions/oreo/android-8.0-changes#abll"&gt;severely throttled&lt;/a&gt;. Apps received less frequent updates for location and thus couldn't track you in real time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Introduction of Autofill
&lt;/h3&gt;

&lt;p&gt;The Android Autofill framework was debuted, along with support for &lt;a href="https://developer.android.com/about/versions/oreo/android-8.0-changes#wfa"&gt;Web form Autofill&lt;/a&gt;. This paved the way for password managers to fill fields for you without relying on hacked up accessibility services or the Android clipboard. This was a major win!&lt;/p&gt;

&lt;h3&gt;
  
  
  Better HTTPS defaults
&lt;/h3&gt;

&lt;p&gt;Android 8.0's implementation of HttpsURLConnection did not perform &lt;a href="https://developer.android.com/about/versions/oreo/android-8.0-changes#networking-all"&gt;insecure TLS/SSL protocol version fallback&lt;/a&gt;, which means connections that failed to negotiate a requested TLS version would now abort rather than fall back to an older version of TLS.&lt;/p&gt;

&lt;h3&gt;
  
  
  ANDROID_ID changes
&lt;/h3&gt;

&lt;p&gt;Access to the &lt;code&gt;ANDROID_ID&lt;/code&gt; field &lt;a href="https://developer.android.com/about/versions/oreo/android-8.0-changes#privacy-all"&gt;was changed significantly&lt;/a&gt;. It is generated per-app and per-signature as opposed to the entire system making it harder to fingerprint users who have multiple apps installed with the same advertising-related SDKs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Android 9
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Limited access to sensors
&lt;/h3&gt;

&lt;p&gt;Beginning Android 9, &lt;a href="https://developer.android.com/about/versions/pie/android-9.0-changes-all#bg-sensor-access"&gt;background access to device sensors&lt;/a&gt; was greatly reduced. Access to microphone and camera was completely denied, and so was the gyroscope, accelerometer and other sensors of that class.&lt;/p&gt;

&lt;h3&gt;
  
  
  Granular call log access
&lt;/h3&gt;

&lt;p&gt;For apps that need to access the user's call logs for any reason, a &lt;a href="https://developer.android.com/about/versions/pie/android-9.0-changes-all#restrict-access-call-logs"&gt;new permission group was introduced&lt;/a&gt;. Now, you don't require granting access to all phone-related permissions to let an app back up your call logs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Restricted access to phone numbers
&lt;/h3&gt;

&lt;p&gt;There are multiple ways to monitor phone calls on Android, and with the introduction of the &lt;code&gt;CALL_LOG&lt;/code&gt; permission group, &lt;a href="https://developer.android.com/about/versions/pie/android-9.0-changes-all#restrict-access-phone-numbers"&gt;these were locked down&lt;/a&gt; to only expose phone numbers to apps that were allowed explicit access to call logs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Making Wi-Fi and cellular networks less privacy invasive
&lt;/h3&gt;

&lt;p&gt;A combination of changes to &lt;a href="https://developer.android.com/about/versions/pie/android-9.0-changes-all#restricted_access_to_wi-fi_location_and_connection_information"&gt;what permissions apps require&lt;/a&gt; to know about your WiFi and &lt;a href="https://developer.android.com/about/versions/pie/android-9.0-changes-all#information_removed_from_wi-fi_service_methods"&gt;how much personally identifiable data is provided by these APIs&lt;/a&gt; further improves your privacy against rogue apps. Disabling device location &lt;a href="https://developer.android.com/about/versions/pie/android-9.0-changes-all#telephony_information_now_relies_on_device_location_setting"&gt;now disables the ability to get information on cell towers&lt;/a&gt; your phone is connected to.&lt;/p&gt;

&lt;h3&gt;
  
  
  No more serials
&lt;/h3&gt;

&lt;p&gt;Requesting access to the device serial number &lt;a href="https://developer.android.com/about/versions/pie/android-9.0-changes-28#build-serial-deprecation"&gt;now requires phone state permissions&lt;/a&gt; making it more explicit when apps are trying to fingerprint you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Android 10
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Scoped Storage
&lt;/h3&gt;

&lt;p&gt;Probably the most controversial change in 10, Scoped Storage segregated the device storage into scopes and &lt;a href="https://developer.android.com/about/versions/10/privacy/changes#scoped-storage"&gt;gave apps access to them without needing extra permissions&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Explicit background permission access
&lt;/h3&gt;

&lt;p&gt;Android 10 introduces the &lt;code&gt;ACCESS_BACKGROUND_LOCATION&lt;/code&gt; permission and &lt;a href="https://developer.android.com/about/versions/10/privacy/changes#app-access-device-location"&gt;completely disables background access&lt;/a&gt; for apps targeting SDK 29 that don't declare it. For older apps, the framework treats granting location access as effectively background location access. When the app upgrades to target SDK 29, the background permission is revoked and must be explicitly requested again.&lt;/p&gt;

&lt;h3&gt;
  
  
  Removal of contacts affinity
&lt;/h3&gt;

&lt;p&gt;Beginning Android 10, the system no longer &lt;a href="https://developer.android.com/about/versions/10/privacy/changes#contacts-affinity"&gt;keeps track of what contacts you interact with most&lt;/a&gt; and thus search results are not weighted anymore.&lt;/p&gt;

&lt;h3&gt;
  
  
  MAC randomization enabled by default
&lt;/h3&gt;

&lt;p&gt;Connecting to a Wi-FI network now uses a &lt;a href="https://developer.android.com/about/versions/10/privacy/changes#randomized-mac-addresses"&gt;randomized MAC address&lt;/a&gt; to prevent fingerprinting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Removal of access to non-resettable identifiers
&lt;/h3&gt;

&lt;p&gt;Access to identifiers such as IMEI and serial was &lt;a href="https://developer.android.com/about/versions/10/privacy/changes#non-resettable-device-ids"&gt;restricted to priviledged apps&lt;/a&gt; which means apps served by the Play Store can no longer see them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Restriction on clipboard access
&lt;/h3&gt;

&lt;p&gt;This is the problem we first talked about. Apps before Android 10 could monitor clipboard events and potentially exfil confidential data like passwords. &lt;a href="https://developer.android.com/about/versions/10/privacy/changes#clipboard-data"&gt;In Android 10 this was completely disabled&lt;/a&gt; for apps that were not in foreground or not your active input method. This change was made with &lt;strong&gt;no&lt;/strong&gt; compatibility changes, which means even older apps would not be able to access clipboard data out of turn.&lt;/p&gt;

&lt;h3&gt;
  
  
  More WiFi and location improvements
&lt;/h3&gt;

&lt;p&gt;Apps can no longer &lt;a href="https://developer.android.com/about/versions/10/privacy/changes#enable-disable-wifi"&gt;toggle WiFi&lt;/a&gt; or &lt;a href="https://developer.android.com/about/versions/10/privacy/changes#configure-wifi"&gt;read a list of configured networks&lt;/a&gt;, and getting access to methods that expose device location requires the &lt;code&gt;ACCESS_FINE_LOCATION&lt;/code&gt; permission to make it obvious that an app is doing it. The last change also affects telephony related APIs, a full list is available &lt;a href="https://developer.android.com/about/versions/10/privacy/changes#telephony-apis"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Permissions controls
&lt;/h3&gt;

&lt;p&gt;Apps no longer have &lt;a href="https://developer.android.com/about/versions/10/privacy/changes#screen-contents"&gt;silent access to screen contents&lt;/a&gt;, and the platform now prompts users &lt;a href="https://developer.android.com/about/versions/10/privacy/changes#user-permission-legacy-apps"&gt;to disallow permissions for legacy apps&lt;/a&gt; that target Android 5.1 or below that would earlier be granted at install time. &lt;a href="https://developer.android.com/about/versions/10/privacy/changes#physical-activity-recognition"&gt;Physical activity recognition&lt;/a&gt; is now given its own permission and common libraries for the purpose like Google's Play Services APIs will now send empty data when an app requests activity without the permissions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Android 11 (tentative)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Storage changes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Apps targeting Android 11 are &lt;a href="https://developer.android.com/preview/privacy/storage#scoped-storage"&gt;no longer allowed to opt out of scoped storage&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;All encompassing access to a large set of directories and files is &lt;a href="https://developer.android.com/preview/privacy/storage#file-directory-restrictions"&gt;completely disabled&lt;/a&gt;, including the root of the internal storage, the &lt;code&gt;Download&lt;/code&gt; folder, and the data and obb subdirectories of the &lt;code&gt;Android&lt;/code&gt; folder.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Permission changes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Location, microphone and camera related permissions can now &lt;a href="https://developer.android.com/preview/privacy/permissions#one-time"&gt;be granted on a one-off basis&lt;/a&gt;, meaning they'll automatically get revoked when the app process exits.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Apps that are not used for a few months will &lt;a href="https://developer.android.com/preview/privacy/permissions#auto-reset"&gt;have their permissions automatically revoked&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A new &lt;code&gt;READ_PHONE_NUMBERS&lt;/code&gt; permission &lt;a href="https://developer.android.com/preview/privacy/permissions#phone-numbers"&gt;has been added&lt;/a&gt; to call certain APIs that expose phone numbers.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Location changes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://developer.android.com/preview/privacy/location#one-time-access"&gt;One time access&lt;/a&gt; is now an option for location, allowing users to not grant persistent access when they don't wish to.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Background location needs to &lt;a href="https://developer.android.com/preview/privacy/location#background-location"&gt;be requested separately now&lt;/a&gt; and asking for it together with foreground location will throw an exception.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Data access auditing
&lt;/h3&gt;

&lt;p&gt;To allow apps to audit their own usage of user data, &lt;a href="https://developer.android.com/preview/privacy/data-access-auditing#log-access"&gt;a new callback is provided&lt;/a&gt;. Apps can implement it and then log all accesses to see if there's any unexpected data use that needs to be resolved.&lt;/p&gt;

&lt;h3&gt;
  
  
  Redacted MAC addresses
&lt;/h3&gt;

&lt;p&gt;Unpriviledged apps targetting SDK 30 will no longer be able to get the device's real MAC address.&lt;/p&gt;

&lt;h1&gt;
  
  
  Closing notes
&lt;/h1&gt;

&lt;p&gt;As you can tell, improving user privacy is a constant journey and Android is doing a better job of it with every new release. This makes it crucial that you stay up-to-date, either by buying phones from an OEM that delivers timely updates for a sufficiently long support period, or by using a trusted custom ROM like &lt;a href="https://grapheneos.org/"&gt;GrapheneOS&lt;/a&gt; or &lt;a href="https://lineageos.org/"&gt;LineageOS&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>android</category>
      <category>updates</category>
      <category>security</category>
      <category>privacy</category>
    </item>
    <item>
      <title>Oh JavaScript...</title>
      <dc:creator>Harsh Shandilya</dc:creator>
      <pubDate>Sat, 11 Jul 2020 07:19:26 +0000</pubDate>
      <link>https://forem.com/msfjarvis/oh-javascript-3flo</link>
      <guid>https://forem.com/msfjarvis/oh-javascript-3flo</guid>
      <description>&lt;blockquote class="ltag__twitter-tweet"&gt;
      &lt;div class="ltag__twitter-tweet__media"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---t5lU87s--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/media/EcoE_IDU4AE-HrE.png" alt="unknown tweet media content"&gt;
      &lt;/div&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--dIOsQ9fd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/1267324027627991040/4fjGteUf_normal.jpg" alt="Harsh Shandilya profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        Harsh Shandilya
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        @msf_jarvis
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ir1kO05j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-f95605061196010f91e64806688390eb1a4dbc9e913682e043eb8b1e06ca484f.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      Them: "You don't need a strongly-typed language"&lt;br&gt;Also them: 
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      06:47 AM - 11 Jul 2020
    &lt;/div&gt;


    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=1281842539239108615" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fFnoeFxk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-reply-action-238fe0a37991706a6880ed13941c3efd6b371e4aefe288fe8e0db85250708bc4.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=1281842539239108615" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k6dcrOn8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-retweet-action-632c83532a4e7de573c5c08dbb090ee18b348b13e2793175fea914827bc42046.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/like?tweet_id=1281842539239108615" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SRQc9lOp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-like-action-1ea89f4b87c7d37465b0eb78d51fcb7fe6c03a089805d7ea014ba71365be5171.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;


</description>
      <category>jokes</category>
    </item>
    <item>
      <title>Switching my email to Purelymail</title>
      <dc:creator>Harsh Shandilya</dc:creator>
      <pubDate>Mon, 13 Apr 2020 11:42:27 +0000</pubDate>
      <link>https://forem.com/msfjarvis/switching-my-email-to-purelymail-4l6i</link>
      <guid>https://forem.com/msfjarvis/switching-my-email-to-purelymail-4l6i</guid>
      <description>&lt;p&gt;Email is a very crucial part of my workflow, and I enjoy using it (and also why I'm beyond excited for what Basecamp has in store with &lt;a href="https://hey.com"&gt;hey.com&lt;/a&gt;). I have switched emails a couple times over the many years I have had an internet presence, finally settling on &lt;a href="mailto:me@msfjarvis.dev"&gt;me@msfjarvis.dev&lt;/a&gt; when I bought my domain. There began the problem.&lt;/p&gt;

&lt;p&gt;I attempt to self-host things when reasonable, to retain some control and not have a single point of failure outside my control that would lock me out. With email, that part is a constant, uphill battle against spam filters to ensure your domain doesn't land in a big filter list that will then start trashing all your email and make life hard. Due to this, I never self-hosted email, instead choosing to forward it through Google Domains (the registrar for this domain) to my existing Google account. While this is a very reliable approach, it still involves depending heavily on my Google account. This has proven to be a problem in many ways, including &lt;a href="https://twitter.com/MSF_Jarvis/status/1217534500550234112"&gt;being locked out after opting into Advanced Protection&lt;/a&gt; and people's accounts being banned for a number of reasons completely unrelated to email. If something like this were to happen to me, I would lose both my Google as well as my domain email instantly. A very scary position to be in for anybody.&lt;/p&gt;

&lt;p&gt;A couple days ago, &lt;a href="https://twitter.com/JakeWharton"&gt;Jake Wharton&lt;/a&gt; retweeted a blog post from &lt;a href="https://twitter.com/rolisz"&gt;Roland Szabo&lt;/a&gt; titled &lt;a href="https://rolisz.ro/2020/04/11/moving-away-from-gmail/"&gt;'Moving away from GMail'&lt;/a&gt;. I read through it, looked at PurelyMail, and was convinced that it was really the solution for my little problem. I am a big believer in paying in dollaroos rather than data so I really loved the transparency behind pricing, data use, infrastructure and just about everything else. Signed up!&lt;/p&gt;

&lt;h2&gt;
  
  
  Migration
&lt;/h2&gt;

&lt;p&gt;Like any other email provider, all you need to configure for PurelyMail to work is DNS. I use Cloudflare for my sites, so there was nothing to do on the Google Domains side of things. I left the forwarding setup as-is to allow any lagging DNS resolvers to still be able to get email to me, even if its to my Google account. I hope to get rid of that setting in the near future since I believe the change will have propagated by then. I maintain my DNS settings under a git repository, using StackExchange's excellent &lt;a href="http://stackexchange.github.io/dnscontrol/"&gt;dnscontrol&lt;/a&gt; tool. DNSControl operates on a JS-like syntax that is parsed, evaluated and then used to publish to the DNS provider of choice. Neat stuff! The changes required looked something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git dnsconfig.js dnsconfig.js
index 29b8d1a927ab..01ea2af1d448 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- dnsconfig.js
&lt;/span&gt;&lt;span class="gi"&gt;+++ dnsconfig.js
&lt;/span&gt;&lt;span class="p"&gt;@@ -6,7 +6,7 @@&lt;/span&gt;

 var REG_NONE = NewRegistrar('none', 'NONE');
 var DNS_CF = NewDnsProvider('cloudflare', 'CLOUDFLAREAPI');
&lt;span class="gi"&gt;+var CF_PROXY_OFF = {'cloudflare_proxy': 'off'};     // Proxy disabled.
&lt;/span&gt;
@@ -18,25 +18,17 @@ var RECORDS = [
&lt;span class="gi"&gt;+    CNAME('_dmarc', '_dmarcroot.purelymail.com.', CF_PROXY_OFF),
+    CNAME('purelymail1._domainkey', 'key1._dkimroot.purelymail.com.', CF_PROXY_OFF),
+    CNAME('purelymail2._domainkey', 'key2._dkimroot.purelymail.com.', CF_PROXY_OFF),
+    CNAME('purelymail3._domainkey', 'key3._dkimroot.purelymail.com.', CF_PROXY_OFF),
&lt;/span&gt;&lt;span class="gd"&gt;-    MX('@', 10, 'alt1.gmr-smtp-in.l.google.com.', TTL('1d')),
-    MX('@', 20, 'alt2.gmr-smtp-in.l.google.com.', TTL('1d')),
-    MX('@', 30, 'alt3.gmr-smtp-in.l.google.com.', TTL('1d')),
-    MX('@', 40, 'alt4.gmr-smtp-in.l.google.com.', TTL('1d')),
-    MX('@', 5, 'gmr-smtp-in.l.google.com.', TTL('1d')),
-    TXT('@', 'v=spf1 include:_spf.google.com ~all', TTL('3600s')),
&lt;/span&gt;&lt;span class="gi"&gt;+    MX('@', 50, 'mailserver.purelymail.com.'),
+    TXT('@', 'v=spf1 include:_spf.purelymail.com ~all'),
+    TXT('@', 'purelymail_ownership_proof=**redacated**'),
&lt;/span&gt; ];
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The only 'unexpected' change I had to make was to disable Cloudflare's proxy feature for the CNAME records. Once that was done, PurelyMail was instantly able to verify all DNS records and I was in business.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pros and Cons of the switch
&lt;/h2&gt;

&lt;p&gt;I've been on PurelyMail for about a day now and poked around enough to have a comprehensive idea of what's different from my usual GMail flow, so let's get into that.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pros
&lt;/h3&gt;

&lt;h4&gt;
  
  
  You pay for it
&lt;/h4&gt;

&lt;p&gt;By now everybody must have realized a simple fact: If you're not paying, you're the product. I do not wish to be a product. Your hard-earned money is more likely to keep companies from being shady than your emails. PurelyMail is a one man operation, which makes it more trustworthy to me than Google's massive scale. Google does not care for a single user, PurelyMail will.&lt;/p&gt;

&lt;h4&gt;
  
  
  Transparency
&lt;/h4&gt;

&lt;p&gt;PurelyMail tells you upfront about what they charge and how they arrive at that number. There is no contract period, and if you wish to have fine grained control over what you pay, you can use their advanced pricing section to calculate your costs based on your exact needs. The website is straightforward and to the point, there is no glossy advertising to obscure flaws, and their security practices are all &lt;a href="https://purelymail.com/docs/security"&gt;documented&lt;/a&gt; on their site, front and center.&lt;/p&gt;

&lt;h4&gt;
  
  
  Failsafe
&lt;/h4&gt;

&lt;p&gt;My GMail is tied to my Google account, which means anything that flags my Google account will bring down my ability to have email. This is a scary position to be in. Having my email separate from my Google account frees me from that looming danger.&lt;/p&gt;

&lt;h4&gt;
  
  
  Easy export
&lt;/h4&gt;

&lt;p&gt;PurelyMail has a tool called &lt;a href="https://purelymail.com/docs/mailPort"&gt;&lt;code&gt;mailPort&lt;/code&gt;&lt;/a&gt; that lets you move email between PurelyMail and other providers. You can bring your entire mailbox to PurelyMail when switching to it, or back to wherever you go next should it not feel sufficient for your needs. No questions asked, and no bullshit. It just works.&lt;/p&gt;

&lt;h4&gt;
  
  
  No client lock-in
&lt;/h4&gt;

&lt;p&gt;Because PurelyMail has no bells and whistles, you won't be penalized on the feature side of things if you use one client compared to another. Things stay consistent.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cons
&lt;/h3&gt;

&lt;h4&gt;
  
  
  You pay for it
&lt;/h4&gt;

&lt;p&gt;I am in a fortunate position where I can pay for things solely based on principle, without having to worry &lt;em&gt;too&lt;/em&gt; much. Not everybody is similarly blessed, or you may simply have technical issues with being able to pay online for internet things. Stripe and PayPal are not available globally, and fees are often insane. I completely understand.&lt;/p&gt;

&lt;h4&gt;
  
  
  Roundcube is great, but it ain't no GMail
&lt;/h4&gt;

&lt;p&gt;PurelyMail uses the Roundcube frontend for its webmail offering, with a couple extra themes. It's not the prettiest, and does not have a lot of bells and whistles that you might get accustomed to from GMail. The change is a bit rough honestly, but the pros certainly outweigh the cons. On the bright side, its easier to influence product direction at PurelyMail, so get on the issue tracker and request or vote for features!&lt;/p&gt;

&lt;h4&gt;
  
  
  No dedicated client
&lt;/h4&gt;

&lt;p&gt;Not having a specialized client unfortunately also means that you'll have to shop around for what works. I still use the GMail mobile app, but K-9 Mail is also pretty decent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I have begun moving my various accounts to my domain mail as and when they remind me of their existence (55 left still, if my &lt;a href="https://passwordstore.org/"&gt;pass&lt;/a&gt; repository is to be believed), and hope to eventually be able to get by without the pinned GMail tab in my browser :)&lt;/p&gt;

&lt;p&gt;PurelyMail has proven to be an excellent platform so far. Support has been swift and helpful, and I haven't had any bad surprises. I hope to be a content user for as long as I possibly can :)&lt;/p&gt;

</description>
      <category>email</category>
      <category>purelymail</category>
    </item>
  </channel>
</rss>
