diff --git a/NEWS.md b/NEWS.md index a62dd5b0f0575..59446146aee9b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -545,6 +545,8 @@ Library improvements * `Char` is now a subtype of `AbstractChar`, and most of the functions that take character arguments now accept any `AbstractChar` ([#26286]). + * `pathof(module)` returns the path a module was imported from ([#28310]). + * `bytes2hex` now accepts an optional `io` argument to output to a hexadecimal stream without allocating a `String` first ([#27121]). diff --git a/base/exports.jl b/base/exports.jl index 958fd6a299937..844a162f7c26f 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -742,6 +742,7 @@ export methods, nameof, parentmodule, + pathof, names, which, @isdefined, diff --git a/base/loading.jl b/base/loading.jl index b4bf214965ff2..d8809a6cf57ad 100644 --- a/base/loading.jl +++ b/base/loading.jl @@ -249,6 +249,18 @@ function locate_package(pkg::PkgId)::Union{Nothing,String} end locate_package(::Nothing) = nothing +""" + pathof(m::Module) + +Return the path of `m.jl` file that was used to `import` module `m`, +or `nothing` if `m` was not imported from a package. +""" +function pathof(m::Module) + pkgid = get(Base.module_keys, m, nothing) + pkgid === nothing && return nothing + return Base.locate_package(pkgid) +end + ## generic project & manifest API ## const project_names = ("JuliaProject.toml", "Project.toml") diff --git a/doc/src/base/base.md b/doc/src/base/base.md index 36e47cc53aae2..d1f68ba82ffbd 100644 --- a/doc/src/base/base.md +++ b/doc/src/base/base.md @@ -332,6 +332,7 @@ Base.AsyncCondition(::Function) ```@docs Base.nameof(::Module) Base.parentmodule +Base.pathof(::Module) Base.moduleroot Base.@__MODULE__ Base.fullname diff --git a/stdlib/Pkg/src/API.jl b/stdlib/Pkg/src/API.jl index b7c006d0d6493..e1dbb7068dd26 100644 --- a/stdlib/Pkg/src/API.jl +++ b/stdlib/Pkg/src/API.jl @@ -429,13 +429,13 @@ function clone(url::String, name::String = "") develop(ctx, [parse_package(url)]) end -function dir(pkg::String, paths::String...) - @warn "Pkg.dir is only kept for legacy CI script reasons" maxlog=1 +function dir(pkg::String, paths::AbstractString...) + @warn "`Pkg.dir(pkgname, paths...)` is deprecated; instead, do `import $pkg; joinpath(dirname(pathof($pkg)), \"..\", paths...)`." maxlog=1 pkgid = Base.identify_package(pkg) - pkgid == nothing && return nothing + pkgid === nothing && return nothing path = Base.locate_package(pkgid) - pkgid == nothing && return nothing - return joinpath(abspath(path, "..", "..", paths...)) + path === nothing && return nothing + return abspath(path, "..", "..", paths...) end precompile() = precompile(Context()) diff --git a/test/loading.jl b/test/loading.jl index e4df5df010073..489bf7998bee9 100644 --- a/test/loading.jl +++ b/test/loading.jl @@ -218,6 +218,8 @@ Base.ACTIVE_PROJECT[] = nothing end end +module NotPkgModule; end + @testset "project & manifest import" begin @test !@isdefined Foo @test !@isdefined Bar @@ -254,6 +256,12 @@ end end end @test Foo.which == "path" + + @testset "pathof" begin + @test pathof(Foo) == normpath(abspath(@__DIR__, "project/deps/Foo1/src/Foo.jl")) + @test pathof(NotPkgModule) === nothing + end + end ## systematic generation of test environments ##