mirror of https://github.com/fredrikekre/Runic.jl
9 changed files with 470 additions and 86 deletions
@ -0,0 +1,26 @@
@@ -0,0 +1,26 @@
|
||||
JULIA ?= /opt/julia/julia-c/bin/julia |
||||
JULIAC ?= $(shell $(JULIA) -e 'print(normpath(joinpath(Sys.BINDIR, Base.DATAROOTDIR, "julia", "juliac.jl")))') |
||||
RUNIC_SRCFILES := $(wildcard ../src/*.jl) |
||||
|
||||
runicc: runicc.jl $(RUNIC_SRCFILES) invalidate-precompile-cache |
||||
$(JULIA) $(JULIAC) --output-exe $@ --trim=unsafe-warn $< |
||||
|
||||
clean: |
||||
rm runicc |
||||
|
||||
# Prune cached precompile files for Runic. This is needed because there are
|
||||
# (compile time) branches in the Runic source code which depends on whether
|
||||
# Runic is compiled or not. It looks like juliac will use existing cache files
|
||||
# but not produce any so there is no need to prune them again after compilation
|
||||
# to force regular usage to recompile.
|
||||
invalidate-precompile-cache: |
||||
$(JULIA) -e ' \
|
||||
ji = Base.compilecache_path(Base.PkgId(Base.UUID("62bfec6d-59d7-401d-8490-b29ee721c001"), "Runic")); \
|
||||
if ji !== nothing; \
|
||||
isfile(ji) && (@info "Deleting precompile file $$(ji)"; rm(ji)); \
|
||||
so = splitext(ji)[1] * "." * Base.BinaryPlatforms.platform_dlext(); \
|
||||
isfile(so) && (@info "Deleting pkgimage file $$(so)"; rm(so)); \
|
||||
end' |
||||
|
||||
|
||||
.PHONY: invalidate-precompile-cache clean |
||||
@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
|
||||
module RunicC |
||||
|
||||
using Runic: Runic |
||||
|
||||
# TODO: Why do we need this shim? Wouldn't it be possible to just compile `src/Runic.jl`? |
||||
Base.@ccallable function main()::Cint |
||||
argv = String[] |
||||
return Runic.main(argv) |
||||
end |
||||
|
||||
end |
||||
@ -0,0 +1,203 @@
@@ -0,0 +1,203 @@
|
||||
# SPDX-License-Identifier: MIT |
||||
|
||||
# juliac-compatible replacement for `read(stdin)` |
||||
function read_juliac() |
||||
bytes = UInt8[] |
||||
size = 1 |
||||
nmemb = 1024 |
||||
buf = zeros(UInt8, nmemb) |
||||
file = Libc.FILE(RawFD(0), "r") # FILE constructor calls `fdopen` |
||||
local fread, feof, ferror # Silence of the Langs(erver) |
||||
while true |
||||
nread = @ccall fread(buf::Ptr{UInt8}, size::Csize_t, nmemb::Cint, file::Ptr{Libc.FILE})::Csize_t |
||||
append!(bytes, @view(buf[1:nread])) |
||||
if nread < nmemb |
||||
if (@ccall feof(file::Ptr{Libc.FILE})::Cint) != 0 |
||||
close(file) |
||||
break |
||||
else |
||||
@assert (@ccall ferror(file::Ptr{Libc.FILE})::Cint) != 0 |
||||
close(file) |
||||
error("ferror: fread failed") |
||||
end |
||||
end |
||||
end |
||||
return bytes |
||||
end |
||||
|
||||
# juliac-compatible `Base.printstyled` that simply forces color |
||||
# TODO: detect color support (and maybe support `--color=(yes|no)`?), right now color is |
||||
# forced. For juliac we can detect whether stdout/stderr is a tty with |
||||
# `(@ccall isatty(RawFD(1)::Cint)::Cint) == 1`. |
||||
function printstyled_juliac(io::IO, str::String; bold = false, color::Symbol = :normal) |
||||
@assert io === Core.stdout || io === Core.stderr |
||||
@assert !occursin('\n', str) |
||||
color === :red && write(io, "\e[31m") |
||||
color === :green && write(io, "\e[32m") |
||||
color === :blue && write(io, "\e[34m") |
||||
color === :normal && write(io, "\e[0m") |
||||
bold && write(io, "\e[1m") |
||||
print(io, str) |
||||
bold && write(io, "\e[22m") |
||||
color in (:red, :green, :blue) && write(io, "\e[39m") |
||||
return |
||||
end |
||||
|
||||
# juliac-compatible `Base.showerror` |
||||
function sprint_showerror_juliac(err::Exception) |
||||
if err isa SystemError |
||||
return "SystemError: " * err.prefix * ": " * Libc.strerror(err.errnum) |
||||
elseif err isa AssertionError |
||||
# sprint uses dynamic dispatch |
||||
io = IOBuffer() |
||||
showerror(io, err) |
||||
return String(take!(io)) |
||||
else |
||||
return string(typeof(err)) |
||||
end |
||||
end |
||||
|
||||
# juliac-compatible `Base.tempdir` and `Base.mktempdir` without logging and deferred cleanup |
||||
function tempdir_juliac() |
||||
buf = Base.StringVector(Base.Filesystem.AVG_PATH - 1) |
||||
sz = Base.RefValue{Csize_t}(length(buf) + 1) |
||||
while true |
||||
rc = ccall(:uv_os_tmpdir, Cint, (Ptr{UInt8}, Ptr{Csize_t}), buf, sz) |
||||
if rc == 0 |
||||
resize!(buf, sz[]) |
||||
break |
||||
elseif rc == Base.UV_ENOBUFS |
||||
resize!(buf, sz[] - 1) |
||||
else |
||||
Base.uv_error("tempdir()", rc) |
||||
end |
||||
end |
||||
tempdir = String(buf) |
||||
return tempdir |
||||
end |
||||
|
||||
function mktempdir_juliac() |
||||
parent = tempdir_juliac() |
||||
prefix = Base.Filesystem.temp_prefix |
||||
if isempty(parent) || occursin(Base.Filesystem.path_separator_re, parent[end:end]) |
||||
tpath = "$(parent)$(prefix)XXXXXX" |
||||
else |
||||
tpath = "$(parent)$(Base.Filesystem.path_separator)$(prefix)XXXXXX" |
||||
end |
||||
req = Libc.malloc(Base._sizeof_uv_fs) |
||||
try |
||||
ret = ccall( |
||||
:uv_fs_mkdtemp, Cint, |
||||
(Ptr{Cvoid}, Ptr{Cvoid}, Cstring, Ptr{Cvoid}), |
||||
C_NULL, req, tpath, C_NULL |
||||
) |
||||
if ret < 0 |
||||
Base.Filesystem.uv_fs_req_cleanup(req) |
||||
Base.uv_error("mktempdir($(repr(parent)))", ret) |
||||
end |
||||
path = unsafe_string(ccall(:jl_uv_fs_t_path, Cstring, (Ptr{Cvoid},), req)) |
||||
Base.Filesystem.uv_fs_req_cleanup(req) |
||||
return path |
||||
finally |
||||
Libc.free(req) |
||||
end |
||||
end |
||||
|
||||
function mktempdir_juliac(f::F) where {F} |
||||
tmpdir = mktempdir_juliac() |
||||
try |
||||
f(tmpdir) |
||||
finally |
||||
try |
||||
rm(tmpdir; force = true, recursive = true) |
||||
catch |
||||
end |
||||
end |
||||
return |
||||
end |
||||
|
||||
# juliac-compatible `run(::Base.CmdRedirect)` where both stdout and stderr are redirected |
||||
# and read. |
||||
function run_juliac(cmd::Base.CmdRedirect) |
||||
# Unpack the redirection layers |
||||
@assert cmd.stream_no == 2 |
||||
@assert cmd.handle::Core.CoreSTDERR === Core.stderr |
||||
cmd′ = cmd.cmd::Base.CmdRedirect |
||||
@assert cmd′.stream_no == 1 |
||||
@assert cmd′.handle::Core.CoreSTDERR === Core.stderr |
||||
cmd′′ = cmd′.cmd::Cmd |
||||
@assert cmd′′.ignorestatus |
||||
argv = cmd′′.exec |
||||
dir = cmd′′.dir |
||||
# Run the command |
||||
bytes = pipe_fork_exec(argv, dir) |
||||
# Write output |
||||
write(Core.stderr, bytes) |
||||
return |
||||
end |
||||
|
||||
function WIFEXITED(status) |
||||
return (status[] & 0x7f) == 0 |
||||
end |
||||
function WEXITSTATUS(status) |
||||
return (status[] & 0xff00) >> 8 |
||||
end |
||||
|
||||
function pipe_fork_exec(argv::Vector{String}, dir::String) |
||||
local pipe, fork, dup2, chdir, execv, waitpid # Silence of the Langs(erver) |
||||
# Set up the pipe |
||||
fds = Vector{Cint}(undef, 2) |
||||
READ_END, WRITE_END = 1, 2 |
||||
err = @ccall pipe(fds::Ref{Cint})::Cint |
||||
err == -1 && systemerror("pipe") |
||||
|
||||
# Fork |
||||
cpid = @ccall fork()::Cint |
||||
cpid == -1 && systemerror("fork") |
||||
|
||||
# Handle the child process |
||||
if cpid == 0 |
||||
# Close read end of the pipe |
||||
err = @ccall close(fds[READ_END]::Cint)::Cint |
||||
err == -1 && systemerror("close") |
||||
# Duplicate write end of the pipe to stdout and stderr |
||||
STDOUT_FILENO, STDERR_FILENO = 1, 2 |
||||
err = @ccall dup2(fds[WRITE_END]::Cint, STDOUT_FILENO::Cint)::Cint |
||||
err == -1 && systemerror("dup2") |
||||
err = @ccall dup2(fds[WRITE_END]::Cint, STDERR_FILENO::Cint)::Cint |
||||
err = @ccall close(fds[WRITE_END]::Cint)::Cint # No longer needed |
||||
err == -1 && systemerror("close") |
||||
# Change directory |
||||
err = @ccall chdir(dir::Cstring)::Cint |
||||
err == 0 || systemerror("chdir") |
||||
# Execute the command |
||||
@ccall execv(argv[1]::Cstring, argv::Ref{Cstring})::Cint |
||||
systemerror("execv") |
||||
end |
||||
|
||||
# Continuing the parent process |
||||
|
||||
# Close write end of the pipe |
||||
err = @ccall close(fds[WRITE_END]::Cint)::Cint |
||||
err == -1 && systemerror("close") |
||||
bytes = UInt8[] |
||||
buf = Vector{UInt8}(undef, 1024) |
||||
while true |
||||
nread = @ccall read(fds[READ_END]::Cint, buf::Ptr{Cvoid}, 1024::Csize_t)::Cssize_t |
||||
nread == -1 && systemerror("read") |
||||
nread == 0 && break # eof |
||||
append!(bytes, @view(buf[1:nread])) |
||||
end |
||||
err = @ccall close(fds[READ_END]::Cint)::Cint # Close the read end of the pipe |
||||
err == -1 && systemerror("close") |
||||
|
||||
# Check exit status of the child |
||||
status = Ref{Cint}() |
||||
wpid = @ccall waitpid(cpid::Cint, status::Ref{Cint}, 0::Cint)::Cint |
||||
wpid == -1 && systemerror("waitpid") |
||||
if !WIFEXITED(status) |
||||
error("child process did not exit normally") |
||||
end |
||||
# crc = WEXITSTATUS(status) # ignore this like `ignorestatus(cmd)` |
||||
return bytes |
||||
end |
||||
Loading…
Reference in new issue