Merge pull request 'feat(addon-system): add <kindred> package.xml extensions and schema docs' (#257) from feat/package-xml-schema into main
Some checks failed
Deploy Docs / build-and-deploy (push) Successful in 46s
Build and Test / build (push) Failing after 2m1s

Reviewed-on: #257
This commit was merged in pull request #257.
This commit is contained in:
2026-02-17 13:15:47 +00:00
5 changed files with 123 additions and 5 deletions

View File

@@ -28,6 +28,7 @@
- [Repository Structure](./development/repo-structure.md) - [Repository Structure](./development/repo-structure.md)
- [Build System](./development/build-system.md) - [Build System](./development/build-system.md)
- [Gui Module Build](./development/gui-build-integration.md) - [Gui Module Build](./development/gui-build-integration.md)
- [Package.xml Schema Extensions](./development/package-xml-schema.md)
# Silo Server # Silo Server

View File

@@ -0,0 +1,109 @@
# Package.xml Schema Extensions
Kindred Create extends FreeCAD's standard `package.xml` addon manifest with a `<kindred>` element. This element provides metadata for the manifest-driven addon loader: version compatibility, load ordering, dependency declarations, and editing context registration.
The `<kindred>` element is ignored by FreeCAD's AddonManager and stock module loader. Addons with this element remain compatible with upstream FreeCAD.
## Field reference
| Field | Parsed by loader | Required | Default | Description |
|---|---|---|---|---|
| `min_create_version` | Yes | No | *(none)* | Minimum Kindred Create version. Addon is skipped if the running version is lower. |
| `max_create_version` | Yes | No | *(none)* | Maximum Kindred Create version. Addon is skipped if the running version is higher. |
| `load_priority` | Yes | No | `100` | Integer. Lower values load first. Used as a secondary sort after dependency resolution. |
| `dependencies` | Yes | No | *(none)* | List of addon names (by `<name>` in their `package.xml`) that must load before this one. |
| `sdk_version` | No | No | *(none)* | Required kindred-addon-sdk version. Reserved for future use when the SDK is available. |
| `pure_python` | No | No | `true` | If `false`, the addon requires compiled C++ components. Informational. |
| `contexts` | No | No | *(none)* | Editing contexts this addon registers or injects into. Informational. |
Fields marked "Parsed by loader" are read by `addon_loader.py` and affect load behavior. Other fields are informational metadata for tooling and documentation.
## Schema
```xml
<kindred>
<min_create_version>0.1.0</min_create_version>
<max_create_version>1.0.0</max_create_version>
<sdk_version>0.1.0</sdk_version>
<load_priority>100</load_priority>
<pure_python>true</pure_python>
<dependencies>
<dependency>sdk</dependency>
<dependency>other-addon</dependency>
</dependencies>
<contexts>
<context id="partdesign.body" action="inject"/>
<context id="sketcher.edit" action="register"/>
<context id="*" action="overlay"/>
</contexts>
</kindred>
```
All child elements are optional. An empty `<kindred/>` element is valid and signals that the addon is Kindred-aware with all defaults.
### Version fields
`min_create_version` and `max_create_version` are compared against the running Kindred Create version using semantic versioning (major.minor.patch). If the running version falls outside the declared range, the addon is skipped with a warning in the report view.
### Load priority
When multiple addons have no dependency relationship, `load_priority` determines their relative order. Lower values load first. Suggested ranges:
| Range | Use |
|---|---|
| 0-9 | SDK and core infrastructure |
| 10-49 | Foundation addons that others may depend on |
| 50-99 | Standard addons |
| 100+ | Optional or late-loading addons |
### Dependencies
Each `<dependency>` names another addon by its `<name>` element in `package.xml`. The loader resolves load order using topological sort. If a dependency is not found among discovered addons, the dependent addon is skipped.
### Contexts
The `<contexts>` element documents which editing contexts the addon interacts with. The `action` attribute describes the type of interaction:
| Action | Meaning |
|---|---|
| `inject` | Addon injects commands into this context's toolbars |
| `register` | Addon registers this as a new context |
| `overlay` | Addon registers an overlay that may apply across contexts |
A wildcard `id="*"` indicates the addon's overlay applies universally (matched by a condition function rather than a specific context ID).
## Example: complete package.xml
```xml
<?xml version="1.0" encoding="UTF-8"?>
<package format="1" xmlns="https://wiki.freecad.org/Package_Metadata">
<name>MyAddon</name>
<description>Example Kindred Create addon</description>
<version>0.2.0</version>
<maintainer email="dev@example.com">Developer</maintainer>
<license>LGPL-2.1-or-later</license>
<url type="repository">https://git.example.com/myaddon</url>
<content>
<workbench>
<classname>MyAddonWorkbench</classname>
<subdirectory>./</subdirectory>
</workbench>
</content>
<kindred>
<min_create_version>0.1.0</min_create_version>
<load_priority>80</load_priority>
<pure_python>true</pure_python>
<contexts>
<context id="partdesign.body" action="inject"/>
</contexts>
</kindred>
</package>
```
## Backward compatibility
- The `<kindred>` element sits outside `<content>` and is not part of FreeCAD's package.xml specification. FreeCAD's `App.Metadata` C++ parser and the AddonManager's Python `MetadataReader` both ignore unknown elements.
- An addon installed in stock FreeCAD will work normally; the `<kindred>` extensions are simply unused.
- The Kindred Create loader works with or without the `<kindred>` element. Addons that omit it load with no version constraints and default priority (100).

View File

@@ -381,7 +381,16 @@ def resolve_load_order(
ts.add(m.name, *known_deps) ts.add(m.name, *known_deps)
try: try:
order = list(ts.static_order()) # Process level by level so we can sort within each topological level
ts.prepare()
order = []
while ts.is_active():
ready = list(ts.get_ready())
# Sort each level by (priority, name) for determinism
ready.sort(key=lambda n: (by_name[n].load_priority, n) if n in by_name else (999, n))
for name in ready:
ts.done(name)
order.extend(ready)
except CycleError as e: except CycleError as e:
FreeCAD.Console.PrintWarning( FreeCAD.Console.PrintWarning(
f"Create: Dependency cycle detected: {e}. Falling back to priority order.\n" f"Create: Dependency cycle detected: {e}. Falling back to priority order.\n"
@@ -391,8 +400,7 @@ def resolve_load_order(
key=lambda m: (m.load_priority, m.name), key=lambda m: (m.load_priority, m.name),
) )
# Filter to actual manifests, preserving topological order # Filter to actual manifests, preserving sorted topological order
# Secondary sort within independent groups by (priority, name)
result = [] result = []
for name in order: for name in order:
m = by_name.get(name) m = by_name.get(name)