Cool news! Thanks to @simonbyrne, I’ve managed to compile a julia program on one mac, ship it to another mac that’s never even heard of julia, and certainly doesn’t have it installed, and it runs!! (And it’s a 10 year old computer!) Just wanted to share we’ve learned.
I did this using juliac.jl
from PackageCompiler
, but the below thoughts should apply to anyone trying to run julia with a different cpu target.
Here’s the things I learned:
1. code is compiled for a cpu target
When julia is invoked, all the code it compiles is compiled for a specific cpu target. By default, I believe it uses the " most specific" cpu target it can – presumably to get the best performance possible. On my machine at least, julia calls this the “native” cpu target. This default target, native
is what is used when building its default sysimg (sys.dylib
).
You can specify a different cpu target to use via the -C
or --cpu-target
command line option. The default is -Cnative
. If you wanted to broaden the supported cpu architectures to, say, all Intel 64-bit cpus, you could invoke julia as julia -Cx86-64
.
2. ERROR: Target architecture mismatch. Please delete or regenerate sys.{so,dll,dylib}.
However, if you just change the target architecture, things don’t work. You’ll get the above error, complaining that the machine code its emitting and its sysimg (sys.dylib
) weren’t built for the same architecture. But fret not! We can simply ask julia to ignore the sysimg. From julia -h
:
--precompiled={yes|no} Use precompiled code from system image if available
3. --precompiled=no --compilecache=no
Cool, so now, this works! (but it’s slower of course)
$ julia -Cx86-64 --precompiled=no
_
_ _ _(_)_ | A fresh approach to technical computing
(_) | (_) (_) | Documentation: https://docs.julialang.org
_ _ _| |_ __ _ | Type "?help" for help.
| | | | | | |/ _ | |
| | |_| | | | (_| | | Version 0.6.2 (2017-12-13 18:08 UTC)
_/ |\__'_|_|_|\__'_| | Official http://julialang.org/ release
|__/ | x86_64-apple-darwin14.5.0
julia> println("Hooray! it works!")
Hooray! it works!
julia> # cool, let's do some stuff
julia> using UnicodePlots
INFO: Precompiling module UnicodePlots.
ERROR: Julia and the system image were compiled for different architectures.
Please delete or regenerate sys.{so,dll,dylib}.
ERROR: write: broken pipe (EPIPE)
Stacktrace:
[1] try_yieldto(::Base.##296#297{Task}, ::Task) at ./event.jl:189
[2] wait() at ./event.jl:234
[3] uv_write(::Base.PipeEndpoint, ::Ptr{UInt8}, ::UInt64) at ./stream.jl:811
[4] unsafe_write(::Base.PipeEndpoint, ::Ptr{UInt8}, ::UInt64) at ./stream.jl:832
[5] unsafe_write(::Base.PipeEndpoint, ::Base.RefValue{UInt8}, ::Int64) at ./io.jl:293
[6] write(::Base.PipeEndpoint, ::UInt8) at ./stream.jl:873
[7] write_as_tag(::Pipe, ::Int32) at ./serialize.jl:128
[8] serialize(::SerializationState{Pipe}, ::DataType) at ./serialize.jl:542
[9] serialize(::SerializationState{Pipe}, ::Expr) at ./serialize.jl:330
[10] create_expr_cache(::String, ::String, ::Array{Any,1}) at ./loading.jl:633
[11] compilecache(::String) at ./loading.jl:709
[12] _require(::Symbol) at ./loading.jl:497
[13] require(::Symbol) at ./loading.jl:405
— wait what!? So, --precompiled=no
is enough to stop using the sysimg, but now we have the problem that the precompiled packages we’re importing were precompiled for the wrong target. So let’s turn off reading from the cached, precompiled packages as well. Right under that last flag in julia -h
is this one:
--compilecache={yes|no} Enable/disable incremental precompilation of modules
Turning compilecache
off will stop importing precompiled package images.
**** So putting those together, the correct way to invoke julia with a custom cpu target is as follows: ****
$ julia -Cx86-64 --precompiled=no --compilecache=no
Cool! So why did all this come up in the first place? Ah yes, I was trying to compile a distributable binary using juliac.jl
. So it turns out juliac.jl
already has a flag to specify the cpu target (also -C
or --cpu-target
), which just gets forwarded along to julia
when it’s invoked. So I just had to change juliac
to also set --precompiled=no --compilecache=no
when the user sets -C
!
So, assuming that PR goes through, or some version of it, you should just be able to pass -C<target>
or --cpu-target=<target>
to juliac in order to compile for a different target.
If, as in my case, you want your compiled binary to support any intel-based mac, you would invoke it like this!:
julia ~/.julia/v0.6/PackageCompiler/juliac.jl -vaej --cpu-target=x86-64 examples/hello.jl
and it will fill examples/builddir
with everything you need to run this binary on another computer:
- the binary itself,
hello
- the binary’s “sysimg”:
hello.dylib
- all the julia libs it needs to link against:
libjulia.dylib
,libLLVM.dylib
,libamd.dylib
, etc…
(And it will also have the temporary file hello.o
in there. You can delete that.)
And now you can simply zip
up builddir and send it to another mac computer, and they can open it and run ./hello
and
hello, world
sin(0.0) = 0.0
┌────────────────────────────────────────┐
1 │⠀⠀⠀⠀⠀⠀⠀⡠⠊⠉⠉⠉⠢⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
│⠀⠀⠀⠀⠀⢠⠎⠀⠀⠀⠀⠀⠀⠘⢆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
│⠀⠀⠀⠀⢠⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠳⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
│⠀⠀⠀⢠⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠱⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
│⠀⠀⢠⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠳⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
│⠀⢀⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢣⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
│⠀⡎⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀│
│⠼⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠬⢦⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⢤│
│⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠇│
│⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡎⠀│
│⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠱⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡞⠀⠀│
│⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠱⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡜⠀⠀⠀│
│⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠱⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡞⠀⠀⠀⠀│
│⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⢆⠀⠀⠀⠀⠀⠀⢠⠎⠀⠀⠀⠀⠀│
-1 │⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⢄⣀⣀⣀⠔⠁⠀⠀⠀⠀⠀⠀│
└────────────────────────────────────────┘
0 100
voila