🐕

Juliaで実行ファイルを作成する方法

2023/12/15に公開2

Julia Advent Calendar 2023の15日目の記事です。

はじめに

世の中はどうやらPythonが主流のようで、私の周りではJuliaを使ってくれる人がなかなか増えませんが、Juliaで実装したものを実行ファイル形式にしてくれれば使いたいと言ってくれる人は、いたりいなかったりするものです。Pythonでは、PyInstallerというパッケージを用いて実行ファイル形式にすることが可能ですが、JuliaでもPackageCompilerというパッケージを使用することで同様のことが実現可能です。

本記事では、PackageCompilerを用いた実行ファイル形式の作成方法について紹介したいと思います。

開発環境

  • Windows 11 (22H2)
    • Julia 1.9.3
    • PackageCompiler 2.1.15
  • Mac上 (Ventura 13.6.1) ではビルドできなかったので今回割愛しますが、作成方法はWindows上とほぼ同じです (XCodeが15.0.1になった影響?gcc-13を使う方法とかいろいろ試してみましたが、現状上手くいかず...)
    (追記:ごまふあざらし様がコメントに解決策を載せてくれております)

作成するもの

MyExeというパッケージを作成して、MyExe.exeというファイルを作成します。MyExe.exeはCSVを引数で渡すとCSVファイルの2列目を足し合わせて、結果を標準出力により表示するという機能にします。これによりパッケージの追加や引数の指定方法がわかると思います。

PS > .\MyExe.exe sample.csv
PS > 2023

以下のようなsample.csvを用意します(数字はなんでもよいです)。

ID Count
1 289
2 578
3 1156

実装

PackageCompilerをインストールします。

(@v1.9) pkg> add PackageCompiler

パッケージを作成します。

(@v1.9) pkg> generate MyExe

必要なパッケージをインストールします。

(@v1.9) pkg> activate MyExe
(MyExe) pkg> add CSV
(MyExe) pkg> add DataFrames

MyExe/src/MyExe.jlを編集します。

module MyExe
  using CSV
  using DataFrames

  function readCSV() 
    if length(ARGS) == 0
      println("Hello,World!")
    else
      df = CSV.File(ARGS[1]) |> DataFrame
      println(sum(df[:,:Count]))
    end
  end

  function julia_main()::Cint
    readCSV()
    return 0
  end
end # module MyExe

下記部分が必須です。

function julia_main()::Cint

  return 0
end

記述が終わったらビルドします。MyExeフォルダと同じ階層でJuliaのREPLを起動して、

julia> using PackageCompiler
julia> create_app("MyExe", "build") # create_app("パッケージ名", "出力フォルダ名")

と実行すると、buildフォルダが作成されて、build/binの下にMyExe.exeが作られます。exeファイルの作成には結構時間がかかります(今回のものだと10分くらい)。

想定通りの動作が確認できます。

PS > .\build\bin\MyExe.exe sample.csv
PS > 2023

配布する場合はbuildフォルダすべてを渡す必要がありますが、配布先でJuliaをインストールする必要はないので、使ってもらいやすくなると思います。

まとめ

PackageCompilerの紹介をしました。パッケージの追加方法や引数の扱い方も特殊な記述が必要なわけではなく、Juliaで実装する際と同様の方法で問題ありません。

良きJuliaライフを!

Discussion

ごまふあざらしごまふあざらし

この Issue にあるハック で一応 macOS(Intel) 環境で通りました.

Project.toml と同じ階層で下記のスクリプトを実行すると sysimage に関するエラーを回避し動作はできました.

using PackageCompiler
using PackageCompiler: get_extra_linker_flags, julia_libdir, julia_private_libdir, ldlibs, bitflag, march, run_compiler
# https://github.com/JuliaLang/PackageCompiler.jl/issues/738#issuecomment-1838901893
function PackageCompiler.create_sysimg_from_object_file(object_files::Vector{String},
                                        sysimage_path::String;
                                        version,
                                        compat_level::String,
                                        soname::Union{Nothing, String})

    if soname === nothing && (Sys.isunix() && !Sys.isapple())
        soname = basename(sysimage_path)
    end
    mkpath(dirname(sysimage_path))
    # Prevent compiler from stripping all symbols from the shared lib.
    o_file_flags = Sys.isapple() ? `-Wl,-all_load $object_files -Wl,-ld_classic` : `-Wl,--whole-archive $object_files -Wl,--no-whole-archive`
    extra = get_extra_linker_flags(version, compat_level, soname)
    cmd = `$(bitflag()) $(march()) -shared -L$(julia_libdir()) -L$(julia_private_libdir()) -o $sysimage_path $o_file_flags $(Base.shell_split(ldlibs())) $extra`
    run_compiler(cmd; cplusplus=true)
    return nothing
end

using MyExe

create_app(pkgdir(MyExe), "build", force=true)

もう少し細かい環境の情報はこちらの gist に配置しました.

https://gist.github.com/terasakisatoshi/c1b03c8b2791340b33e6781038e1c451

きのこのききのこのき

ありがとうございます!
上記方法でビルドできることが確認できました。

私の環境を載せておきます。

julia> versioninfo()
Julia Version 1.9.2
Commit e4ee485e909 (2023-07-05 09:39 UTC)
Platform Info:
  OS: macOS (arm64-apple-darwin22.4.0)
  CPU: 12 × Apple M2 Max
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-14.0.6 (ORCJIT, apple-m1)
  Threads: 1 on 8 virtual cores