Skip to content

Commit 4cc409b

Browse files
author
KristofferC
committed
wip: add a create_distribution entry point
this creates basically a new julia "distribution" with a custom set of packages in it. Right now: - packages get added to the sysimage - packages are treated as stdlib so they can be loaded via `using Package` without a corresponding project file - packages are also treated as stdlibs by Pkg - packages source code is not copied, shims are added so that Pkg will properly treat it as an stdlib
1 parent c1e37c4 commit 4cc409b

File tree

1 file changed

+127
-1
lines changed

1 file changed

+127
-1
lines changed

src/PackageCompiler.jl

Lines changed: 127 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ using TOML
1212
using Glob
1313
using p7zip_jll: p7zip_path
1414

15-
export create_sysimage, create_app, create_library
15+
export create_sysimage, create_app, create_distribution, create_library
1616

1717
include("juliaconfig.jl")
1818
include("../ext/TerminalSpinners.jl")
@@ -168,6 +168,18 @@ function gather_stdlibs_project(ctx)
168168
return stdlib_names
169169
end
170170

171+
function gather_dependency_entries(ctx; include_stdlibs::Bool=false)
172+
manifest = ctx.env.manifest
173+
manifest === nothing && return Pkg.Types.PackageEntry[]
174+
entries = Pkg.Types.PackageEntry[]
175+
for entry in values(manifest)
176+
include_stdlibs || entry.name in _STDLIBS && continue
177+
push!(entries, entry)
178+
end
179+
sort!(entries, by = entry -> something(entry.name, entry.uuid === nothing ? "" : string(entry.uuid)))
180+
return entries
181+
end
182+
171183
function check_packages_in_project(ctx, packages)
172184
packages_in_project = collect(keys(ctx.env.project.deps))
173185
if ctx.env.pkg !== nothing
@@ -979,6 +991,77 @@ function create_app(package_dir::String,
979991
end
980992
end
981993

994+
"""
995+
create_distribution(project_dir::String, dist_dir::String; kwargs...)
996+
997+
Create a relocatable Julia tree rooted at `dist_dir` that behaves like the official
998+
Julia downloads but with the dependencies of `project_dir` baked into the sysimage.
999+
The baked packages are also exposed as stdlibs so that Pkg treats them as part of
1000+
the distribution. Packages are not imported into `Main`, keeping the default Julia
1001+
runtime behavior.
1002+
1003+
# Keyword arguments
1004+
1005+
- `precompile_execution_file`: Same as [`create_app`](@ref), these scripts are executed when generating
1006+
the sysimage.
1007+
- `precompile_statements_file`: Extra precompile statements appended to the sysimage build.
1008+
- `incremental::Bool=true`: Whether to extend the current Julia sysimage instead of creating a fresh one.
1009+
- `force::Bool=false`: Overwrite `dist_dir` if it exists.
1010+
- `cpu_target::String=default_app_cpu_target()`: CPU target used when compiling the sysimage.
1011+
- `include_lazy_artifacts::Bool=false`: If `true`, lazy artifacts referenced by dependencies are bundled.
1012+
- `sysimage_build_args::Cmd=```: Additional flags for the Julia process building the sysimage.
1013+
- `include_transitive_dependencies::Bool=true`: If `true`, include transitive dependencies in the sysimage.
1014+
- `include_preferences::Bool=true`: Bundle package preferences into `share/julia/LocalPreferences.toml`.
1015+
- `script::Union{Nothing,String}=nothing`: Optional script executed while generating the sysimage.
1016+
"""
1017+
function create_distribution(project_dir::String,
1018+
dist_dir::String;
1019+
precompile_execution_file::Union{String, Vector{String}}=String[],
1020+
precompile_statements_file::Union{String, Vector{String}}=String[],
1021+
incremental::Bool=true,
1022+
force::Bool=false,
1023+
cpu_target::String=default_app_cpu_target(),
1024+
include_lazy_artifacts::Bool=false,
1025+
sysimage_build_args::Cmd=``,
1026+
include_transitive_dependencies::Bool=true,
1027+
include_preferences::Bool=true,
1028+
script::Union{Nothing, String}=nothing)
1029+
ctx = create_pkg_context(project_dir)
1030+
Pkg.instantiate(ctx, verbose=true, allow_autoprecomp=false)
1031+
1032+
try_rm_dir(dist_dir; force)
1033+
ensure_default_depot_paths(dist_dir)
1034+
1035+
stdlibs = gather_stdlibs_project(ctx)
1036+
stdlibs = unique(vcat(stdlibs, map(pkg -> pkg.name, stdlibs_in_default_sysimage())))
1037+
bundle_julia_libraries(dist_dir, stdlibs)
1038+
bundle_default_stdlibs(dist_dir)
1039+
1040+
manifest_pkg_entries = gather_dependency_entries(ctx)
1041+
bundle_custom_stdlibs(ctx, dist_dir, manifest_pkg_entries)
1042+
1043+
bundle_julia_libexec(ctx, dist_dir)
1044+
bundle_julia_executable(dist_dir)
1045+
bundle_artifacts(ctx, dist_dir; include_lazy_artifacts)
1046+
include_preferences && bundle_preferences(ctx, dist_dir)
1047+
bundle_cert(dist_dir)
1048+
1049+
sysimage_path = joinpath(dist_dir, "lib", "julia", "sys." * Libdl.dlext)
1050+
project = dirname(ctx.env.project_file)
1051+
create_sysimage(; sysimage_path, project,
1052+
incremental,
1053+
filter_stdlibs=false,
1054+
precompile_execution_file,
1055+
precompile_statements_file,
1056+
cpu_target,
1057+
sysimage_build_args,
1058+
include_transitive_dependencies,
1059+
script,
1060+
import_into_main=false)
1061+
1062+
return nothing
1063+
end
1064+
9821065

9831066
function create_executable_from_sysimg(exe_path::String,
9841067
c_driver_program::String,
@@ -1299,6 +1382,49 @@ function bundle_project(ctx, dir)
12991382
Pkg.Types.write_project(d, joinpath(julia_share, "Project.toml"))
13001383
end
13011384

1385+
function ensure_default_depot_paths(dest_dir)
1386+
mkpath(joinpath(dest_dir, "share", "julia"))
1387+
mkpath(joinpath(dest_dir, "local", "share", "julia"))
1388+
end
1389+
1390+
function bundle_default_stdlibs(dest_dir)
1391+
src_stdlib = abspath(Sys.BINDIR, "..", "share", "julia", "stdlib")
1392+
dest_stdlib = joinpath(dest_dir, "share", "julia", "stdlib")
1393+
mkpath(dirname(dest_stdlib))
1394+
if isdir(dest_stdlib)
1395+
rm(dest_stdlib; recursive=true, force=true)
1396+
end
1397+
cp(src_stdlib, dest_stdlib; force=true)
1398+
end
1399+
1400+
function bundle_custom_stdlibs(ctx, dest_dir, packages::Vector{Pkg.Types.PackageEntry})
1401+
isempty(packages) && return
1402+
version_dir = joinpath(dest_dir, "share", "julia", "stdlib", string('v', VERSION.major, '.', VERSION.minor))
1403+
mkpath(version_dir)
1404+
for pkg in packages
1405+
pkg_source_path = source_path(ctx, pkg)
1406+
pkg_source_path === nothing && error("Unable to locate source for $(pkg.name); ensure the package exists in the current project.")
1407+
project_toml = joinpath(pkg_source_path, "Project.toml")
1408+
isfile(project_toml) || error("Project.toml for package $(pkg.name) not found at $(project_toml)")
1409+
pkg_name = something(pkg.name, string(pkg.uuid))
1410+
pkg_stdlib_dir = joinpath(version_dir, pkg_name)
1411+
if isdir(pkg_stdlib_dir)
1412+
@debug "Stdlib directory $(pkg_stdlib_dir) already exists; not overwriting"
1413+
continue
1414+
end
1415+
mkpath(pkg_stdlib_dir)
1416+
cp(project_toml, joinpath(pkg_stdlib_dir, "Project.toml"); force=true)
1417+
stub_dir = joinpath(pkg_stdlib_dir, "src")
1418+
mkpath(stub_dir)
1419+
stub_path = joinpath(stub_dir, string(pkg_name, ".jl"))
1420+
open(stub_path, "w") do io
1421+
println(io, "# Autogenerated placeholder for $(pkg_name).")
1422+
println(io, "# The module implementation currently ships inside the sysimage.")
1423+
println(io, "# TODO: Copy the full source tree for $(pkg_name) into this stdlib directory.")
1424+
end
1425+
end
1426+
end
1427+
13021428
function bundle_julia_executable(dir::String)
13031429
bindir = joinpath(dir, "bin")
13041430
name = Base.julia_exename()

0 commit comments

Comments
 (0)