Skip to content
lava6666 edited this page Oct 21, 2022 · 55 revisions

This page will give some help and rules for working on porting Crystal to windows. Ongoing efforts will be tracked and coordinated in #5430.

To compile Crystal for Windows, you need to cross-compile from a different platform, one supported by Crystal. This assumes you have already installed Crystal there.

Note some convenient options, unless you want to run both systems natively:

  • Windows 10 host system and Windows Subsystem for Linux to run Ubuntu (18.04 recommended).
  • Linux or macOS host system with a virtual machine running Windows.

Setting up Crystal to cross-compile for Windows

Installing dependencies

C++ build tools

Download Build Tools for Visual Studio 2019 and install it, with "C++ build tools" and "C++ ATL" (LLVM requires the latter).

Either open it directly and set it up like in the screenshot or just run this command (keep the terminal open until it finishes):

.\vs_buildtools.exe --passive --norestart --wait --add Microsoft.VisualStudio.Workload.VCTools --add Microsoft.VisualStudio.Component.VC.ATL --includeRecommended

To use these tools (like cl.exe), one needs to run from x64 Native Tools Command Prompt for VS 2019 (find it in the Start menu). This is assumed from here onwards whenever a CMD (> ) shell is involved.

You can also turn any Command Prompt session into such a special one by running "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Auxiliary\Build\vcvarsall.bat" in it.

Either way, you can then turn this CMD prompt into a PowerShell prompt, if wanted, by running powershell.

CMake

Download latest CMake (Windows win64-x64 Installer) and install it. In the installer, check the box for adding it to current user's PATH.

Directory

This is a good time to prepare a directory on Windows where all subsequent steps will happen, henceforth referred to as "C:\crystal".

Libraries

Run the following (can be pasted directly into a PowerShell window) to build libraries needed for Crystal runtime:

# Download libgc
iwr https://github.com/ivmai/bdwgc/archive/2fd48a92b8bff3afb64b3528ad78b63f033f0436.zip -OutFile bdwgc.zip
Expand-Archive bdwgc.zip -DestinationPath .
mv bdwgc-* bdwgc

# Download libatomic_ops
iwr https://github.com/ivmai/libatomic_ops/archive/v7.6.10.zip -OutFile libatomic_ops.zip
Expand-Archive libatomic_ops.zip -DestinationPath .
mv libatomic_ops-* bdwgc\libatomic_ops

# Build libgc
cd bdwgc
cmake . -DBUILD_SHARED_LIBS=OFF -Denable_large_config=ON -DCMAKE_POLICY_DEFAULT_CMP0091=NEW -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded
cmake --build . --config Release
cd ..

# Download libpcre
iwr https://cs.stanford.edu/pub/exim/pcre/pcre-8.43.zip -OutFile pcre.zip
Expand-Archive pcre.zip -DestinationPath .
mv pcre* pcre

# Build libpcre
cd pcre
cmake . -DBUILD_SHARED_LIBS=OFF -DPCRE_SUPPORT_UNICODE_PROPERTIES=ON -DPCRE_SUPPORT_JIT=ON -DPCRE_STATIC_RUNTIME=ON
cmake --build . --config Release
cd ..

mv pcre\Release\pcre.lib .
mv bdwgc\Release\gc.lib .

# Download mpir
iwr https://github.com/BrianGladman/mpir/archive/d9c9a842be6475bef74324f367ce2c5a78c55d06.zip -OutFile mpir.zip
Expand-Archive mpir.zip -DestinationPath .
mv mpir* mpir

# Build mpir
cd mpir
MSBuild.exe /p:Platform=x64 /p:Configuration=Release /p:DefineConstants=MSC_BUILD_DLL ".\msvc\vs19\lib_mpir_gc\lib_mpir_gc.vcxproj"
cd ..

mv mpir\lib\x64\Release\mpir.lib .

# Download libxml2
iwr https://github.com/GNOME/libxml2/archive/a230b728f1289dd24c1666856ac4fb55579c6dfb.zip -OutFile libxml2.zip
Expand-Archive libxml2.zip -DestinationPath .
mv libxml2* libxml2

# Build libxml2
cd libxml2
cmake . -DBUILD_SHARED_LIBS=OFF -DLIBXML2_WITH_HTTP=OFF -DLIBXML2_WITH_FTP=OFF -DLIBXML2_WITH_TESTS=OFF -DCMAKE_POLICY_DEFAULT_CMP0091=NEW -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DCMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH=OFF
cmake --build . --config Release
cd ..

mv libxml2\Release\xml2.lib .

# Download libyaml
iwr https://github.com/yaml/libyaml/archive/0.2.4.zip -OutFile libyaml.zip
Expand-Archive libyaml.zip -DestinationPath .
mv libyaml* libyaml

