A basis for future *Craft applications.
This project will provide a basis for future projects similar to Snapcraft, Charmcraft and Rockcraft, with the eventual goal of rebasing those applications on this.
TODO
TODO
The basis for *craft applications
Home Page: https://canonical-craft-application.readthedocs-hosted.com/en/latest
License: GNU Lesser General Public License v3.0
A basis for future *Craft applications.
This project will provide a basis for future projects similar to Snapcraft, Charmcraft and Rockcraft, with the eventual goal of rebasing those applications on this.
TODO
TODO
<appname>_PARALLEL_BUILD_COUNT
.CRAFT_PARALLEL_BUILD_COUNT
<appname>_MAX_PARALLEL_BUILD_COUNT
and the total number of CPUs on the system.
<appname>_MAX_PARALLEL_BUILD_COUNT
is not set, then use CRAFT_MAX_PARALLEL_BUILD_COUNT
MAX
envvars are set and the number of CPUs on the system cannot be determined, do not provide a build count to craft-parts (which will use its default of 1).Define and document:
LifecycleService
writing adopt-info
. See: https://github.com/canonical/craft-application/pull/217/files#r1496586665)Some questions to consider:
This is loosely defined currently and has worked fine so far, but we're reaching edge cases that make us need a
When a cache directory already exists, pyxdg throws an exception that we don't catch
See: https://matrix.to/#/!LhFxJIPEcCacgdMghH:ubuntu.com/$kp5AKrB1uWyHgrnl5nFXH-plPE2PXgMpu_YCZLj56pw
No response
2024-01-02 11:27:02.019 Starting charmcraft
2024-01-02 11:27:02.022 Loading project file '/home/runner/work/istio-operators/istio-operators/charms/istio-gateway/charmcraft.yaml'
2024-01-02 11:27:02.023 Setting target machine to x86_64
2024-01-02 11:27:02.026 Running charmcraft:ubuntu-20.04-amd64 in amd64 instance...
2024-01-02 11:27:02.027 Preparing managed instance 'charmcraft-istio-gateway-on-amd64-for-amd64-779228'
2024-01-02 11:27:02.027 Using hostname 'charmcraft-istio-gateway-on-amd64-for-amd64-779228'
2024-01-02 11:27:02.027 Using default provider
2024-01-02 11:27:02.027 Launching managed ubuntu 20.04 instance...
2024-01-02 11:27:02.027 Executing on host: lxc remote list --format=yaml
2024-01-02 11:27:02.063 Executing on host: lxc remote add craft-com.ubuntu.cloud-buildd https://cloud-images.ubuntu.com/buildd/releases --protocol=simplestreams
2024-01-02 11:27:02.098 Remote 'craft-com.ubuntu.cloud-buildd' was successfully added.
2024-01-02 11:27:02.099 Executing on host: lxc project list local: --format=yaml
2024-01-02 11:27:02.139 Executing on host: lxc --project charmcraft project create local:charmcraft
2024-01-02 11:27:02.180 Executing on host: lxc --project default profile show local:default
2024-01-02 11:27:02.233 Executing on host: lxc --project charmcraft profile edit local:default
2024-01-02 11:27:02.285 Set LXD instance name to 'charmcraft-istio-gateway-on-amd64-for-amd64-779228'
2024-01-02 11:27:02.286 Checking for instance 'charmcraft-istio-gateway-on-amd64-for-amd64-779228' in project 'charmcraft' in remote 'local'
2024-01-02 11:27:02.286 Executing on host: lxc --project charmcraft list local: --format=yaml
2024-01-02 11:27:02.331 Instance 'charmcraft-istio-gateway-on-amd64-for-amd64-779228' does not exist.
2024-01-02 11:27:02.331 Set LXD instance name to 'base-instance-charmcraft-buildd-base-v4--b6028d8e72f849bc5897'
2024-01-02 11:27:02.331 Checking for base instance 'base-instance-charmcraft-buildd-base-v4--b6028d8e72f849bc5897' in project 'charmcraft' in remote 'local'
2024-01-02 11:27:02.332 Executing on host: lxc --project charmcraft list local: --format=yaml
2024-01-02 11:27:02.376 Base instance 'base-instance-charmcraft-buildd-base-v4--b6028d8e72f849bc5897' does not exist.
2024-01-02 11:27:02.376 Creating new instance from remote
2024-01-02 11:27:02.376 Creating new base instance from remote
2024-01-02 11:27:02.376 Creating new base instance from image 'core20' from remote 'craft-com.ubuntu.cloud-buildd'
2024-01-02 11:27:02.376 Executing on host: lxc --project charmcraft info local:
2024-01-02 11:27:02.451 Executing on host: lxc --project charmcraft launch craft-com.ubuntu.cloud-buildd:core20 local:base-instance-charmcraft-buildd-base-v4--b6028d8e72f849bc5897 --config 'raw.idmap=both 1001 0' --config security.syscalls.intercept.mknod=true --config user.craft_providers.status=STARTING --config user.craft_providers.timer=2024-01-02T11:27:02.451795+00:00 --config user.craft_providers.pid=16830
2024-01-02 11:27:02.019 Starting charmcraft
2024-01-02 11:27:02.020 charmcraft internal error: FileExistsError(17, 'File exists')
2024-01-02 11:27:02.022 Traceback (most recent call last):
2024-01-02 11:27:02.022 File "/snap/charmcraft/2180/lib/python3.10/site-packages/craft_application/application.py", line 375, in run
2024-01-02 11:27:02.022 self._configure_services(platform, build_for)
2024-01-02 11:27:02.022 File "/snap/charmcraft/2180/lib/python3.10/site-packages/charmcraft/application/main.py", line 107, in _configure_services
2024-01-02 11:27:02.022 cache_dir=self.cache_dir,
2024-01-02 11:27:02.022 File "/snap/charmcraft/2180/lib/python3.10/site-packages/craft_application/application.py", line 185, in cache_dir
2024-01-02 11:27:02.022 return save_cache_path(self.app.name) # type: ignore[no-any-return]
2024-01-02 11:27:02.022 File "/snap/charmcraft/2180/lib/python3.10/site-packages/xdg/BaseDirectory.py", line 84, in save_cache_path
2024-01-02 11:27:02.022 os.makedirs(path)
2024-01-02 11:27:02.022 File "/snap/charmcraft/2180/bin/../usr/lib/python3.10/os.py", line 225, in makedirs
2024-01-02 11:27:02.022 mkdir(name, mode)
2024-01-02 11:27:02.022 FileExistsError: [Errno 17] File exists: '/home/runner/snap/charmcraft/common/cache/charmcraft'
2024-01-02 11:27:02.022 Full execution log: '/home/runner/.local/state/charmcraft/log/charmcraft-20240102-112702.018439.log'
AppMetadata
expects version
to always come from importlib.metadata
, but that doesn't work if the application hasn't been pip install
'd.
A better form of this would be to :
<app_name>.__version__
for a version stringdev
)from craft_application import AppMetadata
AppMetadata(name="something that isn't installed!")
We need some developer documentation for how to use craft-application
Right now moving an app to craft-application requires a lot of looking at source code
Provide an easy way for the app developer to override the default command groups. (e.g. either by making the default command group getters Application class methods or by offering a way to provide override command groups on __init__
)
See: canonical/charmcraft#1340 (comment)
Right now the lifecycle
and other
command groups are created by default using craft-application
command groups. If you need to override those groups (like Charmcraft does), it's possible but not particularly pretty.
Project is loaded through _configure_services even when the command does not need it
Create a command and run it without the yaml representing the project
N/A
Traceback (most recent call last):
File "/snap/appcraft/x38/bin/appcraft", line 8, in <module>
sys.exit(main())
File "/snap/appcraft/x38/lib/python3.10/site-packages/appcraft/cli.py", line 33, in main
return app.run()
File "/snap/appcraft/x38/lib/python3.10/site-packages/craft_application/application.py", line 227, in run
self._configure_services()
File "/snap/appcraft/x38/lib/python3.10/site-packages/appcraft/application.py", line 33, in _configure_services
super()._configure_services()
File "/snap/appcraft/x38/lib/python3.10/site-packages/craft_application/application.py", line 127, in _configure_services
self.services.project = self.project
File "/snap/appcraft/x38/usr/lib/python3.10/functools.py", line 981, in __get__
val = self.func(instance)
File "/snap/appcraft/x38/lib/python3.10/site-packages/craft_application/application.py", line 139, in project
return self.app.ProjectClass.from_yaml_file(project_file)
File "/snap/appcraft/x38/lib/python3.10/site-packages/craft_application/models/base.py", line 74, in from_yaml_file
with path.open() as file:
File "/snap/appcraft/x38/usr/lib/python3.10/pathlib.py", line 1119, in open
return self._accessor.open(self, mode, buffering, encoding, errors,
FileNotFoundError: [Errno 2] No such file or directory: '/home/sergiusens/appcraft.yaml'
It could be handy if a recurring repo could be mentioned only once, for example:
definitions:
kubeflow-source: &kubeflow-source
source: https://github.com/kubeflow/kubeflow
source-type: git
source-tag: v1.7-branch # upstream branch
source-depth: 1
parts:
backend:
plugin: nil
<<: *kubeflow-source
Currently this fails with:
Bad rockcraft.yaml content:
- extra field 'definitions' not permitted in top-level configuration
If the same repo+tag are used multiple times, it could be easier to maintain and easier to read.
Fail early with a good error message if the application is running a lifecycle command in destructive mode and the build plan is incompatible with the running environment (for example, running in Arch Linux)
The current behavior is to... pretend it worked? It might actually "work" depending on the project, but at best the built artifact will contain nonsense.
Once craft-providers has a release supporting 3.12: canonical/craft-providers#317
canonical/rockcraft@2b711d6
(#412)
craft-application should pass through those environment variables (and maybe a few more) automatically.
Every app is doing this on its own.
Revert #176
The latest changes to LXD have temporarily rendered it unusable for all non-Ubuntu systems. We'll need to wait for it to be fixed to re-add these tests.
Right now each AppCommand
class generates a full command for its managed run. Instead, the Application should take care of the application command and global arguments and the Command should generate just the command-specific parameters.
Various applications will have different global arguments and we shouldn't need to modify the Commands for those.
When using craft-provider build instances, the command " clean" should remove the build instance. Instead, it currently launches into the instance and then cleans the lifecycle inside it.
Run " clean".
No response
none
Add a basic linter service that provides an extensible linter framework
Both snapcraft and charmcraft implement linters in pretty similar ways. We should provide a linter service to take care of most of that so all craft applications can use it.
Make this something we can extend:
Snapcraft is overriding it :-/
Add an overrideable method to Application
to resolve the path to the project YAML file.
snapcraft.yaml
can be in several different locations, and we shouldn't have to override the project property for that.
The lifecycle service should get the parallel build size like snapcraft does:
https://github.com/snapcore/snapcraft/blob/main/snapcraft/utils.py#L211
feature parity
This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.
These problems occurred while renovating this repository. View logs.
These updates are awaiting their schedule. Click on a checkbox to get an update now.
dev/coverage
, dev/pytest
, dev/requests
, lint/codespell
, types/mypy
, types/pyright
)pydantic
, pydantic-yaml
).github/workflows/cla-check.yaml
canonical/has-signed-canonical-cla v1
.github/workflows/docs.yaml
actions/checkout v4
actions/setup-python v5
actions/upload-artifact v4
.github/workflows/release-drafter.yaml
release-drafter/release-drafter v6.0.0
.github/workflows/release-publish.yaml
actions/checkout v4
actions/setup-python v5
actions/upload-artifact v4
actions/download-artifact v4
actions/download-artifact v4
softprops/action-gh-release v2
.github/workflows/tests.yaml
actions/checkout v4
actions/setup-python v5
actions/checkout v4
actions/setup-python v5
canonical/setup-lxd v0.1.1
codecov/codecov-action v4
actions/upload-artifact v4
actions/checkout v4
actions/setup-python v5
canonical/setup-lxd v0.1.1
codecov/codecov-action v4
actions/upload-artifact v4
pyproject.toml
craft-archives >=1.1.3
craft-cli >=2.6.0
craft-grammar >=1.2.0
craft-parts >=1.33.0
craft-providers >=1.20.0,<2.0
snap-helpers >=0.4.2
platformdirs >=3.10
pydantic >=1.10,<2.0
pydantic-yaml <1.0
pygit2 >=1.13.0,<1.15.0
PyYaml >=6.0
requests <2.32
typing_extensions >=4.4.0
remote/launchpadlib >=1.10.16
dev/coverage ==7.4.4
dev/hypothesis >=6.0
dev/pyfakefs ~=5.3
dev/pytest ==8.1.1
dev/pytest-check ==2.3.1
dev/pytest-cov ==5.0.0
dev/pytest-mock ==3.14.0
dev/pytest-rerunfailures ==14.0
dev/pytest-time >=0.3.1
dev/requests <2.32.0
dev/responses ~=0.25.0
lint/black ~=24.0
lint/codespell ==2.2.6
lint/yamllint ==1.35.1
types/mypy ==1.9.0
types/pyright ==1.1.359
docs/canonical-sphinx ~=0.1
docs/sphinx-autobuild ==2024.4.16
docs/sphinx-lint ==0.9.1
setuptools ==70.1.0
setuptools_scm >=7.1
tox.ini
tox-ignore-env-name-mismatch >=0.2.0.post2
tox-gh ==1.3.1
tox.ini
tox-ignore-env-name-mismatch 0.2.0.post2
tox-gh 1.3.1
snapcraft.yaml
that can exist in .
, snap/
, etc
To support snapcraft rebasing onto craft-application.
Allow the dictionary sent to dispatcher.load_command
in Application.run
to be overridden without overriding the whole method.
See: canonical/charmcraft#1340 (comment)
Currently we have to override the whole method, which is suboptimal.
Add --destructive-mode
option for in-host builds.
For feature-parity with the existing applications
If you don't have pygit2 installed in your environment, starting up a craft-application app will crash.
It's expected that remote build related tests will fail, but other tests should not.
No response
n/a
The project has summary and description as optional keys, these should be mandatory for all Craft Applications. The packed output risks not having a summary and description
Create a yaml to load with project without a summary and description
name: foo
version: bar
# summary: foobar
# description: foobarbaz
n/a
Print out the app version here: https://github.com/canonical/craft-application/blob/main/craft_application/application.py#L308
easier debugging
The TODO item here is to create the relevant tasks.
Every time we raise a CraftError of any sort, it should have a docs_url
attached to it. This means several things:
raise
statements and attach the relevant documentation.A future goal is to provide a way to rewrite the docs_url link for the specific application.
craft-application current has the command-line options for --shell and --shell-after but the switches aren't actually used. Also we're missing --debug (both the option and the implementation).
For feature-parity with the existing applications.
.
.
Add some basic spread tests running with a fake "testcraft" application
The main goal is to bridge the gap between integration tests in craft-application (which are useful but can never reproduce the "production" environment of an app) and the spread tests of one of our applications (which are the ultimate source of "right" and "wrong" but are very expensive to run).
When running an invalid command, we get a useful message:
$ rockcraft oakfdpao
Usage: rockcraft [options] command [args]...
Try 'rockcraft -h' for help.
Error: no such command 'oakfdpao
However, when using a command line with a valid command but an invalid parameter, we get a message as if this were an internal error in the application:
$ rockcraft pack --oakfdpao
rockcraft internal error: ArgumentParsingError("Usage: rockcraft [options] command [args]...\nTry 'rockcraft pack -h' for help.\n\nError: unrecognized arguments: --oakfdpao\n")
Full execution log: '/home/tiago/.local/state/rockcraft/log/rockcraft-20231002-190658.059266.log'
Run <appname> pack --invalid-param
n/a
rockcraft internal error: ArgumentParsingError("Usage: rockcraft [options] command [args]...\nTry 'rockcraft pack -h' for help.\n\nError: unrecognized arguments: --oakfdpao\n")
Full execution log: '/home/tiago/.local/state/rockcraft/log/rockcraft-20231002-190658.059266.log'
Implement support for build-time, host-provided secrets.
Needs canonical/craft-cli#184
This is necessary to support things like password-accessed Git repositories, enable Ubuntu Pro, etc.
Create a fake testcraft
app to test the application and add spread tests using it.
Testcraft can become a good place to test new features of craft-application, etc.
The original design is for parts, stage and prime to live inside the managed environment.
Create an app using Craft Application
N/A
N/A
Calling Application.add_command_group()
to add a CommandGroup
whose name
matches one the default groups causes the groups to stay duplicated, which affects the help output.
In this example, rockcraft
has an init
command in the Other
group:
$ rockcraft --help
Usage:
rockcraft [help] <command>
Summary: A tool to create OCI images
Global options:
-h, --help: Show this help message and exit
-v, --verbose: Show debug information and be more verbose
-q, --quiet: Only show warnings and errors, not progress
--verbosity: Set the verbosity level to 'quiet', 'brief',
'verbose', 'debug' or 'trace'
-V, --version: Show the application version and exit
Starter commands:
version: Show the application version and exit
Commands can be classified as follows:
Extensions: expand-extensions, list-extensions
Lifecycle: build, clean, pack, prime, pull, stage
Other: version
Other: init
For more information about a command, run 'rockcraft help <command>'.
For a summary of all commands, run 'rockcraft help --all'.
See that there are two rows for "Other:"
n/a
n/a
See this comment thread.
The modifications made to the YAML loader may be more than is needed with the latest pyyaml, but I'm not confident enough to say so. If the questioned except
clause is needed, we should have a test for it that has it raise that error, as right now the loader fails with the same issue before it gets to the dict constructor.
The two fields summary
and description
should be mandatory.
n/a
n/a
n/a
Create a spec + prototype for supporting build-time secrets in the applications. The idea is to follow spread's lead: For example, a declaration like:
parts:
part1:
source: https://user:$(HOST: GIT_PASS)@git.com
source-type: git
....
will cause the application to evaluate the GIT_PASS
environment variable on the host, replacing its value in the source
line.
Users need a way to handle passwords, api keys, etc without recording them in the application's project files.
Some problems related to logging errors:
PartsError
.Get an error, check the log
parts:
my-part:
plugin: nil
override-build: |
exit 1
...
2023-10-24 16:03:25.181 add action my-part:Step.STAGE(ActionType.RUN)
2023-10-24 16:03:25.181 process my-part:Step.PRIME
2023-10-24 16:03:25.181 add action my-part:Step.PRIME(ActionType.RUN)
2023-10-24 16:03:25.181 testcraft internal error: RuntimeError("Parts processing internal error: I'm a regular RuntimeError")
2023-10-24 16:03:25.181 Full execution log: '/home/tiago/.local/state/testcraft/log/testcraft-20231024-160325.177049.log'
Although running testcraft --help
works, testcraft version --help
results in application crash.
Create a simple test application using craft-application, then invoke it using --help
on a command.
$ snarfcraft version --help
Internal error while loading snarfcraft: TypeError("'NoneType' object is not subscriptable")
N/A
Traceback (most recent call last):
File "/home/claudio/.venv/snarfcraft/bin/snarfcraft", line 8, in <module>
sys.exit(cli.main())
File "/home/claudio/starcraft/application-test/snarfcraft/cli.py", line 32, in main
return app.run()
File "/home/claudio/.venv/snarfcraft/lib/python3.10/site-packages/craft_application/application.py", line 253, in run
dispatcher = self._get_dispatcher()
File "/home/claudio/.venv/snarfcraft/lib/python3.10/site-packages/craft_application/application.py", line 219, in _get_dispatcher
global_args = dispatcher.pre_parse_args(sys.argv[1:])
File "/home/claudio/.venv/snarfcraft/lib/python3.10/site-packages/craft_cli/dispatcher.py", line 431, in pre_parse_args
help_text = self._get_requested_help(filtered_sysargs)
File "/home/claudio/.venv/snarfcraft/lib/python3.10/site-packages/craft_cli/dispatcher.py", line 315, in _get_requested_help
command = cmd_class(None)
File "/home/claudio/.venv/snarfcraft/lib/python3.10/site-packages/craft_application/commands/base.py", line 41, in __init__
self._app: application.AppMetadata = config["app"]
TypeError: 'NoneType' object is not subscriptable
Every existing application has some version of support for multiple builds based on a "build matrix". This matrix needs craft-application support because it ends up informing the entire rest of the process. For example:
For feature-parity with the existing applications.
Currently a run that uses a build instance is not fetching its logs after the instance is finished. As a result, the "outer" log ends at the point where the host application executes the instance's one.
Run " pack" and check the logs afterwards.
No response
none
Move the business logic for this:
into its own utility function that can either output to a stream or to a string.
We need this in a few apps, might as well have it consolidated.
Provide an easy way to get relevant paths, at least in managed mode.
We're doing this with pathlib.Path("/root/project")
in a few places and we should make it easier to keep consistent.
Pydantic 2's ErrorDict
includes an input
key that holds the value that failed to validate. Once we update from Pydantic 1 we should use this new field in our validation failure messages.
Ref: #156
To provide clearer messages!
Remove get_build_plan from a Project since we're using a BuildPlanner
Cleanup
Errors generated by the craft-libraries are being silently discarded when sending the error to the emitter. The result is a single " internal error: " in the cli and log
Run any project that fails to build in craft-parts (or through a programming error).
No response
none
The current code that loops over the build plan to launch managed runs just no-ops if the plan is empty (like if all build infos were filtered out). I can't think of a case where that shouldn't be an error instead, specially since we've decided that trying to build an empty plan in destructive mode is an error.
Create a project with a build plan that will get totally filtered out (eg only builds on arm64
and you're on amd64
)
.
.
Craft-application uses the same base instance name. LXD requires every instance to have a unique name, even if they are in different projects.
To make a unique name, the base instance name should include the name of app.
This was an easy error to produce because the compatibility_tag
parameter is optional. See canonical/craft-providers#454 for details.
Run 2 different craft-applications (i.e. charmcraft feature branch and rockcraft)
No response
Error: Failed start validation for device "eth0": Instance DNS name "base-instance-buildd-base-v3-craft-comub-523148486bd0c6cc722c" already used on network
Take an integer value (in AppMetadata
?) that provides a per-application addendum to the craft-providers compatibility tag, defaulting to zero.
Set the compatibility_tag
keyword argument for the provider base to include that value.
Charmcraft has to override ProviderService
and get_base
because it adds custom compatibility tags: https://github.com/canonical/charmcraft/pull/1330/files/84ffcdd7a0731a17fcae83a889ed7e2143f7cfb6#diff-a32511469d8ae4dd52bf0ce115517e3cc980f46568ae386be734af9f2978b7c7
This shouldn't be necessary.
When running a craft tool in --verbose
(or higher, probably), the lifecycle messages like Pulling mypart
get duplicated.
Run any lifecycle-using command with verbose verbosity, e.g. appcraft build -v
.
n/a
Initialising lifecycle
Installing build-packages
Pulling my-part
Pulling my-part
Pulling pebble
Pulling pebble
Overlaying my-part
Overlaying my-part
Overlaying pebble
Overlaying pebble
Building my-part
Building my-part
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.