Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move startup code to new Client.jl stdlib #51350

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ $(build_depsbindir)/stringreplace: $(JULIAHOME)/contrib/stringreplace.c | $(buil
@$(call PRINT_CC, $(HOSTCC) -o $(build_depsbindir)/stringreplace $(JULIAHOME)/contrib/stringreplace.c)

julia-base-cache: julia-sysimg-$(JULIA_BUILD_MODE) | $(DIRS) $(build_datarootdir)/julia
@JULIA_BINDIR=$(call cygpath_w,$(build_bindir)) WINEPATH="$(call cygpath_w,$(build_bindir));$$WINEPATH" \
@JULIA_BINDIR=$(call cygpath_w,$(build_bindir)) JULIA_MINIMAL_CLIENT=1 WINEPATH="$(call cygpath_w,$(build_bindir));$$WINEPATH" \
$(call spawn, $(JULIA_EXECUTABLE) --startup-file=no $(call cygpath_w,$(JULIAHOME)/etc/write_base_cache.jl) \
$(call cygpath_w,$(build_datarootdir)/julia/base.cache))

Expand Down
195 changes: 16 additions & 179 deletions base/client.jl
Original file line number Diff line number Diff line change
Expand Up @@ -227,13 +227,12 @@ end
incomplete_tag(exc::Meta.ParseError) = incomplete_tag(exc.detail)

cmd_suppresses_program(cmd) = cmd in ('e', 'E')

# Minimal exec_options for precompilation
function exec_options(opts)
quiet = (opts.quiet != 0)
startup = (opts.startupfile != 2)
history_file = (opts.historyfile != 0)
color_set = (opts.color != 0) # --color!=auto
global have_color = color_set ? (opts.color == 1) : nothing # --color=on
global is_interactive = (opts.isinteractive != 0)
global have_color = false
global is_interactive = false

# pre-process command line argument list
arg_is_program = !isempty(ARGS)
Expand All @@ -245,13 +244,6 @@ function exec_options(opts)
repl = false
elseif cmd == 'L'
# nothing
elseif cmd == 'B' # --bug-report
# If we're doing a bug report, don't load anything else. We will
# spawn a child in which to execute these options.
let InteractiveUtils = load_InteractiveUtils()
InteractiveUtils.report_bug(arg)
end
return nothing
else
@warn "Unexpected command -$cmd'$arg'"
end
Expand All @@ -260,30 +252,6 @@ function exec_options(opts)
# remove filename from ARGS
global PROGRAM_FILE = arg_is_program ? popfirst!(ARGS) : ""

# Load Distributed module only if any of the Distributed options have been specified.
distributed_mode = (opts.worker == 1) || (opts.nprocs > 0) || (opts.machine_file != C_NULL)
if distributed_mode
let Distributed = require(PkgId(UUID((0x8ba89e20_285c_5b6f, 0x9357_94700520ee1b)), "Distributed"))
Core.eval(Main, :(const Distributed = $Distributed))
Core.eval(Main, :(using .Distributed))
end

invokelatest(Main.Distributed.process_opts, opts)
end

interactiveinput = (repl || is_interactive::Bool) && isa(stdin, TTY)
is_interactive::Bool |= interactiveinput

# load ~/.julia/config/startup.jl file
if startup
try
load_julia_startup()
catch
invokelatest(display_error, scrub_repl_backtrace(current_exceptions()))
!(repl || is_interactive::Bool) && exit(1)
end
end

# process cmds list
for (cmd, arg) in cmds
if cmd == 'e'
Expand All @@ -292,24 +260,13 @@ function exec_options(opts)
invokelatest(show, Core.eval(Main, parse_input_line(arg)))
println()
elseif cmd == 'L'
# load file immediately on all processors
if !distributed_mode
include(Main, arg)
else
# TODO: Move this logic to Distributed and use a callback
@sync for p in invokelatest(Main.procs)
@async invokelatest(Main.remotecall_wait, include, p, Main, arg)
end
end
include(Main, arg)
end
end

# load file
if arg_is_program
# program
if !is_interactive::Bool
exit_on_sigint(true)
end
exit_on_sigint(true)
try
if PROGRAM_FILE == "-"
include_string(Main, read(stdin, String), "stdin")
Expand All @@ -318,52 +275,12 @@ function exec_options(opts)
end
catch
invokelatest(display_error, scrub_repl_backtrace(current_exceptions()))
if !is_interactive::Bool
exit(1)
end
exit(1)
end
end
if repl || is_interactive::Bool
b = opts.banner
auto = b == -1
banner = b == 0 || (auto && !interactiveinput) ? :no :
b == 1 || (auto && interactiveinput) ? :yes :
:short # b == 2
run_main_repl(interactiveinput, quiet, banner, history_file, color_set)
end
nothing
end

function _global_julia_startup_file()
# If the user built us with a specific Base.SYSCONFDIR, check that location first for a startup.jl file
# If it is not found, then continue on to the relative path based on Sys.BINDIR
BINDIR = Sys.BINDIR
SYSCONFDIR = Base.SYSCONFDIR
if !isempty(SYSCONFDIR)
p1 = abspath(BINDIR, SYSCONFDIR, "julia", "startup.jl")
isfile(p1) && return p1
end
p2 = abspath(BINDIR, "..", "etc", "julia", "startup.jl")
isfile(p2) && return p2
return nothing
end

function _local_julia_startup_file()
if !isempty(DEPOT_PATH)
path = abspath(DEPOT_PATH[1], "config", "startup.jl")
isfile(path) && return path
end
return nothing
end

function load_julia_startup()
global_file = _global_julia_startup_file()
(global_file !== nothing) && include(Main, global_file)
local_file = _local_julia_startup_file()
(local_file !== nothing) && include(Main, local_file)
return nothing
end

const repl_hooks = []

"""
Expand All @@ -388,96 +305,9 @@ function __atreplinit(repl)
end
_atreplinit(repl) = invokelatest(__atreplinit, repl)

function load_InteractiveUtils(mod::Module=Main)
# load interactive-only libraries
if !isdefined(mod, :InteractiveUtils)
try
let InteractiveUtils = require(PkgId(UUID(0xb77e0a4c_d291_57a0_90e8_8db25a27a240), "InteractiveUtils"))
Core.eval(mod, :(const InteractiveUtils = $InteractiveUtils))
Core.eval(mod, :(using .InteractiveUtils))
return InteractiveUtils
end
catch ex
@warn "Failed to import InteractiveUtils into module $mod" exception=(ex, catch_backtrace())
end
return nothing
end
return getfield(mod, :InteractiveUtils)
end

# set by Client.jl
global active_repl

# run the requested sort of evaluation loop on stdio
function run_main_repl(interactive::Bool, quiet::Bool, banner::Symbol, history_file::Bool, color_set::Bool)
load_InteractiveUtils()

if interactive && isassigned(REPL_MODULE_REF)
invokelatest(REPL_MODULE_REF[]) do REPL
term_env = get(ENV, "TERM", @static Sys.iswindows() ? "" : "dumb")
global current_terminfo = load_terminfo(term_env)
term = REPL.Terminals.TTYTerminal(term_env, stdin, stdout, stderr)
banner == :no || Base.banner(term, short=banner==:short)
if term.term_type == "dumb"
repl = REPL.BasicREPL(term)
quiet || @warn "Terminal not fully functional"
else
repl = REPL.LineEditREPL(term, get(stdout, :color, false), true)
repl.history_file = history_file
end
global active_repl = repl
# Make sure any displays pushed in .julia/config/startup.jl ends up above the
# REPLDisplay
pushdisplay(REPL.REPLDisplay(repl))
_atreplinit(repl)
REPL.run_repl(repl, backend->(global active_repl_backend = backend))
end
else
# otherwise provide a simple fallback
if interactive && !quiet
@warn "REPL provider not available: using basic fallback"
end
banner == :no || Base.banner(short=banner==:short)
let input = stdin
if isa(input, File) || isa(input, IOStream)
# for files, we can slurp in the whole thing at once
ex = parse_input_line(read(input, String))
if Meta.isexpr(ex, :toplevel)
# if we get back a list of statements, eval them sequentially
# as if we had parsed them sequentially
for stmt in ex.args
eval_user_input(stderr, stmt, true)
end
body = ex.args
else
eval_user_input(stderr, ex, true)
end
else
while isopen(input) || !eof(input)
if interactive
print("julia> ")
flush(stdout)
end
try
line = ""
ex = nothing
while !eof(input)
line *= readline(input, keep=true)
ex = parse_input_line(line)
if !(isa(ex, Expr) && ex.head === :incomplete)
break
end
end
eval_user_input(stderr, ex, true)
catch err
isa(err, InterruptException) ? print("\n\n") : rethrow()
end
end
end
end
end
nothing
end

# MainInclude exists to hide Main.include and eval from `names(Main)`.
baremodule MainInclude
using ..Base
Expand Down Expand Up @@ -549,7 +379,14 @@ function _start()
# clear any postoutput hooks that were saved in the sysimage
empty!(Base.postoutput_hooks)
try
exec_options(JLOptions())
if Base.generating_output() || parse(Bool, get(ENV, "JULIA_MINIMAL_CLIENT", "0"))
exec_options(JLOptions())
else
let Client = require(PkgId(UUID((0x22d9f1f7_1f08_4fdd, 0xa466_30825a29bd37)), "Client"))
Core.eval(Main, :(const Client = $Client))
invokelatest(Client.exec_options, JLOptions())
end
end
catch
invokelatest(display_error, scrub_repl_backtrace(current_exceptions()))
exit(1)
Expand Down
44 changes: 6 additions & 38 deletions base/sysimg.jl
Original file line number Diff line number Diff line change
Expand Up @@ -31,46 +31,14 @@ let
# Run with the `--exclude-jlls` option to filter out all JLL packages
stdlibs = [
# No dependencies
:ArgTools,
:Artifacts,
:Base64,
:CRC32c,
:FileWatching,
:Libdl,
:Logging,
:Mmap,
:NetworkOptions,
:SHA,
:Serialization,
:Sockets,
:Unicode,
:FileWatching, # used by loading.jl -- implicit assumption that init runs
:Libdl, # Transitive through LinAlg
:Artifacts, # Transitive through LinAlg
:SHA, # transitive through Random

# 1-depth packages
:LinearAlgebra,
:Markdown,
:Printf,
:Random,
:Tar,

# 2-depth packages
:Dates,
:Future,
:InteractiveUtils,
:LibGit2,
:UUIDs,

# 3-depth packages
:REPL,
:TOML,

# 4-depth packages
:LibCURL,

# 5-depth packages
:Downloads,

# 6-depth packages
:Pkg,
:LinearAlgebra, # Commits type-piracy and GEMM
:Random, # Can't be removed due to rand being exported by Base
]
# PackageCompiler can filter out stdlibs so it can be empty
maxlen = maximum(textwidth.(string.(stdlibs)); init=0)
Expand Down
18 changes: 9 additions & 9 deletions contrib/generate_precompile.jl
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ UP_ARROW = "\e[A"
DOWN_ARROW = "\e[B"

hardcoded_precompile_statements = """
# loading.jl
precompile(Base.__require_prelocked, (Base.PkgId, Nothing))
precompile(Base._require, (Base.PkgId, Nothing))

# used by Revise.jl
precompile(Tuple{typeof(Base.parse_cache_header), String})
precompile(Base.read_dependency_src, (String, String))
Expand Down Expand Up @@ -127,8 +131,7 @@ precompile_script = """

julia_exepath() = joinpath(Sys.BINDIR, Base.julia_exename())

have_repl = haskey(Base.loaded_modules,
Base.PkgId(Base.UUID("3fa0cd96-eef1-5676-8a61-b3b8758bbffb"), "REPL"))
have_repl = false
if have_repl
hardcoded_precompile_statements *= """
precompile(Tuple{typeof(getproperty), REPL.REPLBackend, Symbol})
Expand All @@ -153,9 +156,7 @@ if Artifacts !== nothing
"""
end

Pkg = get(Base.loaded_modules,
Base.PkgId(Base.UUID("44cfe95a-1eb2-52ea-b672-e2afdf69b78f"), "Pkg"),
nothing)
Pkg = nothing

if Pkg !== nothing
# TODO: Split Pkg precompile script into REPL and script part
Expand All @@ -182,9 +183,7 @@ if Libdl !== nothing
"""
end

InteractiveUtils = get(Base.loaded_modules,
Base.PkgId(Base.UUID("b77e0a4c-d291-57a0-90e8-8db25a27a240"), "InteractiveUtils"),
nothing)
InteractiveUtils = nothing
if InteractiveUtils !== nothing
repl_script *= """
@time_imports using Random
Expand Down Expand Up @@ -240,7 +239,8 @@ procenv = Dict{String,Any}(
"JULIA_PROJECT" => nothing, # remove from environment
"JULIA_LOAD_PATH" => "@stdlib",
"JULIA_DEPOT_PATH" => Sys.iswindows() ? ";" : ":",
"TERM" => "")
"TERM" => "",
"JULIA_MINIMAL_CLIENT" => "true")

generate_precompile_statements() = try # Make sure `ansi_enablecursor` is printed
start_time = time_ns()
Expand Down
Loading