# Build libyaml
cd libyaml
cmake . -DCMAKE_POLICY_DEFAULT_CMP0091=NEW -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DCMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH=OFF
cmake --build . --config Release
cd ..

mv libyaml/Release/yaml.lib .

# Download libz
iwr https://github.com/madler/zlib/archive/v1.2.11.zip -OutFile zlib.zip
Expand-Archive zlib.zip -DestinationPath .
mv zlib* zlib

# Build libz
cd zlib
cmake . -DCMAKE_POLICY_DEFAULT_CMP0091=NEW -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded -DCMAKE_FIND_USE_SYSTEM_ENVIRONMENT_PATH=OFF
cmake --build . --config Release
cd ..

mv zlib/Release/zlibstatic.lib z.lib

(If you're pasting commands, don't forget to press Return after the last line, as it likely didn't execute.)

This should have created the *.lib files (static libraries) in the current folder; the rest can be deleted.

The above instructions are adapted from the continuous testing setup, feel free to consult it directly.

Hello World

To compile your first program, start by running the following on Linux / macOS:

$ echo 'puts "Hello world!"' > hello_world.cr
$ bin/crystal build --cross-compile --target x86_64-pc-windows-msvc hello_world.cr

The compiler should emit an object file (hello_world.obj) and print a suggested linker command. The object file needs to be copied onto a Windows machine, and put into the same directory that the libraries from before are in. After that, the linker command needs to be run there, from x64 Native Tools Command Prompt for VS 2019:

> cd C:\crystal
> cl hello_world.obj "/Fehello_world" pcre.lib gc.lib advapi32.lib legacy_stdio_definitions.lib libcmt.lib
> .\hello_world.exe
Hello world!

Running Crystal compiler on Windows (bootstrapping)

Building LLVM

Download Python (latest version) as a prerequisite and install it.

Download LLVM source code (this was tested with LLVM 10.0.0) and extract it as llvm directory. Here's a shortcut for that, using PowerShell and 7-Zip:

iwr https://github.com/llvm/llvm-project/releases/download/llvmorg-10.0.0/llvm-10.0.0.src.tar.xz -OutFile llvm.tar.xz
& "C:\Program Files\7-Zip\7z.exe" x llvm.tar.xz
& "C:\Program Files\7-Zip\7z.exe" x llvm.tar
move llvm-* llvm

Go into the source directory and build it:

cd llvm
cmake . -DBUILD_SHARED_LIBS=OFF -Thost=x64 -DLLVM_TARGETS_TO_BUILD="X86" -DLLVM_USE_CRT_RELEASE=MT
cmake --build . --config Release

Building Crystal for Windows

This assumes you have Crystal source code on both the host system and on Windows. That can work nicely with a shared directory (on WSL you can use "/mnt/c/crystal"). Importantly, the files need to be put directly into "C:\crystal" rather than a subdirectory "C:\crystal\crystal". You may need to move all files one level up.

On the host system you will also need to run make deps first, to get llvm_ext built.

Cross-compile the compiler:

$ LLVM_TARGETS=X86 bin/crystal build --cross-compile --target x86_64-pc-windows-msvc src/compiler/crystal.cr -Dwithout_playground

Again, a suggested linker command will be printed, which you need to run on Windows after copying crystal.obj.

But before we get to that, also compile Crystal's LLVM extensions:

> cl /MT /c src\llvm\ext\llvm_ext.cc -I llvm\include /Fosrc\llvm\ext\llvm_ext.obj

The linker command actually needs significant modifications to work:

> cl crystal.obj /Fecrystal src\llvm\ext\llvm_ext.obj llvm\Release\lib\LLVM*.lib pcre.lib gc.lib advapi32.lib legacy_stdio_definitions.lib libcmt.lib /F10000000

In case of linking errors you may need to delete the file llvm\Release\lib\LLVM-C.lib.

Building an example fully on Windows

> set CRYSTAL_PATH=src
> set LIB=%LIB%;C:\crystal
> set TERM=dumb
> crystal.exe build hello.cr

Using Windows Crystal to build Crystal

> set LLVM_CONFIG=C:\crystal\llvm\Release\bin\llvm-config.exe
> crystal.exe build src/compiler/crystal.cr -Di_know_what_im_doing -Dwithout_playground --link-flags=/STACK:10000000 -ocrystal2
> crystal2.exe build src/compiler/crystal.cr -Di_know_what_im_doing -Dwithout_playground --link-flags=/STACK:10000000 -ocrystal3

Porting guidelines

Just match namings 1:1 for types, functions and struct fields as much as possible. If Windows uses UPPERCASE, then use UPPERCASE, if it uses CamelCase then use CamelCase, and so on. The only exception is lowercased type names, which must be transformed. Habit in bindings is to CamelCase them (e.g. size_t -> SizeT).

Binding functions can start with an uppercase letter since some versions back, so LibC.GetTimeZoneInformation or LibC.LLVMDisposeModule work nicely.