Skip to content

Commit

Permalink
update
Browse files Browse the repository at this point in the history
  • Loading branch information
KristofferC authored and fredrikekre committed Nov 7, 2024
1 parent c941828 commit 4e80a88
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 77 deletions.
2 changes: 1 addition & 1 deletion ext/REPLExt/completions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ end
import Pkg: Operations, Types, Apps
function complete_installed_apps(options, partial; hint)
manifest = try
Types.read_manifest(joinpath(Apps.APP_ENV_FOLDER, "AppManifest.toml"))
Types.read_manifest(joinpath(Apps.app_env_folder(), "AppManifest.toml"))
catch err
err isa PkgError || rethrow()
return String[]
Expand Down
165 changes: 89 additions & 76 deletions src/Apps/Apps.jl
Original file line number Diff line number Diff line change
@@ -1,32 +1,25 @@
module Apps

using Pkg
using Pkg.Types: AppInfo, PackageSpec, Context, EnvCache, PackageEntry, handle_repo_add!, handle_repo_develop!, write_manifest, write_project,
using Pkg.Types: AppInfo, PackageSpec, Context, EnvCache, PackageEntry, Manifest, handle_repo_add!, handle_repo_develop!, write_manifest, write_project,
pkgerror
using Pkg.Operations: print_single, source_path, update_package_add
using Pkg.API: handle_package_input!
using TOML, UUIDs
import Pkg.Registry

#############
# Constants #
#############
app_env_folder() = joinpath(first(DEPOT_PATH), "environments", "apps")
app_manifest_file() = joinpath(app_env_folder(), "AppManifest.toml")
julia_bin_path() = joinpath(first(DEPOT_PATH), "bin")

# Should use `DEPOT_PATH[1]` instead of `homedir()`?
const APP_ENV_FOLDER = joinpath(homedir(), ".julia", "environments", "apps")
const APP_MANIFEST_FILE = joinpath(APP_ENV_FOLDER, "AppManifest.toml")
const JULIA_BIN_PATH = joinpath(homedir(), ".julia", "bin")
app_context() = Context(env=EnvCache(joinpath(app_env_folder(), "Project.toml")))


##################
# Helper Methods #
##################

function rm_shim(name; kwargs...)
Base.rm(joinpath(JULIA_BIN_PATH, name); kwargs...)
Base.rm(joinpath(julia_bin_path(), name); kwargs...)
end

function handle_project_file(sourcepath)
function get_project(sourcepath)
project_file = joinpath(sourcepath, "Project.toml")
isfile(project_file) || error("Project file not found: $project_file")

Expand All @@ -35,13 +28,8 @@ function handle_project_file(sourcepath)
return project
end

function update_app_manifest(pkg)
manifest = Pkg.Types.read_manifest(APP_MANIFEST_FILE)
manifest.deps[pkg.uuid] = pkg
write_manifest(manifest, APP_MANIFEST_FILE)
end

function overwrite_if_different(file, content)
function overwrite_file_if_different(file, content)
if !isfile(file) || read(file, String) != content
mkpath(dirname(file))
write(file, content)
Expand Down Expand Up @@ -76,14 +64,14 @@ function get_max_version_register(pkg::PackageSpec, regs)
return (max_v, tree_hash)
end

app_context() = Context(env=EnvCache(joinpath(APP_ENV_FOLDER, "Project.toml")))

##################
# Main Functions #
##################

# TODO: Add functions similar to API that takes name, Vector{String} etc and promotes it to `Vector{PackageSpec}`..


function add(pkg::String)
pkg = PackageSpec(pkg)
add(pkg)
Expand All @@ -95,54 +83,70 @@ function add(pkg::Vector{PackageSpec})
end
end


function add(pkg::PackageSpec)
handle_package_input!(pkg)

ctx = app_context()
manifest = ctx.env.manifest
new = false

