Holochain Forum

How to Ship your hApp on Windows

Overview

As some of you may know, I have been working on bringing the SnapMail app to life on Holochain, on behalf of Harris-Braun Enterprises.
I have succeeded in creating releases of the SnapMail happ, which are downloadable as pre-packaged Mac, Linux and Windows desktop applications, by following Connor’s article on How To Ship Your hApp. But his article does not cover Windows, so here I am to explain the extra work that is required to have Windows support.

A Windows package will only work on Windows machines which have Windows Subsystem for Linux 2 (WSL2) installed as Holochain doesn’t work natively on Windows.
WSL2 has been rolled out in the May 2020 update, which is Windows 10 version 2004.
According to this news, Microsoft backported WSL2 to prior versions Windows 10 which brings WSL2 to the Windows 10 May 2019 and November 2019 updates. This can be helpful for setups who can’t upgrade to the latest Windows.

So for your hApp to work on Windows, the users must first make sure they have a WSL2 installed and a working Ubuntu distro.

Code changes

(For those who prefer looking at code to understand, I’d suggest loading the snapmail-release repo in your favorite IDE and look for all occurances of win32 and wsl).

Basically the trick is to add hc & holochain executables that are built for Ubuntu to your bundle in a known subfolder, and to add the path to that folder to Electron’s process.env.PATH. That way, you don’t need to build and setup holochain within Ubuntu. Have your happ spawn holochain within WSL2 with the updated process.env. That way holochain is added to Ubuntu’s PATHS and can be called from any location like a normal install! Magic!

The code for setting PATH is:

/** Add Holochain bins to PATH for WSL */
const BIN_DIR = "bin";
const BIN_PATH = path.join(__dirname, BIN_DIR);
if (process.platform === "win32") {
  process.env.PATH += ';' + BIN_PATH;
}

In your code, when spawning holochain as a subprocess, you need to check if you are running on Windows and change the arguments of the spawn call so it calls wsl instead of hc and holochain directly:

In other words, instead of calling:
holochain -c config.toml
you have to call:
cmd.exe /c wsl holochain -c config.toml

Here is the code I used:

  // spawn "holochain"
  let bin = HOLOCHAIN_BIN;
  let args = ['-c', wslPath(CONDUCTOR_CONFIG_PATH)];
  if (process.platform === "win32") {
    bin = process.env.comspec;
    args.unshift("/c", "wsl", HOLOCHAIN_BIN);
  }
  // spawn subprocess
  g_holochain_proc = spawn(bin, args, { ... })

Notice the call to wslPath(). This is because we need to change the paths we use from a windows point of view to a WSL/linux one. Because once calling wsl you are in that space and must give paths compatible for that system, i.e change C:\Users\alex to /mnt/c/Users/alex.
Luckily for us, WSL provides a path converter that you can call by doing wsl wslpath -a <path>.
This is what my wslPath() function does. And will make sure to not change the path if you are not on Windows.

Also, remember that the conductor config is generated at first launch of the electron happ, so make sure to use wslPath() when generating the conductor config since it will be used by holochain within WSL2. Example:

  // replace dna
  newConductorConfig = newConductorConfig.replace(
    /file = 'dna'/g,
    `file = "${wslPath(newDnaFilePath)}"`
  );

Finally, I encountered some problems reading the DNA hashes that are stored in their own file. Since the line endings are different on Windows, a line ending character was being inserted in the DNA hash when reading it from disk. The workaround I found was to add a regex match when reading the file:

  let snapmailDnaHash = fs.readFileSync(path.join(__dirname, SNAPMAIL_DNA_HASH_FILE)).toString();
  // Make sure there is no trailing end of line
  let regex = /(Qm[a-zA-Z0-9]*)/;
  let match = regex.exec(snapmailDnaHash);
  snapmailDnaHash = match[1];

We should now be all set to have a working Windows build!

For added robustness I have added a killAllWsl() function, that will kill any pending holochain process running within WSL2. This function is called at start and shutdown of the electron happ.
I recommended this because if a crash occurs in your happ, it will properly kill the WSL call but not the spawned holochain process running within WSL2.
So to avoid previous crashes from crippling the user’s system we have to clean things up manually. This is done by calling wsl killall holochain.
Be careful though that this will also kill any holochain process that has been started by any other happ. It would be best to uniquely name the holochain executable that you package with your happ.

Building & packaging

To package a windows bundle, add this script to your package.json:
"package-windows": "electron-packager . Snapmail --platform=win32 --out=build --overwrite",

Make sure to add the hc and holochain linux binaries at the correct location before running the script.

As I work only on Windows, I have not tested if cross-compilation of the windows bundle works on other systems. On my side, I did manage to build a working Linux package from Windows and did not have a working one for Mac. I had to build the Mac package from macOS to have it working.

I do not have CI nor nix setup for Windows yet, and will probably not in a near future as for now the focus will be to switch to Holochain RSM!

Anyhow, I hope you found this useful. I will probably add more to it if I figure out better ways to do it and if people have questions or needs help troubleshooting issues.

5 Likes

Wow thanks bro! Really appreciated! :grin::grin::grin::partying_face::partying_face::slightly_smiling_face::blue_heart::heart::innocent:

I have been looking for this for a long time now! :slightly_smiling_face: I created a .net and unity client called HoloNET last year, not sure if you saw that?

I also have been working on the .NET HDK but I ran into similar issues you were having…

It use to work fine when there was a simple windows binary but when they went down the nixos path I couldn’t get it working anymore. The HoloNET and .net hdk both spawn a hc conductor and that stopped working with holonix and wsl2.

Is the code above for electron only?

I think I can adapt parts for .net… you given me new directions for me to explore and helped solve some pathing issues I was having with wsl2.

How you getting on with RSM? Have you got it working in windows yet? I tried last night but the instructions on the repo didn’t work for me.

Any ideas?

Also, do you know why they stopped releasing windows binaries for the conductor etc? Like I said these worked fine before nixos came along… I preferred it how things worked before! Things were much simpler on windows back then! Lol :wink:

Cheers.
D.

P.s. if your interested in getting involved with HoloNET or .net hdk could really do with your help please! Thanks :slightly_smiling_face:

About to start work on porting them to RSM but need to get it working first as I said above! Thanks :slightly_smiling_face:

Haven’t tried with RSM yet. Should probably work the same.
My explanation is for Electron but you can use the same logic and tricks for .Net, Qt… whatever framework you use as a native windows application.
NixOS is incompatible with windows so there is no chance we’ll get windows binaries in the future :frowning: