Skip to content

Commit 69926e3

Browse files
authored
Backports for 1.12 (#4388)
2 parents 474c628 + e423331 commit 69926e3

File tree

14 files changed

+332
-68
lines changed

14 files changed

+332
-68
lines changed

.github/workflows/test.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,9 @@ jobs:
102102
julia --project=docs --color=yes docs/make.jl pdf
103103
env:
104104
DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }}
105+
- name: Upload documentation artifacts
106+
uses: actions/upload-artifact@v4
107+
if: always()
108+
with:
109+
name: pkg-docs
110+
path: docs/build/

docs/src/apps.md

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ end # module
4242
[apps]
4343
reverse = {}
4444
```
45-
The empty table `{}` is to allow for giving metadata about the app but it is currently unused.
45+
The empty table `{}` is to allow for giving metadata about the app.
4646

4747
After installing this app one could run:
4848

@@ -95,6 +95,66 @@ This will create two executables:
9595
- `main-app` that runs `julia -m MyMultiApp`
9696
- `cli-app` that runs `julia -m MyMultiApp.CLI`
9797

98+
## Configuring Julia Flags
99+
100+
Apps can specify default Julia command-line flags that will be passed to the Julia process when the app is run. This is useful for configuring performance settings, threading, or other Julia options specific to your application.
101+
102+
### Default Julia Flags
103+
104+
You can specify default Julia flags in the `Project.toml` file using the `julia_flags` field:
105+
106+
```toml
107+
# Project.toml
108+
109+
[apps]
110+
myapp = { julia_flags = ["--threads=4", "--optimize=2"] }
111+
performance-app = { julia_flags = ["--threads=auto", "--startup-file=yes", "--depwarn=no"] }
112+
debug-app = { submodule = "Debug", julia_flags = ["--check-bounds=yes", "--optimize=0"] }
113+
```
114+
115+
With this configuration:
116+
- `myapp` will run with 4 threads and optimization level 2
117+
- `performance-app` will run with automatic thread detection, startup file enabled, and deprecation warnings disabled
118+
- `debug-app` will run with bounds checking enabled and no optimization
119+
120+
### Runtime Julia Flags
121+
122+
You can override or add to the default Julia flags at runtime using the `--` separator. Everything before `--` will be passed as flags to Julia, and everything after `--` will be passed as arguments to your app:
123+
124+
```bash
125+
# Uses default flags from Project.toml
126+
myapp input.txt output.txt
127+
128+
# Override thread count, keep other defaults
129+
myapp --threads=8 -- input.txt output.txt
130+
131+
# Add additional flags
132+
myapp --threads=2 --optimize=3 --check-bounds=yes -- input.txt output.txt
133+
134+
# Only Julia flags, no app arguments
135+
myapp --threads=1 --
136+
```
137+
138+
The final Julia command will combine:
139+
1. Fixed flags (like `--startup-file=no` and `-m ModuleName`)
140+
2. Default flags from `julia_flags` in Project.toml
141+
3. Runtime flags specified before `--`
142+
4. App arguments specified after `--`
143+
144+
### Overriding the Julia Executable
145+
146+
By default, apps run with the same Julia executable that was used to install them. You can override this globally using the `JULIA_APPS_JULIA_CMD` environment variable:
147+
148+
```bash
149+
# Use a different Julia version for all apps
150+
export JULIA_APPS_JULIA_CMD=/path/to/different/julia
151+
myapp input.txt
152+
153+
# On Windows
154+
set JULIA_APPS_JULIA_CMD=C:\path\to\different\julia.exe
155+
myapp input.txt
156+
```
157+
98158
## Installing Julia apps
99159

100160
The installation of Julia apps is similar to [installing Julia libraries](@ref Managing-Packages) but instead of using e.g. `Pkg.add` or `pkg> add` one uses `Pkg.Apps.add` or `pkg> app add` (`develop` is also available).

docs/src/index.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,23 @@ Welcome to the documentation for Pkg, [Julia](https://julialang.org)'s package m
44
The documentation covers many things, for example managing package
55
installations, developing packages, working with package registries and more.
66

7+
```@eval
8+
import Markdown
9+
# For Pkg, we need to determine the appropriate Julia version for the PDF
10+
# Since Pkg docs are versioned by Julia version, we'll use a similar approach to Julia docs
11+
julia_patch = if VERSION.prerelease == ()
12+
"v$(VERSION.major).$(VERSION.minor).$(VERSION.patch)"
13+
elseif VERSION.prerelease[1] == "DEV"
14+
"dev"
15+
end
16+
file = "Pkg.jl.pdf"
17+
url = "https://raw.githubusercontent.com/JuliaLang/Pkg.jl/gh-pages-pdf/$(julia_patch)/$(file)"
18+
Markdown.parse("""
19+
!!! note
20+
The documentation is also available in PDF format: [$file]($url).
21+
""")
22+
```
23+
724
Throughout the manual the REPL interface to Pkg, the Pkg REPL mode, is used in the examples.
825
There is also a functional API, which is preferred when not working
926
interactively. This API is documented in the [API Reference](@ref) section.

ext/REPLExt/REPLExt.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ function try_prompt_pkg_add(pkgs::Vector{Symbol})
273273
push!(keybindings, only("$n"))
274274
push!(shown_envs, expanded_env)
275275
end
276-
menu = TerminalMenus.RadioMenu(option_list, keybindings=keybindings, pagesize=length(option_list))
276+
menu = TerminalMenus.RadioMenu(option_list; keybindings = keybindings, pagesize = length(option_list))
277277
default = something(
278278
# select the first non-default env by default, if possible
279279
findfirst(!=(Base.active_project()), shown_envs),

ext/REPLExt/compat.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ function compat(ctx::Context; io = nothing)
1414
push!(opt_strs, Operations.compat_line(io, dep, uuid, compat_str, longest_dep_len, indent = ""))
1515
push!(opt_pkgs, dep)
1616
end
17-
menu = TerminalMenus.RadioMenu(opt_strs, pagesize=length(opt_strs))
17+
menu = TerminalMenus.RadioMenu(opt_strs; pagesize = length(opt_strs))
1818
choice = try
1919
TerminalMenus.request(" Select an entry to edit:", menu)
2020
catch err

src/Apps/Apps.jl

Lines changed: 120 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,7 @@ end
441441
#########
442442

443443
const SHIM_COMMENT = Sys.iswindows() ? "REM " : "#"
444-
const SHIM_VERSION = 1.0
444+
const SHIM_VERSION = 1.1
445445
const SHIM_HEADER = """$SHIM_COMMENT This file is generated by the Julia package manager.
446446
$SHIM_COMMENT Shim version: $SHIM_VERSION"""
447447

@@ -457,18 +457,18 @@ function generate_shim(pkgname, app::AppInfo, env, julia)
457457
validate_submodule_name(app.submodule)
458458

459459
module_spec = app.submodule === nothing ? pkgname : "$(pkgname).$(app.submodule)"
460-
460+
461461
filename = app.name * (Sys.iswindows() ? ".bat" : "")
462462
julia_bin_filename = joinpath(julia_bin_path(), filename)
463463
mkpath(dirname(julia_bin_filename))
464464
content = if Sys.iswindows()
465465
julia_escaped = "\"$(Base.shell_escape_wincmd(julia))\""
466466
module_spec_escaped = "\"$(Base.shell_escape_wincmd(module_spec))\""
467-
windows_shim(julia_escaped, module_spec_escaped, env)
467+
windows_shim(julia_escaped, module_spec_escaped, env, app.julia_flags)
468468
else
469469
julia_escaped = Base.shell_escape(julia)
470470
module_spec_escaped = Base.shell_escape(module_spec)
471-
shell_shim(julia_escaped, module_spec_escaped, env)
471+
shell_shim(julia_escaped, module_spec_escaped, env, app.julia_flags)
472472
end
473473
overwrite_file_if_different(julia_bin_filename, content)
474474
if Sys.isunix()
@@ -477,36 +477,134 @@ function generate_shim(pkgname, app::AppInfo, env, julia)
477477
end
478478

479479

480-
function shell_shim(julia_escaped::String, module_spec_escaped::String, env)
480+
function shell_shim(julia_escaped::String, module_spec_escaped::String, env, julia_flags::Vector{String})
481+
julia_flags_escaped = join(Base.shell_escape.(julia_flags), " ")
482+
julia_flags_part = isempty(julia_flags) ? "" : " $julia_flags_escaped"
483+
484+
load_path_escaped = Base.shell_escape(env)
485+
depot_path_escaped = Base.shell_escape(join(DEPOT_PATH, ':'))
486+
481487
return """
482-
#!/bin/sh
488+
#!/bin/sh
489+
set -eu
483490
484491
$SHIM_HEADER
485492
486-
export JULIA_LOAD_PATH=$(repr(env))
487-
export JULIA_DEPOT_PATH=$(repr(join(DEPOT_PATH, ':')))
488-
exec $julia_escaped \\
489-
--startup-file=no \\
490-
-m $module_spec_escaped \\
491-
"\$@"
492-
"""
493+
# Pin Julia paths for the child process
494+
export JULIA_LOAD_PATH=$load_path_escaped
495+
export JULIA_DEPOT_PATH=$depot_path_escaped
496+
497+
# Allow overriding Julia executable via environment variable
498+
if [ -n "\${JULIA_APPS_JULIA_CMD:-}" ]; then
499+
julia_cmd="\$JULIA_APPS_JULIA_CMD"
500+
else
501+
julia_cmd=$julia_escaped
502+
fi
503+
504+
# If a `--` appears, args before it go to Julia, after it to the app.
505+
# If no `--` appears, all original args go to the app (no Julia args).
506+
found_separator=false
507+
for a in "\$@"; do
508+
[ "\$a" = "--" ] && { found_separator=true; break; }
509+
done
510+
511+
if [ "\$found_separator" = "true" ]; then
512+
# Build julia_args until `--`, then leave the rest in "\$@"
513+
julia_args=""
514+
while [ "\$#" -gt 0 ]; do
515+
case "\$1" in
516+
--) shift; break ;;
517+
*) julia_args="\$julia_args\${julia_args:+ }\$1"; shift ;;
518+
esac
519+
done
520+
# Here: "\$@" are the app args after the separator
521+
exec "\$julia_cmd" --startup-file=no$julia_flags_part \$julia_args -m $module_spec_escaped "\$@"
522+
else
523+
# No separator: all original args go straight to the app
524+
exec "\$julia_cmd" --startup-file=no$julia_flags_part -m $module_spec_escaped "\$@"
525+
fi
526+
"""
493527
end
494528

495-
function windows_shim(julia_escaped::String, module_spec_escaped::String, env)
529+
function windows_shim(
530+
julia_escaped::String,
531+
module_spec_escaped::String,
532+
env,
533+
julia_flags::Vector{String},
534+
)
535+
flags_escaped = join(Base.shell_escape_wincmd.(julia_flags), " ")
536+
flags_part = isempty(julia_flags) ? "" : " $flags_escaped"
537+
538+
depot_path = join(DEPOT_PATH, ';')
539+
496540
return """
497-
@echo off
541+
@echo off
542+
setlocal EnableExtensions DisableDelayedExpansion
498543
499544
$SHIM_HEADER
500545
501-
setlocal
502-
set JULIA_LOAD_PATH=$env
503-
set JULIA_DEPOT_PATH=$(join(DEPOT_PATH, ';'))
504-
505-
$julia_escaped ^
506-
--startup-file=no ^
546+
rem --- Environment (no delayed expansion here to keep '!' literal) ---
547+
set "JULIA_LOAD_PATH=$env"
548+
set "JULIA_DEPOT_PATH=$depot_path"
549+
550+
rem --- Allow overriding Julia executable via environment variable ---
551+
if defined JULIA_APPS_JULIA_CMD (
552+
set "julia_cmd=%JULIA_APPS_JULIA_CMD%"
553+
) else (
554+
set "julia_cmd=$julia_escaped"
555+
)
556+
557+
rem --- Now enable delayed expansion for string building below ---
558+
setlocal EnableDelayedExpansion
559+
560+
rem Parse arguments, splitting on first -- into julia_args / app_args
561+
set "found_sep="
562+
set "julia_args="
563+
set "app_args="
564+
565+
:__next
566+
if "%~1"=="" goto __done
567+
568+
if not defined found_sep if "%~1"=="--" (
569+
set "found_sep=1"
570+
shift
571+
goto __next
572+
)
573+
574+
if not defined found_sep (
575+
if defined julia_args (
576+
set "julia_args=!julia_args! %1"
577+
) else (
578+
set "julia_args=%1"
579+
)
580+
shift
581+
goto __next
582+
)
583+
584+
if defined found_sep (
585+
if defined app_args (
586+
set "app_args=!app_args! %1"
587+
) else (
588+
set "app_args=%1"
589+
)
590+
shift
591+
goto __next
592+
)
593+
594+
:__done
595+
rem If no --, pass all original args to the app; otherwise use split vars
596+
if defined found_sep (
597+
"%julia_cmd%" ^
598+
--startup-file=no$flags_part !julia_args! ^
599+
-m $module_spec_escaped ^
600+
!app_args!
601+
) else (
602+
"%julia_cmd%" ^
603+
--startup-file=no$flags_part ^
507604
-m $module_spec_escaped ^
508605
%*
509-
"""
606+
)
607+
"""
510608
end
511609

512610
end

src/Operations.jl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2684,7 +2684,11 @@ function print_status(env::EnvCache, old_env::Union{Nothing,EnvCache}, registrie
26842684
end
26852685
no_changes = all(p-> p[2] == p[3], xs)
26862686
if no_changes
2687-
printpkgstyle(io, Symbol("No packages added to or removed from"), "$(pathrepr(manifest ? env.manifest_file : env.project_file))", ignore_indent)
2687+
if manifest
2688+
printpkgstyle(io, :Manifest, "No packages added to or removed from $(pathrepr(env.manifest_file))", ignore_indent; color = Base.info_color())
2689+
else
2690+
printpkgstyle(io, :Project, "No packages added to or removed from $(pathrepr(env.project_file))", ignore_indent; color = Base.info_color())
2691+
end
26882692
else
26892693
xs = !filter ? xs : eltype(xs)[(id, old, new) for (id, old, new) in xs if (id in uuids || something(new, old).name in names)]
26902694
if isempty(xs)

src/Types.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,8 @@ struct AppInfo
242242
name::String
243243
julia_command::Union{String, Nothing}
244244
submodule::Union{String, Nothing}
245-
other::Dict{String,Any}
245+
julia_flags::Vector{String}
246+
other::Dict{String, Any}
246247
end
247248
Base.@kwdef mutable struct Project
248249
other::Dict{String,Any} = Dict{String,Any}()

src/manifest.jl

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,19 @@ function read_apps(apps::Dict)
8787
appinfos = Dict{String, AppInfo}()
8888
for (appname, app) in apps
8989
submodule = get(app, "submodule", nothing)
90-
appinfo = AppInfo(appname::String,
91-
app["julia_command"]::String,
92-
submodule,
93-
app)
90+
julia_flags_raw = get(app, "julia_flags", nothing)
91+
julia_flags = if julia_flags_raw === nothing
92+
String[]
93+
else
94+
String[flag::String for flag in julia_flags_raw]
95+
end
96+
appinfo = AppInfo(
97+
appname::String,
98+
app["julia_command"]::String,
99+
submodule,
100+
julia_flags,
101+
app
102+
)
94103
appinfos[appinfo.name] = appinfo
95104
end
96105
return appinfos
@@ -338,6 +347,9 @@ function destructure(manifest::Manifest)::Dict
338347
if appinfo.submodule !== nothing
339348
app_dict["submodule"] = appinfo.submodule
340349
end
350+
if !isempty(appinfo.julia_flags)
351+
app_dict["julia_flags"] = appinfo.julia_flags
352+
end
341353
new_entry["apps"][appname] = app_dict
342354
end
343355
end

src/project.jl

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,15 @@ function read_project_apps(raw::Dict{String,Any}, project::Project)
8383
Expected value for app `$name` to be a dictionary.
8484
""")
8585
submodule = get(info, "submodule", nothing)
86-
appinfos[name] = AppInfo(name, nothing, submodule, other)
86+
julia_flags_raw = get(info, "julia_flags", nothing)
87+
julia_flags = if julia_flags_raw === nothing
88+
String[]
89+
elseif julia_flags_raw isa Vector
90+
String[flag::String for flag in julia_flags_raw]
91+
else
92+
pkgerror("Expected `julia_flags` for app `$name` to be an array of strings")
93+
end
94+
appinfos[name] = AppInfo(name, nothing, submodule, julia_flags, other)
8795
end
8896
return appinfos
8997
end

0 commit comments

Comments
 (0)