diff --git a/base/loading.jl b/base/loading.jl index 4dd829ea8042a..bd6ca229a2465 100644 --- a/base/loading.jl +++ b/base/loading.jl @@ -2246,9 +2246,41 @@ function load_path_setup_code(load_path::Bool=true) return code end +""" + check_src_module_wrap(srcpath::String) + +Checks that a package entry file `srcpath` has a module declaration, and that it is before any using/import statements. +""" +function check_src_module_wrap(pkg::PkgId, srcpath::String) + module_rgx = r"^\s*(?:@\w*\s*)*(?:bare)?module\s" + load_rgx = r"\b(?:using|import)\s" + load_seen = false + inside_comment = false + for s in eachline(srcpath) + if contains(s, "\"\"\"") + # ignore module docstrings + inside_comment = !inside_comment + end + inside_comment && continue + if startswith(s, module_rgx) + if load_seen + throw(ErrorException("Package $pkg source file $srcpath has a using/import before a module declaration.")) + end + return true + end + if startswith(s, load_rgx) + load_seen = true + end + end + throw(ErrorException("Package $pkg source file $srcpath does not contain a module declaration.")) +end + # this is called in the external process that generates precompiled package files function include_package_for_output(pkg::PkgId, input::String, depot_path::Vector{String}, dl_load_path::Vector{String}, load_path::Vector{String}, concrete_deps::typeof(_concrete_dependencies), source::Union{Nothing,String}) + + check_src_module_wrap(pkg, input) + append!(empty!(Base.DEPOT_PATH), depot_path) append!(empty!(Base.DL_LOAD_PATH), dl_load_path) append!(empty!(Base.LOAD_PATH), load_path) diff --git a/test/loading.jl b/test/loading.jl index 72a07835dd339..d55a212490401 100644 --- a/test/loading.jl +++ b/test/loading.jl @@ -1184,3 +1184,75 @@ end @test success(`$(Base.julia_cmd()) --startup-file=no -e 'using DelimitedFiles'`) @test success(`$(Base.julia_cmd()) --startup-file=no -e 'using Statistics'`) end + +@testset "checking srcpath modules" begin + p = Base.PkgId("Dummy") + fpath, _ = mktemp() + @testset "valid" begin + write(fpath, """ + module Foo + using Bar + end + """) + @test Base.check_src_module_wrap(p, fpath) + + write(fpath, """ + baremodule Foo + using Bar + end + """) + @test Base.check_src_module_wrap(p, fpath) + + write(fpath, """ + \"\"\" + Foo + using Foo + \"\"\" + module Foo + using Bar + end + """) + @test Base.check_src_module_wrap(p, fpath) + + write(fpath, """ + # using foo + module Foo + using Bar + end + """) + @test Base.check_src_module_wrap(p, fpath) + + write(fpath, """ + # using foo + module Foo + using Bar + end + """) + @test Base.check_src_module_wrap(p, fpath) + end + @testset "invalid" begin + write(fpath, """ + # module Foo + using Bar + # end + """) + @test_throws ErrorException Base.check_src_module_wrap(p, fpath) + + write(fpath, """ + using Bar + module Foo + end + """) + @test_throws ErrorException Base.check_src_module_wrap(p, fpath) + + write(fpath, """ + using Bar + """) + @test_throws ErrorException Base.check_src_module_wrap(p, fpath) + + write(fpath, """ + x = 1 + """) + @test_throws ErrorException Base.check_src_module_wrap(p, fpath) + end +end diff --git a/test/precompile.jl b/test/precompile.jl index ccc37a32e41c5..671535c360625 100644 --- a/test/precompile.jl +++ b/test/precompile.jl @@ -622,7 +622,11 @@ precompile_test_harness(false) do dir FooBar3_inc = joinpath(dir, "FooBar3_inc.jl") write(FooBar3_inc, "x=1\n") for code in ["Core.eval(Base, :(x=1))", "Base.include(Base, \"FooBar3_inc.jl\")"] - write(FooBar3_file, code) + write(FooBar3_file, """ + module FooBar3 + $code + end + """) @test_warn "Evaluation into the closed module `Base` breaks incremental compilation" try Base.require(Main, :FooBar3) catch exc