diff --git a/.gitignore b/.gitignore index 8cb66d7e..da790038 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ benchmark/tune.json # Pkg deps/build.log /Manifest.toml + +# VSCode settings +.vscode/settings.json diff --git a/Project.toml b/Project.toml index cf091b34..be739db7 100644 --- a/Project.toml +++ b/Project.toml @@ -9,6 +9,7 @@ Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6" InlineStrings = "842dd82b-1e85-43dc-bf29-5d0ee9dffc48" Mocking = "78c3b35d-d492-501b-9361-3d52fe80e533" +PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a" Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" Scratch = "6c6a2e73-6563-6170-7368-637461726353" @@ -23,6 +24,7 @@ Dates = "1" Downloads = "1" InlineStrings = "1" Mocking = "0.7, 0.8" +PrecompileTools = "1" Printf = "1" RecipesBase = "0.7, 0.8, 1" Scratch = "1.1.1" diff --git a/src/TimeZones.jl b/src/TimeZones.jl index ce7032b8..19d0b821 100644 --- a/src/TimeZones.jl +++ b/src/TimeZones.jl @@ -2,6 +2,7 @@ module TimeZones using Artifacts: Artifacts using Dates +using PrecompileTools: @compile_workload, @setup_workload using Printf using Scratch: @get_scratch! using Unicode @@ -40,20 +41,22 @@ const _COMPILED_DIR = Ref{String}() # abstract type UTC <: TimeZone end # Already defined in the Dates stdlib abstract type Local <: TimeZone end +# Resolve the TZJData artifact directory using direct function calls that the +# juliac trimmer can trace. We intentionally avoid TZJData.artifact_dir() which +# uses the @artifact_str macro — that macro expands to Base.invokelatest(...), +# an opaque dynamic dispatch barrier the trimmer cannot follow through, causing +# the callee to be removed from trimmed binaries. +function _resolve_tzjdata_dir() + pkg = Base.identify_package(TZJData, "TZJData") + pkg_dir = dirname(dirname(Base.locate_package(pkg))) + artifact_dict = Artifacts.parse_toml(joinpath(pkg_dir, "Artifacts.toml")) + hash = Base.SHA1(artifact_dict["tzjdata"]["git-tree-sha1"]) + return Artifacts.artifact_path(hash) +end + function __init__() # Set at runtime to ensure relocatability - _COMPILED_DIR[] = @static if isdefined(TZJData, :artifact_dir) - TZJData.artifact_dir() - else - # Backwards compatibility for TZJData versions below v1.3.1. The portion of the - # code which determines the `pkg_dir` could be replaced by `pkgdir(TZJData)` however - # the `pkgdir` function doesn't work well with relocated system images. - pkg = Base.identify_package(TZJData, "TZJData") - pkg_dir = dirname(dirname(Base.locate_package(pkg))) - artifact_dict = Artifacts.parse_toml(joinpath(pkg_dir, "Artifacts.toml")) - hash = Base.SHA1(artifact_dict["tzjdata"]["git-tree-sha1"]) - Artifacts.artifact_path(hash) - end + _COMPILED_DIR[] = _resolve_tzjdata_dir() # Dates extension needs to happen everytime the module is loaded (issue #24) init_dates_extension() @@ -97,4 +100,31 @@ if !isdefined(Base, :get_extension) include("../ext/TimeZonesRecipesBaseExt.jl") end +# Ensure methods used in __init__ are preserved by juliac --trim (requires Julia >= 1.12). +# The trimmer only keeps methods reachable from @ccallable entry points and precompiled +# methods. This workload exercises the __init__ code paths so the trimmer retains them. +# On Julia < 1.9, @compile_workload still executes the code but doesn't cache native code +@setup_workload begin + @compile_workload begin + # Preserve artifact resolution methods needed by __init__. + _resolve_tzjdata_dir() + + @static if VERSION >= v"1.9" + # Also sets _COMPILED_DIR so the TimeZone("UTC") call below can load tzdata. + _COMPILED_DIR[] = _resolve_tzjdata_dir() + + # Exercise core timezone functionality so timezone loading methods + # are also preserved. + TimeZone("UTC") + + # Reset the cache so it doesn't persist into the loaded module with + # stale data from the precompilation environment. Without this, the + # cache's `initialized` flag stays true and `_build()` is never called + # at runtime — breaking `JULIA_TZ_VERSION` overrides and any scenario + # where the compiled tzdata differs from what was available here. + copy!(_TZ_CACHE, TimeZoneCache()) + end + end +end + end # module