@@ -12,7 +12,7 @@ using TOML
1212using Glob
1313using p7zip_jll: p7zip_path
1414
15- export create_sysimage, create_app, create_library
15+ export create_sysimage, create_app, create_distribution, create_library
1616
1717include (" juliaconfig.jl" )
1818include (" ../ext/TerminalSpinners.jl" )
@@ -168,6 +168,18 @@ function gather_stdlibs_project(ctx)
168168 return stdlib_names
169169end
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+
171183function 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
980992end
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
9831066function 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" ))
13001383end
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+
13021428function bundle_julia_executable (dir:: String )
13031429 bindir = joinpath (dir, " bin" )
13041430 name = Base. julia_exename ()
0 commit comments