Skip to content

reenable precompilation progress output even in non-interactive mode#61064

Open
KristofferC wants to merge 9 commits intomasterfrom
kc/precompiling_output
Open

reenable precompilation progress output even in non-interactive mode#61064
KristofferC wants to merge 9 commits intomasterfrom
kc/precompiling_output

Conversation

@KristofferC
Copy link
Copy Markdown
Member

Fixes #59924

@KristofferC KristofferC added the backport 1.13 Change should be backported to release-1.13 label Feb 18, 2026
@topolarity topolarity requested a review from vtjnash February 18, 2026 13:40
@vtjnash
Copy link
Copy Markdown
Member

vtjnash commented Feb 18, 2026

This introduces a regression into our tests output, which needs to be corrected before merging. We can change the noisiness heuristics here, but it needs to be done in a way that avoids printing output from the background to avoid fighting for the terminal, like from a script or other usages like that, including Pkg.precompile after #60943:

precompile                                                   (1) |        started at 2026-02-18T13:50:16.326
Precompiling packages...
    640.0 ms  ✓ F2oo4b3a94a1a081a8cb (serial)
  1 dependency successfully precompiled in 1 seconds
Precompiling packages...
   3830.7 ms  ✓ Foo4b3a94a1a081a8cb (serial)
  1 dependency successfully precompiled in 4 seconds
Precompiling packages...
    627.5 ms  ✓ Nest4b3a94a1a081a8cb (serial)
  1 dependency successfully precompiled in 1 seconds
Precompiling packages...
   1326.4 ms  ✓ UsesB4b3a94a1a081a8cb (serial)
  1 dependency successfully precompiled in 1 seconds
Precompiling packages...
    457.5 ms  ? UseBaz (serial)
Precompiling packages...
    632.2 ms  ✓ FooBar1 (serial)
  1 dependency successfully precompiled in 1 seconds
Precompiling packages...
ERROR: LoadError: break me
Stacktrace:
  [1] error(s::String)
    @ Base ./error.jl:56
  [2] top-level scope
    @ /cache/build/tester-amdci4-8/julialang/julia-master/tmp/jltestdepot_vNl4YS/FooBar2.jl:2
  [3] include(mod::Module, _path::String)
    @ Base ./Base.jl:309
  [4] include_package_for_output(pkg::Base.PkgId, input::String, syntax_version::VersionNumber, depot_path::Vector{String}, dl_load_path::Vector{String}, load_path::Vector{String}, concrete_deps::Vector{Pair{Base.PkgId, UInt128}}, source::Nothing)
    @ Base ./loading.jl:3288
  [5] top-level scope
    @ stdin:5
  [6] eval(m::Module, e::Any)
    @ Core ./boot.jl:489
  [7] include_string(mapexpr::typeof(identity), mod::Module, code::String, filename::String)
    @ Base ./loading.jl:3130
  [8] include_string
    @ ./loading.jl:3140 [inlined]
  [9] exec_options(opts::Base.JLOptions)
    @ Base ./client.jl:342
 [10] _start()
    @ Base ./client.jl:585
in expression starting at /cache/build/tester-amdci4-8/julialang/julia-master/tmp/jltestdepot_vNl4YS/FooBar2.jl:1
in expression starting at stdin:5
              ✗ FooBar2 (serial)
  0 dependencies successfully precompiled in 2 seconds
  1 dependency had output during precompilation:
┌ FooBar2
│  [Output was shown above]
└  
Precompiling packages...
ERROR: LoadError: Evaluation into the closed module `Base` breaks incremental compilation because the side effects will not be permanent. This is likely due to some other module mutating `Base` with `eval` during precompilation - don't do this.
Stacktrace:
  [1] eval(m::Module, e::Any)
    @ Core ./boot.jl:489
  [2] top-level scope
    @ /cache/build/tester-amdci4-8/julialang/julia-master/tmp/jltestdepot_vNl4YS/FooBar3.jl:2
  [3] include(mod::Module, _path::String)
    @ Base ./Base.jl:309
  [4] include_package_for_output(pkg::Base.PkgId, input::String, syntax_version::VersionNumber, depot_path::Vector{String}, dl_load_path::Vector{String}, load_path::Vector{String}, concrete_deps::Vector{Pair{Base.PkgId, UInt128}}, source::Nothing)
    @ Base ./loading.jl:3288
  [5] top-level scope
    @ stdin:5
  [6] eval(m::Module, e::Any)
    @ Core ./boot.jl:489
  [7] include_string(mapexpr::typeof(identity), mod::Module, code::String, filename::String)
    @ Base ./loading.jl:3130
  [8] include_string
    @ ./loading.jl:3140 [inlined]
  [9] exec_options(opts::Base.JLOptions)
    @ Base ./client.jl:342
 [10] _start()
    @ Base ./client.jl:585
