Skip to content

Commit

Permalink
Add support for "glue packages" to code loading
Browse files Browse the repository at this point in the history
This allows packages to define "glue packages" which
are modules that are automatically loaded when
a set of other packages are loaded into the Julia
session.
  • Loading branch information
KristofferC committed Nov 24, 2022
1 parent 726bbd7 commit aee0af8
Show file tree
Hide file tree
Showing 16 changed files with 458 additions and 26 deletions.
6 changes: 5 additions & 1 deletion NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ Julia v1.10 Release Notes
New language features
---------------------


New feature for packages that allows loading a piece of code based on other
packages being loaded in the Julia session.
This has similar applications as the Requires.jl package but also
supports precompilation and setting compatibility.
Look in the documentation for Pkg.jl for "glue packages" for more information.
Language changes
----------------

Expand Down
300 changes: 275 additions & 25 deletions base/loading.jl

Large diffs are not rendered by default.

39 changes: 39 additions & 0 deletions doc/src/manual/code-loading.md
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,46 @@ The subscripted `rootsᵢ`, `graphᵢ` and `pathsᵢ` variables correspond to th
2. Packages in non-primary environments can end up using incompatible versions of their dependencies even if their own environments are entirely compatible. This can happen when one of their dependencies is shadowed by a version in an earlier environment in the stack (either by graph or path, or both).

Since the primary environment is typically the environment of a project you're working on, while environments later in the stack contain additional tools, this is the right trade-off: it's better to break your development tools but keep the project working. When such incompatibilities occur, you'll typically want to upgrade your dev tools to versions that are compatible with the main project.
### "Glue" packages and dependencies

A "glue package" is a module that is automatically loaded when a specified set of other packages (called "glue dependencies") are loaded in the current Julia session.
These are defined by adding the following two sections to a package's `Project.toml` file:

```toml
name = "MyPackage"

[gluedeps]
GlueDep = "c9a23..." # uuid
OtherGlueDep = "862e..." # uuid

[gluepkgs]
GlueFoo = "GlueDep"
GlueBar = ["GlueDep", "OtherGlueDep"]
...
```

The keys under `gluepkgs` are the name of the glue packages.
They are loaded when all the packages on the right hand side (the glue dependencies) of the glue package are loaded.
If a glue package only has one glue dependency the lit of glue dependencies can be written as just a string for breviety.
The location for the entry point of the glue package is either in `glue/GlueFoo.jl` or `glue/GlueFoo/GlueFoo.jl` for
glue package `GlueFoo`.
The glue package can be viewed as a somewhat normal package that has the glue dependencies and the main package as dependencies.
The content of a glue package is often structured as:

```
module GlueFoo
# Load main package and glue dependencies
using MyPackage, GlueDep
# Extend functionality in main package with types from the glue dependencies
MyPackage.func(x::GlueDep.SomeStruct) = ...
end
```

When a package with glue packages is added to an environment, the `gluedeps` and `gluepkgs` sections
are stored in the manifest file in the section for that package.
### Package/Environment Preferences

Preferences are dictionaries of metadata that influence package behavior within an environment.
Expand Down
29 changes: 29 additions & 0 deletions test/loading.jl
Original file line number Diff line number Diff line change
Expand Up @@ -991,5 +991,34 @@ end
end
end


@testset "GluePkgs" begin
old_depot_path = copy(DEPOT_PATH)
try
tmp = mktempdir()
push!(empty!(DEPOT_PATH), joinpath(tmp, "depot"))

proj = joinpath(@__DIR__, "project", "GluePkgs", "HasDepWithGluePkgs.jl")
for i in 1:2 # Once when requiring precomilation, once where it is already precompiled
cmd = `$(Base.julia_cmd()) --project=$proj --startup-file=no -e '
begin
using HasGluePkgs
HasGluePkgs.glue_loaded && error()
using HasDepWithGluePkgs
HasGluePkgs.glue_loaded || error()
HasGluePkgs.glue_folder_loaded && error()
HasDepWithGluePkgs.do_something() || error()
using GlueDep2
HasGluePkgs.glue_folder_loaded || error()
end
'`
@test success(cmd)
end
finally
copy!(DEPOT_PATH, old_depot_path)
end
end


empty!(Base.DEPOT_PATH)
append!(Base.DEPOT_PATH, original_depot_path)
3 changes: 3 additions & 0 deletions test/project/GluePkgs/GlueDep.jl/Project.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
name = "GlueDep"
uuid = "fa069be4-f60b-4d4c-8b95-f8008775090c"
version = "0.1.0"
5 changes: 5 additions & 0 deletions test/project/GluePkgs/GlueDep.jl/src/GlueDep.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module GlueDep

