<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet href="/feed.xsl" type="text/xsl"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Jordan Webb's blog</title>
        <link>https://jordemort.dev</link>
        <description>Development notes and technical musings</description>
        <lastBuildDate>Sat, 11 May 2024 01:40:18 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <image>
            <title>Jordan Webb's blog</title>
            <url>https://avatars.githubusercontent.com/jordemort</url>
            <link>https://jordemort.dev</link>
        </image>
        <copyright>2022 Jordan Webb</copyright>
        <atom:link href="https://pubsubhubbub.appspot.com/" rel="hub"/>
        <item>
            <title><![CDATA[My recipe for BBQ chicken wings]]></title>
            <link>https://jordemort.dev/blog/bbq-chicken-wings-recipe/</link>
            <guid>https://jordemort.dev/blog/bbq-chicken-wings-recipe/</guid>
            <pubDate>Sat, 11 May 2024 01:38:56 GMT</pubDate>
            <description><![CDATA[As long as I'm writing this down, I might as well publish it]]></description>
            <content:encoded><![CDATA[<p>Hey, did you know I make really good BBQ chicken wings?
I use a recipe I invented myself, and every time I make it I’m terrified I’ll forget how to do it; I’ve never written it down.
Today, that changes!</p>
<p>I’m writing this mostly because I want to print it out and add it to my cookbook, but since I don’t have anything else to post about right now, you get to come along for the ride.</p>
<p>The rub is flavorful but not too spicy.
My kid loves it.</p>
<h2 id="equipment">Equipment</h2>
<ul>
<li>Gas grill with top rack</li>
<li>Grill tongs</li>
<li>Meat thermometer</li>
<li>Small bowl for mixing rub</li>
<li>Large bowl for seasoning chicken</li>
<li>Small bowl for BBQ sauce</li>
<li>Silicone brush for BBQ sauce</li>
</ul>
<h2 id="ingredients">Ingredients</h2>
<h3 id="meat">Meat</h3>
<ul>
<li>2 pounds chicken wings, cut up</li>
<li>Around 1 1/2 pounds chicken legs (about six legs)</li>
</ul>
<h3 id="rub">Rub</h3>
<ul>
<li>2 tablespoons brown sugar</li>
<li>1 tablespoon chili powder</li>
<li>1 tablespoon paprika (smoked preferred)</li>
<li>1 tablespoon black pepper</li>
<li>1 tablespoon salt</li>
<li>Some quantity of garlic-infused olive oil</li>
</ul>
<h3 id="dressing">Dressing</h3>
<ul>
<li>Your favorite BBQ sauce</li>
</ul>
<h2 id="instructions">Instructions</h2>
<h3 id="preparation">Preparation</h3>
<ol>
<li>In a small bowl, mix all the components of the rub together until they are evenly distributed throughout the mixture.</li>
<li>Place all of the chicken in a large bowl.</li>
<li>Drizzle the chicken with garlic-infused olive oil.</li>
<li>Sprinkle about 1/3rd of the rub over the chicken.</li>
<li>Mix the chicken around in the bowl until the rub is evenly distributed.</li>
<li>Repeat drizzling the chicken with oil, dusting it with rub, and mixing it two more times, until you are out of rub</li>
</ol>
<h3 id="cooking">Cooking</h3>
<ol>
<li>Preheat your grill at maximum heat with the lid closed for 10 minutes.</li>
<li>Using tongs, arrange your chicken on the top rack of the grill.</li>
<li>Close the lid of the grill and cook the chicken for 8 minutes.</li>
<li>Open the lid and flip the chicken over.</li>
<li>Close the lid and cook the chicken for another 8 minutes.</li>
<li>Using the meat thermometer, ensure that your chicken has reached an internal temperature of 165 degrees Fahrenheit. If it has not, lower the grill’s heat to medium-low to avoid charring the skin too much, flip the chicken, and grill it for an additional 3 minutes. Continue flipping and grilling for 3 minutes until the chicken has reached an internal temperature of 165 degrees Fahrenheit.</li>
<li>Lower the grill’s heat to the minimum.</li>
<li>Using the silicone brush, baste the chicken with BBQ sauce.</li>
<li>Close the lid and cook the chicken on minimum heat for 2-3 minutes, in order to set the sauce.</li>
<li>Open the lid and flip the chicken over.</li>
<li>Using the silicone brush, baste the other side of the chicken with BBQ sauce.</li>
<li>Close the lid and cook the chicken on minimum heat for another 2-3 minutes.</li>
<li>Remove the chicken from the grill and serve it.</li>
</ol>
<h2 id="photo">Photo</h2>
<p>Your chicken should look something like this:</p>
<p><img src="/images/bbq-chicken.jpg" alt="A plate of BBQ chicken" title="A plate of BBQ chicken"></p>
<style>
        
      </style>]]></content:encoded>
            <category>weblogpomo2024</category>
            <category>bbq</category>
            <category>chicken</category>
            <category>wings</category>
            <category>recipe</category>
            <category>grilling</category>
        </item>
        <item>
            <title><![CDATA[Stuff happening behind the scenes]]></title>
            <link>https://jordemort.dev/blog/stuff-happening-behind-the-scenes/</link>
            <guid>https://jordemort.dev/blog/stuff-happening-behind-the-scenes/</guid>
            <pubDate>Mon, 06 May 2024 19:28:00 GMT</pubDate>
            <description><![CDATA[There is a lot of yak hair in this site's plumbing]]></description>
            <content:encoded><![CDATA[<p><a href="https://weblog.anniegreens.lol/weblog-posting-month-2024">WeblogPoMo 2024</a> has been a success in getting me interested in this blog again.
Unfortunately, so far, that’s resulted in a whole lot of coding, and very little actual blogging!
Way back in August of 2022, when I created this site, I wanted it to be a bit of an exercise in getting out of my comfort zone, and demonstrating that I could write TypeScript and do some frontend stuff.
To that end, I picked a fun framework called <a href="https://astro.build/">Astro</a> instead of more hacker-ish static site generator like Hugo or Jekyll.</p>
<p>Astro is great, and I still like it, but back when I started this blog it was version 1.x, and as of this writing, the latest version is 4.x.
Dependencies move pretty fast in the JavaScript world, and I had not been keeping up at all, with the result that the code that generates this site has become a bit of a bit-rotted mess.
When I first got the notion to write something for WeblogPoMo, it actually took me several hours to get the site in a state where it could even build again.</p>
<p>I’ve been working on giving everything a fresh coat of paint and getting it all working with the latest-and-greatest, but I’ve ended up in a very deep yak shave, doing a bunch of late-night coding, and while this is fun, it leaves me with NoPo for the PoMo!
So, here’s an intermediate update on what’s happened so far and what I’m still working on.</p>
<h2 id="moved-from-github-pages-to-self-hosting">Moved from GitHub Pages to self hosting</h2>
<p>Instead of serving this blog from GitHub Pages, I’m now hosting it on my own server.
Initially, I did this because I could get the blog to build locally, but not with GitHub Actions <span role="img" aria-label="smiling face with open mouth &#x26; smiling eyes">😄</span> — the quickest way for me to get a post out for WeblogPoMo was to switch the DNS for jordemort.dev and point it at a server where I could deploy my locally-built copy.
I had been playing with the idea of doing this for a while, and not being able to get my build going in Actions provided the kick in the ass I needed.</p>
<p>Moving to self-hosting has a few upsides:</p>
<ul>
<li>GitHub doesn’t need to know what you’re reading on my blog</li>
<li>I can get actual server logs, instead of just client-side analytics</li>
<li>I can pick off certain URLs and do things other than serve static content with them</li>
</ul>
<p>Speaking of picking off certain URLs…</p>
<h2 id="activitypub-integration">ActivityPub integration</h2>
<p>This blog is now an <a href="/blog/lwn-microblogging-with-activitypub/">ActivityPub actor</a>.
This means that you can now follow this blog with Mastodon or your local moral equivalent at the following address:</p>
<p><code>@jordemort.dev@jordemort.dev</code></p>
<p>This is powered by a neat thing called <a href="https://github.com/importantimport/hatsu">Hatsu</a> that consumes <a href="/json.feed">my JSON feed</a> and turns it into ActivityPub activities.
You can <a href="https://hatsu.jordemort.dev/swagger-ui/">play with the API</a> if you’d like!</p>
<p>I haven’t quite finished setting this up; there’s some <code>&#x3C;link></code> tags I need to generate in my page headers for complete integration, but following the blog works!</p>
<h2 id="coming-soon-pagefind">Coming soon: PageFind</h2>
<p>Implementing my own <a href="/blog/client-side-search/">client-side search</a> was fun, but the libraries I built it on were already unmaintained when I built it, and <a href="https://pagefind.app/">PageFind</a> is a much better and more complete implementation of the same idea that I won’t have to maintain myself.</p>
<h2 id="coming-soon-better-embeds">Coming soon: better embeds</h2>
<p>This is the big yak.
I’ve been using <a href="https://github.com/jodyheavener/remark-plugin-oembed">remark-plugin-oembed</a> to render embedded versions of things like YouTube videos and tweets on this site.
It was kind of broken by upgrading other things, so I decided to build my own replacement.</p>
<p>I’ve never really loved how <a href="https://oembed.com/">oEmbed</a> works; most providers give you some perfunctory metadata, and then a chunk of HTML that you’re supposed to just include in your page.
Generally, the HTML that you’re given contains an <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe"><code>&#x3C;iframe></code></a>, that loads an “embedded” view of the link from the site that you’re linking to.
Twitter’s oEmbed HTML gives you a <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/blockquote"><code>&#x3C;blockquote></code></a> instead, along with a <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script"><code>&#x3C;script></code></a> that styles the blockquote, loads any photos or videos, and does who knows what else.</p>
<p>I <em>hate</em> this.
I hate blindly including HTML on my site that I did not write or generate myself.
I hate making your browser communicate with a third-party that you did not consent to communicate with, simply because you viewed a page on my site with an embedded link.
I hate running random third-party scripts in the context of my site, which are probably collecting and all sorts of analytics that neither you nor I consented to.
I hate everything about it.</p>
<p>So instead, what I’m working on right now is gluing together oEmbed, <a href="https://www.npmjs.com/package/metascraper">Metascraper</a>, and <a href="https://github.com/FixTweet/FxTwitter">FxTwitter</a>, into something that generates embeds that look like this:</p>
<p><img src="/images/embedall-demo.png" alt="A screenshot of an embedded YouTube video" title="A screenshot of an embedded YouTube video"></p>
<p>Some points to note:</p>
<ul>
<li>No more third-party styling; all embeds will be styled consistently with the rest of the site</li>
<li>No more third-party assets; everything needed to display the embedded will be served from <code>jordemort.dev</code>.</li>
<li>No more third-party players; instead, standard HTML <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/audio"><code>&#x3C;audio></code></a> and <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video"><code>&#x3C;video></code></a> elements will be used.
<ul>
<li>If you play audio or video, it will still be loaded from the third-party server, but preloading will be turned off, so nothing will be loaded until you specifically ask your browser to do so.</li>
</ul>
</li>
<li>No more rot; all the data needed to regenerate an embed will be cached in the site’s repository after the first time it is generated.
<ul>
<li>I’m thinking about also automatically submitting anything I embed to the <a href="https://web.archive.org/">Wayback Machine</a> and/or <a href="https://archive.today">archive.today</a>.</li>
</ul>
</li>
<li>A button to go ahead and load the third-party embed anyway, if you still really want to do that for some reason <span role="img" aria-label="face with stuck-out tongue &#x26; winking eye">😜</span></li>
</ul>
<p>Anyway, all of that probably needs another night or two to finish up, which is what I’ve been saying for the past three days or so, but I just keep on having more ideas on how to make them even <em>better</em>, so…
you know how it is.</p>
<style>
        
      </style>]]></content:encoded>
            <category>jordemort.dev</category>
            <category>oembed</category>
            <category>embedall</category>
            <category>activitypub</category>
            <category>mastodon</category>
            <category>fediverse</category>
        </item>
        <item>
            <title><![CDATA[Amazon's Dash Carts are unusably bad]]></title>
            <link>https://jordemort.dev/blog/amazon-dash-carts-are-unusably-bad/</link>
            <guid>https://jordemort.dev/blog/amazon-dash-carts-are-unusably-bad/</guid>
            <pubDate>Fri, 03 May 2024 01:56:58 GMT</pubDate>
            <description><![CDATA[I suppose I shouldn't have been shopping there anyway]]></description>
            <content:encoded><![CDATA[<p>I live not-too-far from one of Amazon’s brick-and-mortar grocery stores.
Until very recently, it was my preferred grocery store; though Amazon is not a force for good in the world, I’m not sure if I can be convinced that Albertson’s, Kroger, or Walmart are any less evil, and Amazon had a big edge on their competitors: <em>Just Walk Out</em>.
Amazon’s prices aren’t much better than other grocery stores, and their selection is decidedly inferior, and their produce is frequently sad (don’t store potatoes under sun lamps, what the hell is wrong with you), but <em>Just Walk Out</em> was the killer feature that nobody else had.</p>
<p>From this end-user’s perspective, <em>Just Walk Out</em> was magical.
I used to be able to grab a cart, arrange my bags in it, fill them up, and leave.
A few hours later, Amazon sent me a bill.
What I especially liked was being able to bag as I go; this allowed me to have dedicated bags for meat, produce, dry goods, non-food items, and so on — this made unloading things when I got home so much easier.</p>
<p>Of course, it wasn’t magic on the backend.
If you ever looked up at the ceiling while in the store, you would notice a grid containing a mind-blowing number of cameras, spaced 3 feet or so apart from each other.
These cameras recorded your every move through the store, through multiple angles.
When you left the store, this footage was combined with data collected through a network of sensors on the shelves and sent to a human working in India to review and ring you up.
A lot of people seemed surprised to learn it wasn’t powered by AI, but it was always obvious to me that there was still a human in the loop, given the several-hour delay between leaving the store and receiving your receipt.</p>
<p>Sadly, <a href="https://arstechnica.com/gadgets/2024/04/amazon-ends-ai-powered-store-checkout-which-needed-1000-video-reviewers/">the magic is now gone</a>; Amazon decided that these humans cost too much and took too long to do their jobs, so they killed the <em>Just Walk Out</em> program in their stores.
In its place, they now offer the <a href="https://www.aboutamazon.com/news/retail/amazon-dash-cart-new-features-whole-foods">Dash Cart</a>.</p>
<p>Using the Dash Cart is a truly wretched experience.
After about 5 minutes with it, I missed <em>Just Walk Out</em> so badly that I put everything back on the shelf, logged out of the cart, and <em>just walked out</em> of the store, never to return again.</p>
<p>The cart is unlike any other grocery cart you’ve used, in a bad way.
It is essentially a large rigid plastic bin on wheels.
It features a comically large grey box housing a touch screen and a sensor package above the handle, where the child seat would normally be, which extends over a good portion of the cart.
If you’re shopping with a young child that can’t walk on their own for the entire trip, well, no, you’re not; not with this cart, anyway.
If, like me, you frequently used this space for delicate produce and squishable items like bread and chips, a small auxiliary bin is provided underneath the sensor package, but it doesn’t have enough clearance to hold a bag standing upright.</p>
<p>Unlike the carts currently on display on Amazon’s PR website, the cart I used didn’t have any sort of venting on any of the bins or on the lower level.
Instead, they were just solid plastic.
This means that any fluids that leak or are spilled in the cart over the course of its use are there forever, until it is hosed out.
If one was left outside in the rain, it would fill up with water.</p>
<p>After I logged into the cart (by using a hidden unlabeled scanner on the underside of the cart that the attendant had to show me,) the screen instructed me that it was a good time to load my own bags into the cart, if I had brought any.
I dutifully started arranging my bags in the cart; it gave me seemingly about 30 seconds to do so before it started screaming at me and demanding to know what I had removed from the cart.
After a couple of attempts at appeasing it, I started to ignore its complaints and went about finishing arranging my bags.
Keep in mind that this was in the vestibule of the store; I hadn’t even gotten within reach of any merchandise yet.</p>
<p>Once I had my bags in place, I was seemingly able to negotiate some sort of peace with the screen by reassuring it that I hadn’t removed anything.
Unfortunately, this peace was short-lived; any time the cart was jostled (by, say, pushing it around the store,) it would again demand to know what had been removed.
It asked me what I had removed from the cart at least three more times as I made my way from the vestibule to the produce section.</p>
<p>I got as far as the cooler containing grapes and strawberries, selected a carton of strawberries, and put it into my produce bag in the cart.
The cart beeped at me.
“Whoops,” I thought, “I forgot, I have to scan things now.”
I removed the strawberries from the bag, found the barcode, and held it up to one of the two large openings in the sensor package that presumably contained cameras.
It scanned the barcode and added the strawberries to my receipt.
Success!
“Well, this is less convenient but I can make it work,” I thought.</p>
<p>I then selected a bag of grapes.
I found the (clearly legible, undamaged) barcode on the grapes and held it up to the cameras; the cart made a positive sounding noise, so I put the grapes into the produce bag.
I went to move on to my next objective, but then the cart indicated that it did not recognize the item I had put in the cart.</p>
<p>Folks, I tried to scan the bag of grapes no less than six different times.
Each time that I did, the cart would make a positive sounding noise, as if it had recognized the item, and then pop up the screen indicating it had not a few seconds later, after I had put the grapes back into the bag.
This was a particularly shitty experience because the screen is angled in a way that it is not visible unless you are standing in the back of the cart, but the sensor package is too large to be convenient to reach over; you have to scan and load each item from the front or side of the cart, and then walk around to the back to be able to see if it worked.</p>
<p>At this point I decided the whole endeavor was untenable.
I was shopping for a week’s worth of groceries for my family and had about 70 items on my list, and I came to the conclusion that repeating this song and dance 69 more times would not be very nice at all.
I put the strawberries back, logged out of the cart, took my bags back to the car, and drove over to the local incarnation of Kroger, where I was able to complete my shopping without having to deal with any testy machines.
Now that <em>Just Walk Out</em> is gone, there is no reason whatsoever for me to choose Amazon over any of their competitors for groceries, and many reasons not to, like that shelf full of green potatoes.</p>
<p>In short, the experience of using the Dash Cart is very similar to using one of those early self-checkout machines (<em>UNEXPECTED ITEM IN BAGGING AREA</em>) except that you push the machine around with you in the store and it harasses you the entire time that you’re shopping.
It should be embarrassing to Amazon that these things can’t reliably scan a bag of grapes with a sensor package larger than many “self-driving” vehicles.
The only good thing about these carts is that they’re so poorly designed that they surely won’t last.
If rain, cars, and the rough-and-tumble life of a grocery cart in a parking lot don’t do them all in by the end of summer, a Chicago winter will surely finish the rest of them off.</p>
<style>
        
      </style>]]></content:encoded>
            <category>amazon</category>
            <category>shopping</category>
            <category>groceries</category>
            <category>weblogpomo2024</category>
        </item>
        <item>
            <title><![CDATA[WeblogPoMo 2024: Books I read on vacation]]></title>
            <link>https://jordemort.dev/blog/weblogpomo-2024-01/</link>
            <guid>https://jordemort.dev/blog/weblogpomo-2024-01/</guid>
            <pubDate>Fri, 03 May 2024 01:56:58 GMT</pubDate>
            <description><![CDATA[Not dead, just elsewhere]]></description>
            <content:encoded><![CDATA[<p>Hey!
How are you doing lately?
I’ve been doing pretty good!
I’ve been settling into my new job and hanging with the family.
I bought myself a Steam Deck at the beginning of the year and played through Baldur’s Gate III, which was amaaaaazing.
You should play it!
I’ve mostly been ignoring this blog and my various side projects… and that’s OK, and I’m not gonna apologize for that!
I’ve been enjoying myself, spending time with the people I care about, and as a result, I feel way more like a functional human than I did last year.</p>
<p>I do still fully intend to get back to a bunch of that stuff, but… later, ya know?
I’ve decided I’m taking a well-being year :)</p>
<p>Anyway, it’s <a href="https://weblog.anniegreens.lol/weblog-posting-month-2024">WeblogPoMo 2024</a>!
Given historical trends, I probably <em>will not</em> be posting every day this month, but in the spirit of things, I thought I’d at least try to post <em>a little</em>.</p>
<h2 id="vacation">Vacation</h2>
<p>As of a roughly a week or so ago, my wife and I have been married for 10 years! Continuously!</p>
<p>To celebrate, we ditched the kid with his aunt &#x26; uncle and headed down to Cancun.
We stayed at a brand-new all-inclusive resort, and I could write a whole tribute to <em>A Supposedly Fun Thing That I’ll Never Do Again</em> about that particular establishment, but I’m not trying to be DFW here.
Despite my issues with the resort, it was overall a very successful, fun, and relaxing vacation.</p>
<p>Here’s a picture of a Mayan ruin:</p>
<p><img src="/images/mayan-ruin.jpg" alt="A crumbling Mayan pyramid, surrounded by vegetation" title="A crumbling Mayan pyramid, surrounded by vegetation"></p>
<p>Here’s a picture of an iguana:</p>
<p><img src="/images/barbecued-iguana.jpg" alt="A tan iguana with black/brown markings, hanging out in the sun on some sandy soil" title="A tan iguana with black/brown markings, hanging out in the sun on some sandy soil"></p>
<p>While I was on vacation, I read the three books!
Here’s what I thought about them:</p>
<h3 id="system-collapse-by-martha-wells"><a href="https://marthawells.com/murderbot7.htm"><em>System Collapse</em> by Martha Wells</a></h3>
<p>At some point last year, Martha Wells made a post on Mastodon saying something like “I’ve got 3 things coming out this year, and you can preorder them all,” and so I did!
Out of the three, I only ever got around to reading <a href="https://www.marthawells.com/witchking.htm">Witch King</a> last year, though; I intended to read <em>System Collapse</em> after that, but then I got distracted by <a href="https://www.yoonhalee.com/?p=742">Ninefox Gambit</a> and was compelled to read the entire <em>Machineries of Empire</em> series, and by the time I had finished with that, <em>System Collapse</em> had disappeared somewhere into my office.
Luckily, I found it the week before I left for vacation.</p>
<p>My favorite bits of this were the interplay between SecUnit and the central system, and the developing aro life partnership between SecUnit and ART.
One day I’ll need to sit down and re-read this series from start to finish in one go; I always have a hard time remembering specifics of what happened in previous books.
I also have a hard time remembering who any of the human characters are, or what differentiates them from each other, but I think at least a little bit of that is deliberate on the part of the author.
I don’t read these for the humans, though.</p>
<h3 id="throne-of-glass-by-sarah-j-maas"><a href="https://sarahjmaas.com/books/throne-of-glass/"><em>Throne of Glass</em> by Sarah J. Maas</a></h3>
<p>I didn’t think this was a YA book when I picked it up, and it doesn’t appear to be marketed as such, but it contains <em>a lot</em> of teenage yearning, and a plot that would be resolved much more easily if any of the characters who obviously care deeply for each other <em>actually talked to each other about anything</em>.
The main plot has a bit of a <em>Hunger Games</em> vibe, with the protagonist taken captive and forced to train for and participate in a lethal tournament by a wicked and corrupt dictator, and developing friendships with her trainer and some of her competitors along the way by being one of the few not-totally-awful people in the vicinity.
There’s also a “mean popular girl versus exciting new girl” subplot that seems to differ wildly in tone from the main plot, until it is tied together at the end; I expect mean girl to reemerge as a harpy queen or something later in the series.</p>
<p>This was pretty different from my usual fare.
Romantic concerns are not usually as much of a factor in the stuff I read, and the author describes the main character’s outfits with the same care that is given to describing feasts in the <em>Redwall</em> books.
I enjoyed it, though, and intend to continue with the series, although the blurb I read for the next book was something like “everyone keeps secrets from each other and shit hits the fan” so I will probably continue to be frustrated with the characters’ lack of communication with each other for at least one more book.</p>
<h3 id="city-of-bones-by-martha-wells"><a href="https://reactormag.com/cover-reveal-city-of-bones-by-martha-wells/"><em>City of Bones</em> by Martha Wells</a></h3>
<p>This is an updated and revised version of one of Martha Wells’ earlier novels.
The original was released long before I was aware of Martha Wells, so I can’t tell you how the revised edition compares to that, but it does compare favorably to her other work.
This reminded me the most of <em>Witch King</em>, which makes sense because that’s the other fantasy novel of hers that I’ve read so far.
The setting also reminded me a bit of Rosemary Kirstein’s <em>Steerswoman</em> series; I won’t spoil you on either, but the backstory of how stuff got wrecked and how it’s been going since then is similar, although the <em>Steerswoman</em> apocalypse is a lot further in the past than the <em>City of Bones</em> one.</p>
<p>Khat is very much a protagonist in the vein of SecUnit and Kai, severed from his people and his original “purpose in life” and using his unique abilities to help his new found family of misfits.
There’s a fair amount of intrigue going on and several plot twists; the big revelation at the end was very satisfying and an excellent payoff to all of the world-building.
It almost reminded me of some of William Gibson’s weirder stuff, except that Martha Wells can actually write a decent ending to a story instead of just chopping it off and calling it done.</p>
<p>The ending of this one was bittersweet; it was believable but I think the romantic dimension of the relationship between the two characters was undersold and brought in too late.
There weren’t a lot of hints that the characters might be starting to feel <em>that way</em> about each other, although it made sense given what they had been through together.
This is a standalone book and works well as one, but I certainly wouldn’t mind a couple more short stories set in this universe, <em>hint hint nudge nudge</em>.</p>
<style>
        
      </style>]]></content:encoded>
            <category>weblogpomo2024</category>
            <category>books</category>
            <category>murderbot</category>
            <category>marthawells</category>
            <category>throneofglass</category>
            <category>sarahjmaas</category>
        </item>
        <item>
            <title><![CDATA[Jobs, swords, and doors]]></title>
            <link>https://jordemort.dev/blog/jobs-swords-and-doors/</link>
            <guid>https://jordemort.dev/blog/jobs-swords-and-doors/</guid>
            <pubDate>Wed, 01 Nov 2023 20:33:37 GMT</pubDate>
            <description><![CDATA[What have I been up to lately?]]></description>
            <content:encoded><![CDATA[<p>Hey folks! Long time, no post!
Between vacation, a new job, more vacation, a new school year for the kid, and trips to visit family, I haven’t had much time to write here.
What have I been up to lately?</p>
<h2 id="work">Work</h2>
<p>I took a full-time job at one of mu consulting clients in May.
It’s been a good experience, but I recently received a very exciting offer from an old friend to join their team at <a href="https://dangerdevices.com/">Danger Devices</a>.
I’m winding up my current position, and then I’ll have a couple weeks of funemployment over the Thanksgiving holiday before starting at Danger in December.</p>
<p>Coinciding with starting my new job, I’ll be winding up <a href="https://caketop.app/">Caketop</a> (my consulting persona) and parking the domain for now.
I like the freedom of being a freelancer, but I don’t like the uncertainty, and I really don’t like having to buy health insurance on the exchange.
This is a bit of a shame, because I have a sweet new logo for Caketop that I commissioned from <a href="https://noct.zone/">Dzuk</a> that I never got to deploy, but once I start my new gig I will no longer want or need to solicit any additional work.</p>
<h2 id="play">Play</h2>
<h3 id="exposition">Exposition</h3>
<p>Before I got on the internet, I hung out a lot on assorted <a href="http://bbsdocumentary.com/">BBS</a> systems, and even ran a grievously unpopular one of my own (called “Norwegian Wood”) for a short time.</p>
<p>Along with allowing me to exchange files and messages with other users, many of my local BBSs featured <a href="https://en.wikipedia.org/wiki/Door_(bulletin_board_system)">door games</a>.
These are multiplayer games, but they operate in a uniquely asynchronous way.
All of the players exist within the same “world” but, but in most BBS door games the players never interact with each other in real-time.
Instead, each player is given a limited number of turns per day; you log in, take your turns, and log off.
If you do something particularly noteworthy during your session, the game may announce your feat to other players when they log on; other than that, interaction between players is mostly limited to competition for spots on one or more leaderboards.</p>
<p>This sort of multiplayer model was particularly suited to the constraints imposed by running as part of a BBS.
Remember that we were dialing into these things with modems; the number of users that could be logged on to a BBS simultaneously was limited by the number of modems, phone lines, and possibly computers (because you couldn’t take multitasking as a given in those days) that the operator of the BBS was willing to pay for.
As a consequence, most BBSs run by hobbyists were single-node; usually only companies, rich people, and for-pay BBSs were willing to shell out for the equipment and phone lines needed to host multiple nodes.
It would be pretty difficult to do real-time multiplayer without multiple nodes, and if you only had a single phone line, you wouldn’t want one player hogging it all day.</p>
<p>I always loved door games; many hours were spent running up my mom’s phone bill to play <a href="https://en.wikipedia.org/wiki/Legend_of_the_Red_Dragon">Legend of the Red Dragon</a> on all of the local BBSs.
I’ve also made multiple attempts to “bring them back”, but a bit more on that in a few paragraphs…</p>
<h3 id="inspiration">Inspiration</h3>
<p>A couple months ago, I was lucky enough to be invited by <a href="http://www.elissablack.com/">Elissa</a> to be a beta tester for <a href="https://swordsoffreeport.com/">Swords of Freeport</a>.
Swords of Freeport (or “SFP”, as we cool kids call it) is essentially a love letter to the door games of yore.
I love it.
I’ve played the crap out of it and I’m the solidly the mightiest hunter and tenuously the richest person on the test server.</p>
<p>I liked it so much that I even made a map!
It’s a few versions out of date, but it should still be useful, if you’re going to play.
I plan to update this for the release version during my holiday downtime:</p>
<p><img src="/images/sfp-map-0.8.5.png" alt="Map of Swords of Freeport 0.8.5" title="Map of Swords of Freeport 0.8.5"></p>
<p>I made the map with <a href="https://diagrams.net">diagrams.net</a> — here it is on <a href="https://drive.google.com/file/d/1_jiXhue5oUFMn6mfGbVkQwQd-T_tPzq4/view?usp=sharing">Google Drive</a> if you want to remix it or update it before I get around to it.</p>
<p>Anyway, go <a href="https://expectproblems.itch.io/swords-of-freeport">buy Swords of Freeport on itch.io</a> now.
It’s amazing, it’ll encourage Elissa to make more weird games, and it’s priced in Australian dollars so the exchange rate ought to be favorable if you’re in the US.</p>
<h3 id="revisitation">Revisitation</h3>
<p>Anyway, back to the idea of bringing these door games back.
There is, of course, plenty of prior art on this.
There are several BBS software packages still being maintained, like <a href="https://www.citadel.org/">Citadel</a>, <a href="https://enigma-bbs.github.io/">ENiGMA½</a>, <a href="https://www.mysticbbs.com/">Mystic</a>, and <a href="https://www.synchro.net/">Synchronet</a>, as well as projects like <a href="https://github.com/rickparrish/GameSrv">GameSrv</a> and services like <a href="http://wiki.throwbackbbs.com/doku.php?id=start">DoorParty</a>.
All of these software packages offer paths to getting old door games running on modern systems, and if I was looking to replicate the old school BBS experience as closely as possible, I would grab one or more of these and be on my merry way.</p>
<p>The rub is that I don’t actually want to replicate the old school BBS experience.
I don’t want BBS-style message boards; even if I did want to exchange  messages in text mode, I’d just run an NNTP server, or fire up <code>mutt</code>.
I don’t want BBS-style file bases; if I want to distribute files, I’ll just do it over HTTP like everyone else does these days.
This is not to disparage those that still enjoy running and/or using BBSs, but for me, almost every part of the BBS experience has been supplanted in my heart by some newer internet-age technology.
The only thing I really miss is the doors.</p>
<p>What I’ve really always wanted is a door launcher that uses the local user database (i.e. <code>/etc/passwd</code> and friends) on a Linux system.
Essentially, I’d like to be able to make doors work like the <a href="https://packages.debian.org/bookworm/bsdgames"><code>bsdgames</code></a> - they should be just another program that someone can launch from their shell account, with a system-wide shared scoreboard.
I’m not interested in running a BBS again, and I doubt any of my friends would use it if I did, but I bet at least some of my friends would be interested in a shell account that came with a bunch of games.</p>
<p>I’ve made multiple abortive attempts at this, going back to college days, and have had almost everything working (at least on the bench, in manual hacky ways) multiple times, but there are a couple things that stopped me from finishing them.</p>
<h4 id="the-soft-reason-not-to">The soft reason not to</h4>
<p>First of all, there’s the nagging feeling that very few people are going to appreciate this but me.
I’m not exactly building the <a href="https://inv.tux.pizza/watch?v=5Ay5GqJwHF8">Field of Dreams</a> here; BBS door games were a pretty niche interest, even in their heyday.
Elissa has inspired me to ignore this feeling, though.
Swords of Freeport has brought me great joy, and if I can bring the same level of joy to even one person, it’s worth it, even if that person is only myself.</p>
<h4 id="the-hard-reason-not-to">The hard reason not to</h4>
<p>The second reason that kept me from ever finishing this was technical.
DOSEMU (and more lately DOSEMU2) has always offered a variety of ways to attach sockets or file descriptors or FIFOs or what have you to emulated serial ports, but what it didn’t offer was a way to signal disconnection of those serial ports.
On a real serial port, there are some additional lines for signaling, separate from the lines that carry data; modems used these lines to tell the PC if a connection was active or not.
If a caller hung up, the modem would signal the disconnection to the PC, and software using the modem could handle this and wrap things up cleanly.</p>
<p>Up until September 22nd of this year, there was no way to emulate this behavior with DOSEMU2 - emulated serial ports always indicated that they had a connection, even after the other end of the socket/file descriptor/whatever they were connected to was closed.
This meant that there was no way to cleanly handle someone who disconnected in the middle of playing a door, unless I wanted to build something around a <a href="https://github.com/freemed/tty0tty">weird out-of-tree kernel module</a>, and I did not.
This was particularly frustrating, because I could absolutely detect a user disconnecting in software, but there was no way to signal it to the emulated DOS program.
I couldn’t just signal the process in DOS, because DOS doesn’t actually have signals, or processes, or really the concept of multitasking at all - once the door was running in DOSEMU, there was no out-of-band way of telling it to cleanly stop.
The only options were:</p>
<ul>
<li>Wait for the door time time out due to inactivity - this sucks because it leaves dead sessions around for 10 minutes or more, and especially sucks because it locks that node of the door for the duration.</li>
<li>Kill the emulator process - this just really isn’t an option because I don’t want to corrupt the door’s game data.</li>
</ul>
<p>So, what happened on September 22nd?
<a href="https://github.com/dosemu2/dosemu2/pull/2102">I happened!</a>
I cracked open the sources to DOSEMU2 and pinpointed the code that was causing the behavior that I didn’t like.
Then, with the gracious guidance and help of the <a href="https://github.com/stsp">DOSEMU2 maintainer</a>, I fixed things, so that DOSEMU2 will notice that the other end of an emulated serial port is closed and indicate a dropped connection.</p>
<p>Even if I went no further, I feel like my patch has improved the state of play for other people wanting to do things related to fake modems in DOSEMU2.
This effort has already borne more fruit than my previous ones; that improvement is out now and included in software that (some) people are actually installing on their machines!</p>
<h3 id="introduction">Introduction</h3>
<p>Introducing <a href="https://github.com/jordemort/doorman">Doorman</a>, the door launcher I’ve always wanted!
It’s early days; there is no documentation yet.
There are no usage instructions yet.
There is, however, working code, that people are using to play LORD on a server in my house at this very moment.</p>
<p>I decided to use Doorman to teach myself Rust.
I like it a lot!
During the process, <a href="https://github.com/jordemort/doorman/pull/1">lots of very nice people</a> also stepped in and taught me <em>proper</em> Rust idioms, instead of the nonsense I had come up with on my own.</p>
<p>The current implementation of Doorman works, but there’s a lot about it that could be better.
Originally I envisioned it as a single <a href="https://en.wikipedia.org/wiki/Setuid"><code>setuid/setgid</code></a> binary and no daemon process, ala <code>bsdgames</code>, but I’m running DOSEMU2 in a container and I want to support rootless <a href="https://podman.io/">Podman</a> and it turns it it’s a real pain-in-the-ass to run rootless Podman without a proper user session, which being <code>setuid</code> definitely does not get you.
So right now I’m reworking it to use a daemon process, and using a nifty trick of passing client file descriptors over a UNIX socket (suggested to me by <a href="https://github.com/jameysharp">Jamey Sharp</a>) to avoid having to get too involved in terminal handling.</p>
<p>I won’t have a lot of time to work on Doorman until I wrap up my current job on the 15th.
After that, I’ll have a <em>bunch of time</em> to work on it, at least for a couple weeks.
Hopefully I can get an initial “proper” release out by the beginning of December.</p>
<p>Thanks for reading!
Hopefully I’ll be back in not-too-long with a release announcement!</p>
<style>
        
      </style>]]></content:encoded>
            <category>personal</category>
            <category>doorman</category>
            <category>bbs</category>
            <category>door</category>
            <category>games</category>
            <category>sfp</category>
            <category>lord</category>
        </item>
        <item>
            <title><![CDATA[Why Android can't use CDC Ethernet]]></title>
            <link>https://jordemort.dev/blog/why-android-cant-use-cdc-ethernet/</link>
            <guid>https://jordemort.dev/blog/why-android-cant-use-cdc-ethernet/</guid>
            <pubDate>Wed, 31 May 2023 12:02:04 GMT</pubDate>
            <description><![CDATA[A detective story with a silly ending]]></description>
            <content:encoded><![CDATA[<p>If you just want the answer to the question posed in the title, click the TLDR below and then move on with your day.
Otherwise, buckle in, we’re going debugging; this post is mostly about my thought process and techniques I used to arrive at the answer rather than the answer itself.</p>
<details>
<summary><b>TLDR:</b> Just tell me why CDC Ethernet doesn't work on Android</summary>
<br>
Android's EthernetTracker service only acknowledges interfaces that are named <tt>ethX</tt>; Linux's CDC Ethernet drivers create interfaces that are named <tt>usbX</tt>. There is no way to work around this, short of rooting the phone to change the value of <tt>config_ethernet_iface_regex</tt>.
</details>
<p>Android contains support for USB ethernet adapters.
There’s even menus for them!</p>
<p><img src="/images/android-cdc/connection-settings.jpg" alt="Android connection settings, with &#x27;Ethernet&#x27; greyed out" title="Android connection settings, with &#x27;Ethernet&#x27; greyed out"></p>
<p>This means that if you very carefully select a USB Ethernet adapter that you know has a chipset compatible with your Android device, you can plug it in and these settings will spring to life.
How do you know what chipsets are compatible with your phone?</p>
<p>Hearsay!</p>
<p>I’m not entirely kidding.
If the company that you bought your phone from sells a USB ethernet adapter as an accessory to it, you have a pretty good chance of that one working.
Otherwise, it’s hit-or-miss; phone manufacturers rarely, if ever, publish lists of supported Ethernet adapters.
The best you’re going to get is finding a forum post from someone that has the same phone as you saying that they bought a particular adapter that worked, and hoping you can find the same thing to buy.</p>
<p>Or is it?</p>
<p><img src="/images/android-cdc/i-know-this.jpg" alt="This is a Linux system... I know this" title="This is a Linux system... I know this"></p>
<p>As you may know, if you dig deep beneath Android’s Googly carapace, you’ll find a Linux kernel.
To build the Linux kernel, you must first configure it.
This configuration determines what features and hardware the resulting kernel will support.
Thus, the list of Ethernet adapters supported by your phone will more-or-less correspond to those selected in the kernel configuration for your phone, although it’s possible (but unlikely) that your phone’s manufacturer doesn’t ship all of the drivers that they build, or that they build additional third-party drivers separately.</p>
<p>So, in order to figure out what Ethernet adapters your phone supports, you’re going to want to find your phone’s kernel configuration.
How do we do that?</p>
<h3 id="first-enable-usb-debugging-and-install-adb">First, enable USB debugging and install ADB</h3>
<p>If you’d like to follow along with this blog post, you’re going to need enable USB debugging and to install ADB (Android Debug Bridge) — this is a command-line tool that is used by developers to interact with Android devices.
In this post, we will be using it to run shell commands on a phone.</p>
<p>There’s good documentation elsewhere on how to do these things so I’m not going to waste time by rewriting it poorly.
Instead, have some links:</p>
<ol>
<li>First, <a href="https://developer.android.com/studio/debug/dev-options#Enable-debugging">enable USB debugging</a> on your phone</li>
<li><a href="https://www.xda-developers.com/install-adb-windows-macos-linux/">Install ADB</a> on your computer</li>
<li>Run <code>adb shell</code>, which will give you a shell prompt on the phone.</li>
</ol>
<p><img src="/images/android-cdc/im-in.jpg" alt="I&#x27;m in" title="I&#x27;m in"></p>
<p>Congratulations, you can now run commands on your phone.
Type <code>exit</code> and press enter when you’re ready to exit the ADB shell.</p>
<p>Next, we need to switch things up so that ADB connects to the phone over the network, instead of via USB.
We need to do this because we’re going to try plugging some network adapters into the phone’s USB port, so we can’t also use the port for debugging.</p>
<p>With your phone connected to your computer via USB:</p>
<ol>
<li>Connect your phone to the same network as your computer via wifi</li>
<li>Figure out your phone’s IP address - you can do this by digging around the Settings app, or you can try <code>adb shell ifconfig wlan0</code></li>
<li>With the phone still connected via USB, run <code>adb tcpip 5555</code></li>
<li>Disconnect the USB cable from the phone</li>
<li>Reconnect to the phone by running <code>adb connect YOUR_PHONE_IP:5555</code> (replacing YOUR_PHONE_IP with the IP address from the phone)</li>
<li>Try <code>adb shell</code> to make sure it still works</li>
</ol>
<p>Once you have ADB working over the network, you can proceed with trying to figure out what version of the kernel your Android device is running.</p>
<h3 id="if-you-have-a-newer-phone">If you have a newer phone…</h3>
<p>These days, Google publishes an <a href="https://source.android.com/docs/core/architecture/kernel/android-common">Android Common Kernel</a>, which downstream phone manufacturers are required to derive their kernels from.
The source to this kernel is hosted in a <a href="https://android.googlesource.com/kernel/common/">Git repository at googlesource.com</a>.</p>
<p>If your phone shipped with Android 11 or later, you have something called a <a href="https://source.android.com/docs/core/architecture/kernel/generic-kernel-image">GKI kernel</a> - in this case, Google builds the kernel and the phone manufacturer puts all of their model-specific secret sauce into kernel modules.
In this case, you can find the configuration that Google is using by navigating to the appropriate branch of the kernel repository, and looking at the file <code>arch/$ARCH/configs/gki_defconfig</code>, where <code>$ARCH</code> is the processor architecture of your phone.
For example, if your phone has a 64-bit ARM processor (and it almost certainly does) then you will find this configuration at <a href="https://android.googlesource.com/kernel/common/+/refs/heads/android-mainline/arch/arm64/configs/gki_defconfig"><code>arch/arm64/configs/gki_defconfig</code></a>.</p>
<h3 id="how-do-i-find-out-for-sure-what-kernel-version-and-processor-architecture-my-phone-has">How do I find out for sure what kernel version and processor architecture my phone has?</h3>
<p>Now that we have the ability to run shell commands on the phone, we can turn to good old <a href="https://man7.org/linux/man-pages/man2/uname.2.html"><code>uname</code></a> to discover the kernel version and architecture that’s currently running.</p>
<ol>
<li>Go back and <a href="#first-enable-usb-debugging-and-install-adb">enable USB debugging and install ADB</a>, if you haven’t arleady</li>
<li>Run <code>uname -a</code> on the phone, either by running <code>adb shell</code> and then running <code>uname -a</code>, or all in one go by running <code>adb shell uname -a</code>.</li>
</ol>
<p>You should get output something like this:</p>
<pre is:raw="" class="astro-code dark-plus" style="background-color: #1E1E1E; overflow-x: auto;" tabindex="0"><code><span class="line"><span style="color: #D4D4D4">Linux localhost 4.19.113-26203352 #1 SMP PREEMPT Tue Apr 18 16:05:51 KST 2023 aarch64 Toybox</span></span></code></pre>
<p>You’ll the kernel version in the third field and the architecture in the second-to-last; you’ll have to make an educated guess about which branch or tag in Google’s kernel repository corresponds to the one running on your phone.</p>
<h3 id="what-if-i-have-an-older-phone">What if I have an older phone?</h3>
<p>If you have an older phone, then you’re in the same boat as me; I have an iPhone as a daily driver, but I keep a Samsung Galaxy s20 around as an Android testbed.
Unfortunately, the s20 shipped with Android 10, which is the version just before all of this standardized kernel stuff from Google became required.
Even though the s20 has since been upgraded to Android 13, Google doesn’t require phone manufacturers to update the kernel along with the Android version, and so Samsung didn’t; it still runs a kernel based on Linux 4.19.</p>
<p>In this case, you need to get the kernel configuration from your phone manufacturer, so you’d better hope they’re actually doing regular source releases.
Samsung does do this; you can find sources for their phones at <a href="https://opensource.samsung.com/uploadList?menuItem=mobile&#x26;classification1=mobile_phone">opensource.samsung.com</a>.</p>
<p>Once you have the sources for your device, you’re going to have to dig around a bit to figure out what kernel config.
The sources I obtained for my phone from Samsung included a <code>Kernel.tar.gz</code>; inside of this archive was a Linux kernel source tree, along with a few additions.
One of those additions was a shell script called <code>build_kernel.sh</code>, which goes a little something like this:</p>
<pre is:raw="" class="astro-code dark-plus" style="background-color: #1E1E1E; overflow-x: auto;" tabindex="0"><code><span class="line"><span style="color: #6A9955">#!/bin/bash</span></span>
<span class="line"></span>
<span class="line"><span style="color: #569CD6">export</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">ARCH</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">arm64</span></span>
<span class="line"><span style="color: #DCDCAA">mkdir</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">out</span></span>
<span class="line"></span>
<span class="line"><span style="color: #9CDCFE">BUILD_CROSS_COMPILE</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">$(</span><span style="color: #DCDCAA">pwd</span><span style="color: #CE9178">)/toolchain/gcc/linux-x86/aarch64/aarch64-linux-android-4.9/bin/aarch64-linux-android-</span></span>
<span class="line"><span style="color: #9CDCFE">KERNEL_LLVM_BIN</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">$(</span><span style="color: #DCDCAA">pwd</span><span style="color: #CE9178">)/toolchain/llvm-arm-toolchain-ship/10.0/bin/clang</span></span>
<span class="line"><span style="color: #9CDCFE">CLANG_TRIPLE</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">aarch64-linux-gnu-</span></span>
<span class="line"><span style="color: #9CDCFE">KERNEL_MAKE_ENV</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">"DTC_EXT=$(</span><span style="color: #DCDCAA">pwd</span><span style="color: #CE9178">)/tools/dtc CONFIG_BUILD_ARM64_DT_OVERLAY=y"</span></span>
<span class="line"></span>
<span class="line"><span style="color: #DCDCAA">make</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">-j8</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">-C</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">$(</span><span style="color: #DCDCAA">pwd</span><span style="color: #CE9178">)</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">O=$(</span><span style="color: #DCDCAA">pwd</span><span style="color: #CE9178">)/out</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">$KERNEL_MAKE_ENV</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">ARCH=arm64</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">CROSS_COMPILE=</span><span style="color: #9CDCFE">$BUILD_CROSS_COMPILE</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">REAL_CC=</span><span style="color: #9CDCFE">$KERNEL_LLVM_BIN</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">CLANG_TRIPLE=</span><span style="color: #9CDCFE">$CLANG_TRIPLE</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">vendor/x1q_usa_singlex_defconfig</span></span>
<span class="line"></span>
<span class="line"><span style="color: #DCDCAA">make</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">-j8</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">-C</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">$(</span><span style="color: #DCDCAA">pwd</span><span style="color: #CE9178">)</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">O=$(</span><span style="color: #DCDCAA">pwd</span><span style="color: #CE9178">)/out</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">$KERNEL_MAKE_ENV</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">ARCH=arm64</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">CROSS_COMPILE=</span><span style="color: #9CDCFE">$BUILD_CROSS_COMPILE</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">REAL_CC=</span><span style="color: #9CDCFE">$KERNEL_LLVM_BIN</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">CLANG_TRIPLE=</span><span style="color: #9CDCFE">$CLANG_TRIPLE</span></span>
<span class="line"></span>
<span class="line"><span style="color: #DCDCAA">cp</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">out/arch/arm64/boot/Image</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">$(</span><span style="color: #DCDCAA">pwd</span><span style="color: #CE9178">)/arch/arm64/boot/Image</span></span></code></pre>
<p>If you squint at this long enough, you’ll spot a reference to something that looks like a kernel config: <code>vendor/x1q_usa_singlex_defconfig</code>.
There isn’t a subdirectory called <code>vendor</code> in the root of the archive, so I used <code>find</code> to figure out exactly where the file lives:</p>
<pre is:raw="" class="astro-code dark-plus" style="background-color: #1E1E1E; overflow-x: auto;" tabindex="0"><code><span class="line"><span style="color: #D4D4D4">$ find . -name x1q_usa_singlex_defconfig</span></span>
<span class="line"><span style="color: #D4D4D4">./arch/arm64/configs/vendor/x1q_usa_singlex_defconfig</span></span></code></pre>
<p>Aha, there it is, deeply nested in a subdirectory.</p>
<h3 id="finding-the-kernel-config-sounds-hard-is-there-an-easier-way">Finding the kernel config sounds hard, is there an easier way?</h3>
<p>There might be, if you’re lucky!
Give this a shot:</p>
<pre is:raw="" class="astro-code dark-plus" style="background-color: #1E1E1E; overflow-x: auto;" tabindex="0"><code><span class="line"><span style="color: #D4D4D4">$ adb shell zcat /proc/config.gz</span></span></code></pre>
<p>If you’re lucky, and your phone manufacturer has enabled the relevant kernel option, then a compressed copy of the configuration that your kernel was compiled with is available at <code>/proc/config.gz</code>.
If this is the case, you’ll have a large amount of output streaming to your terminal.
You probably want to redirect it somewhere so you can peruse it at your leisure:</p>
<pre is:raw="" class="astro-code dark-plus" style="background-color: #1E1E1E; overflow-x: auto;" tabindex="0"><code><span class="line"><span style="color: #D4D4D4">$ adb shell zcat /proc/config.gz > my_kernel_config</span></span></code></pre>
<p>If you’re unlucky, you’ll see something like this:</p>
<pre is:raw="" class="astro-code dark-plus" style="background-color: #1E1E1E; overflow-x: auto;" tabindex="0"><code><span class="line"><span style="color: #D4D4D4">zcat: /proc/config.gz: No such file or directory</span></span></code></pre>
<p>In this case, there is no easy way out; you’ll have to refer to the sources your phone’s kernel was built from.</p>
<h3 id="what-does-a-kernel-configuration-look-like">What does a kernel configuration look like?</h3>
<p>In case you’re interested, here is the kernel configuration for my Galaxy s20: <a href="https://gist.github.com/jordemort/bb9f15028dce9854f9bfd4e750522e48"><code>x1q_usa_singlex_defconfig</code></a></p>
<p>Your kernel configuration should look very similar to this, but not identical, unless you have the same phone that I do.</p>
<h3 id="ok-i-have-the-kernel-configuration-for-my-phone-what-now">OK, I have the kernel configuration for my phone, what now?</h3>
<p>For the purpose of determining which USB Ethernet adapters the kernel supports, most of the configuration variables that we are interested will start with <code>USB_NET</code>, so just <code>grep</code> the kernel configuration for that string:</p>
<pre is:raw="" class="astro-code dark-plus" style="background-color: #1E1E1E; overflow-x: auto;" tabindex="0"><code><span class="line"><span style="color: #D4D4D4">$ grep USB_NET my_kernel_config</span></span>
<span class="line"><span style="color: #D4D4D4">CONFIG_USB_NET_DRIVERS=y</span></span>
<span class="line"><span style="color: #D4D4D4">CONFIG_USB_NET_AX8817X=y</span></span>
<span class="line"><span style="color: #D4D4D4">CONFIG_USB_NET_AX88179_178A=y</span></span>
<span class="line"><span style="color: #D4D4D4">CONFIG_USB_NET_CDCETHER=y</span></span>
<span class="line"><span style="color: #D4D4D4">CONFIG_USB_NET_CDC_EEM=y</span></span>
<span class="line"><span style="color: #D4D4D4">CONFIG_USB_NET_CDC_NCM=y</span></span>
<span class="line"><span style="color: #D4D4D4"># CONFIG_USB_NET_HUAWEI_CDC_NCM is not set</span></span>
<span class="line"><span style="color: #D4D4D4">... and so on ...</span></span></code></pre>
<p>Look for a <code>CONFIG_USB_NET_something</code> that looks like it relates to the chipset of the adapter you want to use.
The best news is if it is set to <code>y</code>; that means the driver is built-in to your kernel and that your phone’s kernel definitely supports that chipset.
If it’s set to <code>m</code>, that’s still <em>probably</em> good news; that means that the driver was compiled as a module when your kernel was built, and that the module is likely loadable on your phone unless your phone’s manufacturer specifically left it out.
If you see <code>is not set</code>, then that is the worst news; the driver was neither built-in to your kernel, nor was it compiled as a module, so it’s likely not available for you to use.</p>
<p>If you’re having trouble figuring out which configuration items correspond to which chipsets, have a look at <a href="https://android.googlesource.com/kernel/common/+/refs/heads/android-mainline/drivers/net/usb/Kconfig"><code>drivers/net/usb/Kconfig</code></a> in your kernel tree.
This file will contain extended descriptions of each configuration item.</p>
<p>Unfortunately, to figure out which chipset a particular adapter uses, you’re mostly back to hearsay; few manufacturers of USB Ethernet adapters explicitly advertise which chipset they use.</p>
<h3 id="so-whats-this-about-cdc-ethernet-and-why-should-i-care">So what’s this about CDC Ethernet and why should I care?</h3>
<p>CDC stands for <a href="https://en.wikipedia.org/wiki/USB_communications_device_class">Communications Device Class</a>.
This is a set of interrelated standards that manufacturers of USB devices can follow; among them are a trio of standards called EEM (Ethernet Emulation Model), ECM (Ethernet Control Model), and NCM (Network Control Model) that can be used to build USB Ethernet adapters.
Most of the difference between these three standards is a matter of complexity; EEM is the simplest to implement and is easy to support on underpowered devices, but may not result in the best performance.
ECM is more complex to implement for both the USB host and the device, but promises better performance than EEM; NCM is a successor to ECM that promises even higher speeds.
Many devices implement more than one of these protocols, and leave it up to the host operating system to communicate with the device using the one that it prefers.</p>
<p>The point of these standards is that, assuming manufacturers follow them, operating systems can provide a single common driver that works with a variety of drivers.
You generally don’t need special drivers for USB keyboards or mice because of the <a href="https://en.wikipedia.org/wiki/USB_human_interface_device_class">USB HID</a> standard; the USB CDC standard attempts to accomplish the same for USB networking devices.</p>
<p>One particularly fun thing is that Linux implements both the host and the device side of the CDC Ethernet standards.
That means that if you have hardware with a <a href="https://en.wikipedia.org/wiki/USB_On-The-Go">USB OTG</a> port, which is common on the Raspberry Pi and other small ARM devices, you can tell the kernel to use that port to <a href="https://learn.adafruit.com/turning-your-raspberry-pi-zero-into-a-usb-gadget/ethernet-gadget">pretend to be an Ethernet adapter</a>.
This creates a USB network interface on the host that is directly connected to an interface on the guest; this lets you build cool things like embedded routers, firewalls, and VPN gateways that look like just another Ethernet adapter to the host.</p>
<p>Linux, as well as Windows and macOS (but not iOS) include drivers for CDC Ethernet devices.
Unfortunately, none of this works on Android devices, despite Android being based on Linux.
Why is Android like this?</p>
<h3 id="based-on-the-kernel-configuration-android-appears-to-support-cdc">Based on the kernel configuration, Android <em>appears</em> to support CDC</h3>
<p>Let’s have another look at our kernel config, and grep for USB_NET_CDC:</p>
<pre is:raw="" class="astro-code dark-plus" style="background-color: #1E1E1E; overflow-x: auto;" tabindex="0"><code><span class="line"><span style="color: #D4D4D4">$ grep USB_NET_CDC my_kernel_config</span></span>
<span class="line"><span style="color: #D4D4D4">CONFIG_USB_NET_CDCETHER=y</span></span>
<span class="line"><span style="color: #D4D4D4">CONFIG_USB_NET_CDC_EEM=y</span></span>
<span class="line"><span style="color: #D4D4D4">CONFIG_USB_NET_CDC_NCM=y</span></span>
<span class="line"><span style="color: #D4D4D4">... and so on ...</span></span></code></pre>
<p>Here we can see that Samsung has built support for all 3 CDC Ethernet standards into their kernel (<code>CONFIG_USB_NET_CDCETHER</code> corresponds to ECM).
Google’s GKI kernels are somewhat less generous and appear to leave out ECM and NCM, but still include support for EEM as a module.</p>
<p>I’ve got a device with an OTG port that I’ve configured as an Ethernet gadget.
It works when I plug it into my Mac.
It works when I plug it into my Ubuntu desktop.
It even works when I plug it into my Windows game machine (actually the same computer as the Ubuntu desktop, booted off of a different drive <span role="img" aria-label="grinning face with smiling eyes">😁</span>).
It doesn’t work at all when I plug it into my Galaxy s20.
The Ethernet settings are still greyed out:</p>
<p><img src="/images/android-cdc/grey-ethernet.jpg" alt="&#x27;Ethernet&#x27; greyed out" title="&#x27;Ethernet&#x27; greyed out"></p>
<p>Let’s grab a shell on the phone and dig in a bit.</p>
<p>The Linux kernel exposes information about itself in a pseudo-filesystem called <a href="https://en.wikipedia.org/wiki/Sysfs">sysfs</a> - this looks like a directory tree full of files, but reading the files actually gets you information about the current state of the kernel.</p>
<p>Among other things, sysfs contains a directory named <code>/sys/class/net</code>, which contains one entry for every network interface that the kernel is aware of.
Let’s connect our Ethernet gadget to the phone and see if anything shows up there:</p>
<pre is:raw="" class="astro-code dark-plus" style="background-color: #1E1E1E; overflow-x: auto;" tabindex="0"><code><span class="line"><span style="color: #D4D4D4">$ adb shell ls /sys/class/net</span></span>
<span class="line"><span style="color: #D4D4D4">... lots of output ...</span></span>
<span class="line"><span style="color: #D4D4D4">usb0</span></span>
<span class="line"><span style="color: #D4D4D4">wlan0</span></span></code></pre>
<p>Could <code>usb0</code> be the gadget?
Let’s use <code>ifconfig</code> to check it out:</p>
<pre is:raw="" class="astro-code dark-plus" style="background-color: #1E1E1E; overflow-x: auto;" tabindex="0"><code><span class="line"><span style="color: #D4D4D4">$ adb shell ifconfig usb0</span></span>
<span class="line"><span style="color: #D4D4D4">usb0      Link encap:UNSPEC    Driver cdc_eem</span></span>
<span class="line"><span style="color: #D4D4D4">          BROADCAST MULTICAST  MTU:1500  Metric:1</span></span>
<span class="line"><span style="color: #D4D4D4">          RX packets:0 errors:0 dropped:0 overruns:0 frame:0</span></span>
<span class="line"><span style="color: #D4D4D4">          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0</span></span>
<span class="line"><span style="color: #D4D4D4">          collisions:0 txqueuelen:1000</span></span>
<span class="line"><span style="color: #D4D4D4">          RX bytes:0 TX bytes:0</span></span></code></pre>
<p>That certainly looks like our gadget.
Too bad the interface is down.
Unfortunately, the Ethernet settings on the phone are still greyed out:</p>
<p><img src="/images/android-cdc/grey-ethernet.jpg" alt="&#x27;Ethernet&#x27; greyed out" title="&#x27;Ethernet&#x27; greyed out"></p>
<p>Let’s unplug the gadget and make sure <code>usb0</code> goes away when we do:</p>
<pre is:raw="" class="astro-code dark-plus" style="background-color: #1E1E1E; overflow-x: auto;" tabindex="0"><code><span class="line"><span style="color: #D4D4D4">$ adb shell ls /sys/class/net | grep usb</span></span>
<span class="line"><span style="color: #D4D4D4">$ # no output</span></span></code></pre>
<p>Yep, it’s gone.</p>
<p>It looks like we’re using EEM mode.
In addition to the <code>g_ether</code> module, Linux also includes a thing called <a href="https://docs.kernel.org/usb/gadget_configfs.html">configfs</a> that can be used to create custom gadgets.
Let’s try one that only supports ECM and see if that works:</p>
<pre is:raw="" class="astro-code dark-plus" style="background-color: #1E1E1E; overflow-x: auto;" tabindex="0"><code><span class="line"><span style="color: #D4D4D4">$ adb shell ls /sys/class/net | grep usb</span></span>
<span class="line"><span style="color: #D4D4D4">usb0</span></span>
<span class="line"><span style="color: #D4D4D4">$ adb shell ifconfig usb0</span></span>
<span class="line"><span style="color: #D4D4D4">usb0      Link encap:UNSPEC    Driver cdc_ether</span></span>
<span class="line"><span style="color: #D4D4D4">          BROADCAST MULTICAST  MTU:1500  Metric:1</span></span>
<span class="line"><span style="color: #D4D4D4">          RX packets:0 errors:0 dropped:0 overruns:0 frame:0</span></span>
<span class="line"><span style="color: #D4D4D4">          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0</span></span>
<span class="line"><span style="color: #D4D4D4">          collisions:0 txqueuelen:1000</span></span>
<span class="line"><span style="color: #D4D4D4">          RX bytes:0 TX bytes:0</span></span></code></pre>
<p><img src="/images/android-cdc/grey-ethernet.jpg" alt="&#x27;Ethernet&#x27; greyed out" title="&#x27;Ethernet&#x27; greyed out"></p>
<p>It’s still detected, but it’s still down.
Will NCM fare any better?</p>
<pre is:raw="" class="astro-code dark-plus" style="background-color: #1E1E1E; overflow-x: auto;" tabindex="0"><code><span class="line"><span style="color: #D4D4D4">$ adb shell ls /sys/class/net | grep usb</span></span>
<span class="line"><span style="color: #D4D4D4">usb0</span></span>
<span class="line"><span style="color: #D4D4D4">$ adb shell ifconfig usb0</span></span>
<span class="line"><span style="color: #D4D4D4">usb0      Link encap:UNSPEC    Driver cdc_ncm</span></span>
<span class="line"><span style="color: #D4D4D4">          BROADCAST MULTICAST  MTU:1500  Metric:1</span></span>
<span class="line"><span style="color: #D4D4D4">          RX packets:0 errors:0 dropped:0 overruns:0 frame:0</span></span>
<span class="line"><span style="color: #D4D4D4">          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0</span></span>
<span class="line"><span style="color: #D4D4D4">          collisions:0 txqueuelen:1000</span></span>
<span class="line"><span style="color: #D4D4D4">          RX bytes:0 TX bytes:0</span></span></code></pre>
<p><img src="/images/android-cdc/grey-ethernet.jpg" alt="&#x27;Ethernet&#x27; greyed out" title="&#x27;Ethernet&#x27; greyed out"></p>
<p>No, it will not.</p>
<h3 id="so-why-doesnt-cdc-work-on-android">So why doesn’t CDC work on Android?</h3>
<p>At this point, we’ve more-or-less established that everything is fine on the kernel level.
I’m pretty sure that if I wanted to, I could root this phone, manually configure the interface with <code>ifconfig</code>, and it would pass traffic just fine.
That means the problem must be somewhere in the stack of software above the kernel.</p>
<p>If this was a regular Linux system, this is the point where I’d start poking at systemd-networkd, or NetworkManager, or ifupdown, depending on the particulars.
This is not a regular Linux system, though; it’s an Android device, and none of that stuff exists here.
What do I know about how Android configures network interfaces?</p>
<p><strong>NOTHING.</strong>
I know nothing about how Android configures network interfaces.
How do we figure this out?</p>
<p>Well, Android is at least sort of open source; many of the good bits are closed behind the veil of something called “Google Play Services” but maybe there’s enough in the sources that are released to figure this out.</p>
<p>To play along with this bit, you’ll need to <a href="https://source.android.com/docs/setup/download/downloading">download the source to Android</a>.
This is a whole process on its own, so I’ll leave you to Google’s documentation for this, except to note that you’ll need a special tool called <code>repo</code>.
This seems to be meant to make it easier to download sources from multiple Git repositories at once; sometimes it feels like I’m the only person that actually likes <a href="https://git-scm.com/book/en/v2/Git-Tools-Submodules">Git submodules</a>.
There are a lot of sources to download, so start this process and then go knock off a few shrines in Zelda while it wraps up.</p>
<p>I figure that searching for the string <code>Ethernet</code> is probably a good starting point.
Because there is so much source to go through, I’m going to skip vanilla <code>grep</code> this time and enlist the aid of <a href="https://github.com/BurntSushi/ripgrep">ripgrep</a>.
There’s a lot of configuration files and other clutter in the Android sources, as well as most of a Linux distro, but I know that any code that we’re going to care about here is likely written in Java, so I’m going to restrict <code>rg</code> to searching in Java files:</p>
<pre is:raw="" class="astro-code dark-plus" style="background-color: #1E1E1E; overflow-x: auto;" tabindex="0"><code><span class="line"><span style="color: #D4D4D4">$ rg -t java Ethernet</span></span>
<span class="line"><span style="color: #D4D4D4">... SO MUCH OUTPUT ...</span></span></code></pre>
<p>At this point, there’s not much else to do but look at the files where we’ve got hits and try to figure out what part of the code we can blame for our problem.
Fortunately for you, I’ve saved you the trouble.
After reading a bunch of Android code, I’m certain that our culprit is <a href="https://android.googlesource.com/platform/packages/modules/Connectivity/+/refs/heads/master/service-t/src/com/android/server/ethernet/EthernetTracker.java"><code>EthernetTracker.java</code></a>.
This appears to be a service that listens on a <a href="https://docs.kernel.org/userspace-api/netlink/intro.html">Netlink</a> socket and receives notifications from the kernel about new network interfaces.
The EthernetTracker contains a method that determines if an Ethernet interface is “valid”; if it is valid, the EthernetTracker reports to the rest of the system that an interface is available, and the Settings app allows the interface to be configured.
If an interface is not valid, then the EthernetTracker simply ignores it.</p>
<p>How does the EthernetTracker determine if an interface is valid?</p>
<pre is:raw="" class="astro-code dark-plus" style="background-color: #1E1E1E; overflow-x: auto;" tabindex="0"><code><span class="line"><span style="color: #569CD6">private</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">boolean</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">isValidEthernetInterface</span><span style="color: #D4D4D4">(</span><span style="color: #4EC9B0">String</span><span style="color: #D4D4D4"> iface) {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">iface</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">matches</span><span style="color: #D4D4D4">(mIfaceMatch) || </span><span style="color: #DCDCAA">isValidTestInterface</span><span style="color: #D4D4D4">(iface);</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre>
<p>With a regex, of course.</p>
<p>Where does this regex come from?</p>
<pre is:raw="" class="astro-code dark-plus" style="background-color: #1E1E1E; overflow-x: auto;" tabindex="0"><code><span class="line"><span style="color: #6A9955">// Interface match regex.</span></span>
<span class="line"><span style="color: #D4D4D4">mIfaceMatch = </span><span style="color: #9CDCFE">mDeps</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">getInterfaceRegexFromResource</span><span style="color: #D4D4D4">(mContext);</span></span></code></pre>
<p>It comes from a method called <code>getInterfaceRegexFromResource</code>.
Where does that method get it from?</p>
<pre is:raw="" class="astro-code dark-plus" style="background-color: #1E1E1E; overflow-x: auto;" tabindex="0"><code><span class="line"><span style="color: #569CD6">public</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">String</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">getInterfaceRegexFromResource</span><span style="color: #D4D4D4">(</span><span style="color: #4EC9B0">Context</span><span style="color: #D4D4D4"> context) {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">final</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">ConnectivityResources</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">resources</span><span style="color: #D4D4D4"> = </span><span style="color: #C586C0">new</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">ConnectivityResources</span><span style="color: #D4D4D4">(context);</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">resources</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">get</span><span style="color: #D4D4D4">().</span><span style="color: #DCDCAA">getString</span><span style="color: #D4D4D4">(</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #9CDCFE">com</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">android</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">connectivity</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">resources</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">R</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">string</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">config_ethernet_iface_regex</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre>
<p>There’s actually a nice comment at the top of the file that explains this:</p>
<pre is:raw="" class="astro-code dark-plus" style="background-color: #1E1E1E; overflow-x: auto;" tabindex="0"><code><span class="line"><span style="color: #6A9955">/**</span></span>
<span class="line"><span style="color: #6A9955"> * Tracks Ethernet interfaces and manages interface configurations.</span></span>
<span class="line"><span style="color: #6A9955"> *</span></span>
<span class="line"><span style="color: #6A9955"> * &#x3C;p>Interfaces may have different {@link android.net.NetworkCapabilities}. This mapping is defined</span></span>
<span class="line"><span style="color: #6A9955"> * in {@code config_ethernet_interfaces}. Notably, some interfaces could be marked as restricted by</span></span>
<span class="line"><span style="color: #6A9955"> * not specifying {@link android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED} flag.</span></span>
<span class="line"><span style="color: #6A9955"> * Interfaces could have associated {@link android.net.IpConfiguration}.</span></span>
<span class="line"><span style="color: #6A9955"> * Ethernet Interfaces may be present at boot time or appear after boot (e.g., for Ethernet adapters</span></span>
<span class="line"><span style="color: #6A9955"> * connected over USB). This class supports multiple interfaces. When an interface appears on the</span></span>
<span class="line"><span style="color: #6A9955"> * system (or is present at boot time) this class will start tracking it and bring it up. Only</span></span>
<span class="line"><span style="color: #6A9955"> * interfaces whose names match the {@code config_ethernet_iface_regex} regular expression are</span></span>
<span class="line"><span style="color: #6A9955"> * tracked.</span></span>
<span class="line"><span style="color: #6A9955"> *</span></span>
<span class="line"><span style="color: #6A9955"> * &#x3C;p>All public or package private methods must be thread-safe unless stated otherwise.</span></span>
<span class="line"><span style="color: #6A9955"> */</span></span></code></pre>
<p>Let’s go back to ripgrep to see if we can skip to finding out what <code>config_ethernet_iface_regex</code> is:</p>
<pre is:raw="" class="astro-code dark-plus" style="background-color: #1E1E1E; overflow-x: auto;" tabindex="0"><code><span class="line"><span style="color: #D4D4D4">$ rg config_ethernet_iface_regex</span></span>
<span class="line"><span style="color: #D4D4D4">...</span></span>
<span class="line"><span style="color: #D4D4D4"></span></span>
<span class="line"><span style="color: #D4D4D4">frameworks/base/core/res/res/values/config.xml</span></span>
<span class="line"><span style="color: #D4D4D4">410:    &#x3C;string translatable="false" name="config_ethernet_iface_regex">eth\\d&#x3C;/string></span></span>
<span class="line"><span style="color: #D4D4D4"></span></span>
<span class="line"><span style="color: #D4D4D4">...</span></span>
<span class="line"><span style="color: #D4D4D4"></span></span>
<span class="line"><span style="color: #D4D4D4">packages/modules/Connectivity/service/ServiceConnectivityResources/res/values/config.xml</span></span>
<span class="line"><span style="color: #D4D4D4">170:    &#x3C;string translatable="false" name="config_ethernet_iface_regex">eth\\d&#x3C;/string></span></span>
<span class="line"><span style="color: #D4D4D4"></span></span>
<span class="line"><span style="color: #D4D4D4">...</span></span></code></pre>
<p>…and there it is.
The default value of <code>config_ethernet_iface_regex</code> is <code>eth\d</code>; in regex parlance, that means the literal string <code>eth</code>, followed by a digit.</p>
<p>The kernel on the phone calls our CDC Ethernet gadget <code>usb0</code>.
This doesn’t start with the string <code>eth</code>, so EthernetTracker ignores it.
Unfortunately, this setting is not user-configurable, although you can hack it by rooting the phone.</p>
<p>It really is that silly; an entire USB device class brought low by a bum regex.</p>
<h3 id="is-it-a-bug">Is it a bug?</h3>
<p>I can’t tell if this is intentional or not; it feels like an oversight by Google, since even the newest GKI kernels apparently go out of their way to include support for EEM adapters, but because the interface name doesn’t match the regex, the kernel’s support for EEM adapters is unusable.
This puts you in a rather perverse situation when shopping for USB Ethernet adapters to use with Android; instead of looking for devices that implement the CDC standards, you need to explicitly <em>AVOID</em> the standards-based devices and look for something that is supported with a vendor/chipset-specific driver.</p>
<h3 id="thanks-for-playing">Thanks for playing!</h3>
<p>I hope you enjoyed going on this journey with me, or even better that I saved you from duplicating my efforts.
Perhaps if I am feeling feisty, I will try to figure out how to submit a patch to Android to change that regex to <code>(eth|usb)\d</code> in the next few weeks.
If a real Android dev or someone at Google reads this and beats me to the punch, I owe you the beverage of your choice.</p>
<style>
        
      </style>]]></content:encoded>
            <category>android</category>
            <category>usb</category>
            <category>cdc</category>
            <category>ethernet</category>
            <category>java</category>
            <category>ripgrep</category>
            <category>regex</category>
        </item>
        <item>
            <title><![CDATA[The License Plate Game]]></title>
            <link>https://jordemort.dev/blog/the-license-plate-game/</link>
            <guid>https://jordemort.dev/blog/the-license-plate-game/</guid>
            <pubDate>Mon, 20 Mar 2023 14:23:42 GMT</pubDate>
            <description><![CDATA[A one-page party game]]></description>
            <content:encoded><![CDATA[<p>One of my favorite accounts on the Fediverse is <a href="https://botsin.space/@ca_dmv_bot">ca-dmv-bot</a>.
This is a bot that posts actual vanity license plate requests that the California Department of Motor Vehicles received between 2015 and 2016, along with a reviewer’s comments on them, and whether they were approved or denied.
It’s a lot of fun seeing what kind of weird and/or obscene things people tried to get on a license plate, and the reviewer’s reasons for approving it or denying it.</p>
<p>I’m not normally a person that makes games, but my brain kept telling me that there’s a game mechanic in this.
So, in order to appease it, I am pleased to present:</p>
<h2 id="the-license-plate-game">The License Plate Game</h2>
<p>This is a party game for at least 4 players, although at least 5 is a more ideal number.
In addition to the players, you will need:</p>
<ul>
<li>Scraps of paper</li>
<li>A writing implement</li>
</ul>
<h3 id="cast-of-characters">Cast of characters</h3>
<h4 id="the-applicant">The applicant</h4>
<p>One player is the applicant.
The applicant wins by getting the clerk to incorrectly approve a plate that is offensive, or by getting the clerk to incorrectly deny an innocent plate.
The applicant must walk the line between fooling the clerk while still having the meaning of their plate be recognizable to the audience.</p>
<h4 id="the-clerk">The clerk</h4>
<p>One player is the clerk.
The clerk wins by correctly guessing the meaning of the plate, or what “most people” would say the plate means.
If the clerk fails at that, they may also win by denying an offensive plate, even if they got the meaning wrong.</p>
<h4 id="the-audience">The audience</h4>
<p>The rest of the players are the audience.
The audience is mostly along for the ride; they can neither win nor lose.
They serve as a check on the clerk’s interpretations and vote to settle disputes.
Otherwise, their job is to heckle the clerk.</p>
<h3 id="how-to-play">How to play</h3>
<ol>
<li>
<p>The applicant makes up a plate number, and secretly writes down the true meaning of their plate. The meaning may or may not be offensive (as defined by your local community standards.)</p>
<ul>
<li>You can make your own house rules for what sorts of plate numbers are allowed, but here’s a starting point: up to 8 characters, A through Z and 0 through 9, spaces are allowed but don’t count toward the limit. Some US states also allow special characters like a heart (<span role="img" aria-label="red heart">❤️</span>) - feel free to adjust this to your taste or to better simulate your own state or country.</li>
</ul>
</li>
<li>
<p>The applicant reveals their plate number to the other players. The clerk has 5 seconds to announce what they think the true meaning of the plate is, and if it is approved or denied. If the clerk thinks the plate is offensive, they should deny the plate; otherwise, they should approve the plate. If 5 seconds pass without the clerk announcing a decision, the plate is considered to be approved.</p>
<ul>
<li>The time limit is meant to simulate the experience of being an overworked clerk in an understaffed bureaucracy.</li>
</ul>
</li>
<li>
<p>Each audience member announces what they think the true meaning of the plate is. They may choose to agree with the clerk’s interpretation, or supply their own. Audience members may discuss their interpretations with each other before committing to them. There is no hard time limit on audience deliberations.</p>
<ul>
<li>Think of the audience as other people on the road who might notice the plate or get stuck behind it in traffic.</li>
</ul>
</li>
<li>
<p>The applicant reveals the true meaning of the plate.</p>
</li>
</ol>
<h3 id="scoring">Scoring</h3>
<ol>
<li>
<p>If the clerk correctly guessed the true meaning of the plate and correctly judged it as either offensive or innocent, the clerk wins the round.</p>
<ul>
<li>Generally, if the clerk gets the meaning of the plate correct, then they will also get the judgement correct, but the clerk’s judgement can be disputed by the applicant or any audience member; see “Exceptional cases” below.</li>
</ul>
</li>
<li>
<p>If the clerk incorrectly guessed the true meaning of the plate, but at least half of the audience agrees with their interpretation, the clerk wins the round.</p>
<ul>
<li>This discourages applicants from providing bad-faith interpretations of their plate numbers.</li>
</ul>
</li>
<li>
<p>If neither the clerk nor any member of the audience correctly guessed the true meaning of the plate, the round is a draw.</p>
<ul>
<li>This discourages applicants from providing completely inscrutable plate numbers.</li>
</ul>
</li>
<li>
<p>Otherwise, at least one person other than the applicant has correctly guessed the true meaning of the plate:</p>
<ul>
<li>
<p>If the meaning of the plate is offensive, and the clerk denied it, the clerk wins the round.</p>
</li>
<li>
<p>If the meaning of the plate is offensive, and the clerk approved it, the applicant wins the round.</p>
</li>
<li>
<p>If the meaning of the plate is innocent, and the clerk denied it, the applicant wins the round.</p>
</li>
<li>
<p>If the meaning of the plate is innocent, and the clerk approved it, the round is a draw.</p>
</li>
</ul>
</li>
</ol>
<h3 id="exceptional-cases">Exceptional cases</h3>
<p>If the applicant or an audience member disagrees with the clerk’s judgement about what is offensive or innocent, they may dispute it.
Disputes are resolved by a vote of the audience.</p>
<p>If an audience member thinks deliberations are going on too long, they can call for cloture.
This is also decided by a vote of the audience.
If the vote succeeds, all audience members who have not done so must immediately supply an interpretation or be quiet for the rest of the round.
An individual audience member can only call for cloture once per round, but if a cloture vote fails, a different audience member may call for cloture later.</p>
<p>In either case, if the vote of the audience ends in a tie, the clerk casts the deciding vote.</p>
<h3 id="notes">Notes</h3>
<p>I haven’t actually played this yet, but I suspect that, as in real life, the rules favor the clerk.
To ensure that everyone has the most fun, each of the players should rotate through the roles.
You may also want to weight applicant wins more heavily; i.e. an applicant win is worth 3 victory points whereas a clerk win is only worth 1.</p>
<p>Thinking up clever plates on the fly can be hard, so you might want to have a brainstorming session before playing, where all the players spend time thinking of and writing down ideas for plates to use when they are the applicant.
Obviously, the players should keep their ideas secret from each other during this process, to avoid tipping off their future clerk.</p>
<p>If you decide to play this, I’d love to know how it goes!
Assorted ways to contact me can be found on the <a href="/">front page</a>.</p>
<style>
        
      </style>]]></content:encoded>
            <category>license plate</category>
            <category>game</category>
        </item>
        <item>
            <title><![CDATA[Stuff I use in the terminal]]></title>
            <link>https://jordemort.dev/blog/stuff-i-use-in-the-terminal/</link>
            <guid>https://jordemort.dev/blog/stuff-i-use-in-the-terminal/</guid>
            <pubDate>Mon, 06 Feb 2023 19:45:00 GMT</pubDate>
            <description><![CDATA[Some bits and bobs that make my life easier]]></description>
            <content:encoded><![CDATA[<p>So, as you might have already guessed, I spend a lot of time in the terminal, using a shell.
After a brief flirtation with <a href="https://www.tcsh.org/">tcsh</a>, which was somewhat fashionable in the 90’s and favored by my Solaris-loving professors for some time after, I settled rather thoroughly into <a href="https://www.gnu.org/software/bash/">Bash</a>, which I will probably now use until I am dead.
We have a, um, complicated relationship; I strongly believe that the only thing worse than using Bash is using any other shell.</p>
<p>I move around a lot between various machines.
I may not have permission/inclination/space to install a bunch of additional software on some of the machines I find myself on.
Some of them might not even be running Linux <span role="img" aria-label="face screaming in fear">😱</span>!</p>
<p>This nomadic lifestyle has had a big influence on my choice of tools.
I try to keep my dependencies light and avoid coming to rely on things that might not be easy to install everywhere.
This is why I settled on Bash; it’s almost always already available on any machine I might need to log in to.
My dotfiles follow a philosophy of <a href="https://en.wikipedia.org/wiki/Progressive_enhancement">progressive enhancement</a> - they should work out-of-the-box with only Bash and a reasonably POSIX-compliant set of CLI tools.
If I want to put something outside of the standard set of tools into my dotfiles, I add a check to see if it’s installed first, and avoid calling it if it isn’t.</p>
<p>An actual tour of my <a href="https://github.com/jordemort/dotfiles">dotfiles</a> would be a bit like taking you through my utility room, in that you might see many things that you find awkward, inexplicable, and/or gross, so I’m not going to do that.
I thought it might be nice to highlight some of my favorite tools, though.</p>
<h2 id="absolutely-essential">Absolutely essential</h2>
<h3 id="homeshick">homeshick</h3>
<p>The first thing that gets installed when I’m moving into a new system is <a href="https://github.com/andsens/homeshick">homeshick</a>.
This is a tool that helps you synchronize your dotfiles between machines by keeping them in one or more Git repositories.
It started out as a clone of <a href="https://github.com/technicalpickles/homesick">homesick</a>, but homesick is written in Ruby, whereas home<i>sh</i>ick  is written in Bash.
Since it requires no other tools besides my preferred shell to bootstrap, I picked homeshick over homesick, eventhough homesick is written by a former coworker <span role="img" aria-label="grinning face with smiling eyes">😁</span>.</p>
<p>My dotfiles contain a <a href="https://github.com/jordemort/dotfiles/blob/main/setup.sh"><code>setup.sh</code></a> that, among other things, handles installing homeshick and setting it up.
All I need to do to move into a new machine is clone the repo and run the script.
When I was at GitHub, a “spiritual successor” of Codespaces would run this script for me automatically when spinning up a new development machine; it looks like Codespaces <a href="https://docs.github.com/en/codespaces/customizing-your-codespace/personalizing-github-codespaces-for-your-account#dotfiles">borrowed this feature</a> from the internal tool that preceded it.</p>
<h3 id="asdf">asdf</h3>
<p>I want to be upfront about this; the way I use <a href="https://asdf-vm.com/">asdf</a> might be considered a bit of an abuse!
It is designed to be a <em>version mananger</em>; it’s meant as a one-size-fits-all replacement for tools like <a href="https://github.com/nvm-sh/nvm">nvm</a>, <a href="https://github.com/pyenv/pyenv">pyenv</a>, and <a href="https://github.com/rbenv/rbenv">rbenv</a>.
If you were using it the way it was intended, you would create a <a href="https://asdf-vm.com/manage/configuration.html#tool-versions"><code>.tool-versions</code></a> file in each of your projects and put whatever version of Node, Python, Ruby, or whatever in there, so that other people could attempt to reproduce your results using the same versions.
This is not how I use it.</p>
<p>Instead, I use asdf as a sort of ad-hoc package manager.
I get the feeling that I’m not the only one doing this, because there are asdf plugins to install all sorts of tools that you would never think need versioning; I just checked and as of this writing, <code>asdf plugin list all</code> indicates that asdf is capable of installing 591 different tools.
Whenever there’s a tool I want to use that I don’t have installed, I check to see if there’s an asdf plugin for it. If there is, I do this and I’m off to the races:</p>
<pre is:raw="" class="astro-code dark-plus" style="background-color: #1E1E1E; overflow-x: auto;" tabindex="0"><code><span class="line"><span style="color: #DCDCAA">asdf</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">plugin</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">add</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">foo</span></span>
<span class="line"><span style="color: #DCDCAA">asdf</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">install</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">foo</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">latest</span></span>
<span class="line"><span style="color: #DCDCAA">asdf</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">global</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">foo</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">latest</span></span>
<span class="line"><span style="color: #DCDCAA">asdf</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">reshim</span></span></code></pre>
<p>Like homeshick, asdf is written in Bash.
That means I can just make it a <a href="https://git-scm.com/book/en/v2/Git-Tools-Submodules">submodule</a> of my dotfiles and have it automatically installed and available when my dotfiles are set up.
It’s the package manager you can keep in your pocket.</p>
<h2 id="pretty-much-essential">Pretty much essential</h2>
<h3 id="ripgrep">ripgrep</h3>
<p><a href="https://github.com/BurntSushi/ripgrep">ripgrep</a> is a better grep.
It’s so much better than grep that I’m willing to seek it out and install it on any system that I’m going to be living on for more than a day or two.
I appreciate that it has sane defaults; it won’t dig into hidden files or binaries without being specifically asked to, and it even respects <code>.gitgnore</code>.
It defaults to recursively searching the current directory, so you can leave off the filename entirely if that’s what you want (and like at least 80% of the time that’s exactly what I want):</p>
<pre is:raw="" class="astro-code dark-plus" style="background-color: #1E1E1E; overflow-x: auto;" tabindex="0"><code><span class="line"><span style="color: #D4D4D4">$ rg JordanRules123</span></span>
<span class="line"><span style="color: #D4D4D4">src/pages/blog/stuff-i-use-feb-2023.md</span></span>
<span class="line"><span style="color: #D4D4D4">79:&#x3C;!-- NOTE: Change GitHub password from JordanRules123 --></span></span></code></pre>
<p>Lots of distros and package managers have <a href="https://github.com/BurntSushi/ripgrep#installation">packages for ripgrep</a>; there’s also an <a href="https://gitlab.com/wt0f/asdf-ripgrep">asdf plugin for ripgrep</a>, which is what I usually use to install it.</p>
<h3 id="mcfly">McFly</h3>
<p><img src="/images/misc/ctrl-r-is-good-but-it-can-be-better.jpg" alt="Maxwell Lord: Ctrl+R is good, but it can be better!" title="Maxwell Lord: Ctrl+R is good, but it can be better!"></p>
<p>First: did you know that you can hit <kbd>Ctrl+R</kbd> in Bash to search your shell history?
I didn’t, for like, the first 10 or 15 years that I used Bash.
I was grepping <code>~/.bash_history</code> like an animal.</p>
<p>Anyway, that’s pretty great, but when you get to my age, sometimes you can’t remember exactly what it is that you’re looking for.
Sometimes your memory is a little… fuzzy.
That’s where <a href="https://github.com/cantino/mcfly">McFly</a> comes in!
It replaces Bash’s stock <kbd>Ctrl+R</kbd> experience with a slick full-screen interface and fuzzy search.
McFly also takes in all sorts of contextual cues when sorting its search results, including your current directory and how recently commands were run.</p>
<p>Sadly, there is no asdf plugin for McFly, but there are pretty easy <a href="https://github.com/cantino/mcfly#installation">installation instructions</a> for a variety of platforms.
It looks like McFly’s maintainer is currently looking for additional help; I’d step up but my Rust skills are rudimentary at best.
Perhaps this could be an opportunity for you?</p>
<h2 id="fun-enough-to-install-most-places">Fun enough to install most places</h2>
<h3 id="starship">Starship</h3>
<p>Once upon a time, I spent a lot of time fine-tuning my hand-rolled, extremely custom Bash prompt.
Eventually I threw most of that out, and just vendored <a href="https://github.com/git/git/blob/master/contrib/completion/git-prompt.sh"><code>git-prompt.sh</code></a> into my dotfiles.
Such was the state of things until last year, when I started longing for something flashier, but found myself utterly without motivation to put it together myself.
That’s when I found <a href="https://starship.rs/">Starship</a>.</p>
<p>Starship comes with a fully-loaded prompt with a version indicator for just about anything you could think of; if you’ve got a lot of stuff on your machine you’ll quickly be overwhelmed.
Fortunately, it is <a href="https://starship.rs/config/#prompt">highly configurable</a>.
I turned most of the stuff off and got a little creative with <a href="https://github.com/ryanoasis/powerline-extra-symbols">Powerline</a> characters:</p>
<p><img src="/images/misc/very-fancy-prompt.png" alt="My very fancy prompt" title="My very fancy prompt"></p>
<p>Now I have a <a href="https://github.com/jordemort/dotfiles/blob/main/home/.config/starship.toml">very fancy prompt</a> that I put very little work into.
My dotfiles fall back to the pre-Starship version of my prompt if it’s not available.
There are <a href="https://starship.rs/guide/#%F0%9F%9A%80-installation">lots of ways to install Starship</a> but I usually use the <a href="https://github.com/grimoh/asdf-starship">asdf-starship plugin</a>.</p>
<h3 id="lsd">LSD</h3>
<p><a href="https://github.com/Peltoche/lsd">LSD</a> is a drop-in replacement for <code>ls</code>; that is to say, you can <code>alias ls=lsd</code> and it will behave more-or-less as you expect, even if you have been using <code>ls</code> for a long time.
The main reason that I use LSD is that in addition to color-coding different types of files like GNU ls, it also adds icons:</p>
<p><img src="/images/misc/lsd.png" alt="A directory listing with icons" title="A directory listing with icons"></p>
<p>This is at least party just for fun, but there’s a real case for it being a usability improvement as well; the icons give your tired eyes one more distinct thing that they can latch on to when you’re scanning that directory listing at 2AM.</p>
<p>If you’re thinking “those don’t look like official Unicode characters,” you’re right.
You’re going to need a special font for this one; you can grab an appropriate one from <a href="https://www.nerdfonts.com/">Nerd Fonts</a>.</p>
<p>Unfortunately there’s no asdf plugin for LSD, but there are <a href="https://github.com/Peltoche/lsd#installation">lots of ways to install it</a>.</p>
<h2 id="thats-all-for-now">That’s all for now!</h2>
<p>Congratulations on getting through January!
I barely made it myself.
I landed a big contract that’s probably going to keep me pretty busy until springtime, so my posting frequency might drop off a bit, but I will try not to abandon you entirely.
Thanks for reading!</p>
<style>
        
      </style>]]></content:encoded>
            <category>cli</category>
            <category>dotfiles</category>
            <category>shell</category>
            <category>homeshick</category>
            <category>asdf</category>
            <category>rg</category>
            <category>ripgrep</category>
            <category>mcfly</category>
            <category>starship</category>
            <category>lsd</category>
        </item>
        <item>
            <title><![CDATA[python-starlark-go 1.0.0 is now available]]></title>
            <link>https://jordemort.dev/blog/python-starlark-go-1.0.0/</link>
            <guid>https://jordemort.dev/blog/python-starlark-go-1.0.0/</guid>
            <pubDate>Mon, 23 Jan 2023 16:50:28 GMT</pubDate>
            <description><![CDATA[I heard you still like Python]]></description>
            <content:encoded><![CDATA[<p><img src="https://repository-images.githubusercontent.com/481312275/48b40583-d3a6-432a-9165-eaf725f7812d" alt="python-starlark-go banner" title="python-starlark-go banner"></p>
<p>Version <a href="https://github.com/caketop/python-starlark-go/releases/tag/v1.0.0">1.0.0</a> of <a href="https://github.com/caketop/python-starlark-go">python-starlark-go</a> is now <a href="https://pypi.org/project/starlark-go/">available on PyPI</a>.</p>
<p>This comes hot on the heels of the <a href="/blog/python-starlark-go-0.1.2/">previous release</a>; the <code>universal2</code> binaries that I published for v0.1.2 turned out to be not-so-universal.
Once again, this issue was noticed and <a href="https://github.com/caketop/python-starlark-go/pull/134">fixed</a> by <a href="https://www.cad.cx/">Colin Dean</a>.</p>
<p>There were a couple of different things that prevented us from publishing working universal2 wheels:</p>
<ul>
<li>We’re using <a href="https://cibuildwheel.readthedocs.io/en/stable/">cibuildwheel</a> to build the wheels, and that understands how to cross-compile things, but since part of the extension is written in Go and needs to be compiled with CGo, some additional tweaks were needed to make sure the compiled bit was coming out for the correct architecture.</li>
<li>Go currently <a href="https://github.com/golang/go/issues/40698">doesn’t support creating universal binaries</a> on its own; you need to <a href="https://dev.to/thewraven/universal-macos-binaries-with-go-1-16-3mm3">do it by hand with <code>lipo</code></a>, and we’d need to <a href="https://github.com/asottile/setuptools-golang/issues/155">build our own tooling</a> for that.</li>
</ul>
<p>As a consequence, we’ve decided it’s simpler to drop <code>universal2</code>, and just publish separate <code>x86_64</code> and <code>arm64</code> wheels for macOS.
That means we have to publish more wheels, but the upside is that <code>pip</code> will definitely be able to find one that works on your Mac.
I also added back <code>i686</code> wheels for Linux; they had inadvertently been dropped from the previous release.</p>
<p>The embedded version of <code>starlark-go</code> has been updated to the latest available version as of this writing, which is <a href="https://pkg.go.dev/go.starlark.net@v0.0.0-20230122040757-066229b0515d">v0.0.0-20230122040757-066229b0515d</a>.</p>
<p>I went ahead and bumped the version to 1.0.0 because there was no reason not to; I consider the code to be feature-complete, I am reasonably certain that everything works as intended, and I have no plans to break the API in the foreseeable future.
I intend to follow <a href="https://semver.org/">SemVer</a> for future releases; any breaking changes will come with a major version bump.</p>
<p>The main thing I’d still like to add is pre-built wheels for Windows; if you’re a Windows-loving Pythonista that can help, please stop by the <a href="https://github.com/caketop/python-starlark-go">GitHub repo</a> and say hello!</p>
<style>
        
      </style>]]></content:encoded>
            <category>python</category>
            <category>starlark</category>
            <category>go</category>
            <category>python-starlark-go</category>
            <category>starlark-go</category>
            <category>pypi</category>
        </item>
        <item>
            <title><![CDATA[python-starlark-go 0.1.2 is now available]]></title>
            <link>https://jordemort.dev/blog/python-starlark-go-0.1.2/</link>
            <guid>https://jordemort.dev/blog/python-starlark-go-0.1.2/</guid>
            <pubDate>Fri, 13 Jan 2023 02:45:05 GMT</pubDate>
            <description><![CDATA[I heard you like Python]]></description>
            <content:encoded><![CDATA[<p><img src="https://repository-images.githubusercontent.com/481312275/48b40583-d3a6-432a-9165-eaf725f7812d" alt="python-starlark-go banner" title="python-starlark-go banner"></p>
<p>Version <a href="https://github.com/caketop/python-starlark-go/releases/tag/v0.1.2">0.1.2</a> of <a href="https://github.com/caketop/python-starlark-go">python-starlark-go</a> is now <a href="https://pypi.org/project/starlark-go/">available on PyPI</a>.</p>
<p><code>python-starlark-go</code> provides Python bindings for <a href="https://github.com/google/starlark-go">starlark-go</a>, which allows you to embed a Starlark interpreter into your Python interpreter.</p>
<p>Starlark is a dialect of Python designed for hermetic execution and deterministic evaluation. That means you can run Starlark code you don’t trust without worrying about it being able access any data you did not explicitly supply to it, and that you can count on the same code to always produce the same value when used with the same input data.</p>
<p>Aside from the usual smattering of updated dependencies, the main feature in the new release is pre-built wheels for <code>aarch64</code> on Linux and <code>universal2</code> on macOS (which includes both aarch64 and x86_64).
This is brought to you through the <a href="https://github.com/caketop/python-starlark-go/pull/120">noble efforts</a> of <a href="https://www.cad.cx/">Colin Dean</a>, who is python-starlark-go’s first contributor!</p>
<style>
        
      </style>]]></content:encoded>
            <category>python</category>
            <category>starlark</category>
            <category>go</category>
            <category>python-starlark-go</category>
            <category>starlark-go</category>
            <category>pypi</category>
        </item>
    </channel>
</rss>