Browse Source

Installer and sysimage builder

(Only supports UNIX for now)
master
François Févotte 3 years ago
parent
commit
cae51a0e91
11 changed files with 191 additions and 76 deletions
  1. 2
    0
      .gitignore
  2. 1
    0
      Project.toml
  3. 60
    0
      README.md
  4. 0
    1
      build/Project.toml
  5. 3
    20
      build/make.jl
  6. 2
    3
      build/precomp.jl
  7. 1
    1
      main.jl
  8. 3
    0
      resources/run.sh
  9. 0
    3
      run/Project.toml
  10. 0
    30
      run/jld2view
  11. 119
    18
      src/JLD2View.jl

+ 2
- 0
.gitignore View File

@@ -0,0 +1,2 @@
Manifest.toml
build/Manifest.toml

+ 1
- 0
Project.toml View File

@@ -9,3 +9,4 @@ JLD2 = "033835bb-8acc-5ee8-8aae-3f567f8a3819"
Mustache = "ffc61752-8dc7-55ee-8c37-f3e9cdd09e70"
NativeFileDialog = "e1fe445b-aa65-4df4-81c1-2041507f0fd4"
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
Scratch = "6c6a2e73-6563-6170-7368-637461726353"

+ 60
- 0
README.md View File

@@ -0,0 +1,60 @@
# JLD2View.jl

Electron-based data viewer for JLD2 files.

## Programmatic usage

In a julia session:
```julia
julia> using JLD2View

julia> jld2_view("path/to/file.jld2")
```


## Command-line

Depending on your workflow, using `JLD2View` from the command-line might be more
convenient. It can also more easily benefit from the use of a custom system
image to reduce latency.

First, install the JLD2View command-line interface:
```julia
julia> import JLD2View

help?> JLD2View.install
JLD2View.install(; kwargs...)

Install the command-line interface.
Keyword arguments:
- `command`: name of the executable command, defaults to `jld2view`.
- `destdir`: writable directory (available in PATH) for the executable, defaults to `~/.julia/bin`.
- `julia`: path to the julia executable, defaults to the path of the currently running julia.
- `julia_flags`: vector of command-line flags for the julia executable, defaults to `$JULIA_FLAGS_DEFAULT`.
- `force`: boolean used to allow overwriting existing commands, defaults to `false`.
- `compile`: boolean to enable the compilation of a custom system image, defaults to `true`.

julia> JLD2View.install()
# [...]
┌ Info: Installing the JLD2View command-line interface
│ launcher = "~/.julia/bin/jld2view"
│ julia = "/path/to/julia/1.7.2/bin/julia"
│ flags = "--startup-file=no --quiet --sysimage=~/.julia/scratchspaces/a494b24a-6d3b-4871-b66b-22839c4c06d4/sysimages/sysimage.so"
└ projectdir = "/path/to/JLD2View/"
```

Make sure that `destdir` (`~/.julia/bin` by default) is in your `PATH`. Then,
you can use `JLD2View` from the command-line

```sh
shell$ jld2view path/to/file.jld2
```

or, simply

```sh
shell$ jld2view
```

In this last case, you'll be prompted to pick a JLD2 file interactively.

+ 0
- 1
build/Project.toml View File

@@ -1,3 +1,2 @@
[deps]
PackageCompiler = "9b87118b-4619-50d2-8e1e-99f35a4d4d9d"
Scratch = "6c6a2e73-6563-6170-7368-637461726353"

+ 3
- 20
build/make.jl View File

@@ -1,28 +1,11 @@
using Pkg; Pkg.activate(@__DIR__); Pkg.instantiate()
using PackageCompiler

using Scratch

Pkg.activate(joinpath(@__DIR__, ".."))
Pkg.activate(joinpath(@__DIR__, ".."), io=devnull)
using JLD2View
scratchdir = get_scratch!(JLD2View, "sysimages")

# List of packages to include in the sysimage
packages = :JLD2View #Symbol.(keys(Pkg.project().dependencies))

# Sysimage base name
sysimage_name = "sysimage"

