diff --git a/CHANGELOG.md b/CHANGELOG.md index 44f76922d..e22222388 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Fixed + +* Top-level `.juliabundleignore` files are now correctly handled when using `JuliaHub.appbundle`. ([#99], [#100]) + ## Version [v0.1.14] - 2025-06-11 ### Added @@ -186,3 +192,5 @@ Initial package release. [#92]: https://github.com/JuliaComputing/JuliaHub.jl/issues/92 [#94]: https://github.com/JuliaComputing/JuliaHub.jl/issues/94 [#96]: https://github.com/JuliaComputing/JuliaHub.jl/issues/96 +[#99]: https://github.com/JuliaComputing/JuliaHub.jl/issues/99 +[#100]: https://github.com/JuliaComputing/JuliaHub.jl/issues/100 diff --git a/src/PackageBundler/utils.jl b/src/PackageBundler/utils.jl index a0df51c9e..81163d575 100644 --- a/src/PackageBundler/utils.jl +++ b/src/PackageBundler/utils.jl @@ -77,7 +77,7 @@ function get_bundleignore(file, top) dir = dirname(file) patterns = Set{Any}() try - while dir != top + while true if isfile(joinpath(dir, ".juliabundleignore")) union!( patterns, @@ -85,7 +85,7 @@ function get_bundleignore(file, top) ) return patterns, dir end - if dir == dirname(dir) + if dir == dirname(dir) || dir == top break end dir = dirname(dir) diff --git a/src/utils.jl b/src/utils.jl index a03630234..8841df66c 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -304,22 +304,55 @@ function Base.show(io::IO, filehash::FileHash) end # Estimates the size of the bundle directory -function _max_appbundle_dir_size(dir; maxsize=100 * 1024 * 1024) +function _max_appbundle_dir_size(dir::AbstractString; maxsize=100 * 1024 * 1024) sz = 0 + _walk_appbundle_files(dir) do filepath + if sz > maxsize + return _WalkFilesReturnEarly() + end + sz += filesize(filepath) + end + return sz, sz <= maxsize +end + +function _walk_appbundle_files(f::Base.Callable, dir::AbstractString) + # Note: even if if `path_filterer` says that directory `foo/bar` + # should not be included, it will still likely return `true` for + # any files in there, like `foo/bar/baz`. So we need to make sure + # we stop recursing into subdirectories. pred = _PackageBundler.path_filterer(dir) - for (root, _, files) in walkdir(dir) - for file in files - file = joinpath(root, file) - if !pred(file) - @debug "ignoring $file in dir size measurement" - continue - end + _walkfiles(dir; descend=pred) do filepath + if !pred(filepath) + @debug "ignoring file in _walk_appbundle_files: $(filepath)" + return nothing + end + return f(filepath) + end +end + +struct _WalkFilesReturnEarly end - sz > maxsize && return sz, false - sz += filesize(file) +# Calls `f` on any non-directory in `root`. +# `descend` gets called on any directory, and if it returns false, +function _walkfiles(f::Base.Callable, root::AbstractString; descend::Base.Callable) + if !isdir(root) + error("Not a directory: $(root)") + end + directories = String[root] + while !isempty(directories) + dir = popfirst!(directories) + for subpath in readdir(dir; join=true) + if isdir(subpath) + if descend(subpath)::Bool + push!(directories, subpath) + end + else + if f(subpath) === _WalkFilesReturnEarly() + break + end + end end end - return sz, sz < maxsize end function _json_get(d::Dict, key, ::Type{T}; var::AbstractString, parse=false) where {T} diff --git a/test/fixtures/ignorefiles/Pkg3/src/foo b/test/fixtures/ignorefiles/Pkg3/src/foo index e69de29bb..ab2be2d4c 100644 --- a/test/fixtures/ignorefiles/Pkg3/src/foo +++ b/test/fixtures/ignorefiles/Pkg3/src/foo @@ -0,0 +1 @@ +000000000000000000000000000000000000000000000000 diff --git a/test/fixtures/ignorefiles/Pkg3/test/bar/test b/test/fixtures/ignorefiles/Pkg3/test/bar/test index e69de29bb..ab2be2d4c 100644 --- a/test/fixtures/ignorefiles/Pkg3/test/bar/test +++ b/test/fixtures/ignorefiles/Pkg3/test/bar/test @@ -0,0 +1 @@ +000000000000000000000000000000000000000000000000 diff --git a/test/fixtures/ignorefiles/Pkg3/test/foo/test b/test/fixtures/ignorefiles/Pkg3/test/foo/test index e69de29bb..ab2be2d4c 100644 --- a/test/fixtures/ignorefiles/Pkg3/test/foo/test +++ b/test/fixtures/ignorefiles/Pkg3/test/foo/test @@ -0,0 +1 @@ +000000000000000000000000000000000000000000000000 diff --git a/test/packagebundler.jl b/test/packagebundler.jl index f2b435181..87a445fdc 100644 --- a/test/packagebundler.jl +++ b/test/packagebundler.jl @@ -225,3 +225,44 @@ end end @test isfile(out) end + +@testset "path_filterer" begin + @testset "subdirectory" begin + dir = joinpath(@__DIR__, "fixtures", "ignorefiles") + pred = JuliaHub._PackageBundler.path_filterer(dir) + @test pred(joinpath(dir, "Pkg3", "README.md")) + + @test pred(joinpath(dir, "Pkg3", "src", "bar")) + @test !pred(joinpath(dir, "Pkg3", "src", "foo")) + @test pred(joinpath(dir, "Pkg3", "src", "fooo")) + + @test !pred(joinpath(dir, "Pkg3", "test", "bar")) + @test !pred(joinpath(dir, "Pkg3", "test", "foo")) + @test pred(joinpath(dir, "Pkg3", "test", "fooo", "test")) + + # Note: even though the test/foo and test/bar directories are + # excluded, the predicate function does not return false if you + # check for file within the directories. + @test pred(joinpath(dir, "Pkg3", "test", "bar", "test")) + @test pred(joinpath(dir, "Pkg3", "test", "foo", "test")) + end + @testset "toplevel" begin + dir = joinpath(@__DIR__, "fixtures", "ignorefiles", "Pkg3") + pred = JuliaHub._PackageBundler.path_filterer(dir) + @test pred(joinpath(dir, "README.md")) + + @test pred(joinpath(dir, "src", "bar")) + @test !pred(joinpath(dir, "src", "foo")) + @test pred(joinpath(dir, "src", "fooo")) + + @test !pred(joinpath(dir, "test", "bar")) + @test !pred(joinpath(dir, "test", "foo")) + @test pred(joinpath(dir, "test", "fooo", "test")) + + # Note: even though the test/foo and test/bar directories are + # excluded, the predicate function does not return false if you + # check for file within the directories. + @test pred(joinpath(dir, "test", "bar", "test")) + @test pred(joinpath(dir, "test", "foo", "test")) + end +end diff --git a/test/utils.jl b/test/utils.jl index cbb6c9bc4..2bb1f3e21 100644 --- a/test/utils.jl +++ b/test/utils.jl @@ -101,3 +101,31 @@ end Dict("_id_missing" => "123e4567-e89b-12d3-a456-426614174000"), "id", UUIDs.UUID ) end + +@testset "_max_appbundle_dir_size" begin + # We check here that the `.juliabundleignore` is honored by making + # sure that the calculated total file size of the Pkg3/ directory is + dir = joinpath(@__DIR__, "fixtures", "ignorefiles", "Pkg3") + + appbundle_files = String[] + JuliaHub._walk_appbundle_files(dir) do filepath + push!(appbundle_files, relpath(filepath, dir)) + end + @test sort(appbundle_files) == [ + ".gitignore", ".juliabundleignore", "Project.toml", "README.md", + joinpath("src", "Pkg3.jl"), joinpath("src", "bar"), joinpath("src", "fooo"), + joinpath("test", "fooo", "test"), joinpath("test", "runtests.jl"), + ] + + # The files that are not meant to be included in the /Pkg3/ bundle here are + # all 50 byte files. Should they should show up in the total size here. + # + # Note: on windows, the files may be checked out with different line endings, + # so the total file size may be slightly different. + sz, sz_is_low = JuliaHub._max_appbundle_dir_size(dir) + @test sz_is_low + @test sz in (Sys.iswindows() ? (405, 432) : (405,)) + + _, sz_low = JuliaHub._max_appbundle_dir_size(dir; maxsize=200) + @test !sz_low +end