wip cpp template

This commit is contained in:
Caelan Sayler
2025-05-14 11:06:43 +01:00
committed by Caelan
parent 0dace00845
commit d6b117964b
10 changed files with 637 additions and 860 deletions

View File

@@ -55,7 +55,7 @@ typedef struct vpkc_asset_t {
*/
char *Version;
/**
* The type of asset (eg. "Full" or "Delta").
* The type of asset (eg. "Full" or "Delta").
*/
char *Type;
/**

View File

@@ -19,194 +19,27 @@
namespace Velopack {
static inline void throw_last_error() {
static inline void throw_last_error()
{
size_t neededSize = vpkc_get_last_error(nullptr, 0);
std::string strError(neededSize, '\0');
vpkc_get_last_error(&strError[0], neededSize);
throw std::runtime_error(strError);
}
static inline std::string to_cppstring(const char* psz) {
return psz == nullptr ? "" : psz;
static inline std::optional<std::string> to_cpp_string(const char* psz)
{
return psz == nullptr ? std::optional<std::string>("") : std::optional<std::string>(psz);
}
static inline char* to_cstring(const std::string& str) {
return const_cast<char*>(str.c_str());
static inline char* alloc_c_string(const std::optional<std::string>& str)
{
if (!str.has_value()) { return nullptr; }
return alloc_c_string(str.value());
}
static inline char* to_cstring_opt(const std::optional<std::string>& str) {
return str.has_value() ? to_cstring(str.value()) : nullptr;
}
static inline std::optional<std::string> to_cppstring_opt(const char* psz) {
return psz == nullptr ? std::nullopt : std::optional<std::string>(psz);
}
static inline bool to_cppbool(bool b) { return b; }
static inline bool to_cbool(bool b) { return b; }
static inline uint64_t to_cu64(uint64_t i) { return i; }
static inline uint64_t to_cppu64(uint64_t i) { return i; }
// !! AUTO-GENERATED-START CPP_TYPES
/// VelopackLocator provides some utility functions for locating the current app important paths (eg. path to packages, update binary, and so forth).
struct VelopackLocatorConfig {
/// The root directory of the current app.
std::string RootAppDir;
/// The path to the Update.exe binary.
std::string UpdateExePath;
/// The path to the packages' directory.
std::string PackagesDir;
/// The current app manifest.
std::string ManifestPath;
/// The directory containing the application's user binaries.
std::string CurrentBinaryDir;
/// Whether the current application is portable or installed.
bool IsPortable;
};
/// An individual Velopack asset, could refer to an asset on-disk or in a remote package feed.
struct VelopackAsset {
/// The name or Id of the package containing this release.
std::string PackageId;
/// The version of this release.
std::string Version;
/// The type of asset (eg. "Full" or "Delta").
std::string Type;
/// The filename of the update package containing this release.
std::string FileName;
/// The SHA1 checksum of the update package containing this release.
std::string SHA1;
/// The SHA256 checksum of the update package containing this release.
std::string SHA256;
/// The size in bytes of the update package containing this release.
uint64_t Size;
/// The release notes in markdown format, as passed to Velopack when packaging the release. This may be an empty string.
std::string NotesMarkdown;
/// The release notes in HTML format, transformed from Markdown when packaging the release. This may be an empty string.
std::string NotesHtml;
};
/// Holds information about the current version and pending updates, such as how many there are, and access to release notes.
struct UpdateInfo {
/// The available version that we are updating to.
VelopackAsset TargetFullRelease;
/// The base release that this update is based on. This is only available if the update is a delta update.
std::optional<VelopackAsset> BaseRelease;
/// The list of delta updates that can be applied to the base version to get to the target version.
std::vector<VelopackAsset> DeltasToTarget;
/// True if the update is a version downgrade or lateral move (such as when switching channels to the same version number).
/// In this case, only full updates are allowed, and any local packages on disk newer than the downloaded version will be
/// deleted.
bool IsDowngrade;
};
/// Options to customise the behaviour of UpdateManager.
struct UpdateOptions {
/// Allows UpdateManager to update to a version that's lower than the current version (i.e. downgrading).
/// This could happen if a release has bugs and was retracted from the release feed, or if you're using
/// ExplicitChannel to switch channels to another channel where the latest version on that
/// channel is lower than the current version.
bool AllowVersionDowngrade;
/// **This option should usually be left None**.
/// Overrides the default channel used to fetch updates.
/// The default channel will be whatever channel was specified on the command line when building this release.
/// For example, if the current release was packaged with '--channel beta', then the default channel will be 'beta'.
/// This allows users to automatically receive updates from the same channel they installed from. This options
/// allows you to explicitly switch channels, for example if the user wished to switch back to the 'stable' channel
/// without having to reinstall the application.
std::optional<std::string> ExplicitChannel;
/// Sets the maximum number of deltas to consider before falling back to a full update.
/// The default is 10. Set to a negative number (eg. -1) to disable deltas.
int64_t MaximumDeltasBeforeFallback;
};
static inline vpkc_locator_config_t to_c(const VelopackLocatorConfig& dto) {
return {
to_cstring(dto.RootAppDir),
to_cstring(dto.UpdateExePath),
to_cstring(dto.PackagesDir),
to_cstring(dto.ManifestPath),
to_cstring(dto.CurrentBinaryDir),
to_cbool(dto.IsPortable),
};
}
static inline VelopackLocatorConfig to_cpp(const vpkc_locator_config_t& dto) {
return {
to_cppstring(dto.RootAppDir),
to_cppstring(dto.UpdateExePath),
to_cppstring(dto.PackagesDir),
to_cppstring(dto.ManifestPath),
to_cppstring(dto.CurrentBinaryDir),
to_cppbool(dto.IsPortable),
};
}
static inline vpkc_asset_t to_c(const VelopackAsset& dto) {
return {
to_cstring(dto.PackageId),
to_cstring(dto.Version),
to_cstring(dto.Type),
to_cstring(dto.FileName),
to_cstring(dto.SHA1),
to_cstring(dto.SHA256),
to_cu64(dto.Size),
to_cstring(dto.NotesMarkdown),
to_cstring(dto.NotesHtml),
};
}
static inline VelopackAsset to_cpp(const vpkc_asset_t& dto) {
return {
to_cppstring(dto.PackageId),
to_cppstring(dto.Version),
to_cppstring(dto.Type),
to_cppstring(dto.FileName),
to_cppstring(dto.SHA1),
to_cppstring(dto.SHA256),
to_cppu64(dto.Size),
to_cppstring(dto.NotesMarkdown),
to_cppstring(dto.NotesHtml),
};
}
static inline vpkc_update_info_t to_c(const UpdateInfo& dto) {
return {
to_c(dto.TargetFullRelease),
to_c_opt(dto.BaseRelease),
to_c(dto.DeltasToTarget),
to_cbool(dto.IsDowngrade),
};
}
static inline UpdateInfo to_cpp(const vpkc_update_info_t& dto) {
return {
to_cpp(dto.TargetFullRelease),
to_cpp_opt(dto.BaseRelease),
to_cpp(dto.DeltasToTarget),
to_cppbool(dto.IsDowngrade),
};
}
static inline vpkc_update_options_t to_c(const UpdateOptions& dto) {
return {
to_cbool(dto.AllowVersionDowngrade),
to_cstring_opt(dto.ExplicitChannel),
to_ci32(dto.MaximumDeltasBeforeFallback),
};
}
static inline UpdateOptions to_cpp(const vpkc_update_options_t& dto) {
return {
to_cppbool(dto.AllowVersionDowngrade),
to_cppstring_opt(dto.ExplicitChannel),
to_cppi32(dto.MaximumDeltasBeforeFallback),
};
}
// !! AUTO-GENERATED-END CPP_TYPES
static inline char* allocate_cstring(const std::string& str) {
static inline char* alloc_c_string(const std::string& str)
{
char* result = new char[str.size() + 1]; // +1 for null-terminator
#ifdef _WIN32
strcpy_s(result, str.size() + 1, str.c_str()); // Copy string content
@@ -217,26 +50,403 @@ static inline char* allocate_cstring(const std::string& str) {
return result;
}
static inline void free_cstring(char* str) {
static inline void free_c_string(char* str)
{
delete[] str;
}
static inline char** allocate_cstring_array(const std::vector<std::string>& vec) {
char** result = new char*[vec.size()];
for (size_t i = 0; i < vec.size(); ++i) {
result[i] = allocate_cstring(vec[i]);
static inline char** alloc_c_string_vec(const std::vector<std::string>& dto, size_t* count)
{
if (dto.empty()) {
*count = 0;
return nullptr;
}
return result;
*count = dto.size();
char** arr = new char* [*count];
for (size_t i = 0; i < *count; ++i) {
arr[i] = alloc_c_string(dto[i]);
}
return arr;
}
static inline void free_cstring_array(char** arr, size_t size) {
static inline void free_c_string_vec(char** arr, size_t size)
{
for (size_t i = 0; i < size; ++i) {
free_cstring(arr[i]);
free_c_string(arr[i]);
arr[i] = nullptr;
}
delete[] arr;
}
// !! AUTO-GENERATED-START CPP_TYPES
/** VelopackLocator provides some utility functions for locating the current app important paths (eg. path to packages, update binary, and so forth). */
struct VelopackLocatorConfig {
/** The root directory of the current app. */
std::string RootAppDir;
/** The path to the Update.exe binary. */
std::string UpdateExePath;
/** The path to the packages' directory. */
std::string PackagesDir;
/** The current app manifest. */
std::string ManifestPath;
/** The directory containing the application's user binaries. */
std::string CurrentBinaryDir;
/** Whether the current application is portable or installed. */
bool IsPortable;
};
static inline std::optional<VelopackLocatorConfig> to_cpp_VelopackLocatorConfig(const vpkc_locator_config_t* dto) {
if (dto == nullptr) { return std::nullopt; }
return std::optional<VelopackLocatorConfig>({
to_cpp_string(dto->RootAppDir).value(),
to_cpp_string(dto->UpdateExePath).value(),
to_cpp_string(dto->PackagesDir).value(),
to_cpp_string(dto->ManifestPath).value(),
to_cpp_string(dto->CurrentBinaryDir).value(),
dto->IsPortable,
});
}
static inline std::vector<VelopackLocatorConfig> to_cpp_VelopackLocatorConfig_vec(const vpkc_locator_config_t* const* arr, size_t c) {
if (arr == nullptr || c < 1) { return std::vector<VelopackLocatorConfig>(); }
std::vector<VelopackLocatorConfig> result;
result.reserve(c);
for (size_t i = 0; i < c; ++i) {
auto dto = arr[i];
if (dto == nullptr) { continue; }
result.push_back(to_cpp_VelopackLocatorConfig(dto).value());
}
return result;
}
static inline vpkc_locator_config_t* alloc_c_VelopackLocatorConfig(const VelopackLocatorConfig* dto) {
if (dto == nullptr) { return nullptr; }
vpkc_locator_config_t* obj = new vpkc_locator_config_t{};
obj->RootAppDir = alloc_c_string(dto->RootAppDir);
obj->UpdateExePath = alloc_c_string(dto->UpdateExePath);
obj->PackagesDir = alloc_c_string(dto->PackagesDir);
obj->ManifestPath = alloc_c_string(dto->ManifestPath);
obj->CurrentBinaryDir = alloc_c_string(dto->CurrentBinaryDir);
obj->IsPortable = dto->IsPortable;
return obj;
}
static inline vpkc_locator_config_t* alloc_c_VelopackLocatorConfig(const std::optional<VelopackLocatorConfig>& dto) {
if (!dto.has_value()) { return nullptr; }
return alloc_c_VelopackLocatorConfig(dto.value());
}
static inline vpkc_locator_config_t** alloc_c_VelopackLocatorConfig_vec(const std::vector<VelopackLocatorConfig>& dto, size_t* count) {
if (dto.empty()) {
*count = 0;
return nullptr;
}
*count = dto.size();
vpkc_locator_config_t** arr = new vpkc_locator_config_t*[*count];
for (size_t i = 0; i < *count; ++i) {
arr[i] = alloc_c_VelopackLocatorConfig(dto[i]);
}
return arr;
}
static inline void free_c_VelopackLocatorConfig(vpkc_locator_config_t* obj) {
if (obj == nullptr) { return; }
free_c_string(obj->RootAppDir);
free_c_string(obj->UpdateExePath);
free_c_string(obj->PackagesDir);
free_c_string(obj->ManifestPath);
free_c_string(obj->CurrentBinaryDir);
delete obj;
}
static inline void free_c_VelopackLocatorConfig_vec(vpkc_locator_config_t** arr, size_t count) {
if (arr == nullptr || count < 1) { return; }
for (size_t i = 0; i < count; ++i) {
free_c_VelopackLocatorConfig(arr[i]);
}
delete[] arr;
}
/** An individual Velopack asset, could refer to an asset on-disk or in a remote package feed. */
struct VelopackAsset {
/** The name or Id of the package containing this release. */
std::string PackageId;
/** The version of this release. */
std::string Version;
/** The type of asset (eg. "Full" or "Delta"). */
std::string Type;
/** The filename of the update package containing this release. */
std::string FileName;
/** The SHA1 checksum of the update package containing this release. */
std::string SHA1;
/** The SHA256 checksum of the update package containing this release. */
std::string SHA256;
/** The size in bytes of the update package containing this release. */
uint64_t Size;
/** The release notes in markdown format, as passed to Velopack when packaging the release. This may be an empty string. */
std::string NotesMarkdown;
/** The release notes in HTML format, transformed from Markdown when packaging the release. This may be an empty string. */
std::string NotesHtml;
};
static inline std::optional<VelopackAsset> to_cpp_VelopackAsset(const vpkc_asset_t* dto) {
if (dto == nullptr) { return std::nullopt; }
return std::optional<VelopackAsset>({
to_cpp_string(dto->PackageId).value(),
to_cpp_string(dto->Version).value(),
to_cpp_string(dto->Type).value(),
to_cpp_string(dto->FileName).value(),
to_cpp_string(dto->SHA1).value(),
to_cpp_string(dto->SHA256).value(),
dto->Size,
to_cpp_string(dto->NotesMarkdown).value(),
to_cpp_string(dto->NotesHtml).value(),
});
}
static inline std::vector<VelopackAsset> to_cpp_VelopackAsset_vec(const vpkc_asset_t* const* arr, size_t c) {
if (arr == nullptr || c < 1) { return std::vector<VelopackAsset>(); }
std::vector<VelopackAsset> result;
result.reserve(c);
for (size_t i = 0; i < c; ++i) {
auto dto = arr[i];
if (dto == nullptr) { continue; }
result.push_back(to_cpp_VelopackAsset(dto).value());
}
return result;
}
static inline vpkc_asset_t* alloc_c_VelopackAsset(const VelopackAsset* dto) {
if (dto == nullptr) { return nullptr; }
vpkc_asset_t* obj = new vpkc_asset_t{};
obj->PackageId = alloc_c_string(dto->PackageId);
obj->Version = alloc_c_string(dto->Version);
obj->Type = alloc_c_string(dto->Type);
obj->FileName = alloc_c_string(dto->FileName);
obj->SHA1 = alloc_c_string(dto->SHA1);
obj->SHA256 = alloc_c_string(dto->SHA256);
obj->Size = dto->Size;
obj->NotesMarkdown = alloc_c_string(dto->NotesMarkdown);
obj->NotesHtml = alloc_c_string(dto->NotesHtml);
return obj;
}
static inline vpkc_asset_t* alloc_c_VelopackAsset(const std::optional<VelopackAsset>& dto) {
if (!dto.has_value()) { return nullptr; }
return alloc_c_VelopackAsset(dto.value());
}
static inline vpkc_asset_t** alloc_c_VelopackAsset_vec(const std::vector<VelopackAsset>& dto, size_t* count) {
if (dto.empty()) {
*count = 0;
return nullptr;
}
*count = dto.size();
vpkc_asset_t** arr = new vpkc_asset_t*[*count];
for (size_t i = 0; i < *count; ++i) {
arr[i] = alloc_c_VelopackAsset(dto[i]);
}
return arr;
}
static inline void free_c_VelopackAsset(vpkc_asset_t* obj) {
if (obj == nullptr) { return; }
free_c_string(obj->PackageId);
free_c_string(obj->Version);
free_c_string(obj->Type);
free_c_string(obj->FileName);
free_c_string(obj->SHA1);
free_c_string(obj->SHA256);
free_c_string(obj->NotesMarkdown);
free_c_string(obj->NotesHtml);
delete obj;
}
static inline void free_c_VelopackAsset_vec(vpkc_asset_t** arr, size_t count) {
if (arr == nullptr || count < 1) { return; }
for (size_t i = 0; i < count; ++i) {
free_c_VelopackAsset(arr[i]);
}
delete[] arr;
}
/** Holds information about the current version and pending updates, such as how many there are, and access to release notes. */
struct UpdateInfo {
/** The available version that we are updating to. */
VelopackAsset TargetFullRelease;
/** The base release that this update is based on. This is only available if the update is a delta update. */
std::optional<VelopackAsset> BaseRelease;
/** The list of delta updates that can be applied to the base version to get to the target version. */
std::vector<VelopackAsset> DeltasToTarget;
/**
* True if the update is a version downgrade or lateral move (such as when switching channels to the same version number).
* In this case, only full updates are allowed, and any local packages on disk newer than the downloaded version will be
* deleted.
*/
bool IsDowngrade;
};
static inline std::optional<UpdateInfo> to_cpp_UpdateInfo(const vpkc_update_info_t* dto) {
if (dto == nullptr) { return std::nullopt; }
return std::optional<UpdateInfo>({
to_cpp_VelopackAsset(dto->TargetFullRelease).value(),
to_cpp_VelopackAsset(dto->BaseRelease),
to_cpp_VelopackAsset_vec(dto->DeltasToTarget, dto->DeltasToTargetCount),
dto->IsDowngrade,
});
}
static inline std::vector<UpdateInfo> to_cpp_UpdateInfo_vec(const vpkc_update_info_t* const* arr, size_t c) {
if (arr == nullptr || c < 1) { return std::vector<UpdateInfo>(); }
std::vector<UpdateInfo> result;
result.reserve(c);
for (size_t i = 0; i < c; ++i) {
auto dto = arr[i];
if (dto == nullptr) { continue; }
result.push_back(to_cpp_UpdateInfo(dto).value());
}
return result;
}
static inline vpkc_update_info_t* alloc_c_UpdateInfo(const UpdateInfo* dto) {
if (dto == nullptr) { return nullptr; }
vpkc_update_info_t* obj = new vpkc_update_info_t{};
obj->TargetFullRelease = alloc_c_VelopackAsset(dto->TargetFullRelease);
obj->BaseRelease = alloc_c_VelopackAsset(dto->BaseRelease);
obj->DeltasToTarget = alloc_c_VelopackAsset_vec(dto->DeltasToTarget, &obj->DeltasToTargetCount);
obj->IsDowngrade = dto->IsDowngrade;
return obj;
}
static inline vpkc_update_info_t* alloc_c_UpdateInfo(const std::optional<UpdateInfo>& dto) {
if (!dto.has_value()) { return nullptr; }
return alloc_c_UpdateInfo(dto.value());
}
static inline vpkc_update_info_t** alloc_c_UpdateInfo_vec(const std::vector<UpdateInfo>& dto, size_t* count) {
if (dto.empty()) {
*count = 0;
return nullptr;
}
*count = dto.size();
vpkc_update_info_t** arr = new vpkc_update_info_t*[*count];
for (size_t i = 0; i < *count; ++i) {
arr[i] = alloc_c_UpdateInfo(dto[i]);
}
return arr;
}
static inline void free_c_UpdateInfo(vpkc_update_info_t* obj) {
if (obj == nullptr) { return; }
free_c_VelopackAsset(obj->TargetFullRelease);
free_c_VelopackAsset(obj->BaseRelease);
free_c_VelopackAsset_vec(obj->DeltasToTarget, obj->DeltasToTargetCount);
delete obj;
}
static inline void free_c_UpdateInfo_vec(vpkc_update_info_t** arr, size_t count) {
if (arr == nullptr || count < 1) { return; }
for (size_t i = 0; i < count; ++i) {
free_c_UpdateInfo(arr[i]);
}
delete[] arr;
}
/** Options to customise the behaviour of UpdateManager. */
struct UpdateOptions {
/**
* Allows UpdateManager to update to a version that's lower than the current version (i.e. downgrading).
* This could happen if a release has bugs and was retracted from the release feed, or if you're using
* ExplicitChannel to switch channels to another channel where the latest version on that
* channel is lower than the current version.
*/
bool AllowVersionDowngrade;
/**
* **This option should usually be left None**.
* Overrides the default channel used to fetch updates.
* The default channel will be whatever channel was specified on the command line when building this release.
* For example, if the current release was packaged with '--channel beta', then the default channel will be 'beta'.
* This allows users to automatically receive updates from the same channel they installed from. This options
* allows you to explicitly switch channels, for example if the user wished to switch back to the 'stable' channel
* without having to reinstall the application.
*/
std::optional<std::string> ExplicitChannel;
/**
* Sets the maximum number of deltas to consider before falling back to a full update.
* The default is 10. Set to a negative number (eg. -1) to disable deltas.
*/
int32_t MaximumDeltasBeforeFallback;
};
static inline std::optional<UpdateOptions> to_cpp_UpdateOptions(const vpkc_update_options_t* dto) {
if (dto == nullptr) { return std::nullopt; }
return std::optional<UpdateOptions>({
dto->AllowVersionDowngrade,
to_cpp_string(dto->ExplicitChannel),
dto->MaximumDeltasBeforeFallback,
});
}
static inline std::vector<UpdateOptions> to_cpp_UpdateOptions_vec(const vpkc_update_options_t* const* arr, size_t c) {
if (arr == nullptr || c < 1) { return std::vector<UpdateOptions>(); }
std::vector<UpdateOptions> result;
result.reserve(c);
for (size_t i = 0; i < c; ++i) {
auto dto = arr[i];
if (dto == nullptr) { continue; }
result.push_back(to_cpp_UpdateOptions(dto).value());
}
return result;
}
static inline vpkc_update_options_t* alloc_c_UpdateOptions(const UpdateOptions* dto) {
if (dto == nullptr) { return nullptr; }
vpkc_update_options_t* obj = new vpkc_update_options_t{};
obj->AllowVersionDowngrade = dto->AllowVersionDowngrade;
obj->ExplicitChannel = alloc_c_string(dto->ExplicitChannel);
obj->MaximumDeltasBeforeFallback = dto->MaximumDeltasBeforeFallback;
return obj;
}
static inline vpkc_update_options_t* alloc_c_UpdateOptions(const std::optional<UpdateOptions>& dto) {
if (!dto.has_value()) { return nullptr; }
return alloc_c_UpdateOptions(dto.value());
}
static inline vpkc_update_options_t** alloc_c_UpdateOptions_vec(const std::vector<UpdateOptions>& dto, size_t* count) {
if (dto.empty()) {
*count = 0;
return nullptr;
}
*count = dto.size();
vpkc_update_options_t** arr = new vpkc_update_options_t*[*count];
for (size_t i = 0; i < *count; ++i) {
arr[i] = alloc_c_UpdateOptions(dto[i]);
}
return arr;
}
static inline void free_c_UpdateOptions(vpkc_update_options_t* obj) {
if (obj == nullptr) { return; }
free_c_string(obj->ExplicitChannel);
delete obj;
}
static inline void free_c_UpdateOptions_vec(vpkc_update_options_t** arr, size_t count) {
if (arr == nullptr || count < 1) { return; }
for (size_t i = 0; i < count; ++i) {
free_c_UpdateOptions(arr[i]);
}
delete[] arr;
}
// !! AUTO-GENERATED-END CPP_TYPES
/**
* VelopackApp helps you to handle app activation events correctly.
* This should be used as early as possible in your application startup code.
@@ -273,9 +483,10 @@ public:
* Override the command line arguments used by VelopackApp. (by default this is env::args().skip(1))
*/
VelopackApp& SetArgs(const std::vector<std::string>& args) {
char** pArgs = allocate_cstring_array(args);
vpkc_app_set_args(pArgs, args.size());
free_cstring_array(pArgs, args.size());
size_t c;
char** pArgs = alloc_c_string_vec(args, &c);
vpkc_app_set_args(pArgs, c);
free_c_string_vec(pArgs, c);
return *this;
};
@@ -283,8 +494,9 @@ public:
* VelopackLocator provides some utility functions for locating the current app important paths (eg. path to packages, update binary, and so forth).
*/
VelopackApp& SetLocator(const VelopackLocatorConfig& locator) {
vpkc_locator_config_t vpkc_locator = to_c(locator);
vpkc_app_set_locator(&vpkc_locator);
vpkc_locator_config_t* vpkc_locator = alloc_c_VelopackLocatorConfig(locator);
vpkc_app_set_locator(vpkc_locator);
free_c_VelopackLocatorConfig(vpkc_locator);
return *this;
};
@@ -360,7 +572,7 @@ public:
/**
* Progress callback function. Call with values between 0 and 100 inclusive.
*/
typedef std::function<void(size_t)> vpkc_progress_send_t;
typedef std::function<void(int16_t)> vpkc_progress_send_t;
/**
* Abstract class for retrieving release feeds and downloading assets. You should subclass this and
@@ -385,16 +597,16 @@ public:
[](void* userData, const char* releasesName) {
IUpdateSource* source = reinterpret_cast<IUpdateSource*>(userData);
std::string json = source->GetReleaseFeed(releasesName);
return allocate_cstring(json);
return alloc_c_string(json);
},
[](void* userData, char* pszFeed) {
free_cstring(pszFeed);
free_c_string(pszFeed);
},
[](void* userData, const struct vpkc_asset_t *pAsset, const char* pszLocalPath, size_t progressCallbackId) {
IUpdateSource* source = reinterpret_cast<IUpdateSource*>(userData);
VelopackAsset asset = to_cpp(*pAsset);
std::string localPath = to_cppstring(pszLocalPath);
std::function<void(size_t)> progress_callback = [progressCallbackId](size_t progress) {
VelopackAsset asset = to_cpp_VelopackAsset(pAsset).value();
std::string localPath = to_cpp_string(pszLocalPath).value();
std::function<void(int16_t)> progress_callback = [progressCallbackId](int16_t progress) {
vpkc_source_report_progress(progressCallbackId, progress);
};
return source->DownloadReleaseEntry(asset, localPath, progress_callback);
@@ -457,21 +669,12 @@ public:
* @param locator Override the default locator configuration (usually used for testing / mocks).
*/
UpdateManager(const std::string& urlOrPath, const UpdateOptions* options = nullptr, const VelopackLocatorConfig* locator = nullptr) {
vpkc_update_options_t vpkc_options;
vpkc_update_options_t* pOptions = nullptr;
if (options != nullptr) {
vpkc_options = to_c(*options);
pOptions = &vpkc_options;
}
vpkc_locator_config_t vpkc_locator;
vpkc_locator_config_t* pLocator = nullptr;
if (locator != nullptr) {
vpkc_locator = to_c(*locator);
pLocator = &vpkc_locator;
}
if (!vpkc_new_update_manager(urlOrPath.c_str(), pOptions, pLocator, &m_pManager)) {
vpkc_update_options_t* pOptions = alloc_c_UpdateOptions(options);
vpkc_locator_config_t* pLocator = alloc_c_VelopackLocatorConfig(locator);
bool result = vpkc_new_update_manager(urlOrPath.c_str(), pOptions, pLocator, &m_pManager);
free_c_UpdateOptions(pOptions);
free_c_VelopackLocatorConfig(pLocator);
if (!result) {
throw_last_error();
}
};
@@ -484,23 +687,14 @@ public:
*/
template <typename T, typename = std::enable_if_t<std::is_base_of_v<IUpdateSource, T>>>
UpdateManager(std::unique_ptr<T> pUpdateSource, const UpdateOptions* options = nullptr, const VelopackLocatorConfig* locator = nullptr) {
vpkc_update_options_t vpkc_options;
vpkc_update_options_t* pOptions = nullptr;
if (options != nullptr) {
vpkc_options = to_c(*options);
pOptions = &vpkc_options;
}
vpkc_locator_config_t vpkc_locator;
vpkc_locator_config_t* pLocator = nullptr;
if (locator != nullptr) {
vpkc_locator = to_c(*locator);
pLocator = &vpkc_locator;
}
vpkc_update_options_t* pOptions = alloc_c_UpdateOptions(options);
vpkc_locator_config_t* pLocator = alloc_c_VelopackLocatorConfig(locator);
m_pUpdateSource = std::unique_ptr<IUpdateSource>(static_cast<IUpdateSource*>(pUpdateSource.release()));
vpkc_update_source_t* pSource = m_pUpdateSource->m_pSource;
if (!vpkc_new_update_manager_with_source(pSource, pOptions, pLocator, &m_pManager)) {
bool result = vpkc_new_update_manager_with_source(pSource, pOptions, pLocator, &m_pManager);
free_c_UpdateOptions(pOptions);
free_c_VelopackLocatorConfig(pLocator);
if (!result) {
throw_last_error();
}
};
@@ -545,10 +739,10 @@ public:
* You can pass the UpdateInfo object to waitExitThenApplyUpdate to apply the update.
*/
std::optional<VelopackAsset> UpdatePendingRestart() noexcept {
vpkc_asset_t asset;
vpkc_asset_t* asset;
if (vpkc_update_pending_restart(m_pManager, &asset)) {
VelopackAsset cpp_asset = to_cpp(asset);
vpkc_free_asset(&asset);
VelopackAsset cpp_asset = to_cpp_VelopackAsset(asset).value();
vpkc_free_asset(asset);
return cpp_asset;
}
return std::nullopt;
@@ -559,7 +753,7 @@ public:
* UpdateInfo object containing the latest available release, and any delta updates that can be applied if they are available.
*/
std::optional<UpdateInfo> CheckForUpdates() {
vpkc_update_info_t update;
vpkc_update_info_t* update;
vpkc_update_check_t result = vpkc_check_for_updates(m_pManager, &update);
switch (result) {
case vpkc_update_check_t::UPDATE_ERROR:
@@ -569,8 +763,8 @@ public:
case vpkc_update_check_t::REMOTE_IS_EMPTY:
return std::nullopt;
case vpkc_update_check_t::UPDATE_AVAILABLE:
UpdateInfo cpp_info = to_cpp(update);
vpkc_free_update_info(&update);
UpdateInfo cpp_info = to_cpp_UpdateInfo(update).value();
vpkc_free_update_info(update);
return cpp_info;
}
return std::nullopt;
@@ -585,8 +779,10 @@ public:
* packages, this method will fall back to downloading the full version of the update.
*/
void DownloadUpdates(const UpdateInfo& update, vpkc_progress_callback_t progress = nullptr, void* pUserData = 0) {
vpkc_update_info_t vpkc_update = to_c(update);
if (!vpkc_download_updates(m_pManager, &vpkc_update, progress, pUserData)) {
vpkc_update_info_t* vpkc_update = alloc_c_UpdateInfo(update);
bool result = vpkc_download_updates(m_pManager, vpkc_update, progress, pUserData);
free_c_UpdateInfo(vpkc_update);
if (!result) {
throw_last_error();
}
};
@@ -606,11 +802,12 @@ public:
* optionally restart your app. The updater will only wait for 60 seconds before giving up.
*/
void WaitExitThenApplyUpdates(const VelopackAsset& asset, bool silent = false, bool restart = true, std::vector<std::string> restartArgs = {}) {
char** pRestartArgs = allocate_cstring_array(restartArgs);
vpkc_asset_t vpkc_asset = to_c(asset);
bool result = vpkc_wait_exit_then_apply_updates(m_pManager, &vpkc_asset, silent, restart, pRestartArgs, restartArgs.size());
free_cstring_array(pRestartArgs, restartArgs.size());
size_t cRestartArgs;
char** pRestartArgs = alloc_c_string_vec(restartArgs, &cRestartArgs);
vpkc_asset_t* vpkc_asset = alloc_c_VelopackAsset(asset);
bool result = vpkc_wait_exit_then_apply_updates(m_pManager, vpkc_asset, silent, restart, pRestartArgs, cRestartArgs);
free_c_string_vec(pRestartArgs, cRestartArgs);
free_c_VelopackAsset(vpkc_asset);
if (!result) {
throw_last_error();
}
@@ -623,11 +820,12 @@ public:
* If waitPid is 0, the updater will not wait for any process to exit before applying updates (Not Recommended).
*/
void UnsafeApplyUpdates(const VelopackAsset& asset, bool silent, uint32_t waitPid, bool restart, std::vector<std::string> restartArgs) {
char** pRestartArgs = allocate_cstring_array(restartArgs);
vpkc_asset_t vpkc_asset = to_c(asset);
bool result = vpkc_unsafe_apply_updates(m_pManager, &vpkc_asset, silent, waitPid, restart, pRestartArgs, restartArgs.size());
free_cstring_array(pRestartArgs, restartArgs.size());
size_t cRestartArgs;
char** pRestartArgs = alloc_c_string_vec(restartArgs, &cRestartArgs);
vpkc_asset_t* vpkc_asset = alloc_c_VelopackAsset(asset);
bool result = vpkc_unsafe_apply_updates(m_pManager, vpkc_asset, silent, waitPid, restart, pRestartArgs, cRestartArgs);
free_c_string_vec(pRestartArgs, cRestartArgs);
free_c_VelopackAsset(vpkc_asset);
if (!result) {
throw_last_error();
}

View File

@@ -223,7 +223,7 @@ pub struct vpkc_asset_t {
pub PackageId: *mut c_char,
/// The version of this release.
pub Version: *mut c_char,
/// The type of asset (eg. &quot;Full&quot; or &quot;Delta&quot;).
/// The type of asset (eg. "Full" or "Delta").
pub Type: *mut c_char,
/// The filename of the update package containing this release.
pub FileName: *mut c_char,
@@ -347,8 +347,8 @@ pub struct vpkc_update_info_t {
/// The number of elements in the DeltasToTarget array.
pub DeltasToTargetCount: size_t,
/// True if the update is a version downgrade or lateral move (such as when switching channels to the same version number).
/// In this case, only full updates are allowed, and any local packages on disk newer than the downloaded version will be
/// deleted.
/// In this case, only full updates are allowed, and any local packages on disk newer than the downloaded version will be
/// deleted.
pub IsDowngrade: bool,
}
@@ -437,20 +437,20 @@ pub unsafe fn free_UpdateInfo_vec(obj: *mut *mut vpkc_update_info_t, count: size
/// Options to customise the behaviour of UpdateManager.
pub struct vpkc_update_options_t {
/// Allows UpdateManager to update to a version that's lower than the current version (i.e. downgrading).
/// This could happen if a release has bugs and was retracted from the release feed, or if you're using
/// ExplicitChannel to switch channels to another channel where the latest version on that
/// channel is lower than the current version.
/// This could happen if a release has bugs and was retracted from the release feed, or if you're using
/// ExplicitChannel to switch channels to another channel where the latest version on that
/// channel is lower than the current version.
pub AllowVersionDowngrade: bool,
/// **This option should usually be left None**.
/// Overrides the default channel used to fetch updates.
/// The default channel will be whatever channel was specified on the command line when building this release.
/// For example, if the current release was packaged with '--channel beta', then the default channel will be 'beta'.
/// This allows users to automatically receive updates from the same channel they installed from. This options
/// allows you to explicitly switch channels, for example if the user wished to switch back to the 'stable' channel
/// without having to reinstall the application.
/// Overrides the default channel used to fetch updates.
/// The default channel will be whatever channel was specified on the command line when building this release.
/// For example, if the current release was packaged with '--channel beta', then the default channel will be 'beta'.
/// This allows users to automatically receive updates from the same channel they installed from. This options
/// allows you to explicitly switch channels, for example if the user wished to switch back to the 'stable' channel
/// without having to reinstall the application.
pub ExplicitChannel: *mut c_char,
/// Sets the maximum number of deltas to consider before falling back to a full update.
/// The default is 10. Set to a negative number (eg. -1) to disable deltas.
/// The default is 10. Set to a negative number (eg. -1) to disable deltas.
pub MaximumDeltasBeforeFallback: i32,
}

View File

@@ -1,61 +0,0 @@
using System.Text;
public class IndentStringBuilder
{
private StringBuilder _sb = new();
int _indent = 0;
public void AppendLine()
{
_sb.AppendLine();
}
public void AppendLine(string text)
{
AppendIndent();
_sb.AppendLine(text);
}
public void AppendDocComment(string comment)
{
if (comment != null) {
foreach (var line in comment.ReplaceLineEndings("\n").Split('\n')) {
AppendLine($"/// {line}");
}
}
}
private void AppendIndent()
{
_sb.Append(' ', _indent * 4);
}
public IDisposable Indent()
{
_indent++;
return new IndentDisposable(this);
}
private void RemoveIndent()
{
_indent--;
}
public override string ToString()
{
return _sb.ToString();
}
private class IndentDisposable(IndentStringBuilder isb) : IDisposable
{
private bool _disposed = false;
public void Dispose()
{
if (!_disposed) {
isb.RemoveIndent();
_disposed = true;
}
}
}
}

View File

@@ -17,13 +17,6 @@ string[] desiredStructs = [
"VelopackLocatorConfig",
];
Dictionary<string, string> basic_libc_names = new() {
{ "VelopackAsset", "vpkc_asset_t" },
{ "UpdateInfo", "vpkc_update_info_t" },
{ "UpdateOptions", "vpkc_update_options_t" },
{ "VelopackLocatorConfig", "vpkc_locator_config_t" },
};
List<RustStruct> availableStructs = new();
string[] searchStrings = desiredStructs.Select(s => "struct " + s + " {").ToArray();
@@ -48,79 +41,44 @@ if (desiredStructs.Length != availableStructs.Count) {
return -1;
}
// rust bridge code
// string rustCppLib = Path.Combine(libcppDir, "src", "lib.rs");
string rustTypes = Path.Combine(libcppDir, "src", "types.rs");
//string rustCppMap = Path.Combine(libcppDir, "src", "map.rs");
string rustCppInclude = Path.Combine(libcppDir, "include", "Velopack.hpp");
//string rustBridgeC = Path.Combine(libcppDir, "src", "bridge.cc");
//Console.WriteLine("Generating bridge dtos");
//var sbBridgeDto = new IndentStringBuilder();
//foreach(var rs in availableStructs) {
// Templates.WriteBridgeDto(desiredStructs, sbBridgeDto, rs);
//}
//Console.WriteLine("Generating bridge to core mappings");
//var sbBridgeMapping = new IndentStringBuilder();
//foreach(var rs in availableStructs) {
// Templates.WriteBridgeToCoreMapping(desiredStructs, sbBridgeMapping, rs);
//}
// Console.WriteLine("Generating C types");
// var cTypes = new IndentStringBuilder();
// cTypes.AppendLine();
// foreach (var rs in availableStructs) {
// Templates.WriteBasicC(basic_libc_names, cTypes, rs);
// }
Console.WriteLine("Generating C++ types");
var cppTypes = new IndentStringBuilder();
cppTypes.AppendLine();
foreach (var rs in availableStructs) {
Templates.WriteCPlusPlus(basic_libc_names, cppTypes, rs);
}
foreach (var rs in availableStructs) {
Templates.WriteC2CPPMapping(basic_libc_names, cppTypes, rs);
}
Console.WriteLine("Generating Rust-C types");
//var rustCTypes = new IndentStringBuilder();
//foreach (var rs in availableStructs) {
// Templates.WriteRustCRepr(basic_libc_names, rustCTypes, rs);
//}
var rustCTypesTemplate = Handlebars.Compile(File.ReadAllText(Path.Combine(templatesDir, "rust_struct.hbs")));
Handlebars.RegisterHelper("indent", (writer, context, args) => {
var comment = (string) context[(string) args[0]];
var indent = (string) args[1];
writer.WriteSafeString(comment.PrefixEveryLine(indent));
writer.WriteSafeString("\n");
});
var types = new List<TypeMap>() {
new TypeMap("VelopackAsset", "vpkc_asset_t", "vpkc_asset_t", false),
new TypeMap("UpdateInfo", "vpkc_update_info_t", "vpkc_update_info_t", false),
new TypeMap("UpdateOptions", "vpkc_update_options_t", "vpkc_update_options_t", false),
new TypeMap("VelopackLocatorConfig", "vpkc_locator_config_t", "vpkc_locator_config_t", false),
new TypeMap("String", "char", "c_char", false),
new TypeMap("PathBuf", "char", "c_char", false),
new TypeMap("bool", "bool", "bool", true),
new TypeMap("i32", "int32_t", "i32", true),
new TypeMap("i64", "int64_t", "i64", true),
new TypeMap("u32", "uint32_t", "u32", true),
new TypeMap("u64", "uint64_t", "u64", true),
TypeMap.RustStruct("VelopackAsset", "vpkc_asset_t"),
TypeMap.RustStruct("UpdateInfo", "vpkc_update_info_t"),
TypeMap.RustStruct("UpdateOptions", "vpkc_update_options_t"),
TypeMap.RustStruct("VelopackLocatorConfig", "vpkc_locator_config_t"),
TypeMap.SystemType("String", "char", "string", "c_char"),
TypeMap.SystemType("PathBuf", "char", "string", "c_char"),
TypeMap.Primitive("bool", "bool"),
TypeMap.Primitive("i32", "int32_t"),
TypeMap.Primitive("i64", "int64_t"),
TypeMap.Primitive("u32", "uint32_t"),
TypeMap.Primitive("u64", "uint64_t"),
}.ToDictionary(v => v.rustType, v => v);
var data = availableStructs.Select(s => new RustStruct_Struct {
rust_comment = s.DocComment.PrefixEveryLine("/// "),
var handlebarData = availableStructs.Select(s => new RustStruct_Struct {
rust_comment = s.DocComment.ToRustComment(),
cpp_comment = s.DocComment.ToCppComment(),
struct_rust_name = s.Name,
struct_c_name = types[s.Name].interopType,
fields = s.Fields.Select(f => {
var isString = types[f.Type].rustType == "PathBuf" || types[f.Type].rustType == "String";
var field = new RustStruct_Field {
rust_comment = f.DocComment.PrefixEveryLine("/// "),
rust_comment = f.DocComment.ToRustComment(),
cpp_comment = f.DocComment.ToCppComment(),
field_name = f.Name,
field_optional = f.Optional,
field_vector = f.Vec,
//field_add_pointer = f.Optional && !f.Vec && !isString,
//field_requires_ref = !isString && !f.Vec && !f.Optional,
//field_string = isString,
field_rust_type = f.Type,
field_c_type = types[f.Type].interopType,
field_cpp_type = types[f.Type].cppType,
field_system = types[f.Type].system,
field_primitive = types[f.Type].primitive,
field_normal = !f.Vec && !types[f.Type].primitive,
};
@@ -128,32 +86,72 @@ var data = availableStructs.Select(s => new RustStruct_Struct {
}).ToArray(),
}).ToArray();
var rustCTypes = rustCTypesTemplate(data);
string rustTypes = Path.Combine(libcppDir, "src", "types.rs");
var rustCTypesTemplate = Handlebars.Compile(File.ReadAllText(Path.Combine(templatesDir, "rust_types.hbs")));
var rustCTypes = rustCTypesTemplate(handlebarData);
Console.WriteLine();
//Console.WriteLine("Generating C to bridge mappings");
//var cToBridgeMapping = new IndentStringBuilder();
//foreach (var rs in availableStructs) {
// Templates.WriteCBridgeMapping(basic_libc_names, cToBridgeMapping, rs);
//}
string rustCppInclude = Path.Combine(libcppDir, "include", "Velopack.hpp");
var cppTypesTemplate = Handlebars.Compile(File.ReadAllText(Path.Combine(templatesDir, "cpp_mapping.hbs")));
var cppTypes = cppTypesTemplate(handlebarData);
Console.WriteLine("Writing all to file");
//Util.ReplaceTextInFile(rustCppLib, "BRIDGE_DTOS", sbBridgeDto.ToString());
//Util.ReplaceTextInFile(rustCppMap, "CORE_MAPPING", sbBridgeMapping.ToString());
Util.ReplaceTextInFile(rustTypes, "RUST_TYPES", rustCTypes.ToString());
// Util.ReplaceTextInFile(rustCppInclude, "C_TYPES", cTypes.ToString());
Util.ReplaceTextInFile(rustCppInclude, "CPP_TYPES", cppTypes.ToString());
//Util.ReplaceTextInFile(rustBridgeC, "BRIDGE_MAPPING", cToBridgeMapping.ToString());
Util.ReplaceTextInFile(rustTypes, "RUST_TYPES", rustCTypes.ToString().ReplaceLineEndings("\n"));
Util.ReplaceTextInFile(rustCppInclude, "CPP_TYPES", cppTypes.ToString().ReplaceLineEndings("\n"));
return 0;
record struct TypeMap(string rustType, string cType, string interopType, bool primitive);
class TypeMap
{
public string rustType;
public string cType;
public string cppType;
public string interopType;
public bool primitive;
public bool system;
public static TypeMap RustStruct(string rustName, string cType)
{
return new TypeMap() {
rustType = rustName,
cType = cType,
cppType = rustName,
interopType = cType,
primitive = false,
system = false,
};
}
public static TypeMap Primitive(string rustName, string cType)
{
return new TypeMap() {
rustType = rustName,
cType = cType,
cppType = cType,
interopType = rustName,
primitive = true,
system = false,
};
}
public static TypeMap SystemType(string rustName, string cType, string cppType, string interopType)
{
return new TypeMap() {
rustType = rustName,
cType = cType,
cppType = cppType,
interopType = interopType,
primitive = false,
system = true,
};
}
}
class RustStruct_Struct
{
public string struct_c_name;
public string struct_rust_name;
public string rust_comment;
public string cpp_comment;
public RustStruct_Field[] fields;
}
@@ -161,14 +159,13 @@ class RustStruct_Field
{
public string field_name;
public string field_c_type;
public string field_cpp_type;
public string field_rust_type;
public bool field_primitive;
public bool field_optional;
public bool field_vector;
//public bool field_prefix;
//public bool field_add_pointer;
//public bool field_requires_ref;
public bool field_system;
public bool field_normal;
//public bool field_string;
public string rust_comment;
public string cpp_comment;
}

View File

@@ -1,449 +0,0 @@
public static class Templates
{
private static string GetBasicCType(Dictionary<string, string> nameMap, string rustType)
{
switch (rustType) {
case "PathBuf":
case "String":
return "char*";
case "bool":
return "bool";
case "i32":
return "int64_t";
case "i64":
return "int64_t";
case "u32":
return "uint32_t";
case "u64":
return "uint64_t";
default:
if (nameMap.TryGetValue(rustType, out var type)) {
return type;
}
throw new NotSupportedException("Unsupported type for basic-c: " + rustType);
}
}
private static string GetBasicCTypeInRust(Dictionary<string, string> nameMap, string rustType)
{
switch (rustType) {
case "PathBuf":
case "String":
return "*mut c_char";
case "bool":
return "bool";
case "i32":
return "i32";
case "i64":
return "i64";
case "u32":
return "u32";
case "u64":
return "u64";
default:
if (nameMap.TryGetValue(rustType, out var type)) {
return type;
}
throw new NotSupportedException("Unsupported type for rust-c: " + rustType);
}
}
public static void WriteRustCRepr(Dictionary<string, string> nameMap, IndentStringBuilder sb, RustStruct rs)
{
var cName = nameMap[rs.Name];
sb.AppendLine("#[rustfmt::skip]");
sb.AppendLine($"#[repr(C)]");
sb.AppendDocComment(rs.DocComment);
sb.AppendLine($"pub struct {cName} {{");
using (sb.Indent()) {
foreach (var field in rs.Fields) {
sb.AppendDocComment(field.DocComment);
var basicc = GetBasicCTypeInRust(nameMap, field.Type);
if (field.Vec) {
sb.AppendLine($"pub {field.Name}: *mut *mut {basicc},");
sb.AppendDocComment($"Count for {field.Name} Array");
sb.AppendLine($"pub {field.Name}Count: size_t,");
} else {
string opt = field.Optional && !basicc.StartsWith("*") ? "*mut " : "";
sb.AppendLine($"pub {field.Name}: {opt}{basicc},");
}
}
}
sb.AppendLine("}");
sb.AppendLine();
sb.AppendLine("#[rustfmt::skip]");
sb.AppendLine($"pub fn c_to_{rs.Name.ToLower()}(obj: &{cName}) -> {rs.Name} {{");
using (sb.Indent()) {
// sb.AppendLine($"let obj = unsafe {{ &*obj }};");
sb.AppendLine($"{rs.Name} {{");
using (sb.Indent()) {
foreach (var field in rs.Fields) {
if (field.Vec) {
sb.AppendLine($"{field.Name}: c_to_{field.Type.ToLower()}_vec(obj.{field.Name}, obj.{field.Name}Count),");
} else if (field.Optional || field.Type == "PathBuf" || field.Type == "String" || nameMap.ContainsKey(field.Type)) {
sb.AppendLine($"{field.Name}: c_to_{field.Type.ToLower()}{(field.Optional ? "_opt" : "")}({(nameMap.ContainsKey(field.Type) && !field.Optional ? "&" : "")}obj.{field.Name}),");
} else {
sb.AppendLine($"{field.Name}: obj.{field.Name},");
}
}
}
sb.AppendLine("}");
}
sb.AppendLine("}");
sb.AppendLine();
sb.AppendLine("#[rustfmt::skip]");
sb.AppendLine($"pub fn c_to_{rs.Name.ToLower()}_opt(obj: *mut {cName}) -> Option<{rs.Name}> {{");
using (sb.Indent()) {
sb.AppendLine("if obj.is_null() { return None; }");
sb.AppendLine($"Some(c_to_{rs.Name.ToLower()}(unsafe {{ &*obj }}))");
}
sb.AppendLine("}");
sb.AppendLine();
sb.AppendLine("#[rustfmt::skip]");
sb.AppendLine($"pub fn c_to_{rs.Name.ToLower()}_vec(obj: *mut *mut {cName}, count: size_t) -> Vec<{rs.Name}> {{");
using (sb.Indent()) {
sb.AppendLine("if obj.is_null() || count == 0 { return Vec::new(); }");
sb.AppendLine("let mut assets = Vec::with_capacity(count as usize);");
sb.AppendLine("for i in 0..count {");
using (sb.Indent()) {
sb.AppendLine("let ptr = unsafe { *obj.add(i as usize) };");
sb.AppendLine($"assets.push(c_to_{rs.Name.ToLower()}(unsafe {{ &*ptr }}));");
}
sb.AppendLine("}");
sb.AppendLine("assets");
}
sb.AppendLine("}");
sb.AppendLine();
sb.AppendLine("#[rustfmt::skip]");
sb.AppendLine($"pub unsafe fn allocate_{rs.Name.ToLower()}(dto: {rs.Name}, obj: *mut {cName}) {{");
using (sb.Indent()) {
sb.AppendLine("if obj.is_null() { return; }");
sb.AppendLine($"log::debug!(\"{cName} allocated\");");
foreach (var field in rs.Fields) {
if (field.Optional || field.Type == "PathBuf" || field.Type == "String" || nameMap.ContainsKey(field.Type)) {
if (field.Optional && !(field.Type == "PathBuf" || field.Type == "String")) {
sb.AppendLine($"if let Some(opt) = dto.{field.Name} {{");
using (sb.Indent()) {
var fieldcName = nameMap[field.Type];
sb.AppendLine($"let ptr = libc::malloc(size_of::<{fieldcName}>()) as *mut {fieldcName};");
sb.AppendLine($"(*obj).{field.Name} = ptr;");
sb.AppendLine($"allocate_{field.Type.ToLower()}(opt, ptr);");
}
sb.AppendLine($"}}");
} else {
sb.AppendLine($"allocate_{field.Type.ToLower()}(dto.{field.Name}, &mut (*obj).{field.Name});");
}
} else {
sb.AppendLine($"(*obj).{field.Name} = dto.{field.Name};");
}
}
}
sb.AppendLine("}");
sb.AppendLine();
sb.AppendLine("#[rustfmt::skip]");
sb.AppendLine($"pub unsafe fn free_{rs.Name.ToLower()}(obj: *mut {cName}) {{");
using (sb.Indent()) {
sb.AppendLine("if obj.is_null() { return; }");
sb.AppendLine($"log::debug!(\"{cName} freed\");");
foreach (var field in rs.Fields) {
if (field.Optional || field.Type == "PathBuf" || field.Type == "String" || nameMap.ContainsKey(field.Type)) {
if (field.Optional) {
sb.AppendLine($"free_{field.Type.ToLower()}((*obj).{field.Name});");
} else {
sb.AppendLine($"free_{field.Type.ToLower()}(&mut (*obj).{field.Name});");
}
}
}
}
sb.AppendLine("}");
sb.AppendLine();
}
private static string GetCPlusPlusType(string[] coreTypes, string rustType, bool optional, bool vec)
{
string type = rustType switch {
"PathBuf" => "std::string",
"String" => "std::string",
"bool" => "bool",
"i32" => "int64_t",
"i64" => "int64_t",
"u32" => "uint32_t",
"u64" => "uint64_t",
_ => coreTypes.Contains(rustType) ? rustType : throw new NotSupportedException("Unsupported type for c-plus-plus: " + rustType),
};
type = vec ? "std::vector<" + type + ">" : type;
type = optional ? "std::optional<" + type + ">" : type;
return type;
}
public static void WriteCBridgeMapping(Dictionary<string, string> nameMap, IndentStringBuilder sb, RustStruct rs)
{
var cName = nameMap[rs.Name];
sb.AppendLine($"static inline {rs.Name}Dto to_bridge({cName}* pDto) {{");
using (sb.Indent()) {
sb.AppendLine($"if (pDto == nullptr) {{ return {{}}; }}");
sb.AppendLine($"return {{");
using (sb.Indent()) {
foreach (var field in rs.Fields) {
string suffix = field.Optional ? "_opt" : "";
string type = field.Type == "PathBuf" ? "string" : field.Type.ToLower();
if (nameMap.ContainsKey(field.Type)) {
sb.AppendLine($"to_bridge{suffix}(&pDto->{field.Name}),");
} else if (type == "string") {
sb.AppendLine($"to_bridge{type}{suffix}(pDto->{field.Name}),");
} else {
sb.AppendLine($"pDto->{field.Name},");
}
}
}
sb.AppendLine("};");
}
sb.AppendLine($"}}");
sb.AppendLine();
sb.AppendLine($"static inline {rs.Name}DtoOption to_bridge_opt({cName}* pDto) {{");
using (sb.Indent()) {
sb.AppendLine($"{rs.Name}DtoOption opt;");
sb.AppendLine($"if (pDto == nullptr) {{");
using (sb.Indent()) {
sb.AppendLine($"opt.has_data = false;");
sb.AppendLine($"return opt;");
}
sb.AppendLine($"}}");
sb.AppendLine();
sb.AppendLine($"opt.has_data = true;");
sb.AppendLine($"opt.data = to_bridge(pDto);");
sb.AppendLine($"return opt;");
}
sb.AppendLine($"}}");
sb.AppendLine();
sb.AppendLine($"static inline void allocate_{rs.Name.ToLower()}({rs.Name}Dto bridgeDto, {cName}* pDto) {{");
using (sb.Indent()) {
sb.AppendLine($"if (pDto == nullptr) {{ return; }}");
foreach (var field in rs.Fields) {
string type = field.Type == "PathBuf" ? "string" : field.Type.ToLower();
string suffix = field.Optional ? "_opt" : "";
sb.AppendLine(
nameMap.ContainsKey(field.Type) || type == "string"
? $"allocate_{type}{suffix}(bridgeDto.{field.Name}, &pDto->{field.Name});"
: $"pDto->{field.Name} = bridgeDto.{field.Name};");
}
}
sb.AppendLine($"}}");
sb.AppendLine();
sb.AppendLine($"static inline void free_{rs.Name.ToLower()}({cName}* pDto) {{");
using (sb.Indent()) {
sb.AppendLine($"if (pDto == nullptr) {{ return; }}");
foreach (var field in rs.Fields) {
string type = field.Type == "PathBuf" ? "string" : field.Type.ToLower();
if (nameMap.ContainsKey(field.Type)) {
sb.AppendLine($"free_{type}(&pDto->{field.Name});");
} else if (type == "string") {
sb.AppendLine($"free(pDto->{field.Name});");
}
}
}
sb.AppendLine($"}}");
sb.AppendLine();
}
public static void WriteBasicC(Dictionary<string, string> nameMap, IndentStringBuilder sb, RustStruct rs)
{
sb.AppendDocComment(rs.DocComment);
sb.AppendLine($"typedef struct {nameMap[rs.Name]} {{");
foreach (var field in rs.Fields) {
using (sb.Indent()) {
sb.AppendDocComment(field.DocComment);
sb.AppendLine($"{GetBasicCType(nameMap, field.Type)} {field.Name};");
}
}
sb.AppendLine($"}} {nameMap[rs.Name]};");
sb.AppendLine();
}
public static void WriteC2CPPMapping(Dictionary<string, string> nameMap, IndentStringBuilder sb, RustStruct rs)
{
sb.AppendLine($"static inline {nameMap[rs.Name]} to_c(const {rs.Name}& dto) {{");
using (sb.Indent()) {
sb.AppendLine("return {");
using (sb.Indent()) {
foreach (var field in rs.Fields) {
string suffix = field.Optional ? "_opt" : "";
string type = field.Type == "PathBuf" ? "string" : field.Type.ToLower();
sb.AppendLine(
nameMap.ContainsKey(field.Type)
? $"to_c{suffix}(dto.{field.Name}),"
: $"to_c{type}{suffix}(dto.{field.Name}),");
}
}
sb.AppendLine("};");
}
sb.AppendLine($"}}");
sb.AppendLine();
sb.AppendLine($"static inline {rs.Name} to_cpp(const {nameMap[rs.Name]}& dto) {{");
using (sb.Indent()) {
sb.AppendLine("return {");
using (sb.Indent()) {
foreach (var field in rs.Fields) {
string suffix = field.Optional ? "_opt" : "";
string type = field.Type == "PathBuf" ? "string" : field.Type.ToLower();
sb.AppendLine(
nameMap.ContainsKey(field.Type)
? $"to_cpp{suffix}(dto.{field.Name}),"
: $"to_cpp{type}{suffix}(dto.{field.Name}),");
}
}
sb.AppendLine("};");
}
sb.AppendLine($"}}");
sb.AppendLine();
}
public static void WriteCPlusPlus(Dictionary<string, string> nameMap, IndentStringBuilder sb, RustStruct rs)
{
var coreTypes = nameMap.Keys.ToArray();
sb.AppendDocComment(rs.DocComment);
sb.AppendLine($"struct {rs.Name} {{");
foreach (var field in rs.Fields) {
using (sb.Indent()) {
sb.AppendDocComment(field.DocComment);
sb.AppendLine($"{GetCPlusPlusType(coreTypes, field.Type, field.Optional, field.Vec)} {field.Name};");
}
}
sb.AppendLine($"}};");
sb.AppendLine();
}
public static void WriteBridgeDto(string[] coreTypes, IndentStringBuilder sb, RustStruct rs)
{
Func<string, string> nameMapper = (str) =>
coreTypes.Contains(str) ? str + "Dto" : str;
using (sb.Indent()) {
sb.AppendLine($"#[derive(Default)]");
sb.AppendLine($"pub struct {nameMapper(rs.Name)} {{");
foreach (var field in rs.Fields) {
string type = field.Type;
if (type == "PathBuf") {
type = "String";
}
using (sb.Indent()) {
if (field.Optional) {
sb.AppendLine($"pub {field.Name}: {nameMapper(type)}Option,");
} else {
sb.AppendLine($"pub {field.Name}: {nameMapper(type)},");
}
}
}
sb.AppendLine($"}}");
sb.AppendLine();
sb.AppendLine($"#[derive(Default)]");
sb.AppendLine($"pub struct {nameMapper(rs.Name)}Option {{");
using (sb.Indent()) {
sb.AppendLine($"pub data: {nameMapper(rs.Name)},");
sb.AppendLine($"pub has_data: bool,");
}
sb.AppendLine($"}}");
sb.AppendLine();
}
}
public static void WriteBridgeToCoreMapping(string[] coreTypes, IndentStringBuilder sb, RustStruct rs)
{
Func<string, string> nameMapper = (str) => coreTypes.Contains(str) ? str + "Dto" : str;
sb.AppendLine($"pub fn {rs.Name.ToLower()}_to_core(dto: &{nameMapper(rs.Name)}) -> {rs.Name} {{");
;
using (sb.Indent()) {
sb.AppendLine($"{rs.Name} {{");
foreach (var field in rs.Fields) {
using (sb.Indent()) {
if (field.Optional) {
sb.AppendLine(
$"{field.Name}: if dto.{field.Name}.has_data {{ Some({field.Type.ToLower()}_to_core(&dto.{field.Name}.data)) }} else {{ None }},");
} else {
sb.AppendLine($"{field.Name}: {field.Type.ToLower()}_to_core(&dto.{field.Name}),");
}
}
}
sb.AppendLine($"}}");
}
sb.AppendLine($"}}");
sb.AppendLine();
sb.AppendLine($"pub fn {rs.Name.ToLower()}_to_bridge(dto: &{rs.Name}) -> {nameMapper(rs.Name)} {{");
using (sb.Indent()) {
sb.AppendLine($"{nameMapper(rs.Name)} {{");
foreach (var field in rs.Fields) {
using (sb.Indent()) {
if (field.Optional) {
sb.AppendLine(
$"{field.Name}: {nameMapper(field.Type)}Option {{ data: {field.Type.ToLower()}_to_bridge(&dto.{field.Name}.clone().unwrap_or_default()), has_data: dto.{field.Name}.is_some() }},");
} else {
sb.AppendLine($"{field.Name}: {field.Type.ToLower()}_to_bridge(&dto.{field.Name}),");
}
}
}
sb.AppendLine($"}}");
}
sb.AppendLine($"}}");
sb.AppendLine();
sb.AppendLine($"pub fn {rs.Name.ToLower()}_to_core_option(dto: &{nameMapper(rs.Name)}Option) -> Option<{rs.Name}> {{");
;
using (sb.Indent()) {
sb.AppendLine($"if dto.has_data {{ Some({rs.Name.ToLower()}_to_core(&dto.data)) }} else {{ None }}");
}
sb.AppendLine($"}}");
sb.AppendLine();
sb.AppendLine($"pub fn {rs.Name.ToLower()}_to_bridge_option(dto: &Option<{rs.Name}>) -> {nameMapper(rs.Name)}Option {{");
;
using (sb.Indent()) {
sb.AppendLine($"match dto {{");
using (sb.Indent()) {
sb.AppendLine($"Some(dto) => {nameMapper(rs.Name)}Option {{ data: {rs.Name.ToLower()}_to_bridge(dto), has_data: true }},");
sb.AppendLine($"None => {nameMapper(rs.Name)}Option {{ data: Default::default(), has_data: false }},");
}
sb.AppendLine($"}}");
}
sb.AppendLine($"}}");
sb.AppendLine();
}
}

View File

@@ -0,0 +1,82 @@
{{#each this}}
{{cpp_comment}}
struct {{struct_rust_name}} {
{{#each fields}}
{{#indent "cpp_comment" " "}}
{{#unless field_vector}}{{#if field_optional}}std::optional<{{/if~}}
{{~#if field_system~}}std::{{~/if~}}{{field_cpp_type}}
{{~#if field_optional}}>{{/if}} {{field_name}};{{~/unless~}}
{{#if field_vector}}std::vector<{{field_cpp_type}}> {{field_name}};{{/if}}
{{/each}}
};
static inline std::optional<{{struct_rust_name}}> to_cpp_{{struct_rust_name}}(const {{struct_c_name}}* dto) {
if (dto == nullptr) { return std::nullopt; }
return std::optional<{{struct_rust_name}}>({
{{#each fields}}
{{#if field_primitive}}dto->{{field_name}},{{/if~}}
{{#if field_normal}}to_cpp_{{field_cpp_type}}(dto->{{field_name}}){{~#unless field_optional}}.value(){{/unless}},{{/if~}}
{{#if field_vector}}to_cpp_{{field_cpp_type}}_vec(dto->{{field_name}}, dto->{{field_name}}Count),{{/if}}
{{/each}}
});
}
static inline std::vector<{{struct_rust_name}}> to_cpp_{{struct_rust_name}}_vec(const {{struct_c_name}}* const* arr, size_t c) {
if (arr == nullptr || c < 1) { return std::vector<{{struct_rust_name}}>(); }
std::vector<{{struct_rust_name}}> result;
result.reserve(c);
for (size_t i = 0; i < c; ++i) {
auto dto = arr[i];
if (dto == nullptr) { continue; }
result.push_back(to_cpp_{{struct_rust_name}}(dto).value());
}
return result;
}
static inline {{struct_c_name}}* alloc_c_{{struct_rust_name}}(const {{struct_rust_name}}* dto) {
if (dto == nullptr) { return nullptr; }
{{struct_c_name}}* obj = new {{struct_c_name}}{};
{{#each fields}}
{{#if field_primitive}}obj->{{field_name}} = dto->{{field_name}};{{/if~}}
{{#if field_normal}}obj->{{field_name}} = alloc_c_{{field_cpp_type}}(dto->{{field_name}});{{/if~}}
{{#if field_vector}}obj->{{field_name}} = alloc_c_{{field_cpp_type}}_vec(dto->{{field_name}}, &obj->{{field_name}}Count);{{/if}}
{{/each}}
return obj;
}
static inline {{struct_c_name}}* alloc_c_{{struct_rust_name}}(const std::optional<{{struct_rust_name}}>& dto) {
if (!dto.has_value()) { return nullptr; }
return alloc_c_{{struct_rust_name}}(dto.value());
}
static inline {{struct_c_name}}** alloc_c_{{struct_rust_name}}_vec(const std::vector<{{struct_rust_name}}>& dto, size_t* count) {
if (dto.empty()) {
*count = 0;
return nullptr;
}
*count = dto.size();
{{struct_c_name}}** arr = new {{struct_c_name}}*[*count];
for (size_t i = 0; i < *count; ++i) {
arr[i] = alloc_c_{{struct_rust_name}}(dto[i]);
}
return arr;
}
static inline void free_c_{{struct_rust_name}}({{struct_c_name}}* obj) {
if (obj == nullptr) { return; }
{{#each fields}}
{{#if field_normal}}free_c_{{field_cpp_type}}(obj->{{field_name}});{{/if~}}
{{#if field_vector}}free_c_{{field_cpp_type}}_vec(obj->{{field_name}}, obj->{{field_name}}Count);{{/if}}
{{/each}}
delete obj;
}
static inline void free_c_{{struct_rust_name}}_vec({{struct_c_name}}** arr, size_t count) {
if (arr == nullptr || count < 1) { return; }
for (size_t i = 0; i < count; ++i) {
free_c_{{struct_rust_name}}(arr[i]);
}
delete[] arr;
}
{{/each}}

View File

@@ -4,7 +4,7 @@
{{rust_comment}}
pub struct {{struct_c_name}} {
{{#each fields}}
{{rust_comment}}
{{#indent "rust_comment" " "}}
pub {{field_name}}: {{#unless field_primitive}}*mut {{/unless}}{{~#if field_vector}}*mut {{/if}}{{field_c_type}},
{{#if field_vector}}
/// The number of elements in the {{field_name}} array.

View File

@@ -4,7 +4,7 @@
{
var body = File.ReadAllText(path);
ReplaceTextBetween(ref body, placeholderName, text);
File.WriteAllText(path, body);
File.WriteAllText(path, body.ReplaceLineEndings("\n"));
}
public static void ReplaceTextBetween(ref string body, string placeholderName, string text)
@@ -31,4 +31,18 @@
var lines = text.ReplaceLineEndings("\n").Split(['\n']).Select(l => prefix + l);
return String.Join("\n", lines);
}
public static string ToRustComment(this string text)
{
return text.PrefixEveryLine("/// ");
}
public static string ToCppComment(this string text)
{
if (text.Contains("\n")) {
return "/**\n" + text.PrefixEveryLine(" * ") + "\n */";
} else {
return $"/** {text} */";
}
}
}

View File

@@ -4,7 +4,7 @@
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Nullable>disable</Nullable>
</PropertyGroup>
<ItemGroup>
@@ -19,8 +19,4 @@
</AssemblyAttribute>
</ItemGroup>
<ItemGroup>
<Folder Include="Templates\" />
</ItemGroup>
</Project>