# Sysimage extension
sysimage_ext = if Sys.iswindows()
".dll"
elseif Sys.isapple()
".dylib"
else
".so"
end

sysimage_path = joinpath(scratchdir, sysimage_name * sysimage_ext)
packages = :JLD2View
sysimage_path = ARGS[1]

@info("Creating system image",
name=sysimage_path,

+ 2
- 3
build/precomp.jl View File

@@ -12,7 +12,7 @@ mktempdir() do path
fname = joinpath(path, "tmp.jld2")
JLD2.save(fname, d)

(viewer, task) = jld2_view(fname, sync=false)
viewer = jld2_view(fname, sync=false)
sleep(1)

function simulate(action, arg)
@@ -34,6 +34,5 @@ mktempdir() do path
end; simulate(:back, 0)

println("Terminating event loop")
schedule(task, :stop, error=true)
wait(task)
JLD2View.stop(viewer)
end

+ 1
- 1
main.jl View File

@@ -1,4 +1,4 @@
import Pkg; Pkg.activate(@__DIR__, io=IOBuffer())
import Pkg; Pkg.activate(@__DIR__, io=devnull)
using JLD2View
using NativeFileDialog


+ 3
- 0
resources/run.sh View File

@@ -0,0 +1,3 @@
#!/bin/bash

exec {{{:JULIA}}} --project="{{{:PROJECT}}}" {{{:FLAGS}}} "{{{:SCRIPT}}}" "$@"

+ 0
- 3
run/Project.toml View File

@@ -1,3 +0,0 @@
[deps]
JLD2View = "a494b24a-6d3b-4871-b66b-22839c4c06d4"
Scratch = "6c6a2e73-6563-6170-7368-637461726353"

+ 0
- 30
run/jld2view View File

@@ -1,30 +0,0 @@
#!/bin/bash
#=
exec julia --color=no --startup-file=no --compile=min --optimize=0 "${BASH_SOURCE[0]}" "$@"
=#

using Pkg
Pkg.activate(@__DIR__, io=IOBuffer())
using Scratch

julia = Base.julia_cmd()[1]
script = joinpath(@__DIR__, "..", "main.jl")
args = String["--compile=min", "--optimize=0"]

scratchdir = get_scratch!(Pkg.project().dependencies["JLD2View"], "sysimages")
sysimage = joinpath(scratchdir, "sysimage.so")
if (ispath(sysimage))
push!(args, "--sysimage=$sysimage")
end

cmd = `$julia $args $script $ARGS`
try
run(cmd)
catch e
process = first(e.procs)
exit(process.exitcode)
end

# Local Variables:
# mode: julia
# End:

+ 119
- 18
src/JLD2View.jl View File

@@ -4,6 +4,7 @@ using Electron
using Mustache
using JLD2
using Printf
using Scratch

export jld2_view

@@ -46,9 +47,12 @@ mutable struct Viewer
path
keys

task

function Viewer(path)
viewer = new()
viewer.fname = last(splitpath(path))
viewer.task = @async true

create_win!(viewer)

@@ -59,6 +63,8 @@ mutable struct Viewer
end
end

Base.show(io::IO, viewer::Viewer) = print(io, """Viewer("$(viewer.fname)")""")

function get_data(viewer, path=viewer.path)
f(dict, ::Tuple{}) = dict
f(dict, path) = f(dict[first(path)], Base.tail(path))
@@ -97,15 +103,17 @@ function update!(viewer, path)
FNAME = viewer.fname,
PATH = [(idx=i, name=x) for (i,x) in enumerate(path)],
WARNING = warning)
open("index.html", "w") do f
write(f, html)
end
Electron.load(viewer.win, html)
end
return viewer
end

function run!(viewer)
function (viewer::Viewer)(; sync=true)
if !istaskdone(viewer.task)
@error "Viewer is already running."
return viewer
end

if !viewer.app.exists
@warn "Window does not exist any more. Creating a new one."
create_win!(viewer)
@@ -114,7 +122,7 @@ function run!(viewer)

ch = msgchannel(viewer.win)

