flatpak-builder/app/flatpak-builtins-build-expo...

1014 lines
34 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/*
* Copyright © 2014 Red Hat, Inc
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors:
* Alexander Larsson <alexl@redhat.com>
*/
#include "config.h"
#include <locale.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <glib/gi18n.h>
#include "libglnx/libglnx.h"
#include "flatpak-builtins.h"
#include "flatpak-utils.h"
static char *opt_subject;
static char *opt_body;
static char *opt_arch;
static gboolean opt_runtime;
static gboolean opt_update_appstream;
static gboolean opt_no_update_summary;
static char **opt_gpg_key_ids;
static char **opt_exclude;
static char **opt_include;
static char *opt_gpg_homedir;
static char *opt_files;
static char *opt_metadata;
static char *opt_timestamp = NULL;
#ifdef FLATPAK_ENABLE_P2P
static char *opt_collection_id = NULL;
#endif /* FLATPAK_ENABLE_P2P */
static GOptionEntry options[] = {
{ "subject", 's', 0, G_OPTION_ARG_STRING, &opt_subject, N_("One line subject"), N_("SUBJECT") },
{ "body", 'b', 0, G_OPTION_ARG_STRING, &opt_body, N_("Full description"), N_("BODY") },
{ "arch", 0, 0, G_OPTION_ARG_STRING, &opt_arch, N_("Architecture to export for (must be host compatible)"), N_("ARCH") },
{ "runtime", 'r', 0, G_OPTION_ARG_NONE, &opt_runtime, N_("Commit runtime (/usr), not /app"), NULL },
{ "update-appstream", 0, 0, G_OPTION_ARG_NONE, &opt_update_appstream, N_("Update the appstream branch"), NULL },
{ "no-update-summary", 0, 0, G_OPTION_ARG_NONE, &opt_no_update_summary, N_("Don't update the summary"), NULL },
{ "files", 0, 0, G_OPTION_ARG_STRING, &opt_files, N_("Use alternative directory for the files"), N_("SUBDIR") },
{ "metadata", 0, 0, G_OPTION_ARG_STRING, &opt_metadata, N_("Use alternative file for the metadata"), N_("FILE") },
{ "gpg-sign", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_gpg_key_ids, N_("GPG Key ID to sign the commit with"), N_("KEY-ID") },
{ "exclude", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_exclude, N_("Files to exclude"), N_("PATTERN") },
{ "include", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_include, N_("Excluded files to include"), N_("PATTERN") },
{ "gpg-homedir", 0, 0, G_OPTION_ARG_STRING, &opt_gpg_homedir, N_("GPG Homedir to use when looking for keyrings"), N_("HOMEDIR") },
{ "timestamp", 0, 0, G_OPTION_ARG_STRING, &opt_timestamp, N_("Override the timestamp of the commit"), N_("ISO-8601-TIMESTAMP") },
#ifdef FLATPAK_ENABLE_P2P
{ "collection-id", 0, 0, G_OPTION_ARG_STRING, &opt_collection_id, N_("Collection ID"), "COLLECTION-ID" },
#endif /* FLATPAK_ENABLE_P2P */
{ NULL }
};
static gboolean
metadata_get_arch (GFile *file, char **out_arch, GError **error)
{
g_autofree char *path = NULL;
g_autoptr(GKeyFile) keyfile = NULL;
g_autofree char *runtime = NULL;
g_auto(GStrv) parts = NULL;
if (opt_arch != NULL)
{
*out_arch = g_strdup (opt_arch);
return TRUE;
}
keyfile = g_key_file_new ();
path = g_file_get_path (file);
if (!g_key_file_load_from_file (keyfile, path, G_KEY_FILE_NONE, error))
return FALSE;
runtime = g_key_file_get_string (keyfile,
"Application",
"runtime", NULL);
if (runtime == NULL)
runtime = g_key_file_get_string (keyfile,
"Application",
"sdk", NULL);
if (runtime == NULL)
runtime = g_key_file_get_string (keyfile,
"Runtime",
"runtime", NULL);
if (runtime == NULL)
runtime = g_key_file_get_string (keyfile,
"Runtime",
"sdk", NULL);
if (runtime == NULL)
{
*out_arch = g_strdup (flatpak_get_arch ());
return TRUE;
}
parts = g_strsplit (runtime, "/", 0);
if (g_strv_length (parts) != 3)
return flatpak_fail (error, "Failed to determine arch from metadata runtime key: %s", runtime);
*out_arch = g_strdup (parts[1]);
return TRUE;
}
static gboolean
is_empty_directory (GFile *file, GCancellable *cancellable)
{
g_autoptr(GFileEnumerator) file_enum = NULL;
g_autoptr(GFileInfo) child_info = NULL;
file_enum = g_file_enumerate_children (file, G_FILE_ATTRIBUTE_STANDARD_NAME,
G_FILE_QUERY_INFO_NONE,
cancellable, NULL);
if (!file_enum)
return FALSE;
child_info = g_file_enumerator_next_file (file_enum, cancellable, NULL);
if (child_info)
return FALSE;
return TRUE;
}
typedef struct
{
const char **exclude;
const char **include;
} CommitData;
static gboolean
matches_patterns (const char **patterns, const char *path)
{
int i;
if (patterns == NULL)
return FALSE;
for (i = 0; patterns[i] != NULL; i++)
{
if (flatpak_path_match_prefix (patterns[i], path) != NULL)
return TRUE;
}
return FALSE;
}
static OstreeRepoCommitFilterResult
commit_filter (OstreeRepo *repo,
const char *path,
GFileInfo *file_info,
CommitData *commit_data)
{
guint mode;
/* No user info */
g_file_info_set_attribute_uint32 (file_info, "unix::uid", 0);
g_file_info_set_attribute_uint32 (file_info, "unix::gid", 0);
/* In flatpak, there is no real reason for files to have different
* permissions based on the group or user really, everything is
* always used readonly for everyone. Having things be writeable
* for anyone but the user just causes risks for the system-installed
* case. So, we canonicalize the mode to writable only by the user,
* readable to all, and executable for all for directories and
* files that the user can execute.
*/
mode = g_file_info_get_attribute_uint32 (file_info, "unix::mode");
if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_DIRECTORY)
mode = 0755 | S_IFDIR;
else if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_REGULAR)
{
/* If use can execute, make executable by all */
if (mode & S_IXUSR)
mode = 0755 | S_IFREG;
else /* otherwise executable by none */
mode = 0644 | S_IFREG;
}
g_file_info_set_attribute_uint32 (file_info, "unix::mode", mode);
if (matches_patterns (commit_data->exclude, path) &&
!matches_patterns (commit_data->include, path))
{
g_debug ("Excluding %s", path);
return OSTREE_REPO_COMMIT_FILTER_SKIP;
}
return OSTREE_REPO_COMMIT_FILTER_ALLOW;
}
static gboolean
add_file_to_mtree (GFile *file,
const char *name,
OstreeRepo *repo,
OstreeMutableTree *mtree,
GCancellable *cancellable,
GError **error)
{
g_autoptr(GFileInfo) file_info = NULL;
g_autoptr(GInputStream) raw_input = NULL;
g_autoptr(GInputStream) input = NULL;
guint64 length;
g_autofree guchar *child_file_csum = NULL;
g_autofree char *tmp_checksum = NULL;
file_info = g_file_query_info (file,
"standard::size",
0, cancellable, error);
if (file_info == NULL)
return FALSE;
g_file_info_set_name (file_info, name);
g_file_info_set_file_type (file_info, G_FILE_TYPE_REGULAR);
g_file_info_set_attribute_uint32 (file_info, "unix::uid", 0);
g_file_info_set_attribute_uint32 (file_info, "unix::gid", 0);
g_file_info_set_attribute_uint32 (file_info, "unix::mode", 0100744);
raw_input = (GInputStream *) g_file_read (file, cancellable, error);
if (raw_input == NULL)
return FALSE;
if (!ostree_raw_file_to_content_stream (raw_input,
file_info, NULL,
&input, &length,
cancellable, error))
return FALSE;
if (!ostree_repo_write_content (repo, NULL, input, length,
&child_file_csum, cancellable, error))
return FALSE;
tmp_checksum = ostree_checksum_from_bytes (child_file_csum);
if (!ostree_mutable_tree_replace_file (mtree, name, tmp_checksum, error))
return FALSE;
return TRUE;
}
static gboolean
find_file_in_tree (GFile *base, const char *filename)
{
g_autoptr(GFileEnumerator) enumerator = NULL;
enumerator = g_file_enumerate_children (base,
G_FILE_ATTRIBUTE_STANDARD_TYPE ","
G_FILE_ATTRIBUTE_STANDARD_NAME,
G_FILE_QUERY_INFO_NONE,
NULL,
NULL);
if (!enumerator)
return FALSE;
do {
g_autoptr(GFileInfo) info = g_file_enumerator_next_file (enumerator, NULL, NULL);
GFileType type;
const char *name;
if (!info)
return FALSE;
type = g_file_info_get_file_type (info);
name = g_file_info_get_name (info);
if (type == G_FILE_TYPE_REGULAR && strcmp (name, filename) == 0)
return TRUE;
else if (type == G_FILE_TYPE_DIRECTORY)
{
g_autoptr(GFile) dir = g_file_get_child (base, name);
if (find_file_in_tree (dir, filename))
return TRUE;
}
} while (1);
return FALSE;
}
static GFile *
convert_app_absolute_path (const char *path, GFile *files)
{
g_autofree char *exec_path = NULL;
if (g_path_is_absolute (path))
{
if (g_str_has_prefix (path, "/app/"))
exec_path = g_strdup (path + 5);
else
exec_path = g_strdup (path);
}
else
exec_path = g_strconcat ("bin/", path, NULL);
return g_file_resolve_relative_path (files, exec_path);
}
static gboolean
validate_desktop_file (GFile *desktop_file,
GFile *export,
GFile *files,
const char *app_id,
char **icon,
gboolean *activatable,
GError **error)
{
g_autofree char *path = g_file_get_path (desktop_file);
g_autoptr(GSubprocess) subprocess = NULL;
g_autofree char *stdout_buf = NULL;
g_autofree char *stderr_buf = NULL;
g_autoptr(GError) local_error = NULL;
g_autoptr(GKeyFile) key_file = NULL;
g_autofree char *command = NULL;
g_auto(GStrv) argv = NULL;
g_autoptr(GFile) bin_file = NULL;
if (!g_file_query_exists (desktop_file, NULL))
return TRUE;
subprocess = g_subprocess_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE |
G_SUBPROCESS_FLAGS_STDERR_PIPE |
G_SUBPROCESS_FLAGS_STDERR_MERGE,
&local_error, "desktop-file-validate", path, NULL);
if (!subprocess)
{
if (!g_error_matches (local_error, G_SPAWN_ERROR, G_SPAWN_ERROR_NOENT))
g_print ("WARNING: Error running desktop-file-validate: %s\n", local_error->message);
g_clear_error (&local_error);
goto check_refs;
}
if (!g_subprocess_communicate_utf8 (subprocess, NULL, NULL, &stdout_buf, &stderr_buf, &local_error))
{
g_print ("WARNING: Error reading from desktop-file-validate: %s\n", local_error->message);
g_clear_error (&local_error);
}
if (g_subprocess_get_if_exited (subprocess) &&
g_subprocess_get_exit_status (subprocess) != 0)
g_print ("WARNING: Failed to validate desktop file %s: %s\n", path, stdout_buf);
check_refs:
/* Test that references to other files are valid */
key_file = g_key_file_new ();
if (!g_key_file_load_from_file (key_file, path, G_KEY_FILE_NONE, error))
return FALSE;
command = g_key_file_get_string (key_file,
G_KEY_FILE_DESKTOP_GROUP,
G_KEY_FILE_DESKTOP_KEY_EXEC,
&local_error);
if (!command)
{
g_print ("WARNING: Can't find Exec key in %s: %s\n", path, local_error->message);
g_clear_error (&local_error);
}
argv = g_strsplit (command, " ", 0);
bin_file = convert_app_absolute_path (argv[0], files);
if (!g_file_query_exists (bin_file, NULL))
g_print ("WARNING: Binary not found for Exec line in %s: %s\n", path, command);
*icon = g_key_file_get_string (key_file,
G_KEY_FILE_DESKTOP_GROUP,
G_KEY_FILE_DESKTOP_KEY_ICON,
NULL);
if (*icon && !g_str_has_prefix (*icon, app_id))
g_print ("WARNING: Icon not matching app id in %s: %s\n", path, *icon);
*activatable = g_key_file_get_boolean (key_file,
G_KEY_FILE_DESKTOP_GROUP,
G_KEY_FILE_DESKTOP_KEY_DBUS_ACTIVATABLE,
NULL);
return TRUE;
}
static gboolean
validate_icon (const char *icon,
GFile *export,
const char *app_id,
GError **error)
{
g_autoptr(GFile) icondir = NULL;
g_autofree char *png = NULL;
g_autofree char *svg = NULL;
if (!icon)
return TRUE;
icondir = g_file_resolve_relative_path (export, "share/icons/hicolor");
png = g_strconcat (icon, ".png", NULL);
svg = g_strconcat (icon, ".svg", NULL);
if (!find_file_in_tree (icondir, png) &&
!find_file_in_tree (icondir, svg))
g_print ("WARNING: Icon referenced in desktop file but not exported: %s\n", icon);
return TRUE;
}
static gboolean
validate_service_file (GFile *service_file,
gboolean activatable,
GFile *files,
const char *app_id,
GError **error)
{
g_autofree char *path = g_file_get_path (service_file);
g_autoptr(GKeyFile) key_file = NULL;
g_autofree char *name = NULL;
g_autofree char *command = NULL;
g_auto(GStrv) argv = NULL;
g_autoptr(GFile) bin_file = NULL;
if (!g_file_query_exists (service_file, NULL))
{
if (activatable)
{
g_set_error (error,
G_IO_ERROR, G_IO_ERROR_FAILED,
"Desktop file D-Bus activatable, but service file not exported: %s", path);
return FALSE;
}
return TRUE;
}
key_file = g_key_file_new ();
if (!g_key_file_load_from_file (key_file, path, G_KEY_FILE_NONE, error))
return FALSE;
name = g_key_file_get_string (key_file, "D-BUS Service", "Name", error);
if (!name)
{
g_prefix_error (error, "Invalid service file %s: ", path);
return FALSE;
}
if (strcmp (name, app_id) != 0)
{
g_set_error (error,
G_IO_ERROR, G_IO_ERROR_FAILED,
"Name in service file %s does not match app id: %s", path, name);
return FALSE;
}
command = g_key_file_get_string (key_file, "D-BUS Service", "Exec", error);
if (!command)
{
g_prefix_error (error, "Invalid service file %s: ", path);
return FALSE;
}
argv = g_strsplit (command, " ", 0);
bin_file = convert_app_absolute_path (argv[0], files);
if (!g_file_query_exists (bin_file, NULL))
g_print ("WARNING: Binary not found for Exec line in %s: %s\n", path, command);
return TRUE;
}
static gboolean
validate_exports (GFile *export, GFile *files, const char *app_id, GError **error)
{
g_autofree char *desktop_path = NULL;
g_autoptr(GFile) desktop_file = NULL;
g_autofree char *service_path = NULL;
g_autoptr(GFile) service_file = NULL;
g_autofree char *icon = NULL;
gboolean activatable = FALSE;
desktop_path = g_strconcat ("share/applications/", app_id, ".desktop", NULL);
desktop_file = g_file_resolve_relative_path (export, desktop_path);
if (!validate_desktop_file (desktop_file, export, files, app_id, &icon, &activatable, error))
return FALSE;
if (!validate_icon (icon, export, app_id, error))
return FALSE;
service_path = g_strconcat ("share/dbus-1/services/", app_id, ".service", NULL);
service_file = g_file_resolve_relative_path (export, service_path);
if (!validate_service_file (service_file, activatable, files, app_id, error))
return FALSE;
return TRUE;
}
static gboolean
collect_extra_data (GKeyFile *metakey, GVariantDict *metadata_dict, GError **error)
{
g_auto(GStrv) keys = NULL;
g_autoptr(GVariantBuilder) extra_data_sources_builder = NULL;
g_autoptr(GVariant) extra_data_sources = NULL;
int i;
keys = g_key_file_get_keys (metakey, "Extra Data", NULL, NULL);
if (keys == NULL)
return TRUE;
extra_data_sources_builder = g_variant_builder_new (G_VARIANT_TYPE ("a(ayttays)"));
for (i = 0; keys[i] != NULL; i++)
{
const char *key = keys[i];
if (g_str_has_prefix (key, "uri"))
{
const char *suffix = key + 3;
g_autofree char *uri = NULL;
g_autofree char *checksum_key = NULL;
g_autofree char *size_key = NULL;
g_autofree char *installed_size_key = NULL;
g_autofree char *name_key = NULL;
g_autofree char *checksum = NULL;
g_autofree char *name = NULL;
guint64 size, installed_size;
checksum_key = g_strconcat ("checksum", suffix, NULL);
size_key = g_strconcat ("size", suffix, NULL);
installed_size_key = g_strconcat ("installed-size", suffix, NULL);
name_key = g_strconcat ("name", suffix, NULL);
uri = g_key_file_get_string (metakey, "Extra Data", key, error);
if (uri == NULL)
return FALSE;
if (!g_str_has_prefix (uri, "http:") &&
!g_str_has_prefix (uri, "https:"))
{
g_set_error (error, G_KEY_FILE_ERROR,
G_KEY_FILE_ERROR_INVALID_VALUE,
_("Invalid uri type %s, only http/https supported"), uri);
return FALSE;
}
if (g_key_file_has_key (metakey, "Extra Data", name_key, NULL))
{
name = g_key_file_get_string (metakey, "Extra Data", name_key, error);
if (name == NULL)
return FALSE;
}
else
{
g_autoptr(GFile) file = g_file_new_for_uri (uri);
name = g_file_get_basename (file);
if (name == NULL || *name == 0)
{
g_set_error (error, G_KEY_FILE_ERROR,
G_KEY_FILE_ERROR_INVALID_VALUE,
_("Unable to find basename in %s, specify a name explicitly"), uri);
return FALSE;
}
}
if (strchr (name, '/') != NULL)
{
g_set_error (error, G_KEY_FILE_ERROR,
G_KEY_FILE_ERROR_INVALID_VALUE,
_("No slashes allowed in extra data name"));
return FALSE;
}
checksum = g_key_file_get_string (metakey, "Extra Data", checksum_key, error);
if (checksum == NULL)
return FALSE;
if (!ostree_validate_checksum_string (checksum, NULL))
{
g_set_error (error, G_KEY_FILE_ERROR,
G_KEY_FILE_ERROR_INVALID_VALUE,
_("Invalid format for sha256 checksum: '%s'"), checksum);
return FALSE;
}
size = g_key_file_get_uint64 (metakey, "Extra Data", size_key, error);
if (size == 0)
{
if (error != NULL && *error == NULL)
g_set_error (error, G_KEY_FILE_ERROR,
G_KEY_FILE_ERROR_INVALID_VALUE,
_("Extra data sizes of zero not supported"));
return FALSE;
}
installed_size = g_key_file_get_uint64 (metakey, "Extra Data", installed_size_key, NULL);
g_variant_builder_add (extra_data_sources_builder,
"(^aytt@ay&s)",
name, GUINT64_TO_BE (size), GUINT64_TO_BE (installed_size),
ostree_checksum_to_bytes_v (checksum), uri);
}
}
extra_data_sources = g_variant_ref_sink (g_variant_builder_end (extra_data_sources_builder));
g_variant_dict_insert_value (metadata_dict, "xa.extra-data-sources", extra_data_sources);
return TRUE;
}
gboolean
flatpak_builtin_build_export (int argc, char **argv, GCancellable *cancellable, GError **error)
{
gboolean ret = FALSE;
g_autoptr(GOptionContext) context = NULL;
g_autoptr(GFile) base = NULL;
g_autoptr(GFile) files = NULL;
g_autoptr(GFile) usr = NULL;
g_autoptr(GFile) metadata = NULL;
g_autoptr(GFile) export = NULL;
g_autoptr(GFile) repofile = NULL;
g_autoptr(GFile) root = NULL;
g_autoptr(OstreeRepo) repo = NULL;
const char *location;
const char *directory;
const char *branch;
g_autofree char *arch = NULL;
g_autofree char *full_branch = NULL;
g_autofree char *id = NULL;
g_autofree char *parent = NULL;
g_autofree char *commit_checksum = NULL;
g_autofree char *metadata_contents = NULL;
g_autofree char *format_size = NULL;
g_autoptr(OstreeMutableTree) mtree = NULL;
g_autoptr(OstreeMutableTree) files_mtree = NULL;
g_autoptr(OstreeMutableTree) export_mtree = NULL;
g_autoptr(GKeyFile) metakey = NULL;
g_autoptr(GError) my_error = NULL;
gsize metadata_size;
g_autofree char *subject = NULL;
g_autofree char *body = NULL;
OstreeRepoTransactionStats stats;
g_autoptr(OstreeRepoCommitModifier) modifier = NULL;
CommitData commit_data = {0};
g_auto(GVariantDict) metadata_dict = FLATPAK_VARIANT_DICT_INITIALIZER;
g_autoptr(GVariant) metadata_dict_v = NULL;
gboolean is_runtime = FALSE;
gboolean is_extension = FALSE;
guint64 installed_size = 0,download_size = 0;
GTimeVal ts;
const char *collection_id;
context = g_option_context_new (_("LOCATION DIRECTORY [BRANCH] - Create a repository from a build directory"));
g_option_context_set_translation_domain (context, GETTEXT_PACKAGE);
if (!flatpak_option_context_parse (context, options, &argc, &argv, FLATPAK_BUILTIN_FLAG_NO_DIR, NULL, cancellable, error))
goto out;
if (argc < 3)
{
usage_error (context, _("LOCATION and DIRECTORY must be specified"), error);
goto out;
}
if (argc > 4)
{
usage_error (context, _("Too many arguments"), error);
goto out;
}
location = argv[1];
directory = argv[2];
if (argc >= 4)
branch = argv[3];
else
branch = "master";
#ifdef FLATPAK_ENABLE_P2P
if (opt_collection_id != NULL &&
!ostree_validate_collection_id (opt_collection_id, &my_error))
{
flatpak_fail (error, _("%s is not a valid collection ID: %s"), opt_collection_id, my_error->message);
goto out;
}
#endif /* FLATPAK_ENABLE_P2P */
if (!flatpak_is_valid_branch (branch, &my_error))
{
flatpak_fail (error, _("'%s' is not a valid branch name: %s"), branch, my_error->message);
goto out;
}
base = g_file_new_for_commandline_arg (directory);
if (opt_files)
files = g_file_resolve_relative_path (base, opt_files);
else
files = g_file_get_child (base, "files");
if (opt_files)
usr = g_file_resolve_relative_path (base, opt_files);
else
usr = g_file_get_child (base, "usr");
if (opt_metadata)
metadata = g_file_resolve_relative_path (base, opt_metadata);
else
metadata = g_file_get_child (base, "metadata");
export = g_file_get_child (base, "export");
if (!g_file_query_exists (files, cancellable) ||
!g_file_query_exists (metadata, cancellable))
{
flatpak_fail (error, _("Build directory %s not initialized, use flatpak build-init"), directory);
goto out;
}
if (!g_file_load_contents (metadata, cancellable, &metadata_contents, &metadata_size, NULL, error))
goto out;
metakey = g_key_file_new ();
if (!g_key_file_load_from_data (metakey, metadata_contents, metadata_size, 0, error))
goto out;
id = g_key_file_get_string (metakey, "Application", "name", NULL);
if (id == NULL)
{
id = g_key_file_get_string (metakey, "Runtime", "name", NULL);
if (id == NULL)
{
flatpak_fail (error, _("No name specified in the metadata"));
goto out;
}
is_runtime = TRUE;
if (g_key_file_has_group (metakey, "ExtensionOf"))
is_extension = TRUE;
}
if (!(opt_runtime || is_runtime) && !g_file_query_exists (export, cancellable))
{
flatpak_fail (error, "Build directory %s not finalized, use flatpak build-finish", directory);
goto out;
}
g_variant_dict_init (&metadata_dict, NULL);
if (!collect_extra_data (metakey, &metadata_dict, error))
goto out;
if (!(opt_runtime || is_runtime) &&
!validate_exports (export, files, id, error))
goto out;
if (!metadata_get_arch (metadata, &arch, error))
goto out;
if (opt_subject)
subject = g_strdup (opt_subject);
else
subject = g_strconcat ("Export ", id, NULL);
if (opt_body)
body = g_strdup (opt_body);
else
body = g_strdup_printf ("Name: %s\n"
"Arch: %s\n"
"Branch: %s\n"
"Built with: "PACKAGE_STRING"\n",
id, arch, branch);
full_branch = g_strconcat ((opt_runtime || is_runtime) ? "runtime/" : "app/", id, "/", arch, "/", branch, NULL);
repofile = g_file_new_for_commandline_arg (location);
repo = ostree_repo_new (repofile);
if (g_file_query_exists (repofile, cancellable) &&
!is_empty_directory (repofile, cancellable))
{
if (!ostree_repo_open (repo, cancellable, error))
goto out;
if (!ostree_repo_resolve_rev (repo, full_branch, TRUE, &parent, error))
goto out;
#ifdef FLATPAK_ENABLE_P2P
if (opt_collection_id != NULL &&
g_strcmp0 (ostree_repo_get_collection_id (repo), opt_collection_id) != 0)
{
flatpak_fail (error, "Specified collection ID %s doesnt match collection ID in repository configuration %s.",
opt_collection_id, ostree_repo_get_collection_id (repo));
goto out;
}
#endif /* FLATPAK_ENABLE_P2P */
}
else
{
#ifdef FLATPAK_ENABLE_P2P
if (opt_collection_id != NULL &&
!ostree_repo_set_collection_id (repo, opt_collection_id, error))
goto out;
#endif /* FLATPAK_ENABLE_P2P */
if (!ostree_repo_create (repo, OSTREE_REPO_MODE_ARCHIVE_Z2, cancellable, error))
goto out;
}
/* Get the canonical collection ID which well use for the commit. This might
* be %NULL if the existing repo doesnt have one and none was specified on
* the command line. */
#ifdef FLATPAK_ENABLE_P2P
collection_id = ostree_repo_get_collection_id (repo);
#else /* if !FLATPAK_ENABLE_P2P */
collection_id = NULL;
#endif /* !FLATPAK_ENABLE_P2P */
if (!ostree_repo_prepare_transaction (repo, NULL, cancellable, error))
goto out;
mtree = ostree_mutable_tree_new ();
if (!flatpak_mtree_create_root (repo, mtree, cancellable, error))
goto out;
if (!ostree_mutable_tree_ensure_dir (mtree, "files", &files_mtree, error))
goto out;
modifier = ostree_repo_commit_modifier_new (OSTREE_REPO_COMMIT_MODIFIER_FLAGS_SKIP_XATTRS,
(OstreeRepoCommitFilter) commit_filter, &commit_data, NULL);
if (is_extension)
{
commit_data.exclude = (const char **) opt_exclude;
commit_data.include = (const char **) opt_include;
if (!ostree_repo_write_directory_to_mtree (repo, files, files_mtree, modifier, cancellable, error))
goto out;
commit_data.exclude = NULL;
commit_data.include = NULL;
}
else if (opt_runtime || is_runtime)
{
commit_data.exclude = (const char **) opt_exclude;
commit_data.include = (const char **) opt_include;
if (!ostree_repo_write_directory_to_mtree (repo, usr, files_mtree, modifier, cancellable, error))
goto out;
commit_data.exclude = NULL;
commit_data.include = NULL;
}
else
{
commit_data.exclude = (const char **) opt_exclude;
commit_data.include = (const char **) opt_include;
if (!ostree_repo_write_directory_to_mtree (repo, files, files_mtree, modifier, cancellable, error))
goto out;
commit_data.exclude = NULL;
commit_data.include = NULL;
if (!ostree_mutable_tree_ensure_dir (mtree, "export", &export_mtree, error))
goto out;
if (!ostree_repo_write_directory_to_mtree (repo, export, export_mtree, modifier, cancellable, error))
goto out;
}
if (!add_file_to_mtree (metadata, "metadata", repo, mtree, cancellable, error))
goto out;
if (!ostree_repo_write_mtree (repo, mtree, &root, cancellable, error))
goto out;
if (!flatpak_repo_collect_sizes (repo, root, &installed_size, &download_size, cancellable, error))
goto out;
/* Binding information. xa.ref is deprecated in favour of the OSTree keys, but
* keep it around for backwards compatibility. Write the bindings even if
* were compiled without P2P support, since other flatpak builds might be. */
g_variant_dict_insert_value (&metadata_dict, "ostree.collection-binding",
g_variant_new_string ((collection_id != NULL) ? collection_id : ""));
g_variant_dict_insert_value (&metadata_dict, "ostree.ref-binding",
g_variant_new_strv ((const gchar * const *) &full_branch, 1));
g_variant_dict_insert_value (&metadata_dict, "xa.ref", g_variant_new_string (full_branch));
g_variant_dict_insert_value (&metadata_dict, "xa.metadata", g_variant_new_string (metadata_contents));
g_variant_dict_insert_value (&metadata_dict, "xa.installed-size", g_variant_new_uint64 (GUINT64_TO_BE (installed_size)));
g_variant_dict_insert_value (&metadata_dict, "xa.download-size", g_variant_new_uint64 (GUINT64_TO_BE (download_size)));
metadata_dict_v = g_variant_ref_sink (g_variant_dict_end (&metadata_dict));
/* required for the metadata and the AppStream commits */
if (opt_timestamp != NULL)
{
if (!g_time_val_from_iso8601 (opt_timestamp, &ts))
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Could not parse '%s'", opt_timestamp);
goto out;
}
}
if (opt_timestamp == NULL)
{
if (!ostree_repo_write_commit (repo, parent, subject, body, metadata_dict_v,
OSTREE_REPO_FILE (root),
&commit_checksum, cancellable, error))
goto out;
}
else
{
if (!ostree_repo_write_commit_with_time (repo, parent, subject, body,
metadata_dict_v,
OSTREE_REPO_FILE (root),
ts.tv_sec, &commit_checksum,
cancellable, error))
goto out;
}
if (opt_gpg_key_ids)
{
char **iter;
for (iter = opt_gpg_key_ids; iter && *iter; iter++)
{
const char *keyid = *iter;
if (!ostree_repo_sign_commit (repo,
commit_checksum,
keyid,
opt_gpg_homedir,
cancellable,
error))
goto out;
}
}
#ifdef FLATPAK_ENABLE_P2P
if (collection_id != NULL)
{
OstreeCollectionRef ref = { (char *) collection_id, full_branch };
ostree_repo_transaction_set_collection_ref (repo, &ref, commit_checksum);
}
else
#endif /* FLATPAK_ENABLE_P2P */
{
ostree_repo_transaction_set_ref (repo, NULL, full_branch, commit_checksum);
}
if (!ostree_repo_commit_transaction (repo, &stats, cancellable, error))
goto out;
if (opt_update_appstream &&
!flatpak_repo_generate_appstream (repo, (const char **) opt_gpg_key_ids, opt_gpg_homedir,
ts.tv_sec, cancellable, error))
return FALSE;
if (!opt_no_update_summary &&
!flatpak_repo_update (repo,
(const char **) opt_gpg_key_ids,
opt_gpg_homedir,
cancellable,
error))
goto out;
format_size = g_format_size (stats.content_bytes_written);
g_print ("Commit: %s\n", commit_checksum);
g_print ("Metadata Total: %u\n", stats.metadata_objects_total);
g_print ("Metadata Written: %u\n", stats.metadata_objects_written);
g_print ("Content Total: %u\n", stats.content_objects_total);
g_print ("Content Written: %u\n", stats.content_objects_written);
g_print ("Content Bytes Written: %" G_GUINT64_FORMAT " (%s)\n", stats.content_bytes_written, format_size);
ret = TRUE;
out:
if (repo)
ostree_repo_abort_transaction (repo, cancellable, NULL);
return ret;
}
gboolean
flatpak_complete_build_export (FlatpakCompletion *completion)
{
g_autoptr(GOptionContext) context = NULL;
context = g_option_context_new ("");
if (!flatpak_option_context_parse (context, options, &completion->argc, &completion->argv,
FLATPAK_BUILTIN_FLAG_NO_DIR, NULL, NULL, NULL))
return FALSE;
switch (completion->argc)
{
case 0:
case 1: /* LOCATION */
flatpak_complete_options (completion, global_entries);
flatpak_complete_options (completion, options);
flatpak_complete_dir (completion);
break;
case 2: /* DIR */
flatpak_complete_dir (completion);
break;
}
return TRUE;
}