Developer reference
Internal global variables
Configuration-related variables
These are set during execution of Revise's __init__
function.
Revise.watching_files
— ConstantRevise.watching_files[]
Returns true
if we watch files rather than their containing directory. FreeBSD and NFS-mounted systems should watch files, otherwise we prefer to watch directories.
Revise.polling_files
— ConstantRevise.polling_files[]
Returns true
if we should poll the filesystem for changes to the files that define loaded code. It is preferable to avoid polling, instead relying on operating system notifications via FileWatching.watch_file
. However, NFS-mounted filesystems (and perhaps others) do not support file-watching, so for code stored on such filesystems you should turn polling on.
See the documentation for the JULIA_REVISE_POLL
environment variable.
Revise.tracking_Main_includes
— ConstantRevise.tracking_Main_includes[]
Returns true
if files directly included from the REPL should be tracked. The default is false
. See the documentation regarding the JULIA_REVISE_INCLUDE
environment variable to customize it.
Path-related variables
Revise.juliadir
— ConstantRevise.juliadir
Constant specifying full path to julia top-level source directory. This should be reliable even for local builds, cross-builds, and binary installs.
Revise.basesrccache
— ConstantRevise.basesrccache
Full path to the running Julia's cache of source code defining Base
.
Revise.basebuilddir
— ConstantRevise.basebuilddir
Julia's top-level directory when Julia was built, as recorded by the entries in Base._included_files
.
Internal state management
Revise.pkgdatas
— ConstantRevise.pkgdatas
pkgdatas
is the core information that tracks the relationship between source code and julia objects, and allows re-evaluation of code in the proper module scope. It is a dictionary indexed by PkgId: pkgdatas[id]
returns a value of type Revise.PkgData
.
Revise.watched_files
— ConstantRevise.watched_files
Global variable, watched_files[dirname]
returns the collection of files in dirname
that we're monitoring for changes. The returned value has type Revise.WatchList
.
This variable allows us to watch directories rather than files, reducing the burden on the OS.
Revise.revision_queue
— ConstantRevise.revision_queue
Global variable, revision_queue
holds (pkgdata,filename)
pairs that we need to revise, meaning that these files have changed since we last processed a revision. This list gets populated by callbacks that watch directories for updates.
Revise.NOPACKAGE
— ConstantRevise.NOPACKAGE
Global variable; default PkgId
used for files which do not belong to any package, but still have to be watched because user callbacks have been registered for them.
Revise.queue_errors
— ConstantRevise.queue_errors
Global variable, maps (pkgdata, filename)
pairs that errored upon last revision to (exception, backtrace)
.
Revise.included_files
— ConstantRevise.included_files
Global variable, included_files
gets populated by callbacks we register with include
. It's used to track non-precompiled packages and, optionally, user scripts (see docs on JULIA_REVISE_INCLUDE
).
Revise.watched_manifests
— ConstantRevise.watched_manifests
Global variable, a set of Manifest.toml
files from the active projects used during this session.
The following are specific to user callbacks (see Revise.add_callback
) and the implementation of entr
:
Revise.revision_event
— ConstantRevise.revision_event
This Condition
is used to notify entr
that one of the watched files has changed.
Revise.user_callbacks_queue
— ConstantRevise.user_callbacks_queue
Global variable, user_callbacks_queue
holds key
values for which the file has changed but the user hooks have not yet been called.
Revise.user_callbacks_by_file
— ConstantRevise.user_callbacks_by_file
Global variable, maps files (identified by their absolute path) to the set of callback keys registered for them.
Revise.user_callbacks_by_key
— ConstantRevise.user_callbacks_by_key
Global variable, maps callback keys to user hooks.
Types
Revise.RelocatableExpr
— TypeA RelocatableExpr
wraps an Expr
to ensure that comparisons between RelocatableExpr
s ignore line numbering information. This allows one to detect that two expressions are the same no matter where they appear in a file.
Revise.ModuleExprsSigs
— TypeModuleExprsSigs
For a particular source file, the corresponding ModuleExprsSigs
is a mapping mod=>exprs=>sigs
of the expressions exprs
found in mod
and the signatures sigs
that arise from them. Specifically, if mes
is a ModuleExprsSigs
, then mes[mod][ex]
is a list of signatures that result from evaluating ex
in mod
. It is possible that this returns nothing
, which can mean either that ex
does not define any methods or that the signatures have not yet been cached.
The first mod
key is guaranteed to be the module into which this file was include
d.
To create a ModuleExprsSigs
from a source file, see Revise.parse_source
.
Revise.FileInfo
— TypeFileInfo(mexs::ModuleExprsSigs, cachefile="")
Structure to hold the per-module expressions found when parsing a single file. mexs
holds the Revise.ModuleExprsSigs
for the file.
Optionally, a FileInfo
can also record the path to a cache file holding the original source code. This is applicable only for precompiled modules and Base
. (This cache file is distinct from the original source file that might be edited by the developer, and it will always hold the state of the code when the package was precompiled or Julia's Base
was built.) When a cache is available, mexs
will be empty until the file gets edited: the original source code gets parsed only when a revision needs to be made.
Source cache files greatly reduce the overhead of using Revise.
Revise.PkgData
— TypePkgData(id, path, fileinfos::Dict{String,FileInfo})
A structure holding the data required to handle a particular package. path
is the top-level directory defining the package, and fileinfos
holds the Revise.FileInfo
for each file defining the package.
For the PkgData
associated with Main
(e.g., for files loaded with includet
), the corresponding path
entry will be empty.
Revise.WatchList
— TypeRevise.WatchList
A struct for holding files that live inside a directory. Some platforms (OSX) have trouble watching too many files. So we watch parent directories, and keep track of which files in them should be tracked.
Fields:
timestamp
: mtime of last updatetrackedfiles
: Set of filenames, generally expressed as a relative path
Revise.TaskThunk
— Typethunk = TaskThunk(f, args)
To facilitate precompilation and reduce latency, we avoid creation of anonymous thunks. thunk
can be used as an argument in schedule(Task(thunk))
.
Revise.ReviseEvalException
— TypeReviseEvalException(loc::String, exc::Exception, stacktrace=nothing)
Provide additional location information about exc
.
When running via the interpreter, the backtraces point to interpreter code rather than the original culprit. This makes it possible to use loc
to provide information about the frame backtrace, and even to supply a fake backtrace.
If stacktrace
is supplied it must be a Vector{Any}
containing (::StackFrame, n)
pairs where n
is the recursion count (typically 1).
Revise.MethodSummary
— TypeMethodSummary(method)
Create a portable summary of a method. In particular, a MethodSummary can be saved to a JLD2 file.
Function reference
Functions called when you load a new package
Revise.watch_package
— Functionwatch_package(id::Base.PkgId)
Start watching a package for changes to the files that define it. This function gets called via a callback registered with Base.require
, at the completion of module-loading by using
or import
.
Revise.parse_pkg_files
— Functionparse_pkg_files(id::PkgId)
This function gets called by watch_package
and runs when a package is first loaded. Its job is to organize the files and expressions defining the module so that later we can detect and process revisions.
Revise.init_watching
— FunctionRevise.init_watching(files)
Revise.init_watching(pkgdata::PkgData, files)
For every filename in files
, monitor the filesystem for updates. When the file is updated, either Revise.revise_dir_queued
or Revise.revise_file_queued
will be called.
Use the pkgdata
version if the files are supplied using relative paths.
Monitoring for changes
These functions get called on each directory or file that you monitor for revisions. These block execution until the file(s) are updated, so you should only call them from within an @async
block. They work recursively: once an update has been detected and execution resumes, they schedule a revision (see Revise.revision_queue
) and then call themselves on the same directory or file to wait for the next set of changes.
Revise.revise_dir_queued
— Functionrevise_dir_queued(dirname)
Wait for one or more of the files registered in Revise.watched_files[dirname]
to be modified, and then queue the corresponding files on Revise.revision_queue
. This is generally called via a Revise.TaskThunk
.
Revise.revise_file_queued
— Functionrevise_file_queued(pkgdata::PkgData, filename)
Wait for modifications to filename
, and then queue the corresponding files on Revise.revision_queue
. This is generally called via a Revise.TaskThunk
.
This is used only on platforms (like BSD) which cannot use Revise.revise_dir_queued
.
The following functions support user callbacks, and are used in the implementation of entr
but can be used more broadly:
Revise.add_callback
— Functionkey = Revise.add_callback(f, files, modules=nothing; key=gensym())
Add a user-specified callback, to be executed during the first run of revise()
after a file in files
or a module in modules
is changed on the file system. If all
is set to true
, also execute the callback whenever any file already monitored by Revise changes. In an interactive session like the REPL, Juno or Jupyter, this means the callback executes immediately before executing a new command / cell.
You can use the return value key
to remove the callback later (Revise.remove_callback
) or to update it using another call to Revise.add_callback
with key=key
.
Revise.remove_callback
— FunctionRevise.remove_callback(key)
Remove a callback previously installed by a call to Revise.add_callback(...)
. See its docstring for details.
Evaluating changes (revising) and computing diffs
revise
is the primary entry point for implementing changes. Additionally,
Revise.revise_file_now
— FunctionRevise.revise_file_now(pkgdata::PkgData, file)
Process revisions to file
. This parses file
and computes an expression-level diff between the current state of the file and its most recently evaluated state. It then deletes any removed methods and re-evaluates any changed expressions. Note that generally it is better to use revise
as it properly handles methods that move from one file to another.
id
must be a key in Revise.pkgdatas
, and file
a key in Revise.pkgdatas[id].fileinfos
.
Caching the definition of methods
Revise.get_def
— Functionsuccess = get_def(method::Method)
As needed, load the source file necessary for extracting the code defining method
. The source-file defining method
must be tracked. If it is in Base, this will execute track(Base)
if necessary.
This is a callback function used by CodeTracking.jl
's definition
.
Parsing source code
Revise.parse_source
— Functionmexs = parse_source(filename::AbstractString, mod::Module)
Parse the source filename
, returning a ModuleExprsSigs
mexs
. mod
is the "parent" module for the file (i.e., the one that include
d the file); if filename
defines more module(s) then these will all have separate entries in mexs
.
If parsing filename
fails, nothing
is returned.
Revise.parse_source!
— Functionparse_source!(mexs::ModuleExprsSigs, filename, mod::Module)
Top-level parsing of filename
as included into module mod
. Successfully-parsed expressions will be added to mexs
. Returns mexs
if parsing finished successfully, otherwise nothing
is returned.
See also Revise.parse_source
.
success = parse_source!(mod_exprs_sigs::ModuleExprsSigs, src::AbstractString, filename::AbstractString, mod::Module)
Parse a string src
obtained by reading file
as a single string. pos
is the 1-based byte offset from which to begin parsing src
.
See also Revise.parse_source
.
Lowered source code
Much of the "brains" of Revise comes from doing analysis on lowered code. This part of the package is not as well documented.
Revise.minimal_evaluation!
— Functionisrequired, evalassign = minimal_evaluation!([predicate,] methodinfo, src::Core.CodeInfo, mode::Symbol)
Mark required statements in src
: isrequired[i]
is true
if src.code[i]
should be evaluated. Statements are analyzed by isreq, haseval = predicate(stmt)
, and predicate
defaults to Revise.is_method_or_eval
. haseval
is true if the statement came from @eval
or eval(...)
call. Since the contents of such expression are difficult to analyze, it is generally safest to execute all such evals.
Revise.methods_by_execution!
— Functionmethods_by_execution!(recurse=JuliaInterpreter.Compiled(), methodinfo, docexprs, mod::Module, ex::Expr;
mode=:eval, disablebp=true, skip_include=mode!==:eval, always_rethrow=false)
Evaluate or analyze ex
in the context of mod
. Depending on the setting of mode
(see the Extended help), it supports full evaluation or just the minimal evaluation needed to extract method signatures. recurse
controls JuliaInterpreter's evaluation of any non-intercepted statement; likely choices are JuliaInterpreter.Compiled()
or JuliaInterpreter.finish_and_return!
. methodinfo
is a cache for storing information about any method definitions (see CodeTrackingMethodInfo
). docexprs
is a cache for storing documentation expressions; obtain an empty one with Revise.DocExprs()
.
Extended help
The action depends on mode
:
:eval
evaluates the expression inmod
, similar toCore.eval(mod, ex)
except thatmethodinfo
anddocexprs
will be populated with information about any signatures or docstrings. This mode is used to implementincludet
.:sigs
analyzesex
and extracts signatures of methods and docstrings (specifically, statements flagged byRevise.minimal_evaluation!
), but does not evaluateex
in the traditional sense. It will selectively execute statements needed to form the signatures of defined methods. It will also expand any@eval
ed expressions, since these might contain method definitions.:evalmeth
analyzesex
and extracts signatures and docstrings like:sigs
, but takes the additional step of evaluating any:method
statements.:evalassign
acts similarly to:evalmeth
, and also evaluates assignment statements.
When selectively evaluating an expression, Revise will incorporate required dependencies, even for minimal-evaluation modes like :sigs
. For example, the method definition
max_values(T::Union{map(X -> Type{X}, Base.BitIntegerSmall_types)...}) = 1 << (8*sizeof(T))
found in base/abstractset.jl
requires that it create the anonymous function in order to compute the signature.
The other keyword arguments are more straightforward:
disablebp
controls whether JuliaInterpreter's breakpoints are disabled before stepping through the code. They are restored on exit.skip_include
prevents execution ofinclude
statements, instead inserting them intomethodinfo
's cache. This defaults totrue
unlessmode
is:eval
.always_rethrow
, if true, causes an error to be thrown if evaluatingex
triggered an error. If false, the error is logged with@error
.InterruptException
s are always rethrown. This is primarily useful for debugging.
Revise.CodeTrackingMethodInfo
— TypeCodeTrackingMethodInfo(ex::Expr)
Create a cache for storing information about method definitions. Adding signatures to such an object inserts them into CodeTracking.method_info
, which maps signature Tuple-types to (lnn::LineNumberNode, ex::Expr)
pairs. Because method signatures are unique within a module, this is the foundation for identifying methods in a manner independent of source-code location.
It also has the following fields:
exprstack
: used when descending into@eval
statements (viapush_expr
andpop_expr!
)ex
(used in creating theCodeTrackingMethodInfo
object) is the first entry in the stack.allsigs
: a list of all method signatures defined by a given expressiondeps
: list of top-level named objects (Symbol
s andGlobalRef
s) that method definitions in this block depend on. For example,if Sys.iswindows() f() = 1 else f() = 2 end
would storeSys.iswindows
here.includes
: a list ofmodule=>filename
for anyinclude
statements encountered while the expression was parsed.
Modules and paths
Revise.modulefiles
— Functionparentfile, included_files = modulefiles(mod::Module)
Return the parentfile
in which mod
was defined, as well as a list of any other files that were include
d to define mod
. If this operation is unsuccessful, (nothing, nothing)
is returned.
All files are returned as absolute paths.
Handling errors
Revise.trim_toplevel!
— Functiontrim_toplevel!(bt)
Truncate a list of instruction pointers, as obtained from backtrace()
or catch_backtrace()
, at the first "top-level" call (e.g., as executed from the REPL prompt) or the first entry corresponding to a method in Revise or its dependencies.
This is used to make stacktraces obtained with Revise more similar to those obtained without Revise, while retaining one entry to reveal Revise's involvement.
In current releases of Julia, hitting Ctrl-C from the REPL can stop tasks running in the background. This risks stopping Revise's ability to watch for changes in files and directories. Revise has a work-around for this problem.
Revise.throwto_repl
— Functionsuccess = throwto_repl(e::Exception)
Try throwing e
from the REPL's backend task. Returns true
if the necessary conditions were met and the throw can be expected to succeed. The throw is generated from another task, so a yield
will need to occur before it happens.
Git integration
Revise.git_source
— FunctionRevise.git_source(file::AbstractString, reference)
Read the source-text for file
from a git commit reference
. The reference may be a string, Symbol, or LibGit2.Tree
.
Example:
Revise.git_source("/path/to/myfile.jl", "HEAD")
Revise.git_source("/path/to/myfile.jl", :abcd1234) # by commit SHA
Revise.git_files
— Functionfiles = git_files(repo)
Return the list of files checked into repo
.
Revise.git_repo
— Functionrepo, repo_path = git_repo(path::AbstractString)
Return the repo::LibGit2.GitRepo
containing the file or directory path
. path
does not necessarily need to be the top-level directory of the repository. Also returns the repo_path
of the top-level directory for the repository.
Distributed computing
Revise.init_worker
— FunctionRevise.init_worker(p)
Define methods on worker p
that Revise needs in order to perform revisions on p
. Revise itself does not need to be running on p
.
Teaching Revise about non-julia source codes
Revise can be made to work for transpilers from non-Julia languages to Julia with a little effort. For example, if you wrote a transpiler from C to Julia, you can define a struct CFile
which overrides enough of the common String
methods (abspath
,isabspath
, joinpath
, normpath
,isfile
,findfirst
, and String
), it will be supported by Revise if you define a method like
function Revise.parse_source!(mod_exprs_sigs::Revise.ModuleExprsSigs, file::CFile, mod::Module; kwargs...)
ex = # julia Expr returned from running transpiler
Revise.process_source!(mod_exprs_sigs, ex, file, mod; kwargs...)
end