Interoperability with Just


Given the feature sets of each, compose.mk isn't really a competitor to tools like just, but since the comparison will come up there might as well be discussion1 and a demo. =)

Actually the specific involvement of just and justfiles as used here is not that important. What this demo really shows is the ability of compose.mk to embrace foreign tools, and to translate across very different systems.

Related Links

If you want to see a similar demo with different base technology, check out the ansible demo.

This is a concrete example of the matrioshki bridge pattern, so if you prefery theory before looking at applications, read those docs first.

Also related are the many other demos that use inlined data and container-definitions.

Justfile Demo


Summary
#!/usr/bin/env -S make -f
# demos/just.mk: 
#   Demonstrates some interoperability with `just`.
#
# Part of the `compose.mk` repo. This file runs as part of the test-suite.  
#
# USAGE: ./demos/just.mk
# USAGE: ./demos/just.mk justfile.target.selector
#

include compose.mk

# Top-level default entrypoint.
__main__: \
  just_container.dispatch/self.just.demo \
  just.recipe just.another.recipe

# No official container (https://github.com/casey/just/issues/1497), 
# so we'll build one.  We could use external files, but to keep the 
# demo self-contained,we'll embed a docker-compose spec for a container
# here, as well as a copy of the official justfile example.  Note that a
# volume or bind-map be faster and more practical here.
define just.services
configs:
  justfile:
    content: |
      recipe-name:
        echo 'This is a recipe!'

      # this is a comment
      another-recipe:
        @echo 'This is another recipe.'
services:
  just_container:
    build:
      context: .
      dockerfile_inline: |
        FROM ${IMG_ALPINE_BASE:-alpine:3.21.2}
        RUN apk add -q --update just bash build-base
    entrypoint: just
    working_dir: /workspace
    volumes:
      - ${PWD}:/workspace
    configs:
      - source: justfile
        target: justfile
endef

# After the inline exists, we get access to scaffolded 
# container targets by calling  `compose.import.string` on it.
$(call compose.import.string, def=just.services)

# A small container-api, considered "private". Targets below run inside the container,
# so we can directly use `just` here without assuming it is available on the host.
self.just.list:; just --summary
self.just.version:; just --version 
self.just.dispatch/%:; just ${*}
self.just.demo: self.just.list self.just.version
    just another-recipe
    just

# Using a dispatch alias, you can "lift" a private target to a "public" one.
# This target is safe to run from the docker host, and abstracts away the container.
just.list: just_container.dispatch/self.just.list

# Stacking 2 layers of dispatch is a bridge! 
# Effectively importing all the justfile targets to top-level `make`.
# Now you can then expose the full underlying tool API, or an opinionated subset, 
# or create an API superset at this layer that extends it.
demo.bridge/%:; ${make} just_container.dispatch/self.just.dispatch/${*}

# Having setup the bridge, we can use it-- create entrypoint aliases,
# or stack them up as prerequisite make targets as usual, etc etc
just.recipe: demo.bridge/recipe-name
just.another.recipe: demo.bridge/another-recipe

# The bridge also allows us to compose brand new functionality really quickly.
#
# As an example, below we provide an interactive runner for the justfile that 
# lets us choose which target to kick off.  To do this, we can use the 
# `io.selector` gadget, which just expects a "get choices" target and a 
# "use chosen" handler target, and we already have both.
justfile.target.selector: io.selector/just.list,demo.bridge

References



  1. If you want more long-format discussion around this sort of thing, there is lots in the design docs