How to build for release?

I always feel confused about building with Crystal.
The build methods adopted in GitHub Actions are quite inconsistent.

Code search results (github.com)

What is the appropriate command to build a release version?

  1. Is shards build --production --release --static --no-debug sufficient?
  2. When should I use crystal build?
  3. How to build universal binary for Darwin with GitHub Actions?

There are some Crystal build options like --static, --release, --no-debug, -Dpreview_mt that are not displayed in the shards prompt, but they donā€™t cause any errors. Should I use them? When should I use them?

Reference
crystal-build
shards-build
github-actions
static_linking

1 Like

First of all, shards build always delegate all it unknown options into Crystal.

Then, if not add --no-debug, it always include debug info, add this option will reduce file size significantly.

Add -Dpreview_mt only if you know what you want.

--static not always work for current version Compiler, in fact, AFAIK, it only work when build static binary from alpine docker container, for build a linux static binary or an universal binary for Darwin without use docker, you probably want to check Use zig cc as a alternative linker (draft version) - #2 by zw963

you should always use --release when release a binary.

For --production, as said by shards help,

--production                     same as `--frozen --without-development`
1 Like

Working with zig is quite an interesting idea.
Currently I have got an effective workflow that is able to build release versions adapted to all Tier 1 platforms, most Tier 2 platforms and some Tier 3 platforms.
Itā€™s partially relying on qemu and I am glad to find more efficient methods.

1 Like

Nice to see Chinese people using Crystal to make products!

Iā€™m not fighting alone~

1 Like

@Sunrise Hereā€™s some explanation: ā€œto buildā€ means compiling and linking an executable program.

The shards build command makes sure dependencies are installed before building, then it calls crystal build. Itā€™s a mere convenience, and not required at all. Basically it does:

$ shards check || shards install
$ crystal build <target.main> -o bin/<target> <args>

The --production and --frozen options are shardsā€™ options. They tell Shards to not install the development dependencies, and to install the locked dependencies. See shards install --help for more details.

The crystal build command is the command that builds your program. Thereā€™s a plethora of arguments you can pass to customize the build. See crystal build --help for details.

For building a production version of your program, youā€™ll likely want --release, that enables both --single-module and -O3 that will enable the most aggressive optimizations. It also adds the :release compile time flag that may be used to differentiate a development build vs a release build.

By default we only enable debug lines sections, in order to have file:line information in backtraces (exceptions and debuggers). This can be disabled with --no-debug. On the opposite, you can enable full debug info with --debug to be able to inspect variables in debuggers. I recommend to use neither unless you need to debug, in which case --debug will be useful.

The --static option will compile a static binary, that doesnā€™t need any shared library to run (theyā€™re embedded right into the executable). It can be useful but can be tricky.

The -Dpreview_mt compile time flag enables a multi-threaded environment (by default crystal programs are single threaded). It makes the stdlib thread safe, and allows to run fibers in parallel in multiple threads. That being said itā€™s still in preview, community shards arenā€™t necessarily thread safe, and itā€™s under heavy refactor in RFC 0002 to finally make it official.

To build a program for production, you can configure a target in shard.yml then call:

$ shards build --production --frozen <target> --release

Or decompose in two calls, which is less confusing:

$ shards install --production --frozen
$ crystal build src/myapp.cr -o bin/myapp --release

Sadly, I canā€™t help with macOS universal builds.

11 Likes

Thank you for the detailed explanation.

I finally found a way to create fat binary for macOS on any platform and that is konoui/lipo. I have added it to my workflow and it works as expected. :smiling_face_with_three_hearts:

1 Like

The most awesome answer I have ever seen!

We need add this answer into somewhere in official book!

ping @straight-shoota

@ysbaddaden , Iā€™m not quite clear about this part, you said enable --debug for used in debuggers, so, debuggers in this case, reference tools like lldb, gdb, right?

Because i saw no different when print the file:line backtrace when build with default or with --debug, except a slight increase in file size,

 ā•°ā”€ $  file bin/{website,website_no_debug,website_debug}
bin/website:          ELF 64-bit LSB executable, x86-64, version 1 (SYSV), static-pie linked, with debug_info, not stripped
bin/website_no_debug: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), static-pie linked, with debug_info, not stripped
bin/website_debug:    ELF 64-bit LSB executable, x86-64, version 1 (SYSV), static-pie linked, with debug_info, not stripped

 ā•°ā”€ $ ls -alh bin/{website,website_no_debug,website_debug}
Permissions Size User  Date Modified Name
.rwxr-xr-x   35M zw963 16 minutes    bin/website*
.rwxr-xr-x   37M zw963 25 minutes    bin/website_debug*
.rwxr-xr-x   33M zw963 16 minutes    bin/website_no_debug*

only --no-debug missing the file:line info in backtrace, so, I wonder when the --debug option is used?


One more question, thanks

what exactly --frozen used for? shouldnā€™t shards install install the precise version contained in the lock file? what I mean is, shards update is the process that upgrades shard.lock, right?

The most awesome answer I have ever seen!

We need add this answer into somewhere in official book!

@straight-shoota , @beta-ziliani , Isnā€™t my suggestion not good?

If there is a shard.lock file, --frozen will work, but in the case of a lib project (not an app), the option will fail when tested against a clean checkout of the project:

$ shards install --frozen
Missing shard.lock

$ echo $?
1

If you do shards install it will install the dependencies indicated in shard.yml and it will generate a .lock file, at which point shards build --frozen (or --production) will not complain about the missing lock file.

Also:

--production implies both --frozen and --without-development, and --release gets forwarded to crystal build to build a release executable.

So:

$ shards build --production <target> --release

Is:

  • Install dependencies if there werenā€™t installed yet
  • Fail if there is no lock file
  • When installing the dependencies, do not install the development ones
  • When building <target>, forward --release argument.

Complicated, but I hope it helps! :sweat_smile:

Cheers.

1 Like

I mean exactly what I said: --no-debug allows you to inspect variables in debuggers.

--no-debug allows you to inspect variables in debuggers.

Sorry, still not clear, what is the debugger stand for in this Crystal context? debugger when used in Interpreterļ¼Ÿ or tools like gdb/lldb?

I mean an actual debugger like gdb or WinDBG because we build an executable with --debug The interpreter doesnā€™t compile code, but interprets it, and thus already has access to all the memory space (it owns it).

1 Like