in expression starting at /cache/build/tester-amdci4-8/julialang/julia-master/tmp/jltestdepot_vNl4YS/FooBar3.jl:1
in expression starting at stdin:5
              ✗ FooBar3 (serial)
  0 dependencies successfully precompiled in 2 seconds
  1 dependency had output during precompilation:
┌ FooBar3
│  [Output was shown above]
└  
Precompiling packages...
ERROR: LoadError: Evaluation into the closed module `Base` breaks incremental compilation because the side effects will not be permanent. This is likely due to some other module mutating `Base` with `eval` during precompilation - don't do this.
Stacktrace:
  [1] include(mod::Module, _path::String)
    @ Base ./Base.jl:309
  [2] top-level scope
    @ /cache/build/tester-amdci4-8/julialang/julia-master/tmp/jltestdepot_vNl4YS/FooBar3.jl:2
  [3] include(mod::Module, _path::String)
    @ Base ./Base.jl:309
  [4] include_package_for_output(pkg::Base.PkgId, input::String, syntax_version::VersionNumber, depot_path::Vector{String}, dl_load_path::Vector{String}, load_path::Vector{String}, concrete_deps::Vector{Pair{Base.PkgId, UInt128}}, source::Nothing)
    @ Base ./loading.jl:3288
  [5] top-level scope
    @ stdin:5
  [6] eval(m::Module, e::Any)
    @ Core ./boot.jl:489
  [7] include_string(mapexpr::typeof(identity), mod::Module, code::String, filename::String)
    @ Base ./loading.jl:3130
  [8] include_string
    @ ./loading.jl:3140 [inlined]
  [9] exec_options(opts::Base.JLOptions)
    @ Base ./client.jl:342
 [10] _start()
    @ Base ./client.jl:585
in expression starting at /cache/build/tester-amdci4-8/julialang/julia-master/tmp/jltestdepot_vNl4YS/FooBar3_inc.jl:1
in expression starting at /cache/build/tester-amdci4-8/julialang/julia-master/tmp/jltestdepot_vNl4YS/FooBar3.jl:1
in expression starting at stdin:5
              ✗ FooBar3 (serial)
  0 dependencies successfully precompiled in 2 seconds
  1 dependency had output during precompilation:
┌ FooBar3
│  [Output was shown above]
└  
Precompiling packages...
   1331.5 ms  ✓ FooBarT2 (serial)
  1 dependency successfully precompiled in 1 seconds
Precompiling packages...
    703.3 ms  ✓ Time4b3a94a1a081a8cb (serial)
  1 dependency successfully precompiled in 2 seconds
Precompiling packages...
    620.6 ms  ✓ Teste4095a83 (serial)
  1 dependency successfully precompiled in 1 seconds
Precompiling packages...
   1362.8 ms  ✓ Issue19960B (serial)
  1 dependency successfully precompiled in 1 seconds
Precompiling packages...
    656.8 ms  ✓ Aedb164bd3a126418 (serial)
  1 dependency successfully precompiled in 1 seconds
Precompiling packages...
    651.9 ms  ✓ Bedb164bd3a126418 (serial)
  1 dependency successfully precompiled in 1 seconds
Precompiling packages...
    661.9 ms  ✓ Issue19030 (serial)
  1 dependency successfully precompiled in 1 seconds
Precompiling packages...
    647.6 ms  ✓ Foo29936 (serial)
  1 dependency successfully precompiled in 1 seconds
Precompiling packages...
    676.0 ms  ✓ InitCachePath (serial)
  1 dependency successfully precompiled in 1 seconds
Precompiling packages...
    629.2 ms  ✓ UniqueBinding1 (serial)
  1 dependency successfully precompiled in 1 seconds
