From 8096fe772a75df310e9bcff3f68cb57038bdf9ce Mon Sep 17 00:00:00 2001 From: vishal2005025 Date: Sat, 25 Oct 2025 23:55:29 +0530 Subject: [PATCH 1/3] Add --trim-mode (compile-time) preference #31 (fixed) --- Project.toml | 2 ++ src/compiling.jl | 35 +++++++++++++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index 3ff423a..f060283 100644 --- a/Project.toml +++ b/Project.toml @@ -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] @@ -23,6 +24,7 @@ PackageCompiler = "2" Patchelf_jll = "0.18" Pkg = "1" RelocatableFolders = "1" +Revise = "3.11.0" StructIO = "0.3" julia = "1.10" diff --git a/src/compiling.jl b/src/compiling.jl index ebb0db4..8211752 100644 --- a/src/compiling.jl +++ b/src/compiling.jl @@ -78,12 +78,43 @@ function compile_products(recipe::ImageRecipe) end project_arg = recipe.project == "" ? Base.active_project() : recipe.project + # Prepare environment overrides for the precompile step. We clear JULIA_LOAD_PATH + # by default so precompilation happens in a clean environment. If trimming is + # enabled, create a temporary depot with a LocalPreferences.toml so packages can + # read a compile-time preference (e.g. Preferences.load_preference(Preferences, "_trim_enabled")). env_overrides = Dict{String,Any}("JULIA_LOAD_PATH"=>nothing) + tmp_depot = nothing + if is_trim_enabled(recipe) + # Create a temporary depot and write LocalPreferences.toml with _trim_enabled = true + tmp_depot = mktempdir() + prefs_dir = joinpath(tmp_depot, "config") + mkpath(prefs_dir) + prefs_file = joinpath(prefs_dir, "LocalPreferences.toml") + open(prefs_file, "w") do io + println(io, "[Preferences]") + println(io, "_trim_enabled = true") + end + # Use this temporary depot for the precompile subprocess so Preferences.jl + # will pick up the compile-time preference. + env_overrides["JULIA_DEPOT_PATH"] = tmp_depot + end + 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.") + try + if !success(pipeline(inst_cmd; stdout, stderr)) + error("Error encountered during instantiate/precompile of app project.") + end + finally + # Cleanup temporary depot if created + if tmp_depot !== nothing + try + rm(tmp_depot; recursive=true, force=true) + catch + @warn "Failed to remove temporary depot: $tmp_depot" + end + end end recipe.verbose && println("Precompilation took $((time_ns() - precompile_time)/1e9) s") # Compile the Julia code From 4f889f1f64d0a387acb8b2aeb25532ab5a203ed4 Mon Sep 17 00:00:00 2001 From: Gabriel Baraldi Date: Thu, 4 Dec 2025 16:54:27 -0300 Subject: [PATCH 2/3] Change Pr slightly and add tests --- .gitignore | 2 +- src/compiling.jl | 105 +++++++++++------- test/TrimPrefsProject/Project.toml | 10 ++ test/TrimPrefsProject/src/TrimPrefsProject.jl | 18 +++ test/programatic.jl | 24 ++++ test/runtests.jl | 1 + 6 files changed, 120 insertions(+), 40 deletions(-) create mode 100644 test/TrimPrefsProject/Project.toml create mode 100644 test/TrimPrefsProject/src/TrimPrefsProject.jl diff --git a/.gitignore b/.gitignore index be81923..f2b7de1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ .DS_Store -*Manifest.toml +*Manifest*.toml .vscode \ No newline at end of file diff --git a/src/compiling.jl b/src/compiling.jl index ebb0db4..f4f9104 100644 --- a/src/compiling.jl +++ b/src/compiling.jl @@ -78,52 +78,79 @@ function compile_products(recipe::ImageRecipe) end project_arg = recipe.project == "" ? Base.active_project() : recipe.project - env_overrides = Dict{String,Any}("JULIA_LOAD_PATH"=>nothing) + env_overrides = Dict{String,Any}() + tmp_prefs_env = nothing + if is_trim_enabled(recipe) + # Create a temporary environment with a LocalPreferences.toml that will be added to JULIA_LOAD_PATH. + tmp_prefs_env = mktempdir() + # Create a Project.toml file so it's recognized as an environment + touch(joinpath(tmp_prefs_env, "Project.toml")) + # 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 + load_path_sep = Sys.iswindows() ? ";" : ":" + env_overrides["JULIA_LOAD_PATH"] = join([tmp_prefs_env, "@", "@stdlib"], load_path_sep) + end + 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)") + 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 - spinner_done[] = true - wait(spinner_task) + # 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 - recipe.verbose && println("Compilation took $((time_ns() - compile_time)/1e9) s") # Print compiled image size if recipe.verbose @assert isfile(recipe.img_path) diff --git a/test/TrimPrefsProject/Project.toml b/test/TrimPrefsProject/Project.toml new file mode 100644 index 0000000..009c624 --- /dev/null +++ b/test/TrimPrefsProject/Project.toml @@ -0,0 +1,10 @@ +name = "TrimPrefsProject" +uuid = "2685b477-6e63-411d-9143-facd9abab21c" +version = "0.1.0" +authors = ["Gabriel Baraldi "] + +[deps] +Preferences = "21216c6a-2e73-6563-6e65-726566657250" + +[compat] +Preferences = "1.5.0" diff --git a/test/TrimPrefsProject/src/TrimPrefsProject.jl b/test/TrimPrefsProject/src/TrimPrefsProject.jl new file mode 100644 index 0000000..77457c1 --- /dev/null +++ b/test/TrimPrefsProject/src/TrimPrefsProject.jl @@ -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 diff --git a/test/programatic.jl b/test/programatic.jl index 86ef18e..51d1426 100644 --- a/test/programatic.jl +++ b/test/programatic.jl @@ -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 diff --git a/test/runtests.jl b/test/runtests.jl index 4a214b3..8f6211c 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -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") From 4bcfbdddb34d8f86b628dd00ab84001e2f194579 Mon Sep 17 00:00:00 2001 From: Gabriel Baraldi Date: Thu, 4 Dec 2025 22:01:09 -0300 Subject: [PATCH 3/3] Also set a depot --- src/compiling.jl | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/compiling.jl b/src/compiling.jl index f4f9104..f264ff9 100644 --- a/src/compiling.jl +++ b/src/compiling.jl @@ -80,18 +80,26 @@ function compile_products(recipe::ImageRecipe) project_arg = recipe.project == "" ? Base.active_project() : recipe.project env_overrides = Dict{String,Any}() tmp_prefs_env = nothing + tmp_depot = nothing if is_trim_enabled(recipe) + # Create a temporary depot so packages are recompiled fresh with the trim preference + load_path_sep = Sys.iswindows() ? ";" : ":" + tmp_depot = mktempdir() + 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() - # Create a Project.toml file so it's recognized as an environment - touch(joinpath(tmp_prefs_env, "Project.toml")) + # 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 - load_path_sep = Sys.iswindows() ? ";" : ":" + env_overrides["JULIA_LOAD_PATH"] = join([tmp_prefs_env, "@", "@stdlib"], load_path_sep) end @@ -150,6 +158,14 @@ function compile_products(recipe::ImageRecipe) @warn "Failed to cleanup temporary preferences environment: $tmp_prefs_env" exception=e end end + # Cleanup temporary depot if created + if tmp_depot !== nothing + try + rm(tmp_depot; recursive=true, force=true) + catch e + @warn "Failed to cleanup temporary depot: $tmp_depot" exception=e + end + end end # Print compiled image size if recipe.verbose