Basic Package Extension Example

Hello,

I’m having some trouble getting a basic example of package extensions working. I might’ve missed it, but there is no “starting from zero” example of how to create package extensions in the docs here. So hopefully someone can help me understand how to do this properly.

I want to use package extensions to add CairoMakie plotting support for my simple package. Here is my simple package defined in src/MyPackage.jl.

module MyPackage

export MyObj

struct MyObj
    a::Vector{Float64}
    U::Float64
    L::Float64
end


end # module MyPackage

Now I want to add CairoMakie support by defining lines() for MyObj. So in ext/ I create PlotsExt.jl.

module PlotsExt

using MyPackage, CairoMakie, LinearAlgebra

function CairoMakie.lines(obj::MyObj)
    n = length(obj.a)
    x = collect(LinRange(obj.L, obj.U, n))
    y = obj.a
    return fig, ax, ln = lines([x y])
end


end # module

I don’t think I am ready to start using this package yet though, right? I need to update the Project.toml file. I need to manually add the following

[extensions]
PlotsExt = "CairoMakie"

And so then my Project.toml looks like this.

name = "MyPackage"
uuid = "..."
authors = ...
version = "0.1.0"

[extensions]
PlotsExt = "CairoMakie"

At this point though I am unsure I’m done? I think I still need to add CairoMakie as a dependency. So then I do that and now my Project.toml looks like this

name = "MyPackage"
uuid = "..."
authors = ...
version = "0.1.0"

[deps]
CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0"

[extensions]
PlotsExt = "CairoMakie"

Hm, so the example in the link above says weakdeps. Am I supposed to manually change that or does Pkg do it? For now I’ll just do it myself.

name = "MyPackage"
uuid = "..."
authors = ...
version = "0.1.0"

[weakdeps]
CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0"

[extensions]
PlotsExt = "CairoMakie"

I go to my Example environment and add my local package.

Pkg.develop(path="MyPackage/")

I also add CairoMakie.

Finally I try to run the following example script.

using MyPackage

L = 10
M1 = MyObj([(x / L)^2 for x in 1:10], 1, 10)

using CairoMakie

tmp = lines(M1)

I get the following error

`Makie.convert_arguments` for the plot type Lines{Tuple{MyObj}} and its conversion trait PointBased() was unsuccessful.

Any hints on where I went wrong here? Or the proper workflow to utilize package extensions?

Try swapping the order of your using statements.

using CairoMakie
using MyPackage
Base.retry_load_extensions() # you might not need this
L = 10
M1 = MyObj([(x / L)^2 for x in 1:10], 1, 10)

tmp = lines(M1)

Also, if you try and resolve and precompile the project, you should see your PlotsExt precompiling if everything if it is detected properly.

AFAIU, yes, you have to do this manually. And I think the easiest way is the one you just used: use Pkg.add to add the [deps] line with the correct UUID, then manually edit the Project.toml file to move the relevant line to a (potentially new) [weakdeps] section.

This looks like everything works from the package extensions point of view, and the error comes from your implementation of CairoMakie.lines(::MyObj)… or am I mistaken? (In any case, this would be more clearly seen if you posted the entire error, including the backtrace)


A debugging hint that helped me when creating my first package extension is to (temporarily) define an __init__() method in the extension to see whether/when it is loaded:

module PlotsExt

using MyPackage, CairoMakie, LinearAlgebra

# TODO: remove this once everything works!
__init__() = println("Extension was loaded!")

function CairoMakie.lines(obj::MyObj)
   # [...]
end

end
1 Like

For my own reference, I made a MWE because I had trouble figuring out package extensions from the documentation. Here is the link in case it is helpful for you or anyone who comes across this thread in the future.

2 Likes

Moving using CairoMakie first was the trick.

On second thought, I’m not sure why I would need Base.retry_load_extensions(). I was just watching the Juliacon presentation and the example shown has the weak dependency (Gadfly) being loaded after the main package (MyPkg).

I only use Base.retry_load_extensions() for debugging/development when I’ve dev’d my package, in case the package extension hasn’t been loaded. It shouldn’t need to used outside of development.

As for the order, I agree that the order in which the deps are loaded should not affect whether the package extension loads. I have only been testing using dev on my main package in which the order matters, so perhaps this is just a bug?