Merge pull request 'feat(addon-system): add <kindred> package.xml extensions and schema docs' (#257) from feat/package-xml-schema into main
Reviewed-on: #257
This commit was merged in pull request #257.
This commit is contained in:
@@ -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
|
||||||
|
|
||||||
|
|||||||
109
docs/src/development/package-xml-schema.md
Normal file
109
docs/src/development/package-xml-schema.md
Normal 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).
|
||||||
Submodule mods/silo updated: dc64a66f0f...7a4ed3550a
Submodule mods/ztools updated: ef16ecbaa2...29ca89e533
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user