This is the story of one programmer Quietly Going Insane With Tools & Automation:
Quietly Going Insane With Tools & AutomationStrings, Actually, Do Not ExistSuffering: The First Two Weeks Of ZigAs a programmer, I sometimes go a bit off the deep end.
This is a story about me doing just that, this week.
The Triggering Event
Unreal has this awesome feature called Live Coding
which will hot reload C++ compilation changes into the Unreal Editor without having to restart the editor.
Anyone who dislikes this feature has not considered the alternative.
But it is a bit janky, as I describe in Actually Writing Some Damn Code ļ»æ:
Compiles green, I save all, all good.
Restart Unreal:
With the helpful compiler message:
Cool.
Real Cool.
Thatās cool.
Modern Solutions for Modern Problems
My understanding is this is caused by the Live Coding compiler adding layers and layers of my garbage changes until something breaks.
Like a true developer, my first thought is to nuke the problem from orbit.
Such is the recommended fix found on the Unreal Dev Wiki:
Delete a few folders, and we are SO back?
This is the way.
But hold on.
You expect me to manually navigate to my Unreal Project folder and remember which subfolders to delete?
Nah, naaaah.
We gonā automate this.
So Begins the Madness of āDoing it Rightā
Ok so if I were in a sane development environment Iād whip up a bash script that ignorantly nukes the paths in a directory.
But Iām in Windows, because itās a better OS for Unreal. Fight me.
Windows, making poor decisions like using \
(not /
) for folder paths, doesnāt do Bash.
It does Powershell.
I aināt shook.
I have a pretty clear, and simple, use case for this Powershell script.
So easy that Chat Jippity can help convert my echo
to Write-Host
, etc.
But before I can actually write the Powershell script that will delete the folders, I ask myself how Iāll be calling this script.
In OSX/Unix, I have terminal open at all times, ls
'ing through life like a god damn wizard.
This is not something I do in Windows.
I use the GUI like a chump.
Thatās when I have myself an idea:
This is the absolute shambles that is my Windows right-click menu.
The idea is simple:
Further shambolism by adding my script to this right-click menu.
This is actually super easy, it turns out.
We just need to start playing with the Windows Registry.
A Totally Normal Thing To Do: Hacking Registry Keys
As a Windows admin, you should have access to the Registry Editor.
Opening that you should see something like:
Apparently, if you add registry keys here, things show up when you right click in File Explorer:
And presto:
The value for the key command
leads to the path the actual CleanBuild script is in.
But waaaait a minute.
How do I reliably know where the script is?
If I wanted to do this ārightā- as in something I could replicate across multiple computers for some ungodly reason, I need a reliable script path.
Thatās easy, I just need to make an installer script that will:
- Add the registry key
- Put the actual
CleanBuild
script in a reliable path the registry value will resolve to.
Things Are Starting To Run Away From Me: Making a Script to Install a Script
I aināt shook.
This is fine.
Iām learning Powershell. Thatās useful, theoretically.
Iām learning how to interact with the Windows operating system on a deeper level.
This exercise has value, surely.
But Iām also fully cogent that Iām several hours deep into the 15-second manual task of deleting 4 directories in my Unreal Project.
This is fine. This is a totally normal thing to be doing:
## Setup local path to install CleanBuild
$AppData = Get-ItemProperty -Path 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders' -Name 'Local AppData' | Select-Object -ExpandProperty 'Local AppData'
$ToolsPath = 'Tools\CleanBuild\CleanBuild.ps1'
$Path = Join-Path -Path $AppData -ChildPath $ToolsPath
Write-Host "Creating path: [$Path]"
New-Item -Path $Path -ItemType File -Force | Out-Null
if (Test-Path -Path $Path) {
Write-Host "*** [OK]"
}
## Download CleanBuild
Write-Host "Download CleanBuild to [$ToolsPath]"
$CleanBuildUrl = "https://raw.githubusercontent.com/JerkyTreats/tools/main/CleanBuild/CleanBuild.ps1"
$Req = Invoke-WebRequest -Uri $CleanBuildUrl
Set-Content -Path $Path -Value $Req.Content
So I whip together a quick installer script that finds my local windows users AppData
folder and copypasta the contents of my CleanBuild
script to {AppData}\Tools\CleanBuild\CleanBuild.ps1
We then add the registry key that will let me run this script from the right-click context menu:
## Add Script to Right-Click Explorer Menu
$RegPath =
'Registry::HKEY_CLASSES_ROOT\Directory\shell\CleanBuild\command', # Right Click on a folder
'Registry::HKEY_CLASSES_ROOT\Directory\Background\shell\CleanBuild\command' #Right Click in explorer window
$RegName = "(Default)"
$RegValue = $Path
$IconPath = Join-Path -Path $AppData -ChildPath "Tools\CleanBuild\icon.ico"
Write-Host "Adding Command to Right-Click File Context Menu"
Write-Host "Writing [$RegPath]"
foreach ( $rp in $RegPath ) {
New-Item -Path $rp -Force
New-ItemProperty -Path $rp $RegName -Value $RegValue -PropertyType String -Force #| Out-Null
New-ItemProperty -Path $rp Icon -Value $IconPath -PropertyType String -Force
if (Test-Path -Path $RegPath){
Write-Host "*** OK"
}
}
Losing the Plot: Itās OK Iām Learning
Thereās a problem.
Itās small.
Insignificant really:
My script doesnāt have an icon.
It doesnāt matter. Right?
Aināt no one using this but me.
I think it matters.
As the only user of this tool, I think it matters.
Literally 100% of users are upset that the icon is missing.
We must go deeper:
Apparently, if you add a reg key called Icon
with a path to an .ico
file, an icon will appear.
So I went to Midjourney, got it to generate me an icon:
That gives me a .png, but I need an .ico.
Icoās are weird, as each image has layers ranging from 16x16 px to 512x512 or something like that.
I just used some randomly googled png ā ico
converter and used that.
Now I feel like we got an absolute banger on our hands:
But this presents us with a new problem.
Now there are multiple files that this installer needs to download- the script and the ico.
I could just loop through a list of files to download from GitHub.
But letās be real, this whole copypasta-raw-github-data is kinda Fād in the first place.
Yes, it works, but damn dude this isnāt how people do things.
Zipping Multiple Files: Now I Need a GitHub Actions Workflow.
But hey, I still aināt shook.
This is fine.
Weāre just going to zip the ico and script together, download the single file in the installer and unzip to the defined path.
Easy.
Buuuuut now I have a build step for my CleanBuild.
I need to zip the files together.
I need to zip the files every time any file changes.
I could do this manually and just add to git.
But we got into this whole mess by avoiding manual labor.
The whole point of this exercise is to do it right.
The definition of which means no manual work ever again.
Fine. This is fine.
I havenāt done any real work in a week, but this is fine, Iām learning.
I add the .github/workflows
folder and add a build yaml file.
name: CleanBuild
run-name: CleanBuild
on:
push:
branches:
- main
jobs:
GenerateRelease:
permissions: write-all
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v4
- run: |
echo "š” The ${{ github.repository }} repository has been cloned to the runner."
echo "PWD is [ ${{ github.workspace }} ]"
ls -al .
### Create Build Files
- name: Zip CleanBuild
uses: montudor/action-zip@v1
with:
args: zip -qq -r cleanbuild.zip CleanBuild/CleanBuild.ps1 CleanBuild/icon.ico
### Create Tag and Release
- name: Bump version and push tag
uses: anothrNick/[email protected]
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
WITH_V: true
PRERELEASE: false
- uses: "marvinpinto/[email protected]"
with:
repo_token: "${{ secrets.GITHUB_TOKEN }}"
automatic_release_tag: "latest"
files: |
cleanbuild.zip
Weāre doing this ārightā, which means setting up semantic versioning for this damned tool, creating a full Release on push to main, and adding our .zip artifact as a release file.
Which is all cool, and works, buuuut I still feel like mucking with a zip in the first place is jank as hell.
Letās look at this from a prospective users POV:
- I am asking the user to download a random Powershell script
- That script requires elevated privileges as its blowing up the registry and downloading random internet files.
- This is total nonsense.
Thatās not how Windows users do things.
No one is going to download and run random .ps1
files they find on the internet.
However, Windows users will absolutely run a random setup.exe
they find off the internet.
Theyāll click through an install wizard faster than they accept the Terms and Conditions for every service theyāve ever used ever.
Can I wrap my jank ass Powershell script into an .exe?
Youāre god damned right I can.
Figuring out how to make literal windows executables because Iāve gone totally insane.
At this point Iāve accepted the madness.
Iāve become the hatter, and out of my hat Iām going to pull literal .exe
files for CleanBuild.
Itās time to go all the way down, Alice.
The easiest solution is to find some random internet dev who has built a powershell-to-exe converter so that I donāt have to do any real work.
Thereās a few options it seems:
- ps2exe
- NSIS
- iexpress
ps2exe is the exact thing Iām looking for, but executables built with ps2exe are routinely flagged by anti-virus software as malicious actors have used it to build bad things.
NSIS is a whole scripting thing, hosted through SourceForge.
Buuuut SourceForge has a bit of a smell.
Smells like warez, a relic of a long forgotten internet, an internet probably best left forgotten.
iExpress is apparently available on all Windows machines by default. Itās just a wizard to make arbitrary installer wizards.
Iām not sure how Iād automate this as a build step though.
In all, Iām not thrilled with any of these options.
It feels like weāve gone past the point of reasonable jankiness.
The non-jank way to do this is to use a real programming language, build a real executable, like a real programmer would.
This is fine.
Itās not like the Powershell script is doing anything too fancy here.
Basic CRUD work on Windows paths.
Surely there is a maximally degenerate language I can use to build my stupid script.
Zig: The Maximally Degenerate Language to Build Your Stupid Scripts
Gentlemen persons, it is time to learn Zig.
I am choosing Zig after a solid 30 seconds of thought.
Why?
I was able to successfully Bing zig build for windows
and get results for building an exe and adding an ico.
I considered using C++ for this, as it allows me to practice the C++ work I have to do as part of learning Unreal.
But Biden said to avoid C++ because itās not memory safe.
You can tell it didnāt take much to convince me not to use C++.
So why Zig?
Well, you know those insufferable devs on your team constantly proselytizing the virtues of Rust?
Those devs find the Zig devs insufferable.
In other words, if I am to fully lose my mind, I may as well chose the language of maximal degeneracy.
Zig is the way.
Literally Two Weeks Later: I've Done No Work, God is Dead, There Are No Strings
Sooo Iām writing this part a full 2 weeks later, conceding that I may have been a bit hasty with my 30 second decision to learn Zig.
Those four damned folders in my Unreal Project have still not been deleted.
Instead, Iāve invested dozens of hours, amortized over the last 2 weeks, trying to grok basic Zig usage.
It turns out, Zig devs are insufferable because they have all suffered.
Suffered, at some point in their lives, the unforgiving initial learning curve of low-level programming.
There are no strings here, just arbitrary slices of null terminated bytes.
WHAT DOES THAT EVEN MEAN?
Instead Iām stuck trying to coerce [_][] const u8 { "foo", "bar" };
to u16
because windows calls wonāt accept u8 because Microsoft is the one that made bad decisions.
Besides, itās gotten to the point where I figure I can farm additional content out of a dedicated āFirst Impressions of Zigā post.
I get the feeling Zig users will consume any content about Zig.
Gotta farm the engagement where you can get it, folks. The Story Continues: