ropkg: implement create command

This commit is contained in:
2025-07-17 18:12:44 +01:00
parent add651a3ed
commit 10603ed2d1
2 changed files with 342 additions and 126 deletions

View File

@@ -1,4 +1,6 @@
file(GLOB sources *.c *.h) file(GLOB sources *.c *.h)
add_executable(ropkg ${sources}) 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)

View File

@@ -1,6 +1,14 @@
#include "commands.h" #include "commands.h"
#include <blue/cmd.h> #include <blue/cmd.h>
#include <blue/io/directory.h>
#include <blue/term/print.h>
#include <errno.h>
#include <ropkg/compress.h>
#include <ropkg/stream.h>
#include <ropkg/writer.h>
#include <stdio.h>
#include <stdlib.h>
enum { enum {
OPT_OUTPATH, OPT_OUTPATH,
@@ -9,19 +17,290 @@ enum {
OPT_MANIFEST, OPT_MANIFEST,
OPT_MANIFEST_PATH, OPT_MANIFEST_PATH,
OPT_NEWS,
OPT_NEWS_PATH,
// OPT_CONTROL,
// OPT_CONTROL_PATH,
OPT_PARAMETER, OPT_PARAMETER,
OPT_PARAMETER_NAME, OPT_PARAMETER_NAME,
OPT_PARAMETER_VALUE, OPT_PARAMETER_VALUE,
OPT_SCRIPT,
OPT_SCRIPT_NAME,
OPT_SCRIPT_PATH,
ARG_SOURCE_DIR, 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( static int create(
const b_command *self, const b_command *self,
const b_arglist *opt, const b_arglist *opt,
const b_array *args) 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) B_COMMAND(CMD_CREATE, CMD_ROOT)
@@ -53,10 +332,7 @@ B_COMMAND(CMD_CREATE, CMD_ROOT)
B_OPTION_LONG_NAME("manifest"); B_OPTION_LONG_NAME("manifest");
B_OPTION_DESC( B_OPTION_DESC(
"the path to a manifest file describing the " "the path to a manifest file describing the "
"package to be created. if no manifest is specified, " "package to be created.");
"the package binary directory will be searched for a "
"manifest");
B_OPTION_ARG(OPT_MANIFEST_PATH) B_OPTION_ARG(OPT_MANIFEST_PATH)
{ {
B_ARG_NAME("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_COMMAND_OPTION(OPT_PARAMETER)
{ {
B_OPTION_SHORT_NAME('p'); B_OPTION_SHORT_NAME('p');
@@ -101,126 +434,7 @@ B_COMMAND(CMD_CREATE, CMD_ROOT)
B_COMMAND_USAGE() B_COMMAND_USAGE()
{ {
B_COMMAND_USAGE_OPT(OPT_OUTPATH); B_COMMAND_USAGE_OPT(OPT_OUTPATH);
B_COMMAND_USAGE_ARG(ARG_SOURCE_DIR); B_COMMAND_USAGE_OPT_PLACEHOLDER();
}
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 <blue/cmd.h>
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_ARG(ARG_SOURCE_DIR); B_COMMAND_USAGE_ARG(ARG_SOURCE_DIR);
} }
} }