Implemented multi-binary app packaging (#648)

This commit is contained in:
Ken Van Hoeylandt
2026-09-09 20:05:04 +02:00
committed by GitHub
parent 2496dea5c2
commit 8556103eb1
62 changed files with 1746 additions and 981 deletions
+15 -14
View File
@@ -12,8 +12,8 @@ extern "C" {
/**
* Computes the install directory for @a app_id (does not check whether anything is actually
* installed there).
* @param[out] path always NULL-terminated on return, even on failure (empty string if
* @a path_size == 0 - nothing is written in that case; otherwise at least "" is written)
* @param[out] path always NULL-terminated on return, even on failure. Empty when @a path_size
* is 0, since nothing is written in that case.
* @retval ERROR_NONE on success
* @retval ERROR_BUFFER_OVERFLOW @a path_size is too small to hold the path (including the
* NULL terminator)
@@ -22,14 +22,15 @@ extern "C" {
error_t app_get_install_path(const char* app_id, char* path, size_t path_size);
/**
* Installs an app from a tarball at @a source_path: extracts it into the app install directory,
* parses the extracted manifest.properties (see app/metadata.h) to determine its id, then
* registers it with app_manager_add() as an AppLocation{APP_LOCATION_PATH, <install dir>} app.
* If an app with the same id is already installed (via a previous app_install() call), it is
* uninstalled first - stopped if running, its old install directory removed - before the new
* one takes its place.
* @param[in] source_path path to a tar file containing the app (must have manifest.properties
* at its root)
* Installs a package from a tarball at @a source_path: extracts it into the package's install
* directory, parses the extracted manifest.properties (see app/package_manifest.h) into a
* PackageManifest and one or more AppManifestBindings, then registers each with app_manager_add()
* as an AppLocation{APP_LOCATION_PATH, <binary path>} app.
* If a package with the same id is already installed (via a previous app_install() call), it is
* uninstalled first: every one of its apps stopped if running, then its old install directory
* removed, before the new one takes its place.
* @param[in] source_path path to a tar file containing the package (must have
* manifest.properties at its root)
* @retval ERROR_NONE on success
* @retval ERROR_NOT_FOUND @a source_path doesn't exist / can't be read
* @retval ERROR_INVALID_ARGUMENT the tarball has no valid manifest.properties at its root
@@ -37,11 +38,11 @@ error_t app_get_install_path(const char* app_id, char* path, size_t path_size);
error_t app_install(const char* source_path);
/**
* Uninstalls a previously app_install()-ed app: stops it if currently running, deletes its
* install directory, and unregisters it (app_manager_remove()).
* @param[in] app_id the id the app was installed under (AppMetadata::app_id)
* Uninstalls a previously app_install()-ed package: stops every one of its apps if currently
* running, deletes its install directory, and unregisters all of them (app_manager_remove()).
* @param[in] app_id the package id it was installed under (PackageManifest::id)
* @retval ERROR_NONE on success
* @retval ERROR_NOT_FOUND no such app was installed via app_install()
* @retval ERROR_NOT_FOUND no such package was installed via app_install()
*/
error_t app_uninstall(const char* app_id);
+2 -2
View File
@@ -1,11 +1,11 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include "location.h"
#include <app/manifest.h>
#include <tactility/error.h>
#include <stdbool.h>
#include <stdint.h>
#include "location.h"
#include <tactility/error.h>
#ifdef __cplusplus
extern "C" {
+45 -2
View File
@@ -3,6 +3,7 @@
#include <app/instance.h>
#include <app/manifest.h>
#include <app/package_manifest.h>
#include <app/stream.h>
#include <tactility/error.h>
@@ -44,6 +45,48 @@ error_t app_manager_find_manifest(const char* id, struct AppManifest* out_manife
typedef void (*AppManifestVisitorFn)(const struct AppManifest* manifest, void* context);
void app_manager_for_each_manifest(AppManifestVisitorFn visitor, void* context);
/**
* Registers a package for enumeration via app_manager_for_each_package(). Separate from
* registering its apps - the caller still calls app_manager_add() for each AppManifest.
* @param[in] app_ids the ids of the AppManifest(s) this package registered
* @retval ERROR_INVALID_ARGUMENT a package with the same id is already registered
* @retval ERROR_NONE on success
*/
error_t app_manager_add_package(const struct PackageManifest* package, const char* const* app_ids, size_t app_id_count);
/**
* Unregisters a previously-added package. Does not touch its apps' own registrations.
* @retval ERROR_NOT_FOUND no package with this id is registered
* @retval ERROR_NONE on success
*/
error_t app_manager_remove_package(const char* package_id);
/**
* @param[out] out_package set to a copy of the package on success
* @retval ERROR_NOT_FOUND no package with this id is registered
* @retval ERROR_NONE on success
*/
error_t app_manager_find_package(const char* package_id, struct PackageManifest* out_package);
/** One registered package, handed to AppPackageVisitorFn - see app_manager_for_each_package(). */
struct AppPackage {
struct PackageManifest package;
/** How many entries @a app_ids points to. */
size_t app_id_count;
/** Valid only for the duration of the app_manager_for_each_package() call that produced
* this - copy out what's needed before returning from the visitor. */
const char* const* app_ids;
};
typedef void (*AppPackageVisitorFn)(const struct AppPackage* pkg, void* context);
/**
* Calls @a visitor once for every registered package. Iteration order is unspecified.
* @warning Same threading contract as app_manager_for_each_manifest(): runs with an internal
* lock held - do not call any app_manager_*() function from inside @a visitor.
*/
void app_manager_for_each_package(AppPackageVisitorFn visitor, void* context);
/** One fd-to-stream binding for app_start_with_streams() (app/start.h). Every field is passed
* through to app_stream_subscribe() as-is; see its own doc for the ownership contracts. */
struct AppStreamBinding {
@@ -90,8 +133,8 @@ error_t app_manager_get_topmost_app_id(char* buffer, size_t buffer_size);
/**
* Registers @a path as a directory to scan for app manifests - each direct subdirectory of
* @a path is expected to hold a manifest.properties (see app/metadata.h), matching the layout
* app_install() creates ({install dir}/{app_id}/manifest.properties), though this is not
* @a path is expected to hold a manifest.properties (see app/package_manifest.h), matching the
* layout app_install() creates ({install dir}/{package id}/manifest.properties), though this is not
* install/uninstall - it only ever adds/removes manifest registrations, never touches files on
* disk or running instances. No-op if @a path is already registered. Does not scan immediately -
* call app_manager_install_path_scan() to do that.
+11 -6
View File
@@ -11,7 +11,10 @@ extern "C" {
#endif
// Character count, excluding null terminator
#define APP_ID_LENGTH 32
#define APP_MANIFEST_ID_LENGTH 32
// Character count, excluding null terminator
#define APP_MANIFEST_NAME_LENGTH 32
/** Broad classification of an app, used for grouping/launcher presentation. */
enum AppCategory {
@@ -45,10 +48,10 @@ struct AppStackConfig {
/** Describes a registrable app. One manifest exists per app id. */
struct AppManifest {
/** Unique app identifier. Should never be NULL. */
const char* id;
/** Human-readable name. Should never be NULL. */
const char* name;
/** Unique app identifier. Must be NULL-terminated. */
char id[APP_MANIFEST_ID_LENGTH + 1];
/** Human-readable name. Must be NULL-terminated. */
char name[APP_MANIFEST_NAME_LENGTH + 1];
enum AppCategory category;
struct AppLocation location;
/** Bitmask of AppManifestFlags. Most apps should leave this 0. */
@@ -57,7 +60,9 @@ struct AppManifest {
struct AppStackConfig stack;
};
bool app_id_is_valid(const char* id);
bool app_manifest_id_is_valid(const char* id);
bool app_manifest_name_is_valid(const char* name);
bool app_manifest_stack_size_is_valid(const char* value);
#ifdef __cplusplus
}
-74
View File
@@ -1,74 +0,0 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <tactility/error.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#define APP_METADATA_TARGET_SDK_LENGTH 16
#define APP_METADATA_APP_ID_LENGTH 32
#define APP_METADATA_APP_NAME_LENGTH 32
#define APP_METADATA_APP_VERSION_NAME_LENGTH 16
#define APP_METADATA_REQUIRES_DEVICE_ID_LENGTH 64
struct AppMetadata {
/**
* The SDK version that was used to compile this app. (e.g. "0.6.0")
* Must be NULL-terminated.
*/
char target_sdk[APP_METADATA_TARGET_SDK_LENGTH + 1];
/**
* The identifier by which the app is launched by the system and other apps.
* Must be NULL-terminated.
*/
char app_id[APP_METADATA_APP_ID_LENGTH + 1];
/**
* The user-readable name of the app. Used in UI.
* Must be NULL-terminated.
*/
char app_name[APP_METADATA_APP_NAME_LENGTH + 1];
/**
* The version as it is displayed to the user (e.g. "1.2.0")
* Must be NULL-terminated.
*/
char app_version_name[APP_METADATA_APP_VERSION_NAME_LENGTH + 1];
/** The technical version (must be incremented with new releases of the app) */
uint64_t app_version_code;
/**
* Comma-separated list of device ids the app is restricted to (e.g. "m5stack-tab5"), matching
* the folder names under Devices/. Empty means unrestricted.
* Must be NULL-terminated.
*/
char requires_device_id[APP_METADATA_REQUIRES_DEVICE_ID_LENGTH + 1];
/**
* Stack depth (in words) for the app's task. Optional; 0 means scheduler default.
* @warning Avoid default values: the default is conservative, which wastes memory.
*/
uint32_t stack_depth;
};
/**
* Parses a manifest.properties file at @a path into @a out_metadata, auto-detecting the V1
* (sectioned, e.g. "[app]id=...") or V2 (flat dot-notation, e.g. "app.id=...") format from its
* first line.
* @retval ERROR_NONE on success
* @retval ERROR_NOT_FOUND the file doesn't exist / couldn't be opened
* @retval ERROR_INVALID_ARGUMENT the file isn't a valid manifest, or a field's value doesn't fit
* @a out_metadata's fixed-size buffers
*/
error_t app_metadata_parse(const char* path, struct AppMetadata* out_metadata);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,88 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <app/manifest.h>
#include <tactility/error.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#define PACKAGE_MANIFEST_TARGET_SDK_LENGTH 16
#define PACKAGE_MANIFEST_ID_LENGTH 32
#define PACKAGE_MANIFEST_VERSION_NAME_LENGTH 16
#define PACKAGE_MANIFEST_REQUIRES_DEVICE_ID_LENGTH 64
/** Character count, excluding null terminator, for AppManifestBinding::binary. */
#define APP_MANIFEST_BINARY_LENGTH 31
/** Largest number of AppManifest entries package_manifest_parse() can produce from a single
* manifest.properties file. */
#define PACKAGE_MANIFEST_MAX_APP_MANIFESTS 32
/** A package-level manifest.properties: SDK/version metadata that applies to the whole package,
* not to any one of its (possibly several) apps. Not kept in memory at runtime - only used to
* create and cache the AppManifest(s) it describes. */
struct PackageManifest {
/**
* The package identifier (e.g. the install directory name). Distinct from any of its own
* AppManifest ids.
* Must be NULL-terminated.
*/
char id[PACKAGE_MANIFEST_ID_LENGTH + 1];
/**
* The package version as it is displayed to the user (e.g. "1.2.0")
* Must be NULL-terminated.
*/
char version_name[PACKAGE_MANIFEST_VERSION_NAME_LENGTH + 1];
/** The package's technical version (must be incremented with new releases). */
uint64_t version_code;
/**
* The SDK version that was used to compile this package. (e.g. "0.6.0")
* Must be NULL-terminated.
*/
char target_sdk[PACKAGE_MANIFEST_TARGET_SDK_LENGTH + 1];
/**
* Comma-separated list of device ids the package is restricted to (e.g. "m5stack-tab5"),
* matching the folder names under Devices/. Empty means unrestricted.
* Must be NULL-terminated.
*/
char requires_device_id[PACKAGE_MANIFEST_REQUIRES_DEVICE_ID_LENGTH + 1];
/** How many AppManifest entries this package's manifest.properties declared. */
uint32_t app_manifest_count;
};
/** Pairs a parsed AppManifest with the filename (without extension) it installs as under
* bin/<platform>/ - e.g. "main" resolves to bin/posix-x86_64/main.so. Not kept anywhere at
* runtime - same lifetime as PackageManifest, only used to hand parse results to the installer/
* scanner, which resolve `binary` into AppManifest::location::location. */
struct AppManifestBinding {
struct AppManifest manifest;
char binary[APP_MANIFEST_BINARY_LENGTH + 1];
};
/**
* Parses a manifest.properties file at @a path (flat dot-notation, e.g. "app.id=...") into
* @a out_package and @a out_bindings.
* @param[out] out_bindings written with up to @a bindings_capacity entries (see
* PackageManifest::app_manifest_count for how many)
* @param[in] bindings_capacity the capacity of @a out_bindings
* @retval ERROR_NONE on success
* @retval ERROR_NOT_FOUND the file doesn't exist / couldn't be opened
* @retval ERROR_INVALID_ARGUMENT the file isn't a valid manifest, or a field's value doesn't fit
* its fixed-size buffer
* @retval ERROR_BUFFER_OVERFLOW the manifest declares more apps than @a bindings_capacity
*/
error_t app_package_manifest_parse(const char* path, struct PackageManifest* out_package, struct AppManifestBinding* out_bindings, size_t bindings_capacity);
#ifdef __cplusplus
}
#endif