Implemented multi-binary app packaging (#648)
This commit is contained in:
committed by
GitHub
parent
2496dea5c2
commit
8556103eb1
@@ -1,13 +1,17 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <app/manager.h>
|
||||
#include <app/metadata.h>
|
||||
#include <app/package_manifest.h>
|
||||
#include <app/private/arguments.h>
|
||||
#include <app/private/binary_path.h>
|
||||
#include <app/private/fd_table.h>
|
||||
#include <app/private/fs.h>
|
||||
#include <app/private/ledger.h>
|
||||
#include <app/private/manager_internal.h>
|
||||
#include <app/private/package_manifest_parsing.h>
|
||||
#include <app/private/scheduler.h>
|
||||
|
||||
#include <TactilityCpp/Allocator.h>
|
||||
|
||||
#include <tactility/concurrent/mutex.h>
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/log.h>
|
||||
@@ -72,6 +76,67 @@ void app_manager_for_each_manifest(AppManifestVisitorFn visitor, void* context)
|
||||
mutex_unlock(&ledger.mutex);
|
||||
}
|
||||
|
||||
error_t app_manager_add_package(const PackageManifest* package, const char* const* app_ids, size_t app_id_count) {
|
||||
auto& ledger = app_ledger();
|
||||
mutex_lock(&ledger.mutex);
|
||||
if (ledger.packages.contains(package->id)) {
|
||||
mutex_unlock(&ledger.mutex);
|
||||
LOG_E(TAG, "Package with id '%s' is already registered", package->id);
|
||||
return ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
AppPackageRecord record { .package = *package };
|
||||
record.app_ids.reserve(app_id_count);
|
||||
for (size_t i = 0; i < app_id_count; i++) {
|
||||
record.app_ids.emplace_back(app_ids[i]);
|
||||
}
|
||||
ledger.packages[package->id] = std::move(record);
|
||||
mutex_unlock(&ledger.mutex);
|
||||
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
error_t app_manager_remove_package(const char* package_id) {
|
||||
auto& ledger = app_ledger();
|
||||
mutex_lock(&ledger.mutex);
|
||||
auto iterator = ledger.packages.find(package_id);
|
||||
if (iterator == ledger.packages.end()) {
|
||||
mutex_unlock(&ledger.mutex);
|
||||
return ERROR_NOT_FOUND;
|
||||
}
|
||||
ledger.packages.erase(iterator);
|
||||
mutex_unlock(&ledger.mutex);
|
||||
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
error_t app_manager_find_package(const char* package_id, PackageManifest* out_package) {
|
||||
auto& ledger = app_ledger();
|
||||
mutex_lock(&ledger.mutex);
|
||||
auto iterator = ledger.packages.find(package_id);
|
||||
if (iterator == ledger.packages.end()) {
|
||||
mutex_unlock(&ledger.mutex);
|
||||
return ERROR_NOT_FOUND;
|
||||
}
|
||||
*out_package = iterator->second.package;
|
||||
mutex_unlock(&ledger.mutex);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
void app_manager_for_each_package(AppPackageVisitorFn visitor, void* context) {
|
||||
auto& ledger = app_ledger();
|
||||
mutex_lock(&ledger.mutex);
|
||||
for (auto& [id, record] : ledger.packages) {
|
||||
std::vector<const char*> app_id_ptrs;
|
||||
app_id_ptrs.reserve(record.app_ids.size());
|
||||
for (const auto& app_id : record.app_ids) {
|
||||
app_id_ptrs.push_back(app_id.c_str());
|
||||
}
|
||||
AppPackage pkg { .package = record.package, .app_id_count = app_id_ptrs.size(), .app_ids = app_id_ptrs.data() };
|
||||
visitor(&pkg, context);
|
||||
}
|
||||
mutex_unlock(&ledger.mutex);
|
||||
}
|
||||
|
||||
error_t app_manager_start_internal(const AppManifest* manifest, AppLocation location, AppStackConfig stack, AppInstanceId parent_instance_id, int argc, const char* const argv_in[], const AppStreamBinding* bindings, size_t binding_count, AppInstanceId* out_app_instance_id) {
|
||||
char** argv = app_arguments_copy(argc, argv_in);
|
||||
if (argc > 0 && argv == nullptr) {
|
||||
@@ -200,19 +265,19 @@ error_t app_manager_get_topmost_app_id(char* buffer, size_t buffer_size) {
|
||||
|
||||
namespace {
|
||||
|
||||
// Owns the AppManifest (and its id/name/path strings) that app_manager_add() only keeps a
|
||||
// non-owning pointer to. Separate from app_install.cpp's registry: scanning only
|
||||
// adds/removes registrations, never touches disk or running instances.
|
||||
struct ScannedAppManifest {
|
||||
std::string id;
|
||||
std::string name;
|
||||
std::string path;
|
||||
AppManifest manifest {};
|
||||
// Owns the AppManifests (and their backing location-path strings) that app_manager_add() only
|
||||
// keeps non-owning pointers to. Separate from app_install.cpp's registry: scanning only
|
||||
// adds/removes registrations, never touches disk or running instances. AppManifest::id/name own
|
||||
// their own storage directly (fixed arrays), so this doesn't need to separately own those.
|
||||
struct ScannedPackageManifest {
|
||||
std::string path; // scanned directory
|
||||
std::vector<AppManifest> manifests; // one per AppManifest the package declared
|
||||
std::vector<std::string> locations; // backs manifests[i].location.location, same indices
|
||||
};
|
||||
|
||||
struct InstallPathRegistry {
|
||||
std::vector<std::string> paths;
|
||||
std::unordered_map<std::string, std::unique_ptr<ScannedAppManifest>> scanned;
|
||||
std::unordered_map<std::string, std::unique_ptr<ScannedPackageManifest>> scanned;
|
||||
Mutex mutex {};
|
||||
|
||||
InstallPathRegistry() { mutex_construct(&mutex); }
|
||||
@@ -249,50 +314,69 @@ void app_manager_install_path_scan(void) {
|
||||
app_fs_list_direct_subdirectories(root, found_app_dirs);
|
||||
}
|
||||
|
||||
// Snapshot once so the rest of the scan doesn't hold registry.mutex.
|
||||
// Snapshot once so the rest of the scan doesn't hold registry.mutex. Keeps each known
|
||||
// package's own manifest ids too, so a package whose directory has disappeared can have all
|
||||
// of its (possibly several) app_manager registrations removed below, not just one.
|
||||
struct KnownPackage {
|
||||
std::string path;
|
||||
std::vector<std::string> manifest_ids;
|
||||
};
|
||||
mutex_lock(®istry.mutex);
|
||||
std::unordered_map<std::string, std::string> known_paths;
|
||||
std::unordered_map<std::string, KnownPackage> known_packages;
|
||||
for (const auto& [id, record] : registry.scanned) {
|
||||
known_paths.emplace(id, record->path);
|
||||
KnownPackage known { .path = record->path };
|
||||
for (const auto& manifest : record->manifests) {
|
||||
known.manifest_ids.emplace_back(manifest.id);
|
||||
}
|
||||
known_packages.emplace(id, std::move(known));
|
||||
}
|
||||
mutex_unlock(®istry.mutex);
|
||||
|
||||
// Parses without registry.mutex held; filesystem IO is slow.
|
||||
std::vector<std::unique_ptr<ScannedAppManifest>> new_records;
|
||||
std::vector<std::unique_ptr<ScannedPackageManifest>> new_records;
|
||||
std::vector<std::string> new_package_ids;
|
||||
std::vector<PackageManifest> new_packages;
|
||||
for (const auto& app_dir : found_app_dirs) {
|
||||
auto manifest_path = app_dir + "/manifest.properties";
|
||||
if (!app_fs_is_file(manifest_path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
AppMetadata metadata {};
|
||||
if (app_metadata_parse(manifest_path.c_str(), &metadata) != ERROR_NONE) {
|
||||
PackageManifest package {};
|
||||
// Heap-allocated (not a stack array - too large for a typical app task's stack; this
|
||||
// function runs on whichever task calls app_manager_install_path_scan(), e.g. Boot's, via
|
||||
// registerInstalledAppsFromFileSystems()) and sized to fit exactly, not pre-allocated to
|
||||
// some fixed maximum (see app_package_manifest_parse_into()). OptExternalAllocator prefers
|
||||
// PSRAM for this transient buffer, freeing up scarce internal RAM.
|
||||
std::vector<AppManifestBinding, tt::OptExternalAllocator<AppManifestBinding>> app_bindings;
|
||||
if (app_package_manifest_parse_into(manifest_path.c_str(), package, app_bindings) != ERROR_NONE) {
|
||||
LOG_W(TAG, "Invalid manifest at %s", manifest_path.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (known_paths.contains(metadata.app_id)) {
|
||||
if (known_packages.contains(package.id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto record = std::make_unique<ScannedAppManifest>();
|
||||
record->id = metadata.app_id;
|
||||
record->name = metadata.app_name;
|
||||
auto record = std::make_unique<ScannedPackageManifest>();
|
||||
record->path = app_dir;
|
||||
record->manifest = AppManifest {
|
||||
.id = record->id.c_str(),
|
||||
.name = record->name.c_str(),
|
||||
.category = APP_CATEGORY_USER,
|
||||
.location = { APP_LOCATION_PATH, const_cast<char*>(record->path.c_str()) },
|
||||
.flags = 0,
|
||||
.stack = { .depth = static_cast<uint16_t>(metadata.stack_depth), .desired_memory_capability = 0 },
|
||||
};
|
||||
// Sized once, up front: manifests[i].location.location points into locations[i].c_str(),
|
||||
// which would dangle if either vector reallocated afterward.
|
||||
record->manifests.resize(app_bindings.size());
|
||||
record->locations.resize(app_bindings.size());
|
||||
for (size_t i = 0; i < app_bindings.size(); i++) {
|
||||
record->manifests[i] = app_bindings[i].manifest;
|
||||
record->locations[i] = app_resolve_binary_path(app_dir, app_bindings[i].binary);
|
||||
record->manifests[i].location = { APP_LOCATION_PATH, const_cast<char*>(record->locations[i].c_str()) };
|
||||
}
|
||||
new_package_ids.emplace_back(package.id);
|
||||
new_packages.push_back(package);
|
||||
new_records.push_back(std::move(record));
|
||||
}
|
||||
|
||||
std::vector<std::string> missing_ids;
|
||||
for (const auto& [id, path] : known_paths) {
|
||||
if (!app_fs_is_directory(path)) {
|
||||
for (const auto& [id, known] : known_packages) {
|
||||
if (!app_fs_is_directory(known.path)) {
|
||||
missing_ids.push_back(id);
|
||||
}
|
||||
}
|
||||
@@ -301,23 +385,60 @@ void app_manager_install_path_scan(void) {
|
||||
// registry.mutex would fix a lock order an opposite-order caller could deadlock against.
|
||||
// registry.mutex is retaken afterward only to publish the in-memory results.
|
||||
for (const auto& id : missing_ids) {
|
||||
app_manager_remove(id.c_str());
|
||||
}
|
||||
std::vector<std::unique_ptr<ScannedAppManifest>> added_records;
|
||||
for (auto& record : new_records) {
|
||||
if (app_manager_add(&record->manifest) == ERROR_NONE) {
|
||||
added_records.push_back(std::move(record));
|
||||
} else {
|
||||
LOG_E(TAG, "Failed to register app %s (duplicate id?)", record->id.c_str());
|
||||
for (const auto& manifest_id : known_packages.at(id).manifest_ids) {
|
||||
app_manager_remove(manifest_id.c_str());
|
||||
}
|
||||
app_manager_remove_package(id.c_str());
|
||||
}
|
||||
std::vector<std::unique_ptr<ScannedPackageManifest>> added_records;
|
||||
std::vector<std::string> added_package_ids;
|
||||
for (size_t r = 0; r < new_records.size(); r++) {
|
||||
auto& record = new_records[r];
|
||||
|
||||
// known_packages is only a pre-scan snapshot - two new directories can still share an id.
|
||||
if (std::ranges::find(added_package_ids, new_package_ids[r]) != added_package_ids.end()) {
|
||||
LOG_W(TAG, "Skipping duplicate package id %s found in this scan", new_package_ids[r].c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t added_count = 0;
|
||||
for (; added_count < record->manifests.size(); added_count++) {
|
||||
if (app_manager_add(&record->manifests[added_count]) != ERROR_NONE) {
|
||||
LOG_E(TAG, "Failed to register app %s (duplicate id?)", record->manifests[added_count].id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (added_count != record->manifests.size()) {
|
||||
// All-or-nothing: unregister whatever this package already added before failing.
|
||||
for (size_t j = 0; j < added_count; j++) {
|
||||
app_manager_remove(record->manifests[j].id);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
std::vector<const char*> app_id_ptrs;
|
||||
app_id_ptrs.reserve(record->manifests.size());
|
||||
for (const auto& app_manifest : record->manifests) {
|
||||
app_id_ptrs.push_back(app_manifest.id);
|
||||
}
|
||||
if (app_manager_add_package(&new_packages[r], app_id_ptrs.data(), app_id_ptrs.size()) != ERROR_NONE) {
|
||||
LOG_E(TAG, "Failed to register package %s (duplicate id?)", new_packages[r].id);
|
||||
for (const auto& app_manifest : record->manifests) {
|
||||
app_manager_remove(app_manifest.id);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
added_package_ids.push_back(new_package_ids[r]);
|
||||
added_records.push_back(std::move(record));
|
||||
}
|
||||
|
||||
mutex_lock(®istry.mutex);
|
||||
for (const auto& id : missing_ids) {
|
||||
registry.scanned.erase(id);
|
||||
}
|
||||
for (auto& record : added_records) {
|
||||
registry.scanned[record->id] = std::move(record);
|
||||
for (size_t i = 0; i < added_records.size(); i++) {
|
||||
registry.scanned[added_package_ids[i]] = std::move(added_records[i]);
|
||||
}
|
||||
mutex_unlock(®istry.mutex);
|
||||
}
|
||||
@@ -332,7 +453,10 @@ error_t app_manager_install_path_uninstall(const char* app_id) {
|
||||
return ERROR_NOT_FOUND;
|
||||
}
|
||||
|
||||
const AppManifest* manifest = &iterator->second->manifest;
|
||||
// Pointer, not a copy: the ledger's own AppInstanceRecord::manifest pointers (set by
|
||||
// app_manager_add() from this exact vector) are compared against it by address below, same
|
||||
// as the pre-existing single-manifest version of this function did.
|
||||
const std::vector<AppManifest>* manifests = &iterator->second->manifests;
|
||||
auto path = iterator->second->path;
|
||||
mutex_unlock(®istry.mutex);
|
||||
|
||||
@@ -342,8 +466,11 @@ error_t app_manager_install_path_uninstall(const char* app_id) {
|
||||
auto& ledger = app_ledger();
|
||||
mutex_lock(&ledger.mutex);
|
||||
for (const auto& [id, record] : ledger.instances) {
|
||||
if (record.manifest == manifest) {
|
||||
instance_ids.push_back(id);
|
||||
for (const auto& manifest : *manifests) {
|
||||
if (record.manifest == &manifest) {
|
||||
instance_ids.push_back(id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
mutex_unlock(&ledger.mutex);
|
||||
@@ -354,7 +481,10 @@ error_t app_manager_install_path_uninstall(const char* app_id) {
|
||||
|
||||
// app_manager_remove() takes ledger.mutex; call outside registry.mutex too, matching
|
||||
// the lock order in app_manager_install_path_scan().
|
||||
app_manager_remove(app_id);
|
||||
for (const auto& manifest : *manifests) {
|
||||
app_manager_remove(manifest.id);
|
||||
}
|
||||
app_manager_remove_package(app_id);
|
||||
|
||||
// Delete before erasing the scan record, so a failed deletion still leaves the
|
||||
// entry discoverable for a retry.
|
||||
|
||||
Reference in New Issue
Block a user