Skip to content

Add a build command#14

Merged
tchx84 merged 30 commits into
mainfrom
build-command
May 22, 2026
Merged

Add a build command#14
tchx84 merged 30 commits into
mainfrom
build-command

Conversation

@ebassi

@ebassi ebassi commented May 19, 2026

Copy link
Copy Markdown
Member

Allows us to replace our use of kas-container for building Moonforge projects.

Most of the build details can be controlled by the project metadata, and by a configuration file, instead of using environment variables. We can also do better validation of inputs than using a shell script.

Closes: #8

ebassi added 12 commits May 6, 2026 14:19
It really doesn't need to be inside the init sub-command.
The project metadata allows us to reconstruct the project state at
the time of its initialization.

Closes: #9
A command that sets up a container for building the given Moonforge
project, using the project metadata to find all the required components
and files without having to specify them on the command line.
A handy way to set up the configuration per-project or per-user.

The "config" sub-command works similarly to "git config":

- there is a user configuration and a project configuration scope
- the project configuration, if found, takes precedence over the user
  configuration
- if no scope is defined, the values are pooled together from project
  and user scopes
- if no scope is defined, keys are read from the common pool and written
  to the project scope

Examples:

    # read from all scopes
    moonforge config list

    # read from project scope
    moonforge config --project get container.engine

    # write to the user scope
    moonforge config --user set container.engine podman

    # read from all scopes, write to the project scope
    moonforge config set container.engine podman
Use the project metadata to determine the appropriate kas file to build,
and the Moonforge configuration data for the container engine and cache
directories.
Ideally, we want this description to be included in the help output.
Right now, argparse is making it slightly more difficult than necessary.
If a command has an embedded long description, show it after the basic
argument help screen, as a replacement for a manual page.
Kas allows specifying additional fragments alongside the main YAML file.
The tricky bit is to normalize the path for each fragment so that its
rooted under the volume we use to share the project directory inside the
container.
Follow kas-container, and add --runtime-args for passing extra arguments
when setting up the runtime.
Needed for maximum conformance.
@ebassi
ebassi requested a review from tchx84 May 19, 2026 15:37
ebassi added 7 commits May 19, 2026 16:54
We depend on Python 3.10 (EOL 2026-10-31), so we can't use typing.Self.
The utils.find_program() function can return None according to the
signature, but we use error_if_not_found to ensure that we bail out if
we can't find the binary specified inside container.engine in our PATH.
The long description for the command is allowed to go over the column
limit.
Exactly like the build command, except:

- mounts the repository volume as read-write
- uses "kas shell" instead of "kas build"
We may be accessing project attributes, and we don't use anything else
from other objects. The alternative would be to put it into utils, but
then we'd need to access the Project class from utils.
Use Project.machine.name to expand @MACHINE_NAME@ in build paths.

@tchx84 tchx84 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good, but we really need a README with the basics now 🙏

Additionally, I left a few comments for minor things require fixing, and other questions.

Comment thread src/moonforgecli/config.py Outdated
Comment thread src/moonforgecli/config.py Outdated
Comment thread src/moonforgecli/project.py
Comment thread src/moonforgecli/project.py Outdated
Comment thread src/moonforgecli/shell.py


def add_args(parser):
parser.add_argument("--fragment", metavar="FILE", dest="fragments",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can have an option to specify the image file?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not entirely a fan of the concept; the whole idea is to remove the need to know the image file, since its name is determined by the project name and machine name.

The only case in which I'd see a need for that is building different images for different machines, but currently we don't allow initializing a project with more than one machine. If that were the case, I'd likely add a --machine switch to the build and shell commands.

I understand that we have a bunch of layers that have multiple image files, but those are mainly examples; I don't think you want to have the same inside an actual project. If that were the case, we'd need to have a way to generate image files from additional tokens.

Comment thread src/moonforgecli/shell.py


def add_args(parser):
parser.add_argument("--fragment", metavar="FILE", dest="fragments",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you considered keeping kas' colon formatting for fragments?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did, but I find the foo:bar:baz format is a bit suspect, especially when the fragments have longer path components.

Comment thread README.md Outdated
ebassi added 5 commits May 20, 2026 16:04
Show how to use "moonforge config" to set various configuration options.
Since we might want to change the project metadata across revisions of
moonforge-cli, we should have a way to specify the version that
initialised the project, and a way to parse the project metadata
depending on that version. Using Rust as an example, the version is
called "edition", and its identifier uses a "YYYY.N" format. We use that
identifier to determine which fields are known and valid.
There's no point, and it prevents us from transparently updating the
defaults.
We start from the user configuration, as the baseline, and then we load
the project configuration, as an override.

If a configuration source does not exist, we skip it; to ensure that
we're using the most appropriate source, we update the configuration
source type when loading them all in sequence, so that:

1. we do not create unnecessary configuration values
2. we do not create unnecessary configuration files

This means that calling:

    moonforge config set key value

Inside a project directory will set the project's configuration, while
calling it outside of a project directory will set the user's
configuration.
This way, calling `moonforge config` inside the project will access the
project configuration, and we can pre-seed a project with the values we
care about.
Comment thread README.md Outdated
Comment thread src/moonforgecli/project.py
@tchx84
tchx84 merged commit 45d50a5 into main May 22, 2026
2 checks passed
@tchx84
tchx84 deleted the build-command branch May 22, 2026 13:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add "build" subcommand to replace kas-container

2 participants