struct GlueDepStruct end

end # module GlueDep
3 changes: 3 additions & 0 deletions test/project/GluePkgs/GlueDep2/Project.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
name = "GlueDep2"
uuid = "55982ee5-2ad5-4c40-8cfe-5e9e1b01500d"
version = "0.1.0"
5 changes: 5 additions & 0 deletions test/project/GluePkgs/GlueDep2/src/GlueDep2.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module GlueDep2

greet() = print("Hello World!")

end # module GlueDep2
25 changes: 25 additions & 0 deletions test/project/GluePkgs/HasDepWithGluePkgs.jl/Manifest.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# This file is machine-generated - editing it directly is not advised

julia_version = "1.10.0-DEV"
manifest_format = "2.0"
project_hash = "7cbe1857ecc6692a8cc8be428a5ad5073531ff98"

[[deps.GlueDep]]
path = "../GlueDep.jl"
uuid = "fa069be4-f60b-4d4c-8b95-f8008775090c"
version = "0.1.0"

[[deps.GlueDep2]]
path = "../GlueDep2"
uuid = "55982ee5-2ad5-4c40-8cfe-5e9e1b01500d"
version = "0.1.0"

[[deps.HasGluePkgs]]
gluedeps = ["GlueDep", "GlueDep2"]
path = "../HasGluePkgs.jl"
uuid = "4d3288b3-3afc-4bb6-85f3-489fffe514c8"
version = "0.1.0"

[deps.HasGluePkgs.gluepkgs]
GluePkg = "GlueDep"
GluePkgFolder = ["GlueDep", "GlueDep2"]
8 changes: 8 additions & 0 deletions test/project/GluePkgs/HasDepWithGluePkgs.jl/Project.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
name = "HasDepWithGluePkgs"
uuid = "d4ef3d4a-8e22-4710-85d8-c6cf2eb9efca"
version = "0.1.0"

[deps]
GlueDep = "fa069be4-f60b-4d4c-8b95-f8008775090c"
GlueDep2 = "55982ee5-2ad5-4c40-8cfe-5e9e1b01500d"
HasGluePkgs = "4d3288b3-3afc-4bb6-85f3-489fffe514c8"
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module HasDepWithGluePkgs

using HasGluePkgs: HasGluePkgs, HasGluePkgsStruct
using GlueDep: GlueDepStruct
# Loading GlueDep makes the glue module "GluePkg" load

function do_something()
HasGluePkgs.foo(HasGluePkgsStruct()) == 1 || error()
HasGluePkgs.foo(GlueDepStruct()) == 2 || error()
return true
end

end # module
7 changes: 7 additions & 0 deletions test/project/GluePkgs/HasGluePkgs.jl/Manifest.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# This file is machine-generated - editing it directly is not advised

julia_version = "1.10.0-DEV"
manifest_format = "2.0"
project_hash = "c87947f1f1f070eea848950c304d668a112dec3d"

[deps]
11 changes: 11 additions & 0 deletions test/project/GluePkgs/HasGluePkgs.jl/Project.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
name = "HasGluePkgs"
uuid = "4d3288b3-3afc-4bb6-85f3-489fffe514c8"
version = "0.1.0"

[gluedeps]
GlueDep = "fa069be4-f60b-4d4c-8b95-f8008775090c"
GlueDep2 = "55982ee5-2ad5-4c40-8cfe-5e9e1b01500d"

[gluepkgs]
GluePkg = "GlueDep"
GluePkgFolder = ["GlueDep", "GlueDep2"]
11 changes: 11 additions & 0 deletions test/project/GluePkgs/HasGluePkgs.jl/glue/GluePkg.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module GluePkg

using HasGluePkgs, GlueDep

HasGluePkgs.foo(::GlueDep.GlueDepStruct) = 2

function __init__()
HasGluePkgs.glue_loaded = true
end

end
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module GluePkgFolder

using GlueDep, GlueDep2, HasGluePkgs

function __init__()
HasGluePkgs.glue_folder_loaded = true
end

end
10 changes: 10 additions & 0 deletions test/project/GluePkgs/HasGluePkgs.jl/src/HasGluePkgs.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module HasGluePkgs

struct HasGluePkgsStruct end

foo(::HasGluePkgsStruct) = 1

glue_loaded = false
glue_folder_loaded = false

end # module

0 comments on commit aee0af8

Please sign in to comment.