# Download package
if pkg.repo.source !== nothing || pkg.repo.rev !== nothing
entry = Pkg.API.manifest_info(ctx.env.manifest, pkg.uuid)
pkg = update_package_add(ctx, pkg, entry, false)
new = handle_repo_add!(ctx, pkg)
else
pkgs = [pkg]
Pkg.Operations.registry_resolve!(ctx.registries, pkgs)
Pkg.Operations.ensure_resolved(ctx, ctx.env.manifest, pkgs, registry=true)
Pkg.Operations.ensure_resolved(ctx, manifest, pkgs, registry=true)

pkg.version, pkg.tree_hash = get_max_version_register(pkg, ctx.registries)

new = Pkg.Operations.download_source(ctx, pkgs)
end

sourcepath = source_path(ctx.env.manifest_file, pkg)
project = handle_project_file(sourcepath)
sourcepath = source_path(manifest_file, pkg)
project = get_project(sourcepath)
# TODO: Wrong if package itself has a sourcepath?
project.entryfile = joinpath(sourcepath, "src", "$(project.name).jl")

# TODO: Type stab
# appdeps = get(project, "appdeps", Dict())
# merge!(project.deps, appdeps)
entry = PackageEntry(;apps = project.apps, name = pkg.name, version = project.version, tree_hash = pkg.tree_hash, path = pkg.path, repo = pkg.repo, uuid=pkg.uuid)
manifest.deps[pkg.uuid] = entry

projectfile = joinpath(APP_ENV_FOLDER, pkg.name, "Project.toml")
mkpath(dirname(projectfile))
write_project(project, projectfile)
_resolve(manifest, pkg.name)
@info "For package: $(pkg.name) installed apps $(join(keys(project.apps), ","))"
end

# Move manifest if it exists here.
Pkg.activate(joinpath(APP_ENV_FOLDER, pkg.name)) do
Pkg.instantiate()
end
function _resolve(manifest::Manifest, pkgname=nothing)
for (uuid, pkg) in manifest.deps
if pkgname !== nothing && pkg.name !== pkgname
continue
end
if pkg.path == nothing
projectfile = joinpath(app_env_folder(), pkg.name, "Project.toml")
sourcepath = source_path(app_manifest_file(), pkg)
project = get_project(sourcepath)
project.entryfile = joinpath(sourcepath, "src", "$(project.name).jl")
mkpath(dirname(projectfile))
write_project(project, projectfile)
# Move manifest if it exists here.

Pkg.activate(joinpath(app_env_folder(), pkg.name)) do
Pkg.instantiate()
end
else
# TODO: Not hardcode Project.toml
projectfile = joinpath(source_path(app_manifest_file(), pkg), "Project.toml")
@show projectfile
end

if new
# TODO: Call build on the package if it was freshly installed?
# TODO: Julia path
generate_shims_for_apps(pkg.name, pkg.apps, dirname(projectfile), joinpath(Sys.BINDIR, "julia"))
end

# Create the new package env.
entry = PackageEntry(;apps = project.apps, name = pkg.name, version = project.version, tree_hash = pkg.tree_hash, path = pkg.path, repo = pkg.repo, uuid=pkg.uuid)
update_app_manifest(entry)
generate_shims_for_apps(entry.name, entry.apps, dirname(projectfile))
write_manifest(manifest, app_manifest_file())
end


