How to Ship your hApp on Windows

(EDIT: This is out of date as Holochain is native on windows now. I’ll do an update in a reply)

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:

1 Like

I heard there were plans to make NixOS compatible with Windows in future? I REALLY pray they do… would make life a LOT easier for us then! :wink: It has really slowed me down and caused a LOT of issues when they moved to NixOS, it was MUCH easier on Windows before… :slight_smile:

Good information thanks for sharing
Fieldengineer

1 Like

Any updates?

1 Like

Hi FB123,

There would certainly be updates to make on this. The biggest and most important to note would be the deprecated status of Holoscape.

If one were to bundle with Electron with recent versions of Holochain, they would want to refer to GitHub - h-be/acorn: Acorn is a way of organizing intended Goals and Outcomes in tree-like structures. You can think of it as a virtual whiteboard for collaborative planning. and read the ‘technical overview’ section of the README, and furthermore watch the first half of the Acorn sessions in Holochain-in-Action which are a guided tour and collaborative exploration of the Acorn code, which you would want to read to see how it’s now done.
Acorn Technical Deep Dive-Part1-Architect and Design Review-Multi DNA Architect-Electron in UI - YouTube

What is your interest in it? What updates that I didn’t mention would you like to see or hear about?

Oh oops, I prbly misinterpreted this and didn’t totally realize which thread we were on. There may be some updates soon, based on work that is being put into preparing Holochain compatibility for Windows again. It is slightly too soon to say what the results of that work will be or when, but there IS progress.

Ok thanks, yes it was more related to the Windows support - so far I’ve been spinning my wheels with what is currently documented.

The HoloNET or .net hdk that dellams mentioned is of significant interest and I would be able to help out if/where I can.

Updated for 2022-01-01

Overview

Holochain now works natively on Windows. Meaning without having to use WSL2.
So there is now a lot less work to do to have an Electron happ working on Windows.

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).

In your code, when spawning holochain and lair as a subprocess, you just need to check if you are running on Windows and change the path to match windows path format

Here is the code I use for spawning lair:

  // -- Spawn Keystore -- //
  let bin = keystore_bin;
  const lair_dir = winPath(path.join(storagePath, "keystore"))
  let args = ['-d', lair_dir];
  log('info', 'Spawning ' + bin + ' (dirname: ' + CURRENT_DIR + ') | spawnKeystore()');
  const keystore_proc = spawn(bin, args, {
    cwd: CURRENT_DIR,
    detached: false,
    env: {
      ...process.env,
    },
  });

Notice the call to winPath(). Its code is basic:

function winPath(path) {
  if (process.platform !== "win32") {
    return path;
  }
  let fp = path.replace(/\\/g, "\\\\");
  return fp;
}

Also, remember that the conductor config is generated at first launch of the electron happ, so make sure to use winPath() when generating the conductor config.

Building & packaging

To package a windows bundle, I use electron-builder now.
For that install it and add this line to your package.json:
"dist-win" : "electron-builder --win",

Make sure to add the lair-keystore and holochain binaries at the correct location before running the script.

I have a github workflow setup for releasing on all platforms including windows. There is nothing really specific to Windows. Check it out.

2 Likes