How to set up a monorepo with multiple Julia packages and GitHub Actions?

Hey everyone,

I could use some guidance from folks who’ve gone through this before. I’ve been developing locally with a setup that has about ten Julia modules in a single repo. Each module is in its own sub-directory with its own src/, test/, and docs/ folder. Right now I just use plain include() and using across modules — no formal Project.toml or package registration yet.

Here’s roughly what it looks like:

MyApp/
├── Module A/
│ ├── src/
│ ├── test/
│ └── docs/
├── Module B/
│ ├── src/
│ ├── test/
│ └── docs/

Each module is cleanly separated, and everything runs fine locally when I use relative paths and using. Now I’m getting ready to move this to GitHub and want to do it right — with proper Project.toml files, CI/CD testing, and documentation builds (Docs.jl, GitHub Actions, etc.).

What I’m trying to do

  • Keep a monorepo, not split into ten separate repos.

  • Make each module a package with its own Project.toml so I can using Module A etc.

  • Have GitHub Actions “just work” for testing and docs for all modules.

  • Avoid mistakes or rework others may have already gone through setting up monorepo workflows.

My questions

  • What’s the best way to structure this monorepo so that CI/CD (GitHub Actions, coverage, docs) works cleanly for all sub-packages? Should I have one top-level Project.toml that references the sub-packages, or should everything be independent?

  • How do people typically handle cross-module dependencies inside a monorepo (e.g., Module A depending on Module) — through dev paths or local registries? (not all modules are dependent upon each other, but some are at higher levels)

  • Any examples of monorepos in the Julia ecosystem that follow this pattern (or close to it)?

I’ve seen plenty of examples of single-package setups, watched videos on Projects and Packages, but not many covering multi-package “app” style projects like this. I’ve had my head down developing, but need to plan for this migration now. I’m certain others have done this though, so I would love to hear from anyone who’s already worked through the packaging + CI details for something similar or if I have missed existing threads or docs that address this.

Thanks in advance — I’d really like to get this right before I migrate everything to GitHub!

— Steve

1 Like

I think monorepos are possible since v1.11.
You should have a Project.toml for each submodule and an additional top-level one.
Since 1.11, a Project.toml has the “Sources” section (11. Project.toml and Manifest.toml · Pkg.jl), so that you can add unregistered dependencies in a portable way.

I’ve never tried a monorepo approach, but it seems that Documenter.makedocs (Public API · Documenter.jl) has the capability you need.

For testing, it should be simply using Pkg; Pkg.test("ModuleA"); Pkg.test("ModuleB"); ... Pkg.test() from within MyApp environment. That will run test/runtests.jl files for the submodules (13. API Reference · Pkg.jl).

1 Like

Also check out the new workspace feature introduced in 1.12 that simplifies this a lot. This may remove any need for specifying sources in the first place.

2 Likes

@Vasily_Pisarev and @RomeoV Thanks for the insights. Super helpful. And, the new workspace features seems built-in for this as of just last week. Perfect timing! From the docs

"A workspace is defined in the base project by giving a list of the projects in it …This structure is particularly beneficial for developers using a monorepo approach, where a large number of unregistered packages may be involved. It is also useful for adding documentation or benchmarks to a package by including additional dependencies beyond those of the package itself. "

I am trying to finish up a new feature in the next few days and then I will give this a try and create a public repo example that uses these approaches to share back.

1 Like

Some example monorepos are:

https://github.com/MakieOrg/Makie.jl
and https://github.com/JuliaIO/ChunkCodecs.jl

ChunkCodecs.jl is using workspace, and right now Makie.jl is using sources.

One big decision is whether you want all the packages in the monorepo to be updated in lockstep or if each can have completely independent versions. In ChunkCodecs.jl each package has independent versions and independent changelogs, and only uses the public stable API of the other packages.
From what I can Makie.jl has more tight control of relative versions Makie.jl/CairoMakie/Project.toml at 10d65fd67bb269ea96c6748d754fd3544bb3051c · MakieOrg/Makie.jl · GitHub

Also, most of the GitHub tools don’t work well with monorepos out of the box. For example, I never got tagbot or code coverage checks working in ChunkCodecs.jl, and currently do those manually.

It took me longer than planned to finish the dev task I was doing. But, it’s done. I’ll start this experiment tomorrow and put any progress here. Thanks again for pointers to help me get off on the right track.

I have started a toy repo as an experiment. I am not using workspaces yet (because it will be a while until that is in most people’s workflow, I’ll add that soon though).

TheForce is a base module that “runs through all things”. TheEmpire, and TheResistance use TheForce. The StarWarsApp uses everything.

using Pkg
Pkg.activate(".")
using TheResistance

loads TheResistance and all deps for TheResistance. The same for using on any other module or the whole app.

Github CI/CD is working for build and test. Not for docs yet. I am working on a doc that will go in StarWarsApp/docs to explain how I set this up based on guidance here, some videos, and Claude Sonnet 4.

I’ll work on docs CI/CD next.

3 Likes