Skip to content

Commit 2a7bb25

Browse files
tkfstevengj
authored andcommitted
Make PyCall.jl AOT-compilable (#651)
* Support PackageCompiler.jl; clear out global states * Add scripts to run PackageCompiler * Run tests with AOT-compiled PyCall in Travis * Clear other global states * Add aot/README.md * Name Travis Jobs * Add MacroTools to aot/Project.toml * Add Pkg.activate in aot/precompile.jl * Add Pkg.build("PyCall") in aot/compile.jl
1 parent 5f141ee commit 2a7bb25

File tree

8 files changed

+114
-0
lines changed

8 files changed

+114
-0
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
aot/Manifest.toml
2+
aot/Project.toml
3+
aot/_julia_path
4+
aot/sys.*
15
deps/deps.jl
26
deps/PYTHON
37
deps/build.log

.travis.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,19 @@ matrix:
4848
# https://github.com/pytest-dev/pytest/blob/799b72cf6f35c4c4c73797303d3f78c12a795f77/.travis.yml#L38-L52
4949
# https://github.com/pytest-dev/pytest/pull/3893
5050

51+
- &test-aot
52+
name: "AOT (Julia: 1.0)"
53+
language: julia
54+
os: linux
55+
env: PYTHON=python3
56+
julia: 1.0
57+
script:
58+
- julia --color=yes -e 'using Pkg; Pkg.add("PackageCompiler")'
59+
- julia --color=yes aot/compile.jl
60+
- aot/runtests.sh
61+
after_success: skip
62+
- {<<: *test-aot, name: "AOT (Julia: nightly)", julia: nightly}
63+
5164
after_success:
5265
- julia -e 'Pkg.add("Coverage"); using Coverage; Codecov.submit(process_folder())'
5366
notifications:

aot/README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Ahead-of-time compilation for PyCall
2+
3+
This directory contains a set of scripts for testing compatibility of PyCall.jl
4+
with [PackageCompiler.jl](https://github.com/JuliaLang/PackageCompiler.jl).
5+
6+
See `.travis.yml` for how it is actually used.
7+
8+
## How to compile system image
9+
10+
To create a system image with PyCall.jl, run `aot/compile.jl` (which
11+
is executable in *nix):
12+
13+
```sh
14+
aot/compile.jl --color=yes
15+
JULIA=PATH/TO/CUSTOM/julia aot/compile.jl # to specify a julia binary
16+
```
17+
18+
Resulting system image is stored at `aot/sys.so`.
19+
20+
## How to use compiled system image
21+
22+
To use compiled system image, run `aot/julia.sh`, e.g.:
23+
24+
```sh
25+
aot/julia.sh --compiled-modules=no --startup-file=no
26+
```
27+
28+
Note that Julia binary used for compiling the system image is cached
29+
and automatically picked by `aot/julia.sh`. You don't need to specify
30+
the Julia binary.
31+
32+
Since Julia needs to re-compile packages when switching system images,
33+
it is recommended to pass `--compiled-modules=no` if you are using it
34+
in your machine with a standard Julia setup.

aot/compile.jl

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#!/bin/bash
2+
# -*- mode: julia -*-
3+
#=
4+
exec "${JULIA:-julia}" "$@" ${BASH_SOURCE[0]}
5+
=#
6+
7+
using Pkg
8+
Pkg.activate(@__DIR__)
9+
Pkg.add("MacroTools")
10+
Pkg.develop(PackageSpec(path=dirname(@__DIR__)))
11+
Pkg.build("PyCall")
12+
Pkg.activate()
13+
14+
using PackageCompiler
15+
sysout, _curr_syso = compile_incremental(
16+
joinpath(@__DIR__, "Project.toml"),
17+
joinpath(@__DIR__, "precompile.jl"),
18+
)
19+
20+
pysysout = joinpath(@__DIR__, basename(sysout))
21+
cp(sysout, pysysout, force=true)
22+
23+
write(joinpath(@__DIR__, "_julia_path"), Base.julia_cmd().exec[1])
24+
25+
@info "System image: $pysysout"

aot/julia.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/bin/bash
2+
thisdir="$(dirname "${BASH_SOURCE[0]}")"
3+
JULIA="${JULIA:-$(cat "$thisdir/_julia_path")}"
4+
exec "${JULIA}" --sysimage="$thisdir/sys.so" "$@"

aot/precompile.jl

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Activate ./Project.toml. Excluding `"@v#.#"` from `Base.LOAD_PATH`
2+
# to make compilation more reproducible.
3+
using Pkg
4+
empty!(Base.LOAD_PATH)
5+
append!(Base.LOAD_PATH, ["@", "@stdlib"])
6+
Pkg.activate(@__DIR__)
7+
8+
# Manually invoking `__init__` to workaround:
9+
# https://github.com/JuliaLang/julia/issues/22910
10+
11+
import MacroTools
12+
isdefined(MacroTools, :__init__) && MacroTools.__init__()
13+
14+
using PyCall
15+
PyCall.__init__()
16+
PyCall.pyimport("sys")[:executable]

aot/runtests.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/bin/bash
2+
thisdir="$(dirname "${BASH_SOURCE[0]}")"
3+
exec "$thisdir/julia.sh" --startup-file=no --color=yes -e '
4+
using Pkg
5+
Pkg.test("PyCall")
6+
'

src/pyinit.jl

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const pyxrange = Ref{PyPtr}(0)
2626

2727
function pyjlwrap_init()
2828
# PyMemberDef stores explicit pointers, hence must be initialized at runtime
29+
empty!(pyjlwrap_members) # for AOT
2930
push!(pyjlwrap_members, PyMemberDef(pyjlwrap_membername,
3031
T_PYSSIZET, sizeof_pyjlwrap_head, READONLY,
3132
pyjlwrap_doc),
@@ -125,6 +126,17 @@ function Py_Finalize()
125126
end
126127

127128
function __init__()
129+
# Clear out global states. This is required only for PyCall
130+
# AOT-compiled into system image.
131+
_finalized[] = false
132+
empty!(_namespaces)
133+
empty!(eventloops)
134+
empty!(npy_api)
135+
empty!(pycall_gc)
136+
empty!(pyexc)
137+
empty!(pytype_queries)
138+
empty!(permanent_strings)
139+
128140
# sanity check: in Pkg for Julia 0.7+, the location of Conda can change
129141
# if e.g. you checkout Conda master, and we'll need to re-build PyCall
130142
# for something like pyimport_conda to continue working.

0 commit comments

Comments
 (0)