# Creating fully self-contained and pre-compiled library

**URL:** <https://discourse.julialang.org/t/creating-fully-self-contained-and-pre-compiled-library/131374>\
**Category:** General Usage\
**Created:** [August 5, 2025, 1:40pm UTC](https://discourse.julialang.org/t/creating-fully-self-contained-and-pre-compiled-library/131374 "2025-08-05T13:40:46Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![asprionj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/asprionj/32/6856_2.png) [@asprionj](https://discourse.julialang.org/u/asprionj)\
**Post date:** [August 5, 2025, 1:40pm UTC](https://discourse.julialang.org/t/creating-fully-self-contained-and-pre-compiled-library/131374/1 "2025-08-05T13:40:46Z")

</div>

Hi everyone. We intend to use julia to create a fully self-contained, completely pre-compiled library for integration in a larger software. I did read several posts, watched YouTube, etc. – there’s a lot but also quite scattered information. Also, there’s (for me) still the confusion about PackageCompiler and juliac. Size of the resulting library is not that critical, but smaller is better, of course. So, my questions are:

_[**Edit** : I state the answers to the questions, as found so far, directly here. Will update whenever having found out something more. Also, follow-up questions are added.]_

- What is, as of 2025-08-05, the best way of compiling a standalone library (with C ABI), that does not rely (and thus not include) the julia “runtime”/compiler? (Relying on official releases, no nightly builds etc., and supporting “all” functionality.)
  - _If I understood correctly, the “runtime” (libjulia) is always required, and in order not to have the compiler (LLVM) bundled, the only way is `juliac`._
  - _The workflow, for now, is to call julia on `<julia-root-folder>/share/julia/juliac/juliac.jl` from the command line, providing options and the input .jl file._

- Does a single library (DLL on Windows) result, or does it rely on other libraries that I need to bundle for distribution?
  - _The resulting binary (exe or DLL) is **not** self-contained, many (to most/all) libraries in `<julia-root-folder>/bin` are required._

- _[follow-up]_ Is there a way to identify which ones need to be bundled along the binary to make it work?
- What about packages that rely on third-party binaries? (Relates to the previous point.)
- Is that workflow “officially” documented somewhere?
  - _It seems **not**. Correct?_

- What will change with the release of julia 1.12? (buzzwords `--trim`, `juliac`)
  - _Only with 1.12 (nightly, for now; not rc1), most features of `juliac`, including `--trim` become available._
  - _In the future, `juliac` and `PackageCompiler.jl` should converge (since they “target the same but the former from the command-line, the latter from within julia”)._

- Will the “best way” change in the near future? (So, if possible, would it be better to wait for some weeks, or months, before starting this journey?)
  - _There’s only the way with `juliac` for now, and a nightly build of julia 1.12 is required. Best would be to wait for the official 1.12 release, but these features will retain status “experimental” also at that point._

Thanks for pointing me to the currently right direction; could save me a lot of time searching, reading, and trying. I can also document my “journey” here, or help in updating / extending documentation etc.

* * *

**Just some of the posts and other resources I found, as reference**

- 2018-2020: [Building – and releasing – a fully self-contained Application written in julia - Tooling - Julia Programming Language](https://discourse.julialang.org/t/building-and-releasing-a-fully-self-contained-application-written-in-julia/10418)
- May 2022: [How do I create a binary from .jl files? - New to Julia - Julia Programming Language](https://discourse.julialang.org/t/how-do-i-create-a-binary-from-jl-files/80521)
- Latest PackageCompiler docs on building Apps: [Libraries · PackageCompiler](https://julialang.github.io/PackageCompiler.jl/stable/libs.html)
- YouTube, September 2024: [New Ways to Compile Julia](https://www.youtube.com/watch?v=R0DEG-ddBZA) (which is +/- the same as the JuliaCon’24 talk)
- r/Julia late 2023 to January 2024: [State of Compile to Binary in late 2023? : r/Julia](https://www.reddit.com/r/Julia/comments/17tot82/state_of_compile_to_binary_in_late_2023/)
- Around the same time: [Plans for static compiled binaries? - Internals & Design - Julia Programming Language](https://discourse.julialang.org/t/plans-for-static-compiled-binaries/107413)

* * *

**Edit: Just wanted to say thanks to everyone working on, or otherwise involved in, julia and especially `juliac` and related / enabling stuff!** ❤️‍🔥 Really awesome to have an interactive language / environment to prototype (but running at C-like speed) and then being able to generate a binary (even if it is not as small as if it had been generated using e.g. C or Rust). Folks just have to understand that the former was the initial (and IMO still primary) focus. Still, I do think that the latter (with nice tooling to make binary generation accessible and straightforward) could boost adoption of julia a lot, since it enabled integration of julia-created modules in any other environment, in binary (and thus closed-source) form.

---

<div class="post-metadata">

**Author:** ![mbauman](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mbauman/32/31082_2.png) [@mbauman](https://discourse.julialang.org/u/mbauman)\
**Post date:** [August 5, 2025, 2:05pm UTC](https://discourse.julialang.org/t/creating-fully-self-contained-and-pre-compiled-library/131374/2 "2025-08-05T14:05:39Z")

</div>

To achieve these two goals:

- fully self-contained, completely pre-compiled
- does not rely (and thus not include) the julia “runtime”/compiler

you actually do need `--trim` — even though you don’t care about file size. And `--trim` requires the juliac capabilities in the v1.12 RC.

PackageCompiler.jl pre-v1.12 can also create a dll/dylib/so, but it does not have the tooling to guarantee/enforce that _everything_ gets compiled like `juliac --trim` does. So it bundles the compiler for anything you may have missed.

---

<div class="post-metadata">

**Author:** ![KnutAM](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/knutam/32/37720_2.png) [@KnutAM](https://discourse.julialang.org/u/KnutAM)\
**Post date:** [August 5, 2025, 7:58pm UTC](https://discourse.julialang.org/t/creating-fully-self-contained-and-pre-compiled-library/131374/3 "2025-08-05T19:58:59Z")

</div>

To add to the reference list: The “state of `--trim`” talk from JuliaCon’25: [https://www.youtube.com/watch?v=3o0lAXCa9Wg&t=24710s](https://www.youtube.com/watch?v=3o0lAXCa9Wg&t=24710s)

---

<div class="post-metadata">

**Author:** ![asprionj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/asprionj/32/6856_2.png) [@asprionj](https://discourse.julialang.org/u/asprionj)\
**Post date:** [August 6, 2025, 10:20am UTC](https://discourse.julialang.org/t/creating-fully-self-contained-and-pre-compiled-library/131374/4 "2025-08-06T10:20:18Z")

</div>

Thanks for the hints. Forgot to note that currently I’m developing on Windows (but will later deploy on linux; but want to have a proof-of-concept on Windows first). So I downloaded the portable nightly binaries (from today, 05:06) and tried to compile the hello-world example. That is, just the following in a .jl file:

```julia
function @main(args::Vector{String})::Cint
    println(Core.stdout, "Hello, World!")
    return 0
end

```

Compilation works fine, and I can see that the generated .exe depends on `libjulia.dll` and `libjulia-internal.dll`. So, put these two right next to the .exe such that they are found.

When I run the .exe, it errors out. Could get the following message (when I double-click the .exe instead of calling it from bash/cmd):

 ![error message upon running the juliac-generated .exe](https://global.discourse-cdn.com/julialang/original/3X/4/e/4efa8c523f0e09f9133f47e7844aa4e6c365e311.png)

Sorry for the German. It says that the entrypoint with that very cryptic name cannot be found in `libjulia-internal.dll`. Any ideas what is going wrong?

* * *

**Edit 1** : went deeper into analysing dependencies. `libjulia-internal` depends on `...\mingw64\bin\libstdc++-6`. So, I happened to have mingw64 installed / on the path and was not aware that this was required for `juliac`. For some reasons I had da very old version of it installed (from 2012 or so), but also changing to a recent one (14.2.0) does not solve the problem (but a different entrypoint is not found). For this version, it does not have available:

- `std::__once_functor`
- `std::__get_once_mutex()`
- `std::__set_once_functor_lock_ptr(std::unique_lock<std::mutex>*)`

I then found the sentence “The msys2 libstdc+±6.dll appears to not have the needed symbols while the one distributed with Julia does.” in [this thread](https://discourse.julialang.org/t/issue-with-xgboost-jl-and-libsvm-jl-when-julia-1-8-4/92396/41). So, due to my installation of mingw64 being on the path, these libs are loaded.

If I remove my mingw64 installation from the PATH, four mingw64-libs are not found (`atomic-1`, `gcc_s_seh-1`, `winpthread-1`, `stdc++-6`). If I use those from the `/bin` folder of julia, I get a step further, namely calling the .exe providing console output about what else is missing (funnily I cannot find that these are missing using a dependency analyser on my .exe).

In the end, **I get the .exe for the hello-world example to work** (🙂) if these libs from `<julia-root>/bin` are available (besides the `hello.exe`):

- hello.exe (1.648 MB)
- libatomic-1.dll (0.256 MB)
- libgcc\_s\_seh-1.dll (0.932 MB)
- libjulia-internal.dll ( **14.556 MB** )
- libjulia.dll (0.214 MB)
- libopenlibm.dll (0.523 MB)
- libpcre2-8.dll (0.802 MB)
- libstdc+±6.dll ( **25.018 MB** )
- libwinpthread-1.dll (0.323 MB)

For all binary-size guys out there, that’s 42.6 MB of libraries (almost exclusively made up by the two highlighted-in-bold-font ones).

* * *

**Edit 2** : I was calling the .exe from bash (on mingw64) before. Then the above libs are sufficient. If called from cmd/powershell, it complains about two other libs not being available, but still prints the “Hello world!” to the console. These libs are:

- libgmp-10.dll (1.054 MB)
- libmpfr-6.dll (2.505 MB)

---

<div class="post-metadata">

**Author:** ![asprionj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/asprionj/32/6856_2.png) [@asprionj](https://discourse.julialang.org/u/asprionj)\
**Post date:** [August 6, 2025, 12:38pm UTC](https://discourse.julialang.org/t/creating-fully-self-contained-and-pre-compiled-library/131374/5 "2025-08-06T12:38:47Z")

</div>

First, a question: is there any way of finding out what libraries (from `<julia-root>/bin`) are needed for a given `juliac`-compiled binary? Either by `juliac`-related tooling, or then afterwards by analysing the binary?

* * *

So, next step: move from an .exe to a library (.dll on Windows). Changed the file to now just be a `Base.@ccallable function say_hello()::Cint`, everything else is the same. Compilation worked (just using `--output-lib`), but when I want to `dlopen` the library (from julia, of course 😉), I get a popup-error-message again stating that entrypoint `ijl_gc_small_alloc` is not available. Any hints on what happened now and where to start to make the library work?

**Edit** : Forgot the additional `--compile-ccallable` argument. But still, when trying to load the library in julia, I now get the message about a new missing entrypoint named `ijl_autoinit_and_adopt_thread`.

However, when calling the library from Go, for example, it works. 🤔

---

<div class="post-metadata">

**Author:** ![mbauman](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mbauman/32/31082_2.png) [@mbauman](https://discourse.julialang.org/u/mbauman)\
**Post date:** [August 6, 2025, 4:16pm UTC](https://discourse.julialang.org/t/creating-fully-self-contained-and-pre-compiled-library/131374/6 "2025-08-06T16:16:32Z")

</div>

Yeah, it’s still quite hard and on the bleeding edge right now. Julia dynamically links to lots of libraries — both at startup and dynamically loaded. A few quick notes:

- PackageCompiler has the smarts to get the rpaths right and gather all the artifacts and other libraries. The current `juliac.jl` script doesn’t incorporate all of that, but the `--relative-rpath` flag can get you a little closer. This is a very good reason to incorporate `juliac.jl` into PackageCompiler.
- Using `--trim` on nightly is currently broken for non-trivial use due to [trimming: no way to `ccall` into a dynamically-computed library name · Issue #57707 · JuliaLang/julia · GitHub](https://github.com/JuliaLang/julia/issues/57707). The v1.12 RC (juliaup’s `+beta` channel currently) will probably get you farther.

---

<div class="post-metadata">

**Author:** ![RoyiAvital](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/royiavital/32/571_2.png) [@RoyiAvital](https://discourse.julialang.org/u/RoyiAvital)\
**Post date:** [August 6, 2025, 5:16pm UTC](https://discourse.julialang.org/t/creating-fully-self-contained-and-pre-compiled-library/131374/7 "2025-08-06T17:16:48Z")

</div>

If your functions are simple enough you may find [`StaticCompiler.jl`](https://github.com/tshort/StaticCompiler.jl) as an alternative.

---

<div class="post-metadata">

**Author:** ![RomeoV](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/romeov/32/37687_2.png) [@RomeoV](https://discourse.julialang.org/u/RomeoV)\
**Post date:** [August 6, 2025, 5:16pm UTC](https://discourse.julialang.org/t/creating-fully-self-contained-and-pre-compiled-library/131374/8 "2025-08-06T17:16:53Z")

</div>

I am also very interested in this and actively trying to get a library to trim and distribute. I am currently able to generate the “.so” file and have filtered out the required libraries “by hand” (going through one-by-one in the RPATH and checking if renaming them breaks the executable). Now when I try to run a binary compiled with the library on another machine I am still missing the artifacts, so I’m trying to understand how PackageCompiler.jl includes them so that it’s portable… working on that right now. It seems to me that we can just copy over the required artifacts to a local dir (just like the RPATH libraries), but I’m not sure if I have to tell julia still where to look.

---

<div class="post-metadata">

**Author:** ![RomeoV](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/romeov/32/37687_2.png) [@RomeoV](https://discourse.julialang.org/u/RomeoV)\
**Post date:** [August 6, 2025, 5:18pm UTC](https://discourse.julialang.org/t/creating-fully-self-contained-and-pre-compiled-library/131374/9 "2025-08-06T17:18:25Z")

</div>

I was able to strip out `libstdc+±6.dll` by the way, which seems to take up a majority of your memory. I still have BLAS bundled though (35MB), and getting rid of that is turning out to be tricky, for instance because it is challenging to track from where BLAS is actually called.

---

<div class="post-metadata">

**Author:** ![MDSW](https://avatars.discourse-cdn.com/v4/letter/m/94ad74/32.png) [@MDSW](https://discourse.julialang.org/u/MDSW)\
**Post date:** [August 6, 2025, 8:09pm UTC](https://discourse.julialang.org/t/creating-fully-self-contained-and-pre-compiled-library/131374/10 "2025-08-06T20:09:09Z")

</div>

I’m similarly trying to figure out (by reverse engineering PackageCompiler, among other things) what the minimum set of DLLs is for a given program. I’d be interested in knowing what the minimum set of these is for the Julia language to fully work – I don’t believe it is the full distro you get on a basic juliaup install. The test case would be a minimal shell application that links to `libjulia` and provides a REPL-like experience that would include adding packages, `eval`, and AOT/JIT compilation. And flag those that you could take away if you ran strictly on the interpreter.

I have not been able to find an authoritative list or tool (other than PackageCompiler, which is doing something a little different) with this information.

---

<div class="post-metadata">

**Author:** ![GeorgeGkountouras](https://avatars.discourse-cdn.com/v4/letter/g/77aa72/32.png) [@GeorgeGkountouras](https://discourse.julialang.org/u/GeorgeGkountouras)\
**Post date:** [August 7, 2025, 12:24pm UTC](https://discourse.julialang.org/t/creating-fully-self-contained-and-pre-compiled-library/131374/11 "2025-08-07T12:24:03Z")

</div>

> [@RomeoV](#):
>
> I am also very interested in this and actively trying to get a library to trim and distribute.

Depending on your use case, this might work: put your julia code inside an AppBundler.jl app and have your library call _that_. No trim functionality, unless you delete files manually.

---

<div class="post-metadata">

**Author:** ![RomeoV](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/romeov/32/37687_2.png) [@RomeoV](https://discourse.julialang.org/u/RomeoV)\
**Post date:** [August 7, 2025, 6:52pm UTC](https://discourse.julialang.org/t/creating-fully-self-contained-and-pre-compiled-library/131374/12 "2025-08-07T18:52:48Z")

</div>

I have now managed to also compile it and run everything on a clean `podman` VM. For the artifacts, as a crutch I also compile with PackageCompiler.jl and then just copy over the artifacts that are bundled there. Probably I could go in and figure out how PackageCompiler.jl constructs the artifacts bundle and reuse that code.

---

<div class="post-metadata">

**Author:** ![asprionj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/asprionj/32/6856_2.png) [@asprionj](https://discourse.julialang.org/u/asprionj)\
**Post date:** [August 7, 2025, 8:34pm UTC](https://discourse.julialang.org/t/creating-fully-self-contained-and-pre-compiled-library/131374/13 "2025-08-07T20:34:22Z")

</div>

I think in my many follow-ups and edits, my current pain point was lost.

So I can compile the “hello world” example, but when trying to load the library in julia, I get an error about a missing entrypoint named `ijl_autoinit_and_adopt_thread`.

However, when calling the library from Go, for example, it works. What happens when `Libdl.dlopen`-ing (not even `ccall`-ing) a shared library in julia? Why is this entrypoint called (and/or by whom)?

_(“But why calling it from julia?!”, you might ask. Because tests and “validation scenarios” will be available for the julia-code version, and I will want to run them again against the `juliac`-generated library to ensure that it works exactly the same.)_

* * *

**Edit** : one step further. Noted that I used the release-version of julia (1.11.x) to load and call the `juliac`-generated DLL. So, when also using nightly to call the generated DLL, I can load it (`Libdl.dlopen`) and get the symbol (`Libdl.dlsym`). But when I then call the function, I get a fatal crash.

Here’s a minimal example:

_libmwe.jl_:

```julia
Base.@ccallable function answer()::Cint
    return 42
end

```

Compile using:

```julia-auto
julia.exe ../share/julia/juliac/juliac.jl --output-lib libmwe --experimental --trim --compile-ccallable libmwe.jl

```

In the julia REPL (in the directory where the .dll is located), execute:

```julia
 @ccall "libmwe.dll".answer()::Cint

```

This throws the following error with trace:

```julia-auto
[3980] signal 22: SIGABRT
in expression starting at REPL[1]:1
crt_sig_handler at C:/workdir/src\signals-win.c:99
raise at C:\WINDOWS\System32\msvcrt.dll (unknown line)
abort at C:\WINDOWS\System32\msvcrt.dll (unknown line)
jl_init_threadtls at C:/workdir/src\threading.c:334
ijl_adopt_thread at C:/workdir/src\threading.c:447
.text at C:\julia-nightly\bin_compiled\libmwe.dll (unknown line)
top-level scope at .\REPL[1]:1
jl_toplevel_eval_flex at C:/workdir/src\toplevel.c:762
__repl_entry_eval_expanded_with_loc at C:\workdir\usr\share\julia\stdlib\v1.13\REPL\src\REPL.jl:304
jl_apply at C:/workdir/src\julia.h:2375 [inlined]
jl_f_invokelatest at C:/workdir/src\builtins.c:877
toplevel_eval_with_hooks at C:\workdir\usr\share\julia\stdlib\v1.13\REPL\src\REPL.jl:311
toplevel_eval_with_hooks at C:\workdir\usr\share\julia\stdlib\v1.13\REPL\src\REPL.jl:315
toplevel_eval_with_hooks at C:\workdir\usr\share\julia\stdlib\v1.13\REPL\src\REPL.jl:308 [inlined]
eval_user_input at C:\workdir\usr\share\julia\stdlib\v1.13\REPL\src\REPL.jl:333
repl_backend_loop at C:\workdir\usr\share\julia\stdlib\v1.13\REPL\src\REPL.jl:460
#start_repl_backend#41 at C:\workdir\usr\share\julia\stdlib\v1.13\REPL\src\REPL.jl:435
start_repl_backend at C:\workdir\usr\share\julia\stdlib\v1.13\REPL\src\REPL.jl:432 [inlined]
#run_repl#50 at C:\workdir\usr\share\julia\stdlib\v1.13\REPL\src\REPL.jl:671
run_repl at C:\workdir\usr\share\julia\stdlib\v1.13\REPL\src\REPL.jl:657
jfptr_run_repl_28061.1 at C:\julia-nightly\share\julia\compiled\v1.13\REPL\u0gqU_fBDF2.dll (unknown line)
run_std_repl at .\client.jl:481
jfptr_run_std_repl_75693.1 at C:\julia-nightly\lib\julia\sys.dll (unknown line)
jl_apply at C:/workdir/src\julia.h:2375 [inlined]
jl_f_invokelatest at C:/workdir/src\builtins.c:877
run_main_repl at .\client.jl:502
repl_main at .\client.jl:589 [inlined]
_start at .\client.jl:564
jfptr__start_43814.1 at C:\julia-nightly\lib\julia\sys.dll (unknown line)
jl_apply at C:/workdir/src\julia.h:2375 [inlined]
true_main at C:/workdir/src\jlapi.c:971
jl_repl_entrypoint at C:/workdir/src\jlapi.c:1138
mainCRTStartup at C:/workdir/cli\loader_exe.c:58
BaseThreadInitThunk at C:\WINDOWS\System32\KERNEL32.DLL (unknown line)
RtlUserThreadStart at C:\WINDOWS\SYSTEM32\ntdll.dll (unknown line)
Allocations: 1 (Pool: 1; Big: 0); GC: 0

```

I double-checked, and calling the `answer` function in that library from a Go programm works perfectly fine. The Go code for reference:

```Go
package main

import "fmt"

/*
#cgo LDFLAGS: -L. -llibmwe
int answer();
*/
import "C"

func main() {
	answer := C.answer()
	fmt.Println("This is Go. Value returned by answer: ", answer)
}

```

* * *

**Edit 2** : just opened an issue: [Library generated with `juliac` errors when called from julia itself, but works when called by other program · Issue #59237 · JuliaLang/julia](https://github.com/JuliaLang/julia/issues/59237)

---

<div class="post-metadata">

**Author:** ![asprionj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/asprionj/32/6856_2.png) [@asprionj](https://discourse.julialang.org/u/asprionj)\
**Post date:** [August 18, 2025, 9:25am UTC](https://discourse.julialang.org/t/creating-fully-self-contained-and-pre-compiled-library/131374/14 "2025-08-18T09:25:14Z")

</div>

Update:

**Julia not being able to call `juliac`-compiled library** : See the progress on the issue linked above:

> This is a known limitation currently, as each system image thinks it’s the only one that should be loaded. Soon we will have a workaround for this by allowing you to give a library its own private copy of the julia runtime.

**`juliac` vs. binding partitions** : I run into the same problems as reported here: [Segmentation fault in executable after compilation with juliac including --trim --experimental arguments - General Usage - Julia Programming Language](https://discourse.julialang.org/t/segmentation-fault-in-executable-after-compilation-with-juliac-including-trim-experimental-arguments/125303).

- As long as I have a simple example that does not rely on other packages (no `using` or `import` statements), I can use `--experimental --trim`.
- As soon as I rely on other packages (`JSON.jl` in my PoC), I cannot use `--trim` anymore. Errors similar to the ones reported in the referenced thread occur.
- This seems to be due to a regression between the two experimental features “_trimming during binary generation_” and “_binding partitions_”.
- I can compile and successfully run (on Windows and in a Docker container under `ubuntu:latest`) examples relying on other packages, just without `--trim` and thus a ~200 MB instead of 1.8 MB library. But for my PoC, that’s OK for the moment.

---

<div class="post-metadata">

**Author:** ![asprionj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/asprionj/32/6856_2.png) [@asprionj](https://discourse.julialang.org/u/asprionj)\
**Post date:** [August 19, 2025, 6:35am UTC](https://discourse.julialang.org/t/creating-fully-self-contained-and-pre-compiled-library/131374/15 "2025-08-19T06:35:42Z")

</div>

Another update, this time about Artifacts. I think to remember I read something about `juliac` and Artifacts, and that `PackageCompiler` has functionality to collect all required Artifacts – but don’t remember where. Any pointers are welcome, thanks!

Artifacts are the binary dependencies (compiled shared libraries, mostly) some packages rely on. These are automatically downloaded to a dedicated folder (something like `<user-folder>.julia/artifacts`), and the julia runtime knows where to look for them.

Now, there’s not much documentation on this; [Artifacts · The Julia Language](https://docs.julialang.org/en/v1/stdlib/Artifacts/) is quite minimalistic and the referenced section about `Pkg` is intended for package devs. There is some more explanation in [Custom binaries · JuMP](https://jump.dev/JuMP.jl/stable/developers/custom_solver_binaries/).

When I use a package which requires artifacts (my example is [jump-dev/HiGHS.jl](https://github.com/jump-dev/HiGHS.jl)) and use `juliac` to generate a binary, upon execution I get the error:

```julia-auto
ErrorException("`ccall` requires the compiler")

```

Now:

- What is meant by the “compiler”? Initially I thought `libLLVM*` (which I remove due to its size) or a C compiler such as `gcc` (which I do have available). → **But it seems to be `libjulia-codegen*` AND `libLLVM*`.** Together, they are 218MB and, what concerns me more, make up all the dynamic “interpreted”/JIT-compiled machinery of julia, which I exactly do NOT want in a fully pre-compiled binary.
- Why is it needed for a `ccall`?
- How do I identify all required artifacts, and where do I put them (under Windows and Linux), absolute and relative to my `juliac`-generated binary?
  - I can run the “MWE” in `juliac`-compiled form, when staying on the build machine with the auto-downloaded artifacts stil in place.
  - Would just be good to know which artifacts are actually downloaded and used, where they are, and if I could package them into a portable “distribution” somehow.

---

<div class="post-metadata">

**Author:** ![asprionj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/asprionj/32/6856_2.png) [@asprionj](https://discourse.julialang.org/u/asprionj)\
**Post date:** [August 20, 2025, 6:08am UTC](https://discourse.julialang.org/t/creating-fully-self-contained-and-pre-compiled-library/131374/16 "2025-08-20T06:08:56Z")

</div>

@RomeoV, did you continue with this activity? Did it lead anywhere?

BTW, is there any central roadmap / overview on binary generation (i.e. what I assume a combination of `juliac` and `PackageCompiler` would be in the future)? Just to see where it’s at, known limitations, what is (roughly) going to come at what point in time, how one can help/contribute, etc.

---

<div class="post-metadata">

**Author:** ![RomeoV](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/romeov/32/37687_2.png) [@RomeoV](https://discourse.julialang.org/u/RomeoV)\
**Post date:** [August 20, 2025, 1:00pm UTC](https://discourse.julialang.org/t/creating-fully-self-contained-and-pre-compiled-library/131374/17 "2025-08-20T13:00:42Z")

</div>

Yes, I have managed to make a self-contained library that can be relocated.  
You can find the reference here: [GitHub - RomeoV/RunwayLib.jl](https://github.com/RomeoV/RunwayLib.jl)

In essence, my build and bundling steps are as follows:

- Write your normal julia code. Make `Base.@entrypoint`s ([example](https://github.com/RomeoV/RunwayLib.jl/blob/master/src/c_api.jl)). Write tests that test your code with `JET.@test_opt` and fix all tests, possibly by forking upstream repositories and fixing things. Depending on what you’re doing, this can be easy or challenging. It was challenging for me.
- Make a new dir next to `src`, I call it `juliac`. Put a Makefile with something like

```make
COMPILED_DIR = MyLibCompiled
LIB_DIR = $(COMPILED_DIR)/lib

$(LIB_DIR)/libmylib.so: loadmylib.jl Project.toml Manifest.toml $(SRC)
    @mkdir -p $(LIB_DIR)
    JULIAC=$$(julia -e 'print(normpath(joinpath(Sys.BINDIR, Base.DATAROOTDIR, "julia", "juliac", "juliac.jl")))' 2>/dev/null || echo "") ; \
    $(JULIA) --project=. --depwarn=error "$$JULIAC" --experimental --trim=unsafe-warn --compile-ccallable --output-lib $(LIB_DIR)/libmylib loadmylib.jl --relative-rpath

Manifest.toml: Project.toml ../Project.toml
    -rm -f Manifest.toml
    $(JULIA) --project=. -e 'using Pkg; Pkg.instantiate()'
    @touch $@ # Pkg.instantiate doesn't update the mtime if there are no changes

```

Notice the `--relative-rpath`, which will allow us to put all the required libraries next to each other.  
Remember to run `juliaup override set 1.12` in the directory.
- Manually write a [header file](https://github.com/RomeoV/RunwayLib.jl/blob/master/juliac/libposeest.h), and add

```make
# Create include directory with header file
$(INCLUDE_DIR)/libmylib.h: libmylib.h
	@mkdir -p $(INCLUDE_DIR)
	cp libmylib.h $(INCLUDE_DIR)/

```

@tim.holy has started automating this: [GitHub - JuliaInterop/JuliaLibWrapping.jl: Generate language-bindings for Julia-generated shared libraries](https://github.com/JuliaInterop/JuliaLibWrapping.jl)
- Make a `Project.toml` in `juliac` that contains something like

```toml
[sources]
MyLib = {path = ".."}

```

Also copy over any other custom sources from your main Project.toml file (it seems sources are [not applied transitively](https://github.com/JuliaLang/Pkg.jl/pull/3783#issuecomment-1935523553)…)
- Make a simple `loadmylib.jl` file that just runs `using MyLib`
- Run the compilation

```bash
make all

```

Now it’s time to create the “bundle” that we can ship. This includes three aspects.

1. ship all the required libraries (such as libjulia.so, libjulia-internal.so, libopenblas, etc) but try to strip out the libraries that we don’t need
2. figure out which artifacts (`share/julia`) we have to ship
3. make sure to compile on a machine where the GLIBC version is **older** than on your target deployment machine.

- ~~to bundle up all the libraries, you can do something like~~ **see EDIT below**

```make
$(LIB_DIR)/.installed:
	@echo "Creating directory structure..."
	@mkdir -p $(LIB_DIR)
	@echo "Copying Julia libraries..."
	JULIA_LIB_DIR=$$($(JULIA) -e "print(joinpath(Sys.BINDIR, \"..\", \"lib\"))"); \
	cp -r "$$JULIA_LIB_DIR"/* $(LIB_DIR)
	@touch $(LIB_DIR)/.installed
	@echo "Julia libraries installed to $(LIB_DIR)"

```

in your Makefile.  
To then filter for only the required libraries, I have not found a reliable way. What I have done is make a small `mainc` file that you can execute and see if it runs, and then by hand start deleting libraries and see if `mainc` crashes. Through this process, I made a `required_libraries.txt` file and then filter the bundled libraries with

```make
$(LIB_DIR)/.cleaned: $(LIB_DIR)/.installed required_libraries.txt
	@echo "Cleaning $(LIB_DIR)/julia directory based on required_libraries.txt"
	@echo "Before cleanup: $$(du -sh $(LIB_DIR)/ | cut -f1)"
	cd $(LIB_DIR)/julia && \
	for file in *.so*; do \
		[-f "$$file"] && \
		stem=$$(echo "$$file" | sed 's/\.so.*/.so/') && \
		! grep -q "$$stem" ../../../required_libraries.txt && \
		rm -f "$$file" || true; \
	done
	@echo "After cleanup: $$(du -sh $(LIB_DIR)/julia/ | cut -f1)"
	@touch $(LIB_DIR)/.cleaned

```

I would warmly welcome a better way to do this.
- ~~to figure out which artifacts to bundle, I do something equally terrifying.~~ **see EDIT below** I use PackageCompiler.jl to create a bundled version of my code, and then just copy over the `share` directory that PackageCompiler comes up with. Probably it’s possible to directly use [`PackageCompiler.bundle_artifacts`](https://github.com/JuliaLang/PackageCompiler.jl/blob/57505caad062b5b1985bcbbb2fb15b9b6e9e6d99/src/PackageCompiler.jl#L1492), but I haven’t dug into it enough yet.
- finally, you can compile everything first locally, and then on a CI machine running e.g. a `ubuntu:22.04` container. I have had trouble getting everything to run under `ubuntu:20.04`, unfortunately. For examples, check [my juliac workflow with relocation test](https://github.com/RomeoV/RunwayLib.jl/blob/master/.github/workflows/juliac.yml) or [my python packaging (glibc) workflow](https://github.com/RomeoV/RunwayLib.jl/blob/master/.github/workflows/package-python.yml).

Then, when this is all done you can even write a python interface to the c\_api of your code and package it up as a python package, see e.g. [here](https://github.com/RomeoV/RunwayLib.jl?tab=readme-ov-file#python-usage).  
I am currently in the process of streamlining this.

Happy to have more discussions on this, and am looking forward to each step becoming more automated.

EDIT: It seems I have also found a way to bundle the libraries and artifacts more easily with PackageCompiler.jl. I think something like this should do the trick:

```julia
using PackageCompiler
ctx = PackageCompiler.create_pkg_context(".")
PackageCompiler.bundle_artifacts(ctx, ENV["COMPILED_DIR"]; include_lazy_artifacts=false)
stdlibs = PackageCompiler.gather_stdlibs_project(ctx)
PackageCompiler.bundle_julia_libraries(ENV["COMPILED_DIR"], stdlibs)
for (root, _, files) in walkdir(ENV["COMPILED_DIR"]), file in files
  (contains(file, "libjulia-codegen") || contains(file, "libLLVM")) && rm(joinpath(root, file); force=true)
end

```

---

<div class="post-metadata">

**Author:** ![RomeoV](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/romeov/32/37687_2.png) [@RomeoV](https://discourse.julialang.org/u/RomeoV)\
**Post date:** [August 20, 2025, 3:50pm UTC](https://discourse.julialang.org/t/creating-fully-self-contained-and-pre-compiled-library/131374/18 "2025-08-20T15:50:16Z")

</div>

Also, FYI trying to build with `musl libc` for better compatibility seems to be a [red herring](https://github.com/julia-actions/setup-julia/issues/345#issuecomment-3206955741), in case someone else was thinking in this direction.

---

<div class="post-metadata">

**Author:** ![asprionj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/asprionj/32/6856_2.png) [@asprionj](https://discourse.julialang.org/u/asprionj)\
**Post date:** [August 20, 2025, 8:39pm UTC](https://discourse.julialang.org/t/creating-fully-self-contained-and-pre-compiled-library/131374/19 "2025-08-20T20:39:15Z")

</div>

Wow, thanks! Quite a lot of manual steps, though. But nice that it is basically doable, and that `PackageCompiler` seems to provide a good portion of it already.

One question though: You do remove the `libjulia-codegen` and `libLLVM` libs, which I have found to be required when artifacts (a third-party dynamically loaded library) are called from the compiled code. Or is this resolved by

> just copy over the `share` directory that PackageCompiler comes up with

or

`PackageCompiler.bundle_artifacts(ctx, ENV["COMPILED_DIR"]; include_lazy_artifacts=false)`

respectively?

---

<div class="post-metadata">

**Author:** ![asprionj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/asprionj/32/6856_2.png) [@asprionj](https://discourse.julialang.org/u/asprionj)\
**Post date:** [August 20, 2025, 8:40pm UTC](https://discourse.julialang.org/t/creating-fully-self-contained-and-pre-compiled-library/131374/20 "2025-08-20T20:40:39Z")

</div>

Thanks for the heads up! A colleague wanted to switch to an Alpine base image and I intuitively said this would most probably cause problems due to `musl` 😅.

[Next page](https://discourse.julialang.org/t/creating-fully-self-contained-and-pre-compiled-library/131374.md?page=2)
