• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JuliaLang / julia / #37610

03 Sep 2023 03:55PM UTC coverage: 86.433% (-0.8%) from 87.218%
#37610

push

local

web-flow
Standardize the entry-point for Julia execution (#50974)

This is a bit of a straw-man proposal (though I think mergable if people
agree) to standardize the execution entrypoint for Julia scripts. I
think there's at least four different ways that people might run a
script:

- As `julia main.jl`
- As a PkgCompiler sysimage, then calling the main entry point
- As a PkgCompiler "app", with the magic `julia_main` function
- As a StaticCompiler product with an explicit entrypoint specified on
the API.

The main problem I have with all of these variants is that they're all
different and it's kind of a pain to move between them. Here I propose
that we standardize on `Main.main(ARGS)` as the entrypoint for all
scripts. Downstream from that proposal, this PR then makes the following
changes:

1. If a system image has an existing `Main.main`, that is the entry
point for `julia -Jsysimage.so`.
2. If not, and the sysimage has a REPL, we call REPL.main (we could
handle this by defaulting `Main.main` to a weak import of `REPL.main`,
but for the purpose of this PR, it's an explicit fallback. That said, I
do want to emhpasize the direction of moving the REPL to be "just
another app".
3. If the REPL code is called and passed a script file, the REPL
executes any newly defined Main.main after loading the script file. As a
result, `julia` behaves the same as if we had generated a new system
image after loading `main.jl` and then running julia with that system
image.

The further downstream implication of this is that I'd like to get rid
of the distinction between PkgCompiler apps and system images. An app is
simply a system image with a `Main.main` function defined (note that
currently PkgCompiler uses `julia_main` instead).

---------

Co-authored-by: Martijn Visser <mgvisser@gmail.com>

25 of 25 new or added lines in 2 files covered. (100.0%)

73492 of 85028 relevant lines covered (86.43%)

13617619.45 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

52.75
/stdlib/Logging/src/ConsoleLogger.jl
1
# This file is a part of Julia. License is MIT: https://julialang.org/license
2

3
"""
4
    ConsoleLogger([stream,] min_level=Info; meta_formatter=default_metafmt,
5
                  show_limited=true, right_justify=0)
6

7
Logger with formatting optimized for readability in a text console, for example
8
interactive work with the Julia REPL.
9

10
Log levels less than `min_level` are filtered out.
11

12
Message formatting can be controlled by setting keyword arguments:
13

14
* `meta_formatter` is a function which takes the log event metadata
15
  `(level, _module, group, id, file, line)` and returns a color (as would be
16
  passed to printstyled), prefix and suffix for the log message.  The
17
  default is to prefix with the log level and a suffix containing the module,
18
  file and line location.
19
* `show_limited` limits the printing of large data structures to something
20
  which can fit on the screen by setting the `:limit` `IOContext` key during
21
  formatting.
22
* `right_justify` is the integer column which log metadata is right justified
23
  at. The default is zero (metadata goes on its own line).
24
"""
25
struct ConsoleLogger <: AbstractLogger
26
    stream::IO
467✔
27
    min_level::LogLevel
28
    meta_formatter
29
    show_limited::Bool
30
    right_justify::Int
31
    message_limits::Dict{Any,Int}
32
end
33
function ConsoleLogger(stream::IO, min_level=Info;
×
34
                       meta_formatter=default_metafmt, show_limited=true,
35
                       right_justify=0)
36
    ConsoleLogger(stream, min_level, meta_formatter,
×
37
                  show_limited, right_justify, Dict{Any,Int}())
38
end
39
function ConsoleLogger(min_level=Info;
934✔
40
                       meta_formatter=default_metafmt, show_limited=true,
41
                       right_justify=0)
42
    ConsoleLogger(closed_stream, min_level, meta_formatter,
467✔
43
                  show_limited, right_justify, Dict{Any,Int}())
44
end
45

46

47
shouldlog(logger::ConsoleLogger, level, _module, group, id) =
1✔
48
    get(logger.message_limits, id, 1) > 0
49

50
min_enabled_level(logger::ConsoleLogger) = logger.min_level
468✔
51

52
# Formatting of values in key value pairs
53
showvalue(io, msg) = show(io, "text/plain", msg)
×
54
function showvalue(io, e::Tuple{Exception,Any})
1✔
55
    ex,bt = e
1✔
56
    showerror(io, ex, bt; backtrace = bt!==nothing)
1✔
57
end
58
showvalue(io, ex::Exception) = showerror(io, ex)
×
59

60
function default_logcolor(level::LogLevel)
×
61
    level < Info  ? Base.debug_color() :
2✔
62
    level < Warn  ? Base.info_color()  :
63
    level < Error ? Base.warn_color()  :
64
                    Base.error_color()
65
end
66

67
function default_metafmt(level::LogLevel, _module, group, id, file, line)
1✔
68
    @nospecialize
×
69
    color = default_logcolor(level)
2✔
70
    prefix = string(level == Warn ? "Warning" : string(level), ':')
2✔
71
    suffix::String = ""
×
72
    Info <= level < Warn && return color, prefix, suffix
1✔
73
    _module !== nothing && (suffix *= string(_module)::String)
×
74
    if file !== nothing
×
75
        _module !== nothing && (suffix *= " ")
×
76
        suffix *= contractuser(file)::String
×
77
        if line !== nothing
×
78
            suffix *= ":$(isa(line, UnitRange) ? "$(first(line))-$(last(line))" : line)"
×
79
        end
80
    end
81
    !isempty(suffix) && (suffix = "@ " * suffix)
×
82
    return color, prefix, suffix
×
83
end
84

85
# Length of a string as it will appear in the terminal (after ANSI color codes
86
# are removed)
87
function termlength(str)
1✔
88
    N = 0
×
89
    in_esc = false
×
90
    for c in str
2✔
91
        if in_esc
3✔
92
            if c == 'm'
×
93
                in_esc = false
×
94
            end
95
        else
96
            if c == '\e'
3✔
97
                in_esc = true
×
98
            else
99
                N += 1
3✔
100
            end
101
        end
102
    end
5✔
103
    return N
1✔
104
end
105

106
function handle_message(logger::ConsoleLogger, level::LogLevel, message, _module, group, id,
3✔
107
                        filepath, line; kwargs...)
108
    @nospecialize
×
109
    hasmaxlog = haskey(kwargs, :maxlog) ? 1 : 0
1✔
110
    maxlog = get(kwargs, :maxlog, nothing)
2✔
111
    if maxlog isa Core.BuiltinInts
1✔
112
        remaining = get!(logger.message_limits, id, Int(maxlog)::Int)
×
113
        logger.message_limits[id] = remaining - 1
×
114
        remaining > 0 || return
×
115
    end
116

117
    # Generate a text representation of the message and all key value pairs,
118
    # split into lines.
119
    msglines = [(indent=0, msg=l) for l in split(chomp(convert(String, string(message))::String), '\n')]
2✔
120
    stream::IO = logger.stream
1✔
121
    if !(isopen(stream)::Bool)
1✔
122
        stream = stderr
1✔
123
    end
124
    dsize = displaysize(stream)::Tuple{Int,Int}
1✔
125
    nkwargs = length(kwargs)::Int
1✔
126
    if nkwargs > hasmaxlog
1✔
127
        valbuf = IOBuffer()
×
128
        rows_per_value = max(1, dsize[1] รท (nkwargs + 1 - hasmaxlog))
×
129
        valio = IOContext(IOContext(valbuf, stream),
×
130
                          :displaysize => (rows_per_value, dsize[2] - 5),
131
                          :limit => logger.show_limited)
132
        for (key, val) in kwargs
×
133
            key === :maxlog && continue
×
134
            showvalue(valio, val)
×
135
            vallines = split(String(take!(valbuf)), '\n')
×
136
            if length(vallines) == 1
×
137
                push!(msglines, (indent=2, msg=SubString("$key = $(vallines[1])")))
×
138
            else
139
                push!(msglines, (indent=2, msg=SubString("$key =")))
×
140
                append!(msglines, ((indent=3, msg=line) for line in vallines))
×
141
            end
142
        end
×
143
    end
144

145
    # Format lines as text with appropriate indentation and with a box
146
    # decoration on the left.
147
    color, prefix, suffix = logger.meta_formatter(level, _module, group, id, filepath, line)::Tuple{Union{Symbol,Int},String,String}
1✔
148
    minsuffixpad = 2
×
149
    buf = IOBuffer()
1✔
150
    iob = IOContext(buf, stream)
1✔
151
    nonpadwidth = 2 + (isempty(prefix) || length(msglines) > 1 ? 0 : length(prefix)+1) +
1✔
152
                  msglines[end].indent + termlength(msglines[end].msg) +
153
                  (isempty(suffix) ? 0 : length(suffix)+minsuffixpad)
154
    justify_width = min(logger.right_justify, dsize[2])
1✔
155
    if nonpadwidth > justify_width && !isempty(suffix)
1✔
156
        push!(msglines, (indent=0, msg=SubString("")))
×
157
        minsuffixpad = 0
×
158
        nonpadwidth = 2 + length(suffix)
×
159
    end
160
    for (i, (indent, msg)) in enumerate(msglines)
2✔
161
        boxstr = length(msglines) == 1 ? "[ " :
1✔
162
                 i == 1                ? "โ”Œ " :
163
                 i < length(msglines)  ? "โ”‚ " :
164
                                         "โ”” "
165
        printstyled(iob, boxstr, bold=true, color=color)
1✔
166
        if i == 1 && !isempty(prefix)
1✔
167
            printstyled(iob, prefix, " ", bold=true, color=color)
1✔
168
        end
169
        print(iob, " "^indent, msg)
1✔
170
        if i == length(msglines) && !isempty(suffix)
1✔
171
            npad = max(0, justify_width - nonpadwidth) + minsuffixpad
×
172
            print(iob, " "^npad)
×
173
            printstyled(iob, suffix, color=:light_black)
×
174
        end
175
        println(iob)
1✔
176
    end
1✔
177

178
    write(stream, take!(buf))
1✔
179
    nothing
1✔
180
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc