Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
.DS_Store
*Manifest.toml
*Manifest*.toml
.vscode
2 changes: 2 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ PackageCompiler = "9b87118b-4619-50d2-8e1e-99f35a4d4d9d"
Patchelf_jll = "f2cf89d6-2bfd-5c44-bd2c-068eea195c0c"
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
RelocatableFolders = "05181044-ff0b-4ac5-8273-598c1e38db00"
Revise = "295af30f-e4ad-537b-8983-00126c2a3abe"
StructIO = "53d494c1-5632-5724-8f4c-31dff12d585f"

[compat]
Expand All @@ -23,6 +24,7 @@ PackageCompiler = "2"
Patchelf_jll = "0.18"
Pkg = "1"
RelocatableFolders = "1"
Revise = "3.11.0"
StructIO = "0.3"
julia = "1.10"

Expand Down
137 changes: 94 additions & 43 deletions src/compiling.jl
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
# Helper to conditionally create and manage a temporary depot for trim preferences
# When trim is enabled, creates a temp depot and calls f(depot_path) with automatic cleanup.
# When trim is disabled, calls f(nothing) without creating a depot.
function mktempdepot(f, trim_enabled::Bool)
if trim_enabled
mktempdir(f)
else
f(nothing)
end
end

# Lightweight terminal spinner
function _start_spinner(message::String; io::IO=stderr)
anim_chars = ("◐", "◓", "◑", "◒")
Expand Down Expand Up @@ -78,52 +89,92 @@ function compile_products(recipe::ImageRecipe)
end

project_arg = recipe.project == "" ? Base.active_project() : recipe.project
env_overrides = Dict{String,Any}("JULIA_LOAD_PATH"=>nothing)
inst_cmd = addenv(`$(Base.julia_cmd(cpu_target=precompile_cpu_target)) --project=$project_arg -e "using Pkg; Pkg.instantiate(); Pkg.precompile()"`, env_overrides...)
recipe.verbose && println("Running: $inst_cmd")
precompile_time = time_ns()
if !success(pipeline(inst_cmd; stdout, stderr))
error("Error encountered during instantiate/precompile of app project.")
end
recipe.verbose && println("Precompilation took $((time_ns() - precompile_time)/1e9) s")
# Compile the Julia code
if recipe.img_path == ""
tmpdir = mktempdir()
recipe.img_path = joinpath(tmpdir, "image.o.a")
end
project_arg = recipe.project == "" ? Base.active_project() : recipe.project
# Build command incrementally to guarantee proper token separation
cmd = julia_cmd
cmd = `$cmd --project=$project_arg $(image_arg) $(recipe.img_path) --output-incremental=no`
for a in strip_args
cmd = `$cmd $a`
end
for a in recipe.julia_args
cmd = `$cmd $a`
end
cmd = `$cmd $(joinpath(JuliaC.SCRIPTS_DIR, "juliac-buildscript.jl")) --scripts-dir $(JuliaC.SCRIPTS_DIR) --source $(abspath(recipe.file)) $(recipe.output_type)`
if recipe.add_ccallables
cmd = `$cmd --compile-ccallable`
end
if recipe.use_loaded_libs
cmd = `$cmd --use-loaded-libs`
end

# Threading
cmd = addenv(cmd, env_overrides...)
recipe.verbose && println("Running: $cmd")
# Show a spinner while the compiler runs
spinner_done, spinner_task = _start_spinner("Compiling...")
compile_time = time_ns()
try
if !success(pipeline(cmd; stdout, stderr))
error("Failed to compile $(recipe.file)")
# Use mktempdepot helper to conditionally manage temp depot and preferences environment.
# When trim is enabled, the helper creates a temp depot and passes it to the block.
# When trim is disabled, it passes nothing.
mktempdepot(is_trim_enabled(recipe)) do tmp_depot
env_overrides = Dict{String,Any}()
tmp_prefs_env = nothing

# If a temporary depot was created, set JULIA_DEPOT_PATH and create preferences environment
if tmp_depot !== nothing
load_path_sep = Sys.iswindows() ? ";" : ":"
env_overrides["JULIA_DEPOT_PATH"] = tmp_depot * load_path_sep

# Create a temporary environment with a LocalPreferences.toml that will be added to JULIA_LOAD_PATH
tmp_prefs_env = mktempdir()
# Write Project.toml with Preferences as a dependency so preferences are inherited
open(joinpath(tmp_prefs_env, "Project.toml"), "w") do io
println(io, "[deps]")
println(io, "Preferences = \"21216c6a-2e73-6563-6e65-726566657250\"")
end
# Write LocalPreferences.toml with the trim preferences
open(joinpath(tmp_prefs_env, "LocalPreferences.toml"), "w") do io
println(io, "[Preferences]")
println(io, "trim_enabled = true")
end
# Prepend the temp env to JULIA_LOAD_PATH; let Julia expand @ and @stdlib implicitly
env_overrides["JULIA_LOAD_PATH"] = tmp_prefs_env * load_path_sep
end

try
inst_cmd = addenv(`$(Base.julia_cmd(cpu_target=precompile_cpu_target)) --project=$project_arg -e "using Pkg; Pkg.instantiate(); Pkg.precompile()"`, env_overrides...)
recipe.verbose && println("Running: $inst_cmd")
precompile_time = time_ns()
if !success(pipeline(inst_cmd; stdout, stderr))
error("Error encountered during instantiate/precompile of app project.")
end
recipe.verbose && println("Precompilation took $((time_ns() - precompile_time)/1e9) s")
# Compile the Julia code
if recipe.img_path == ""
tmpdir = mktempdir()
recipe.img_path = joinpath(tmpdir, "image.o.a")
end
project_arg = recipe.project == "" ? Base.active_project() : recipe.project
# Build command incrementally to guarantee proper token separation
cmd = julia_cmd
cmd = `$cmd --project=$project_arg $(image_arg) $(recipe.img_path) --output-incremental=no`
for a in strip_args
cmd = `$cmd $a`
end
for a in recipe.julia_args
cmd = `$cmd $a`
end
cmd = `$cmd $(joinpath(JuliaC.SCRIPTS_DIR, "juliac-buildscript.jl")) --scripts-dir $(JuliaC.SCRIPTS_DIR) --source $(abspath(recipe.file)) $(recipe.output_type)`
if recipe.add_ccallables
cmd = `$cmd --compile-ccallable`
end
if recipe.use_loaded_libs
cmd = `$cmd --use-loaded-libs`
end

# Threading
cmd = addenv(cmd, env_overrides...)
recipe.verbose && println("Running: $cmd")
# Show a spinner while the compiler runs
spinner_done, spinner_task = _start_spinner("Compiling...")
compile_time = time_ns()
try
if !success(pipeline(cmd; stdout, stderr))
error("Failed to compile $(recipe.file)")
end
finally
spinner_done[] = true
wait(spinner_task)
end
recipe.verbose && println("Compilation took $((time_ns() - compile_time)/1e9) s")
finally
# Cleanup temporary preferences environment if created
if tmp_prefs_env !== nothing
try
rm(tmp_prefs_env; recursive=true, force=true)
catch e
@warn "Failed to cleanup temporary preferences environment: $tmp_prefs_env" exception=e
end
end
end
finally
spinner_done[] = true
wait(spinner_task)
end
recipe.verbose && println("Compilation took $((time_ns() - compile_time)/1e9) s")
# Print compiled image size
if recipe.verbose
@assert isfile(recipe.img_path)
Expand Down
10 changes: 10 additions & 0 deletions test/TrimPrefsProject/Project.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
name = "TrimPrefsProject"
uuid = "2685b477-6e63-411d-9143-facd9abab21c"
version = "0.1.0"
authors = ["Gabriel Baraldi <baraldigabriel@gmail.com>"]

[deps]
Preferences = "21216c6a-2e73-6563-6e65-726566657250"

[compat]
Preferences = "1.5.0"
18 changes: 18 additions & 0 deletions test/TrimPrefsProject/src/TrimPrefsProject.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module TrimPrefsProject

using Preferences

# Read the trim_enabled preference at compile time from the Preferences package
# This preference is set by JuliaC in the temporary depot during compilation
const TRIM_ENABLED = Preferences.load_preference(Preferences, "trim_enabled", false)::Bool

function @main(ARGS)
if TRIM_ENABLED
println(Core.stdout, "TRIM_MODE_ENABLED")
else
println(Core.stdout, "TRIM_MODE_DISABLED")
end
return 0
end

end # module TrimPrefsProject
24 changes: 24 additions & 0 deletions test/programatic.jl
Original file line number Diff line number Diff line change
Expand Up @@ -302,3 +302,27 @@ end
# Print tree for debugging/inspection
print_tree_with_sizes(outdir)
end

@testset "Trim preference propagation" begin
# Test that the trim_enabled preference is correctly propagated to packages
# during precompilation and compilation when trim mode is enabled.
# This verifies that LocalPreferences.toml is written and read correctly.
outdir = mktempdir()
exeout = joinpath(outdir, "trim_prefs_exe")
img = JuliaC.ImageRecipe(
file = TEST_TRIM_PREFS_PROJ,
output_type = "--output-exe",
trim_mode = "safe",
verbose = true,
)
JuliaC.compile_products(img)
link = JuliaC.LinkRecipe(image_recipe=img, outname=exeout)
JuliaC.link_products(link)
bun = JuliaC.BundleRecipe(link_recipe=link, output_dir=outdir)
JuliaC.bundle_products(bun)
actual_exe = Sys.iswindows() ? joinpath(outdir, "bin", basename(exeout) * ".exe") : joinpath(outdir, "bin", basename(exeout))
@test isfile(actual_exe)
output = read(`$actual_exe`, String)
# When trim is enabled, the package should see trim_enabled = true
@test occursin("TRIM_MODE_ENABLED", output)
end
1 change: 1 addition & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const TEST_PROJ = abspath(joinpath(@__DIR__, "AppProject"))
const TEST_SRC = joinpath(TEST_PROJ, "src", "test.jl")
const TEST_LIB_PROJ = abspath(joinpath(@__DIR__, "lib_project"))
const TEST_LIB_SRC = joinpath(TEST_LIB_PROJ, "src", "libtest.jl")
const TEST_TRIM_PREFS_PROJ = abspath(joinpath(@__DIR__, "TrimPrefsProject"))

include("utils.jl")
include("programatic.jl")
Expand Down