Skip to content

Commit 6de46f2

Browse files
KristofferCKristofferC
andcommitted
Various app improvements (#4278)
Co-authored-by: KristofferC <[email protected]> (cherry picked from commit 109eaea)
1 parent ff55af2 commit 6de46f2

File tree

4 files changed

+81
-17
lines changed

4 files changed

+81
-17
lines changed

src/Apps/Apps.jl

Lines changed: 79 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,41 @@ julia_bin_path() = joinpath(first(DEPOT_PATH), "bin")
1515

1616
app_context() = Context(env=EnvCache(joinpath(app_env_folder(), "Project.toml")))
1717

18+
function validate_app_name(name::AbstractString)
19+
if isempty(name)
20+
error("App name cannot be empty")
21+
end
22+
if !occursin(r"^[a-zA-Z][a-zA-Z0-9_-]*$", name)
23+
error("App name must start with a letter and contain only letters, numbers, underscores, and hyphens")
24+
end
25+
if occursin(r"\.\.", name) || occursin(r"[/\\]", name)
26+
error("App name cannot contain path traversal sequences or path separators")
27+
end
28+
end
29+
30+
function validate_package_name(name::AbstractString)
31+
if isempty(name)
32+
error("Package name cannot be empty")
33+
end
34+
if !occursin(r"^[a-zA-Z][a-zA-Z0-9_]*$", name)
35+
error("Package name must start with a letter and contain only letters, numbers, and underscores")
36+
end
37+
end
38+
39+
function validate_submodule_name(name::Union{AbstractString,Nothing})
40+
if name !== nothing
41+
if isempty(name)
42+
error("Submodule name cannot be empty")
43+
end
44+
if !occursin(r"^[a-zA-Z][a-zA-Z0-9_]*$", name)
45+
error("Submodule name must start with a letter and contain only letters, numbers, and underscores")
46+
end
47+
end
48+
end
49+
1850

1951
function rm_shim(name; kwargs...)
52+
validate_app_name(name)
2053
Base.rm(joinpath(julia_bin_path(), name * (Sys.iswindows() ? ".bat" : "")); kwargs...)
2154
end
2255

@@ -38,6 +71,30 @@ function overwrite_file_if_different(file, content)
3871
end
3972
end
4073

74+
function check_apps_in_path(apps)
75+
for app_name in keys(apps)
76+
which_result = Sys.which(app_name)
77+
if which_result === nothing
78+
@warn """
79+
App '$app_name' was installed but is not available in PATH.
80+
Consider adding '$(julia_bin_path())' to your PATH environment variable.
81+
""" maxlog=1
82+
break # Only show warning once per installation
83+
else
84+
# Check for collisions
85+
expected_path = joinpath(julia_bin_path(), app_name * (Sys.iswindows() ? ".bat" : ""))
86+
if which_result != expected_path
87+
@warn """
88+
App '$app_name' collision detected:
89+
Expected: $expected_path
90+
Found: $which_result
91+
Another application with the same name exists in PATH.
92+
"""
93+
end
94+
end
95+
end
96+
end
97+
4198
function get_max_version_register(pkg::PackageSpec, regs)
4299
max_v = nothing
43100
tree_hash = nothing
@@ -157,6 +214,7 @@ function add(pkg::PackageSpec)
157214
precompile(pkg.name)
158215

159216
@info "For package: $(pkg.name) installed apps $(join(keys(project.apps), ","))"
217+
check_apps_in_path(project.apps)
160218
end
161219

162220
function develop(pkg::Vector{PackageSpec})
@@ -192,6 +250,7 @@ function develop(pkg::PackageSpec)
192250
_resolve(manifest, pkg.name)
193251
precompile(pkg.name)
194252
@info "For package: $(pkg.name) installed apps: $(join(keys(project.apps), ","))"
253+
check_apps_in_path(project.apps)
195254
end
196255

197256

@@ -205,6 +264,7 @@ function update(pkgs_or_apps::Vector)
205264
end
206265
end
207266

267+
# XXX: Is updating an app ever different from rm-ing and adding it from scratch?
208268
function update(pkg::Union{PackageSpec, Nothing}=nothing)
209269
ctx = app_context()
210270
manifest = ctx.env.manifest
@@ -385,21 +445,30 @@ const SHIM_VERSION = 1.0
385445
const SHIM_HEADER = """$SHIM_COMMENT This file is generated by the Julia package manager.
386446
$SHIM_COMMENT Shim version: $SHIM_VERSION"""
387447

388-
389448
function generate_shims_for_apps(pkgname, apps, env, julia)
390449
for (_, app) in apps
391450
generate_shim(pkgname, app, env, julia)
392451
end
393452
end
394453

395454
function generate_shim(pkgname, app::AppInfo, env, julia)
455+
validate_package_name(pkgname)
456+
validate_app_name(app.name)
457+
validate_submodule_name(app.submodule)
458+
459+
module_spec = app.submodule === nothing ? pkgname : "$(pkgname).$(app.submodule)"
460+
396461
filename = app.name * (Sys.iswindows() ? ".bat" : "")
397462
julia_bin_filename = joinpath(julia_bin_path(), filename)
398-
mkpath(dirname(filename))
463+
mkpath(dirname(julia_bin_filename))
399464
content = if Sys.iswindows()
400-
windows_shim(pkgname, app, julia, env)
465+
julia_escaped = "\"$(Base.shell_escape_wincmd(julia))\""
466+
module_spec_escaped = "\"$(Base.shell_escape_wincmd(module_spec))\""
467+
windows_shim(julia_escaped, module_spec_escaped, env)
401468
else
402-
bash_shim(pkgname, app, julia, env)
469+
julia_escaped = Base.shell_escape(julia)
470+
module_spec_escaped = Base.shell_escape(module_spec)
471+
bash_shim(julia_escaped, module_spec_escaped, env)
403472
end
404473
overwrite_file_if_different(julia_bin_filename, content)
405474
if Sys.isunix()
@@ -408,24 +477,22 @@ function generate_shim(pkgname, app::AppInfo, env, julia)
408477
end
409478

410479

411-
function bash_shim(pkgname, app::AppInfo, julia::String, env)
412-
module_spec = app.submodule === nothing ? pkgname : "$(pkgname).$(app.submodule)"
480+
function bash_shim(julia_escaped::String, module_spec_escaped::String, env)
413481
return """
414482
#!/usr/bin/env bash
415483
416484
$SHIM_HEADER
417485
418486
export JULIA_LOAD_PATH=$(repr(env))
419487
export JULIA_DEPOT_PATH=$(repr(join(DEPOT_PATH, ':')))
420-
exec $julia \\
488+
exec $julia_escaped \\
421489
--startup-file=no \\
422-
-m $(module_spec) \\
490+
-m $module_spec_escaped \\
423491
"\$@"
424492
"""
425493
end
426494

427-
function windows_shim(pkgname, app::AppInfo, julia::String, env)
428-
module_spec = app.submodule === nothing ? pkgname : "$(pkgname).$(app.submodule)"
495+
function windows_shim(julia_escaped::String, module_spec_escaped::String, env)
429496
return """
430497
@echo off
431498
@@ -435,9 +502,9 @@ function windows_shim(pkgname, app::AppInfo, julia::String, env)
435502
set JULIA_LOAD_PATH=$env
436503
set JULIA_DEPOT_PATH=$(join(DEPOT_PATH, ';'))
437504
438-
$julia ^
505+
$julia_escaped ^
439506
--startup-file=no ^
440-
-m $(module_spec) ^
507+
-m $module_spec_escaped ^
441508
%*
442509
"""
443510
end

src/Types.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,6 @@ Base.hash(t::Compat, h::UInt) = hash(t.val, h)
241241
struct AppInfo
242242
name::String
243243
julia_command::Union{String, Nothing}
244-
julia_version::Union{VersionNumber, Nothing}
245244
submodule::Union{String, Nothing}
246245
other::Dict{String,Any}
247246
end

src/manifest.jl

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,6 @@ function read_apps(apps::Dict)
8989
submodule = get(app, "submodule", nothing)
9090
appinfo = AppInfo(appname::String,
9191
app["julia_command"]::String,
92-
VersionNumber(app["julia_version"]::String),
9392
submodule,
9493
app)
9594
appinfos[appinfo.name] = appinfo
@@ -335,8 +334,7 @@ function destructure(manifest::Manifest)::Dict
335334
new_entry["apps"] = Dict{String,Any}()
336335
for (appname, appinfo) in entry.apps
337336
julia_command = @something appinfo.julia_command joinpath(Sys.BINDIR, "julia" * (Sys.iswindows() ? ".exe" : ""))
338-
julia_version = @something appinfo.julia_version VERSION
339-
app_dict = Dict{String,Any}("julia_command" => julia_command, "julia_version" => julia_version)
337+
app_dict = Dict{String,Any}("julia_command" => julia_command)
340338
if appinfo.submodule !== nothing
341339
app_dict["submodule"] = appinfo.submodule
342340
end

src/project.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ 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, nothing, submodule, other)
86+
appinfos[name] = AppInfo(name, nothing, submodule, other)
8787
end
8888
return appinfos
8989
end

0 commit comments

Comments
 (0)