Precompiling packages...
    643.0 ms  ✓ UniqueBinding2 (serial)
  1 dependency successfully precompiled in 1 seconds
Precompiling packages...
    651.3 ms  ✓ LLVMCall (serial)
  1 dependency successfully precompiled in 1 seconds
Precompiling packages...
    649.9 ms  ✓ LLVMCall (serial)
  1 dependency successfully precompiled in 1 seconds
Precompiling packages...
    630.5 ms  ✓ TestPkgNoManifest (serial)
  1 dependency successfully precompiled in 1 seconds
precompile                                                   (1) |   275.17 |   0.87 |  0.3 |     568.26 |   346.70

@topolarity
Copy link
Copy Markdown
Member

it needs to be done in a way that avoids printing output from the background to avoid fighting for the terminal, like from a script or other usages like that, including Pkg.precompile after #60943:

That sounds sensible, but how do we distinguish distinguish "background" non-interactive executions vs. "foreground" (e.g. CLI tools / Pkg app wrappers)?

Do we need to go by the -q flag or similar?

@vtjnash
Copy link
Copy Markdown
Member

vtjnash commented Feb 18, 2026

Maybe we should track the controlling terminal in the ScopedValue, just like unix?

@topolarity
Copy link
Copy Markdown
Member

Maybe we should track the controlling terminal in the ScopedValue, just like unix?

How does an (in-process) ScopedValue help with sharing the terminal across processes? Where does "control" transfer?

@topolarity
Copy link
Copy Markdown
Member

After a talk with @vtjnash , I think we clarified the request / idea:

@vtjnash agreed that these are OK to print if the user is "at the keyboard" and directly issued the command that's precompiling, but he wants to avoid clogging sub-process / script output with them. In particular, these messages can cause errors if your script calls Julia or a Julia-based app, but doesn't expect the "Precompiling..." message. The requirement then is to avoid adding this printing for non-interactive "background" / "child" processes (intentionally left a bit hand-wavey).

The main idea we discussed was to add a Base._is_foreground_process to complement Base.isinteractive as a heuristic for when we should be printing these "Precompiling" messages. We explored trying to check whether the current process is in a foreground vs. background process group via tcgetpgrp() or other utilities (https://pubs.opengroup.org/onlinepubs/009695299/basedefs/xbd_chap11.html), but these API's have no equivalent on Windows as far as we can tell. So this heuristic does not seem straightforward to implement across platforms. That heuristic is likely also flawed: Sometimes a "child" Julia process is likely to end up in a foreground process group, despite having an automated caller that is not expecting a "precompile" message.

Long story short, we think that the simplest thing to do here may be to disable this logging in -q quiet mode, which is similar to what uv does (uv run is similar to executing a Julia script / Pkg app):

--quiet, -q
Use quiet output.

Repeating this option, e.g., -qq, will enable a silent mode in which uv will write no output to stdout.

and Base.julia_cmd() should (arguably) be updated to enable this quiet mode by default.

@KristofferC
Copy link
Copy Markdown
Member Author

, these messages can cause errors if your script calls Julia or a Julia-based app, but doesn't expect the "Precompiling..."

I think this gets printed to stderr.

@topolarity
Copy link
Copy Markdown
Member

I think this gets printed to stderr.

That avoids some issues, but I still think it's fair that we support a mode that leaves all IO to the user-level Julia application, incl. stderr (equivalent to uv's -qq)

@KristofferC
Copy link
Copy Markdown
Member Author

Take a look at the last commit please.

push!(addflags, "--color=no")
end
if opts.quiet != 0
push!(addflags, "-q")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about setting this by default (based on a kwarg?) instead of propagating?

julia_cmd() is typically used for automated executions IME

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems reasonable to me, right now it is only for banner which is kind of pointless anyway.

Copy link
Copy Markdown
Member Author

@KristofferC KristofferC Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although, we don't have a "verbose" so how do you turn it off if you want to?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Base.julia_cmd(; quiet=false)?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess... just feels like you should be able to do something like $(julia_cmd()) --verbose

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think trying to mix "opposing" flags like that is its own kind of confusing TBH

uv doesn't allow it either:

$ uv -q -v help
error: the argument '--quiet...' cannot be used with '--verbose...'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normally our options allow last-one-wins

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am OK with tweaking --quiet to also accept --quiet=no and --quiet=yes