function develop(pkg::String)
develop(PackageSpec(pkg))
end
Expand All @@ -161,7 +165,7 @@ function develop(pkg::PackageSpec)
ctx = app_context()
handle_repo_develop!(ctx, pkg, #=shared =# true)
sourcepath = abspath(source_path(ctx.env.manifest_file, pkg))
project = handle_project_file(sourcepath)
project = get_project(sourcepath)

# Seems like the `.repo.source` field is not cleared.
# At least repo-url is still in the manifest after doing a dev with a path
Expand All @@ -171,9 +175,13 @@ function develop(pkg::PackageSpec)
pkg.repo.source = nothing
end


entry = PackageEntry(;apps = project.apps, name = pkg.name, version = project.version, tree_hash = pkg.tree_hash, path = sourcepath, repo = pkg.repo, uuid=pkg.uuid)
update_app_manifest(entry)
generate_shims_for_apps(entry.name, entry.apps, sourcepath)
manifest = ctx.env.manifest
manifest.deps[pkg.uuid] = entry

_resolve(manifest, pkg.name)
@info "For package: $(pkg.name) installed apps: $(join(keys(project.apps), ","))"
end

function status(pkgs_or_apps::Vector)
Expand All @@ -193,7 +201,7 @@ function status(pkg_or_app::Union{PackageSpec, Nothing}=nothing)
# TODO: Sort.
# TODO: Show julia version
pkg_or_app = pkg_or_app === nothing ? nothing : pkg_or_app.name
manifest = Pkg.Types.read_manifest(joinpath(APP_ENV_FOLDER, "AppManifest.toml"))
manifest = Pkg.Types.read_manifest(joinpath(app_env_folder(), "AppManifest.toml"))
deps = Pkg.Operations.load_manifest_deps(manifest)

is_pkg = pkg_or_app !== nothing && any(dep -> dep.name == pkg_or_app, values(manifest.deps))
Expand Down Expand Up @@ -223,15 +231,15 @@ end

#=
function precompile(pkg::Union{Nothing, String}=nothing)
manifest = Pkg.Types.read_manifest(joinpath(APP_ENV_FOLDER, "AppManifest.toml"))
manifest = Pkg.Types.read_manifest(joinpath(app_env_folder(), "AppManifest.toml"))
deps = Pkg.Operations.load_manifest_deps(manifest)
for dep in deps
# TODO: Parallel app compilation..?
info = manifest.deps[dep.uuid]
if pkg !== nothing && info.name !== pkg
continue
end
Pkg.activate(joinpath(APP_ENV_FOLDER, info.name)) do
Pkg.activate(joinpath(app_env_folder(), info.name)) do
@info "Precompiling $(info.name)..."
Pkg.precompile()
end
Expand Down Expand Up @@ -262,18 +270,18 @@ function rm(pkg_or_app::Union{PackageSpec, Nothing}=nothing)

require_not_empty(pkg_or_app, :rm)

manifest = Pkg.Types.read_manifest(joinpath(APP_ENV_FOLDER, "AppManifest.toml"))
manifest = Pkg.Types.read_manifest(joinpath(app_env_folder(), "AppManifest.toml"))
dep_idx = findfirst(dep -> dep.name == pkg_or_app, manifest.deps)
if dep_idx !== nothing
dep = manifest.deps[dep_idx]
@info "Deleted all apps for package $(dep.name)"
@info "Deleting all apps for package $(dep.name)"
delete!(manifest.deps, dep.uuid)
for (appname, appinfo) in dep.apps
@info "Deleted $(appname)"
rm_shim(appname; force=true)
end
if dep.path === nothing
Base.rm(joinpath(APP_ENV_FOLDER, dep.name); recursive=true)
Base.rm(joinpath(app_env_folder(), dep.name); recursive=true)
end
else
for (uuid, pkg) in manifest.deps
Expand All @@ -286,70 +294,75 @@ function rm(pkg_or_app::Union{PackageSpec, Nothing}=nothing)
end
if isempty(pkg.apps)
delete!(manifest.deps, uuid)
Base.rm(joinpath(APP_ENV_FOLDER, pkg.name); recursive=true)
Base.rm(joinpath(app_env_folder(), pkg.name); recursive=true)
end
end
end

Pkg.Types.write_manifest(manifest, APP_MANIFEST_FILE)
Pkg.Types.write_manifest(manifest, app_manifest_file())
return
end



#########
# Shims #
#########

function generate_shims_for_apps(pkgname, apps, env)
const SHIM_COMMENT = Sys.iswindows() ? "REM " : "#"
const SHIM_VERSION = 1.0
const SHIM_HEADER = """$SHIM_COMMENT This file is generated by the Julia package manager.
$SHIM_COMMENT Shim version: $SHIM_VERSION"""


function generate_shims_for_apps(pkgname, apps, env, julia)
for (_, app) in apps
generate_shim(app, pkgname; env)
generate_shim(pkgname, app, env, julia)
end
end

function generate_shim(app::AppInfo, pkgname; julia_executable_path::String=joinpath(Sys.BINDIR, "julia"), env=joinpath(homedir(), ".julia", "environments", "apps", pkgname))
function generate_shim(pkgname, app::AppInfo, env, julia)
filename = app.name * (Sys.iswindows() ? ".bat" : "")
julia_bin_filename = joinpath(JULIA_BIN_PATH, filename)
julia_bin_filename = joinpath(julia_bin_path(), filename)
mkpath(dirname(filename))
content = if Sys.iswindows()
windows_shim(pkgname, julia_executable_path, env)
windows_shim(pkgname, julia, env)
else
bash_shim(pkgname, julia_executable_path, env)
bash_shim(pkgname, julia, env)
end
overwrite_if_different(julia_bin_filename, content)
overwrite_file_if_different(julia_bin_filename, content)
if Sys.isunix()
chmod(julia_bin_filename, 0o755)
end
end


function bash_shim(pkgname, julia_executable_path::String, env)
function bash_shim(pkgname, julia::String, env)
return """
#!/usr/bin/env bash
$SHIM_HEADER
export JULIA_LOAD_PATH=$(repr(env))
exec $julia_executable_path \\
exec $julia \\
--startup-file=no \\
-m $(pkgname) \\
"\$@"
"""
end

function windows_shim(pkgname, julia_executable_path::String, env)
function windows_shim(pkgname, julia::String, env)
return """
@echo off
set JULIA_LOAD_PATH=$(repr(env))
$julia_executable_path ^
$julia ^
--startup-file=no ^
-m $(pkgname) ^
%*
"""
end




#=
#################
# PATH handling #
Expand All @@ -363,26 +376,26 @@ function add_bindir_to_path()
end
end
function get_shell_config_file(julia_bin_path)
function get_shell_config_file(julia_bin_path())
home_dir = ENV["HOME"]
# Check for various shell configuration files
if occursin("/zsh", ENV["SHELL"])
return (joinpath(home_dir, ".zshrc"), "path=('$julia_bin_path' \$path)\nexport PATH")
return (joinpath(home_dir, ".zshrc"), "path=('$julia_bin_path()' \$path)\nexport PATH")
elseif occursin("/bash", ENV["SHELL"])
return (joinpath(home_dir, ".bashrc"), "export PATH=\"\$PATH:$julia_bin_path\"")
return (joinpath(home_dir, ".bashrc"), "export PATH=\"\$PATH:$julia_bin_path()\"")
elseif occursin("/fish", ENV["SHELL"])
return (joinpath(home_dir, ".config/fish/config.fish"), "set -gx PATH \$PATH $julia_bin_path")
return (joinpath(home_dir, ".config/fish/config.fish"), "set -gx PATH \$PATH $julia_bin_path()")
elseif occursin("/ksh", ENV["SHELL"])
return (joinpath(home_dir, ".kshrc"), "export PATH=\"\$PATH:$julia_bin_path\"")
return (joinpath(home_dir, ".kshrc"), "export PATH=\"\$PATH:$julia_bin_path()\"")
elseif occursin("/tcsh", ENV["SHELL"]) || occursin("/csh", ENV["SHELL"])
return (joinpath(home_dir, ".tcshrc"), "setenv PATH \$PATH:$julia_bin_path") # or .cshrc
return (joinpath(home_dir, ".tcshrc"), "setenv PATH \$PATH:$julia_bin_path()") # or .cshrc
else
return (nothing, nothing)
end
end
function update_unix_PATH()
shell_config_file, path_command = get_shell_config_file(JULIA_BIN_PATH)
shell_config_file, path_command = get_shell_config_file(julia_bin_path())
if shell_config_file === nothing
@warn "Failed to insert `.julia/bin` to PATH: Failed to detect shell"
return
Expand Down Expand Up @@ -411,8 +424,8 @@ end
function update_windows_PATH()
current_path = ENV["PATH"]
occursin(JULIA_BIN_PATH, current_path) && return
new_path = "$current_path;$JULIA_BIN_PATH"
occursin(julia_bin_path(), current_path) && return
new_path = "$current_path;$julia_bin_path()"
run(`setx PATH "$new_path"`)
end
=#
Expand Down

0 comments on commit 4e80a88

Please sign in to comment.