How to find the package file that stores a certain symbol?
My previous answer had heavy shortcomings and errors, so I took a deeper breath and figured out a more robust way.
The problem with FindFile["context`"]
is twofold. First, it can only return the first file in a possibly long list of files adding to the same context. Second, it might not work on a context extracted from a symbol because symbol contexts might not be identical to the package name. The package specification in Mathematica is confusing: myPackage`
means two different things:
- FILENAME: If
Get["myPackage`"]
(orNeeds
) is called, Mathematica will interpretmyPackage
as a file path usingFindFile
to figure out the source. - CONTEXT: In
BeginPackage["myPackage`"]
(orBegin
) and inmyPackage`symbol
,mypackage
is interpreted as a namespace within the memory, and has nothing to do with files.$Packages
and$ContextPath
only store these contexts but not package-file-path-specifiers of point 1.
There is no guarantee, that the package name agrees with the context name. Though this is the convention, a package defined at path .../myPackage
might not contain the line BeginPackage["myPackage`"]
in it. One can easily create artificial cases where a package file at .../package.m
(called as Get["package`"]
) loads symbols like context`sym
.
To be clear, any package you design should have at least one file in it which has the same name as the context it generates. If context and file name do not match, either the file is not found (if context is called by Needs
, resulting in Get::noopen
error) or context is not created (if filename is called by Needs
, resulting in Needs::nocont
error). Note however, that in the latter case, the file is nevertheless loaded, regardless of the expected context. The reason I say "at least one file" above is because this file then can call any other package file in the same directory (obviously with different file names) or from other directories.
So a foolproof solution to find all package files could only work if it knows the associations between filenames and contexts. To have that, package calls have to be monitored and the collected data stored. In another answer I presented safeGet
that can read a package, collect all the files that are touched by successive package calls from within and at the end removes all contexts and symbols introduced during all the calls. With safeGet
it becomes possible to capture the necessary data to identify sources. safeGetSource
is a light version of safeGet
that only captures context -> file associations but not all files (like init.m
-s that don't contain context-creating code).
contextJoin[s:{__String}] := StringReplace[StringJoin[#<>"`"& /@s], "`".. -> "`"]
packageButton[file_String] := Button[FileNameTake@file, NotebookOpen@file,
Appearance -> "Palette"];
safeGetSource[pkg_String, arg___] := Module[{
bp, ep, begin, end, contexts = {}, assoc = {}, all,
cStack = {$Context}, cpStack = {$ContextPath}},
Block[{$Packages = $Packages, $ContextPath = $ContextPath, $Context = $Context},
Off[General::shdw];
bp[ctx_] := bp[ctx, {}];
bp[ctx_, needed_List] := (
AppendTo[cStack, $Context];
AppendTo[cpStack, $ContextPath];
$ContextPath = DeleteDuplicates@Join[{ctx}, needed, {"System`"}];
$Packages = DeleteDuplicates@Prepend[$Packages, ctx];
$Context = ctx;
assoc = Union[assoc, {$Context -> packageButton@$InputFileName}];
contexts = Union[contexts, {$Context}];
Needs /@ needed;
ctx);
begin[ctx_] := (
AppendTo[cStack, $Context];
$Context = contextJoin@{$Context, ctx};
assoc = Union[assoc, {$Context -> packageButton@$InputFileName}];
contexts = Union[contexts, {$Context}];
ctx);
ep[] := ({$ContextPath, cpStack} = {Last@cpStack, Most@cpStack};
{$Context, cStack} = {Last@cStack, Most@cStack};);
end[] := ({$Context, cStack} = {Last@cStack, Most@cStack};);
Block[{BeginPackage=bp, EndPackage=ep, Begin=begin, End=end}, Get[pkg, arg]];
all = # <> "*" & /@ contexts;
Unprotect /@ all;
Quiet[Remove /@ all];
On[General::shdw];
assoc
]];
assoc = safeGetSource@"OpenCLLink`"
{ "CCompilerDriver`" -> "CCompilerDriver.m", "CCompilerDriver`CCompilerDriverBase`" -> "CCompilerDriverBase.m", "CCompilerDriver`CCompilerDriverBase`Private`" -> "CCompilerDriverBase.m", "CCompilerDriver`CCompilerDriverRegistry`" -> "CCompilerDriverRegistry.m", "CCompilerDriver`CCompilerDriverRegistry`Private`" -> "CCompilerDriverRegistry.m", "CCompilerDriver`GenericCCompiler`" -> "GenericCCompiler.m", "CCompilerDriver`GenericCCompiler`Private`" -> "GenericCCompiler.m", "CCompilerDriver`IntelCompiler`" -> "IntelCompiler.m", "CCompilerDriver`IntelCompilerWindows`" -> "IntelCompilerWindows.m", "CCompilerDriver`IntelCompilerWindows`Private`" -> "IntelCompilerWindows.m", "CCompilerDriver`Private`" -> "CCompilerDriver.m", "CCompilerDriver`System`" -> "System.m", "CCompilerDriver`System`Private`" -> "System.m", "CCompilerDriver`VisualStudioCompiler`" -> "VisualStudioCompiler.m", "CCompilerDriver`VisualStudioCompiler`Private`" -> "VisualStudioCompiler.m", "CUDALink`" -> "CUDALink.m", "CUDALink`NVCCCompiler`" -> "NVCCCompiler.m", "CUDALink`NVCCCompiler`Private`" -> "NVCCCompiler.m", "CUDALink`Private`" -> "CUDALink.m", "GPUTools`" -> "GPUTools.m", "GPUTools`CodeGenerator`" -> "CodeGenerator.m", "GPUTools`CodeGenerator`Private`" -> "CodeGenerator.m", "GPUTools`Detection`" -> "Detection.m", "GPUTools`Detection`Private`" -> "Detection.m", "GPUTools`Private`" -> "GPUTools.m", "GPUTools`SymbolicGPU`" -> "SymbolicGPU.m", "GPUTools`SymbolicGPU`Private`" -> "SymbolicGPU.m", "GPUTools`Utilities`" -> "Utilities.m", "GPUTools`Utilities`Private`" -> "Utilities.m", "LibraryLink`" -> "LibraryLink.m", "LibraryLink`Private`" -> "LibraryLink.m", "OpenCLLink`" -> "OpenCLLink.m", "OpenCLLink`Private`" -> "OpenCLLink.m", "SymbolicC`" -> "SymbolicC.m", "SymbolicC`Private`" -> "SymbolicC.m" }
Querying a symbol can now be done as:
Context@symbol /. assoc
Examining "PacletManager
"`` reveals that the one public context calls quite a few files:
safeGetSource@"PacletManager`"
{ "PacletManager`" -> "PacletManager.m", "PacletManager`Collection`Private`" -> "Collection.m", "PacletManager`Documentation`Private`" -> "Documentation.m", "PacletManager`Extension`Private`" -> "Extension.m", "PacletManager`LayoutDocsCollection`Private`" -> "LayoutDocsCollection.m", "PacletManager`Manager`Private`" -> "Manager.m", "PacletManager`MemoryCollection`Private`" -> "MemoryCollection.m", "PacletManager`Package`" -> "Collection.m", "PacletManager`Package`" -> "Documentation.m", "PacletManager`Package`" -> "Extension.m", "PacletManager`Package`" -> "LayoutDocsCollection.m", "PacletManager`Package`" -> "Manager.m", "PacletManager`Package`" -> "MemoryCollection.m", "PacletManager`Package`" -> "Packer.m", "PacletManager`Package`" -> "Paclet.m", "PacletManager`Package`" -> "PacletManager.m", "PacletManager`Package`" -> "Services.m", "PacletManager`Package`" -> "Utils.m", "PacletManager`Package`" -> "Zip.m", "PacletManager`Packer`Private`" -> "Packer.m", "PacletManager`Paclet`Private`" -> "Paclet.m", "PacletManager`Private`" -> "PacletManager.m", "PacletManager`Services`Private`" -> "Services.m", "PacletManager`Utils`Private`" -> "Utils.m", "PacletManager`Zip`Private`" -> "Zip.m" }
Let's see a less complicated example, with a non-conventional package-context association (made for this purpose). Note, that while the package was called as "MyPackage`"
, the context added to memory and captured by safeGetSource
is "MyContext`"
:
safeGetSource@"MyPackage`"
{ "Functions`" -> "Functions.m", "Functions`Private`" -> "Functions.m", "MyContext`" -> "file.m", "Test`" -> "Common.m", "Test`" -> "TestA.m", "Test`" -> "TestB.m", "Test`Private`" -> "TestA.m", "Test`Private`" -> "TestB.m" }
The dependency tree of the above call, created by safeGet
:
Just a really quick hack that is nevertheless useful sometimes:
LocateFunction[f_]:=(SystemOpen[Context[f]];NotebookFind[SelectedNotebook[],f//ToString])
If applicable, this opens the package relating to the symbol´s context and primes the search (use F3 to search for subsequent locations of the symbol).