<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Craftip on Blog</title><link>https://jurek.io/tags/craftip/</link><description>Recent content in Craftip on Blog</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><lastBuildDate>Mon, 10 Aug 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://jurek.io/tags/craftip/index.xml" rel="self" type="application/rss+xml"/><item><title>From Github Actions to Woodpecker - Rust GUI cross compilation</title><link>https://jurek.io/posts/cross-compilation-cicd/</link><pubDate>Mon, 10 Aug 2026 00:00:00 +0000</pubDate><guid>https://jurek.io/posts/cross-compilation-cicd/</guid><description>&lt;p&gt;I am building a small GUI application called CraftIP. Minecraft players can use it to bypass the NAT using an online proxy hosted by me. In other words, it makes it easy to make a local Minecraft server public to simplify playing with friends. The program itself is built with Rust and &lt;a href="https://egui.rs"&gt;Egui&lt;/a&gt;, which is a simple-to-use immediate mode GUI library.&lt;/p&gt;
&lt;center&gt;
&lt;figure&gt;
&lt;picture&gt;
&lt;source
srcset="https://jurek.io/posts/cross-compilation-cicd/craftip-ui_hu_d0002a5ac9e29046.avif"
type="image/avif"
width="556"
height="500"
&gt;
&lt;source
srcset="https://jurek.io/posts/cross-compilation-cicd/craftip-ui_hu_a65fd2fe25dc58e3.webp"
type="image/webp"
width="556"
height="500"
&gt;
&lt;img
src="https://jurek.io/posts/cross-compilation-cicd/craftip-ui_hu_a65fd2fe25dc58e3.webp"
alt="The CraftIP application"
width="556"
height="500"
loading="lazy"
decoding="async"
style="max-width: 100%; height: auto;"
&gt;
&lt;/picture&gt;&lt;figcaption style="margin-top: -35px;"&gt;
&lt;h4&gt;Fig. 1:&lt;/h4&gt;
&lt;p&gt;How CraftIP looks like&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/center&gt;
&lt;p&gt;Previously on GitHub, I used macOS and Windows runners to compile the software. After switching to Forgejo/Codeberg, I needed an alternative. As a CI/CD pipeline I choose &lt;a href="https://woodpecker-ci.org/"&gt;Woodpecker&lt;/a&gt;, a super simple CI solution, that does cloning the repo and run Docker containers. Nothing more.&lt;/p&gt;
&lt;p&gt;After some research I found two promising projects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/rust-cross/cargo-zigbuild"&gt;cargo zigbuild&lt;/a&gt; for macOS builds&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rust-cross/cargo-xwin"&gt;cargo xwin&lt;/a&gt; for Windows builds&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="windows-packaging"&gt;Windows packaging&lt;/h1&gt;
&lt;p&gt;To cross compile a GUI program for Windows, it needs to be link against system libraries. Luckily, the program &lt;a href="https://github.com/Jake-Shadle/xwin"&gt;xwin&lt;/a&gt; does the trick by downloading the Windows system libraries, to be able to link against them.&lt;/p&gt;
&lt;h2 id="building-for-windows"&gt;Building for Windows&lt;/h2&gt;
&lt;p&gt;To speed up the build process, I packaged the Windows system libraries and headers in a &lt;a href="https://codeberg.org/craftip/builder/src/commit/455c9ffdb06d3d855224db2f488da21f79afdd59/Dockerfile.windows"&gt;Docker container&lt;/a&gt;, which can then be used to cross-compile the Rust application for Windows x86 (32 and 64 bit).&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://codeberg.org/craftip/craftip/src/commit/dbede34c32b8fcad2857b5069e8e4e777bd18ae7/.woodpecker/compile-windows.yml"&gt;resulting Windows compilation pipeline&lt;/a&gt; becomes super simple:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;when&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;push&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;branch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;main&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ARCH&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;i686-pc-windows-msvc&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;x86_64-pc-windows-msvc&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;build&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;codeberg.org/craftip/builder.windows:latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;CARGO_TERM_COLOR&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;always&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; cargo xwin build --target ${ARCH} --release --bin client-gui&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;a href="https://github.com/rust-cross/cargo-xwin"&gt;&lt;code&gt;cargo xwin&lt;/code&gt;&lt;/a&gt; command sets all the required environment variables for the compilation process.&lt;/p&gt;
&lt;h2 id="windows-exe-logo"&gt;Windows .exe logo&lt;/h2&gt;
&lt;p&gt;To set the correct icon in the &lt;code&gt;.exe&lt;/code&gt; file, the library &lt;a href="https://docs.rs/winresource/"&gt;&lt;code&gt;winresource&lt;/code&gt;&lt;/a&gt; was added to the dependencies in &lt;code&gt;Cargo.toml&lt;/code&gt; &lt;a href="https://codeberg.org/craftip/craftip/src/commit/dbede34c32b8fcad2857b5069e8e4e777bd18ae7/client-gui/Cargo.toml#L41-L49"&gt;like so&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;build-dependencies&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;winresource&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;0.1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;package&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;winresource&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;FileDescription&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;CraftIP&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;ProductName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;CraftIP&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;OriginalFilename&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;CraftIP.exe&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;LegalCopyright&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Copyright © 2026&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;By adding &lt;a href="https://codeberg.org/craftip/craftip/src/commit/dbede34c32b8fcad2857b5069e8e4e777bd18ae7/client-gui/build.rs#L29-L34"&gt;these lines&lt;/a&gt; in the &lt;code&gt;build.rs&lt;/code&gt; file, the correct icon gets assigned to the &lt;code&gt;.exe&lt;/code&gt; file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rust" data-lang="rust"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;::&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;winresource&lt;/span&gt;::&lt;span class="n"&gt;WindowsResource&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&amp;gt; &lt;span class="nc"&gt;io&lt;/span&gt;::&lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;::&lt;span class="n"&gt;var_os&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;CARGO_CFG_WINDOWS&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;is_some&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;WindowsResource&lt;/span&gt;::&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// This path can be absolute, or relative to your crate root.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;../build/resources/logo-win.ico&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;As a result you get two binaries, one for 32bit Windows for legacy reasons and one for 64 bit Windows.&lt;/p&gt;
&lt;center&gt;
&lt;figure&gt;
&lt;picture&gt;
&lt;source
srcset="https://jurek.io/posts/cross-compilation-cicd/CraftIP.exe_hu_ae2cd50aa9d9839e.avif"
type="image/avif"
width="235"
height="152"
&gt;
&lt;source
srcset="https://jurek.io/posts/cross-compilation-cicd/CraftIP.exe_hu_d921c2f5451ddbd0.webp"
type="image/webp"
width="235"
height="152"
&gt;
&lt;img
src="https://jurek.io/posts/cross-compilation-cicd/CraftIP.exe_hu_d921c2f5451ddbd0.webp"
alt="CraftIP.exe on windows"
width="235"
height="152"
loading="lazy"
decoding="async"
style="max-width: 100%; height: auto;"
&gt;
&lt;/picture&gt;&lt;figcaption &gt;
&lt;h4&gt;Fig. 2:&lt;/h4&gt;
&lt;p&gt;The CraftIP.exe on Windows&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/center&gt;
&lt;h1 id="macos"&gt;macOS&lt;/h1&gt;
&lt;p&gt;macOS is a little more complex than that since both the SDK as well as a special linker are required. Luckily, the &lt;a href="https://github.com/rust-cross/cargo-zigbuild/"&gt;cargo-zigbuild&lt;/a&gt; project works on solving both of these issues.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="cp"&gt;&amp;amp;macos_archs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;x86_64-apple-darwin aarch64-apple-darwin universal2-apple-darwin&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;build&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;codeberg.org/craftip/builder.macos:latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;MACOS_ARCHS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;*macos_archs&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;CARGO_TERM_COLOR&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;always&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; for target in $MACOS_ARCHS; do
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; cargo zigbuild --release --target $target --bin client-gui --manifest-path client-gui/Cargo.toml
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; done&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;build dmg&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;codeberg.org/jurek/libdmg-hfsplus:latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; export DMG_OUTPUT_PATH=&amp;#34;target/CraftIP.dmg&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; sh ./build/build-mac.sh&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Since &lt;code&gt;cargo zigbuild&lt;/code&gt; only creates binaries, these binaries have to be encapsulated in a nice looking &lt;code&gt;.app&lt;/code&gt; folder. This &lt;code&gt;.app&lt;/code&gt; folder can then be distributed using &lt;code&gt;.dmg&lt;/code&gt; files, which behave like a &lt;code&gt;.iso&lt;/code&gt; but with some macOS specific features.&lt;/p&gt;
&lt;p&gt;Creating a &lt;code&gt;.app&lt;/code&gt; file is relatively straightforward:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ tree CraftIP.app
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;CraftIP.app
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── Contents
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├── Info.plist
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├── MacOS
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ └── CraftIP
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └── Resources
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └── logo.icns
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Info.plist&lt;/code&gt; contains general information about the binary as well as which icon should be used&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CraftIP&lt;/code&gt; is the binary itself (universal aarch64 and x86_64)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;logo.icns&lt;/code&gt; is the logo, as which the &lt;code&gt;CraftIP.app&lt;/code&gt; folder will look like&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All this is created with a &lt;a href="https://codeberg.org/craftip/craftip/src/commit/dbede34c32b8fcad2857b5069e8e4e777bd18ae7/build/build-mac.sh"&gt;little shell script&lt;/a&gt;, but it is nothing special.&lt;/p&gt;
&lt;h2 id="generating-dmgs"&gt;Generating DMG&amp;rsquo;s&lt;/h2&gt;
&lt;p&gt;To create the &lt;code&gt;.dmg&lt;/code&gt; two more things are required:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a &lt;strong&gt;DMG encoder&lt;/strong&gt; that creates the dmg and compresses it&lt;/li&gt;
&lt;li&gt;a &lt;code&gt;.DS_Store&lt;/code&gt; file that makes the DMG look nice (background picture, initial size of the window, placement of the folders in the DMG window)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As a .DS_Store file, I decided to just use one that I had generated before on MacOS.&lt;/p&gt;
&lt;p&gt;To generate the DMG volume on Linux, I used the program &lt;a href="https://linux.die.net/man/1/genisoimage"&gt;&lt;code&gt;genisoimage&lt;/code&gt;&lt;/a&gt;. This is able to create a dmg file that works on macOS.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;genisoimage -D &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -V &lt;span class="s2"&gt;&amp;#34;CraftIP Installer&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -no-pad &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -R -uid &lt;span class="m"&gt;0&lt;/span&gt; -gid &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -hfs &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -o &lt;span class="s2"&gt;&amp;#34;/tmp/CraftIP.uncompressed.dmg&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/tmp/dmg&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Unfortunately however, this dmg file is not compressed yet. For this, I used &lt;a href="https://github.com/fanquake/libdmg-hfsplus"&gt;&lt;code&gt;libdmg-hfsplus&lt;/code&gt;&lt;/a&gt; which does the job.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dmg &lt;span class="s2"&gt;&amp;#34;/tmp/CraftIP.uncompressed.dmg&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/tmp/CraftIP.dmg&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;/tmp/CraftIP.dmg&lt;/code&gt; is now the final compressed dmg for distribution.&lt;/p&gt;
&lt;p&gt;For further details, check out the &lt;a href="https://codeberg.org/craftip/craftip/src/commit/dbede34c32b8fcad2857b5069e8e4e777bd18ae7/build/build-mac.sh"&gt;shell script&lt;/a&gt; I use for CraftIP.&lt;/p&gt;
&lt;h3 id="permissions-using-genisoimage"&gt;Permissions using &lt;code&gt;genisoimage&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Initially I was using the &lt;code&gt;-r&lt;/code&gt; flag, which according to the documentation clears all write bits. This is problematic however, since then the &lt;code&gt;.app&lt;/code&gt; folder becomes read-only, requiring the root-permissions to delete the &lt;code&gt;.app&lt;/code&gt; folder from the &lt;code&gt;/Applications&lt;/code&gt; folder in the future.&lt;/p&gt;
&lt;center&gt;
&lt;figure&gt;
&lt;picture&gt;
&lt;source
srcset="https://jurek.io/posts/cross-compilation-cicd/craftip-uninstall-permission_hu_9e541827fc9b19d0.avif"
type="image/avif"
width="273"
height="300"
&gt;
&lt;source
srcset="https://jurek.io/posts/cross-compilation-cicd/craftip-uninstall-permission_hu_ee31c1ee8e902896.webp"
type="image/webp"
width="273"
height="300"
&gt;
&lt;img
src="https://jurek.io/posts/cross-compilation-cicd/craftip-uninstall-permission_hu_ee31c1ee8e902896.webp"
alt="CraftIP permission problem"
width="273"
height="300"
loading="lazy"
decoding="async"
style="max-width: 100%; height: auto;"
&gt;
&lt;/picture&gt;&lt;figcaption style="margin-top: -35px;"&gt;
&lt;h4&gt;Fig. 3:&lt;/h4&gt;
&lt;p&gt;The error you get when trying to uninstall the app&lt;/p&gt;
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/center&gt;
&lt;p&gt;In the case of CraftIP this is problematic, since the Application self-updates by replacing the binary with the newer version. This stops working, if the whole folder is read-only.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ ls -l /Applications/CraftIP.app/Contents/MacOS
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;total &lt;span class="m"&gt;70424&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-r-xr-xr-x@ &lt;span class="m"&gt;1&lt;/span&gt; jurek admin &lt;span class="m"&gt;36056144&lt;/span&gt; Jul &lt;span class="m"&gt;10&lt;/span&gt; 12:08 CraftIP
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Using the &lt;code&gt;-R -uid 0 -gid 0&lt;/code&gt; flags instead, fixes the issue. The write permissions are kept. When copying the &lt;code&gt;CraftIP.app&lt;/code&gt; to the &lt;code&gt;/Applications&lt;/code&gt; folder, they will persist so that the user can uninstall the application without root permissions.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ ls -l /Applications/CraftIP.app/Contents/MacOS
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;total &lt;span class="m"&gt;70272&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-rwxr-xr-x@ &lt;span class="m"&gt;1&lt;/span&gt; jurek admin &lt;span class="m"&gt;35977424&lt;/span&gt; Jul &lt;span class="m"&gt;27&lt;/span&gt; 17:50 CraftIP
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id="takeaway"&gt;Takeaway&lt;/h1&gt;
&lt;p&gt;Windows and macOS applications can be cross-compiled and packaged in Docker containers. This is extremely valuable, since these containers can be used far in the future and continue to work. In theory, this should require less maintenance than using native runners that have to be constantly updated.&lt;/p&gt;
&lt;p&gt;Github actions can be fully replaced with fully self-hosted Woodpecker CI that can be hosted everywhere.&lt;/p&gt;</description></item></channel></rss>