From 10603ed2d1cf179f52a5132d2c50c6becbafd7d5 Mon Sep 17 00:00:00 2001 From: Max Wash Date: Thu, 17 Jul 2025 18:12:44 +0100 Subject: [PATCH] ropkg: implement create command --- ropkg/CMakeLists.txt | 4 +- ropkg/create.c | 464 +++++++++++++++++++++++++++++++------------ 2 files changed, 342 insertions(+), 126 deletions(-) diff --git a/ropkg/CMakeLists.txt b/ropkg/CMakeLists.txt index b7830d6..840b56a 100644 --- a/ropkg/CMakeLists.txt +++ b/ropkg/CMakeLists.txt @@ -1,4 +1,6 @@ file(GLOB sources *.c *.h) add_executable(ropkg ${sources}) -target_link_libraries(ropkg Bluelib::Core Bluelib::Object Bluelib::Io Bluelib::Term Bluelib::Cmd) +target_link_libraries(ropkg + libropkg + Bluelib::Core Bluelib::Object Bluelib::Io Bluelib::Term Bluelib::Cmd) diff --git a/ropkg/create.c b/ropkg/create.c index 8ba3a7b..3ede1a2 100644 --- a/ropkg/create.c +++ b/ropkg/create.c @@ -1,6 +1,14 @@ #include "commands.h" #include +#include +#include +#include +#include +#include +#include +#include +#include enum { OPT_OUTPATH, @@ -9,19 +17,290 @@ enum { OPT_MANIFEST, OPT_MANIFEST_PATH, + OPT_NEWS, + OPT_NEWS_PATH, + + // OPT_CONTROL, + // OPT_CONTROL_PATH, + OPT_PARAMETER, OPT_PARAMETER_NAME, OPT_PARAMETER_VALUE, + OPT_SCRIPT, + OPT_SCRIPT_NAME, + OPT_SCRIPT_PATH, + ARG_SOURCE_DIR, }; +static int add_file_to_archive_ex( + struct ropkg_writer *writer, + b_directory *src_dir, + const b_path *src, + const char *dest) +{ + b_file *fp; + b_status status = b_file_open( + src_dir, + src, + B_FILE_READ_ONLY | B_FILE_BINARY, + &fp); + if (!fp) { + b_err("cannot open file '%s'", src); + b_i("reason: %s", b_status_to_string(status)); + return -1; + } + + struct ropkg_writer_file_info file = {0}; + + b_file_size(fp, &file.f_length); + + ropkg_writer_begin_file(writer, dest, &file); + + char buf[4096]; + size_t offset = 0; + int c; + while (1) { + size_t r; + status = b_file_read(fp, offset, sizeof buf, buf, &r); + if (!B_OK(status)) { + break; + } + + size_t w; + char s = c; + ropkg_writer_write(writer, buf, r, &w); + offset += r; + + if (r < sizeof buf) { + break; + } + } + + b_file_release(fp); + ropkg_writer_end_file(writer); + return 0; +} + +static int add_file_to_archive( + struct ropkg_writer *writer, + const char *src, + const char *dest) +{ + b_path *src_path = b_path_create_from_cstr(src); + int ret = add_file_to_archive_ex(writer, NULL, src_path, dest); + b_path_release(src_path); + + return ret; +} + +static int add_data_archive( + const b_arglist *args, + struct ropkg_stream *stream, + struct ropkg_writer *pkg) +{ + const char *data_path_cstr; + b_status bstatus = b_arglist_get_string( + args, + B_COMMAND_INVALID_ID, + ARG_SOURCE_DIR, + 0, + &data_path_cstr); + if (!B_OK(bstatus)) { + b_arglist_report_missing_option(args, OPT_MANIFEST); + return -1; + } + + b_path *data_path = b_path_create_from_cstr(data_path_cstr); + b_directory *data_dir; + bstatus = b_directory_open(NULL, data_path, &data_dir); + if (!B_OK(bstatus)) { + b_err("cannot open directory '%s'", data_path_cstr); + b_i("reason: %s", b_status_to_string(bstatus)); + return -1; + } + + struct ropkg_writer *data; + struct ropkg_writer_file_info file = {0}; + ropkg_writer_begin_file(pkg, ROPKG_PATH_DATA ".zst", &file); + + ropkg_stream_begin_compressed_section(stream); + enum ropkg_status status = ropkg_writer_open(stream, &data); + + int ret = 0; + b_directory_iterator it; + b_directory_foreach(&it, data_dir, B_DIRECTORY_ITERATE_PARENT_FIRST) + { + if (!(it.info.attrib & B_FILE_ATTRIB_NORMAL)) { + continue; + } + + ret = add_file_to_archive_ex( + data, + data_dir, + it.filepath, + b_path_ptr(it.filepath)); + + if (ret != 0) { + break; + } + } + + ropkg_writer_close(data); + ropkg_stream_end_compressed_section(stream, NULL, NULL); + ropkg_writer_end_file(pkg); + + return ret; +} + +static int add_control_archive( + const b_arglist *args, + struct ropkg_stream *stream, + struct ropkg_writer *pkg) +{ + int ret; + + struct ropkg_writer *control; + struct ropkg_writer_file_info file = {0}; + ropkg_writer_begin_file(pkg, ROPKG_PATH_CONTROL ".zst", &file); + + ropkg_stream_begin_compressed_section(stream); + enum ropkg_status status = ropkg_writer_open(stream, &control); + + b_arglist_option_iterator it; + b_arglist_option_foreach_filtered(&it, args, OPT_SCRIPT) + { + struct b_arglist_value *type, *path; + b_arglist_option_get_value(it.opt, OPT_SCRIPT_NAME, 0, &type); + b_arglist_option_get_value(it.opt, OPT_SCRIPT_PATH, 0, &path); + + ret = add_file_to_archive( + control, + path->val_str, + type->val_str); + + if (ret != 0) { + ropkg_writer_close(control); + return ret; + } + } + + ropkg_writer_close(control); + + ropkg_stream_end_compressed_section(stream, NULL, NULL); + + ropkg_writer_end_file(pkg); + + return 0; +} + +static int add_meta_archive( + const b_arglist *args, + struct ropkg_stream *stream, + struct ropkg_writer *pkg) +{ + const char *manifest_path, *news_path; + b_status bstatus = b_arglist_get_string( + args, + OPT_MANIFEST, + OPT_MANIFEST_PATH, + 1, + &manifest_path); + if (!B_OK(bstatus)) { + b_arglist_report_missing_option(args, OPT_MANIFEST); + return -1; + } + + int ret; + + struct ropkg_writer *meta; + struct ropkg_writer_file_info file = {0}; + ropkg_writer_begin_file(pkg, ROPKG_PATH_META ".zst", &file); + + ropkg_stream_begin_compressed_section(stream); + enum ropkg_status status = ropkg_writer_open(stream, &meta); + + bstatus = b_arglist_get_string( + args, + OPT_NEWS, + OPT_NEWS_PATH, + 1, + &news_path); + if (B_OK(bstatus)) { + ret = add_file_to_archive(meta, news_path, "news"); + if (ret != 0) { + ropkg_writer_close(meta); + return ret; + } + } + + ret = add_file_to_archive(meta, manifest_path, "manifest"); + if (ret != 0) { + ropkg_writer_close(meta); + return ret; + } + + ropkg_writer_close(meta); + + ropkg_stream_end_compressed_section(stream, NULL, NULL); + + ropkg_writer_end_file(pkg); + + return 0; +} + static int create( const b_command *self, const b_arglist *opt, const b_array *args) { - return 0; + const struct ropkg_compression_function *zstd + = ropkg_compression_function_for_type(ROPKG_COMPRESSION_ZSTD); + struct ropkg_writer *pkg, *subpkg; + enum ropkg_status status; + + const char *out_path; + b_status bstatus = b_arglist_get_string( + opt, + OPT_OUTPATH, + OPT_OUTPATH_PATH, + 1, + &out_path); + if (!B_OK(bstatus)) { + b_arglist_report_missing_option(opt, OPT_OUTPATH); + return -1; + } + + FILE *fp = fopen(out_path, "wb"); + struct ropkg_stream *stream; + status = ropkg_stream_open(fp, zstd, ROPKG_STREAM_WRITE, &stream); + + status = ropkg_writer_open(stream, &pkg); + + int ret; + + ret = add_data_archive(opt, stream, pkg); + if (ret != 0) { + goto end; + } + + ret = add_control_archive(opt, stream, pkg); + if (ret != 0) { + goto end; + } + + ret = add_meta_archive(opt, stream, pkg); + if (ret != 0) { + goto end; + } + +end: + ropkg_writer_close(pkg); + ropkg_stream_close(stream); + fclose(fp); + + return ret; } B_COMMAND(CMD_CREATE, CMD_ROOT) @@ -53,10 +332,7 @@ B_COMMAND(CMD_CREATE, CMD_ROOT) B_OPTION_LONG_NAME("manifest"); B_OPTION_DESC( "the path to a manifest file describing the " - "package to be created. if no manifest is specified, " - "the package binary directory will be searched for a " - "manifest"); - + "package to be created."); B_OPTION_ARG(OPT_MANIFEST_PATH) { B_ARG_NAME("path"); @@ -64,6 +340,63 @@ B_COMMAND(CMD_CREATE, CMD_ROOT) } } + B_COMMAND_OPTION(OPT_NEWS) + { + B_OPTION_SHORT_NAME('n'); + B_OPTION_LONG_NAME("news"); + B_OPTION_DESC( + "the path to a news file to be included in the " + "package."); + B_OPTION_ARG(OPT_NEWS_PATH) + { + B_ARG_NAME("path"); + B_ARG_NR_VALUES(1); + } + } + +#if 0 + B_COMMAND_OPTION(OPT_CONTROL) + { + B_OPTION_SHORT_NAME('c'); + B_OPTION_LONG_NAME("control"); + B_OPTION_DESC( + "the path to a control directory containing package " + "scripts. this option cannot be used with " + "-s/--script."); + B_OPTION_ARG(OPT_CONTROL_PATH) + { + B_ARG_NAME("path"); + B_ARG_NR_VALUES(1); + } + } +#endif + + B_COMMAND_OPTION(OPT_SCRIPT) + { + B_OPTION_SHORT_NAME('s'); + B_OPTION_LONG_NAME("script"); + B_OPTION_DESC( + "a control script to be run at a certain point in the " + "package (un)installation process."); + + B_OPTION_ARG(OPT_SCRIPT_NAME) + { + B_ARG_NAME("event"); + B_ARG_NR_VALUES(1); + B_ARG_ALLOWED_VALUES( + "pre-install", + "post-install", + "pre-uninstall", + "post-uninstall"); + } + + B_OPTION_ARG(OPT_SCRIPT_PATH) + { + B_ARG_NAME("path"); + B_ARG_NR_VALUES(1); + } + } + B_COMMAND_OPTION(OPT_PARAMETER) { B_OPTION_SHORT_NAME('p'); @@ -101,126 +434,7 @@ B_COMMAND(CMD_CREATE, CMD_ROOT) B_COMMAND_USAGE() { B_COMMAND_USAGE_OPT(OPT_OUTPATH); - B_COMMAND_USAGE_ARG(ARG_SOURCE_DIR); - } - - B_COMMAND_USAGE() - { - B_COMMAND_USAGE_OPT(OPT_MANIFEST); - B_COMMAND_USAGE_OPT(OPT_OUTPATH); - B_COMMAND_USAGE_ARG(ARG_SOURCE_DIR); - } -} -#include "commands.h" - -#include - -enum { - OPT_OUTPATH, - OPT_OUTPATH_PATH, - - OPT_MANIFEST, - OPT_MANIFEST_PATH, - - OPT_PARAMETER, - OPT_PARAMETER_NAME, - OPT_PARAMETER_VALUE, - - ARG_SOURCE_DIR, -}; - -static int create( - const b_command *self, - const b_arglist *opt, - const b_array *args) -{ - return 0; -} - -B_COMMAND(CMD_CREATE, CMD_ROOT) -{ - B_COMMAND_NAME("create"); - B_COMMAND_SHORT_NAME('C'); - B_COMMAND_DESC("create a Rosetta package from a directory"); - B_COMMAND_FLAGS(B_COMMAND_SHOW_HELP_BY_DEFAULT); - B_COMMAND_FUNCTION(create); - - B_COMMAND_HELP_OPTION(); - - B_COMMAND_OPTION(OPT_OUTPATH) - { - B_OPTION_SHORT_NAME('o'); - B_OPTION_LONG_NAME("out"); - B_OPTION_DESC("the path to save the new package file to"); - - B_OPTION_ARG(OPT_OUTPATH_PATH) - { - B_ARG_NAME("path"); - B_ARG_NR_VALUES(1); - } - } - - B_COMMAND_OPTION(OPT_MANIFEST) - { - B_OPTION_SHORT_NAME('m'); - B_OPTION_LONG_NAME("manifest"); - B_OPTION_DESC( - "the path to a manifest file describing the " - "package to be created. if no manifest is specified, " - "the package binary directory will be searched for a " - "manifest"); - - B_OPTION_ARG(OPT_MANIFEST_PATH) - { - B_ARG_NAME("path"); - B_ARG_NR_VALUES(1); - } - } - - B_COMMAND_OPTION(OPT_PARAMETER) - { - B_OPTION_SHORT_NAME('p'); - B_OPTION_LONG_NAME("parameter"); - B_OPTION_DESC( - "a parameter to use when creating the new file. " - "if a parameter EXAMPLE is defined, any variable " - "reference " - "of the form ${EXAMPLE} in the package manifest will " - "be " - "replaced with the specified parameter value."); - - B_OPTION_ARG(OPT_PARAMETER_NAME) - { - B_ARG_NAME("name"); - B_ARG_NR_VALUES(1); - } - - B_OPTION_ARG(OPT_PARAMETER_VALUE) - { - B_ARG_NAME("value"); - B_ARG_NR_VALUES(1); - } - } - - B_COMMAND_ARG(ARG_SOURCE_DIR) - { - B_ARG_NAME("source-dir"); - B_ARG_DESC( - "the directory to package. the specified directory " - "will become the root of the newly created package."); - B_ARG_NR_VALUES(1); - } - - B_COMMAND_USAGE() - { - B_COMMAND_USAGE_OPT(OPT_OUTPATH); - B_COMMAND_USAGE_ARG(ARG_SOURCE_DIR); - } - - B_COMMAND_USAGE() - { - B_COMMAND_USAGE_OPT(OPT_MANIFEST); - B_COMMAND_USAGE_OPT(OPT_OUTPATH); + B_COMMAND_USAGE_OPT_PLACEHOLDER(); B_COMMAND_USAGE_ARG(ARG_SOURCE_DIR); } }