But --verbose is probably not the right negated flag to think of (--verbose means "not quiet, and also more noisy than normal" imo)

@KristofferC based on the experience in #61126, I'm kind of inclined to have julia_cmd() not be quiet by default anyway. We'll have to adjust our uses in Base / Pkg to add the quiet flag / kwarg instead where necessary.

@KristofferC
Copy link
Copy Markdown
Member Author

The help manual for -q says "quiet startup" which should probably be amended.

@KristofferC KristofferC mentioned this pull request Mar 3, 2026
56 tasks
@topolarity
Copy link
Copy Markdown
Member

#61126 ended up being a pre-compilation issue that was weirdly invisible in CI

That mis-configuration led to some wasted engineering (and CI machine) time for us and the community, which I think is a good argument in favor of this PR.

Copy link
Copy Markdown
Member

@topolarity topolarity left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

I would like to merge this and then quiet-ify any places that @vtjnash (or others) consider too noisy, rather than leaving silent pre-compilation around to surprise users in CI and elsewhere.

Copy link
Copy Markdown
Member

@vtjnash vtjnash left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you at least make sure Distributed and Test add the flag, so we don't get regressions in PkgEval or HPC startup behaviors?

@topolarity
Copy link
Copy Markdown
Member

Can you at least make sure Distributed

Sure thing 👍

Test

Do you mean Pkg.test? We can add it there, although I'd prefer not to - to catch things like #61126 and also I don't see how emitting this during tests will lead to PkgEval regressions.

Can you enlighten me?

@vtjnash
Copy link
Copy Markdown
Member

vtjnash commented Mar 4, 2026

I think we run all of PkgEval via Pkg.test or Base.runtests? Things get very noisy in CI logs, making it hard to read them, when we get this auto-generated spam

@topolarity
Copy link
Copy Markdown
Member

How often do we expect to encounter precompilation-from-loading in a typical Pkg.test run?

The main reason I want to keep this logging is because that precompilation is surprising / unexpected in Pkg.test for most configurations (and can inflate CI time, etc.). It is also sometimes the result of mis-configuration (e.g. flags, preferences, etc.).

Does it actually appear in the majority of test runs?

topolarity added a commit to JuliaLang/Distributed.jl that referenced this pull request Mar 4, 2026
As of 1.13+ this will suppress logging output from Julia (specifically
the loading system doing any pre-compilation, see
JuliaLang/julia#61064).
@vtjnash
Copy link
Copy Markdown
Member

vtjnash commented Mar 5, 2026

Always? It changes whether PkgEval logs have the error visible on the first screen, or just log spam

@topolarity
Copy link
Copy Markdown
Member

Always? It changes whether PkgEval logs have the error visible on the first screen, or just log spam

Don't we expect Pkg.precompile() to have run before you do the tests, so that you encounter only exceptional precompilation in the tests themselves?

@vtjnash
Copy link
Copy Markdown
Member

vtjnash commented Mar 5, 2026

It's pretty common for that to compile the wrong set of packages (since it compiles the package itself and not test environment and injects Pkg into the mix), to the point that doing it does slow down the tests. I thought we'd removed that by now?

@topolarity
Copy link
Copy Markdown
Member

topolarity commented Mar 5, 2026

I see. I am OK with disabling this logging in the tests until we have made Pkg.precompile() predictive enough that this pre-compilation is actually "exceptional" (assuming we actually think that's worth the making the required language trade-offs) - I think that's a fair expectation

My hope is definitely not to spam logs with useless messages about work that must routinely be done anyway. The desire is to make it noisy when you fall off the "happy path" and the work you thought you'd already done ended up useless (as happened in #61126). But that means we need to make the "happy path" good enough first.

@KristofferC
Copy link
Copy Markdown
Member Author

Funny, now the precompile tests fail because they read the stderr output of the precompile function 😭 😆

@KristofferC KristofferC force-pushed the kc/precompiling_output branch from 9ecf8c8 to f6391b1 Compare March 27, 2026 13:59
@KristofferC KristofferC force-pushed the kc/precompiling_output branch from f6391b1 to ad2d9cc Compare March 28, 2026 21:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport 1.13 Change should be backported to release-1.13

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Precompilation progress is not shown in non-interactive mode

3 participants