try
viewer.task = @async try
while true
msg = try
take!(ch)
@@ -139,10 +147,15 @@ function run!(viewer)
update!(viewer, path)
end
end
catch e
e == :stop || rethrow()
finally
close(viewer.app)
sleep(0.1)
end

sync && wait(viewer.task)
return viewer
end

send_signal(viewer, action::Symbol, args...) = send_signal(viewer, Val(action), args...)
@@ -156,28 +169,116 @@ function send_signal(viewer, ::Val{:select}, idx::Integer)
end

function send_signal(viewer, ::Val{:select_key}, key)
idx = findfirst(==(key), viewer.keys)
@assert !isnothing(idx)
idx = nothing
for i in 1:5
idx = findfirst(==(key), viewer.keys)
isnothing(idx) || break

@warn "Could not find key to select. Retrying in 1s." key "try"=i
sleep(1)
end
send_signal(viewer, :select, idx)
end

function stop(viewer::Viewer)
schedule(viewer.task, :stop, error=true)
wait(viewer.task)
end


"""
jld2_view(path; sync=true) -> viewer

Open a window showing the contents of the JLD2 data file located at the given path.

The returned `viewer` object can be re-used to re-display the contents of the same file:

viewer(; sync=true)


If `sync` is `true` (the default), wait for the window to be closed before
returning.

Setting `sync` to `false` will however return immediately. In that case, the
application window can be stopped using:

JLD2View.stop(viewer)

"""
function jld2_view(fname; sync=true)
viewer = Viewer(fname)
viewer(; sync)
end

task = @async try
run!(viewer)
catch e
e == :stop || rethrow()



const JULIA_FLAGS_DEFAULT = ["--startup-file=no", "--quiet"]

"""
JLD2View.install(; kwargs...)

Install the command-line interface.

*Keyword arguments:*
- `command`: name of the executable command, defaults to `jld2view`.
- `destdir`: writable directory (available in PATH) for the executable, defaults to `~/.julia/bin`.
- `julia`: path to the julia executable, defaults to the path of the currently running julia.
- `julia_flags`: vector of command-line flags for the julia executable, defaults to `$JULIA_FLAGS_DEFAULT`.
- `force`: boolean used to allow overwriting existing commands, defaults to `false`.
- `compile`: boolean to enable the compilation of a custom system image, defaults to `true`.
"""
function install(;
command = "jld2view",
destdir = joinpath(first(DEPOT_PATH), "bin"),
julia = first(Base.julia_cmd()),
julia_flags = JULIA_FLAGS_DEFAULT,
force = false,
compile = true)
flags = copy(julia_flags)
projectdir = abspath(joinpath(@__DIR__, ".."))

destdir = abspath(expanduser(destdir))
launcher = joinpath(destdir, command)
if ispath(launcher) && !force
error("File `launcher` already exists; please set `force=true` if you really want to overwrite it.")
end

sync && wait(task)
return (viewer, task)
end
if compile
make = joinpath(projectdir, "build", "make.jl")
scratchdir = get_scratch!(@__MODULE__, "sysimages")

# Sysimage base name
sysimage_name = "sysimage"

# Sysimage extension
sysimage_ext = if Sys.iswindows()
".dll"
elseif Sys.isapple()
".dylib"
else
".so"
end

sysimage = joinpath(scratchdir, sysimage_name*sysimage_ext)
run(`$julia $make $sysimage`)

function install()
julia = first(Base.julia_cmd())
make = joinpath(@__DIR__, "..", "build", "make.jl")
run(`$julia $make`)
push!(flags, "--sysimage=$sysimage")
end

flags = join(flags, " ")
@info "Installing the JLD2View command-line interface" launcher julia flags projectdir

tmpl = Mustache.load(joinpath(projectdir, "resources", "run.sh")) # TODO: provide a run.bat template as well?
mkpath(destdir)
open(launcher, "w") do f
write(f, render(tmpl,
JULIA = julia,
FLAGS = flags,
PROJECT = projectdir,
SCRIPT = joinpath(projectdir, "main.jl")))
end
chmod(launcher, 0o0100775)
end

end # module

Loading…
Cancel
Save