diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ec2c153d4..cf8adf90b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,10 +18,10 @@ jobs: arch: - x64 build_spec: - - v1.7.0 # release from versions.json - - nightly # special release - - master # directly from Git, likely built by CI - - master~500 # directly from Git, unlikely built by CI + - v1.7.0 # release from versions.json + - nightly # special release + - master # directly from Git, likely built by CI + - 917b11e928612b5352c4e157b38759eb11aa9152 # directly from Git, unlikely built by CI env: JULIA_DEBUG: PkgEval JULIA: ${{ matrix.build_spec }} diff --git a/src/evaluate.jl b/src/evaluate.jl index 23c13c3bd..290282145 100644 --- a/src/evaluate.jl +++ b/src/evaluate.jl @@ -99,20 +99,29 @@ const xvfb_proc = Ref{Union{Base.Process,Nothing}}(nothing) function sandboxed_julia_cmd(config::Configuration, executor, args=``; env::Dict{String,String}=Dict{String,String}(), mounts::Dict{String,String}=Dict{String,String}()) + # split mounts into read-only and read-write maps + read_only_maps = Dict{String,String}() + read_write_maps = Dict{String,String}() + for (dst, src) in mounts + if endswith(dst, ":ro") + read_only_maps[dst[begin:end-3]] = src + else + read_write_maps[dst] = src + end + end + rootfs = create_rootfs(config) install = install_julia(config) registries = joinpath(first(DEPOT_PATH), "registries") - read_only_maps = Dict( + read_only_maps = merge(read_only_maps, Dict( "/" => rootfs, config.julia_install_dir => install, "/usr/local/share/julia/registries" => registries - ) + )) - compiled = get_compilecache(config) packages = joinpath(storage_dir, "packages") artifacts = joinpath(storage_dir, "artifacts") - read_write_maps = merge(mounts, Dict( - joinpath(config.home, ".julia", "compiled") => compiled, + read_write_maps = merge(read_write_maps, Dict( joinpath(config.home, ".julia", "packages") => packages, joinpath(config.home, ".julia", "artifacts") => artifacts )) @@ -209,7 +218,8 @@ failure reason if any (both represented by a symbol), and the full log. Refer to `sandboxed_julia`[@ref] for more possible `keyword arguments. """ function sandboxed_script(config::Configuration, script::String, args=``; - env::Dict{String,String}=Dict{String,String}(), kwargs...) + env::Dict{String,String}=Dict{String,String}(), + mounts::Dict{String,String}=Dict{String,String}(), kwargs...) @assert config.log_limit > 0 cmd = `--eval 'eval(Meta.parse(read(stdin,String)))' $args` @@ -226,9 +236,18 @@ function sandboxed_script(config::Configuration, script::String, args=``; env["JULIA_PKG_SERVER"] = ENV["JULIA_PKG_SERVER"] end + # set-up a compile cache. because we may be running many instances, mount the + # shared cache read-only, and synchronize entries after we finish the script. + shared_compilecache = get_compilecache(config) + local_compilecache = mktempdir() + mounts = merge(mounts, Dict( + "/usr/local/share/julia/compiled:ro" => shared_compilecache, + joinpath(config.home, ".julia", "compiled") => local_compilecache + )) + input = Pipe() output = Pipe() - proc = sandboxed_julia(config, cmd; env, wait=false, + proc = sandboxed_julia(config, cmd; env, mounts, wait=false, stdout=output, stderr=output, stdin=input, kwargs...) close(output.in) @@ -322,6 +341,24 @@ function sandboxed_script(config::Configuration, script::String, args=``; log = log[1:ind] end + # copy new files from the local compilecache into the shared one + function copy_files(subpath=""; src, dst) + for entry in readdir(joinpath(src, subpath)) + path = joinpath(subpath, entry) + srcpath = joinpath(src, path) + dstpath = joinpath(dst, path) + + if isdir(srcpath) + isdir(dstpath) || mkdir(joinpath(dst, path)) + copy_files(path; src, dst) + elseif !ispath(dstpath) + cp(srcpath, dstpath) + end + end + end + copy_files(src=local_compilecache, dst=shared_compilecache) + rm(local_compilecache; recursive=true) + return status, reason, log end @@ -338,7 +375,7 @@ function sandboxed_test(config::Configuration, pkg::Package; kwargs...) end script = raw""" - try + begin using Dates print('#'^80, "\n# PkgEval set-up: $(now())\n#\n\n") @@ -346,52 +383,54 @@ function sandboxed_test(config::Configuration, pkg::Package; kwargs...) versioninfo() println() - - print("\n\n", '#'^80, "\n# Installation: $(now())\n#\n\n") - using Pkg package_spec = eval(Meta.parse(ARGS[1])) - Pkg.add(; package_spec...) + try + print("\n\n", '#'^80, "\n# Installation: $(now())\n#\n\n") - print("\n\n", '#'^80, "\n# Testing: $(now())\n#\n\n") + Pkg.add(; package_spec...) - if get(ENV, "PKGEVAL_RR", "false") == "true" - Pkg.test(package_spec.name; julia_args=`--bug-report=rr-local`) - else - Pkg.test(package_spec.name) - end - println("\nPkgEval succeeded") + print("\n\n", '#'^80, "\n# Testing: $(now())\n#\n\n") - catch err - print("\nPkgEval failed: ") - showerror(stdout, err) - Base.show_backtrace(stdout, catch_backtrace()) - println() + if get(ENV, "PKGEVAL_RR", "false") == "true" + Pkg.test(package_spec.name; julia_args=`--bug-report=rr-local`) + else + Pkg.test(package_spec.name) + end - if get(ENV, "PKGEVAL_RR", "false") == "true" - print("\n\n", '#'^80, "\n# BugReporting post-processing: $(now())\n#\n\n") + println("\nPkgEval succeeded") - # pack-up our rr trace. this is expensive, so we only do it for failures. - # it also needs to happen in a clean environment, or BugReporting's deps - # could affect/be affected by the tested package's dependencies. - Pkg.activate(; temp=true) - Pkg.add("BugReporting") - try - using BugReporting - trace_dir = BugReporting.default_rr_trace_dir() - trace = BugReporting.find_latest_trace(trace_dir) - BugReporting.compress_trace(trace, "/traces/$(ARGS[1]).tar.zst") - println("\nBugReporting succeeded") - catch err - print("\nBugReporting failed: ") - showerror(stdout, err) - Base.show_backtrace(stdout, catch_backtrace()) - println() + catch err + print("\nPkgEval failed: ") + showerror(stdout, err) + Base.show_backtrace(stdout, catch_backtrace()) + println() + + if get(ENV, "PKGEVAL_RR", "false") == "true" + print("\n\n", '#'^80, "\n# BugReporting post-processing: $(now())\n#\n\n") + + # pack-up our rr trace. this is expensive, so we only do it for failures. + # it also needs to happen in a clean environment, or BugReporting's deps + # could affect/be affected by the tested package's dependencies. + Pkg.activate(; temp=true) + Pkg.add("BugReporting") + try + using BugReporting + trace_dir = BugReporting.default_rr_trace_dir() + trace = BugReporting.find_latest_trace(trace_dir) + BugReporting.compress_trace(trace, "/traces/$(package_spec.name).tar.zst") + println("\nBugReporting succeeded") + catch err + print("\nBugReporting failed: ") + showerror(stdout, err) + Base.show_backtrace(stdout, catch_backtrace()) + println() + end end end - finally + print("\n\n", '#'^80, "\n# PkgEval teardown: $(now())\n#\n\n") end""" diff --git a/src/julia.jl b/src/julia.jl index 46e2ea880..c56c84d54 100644 --- a/src/julia.jl +++ b/src/julia.jl @@ -81,16 +81,17 @@ function get_julia_repo(spec) # remotes? most objects are going to be shared, so this would save time and space. @debug "Cloning/updating $repo_spec..." bare_checkout = joinpath(download_dir, repo_spec) - if ispath(joinpath(bare_checkout, "config")) - run(`$(git()) -C $bare_checkout fetch --quiet`) - else + if !ispath(joinpath(bare_checkout, "config")) run(`$(git()) clone --quiet --bare https://github.com/$(repo_spec).git $bare_checkout`) end + # explicitly fetch the requested commit from the remote and put it on the master branch. + # we need to do this as not all specs (e.g. `pull/42/merge`) might be available locally + run(`$(git()) -C $bare_checkout fetch --quiet --force origin $commit_spec:master`) + # check-out the actual source code into a temporary directory julia_checkout = mktempdir() - run(`$(git()) clone --quiet $bare_checkout $julia_checkout`) - run(`$(git()) -C $julia_checkout checkout --quiet $commit_spec`) + run(`$(git()) clone --quiet --branch master $bare_checkout $julia_checkout`) return julia_checkout end diff --git a/test/runtests.jl b/test/runtests.jl index cd176a6e0..f54395dfe 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -2,7 +2,11 @@ using PkgEval using Test julia = get(ENV, "JULIA", string(VERSION)) -julia_version = tryparse(VersionNumber, julia) +julia_release = if contains(julia, r"^v\d") + parse(VersionNumber, julia) +else + nothing +end @testset "PkgEval using Julia $julia" begin @testset "julia installation" begin @@ -16,12 +20,12 @@ julia_version = tryparse(VersionNumber, julia) end # try to compare the version info - if julia_version !== nothing + if julia_release !== nothing p = Pipe() close(p.in) PkgEval.sandboxed_julia(config, `-e 'println(VERSION)'`; stdout=p.out) version_str = read(p.out, String) - @test parse(VersionNumber, version_str) == julia_version + @test parse(VersionNumber, version_str) == julia_release end end @@ -85,7 +89,7 @@ end packages = [Package(; name) for name in package_names] results = evaluate([Configuration(; julia)], packages) - if !(julia == "master" || julia == "nightly") + if julia_release !== nothing @test all(results.status .== :ok) for result in eachrow(results) @test occursin("Testing $(result.package) tests passed", result.log) @@ -105,7 +109,7 @@ end elseif result.configuration == "compiled" @test contains(result.log, "PackageCompiler succeeded") end - if !(julia == "master" || julia == "nightly") + if julia_release !== nothing @test result.status == :ok @test contains(result.log, "Testing Example tests passed") end @@ -117,7 +121,7 @@ haskey(ENV, "CI") || @testset "rr" begin [Package(; name="Example")]) @test all(results.status .== :ok) @test contains(results[1, :log], "BugReporting") - if !(julia == "master" || julia == "nightly") + if julia_release !== nothing @test results[1, :status] == :ok @test contains(results[1, :log], "Testing Example tests passed") end