diff --git a/docs/src/lib/internals/documenter.md b/docs/src/lib/internals/documenter.md index ea7d875243..c69a8c3cd8 100644 --- a/docs/src/lib/internals/documenter.md +++ b/docs/src/lib/internals/documenter.md @@ -7,4 +7,5 @@ Documenter.user_host_upstream Documenter.find_object Documenter.xrefname Documenter.crossref +Documenter.DocumenterBuildException ``` diff --git a/docs/src/lib/public.md b/docs/src/lib/public.md index ea725bced5..7d6f94547b 100644 --- a/docs/src/lib/public.md +++ b/docs/src/lib/public.md @@ -31,6 +31,7 @@ doctest DocMeta DocMeta.getdocmeta DocMeta.setdocmeta! +Documenter.DocumenterError ``` ## DocumenterTools diff --git a/src/Documenter.jl b/src/Documenter.jl index eab6f77716..f15ae35471 100644 --- a/src/Documenter.jl +++ b/src/Documenter.jl @@ -65,6 +65,14 @@ abstract type Plugin end abstract type Writer end +""" + abstract type DocumenterBuildException <: Exception + +Internal abstract supertype for all the exceptions that get propagated to the user as +[`DocumenterError`](@ref)s. +""" +abstract type DocumenterBuildException <: Exception end + include("utilities/DOM.jl") include("utilities/JSDependencies.jl") include("utilities/MDFlatten.jl") diff --git a/src/builder_pipeline.jl b/src/builder_pipeline.jl index af98526ae3..a3ac6003ee 100644 --- a/src/builder_pipeline.jl +++ b/src/builder_pipeline.jl @@ -250,15 +250,25 @@ function Selectors.runner(::Type{Builder.RenderDocument}, doc::Documenter.Docume fatal_errors = filter(is_strict(doc), doc.internal.errors) c = length(fatal_errors) if c > 0 - error("`makedocs` encountered $(c > 1 ? "errors" : "an error") [" - * join(Ref(":") .* string.(fatal_errors), ", ") - * "] -- terminating build before rendering.") + msg = string( + "`makedocs` encountered $(c > 1 ? "errors" : "an error") [", + join(Ref(":") .* string.(fatal_errors), ", "), + "] -- terminating build before rendering." + ) + throw(DocCheckError(msg)) else @info "RenderDocument: rendering document." Documenter.render(doc) end end +struct DocCheckError <: DocumenterBuildException + msg::String +end +function Base.showerror(io::IO, e::DocCheckError) + println(io, "DocCheckError: $(e.msg)") +end + Selectors.runner(::Type{Builder.DocumentPipeline}, doc::Documenter.Document) = nothing function is_doctest_only(doc, stepname) diff --git a/src/makedocs.jl b/src/makedocs.jl index 68aca25a0c..6b5dd70143 100644 --- a/src/makedocs.jl +++ b/src/makedocs.jl @@ -233,21 +233,69 @@ Other formats can be enabled by using other addon-packages. For example, the the original Markdown -> Markdown output. See the [Other Output Formats](@ref) for more information. +# Errors + +`makedocs` will throw a [`DocumenterError`](@ref) if it has a "controlled failure", e.g. due +document checks failing, or some other issue that is likely due to user configuration problems. +`makedocs` throwing something other than [`DocumenterError`](@ref) should, generally be considered +an error. + # See Also A guide detailing how to document a package using Documenter's [`makedocs`](@ref) is provided in the [setup guide in the manual](@ref Package-Guide). """ function makedocs(; debug = false, format = HTML(), kwargs...) - document = Documenter.Document(; format=format, kwargs...) - # Before starting the build pipeline, we empty out the subtype cache used by - # Selectors.dispatch. This is to make sure that we pick up any new selector stages that - # may have been added to the selector pipelines between makedocs calls. - empty!(Selectors.selector_subtypes) - cd(document.user.root) do; withenv(NO_KEY_ENV...) do - Selectors.dispatch(Builder.DocumentPipeline, document) - end end - debug ? document : nothing + exception = nothing + try + document = Documenter.Document(; format=format, kwargs...) + # Before starting the build pipeline, we empty out the subtype cache used by + # Selectors.dispatch. This is to make sure that we pick up any new selector stages that + # may have been added to the selector pipelines between makedocs calls. + empty!(Selectors.selector_subtypes) + cd(document.user.root) do + withenv(NO_KEY_ENV...) do + Selectors.dispatch(Builder.DocumentPipeline, document) + end + end + return debug ? document : nothing + catch e + if isa(e, DocumenterBuildException) + exc_error_log = IOBuffer() + showerror(exc_error_log, e, catch_backtrace()) + @debug String(take!(exc_error_log)) + exception = DocumenterError(:makedocs, (e, catch_backtrace())) + else + rethrow(e) + end + end + isnothing(exception) || throw(exception) +end + +""" + struct DocumenterError <: Exception + +Documenter throws this error when it fails in a "controlled" way (e.g. when docchecks or +doctests have failed). + +!!! note + + Documenter is not fully consistent about using this exception yet. So while Documenter + throwing a different error object should be considered a bug, it may still be indicating + a user error (rather than Documenter itself breaking in some way). +""" +struct DocumenterError <: Exception + fn::Symbol + exception::Union{Tuple{Any,Any},Nothing} +end + +function Base.showerror(io::IO, e::DocumenterError) + println(io, "DocumenterError ($(e.fn)): Documenter build failed to complete.") + if !isnothing(e.exception) + println("Underlying exception:") + showerror(io, e.exception[1]) + end + print(io, "Please check the error logs above as well.") end """