/* builder-manifest.c * * Copyright (C) 2015 Red Hat, Inc * * This file 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 of the * License, or (at your option) any later version. * * This file 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 program. If not, see . * * Authors: * Alexander Larsson */ #include "config.h" #include #include #include #include #include #include #include "builder-manifest.h" #include "builder-utils.h" #include "builder-flatpak-utils.h" #include "builder-post-process.h" #include "builder-extension.h" #include #include "libglnx/libglnx.h" #define LOCALES_SEPARATE_DIR "share/runtime/locale" static GFile *demarshal_base_dir = NULL; void builder_manifest_set_demarshal_base_dir (GFile *dir) { g_set_object (&demarshal_base_dir, dir); } GFile * builder_manifest_get_demarshal_base_dir (void) { return g_object_ref (demarshal_base_dir); } struct BuilderManifest { GObject parent; char *id; char *id_platform; char *branch; char *default_branch; char *collection_id; gint32 token_type; char *extension_tag; char *type; char *runtime; char *runtime_commit; char *runtime_version; char *sdk; char *sdk_commit; char *var; char *base; char *base_commit; char *base_version; char **base_extensions; char *metadata; char *metadata_platform; gboolean separate_locales; char **cleanup; char **cleanup_commands; char **cleanup_platform; char **cleanup_platform_commands; char **prepare_platform_commands; char **finish_args; char **inherit_extensions; char **inherit_sdk_extensions; char **tags; char *rename_desktop_file; char *rename_appdata_file; char *appdata_license; char *rename_icon; gboolean copy_icon; char *desktop_file_name_prefix; char *desktop_file_name_suffix; gboolean build_runtime; gboolean build_extension; gboolean writable_sdk; gboolean appstream_compose; char **sdk_extensions; char **platform_extensions; char *command; BuilderOptions *build_options; GList *modules; GList *expanded_modules; GList *add_extensions; GList *add_build_extensions; }; typedef struct { GObjectClass parent_class; } BuilderManifestClass; static void serializable_iface_init (JsonSerializableIface *serializable_iface); G_DEFINE_TYPE_WITH_CODE (BuilderManifest, builder_manifest, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (JSON_TYPE_SERIALIZABLE, serializable_iface_init)); enum { PROP_0, PROP_APP_ID, /* Backwards compat with early version, use id */ PROP_ID, PROP_ID_PLATFORM, PROP_BRANCH, PROP_DEFAULT_BRANCH, PROP_RUNTIME, PROP_RUNTIME_VERSION, PROP_RUNTIME_COMMIT, PROP_SDK, PROP_SDK_COMMIT, PROP_BASE, PROP_BASE_VERSION, PROP_BASE_COMMIT, PROP_BASE_EXTENSIONS, PROP_VAR, PROP_METADATA, PROP_METADATA_PLATFORM, PROP_BUILD_OPTIONS, PROP_COMMAND, PROP_MODULES, PROP_CLEANUP, PROP_CLEANUP_COMMANDS, PROP_CLEANUP_PLATFORM_COMMANDS, PROP_CLEANUP_PLATFORM, PROP_PREPARE_PLATFORM_COMMANDS, PROP_BUILD_RUNTIME, PROP_BUILD_EXTENSION, PROP_SEPARATE_LOCALES, PROP_WRITABLE_SDK, PROP_APPSTREAM_COMPOSE, PROP_SDK_EXTENSIONS, PROP_PLATFORM_EXTENSIONS, PROP_FINISH_ARGS, PROP_INHERIT_EXTENSIONS, PROP_INHERIT_SDK_EXTENSIONS, PROP_TAGS, PROP_RENAME_DESKTOP_FILE, PROP_RENAME_APPDATA_FILE, PROP_APPDATA_LICENSE, PROP_RENAME_ICON, PROP_COPY_ICON, PROP_DESKTOP_FILE_NAME_PREFIX, PROP_DESKTOP_FILE_NAME_SUFFIX, PROP_COLLECTION_ID, PROP_ADD_EXTENSIONS, PROP_ADD_BUILD_EXTENSIONS, PROP_EXTENSION_TAG, PROP_TOKEN_TYPE, LAST_PROP }; static void builder_manifest_finalize (GObject *object) { BuilderManifest *self = (BuilderManifest *) object; g_free (self->id); g_free (self->branch); g_free (self->default_branch); g_free (self->collection_id); g_free (self->extension_tag); g_free (self->runtime); g_free (self->runtime_commit); g_free (self->runtime_version); g_free (self->sdk); g_free (self->sdk_commit); g_free (self->base); g_free (self->base_commit); g_free (self->base_version); g_free (self->var); g_free (self->metadata); g_free (self->metadata_platform); g_free (self->command); g_clear_object (&self->build_options); g_list_free_full (self->modules, g_object_unref); g_list_free_full (self->add_extensions, g_object_unref); g_list_free_full (self->add_build_extensions, g_object_unref); g_list_free (self->expanded_modules); g_strfreev (self->cleanup); g_strfreev (self->cleanup_commands); g_strfreev (self->cleanup_platform); g_strfreev (self->cleanup_platform_commands); g_strfreev (self->prepare_platform_commands); g_strfreev (self->finish_args); g_strfreev (self->inherit_extensions); g_strfreev (self->inherit_sdk_extensions); g_strfreev (self->tags); g_free (self->rename_desktop_file); g_free (self->rename_appdata_file); g_free (self->appdata_license); g_free (self->rename_icon); g_free (self->desktop_file_name_prefix); g_free (self->desktop_file_name_suffix); G_OBJECT_CLASS (builder_manifest_parent_class)->finalize (object); } static gboolean expand_modules (BuilderContext *context, GList *modules, GList **expanded, GHashTable *names, GError **error) { GList *l; for (l = modules; l; l = l->next) { BuilderModule *m = l->data; GList *submodules = NULL; g_autofree char *new_name = NULL; int new_name_counter; const char *orig_name; const char *name; if (!builder_module_is_enabled (m, context)) continue; if (!expand_modules (context, builder_module_get_modules (m), &submodules, names, error)) return FALSE; *expanded = g_list_concat (*expanded, submodules); name = builder_module_get_name (m); if (name == NULL) { /* FIXME: We'd like to report *something* for the user to locate the errornous module definition. */ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Module has no 'name' attribute set"); return FALSE; } orig_name = name; /* Duplicated name happen sometimes, like e.g. when including snippets out of your control. * It is not a huge problem for building, but we need unique names for e.g the cache, so * uniquify on collision */ new_name_counter = 2; while (g_hash_table_lookup (names, name) != NULL) { g_free (new_name); new_name = g_strdup_printf ("%s-%d", orig_name, new_name_counter++); name = new_name; } if (name != orig_name) builder_module_set_name (m, name); g_hash_table_insert (names, (char *)name, (char *)name); *expanded = g_list_append (*expanded, m); } return TRUE; } static void builder_manifest_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { BuilderManifest *self = BUILDER_MANIFEST (object); switch (prop_id) { case PROP_APP_ID: g_value_set_string (value, NULL); break; case PROP_ID: g_value_set_string (value, self->id); break; case PROP_ID_PLATFORM: g_value_set_string (value, self->id_platform); break; case PROP_BRANCH: g_value_set_string (value, self->branch); break; case PROP_DEFAULT_BRANCH: g_value_set_string (value, self->default_branch); break; case PROP_RUNTIME: g_value_set_string (value, self->runtime); break; case PROP_RUNTIME_COMMIT: g_value_set_string (value, self->runtime_commit); break; case PROP_RUNTIME_VERSION: g_value_set_string (value, self->runtime_version); break; case PROP_SDK: g_value_set_string (value, self->sdk); break; case PROP_SDK_COMMIT: g_value_set_string (value, self->sdk_commit); break; case PROP_BASE: g_value_set_string (value, self->base); break; case PROP_BASE_COMMIT: g_value_set_string (value, self->base_commit); break; case PROP_BASE_VERSION: g_value_set_string (value, self->base_version); break; case PROP_BASE_EXTENSIONS: g_value_set_boxed (value, self->base_extensions); break; case PROP_VAR: g_value_set_string (value, self->var); break; case PROP_METADATA: g_value_set_string (value, self->metadata); break; case PROP_METADATA_PLATFORM: g_value_set_string (value, self->metadata_platform); break; case PROP_COMMAND: g_value_set_string (value, self->command); break; case PROP_BUILD_OPTIONS: g_value_set_object (value, self->build_options); break; case PROP_MODULES: g_value_set_pointer (value, self->modules); break; case PROP_ADD_EXTENSIONS: g_value_set_pointer (value, self->add_extensions); break; case PROP_ADD_BUILD_EXTENSIONS: g_value_set_pointer (value, self->add_build_extensions); break; case PROP_CLEANUP: g_value_set_boxed (value, self->cleanup); break; case PROP_CLEANUP_COMMANDS: g_value_set_boxed (value, self->cleanup_commands); break; case PROP_CLEANUP_PLATFORM: g_value_set_boxed (value, self->cleanup_platform); break; case PROP_CLEANUP_PLATFORM_COMMANDS: g_value_set_boxed (value, self->cleanup_platform_commands); break; case PROP_PREPARE_PLATFORM_COMMANDS: g_value_set_boxed (value, self->prepare_platform_commands); break; case PROP_FINISH_ARGS: g_value_set_boxed (value, self->finish_args); break; case PROP_INHERIT_EXTENSIONS: g_value_set_boxed (value, self->inherit_extensions); break; case PROP_INHERIT_SDK_EXTENSIONS: g_value_set_boxed (value, self->inherit_sdk_extensions); break; case PROP_TAGS: g_value_set_boxed (value, self->tags); break; case PROP_BUILD_RUNTIME: g_value_set_boolean (value, self->build_runtime); break; case PROP_BUILD_EXTENSION: g_value_set_boolean (value, self->build_extension); break; case PROP_SEPARATE_LOCALES: g_value_set_boolean (value, self->separate_locales); break; case PROP_WRITABLE_SDK: g_value_set_boolean (value, self->writable_sdk); break; case PROP_APPSTREAM_COMPOSE: g_value_set_boolean (value, self->appstream_compose); break; case PROP_SDK_EXTENSIONS: g_value_set_boxed (value, self->sdk_extensions); break; case PROP_PLATFORM_EXTENSIONS: g_value_set_boxed (value, self->platform_extensions); break; case PROP_COPY_ICON: g_value_set_boolean (value, self->copy_icon); break; case PROP_RENAME_DESKTOP_FILE: g_value_set_string (value, self->rename_desktop_file); break; case PROP_RENAME_APPDATA_FILE: g_value_set_string (value, self->rename_appdata_file); break; case PROP_APPDATA_LICENSE: g_value_set_string (value, self->appdata_license); break; case PROP_RENAME_ICON: g_value_set_string (value, self->rename_icon); break; case PROP_DESKTOP_FILE_NAME_PREFIX: g_value_set_string (value, self->desktop_file_name_prefix); break; case PROP_DESKTOP_FILE_NAME_SUFFIX: g_value_set_string (value, self->desktop_file_name_suffix); break; case PROP_COLLECTION_ID: g_value_set_string (value, self->collection_id); break; case PROP_EXTENSION_TAG: g_value_set_string (value, self->extension_tag); break; case PROP_TOKEN_TYPE: g_value_set_int (value, (int)self->token_type); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void builder_manifest_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { BuilderManifest *self = BUILDER_MANIFEST (object); gchar **tmp; switch (prop_id) { case PROP_APP_ID: g_free (self->id); self->id = g_value_dup_string (value); break; case PROP_ID: g_free (self->id); self->id = g_value_dup_string (value); break; case PROP_ID_PLATFORM: g_free (self->id_platform); self->id_platform = g_value_dup_string (value); break; case PROP_BRANCH: g_free (self->branch); self->branch = g_value_dup_string (value); break; case PROP_DEFAULT_BRANCH: g_free (self->default_branch); self->default_branch = g_value_dup_string (value); break; case PROP_RUNTIME: g_free (self->runtime); self->runtime = g_value_dup_string (value); break; case PROP_RUNTIME_COMMIT: g_free (self->runtime_commit); self->runtime_commit = g_value_dup_string (value); break; case PROP_RUNTIME_VERSION: g_free (self->runtime_version); self->runtime_version = g_value_dup_string (value); break; case PROP_SDK: g_free (self->sdk); self->sdk = g_value_dup_string (value); break; case PROP_SDK_COMMIT: g_free (self->sdk_commit); self->sdk_commit = g_value_dup_string (value); break; case PROP_BASE: g_free (self->base); self->base = g_value_dup_string (value); break; case PROP_BASE_COMMIT: g_free (self->base_commit); self->base_commit = g_value_dup_string (value); break; case PROP_BASE_VERSION: g_free (self->base_version); self->base_version = g_value_dup_string (value); break; case PROP_BASE_EXTENSIONS: tmp = self->base_extensions; self->base_extensions = g_strdupv (g_value_get_boxed (value)); g_strfreev (tmp); break; case PROP_VAR: g_free (self->var); self->var = g_value_dup_string (value); break; case PROP_METADATA: g_free (self->metadata); self->metadata = g_value_dup_string (value); break; case PROP_METADATA_PLATFORM: g_free (self->metadata_platform); self->metadata_platform = g_value_dup_string (value); break; case PROP_COMMAND: g_free (self->command); self->command = g_value_dup_string (value); break; case PROP_BUILD_OPTIONS: g_set_object (&self->build_options, g_value_get_object (value)); break; case PROP_MODULES: g_list_free_full (self->modules, g_object_unref); /* NOTE: This takes ownership of the list! */ self->modules = g_value_get_pointer (value); break; case PROP_ADD_EXTENSIONS: g_list_free_full (self->add_extensions, g_object_unref); /* NOTE: This takes ownership of the list! */ self->add_extensions = g_value_get_pointer (value); break; case PROP_ADD_BUILD_EXTENSIONS: g_list_free_full (self->add_build_extensions, g_object_unref); /* NOTE: This takes ownership of the list! */ self->add_build_extensions = g_value_get_pointer (value); break; case PROP_CLEANUP: tmp = self->cleanup; self->cleanup = g_strdupv (g_value_get_boxed (value)); g_strfreev (tmp); break; case PROP_CLEANUP_COMMANDS: tmp = self->cleanup_commands; self->cleanup_commands = g_strdupv (g_value_get_boxed (value)); g_strfreev (tmp); break; case PROP_CLEANUP_PLATFORM: tmp = self->cleanup_platform; self->cleanup_platform = g_strdupv (g_value_get_boxed (value)); g_strfreev (tmp); break; case PROP_CLEANUP_PLATFORM_COMMANDS: tmp = self->cleanup_platform_commands; self->cleanup_platform_commands = g_strdupv (g_value_get_boxed (value)); g_strfreev (tmp); break; case PROP_PREPARE_PLATFORM_COMMANDS: tmp = self->prepare_platform_commands; self->prepare_platform_commands = g_strdupv (g_value_get_boxed (value)); g_strfreev (tmp); break; case PROP_FINISH_ARGS: tmp = self->finish_args; self->finish_args = g_strdupv (g_value_get_boxed (value)); g_strfreev (tmp); break; case PROP_INHERIT_EXTENSIONS: tmp = self->inherit_extensions; self->inherit_extensions = g_strdupv (g_value_get_boxed (value)); g_strfreev (tmp); break; case PROP_INHERIT_SDK_EXTENSIONS: tmp = self->inherit_sdk_extensions; self->inherit_sdk_extensions = g_strdupv (g_value_get_boxed (value)); g_strfreev (tmp); break; case PROP_TAGS: tmp = self->tags; self->tags = g_strdupv (g_value_get_boxed (value)); g_strfreev (tmp); break; case PROP_BUILD_RUNTIME: self->build_runtime = g_value_get_boolean (value); break; case PROP_BUILD_EXTENSION: self->build_extension = g_value_get_boolean (value); break; case PROP_SEPARATE_LOCALES: self->separate_locales = g_value_get_boolean (value); break; case PROP_WRITABLE_SDK: self->writable_sdk = g_value_get_boolean (value); break; case PROP_APPSTREAM_COMPOSE: self->appstream_compose = g_value_get_boolean (value); break; case PROP_SDK_EXTENSIONS: tmp = self->sdk_extensions; self->sdk_extensions = g_strdupv (g_value_get_boxed (value)); g_strfreev (tmp); break; case PROP_PLATFORM_EXTENSIONS: tmp = self->platform_extensions; self->platform_extensions = g_strdupv (g_value_get_boxed (value)); g_strfreev (tmp); break; case PROP_COPY_ICON: self->copy_icon = g_value_get_boolean (value); break; case PROP_RENAME_DESKTOP_FILE: g_free (self->rename_desktop_file); self->rename_desktop_file = g_value_dup_string (value); break; case PROP_RENAME_APPDATA_FILE: g_free (self->rename_appdata_file); self->rename_appdata_file = g_value_dup_string (value); break; case PROP_APPDATA_LICENSE: g_free (self->appdata_license); self->appdata_license = g_value_dup_string (value); break; case PROP_RENAME_ICON: g_free (self->rename_icon); self->rename_icon = g_value_dup_string (value); break; case PROP_DESKTOP_FILE_NAME_PREFIX: g_free (self->desktop_file_name_prefix); self->desktop_file_name_prefix = g_value_dup_string (value); break; case PROP_DESKTOP_FILE_NAME_SUFFIX: g_free (self->desktop_file_name_suffix); self->desktop_file_name_suffix = g_value_dup_string (value); break; case PROP_COLLECTION_ID: g_free (self->collection_id); self->collection_id = g_value_dup_string (value); break; case PROP_EXTENSION_TAG: g_free (self->extension_tag); self->extension_tag = g_value_dup_string (value); break; case PROP_TOKEN_TYPE: self->token_type = (gint32)g_value_get_int (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void builder_manifest_class_init (BuilderManifestClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = builder_manifest_finalize; object_class->get_property = builder_manifest_get_property; object_class->set_property = builder_manifest_set_property; g_object_class_install_property (object_class, PROP_APP_ID, g_param_spec_string ("app-id", "", "", NULL, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_ID, g_param_spec_string ("id", "", "", NULL, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_ID_PLATFORM, g_param_spec_string ("id-platform", "", "", NULL, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_BRANCH, g_param_spec_string ("branch", "", "", NULL, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_DEFAULT_BRANCH, g_param_spec_string ("default-branch", "", "", NULL, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_RUNTIME, g_param_spec_string ("runtime", "", "", NULL, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_RUNTIME_COMMIT, g_param_spec_string ("runtime-commit", "", "", NULL, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_RUNTIME_VERSION, g_param_spec_string ("runtime-version", "", "", NULL, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_SDK, g_param_spec_string ("sdk", "", "", NULL, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_SDK_COMMIT, g_param_spec_string ("sdk-commit", "", "", NULL, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_BASE, g_param_spec_string ("base", "", "", NULL, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_BASE_COMMIT, g_param_spec_string ("base-commit", "", "", NULL, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_BASE_VERSION, g_param_spec_string ("base-version", "", "", NULL, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_BASE_EXTENSIONS, g_param_spec_boxed ("base-extensions", "", "", G_TYPE_STRV, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_VAR, g_param_spec_string ("var", "", "", NULL, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_METADATA, g_param_spec_string ("metadata", "", "", NULL, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_METADATA_PLATFORM, g_param_spec_string ("metadata-platform", "", "", NULL, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_COMMAND, g_param_spec_string ("command", "", "", NULL, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_BUILD_OPTIONS, g_param_spec_object ("build-options", "", "", BUILDER_TYPE_OPTIONS, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_MODULES, g_param_spec_pointer ("modules", "", "", G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_ADD_EXTENSIONS, g_param_spec_pointer ("add-extensions", "", "", G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_ADD_BUILD_EXTENSIONS, g_param_spec_pointer ("add-build-extensions", "", "", G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_CLEANUP, g_param_spec_boxed ("cleanup", "", "", G_TYPE_STRV, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_CLEANUP_COMMANDS, g_param_spec_boxed ("cleanup-commands", "", "", G_TYPE_STRV, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_CLEANUP_PLATFORM, g_param_spec_boxed ("cleanup-platform", "", "", G_TYPE_STRV, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_CLEANUP_PLATFORM_COMMANDS, g_param_spec_boxed ("cleanup-platform-commands", "", "", G_TYPE_STRV, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_PREPARE_PLATFORM_COMMANDS, g_param_spec_boxed ("prepare-platform-commands", "", "", G_TYPE_STRV, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_FINISH_ARGS, g_param_spec_boxed ("finish-args", "", "", G_TYPE_STRV, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_INHERIT_EXTENSIONS, g_param_spec_boxed ("inherit-extensions", "", "", G_TYPE_STRV, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_INHERIT_SDK_EXTENSIONS, g_param_spec_boxed ("inherit-sdk-extensions", "", "", G_TYPE_STRV, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_BUILD_RUNTIME, g_param_spec_boolean ("build-runtime", "", "", FALSE, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_BUILD_EXTENSION, g_param_spec_boolean ("build-extension", "", "", FALSE, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_SEPARATE_LOCALES, g_param_spec_boolean ("separate-locales", "", "", TRUE, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_WRITABLE_SDK, g_param_spec_boolean ("writable-sdk", "", "", FALSE, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_APPSTREAM_COMPOSE, g_param_spec_boolean ("appstream-compose", "", "", TRUE, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_SDK_EXTENSIONS, g_param_spec_boxed ("sdk-extensions", "", "", G_TYPE_STRV, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_PLATFORM_EXTENSIONS, g_param_spec_boxed ("platform-extensions", "", "", G_TYPE_STRV, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_TAGS, g_param_spec_boxed ("tags", "", "", G_TYPE_STRV, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_RENAME_DESKTOP_FILE, g_param_spec_string ("rename-desktop-file", "", "", NULL, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_RENAME_APPDATA_FILE, g_param_spec_string ("rename-appdata-file", "", "", NULL, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_APPDATA_LICENSE, g_param_spec_string ("appdata-license", "", "", NULL, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_RENAME_ICON, g_param_spec_string ("rename-icon", "", "", NULL, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_COPY_ICON, g_param_spec_boolean ("copy-icon", "", "", FALSE, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_DESKTOP_FILE_NAME_PREFIX, g_param_spec_string ("desktop-file-name-prefix", "", "", NULL, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_DESKTOP_FILE_NAME_SUFFIX, g_param_spec_string ("desktop-file-name-suffix", "", "", NULL, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_COLLECTION_ID, g_param_spec_string ("collection-id", "", "", NULL, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_EXTENSION_TAG, g_param_spec_string ("extension-tag", "", "", NULL, G_PARAM_READWRITE)); g_object_class_install_property (object_class, PROP_TOKEN_TYPE, g_param_spec_int ("token-type", "", "", -1, G_MAXINT32, -1, G_PARAM_READWRITE)); } static void builder_manifest_init (BuilderManifest *self) { self->token_type = -1; self->appstream_compose = TRUE; self->separate_locales = TRUE; } static JsonNode * builder_manifest_serialize_property (JsonSerializable *serializable, const gchar *property_name, const GValue *value, GParamSpec *pspec) { if (strcmp (property_name, "modules") == 0) { BuilderManifest *self = BUILDER_MANIFEST (serializable); JsonNode *retval = NULL; GList *l; if (self->modules) { JsonArray *array; array = json_array_sized_new (g_list_length (self->modules)); for (l = self->modules; l != NULL; l = l->next) { JsonNode *child = json_gobject_serialize (l->data); json_array_add_element (array, child); } retval = json_node_init_array (json_node_alloc (), array); json_array_unref (array); } return retval; } else if (strcmp (property_name, "add-extensions") == 0 || strcmp (property_name, "add-build-extensions") == 0) { BuilderManifest *self = BUILDER_MANIFEST (serializable); JsonNode *retval = NULL; GList *extensions; if (strcmp (property_name, "add-extensions") == 0) extensions = self->add_extensions; else extensions = self->add_build_extensions; if (extensions) { JsonObject *object; GList *l; object = json_object_new (); for (l = extensions; l != NULL; l = l->next) { BuilderExtension *e = l->data; JsonNode *child = json_gobject_serialize (G_OBJECT (e)); json_object_set_member (object, (char *) builder_extension_get_name (e), child); } retval = json_node_init_object (json_node_alloc (), object); json_object_unref (object); } return retval; } else { return builder_serializable_serialize_property (serializable, property_name, value, pspec); } } static gint sort_extension (gconstpointer a, gconstpointer b) { return strcmp (builder_extension_get_name (BUILDER_EXTENSION (a)), builder_extension_get_name (BUILDER_EXTENSION (b))); } static gboolean builder_manifest_deserialize_property (JsonSerializable *serializable, const gchar *property_name, GValue *value, GParamSpec *pspec, JsonNode *property_node) { if (strcmp (property_name, "modules") == 0) { if (JSON_NODE_TYPE (property_node) == JSON_NODE_NULL) { g_value_set_pointer (value, NULL); return TRUE; } else if (JSON_NODE_TYPE (property_node) == JSON_NODE_ARRAY) { JsonArray *array = json_node_get_array (property_node); guint i, array_len = json_array_get_length (array); g_autoptr(GFile) saved_demarshal_base_dir = builder_manifest_get_demarshal_base_dir (); GList *modules = NULL; GObject *module; for (i = 0; i < array_len; i++) { JsonNode *element_node = json_array_get_element (array, i); module = NULL; if (JSON_NODE_HOLDS_VALUE (element_node) && json_node_get_value_type (element_node) == G_TYPE_STRING) { const char *module_relpath = json_node_get_string (element_node); g_autoptr(GFile) module_file = g_file_resolve_relative_path (demarshal_base_dir, module_relpath); const char *module_path = flatpak_file_get_path_cached (module_file); g_autofree char *module_contents = NULL; g_autoptr(GError) error = NULL; if (g_file_get_contents (module_path, &module_contents, NULL, &error)) { g_autoptr(GFile) module_file_dir = g_file_get_parent (module_file); builder_manifest_set_demarshal_base_dir (module_file_dir); module = builder_gobject_from_data (BUILDER_TYPE_MODULE, module_relpath, module_contents, &error); builder_manifest_set_demarshal_base_dir (saved_demarshal_base_dir); if (module) { builder_module_set_json_path (BUILDER_MODULE (module), module_path); builder_module_set_base_dir (BUILDER_MODULE (module), module_file_dir); } } if (error != NULL) { g_error ("Failed to load included manifest (%s): %s", module_path, error->message); } } else if (JSON_NODE_HOLDS_OBJECT (element_node)) { module = json_gobject_deserialize (BUILDER_TYPE_MODULE, element_node); if (module != NULL) builder_module_set_base_dir (BUILDER_MODULE (module), saved_demarshal_base_dir); } if (module == NULL) { g_list_free_full (modules, g_object_unref); return FALSE; } modules = g_list_prepend (modules, module); } g_value_set_pointer (value, g_list_reverse (modules)); return TRUE; } return FALSE; } else if (strcmp (property_name, "add-extensions") == 0 || strcmp (property_name, "add-build-extensions") == 0) { if (JSON_NODE_TYPE (property_node) == JSON_NODE_NULL) { g_value_set_pointer (value, NULL); return TRUE; } else if (JSON_NODE_TYPE (property_node) == JSON_NODE_OBJECT) { JsonObject *object = json_node_get_object (property_node); g_autoptr(GHashTable) hash = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_object_unref); g_autoptr(GList) members = NULL; GList *extensions; GList *l; members = json_object_get_members (object); for (l = members; l != NULL; l = l->next) { const char *member_name = l->data; JsonNode *val; GObject *extension; val = json_object_get_member (object, member_name); extension = json_gobject_deserialize (BUILDER_TYPE_EXTENSION, val); if (extension == NULL) return FALSE; builder_extension_set_name (BUILDER_EXTENSION (extension), member_name); g_hash_table_insert (hash, (char *)builder_extension_get_name (BUILDER_EXTENSION (extension)), extension); } extensions = g_hash_table_get_values (hash); g_hash_table_steal_all (hash); extensions = g_list_sort (extensions, sort_extension); g_value_set_pointer (value, extensions); return TRUE; } return FALSE; } else { return builder_serializable_deserialize_property (serializable, property_name, value, pspec, property_node); } } static void serializable_iface_init (JsonSerializableIface *serializable_iface) { serializable_iface->serialize_property = builder_manifest_serialize_property; serializable_iface->deserialize_property = builder_manifest_deserialize_property; serializable_iface->find_property = builder_serializable_find_property; serializable_iface->list_properties = builder_serializable_list_properties; serializable_iface->set_property = builder_serializable_set_property; serializable_iface->get_property = builder_serializable_get_property; } char * builder_manifest_serialize (BuilderManifest *self) { JsonNode *node; JsonGenerator *generator; char *json; node = json_gobject_serialize (G_OBJECT (self)); generator = json_generator_new (); json_generator_set_pretty (generator, TRUE); json_generator_set_root (generator, node); json = json_generator_to_data (generator, NULL); g_object_unref (generator); json_node_free (node); return json; } const char * builder_manifest_get_id (BuilderManifest *self) { return self->id; } char * builder_manifest_get_locale_id (BuilderManifest *self) { g_autofree char *id = flatpak_make_valid_id_prefix (self->id); return g_strdup_printf ("%s.Locale", id); } char * builder_manifest_get_debug_id (BuilderManifest *self) { g_autofree char *id = flatpak_make_valid_id_prefix (self->id); return g_strdup_printf ("%s.Debug", id); } char * builder_manifest_get_sources_id (BuilderManifest *self) { g_autofree char *id = flatpak_make_valid_id_prefix (self->id); return g_strdup_printf ("%s.Sources", id); } const char * builder_manifest_get_id_platform (BuilderManifest *self) { return self->id_platform; } char * builder_manifest_get_locale_id_platform (BuilderManifest *self) { char *res = NULL; if (self->id_platform != NULL) { g_autofree char *id = flatpak_make_valid_id_prefix (self->id_platform); res = g_strdup_printf ("%s.Locale", id); } return res; } BuilderOptions * builder_manifest_get_build_options (BuilderManifest *self) { return self->build_options; } GList * builder_manifest_get_modules (BuilderManifest *self) { return self->modules; } GList * builder_manifest_get_add_extensions (BuilderManifest *self) { return self->add_extensions; } GList * builder_manifest_get_add_build_extensions (BuilderManifest *self) { return self->add_build_extensions; } static const char * builder_manifest_get_runtime_version (BuilderManifest *self) { return self->runtime_version ? self->runtime_version : "master"; } const char * builder_manifest_get_branch (BuilderManifest *self, BuilderContext *context) { if (self->branch) return self->branch; if (context && builder_context_get_default_branch (context)) return builder_context_get_default_branch (context); if (self->default_branch) return self->default_branch; return "master"; } const char * builder_manifest_get_collection_id (BuilderManifest *self) { return self->collection_id; } void builder_manifest_set_default_collection_id (BuilderManifest *self, const char *default_collection_id) { if (self->collection_id == NULL) self->collection_id = g_strdup (default_collection_id); } gint32 builder_manifest_get_token_type (BuilderManifest *self) { return self->token_type; } void builder_manifest_set_default_token_type (BuilderManifest *self, gint32 default_token_type) { if (self->token_type == -1) self->token_type = default_token_type; } void builder_manifest_add_tags (BuilderManifest *self, const char **add_tags) { GPtrArray *new_tags = g_ptr_array_new (); int i; for (i = 0; self->tags != NULL && self->tags[i] != NULL; i++) g_ptr_array_add (new_tags, self->tags[i]); for (i = 0; add_tags[i] != NULL; i++) { const char *new_tag = add_tags[i]; if (self->tags == NULL || !g_strv_contains ((const char **)self->tags, new_tag)) g_ptr_array_add (new_tags, g_strdup (new_tag)); } g_ptr_array_add (new_tags, NULL); g_free (self->tags); self->tags = (char **)g_ptr_array_free (new_tags, FALSE); } void builder_manifest_remove_tags (BuilderManifest *self, const char **remove_tags) { GPtrArray *new_tags = g_ptr_array_new (); int i; if (self->tags) { for (i = 0; self->tags[i] != NULL; i++) { char *old_tag = self->tags[i]; if (g_strv_contains (remove_tags, old_tag)) g_free (old_tag); else g_ptr_array_add (new_tags, old_tag); } } g_ptr_array_add (new_tags, NULL); g_free (self->tags); self->tags = (char **)g_ptr_array_free (new_tags, FALSE); } const char * builder_manifest_get_extension_tag (BuilderManifest *self) { return self->extension_tag; } static const char * builder_manifest_get_base_version (BuilderManifest *self) { return self->base_version ? self->base_version : builder_manifest_get_branch (self, NULL); } G_GNUC_NULL_TERMINATED static char * flatpak (GError **error, ...) { gboolean res; g_autofree char *output = NULL; g_autoptr(GPtrArray) ar = g_ptr_array_new (); va_list ap; va_start (ap, error); g_ptr_array_add (ar, "flatpak"); while (TRUE) { gchar *param = va_arg (ap, gchar *); g_ptr_array_add (ar, param); if (param == NULL) break; } va_end (ap); res = builder_maybe_host_spawnv (NULL, &output, 0, error, (const gchar * const *)ar->pdata); if (res) { g_strchomp (output); return g_steal_pointer (&output); } return NULL; } static void add_installation_args (GPtrArray *args, gboolean opt_user, const char *opt_installation) { if (opt_user) g_ptr_array_add (args, g_strdup ("--user")); else if (opt_installation) g_ptr_array_add (args, g_strdup_printf ("--installation=%s", opt_installation)); else g_ptr_array_add (args, g_strdup ("--system")); } static char * flatpak_info (gboolean opt_user, const char *opt_installation, const char *ref, const char *extra_arg, GError **error) { gboolean res; g_autofree char *output = NULL; g_autoptr(GPtrArray) args = NULL; args = g_ptr_array_new_with_free_func (g_free); g_ptr_array_add (args, g_strdup ("flatpak")); add_installation_args (args, opt_user, opt_installation); g_ptr_array_add (args, g_strdup ("info")); if (extra_arg) g_ptr_array_add (args, g_strdup (extra_arg)); g_ptr_array_add (args, g_strdup (ref)); g_ptr_array_add (args, NULL); res = builder_maybe_host_spawnv (NULL, &output, G_SUBPROCESS_FLAGS_STDERR_SILENCE, error, (const char * const *)args->pdata); if (res) { g_strchomp (output); return g_steal_pointer (&output); } return NULL; } static char * flatpak_info_show_path (const char *id, const char *branch, BuilderContext *context) { g_autofree char *output = NULL; g_autofree char *arch_option = NULL; arch_option = g_strdup_printf ("--arch=%s", builder_context_get_arch (context)); output = flatpak (NULL, "info", "--show-location", arch_option, id, branch, NULL); if (output == NULL) return NULL; g_strchomp (output); return g_steal_pointer (&output); } gboolean builder_manifest_start (BuilderManifest *self, gboolean download_only, gboolean allow_missing_runtimes, BuilderContext *context, GError **error) { g_autofree char *arch_option = NULL; g_autoptr(GHashTable) names = g_hash_table_new (g_str_hash, g_str_equal); g_autofree char *sdk_path = NULL; const char *stop_at; if (self->sdk == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "sdk not specified"); return FALSE; } arch_option = g_strdup_printf ("--arch=%s", builder_context_get_arch (context)); self->sdk_commit = flatpak (NULL, "info", arch_option, "--show-commit", self->sdk, builder_manifest_get_runtime_version (self), NULL); if (!download_only && !allow_missing_runtimes && self->sdk_commit == NULL) return flatpak_fail (error, "Unable to find sdk %s version %s", self->sdk, builder_manifest_get_runtime_version (self)); sdk_path = flatpak_info_show_path (self->sdk, builder_manifest_get_runtime_version (self), context); if (sdk_path != NULL && !builder_context_load_sdk_config (context, sdk_path, error)) return FALSE; self->runtime_commit = flatpak (NULL, "info", arch_option, "--show-commit", self->runtime, builder_manifest_get_runtime_version (self), NULL); if (!download_only && !allow_missing_runtimes && self->runtime_commit == NULL) return flatpak_fail (error, "Unable to find runtime %s version %s", self->runtime, builder_manifest_get_runtime_version (self)); if (self->base != NULL && *self->base != 0) { self->base_commit = flatpak (NULL, "info", arch_option, "--show-commit", self->base, builder_manifest_get_base_version (self), NULL); if (!download_only && self->base_commit == NULL) return flatpak_fail (error, "Unable to find app %s version %s", self->base, builder_manifest_get_base_version (self)); } if (!expand_modules (context, self->modules, &self->expanded_modules, names, error)) return FALSE; stop_at = builder_context_get_stop_at (context); if (stop_at != NULL && g_hash_table_lookup (names, stop_at) == NULL) return flatpak_fail (error, "No module named %s (specified with --stop-at)", stop_at); return TRUE; } gboolean builder_manifest_init_app_dir (BuilderManifest *self, BuilderCache *cache, BuilderContext *context, GError **error) { GFile *app_dir = builder_context_get_app_dir (context); g_autoptr(GPtrArray) args = NULL; GList *l; int i; g_print ("Initializing build dir\n"); if (self->id == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "id not specified"); return FALSE; } if (self->runtime == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "runtime not specified"); return FALSE; } if (self->sdk == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "sdk not specified"); return FALSE; } args = g_ptr_array_new_with_free_func (g_free); g_ptr_array_add (args, g_strdup ("flatpak")); g_ptr_array_add (args, g_strdup ("build-init")); if (self->writable_sdk || self->build_runtime) { if (self->build_runtime) g_ptr_array_add (args, g_strdup ("--type=runtime")); else g_ptr_array_add (args, g_strdup ("--writable-sdk")); } for (l = self->add_build_extensions; l != NULL; l = l->next) builder_extension_add_finish_args (l->data, args); for (i = 0; self->sdk_extensions != NULL && self->sdk_extensions[i] != NULL; i++) { const char *ext = self->sdk_extensions[i]; g_ptr_array_add (args, g_strdup_printf ("--sdk-extension=%s", ext)); } if (self->build_extension) { g_ptr_array_add (args, g_strdup ("--type=extension")); } if (self->tags) { for (i = 0; self->tags[i] != NULL; i++) g_ptr_array_add (args, g_strdup_printf ("--tag=%s", self->tags[i])); } if (self->var) g_ptr_array_add (args, g_strdup_printf ("--var=%s", self->var)); if (self->base) { g_ptr_array_add (args, g_strdup_printf ("--base=%s", self->base)); g_ptr_array_add (args, g_strdup_printf ("--base-version=%s", builder_manifest_get_base_version (self))); for (i = 0; self->base_extensions != NULL && self->base_extensions[i] != NULL; i++) { const char *ext = self->base_extensions[i]; g_ptr_array_add (args, g_strdup_printf ("--base-extension=%s", ext)); } } if (self->extension_tag != NULL) g_ptr_array_add (args, g_strdup_printf ("--extension-tag=%s", self->extension_tag)); g_ptr_array_add (args, g_strdup_printf ("--arch=%s", builder_context_get_arch (context))); g_ptr_array_add (args, g_file_get_path (app_dir)); g_ptr_array_add (args, g_strdup (self->id)); g_ptr_array_add (args, g_strdup (self->sdk)); g_ptr_array_add (args, g_strdup (self->runtime)); g_ptr_array_add (args, g_strdup (builder_manifest_get_runtime_version (self))); g_ptr_array_add (args, NULL); if (!flatpak_spawnv (NULL, NULL, G_SUBPROCESS_FLAGS_NONE, error, (const gchar * const *) args->pdata)) return FALSE; if (self->build_runtime && self->separate_locales) { g_autoptr(GFile) root_dir = NULL; root_dir = g_file_get_child (app_dir, "usr"); if (!builder_migrate_locale_dirs (root_dir, error)) return FALSE; } /* Fix up any python timestamps from base */ if (!builder_post_process (BUILDER_POST_PROCESS_FLAGS_PYTHON_TIMESTAMPS, app_dir, cache, context, error)) return FALSE; return TRUE; } /* This gets the checksum of everything that globally affects the build */ void builder_manifest_checksum (BuilderManifest *self, BuilderCache *cache, BuilderContext *context) { GList *l; builder_cache_checksum_str (cache, BUILDER_MANIFEST_CHECKSUM_VERSION); builder_cache_checksum_str (cache, self->id); /* No need to include version here, it doesn't affect the build */ builder_cache_checksum_str (cache, self->runtime); builder_cache_checksum_str (cache, builder_manifest_get_runtime_version (self)); builder_cache_checksum_str (cache, self->sdk); /* Always rebuild on sdk change if we're actually including the sdk in the cache */ if (self->writable_sdk || self->build_runtime || builder_context_get_rebuild_on_sdk_change (context)) builder_cache_checksum_str (cache, self->sdk_commit); builder_cache_checksum_str (cache, self->var); builder_cache_checksum_str (cache, self->metadata); builder_cache_checksum_strv (cache, self->tags); builder_cache_checksum_boolean (cache, self->writable_sdk); builder_cache_checksum_strv (cache, self->sdk_extensions); builder_cache_checksum_boolean (cache, self->build_runtime); builder_cache_checksum_boolean (cache, self->build_extension); builder_cache_checksum_boolean (cache, self->separate_locales); builder_cache_checksum_str (cache, self->base); builder_cache_checksum_str (cache, self->base_version); builder_cache_checksum_str (cache, self->base_commit); builder_cache_checksum_strv (cache, self->base_extensions); builder_cache_checksum_compat_str (cache, self->extension_tag); if (self->build_options) builder_options_checksum (self->build_options, cache, context); for (l = self->add_build_extensions; l != NULL; l = l->next) { BuilderExtension *e = l->data; builder_extension_checksum (e, cache, context); } } static void builder_manifest_checksum_for_cleanup (BuilderManifest *self, BuilderCache *cache, BuilderContext *context) { GList *l; builder_cache_checksum_str (cache, BUILDER_MANIFEST_CHECKSUM_CLEANUP_VERSION); builder_cache_checksum_strv (cache, self->cleanup); builder_cache_checksum_strv (cache, self->cleanup_commands); builder_cache_checksum_str (cache, self->rename_desktop_file); builder_cache_checksum_str (cache, self->rename_appdata_file); builder_cache_checksum_str (cache, self->appdata_license); builder_cache_checksum_str (cache, self->rename_icon); builder_cache_checksum_boolean (cache, self->copy_icon); builder_cache_checksum_str (cache, self->desktop_file_name_prefix); builder_cache_checksum_str (cache, self->desktop_file_name_suffix); builder_cache_checksum_boolean (cache, self->appstream_compose); for (l = self->expanded_modules; l != NULL; l = l->next) { BuilderModule *m = l->data; builder_module_checksum_for_cleanup (m, cache, context); } } static void builder_manifest_checksum_for_finish (BuilderManifest *self, BuilderCache *cache, BuilderContext *context) { GList *l; g_autofree char *json = NULL; builder_cache_checksum_str (cache, BUILDER_MANIFEST_CHECKSUM_FINISH_VERSION); builder_cache_checksum_strv (cache, self->finish_args); builder_cache_checksum_str (cache, self->command); builder_cache_checksum_strv (cache, self->inherit_extensions); builder_cache_checksum_compat_strv (cache, self->inherit_sdk_extensions); for (l = self->add_extensions; l != NULL; l = l->next) { BuilderExtension *e = l->data; builder_extension_checksum (e, cache, context); } if (self->metadata) { GFile *base_dir = builder_context_get_base_dir (context); g_autoptr(GFile) metadata = g_file_resolve_relative_path (base_dir, self->metadata); g_autofree char *data = NULL; g_autoptr(GError) my_error = NULL; gsize len; if (g_file_load_contents (metadata, NULL, &data, &len, NULL, &my_error)) builder_cache_checksum_data (cache, (guchar *) data, len); else g_warning ("Can't load metadata file %s: %s", self->metadata, my_error->message); } json = builder_manifest_serialize (self); builder_cache_checksum_str (cache, json); } static void builder_manifest_checksum_for_bundle_sources (BuilderManifest *self, BuilderCache *cache, BuilderContext *context) { builder_cache_checksum_str (cache, BUILDER_MANIFEST_CHECKSUM_BUNDLE_SOURCES_VERSION); builder_cache_checksum_boolean (cache, builder_context_get_bundle_sources (context)); } static void builder_manifest_checksum_for_platform_base (BuilderManifest *self, BuilderCache *cache, BuilderContext *context) { builder_cache_checksum_str (cache, BUILDER_MANIFEST_CHECKSUM_PLATFORM_VERSION); builder_cache_checksum_str (cache, self->id_platform); builder_cache_checksum_str (cache, self->runtime_commit); builder_cache_checksum_strv (cache, self->platform_extensions); builder_cache_checksum_str (cache, self->metadata_platform); if (self->metadata_platform) { GFile *base_dir = builder_context_get_base_dir (context); g_autoptr(GFile) metadata = g_file_resolve_relative_path (base_dir, self->metadata_platform); g_autofree char *data = NULL; g_autoptr(GError) my_error = NULL; gsize len; if (g_file_load_contents (metadata, NULL, &data, &len, NULL, &my_error)) builder_cache_checksum_data (cache, (guchar *) data, len); else g_warning ("Can't load metadata-platform file %s: %s", self->metadata_platform, my_error->message); } } static void builder_manifest_checksum_for_platform_prepare (BuilderManifest *self, BuilderCache *cache, BuilderContext *context) { GList *l; builder_cache_checksum_strv (cache, self->prepare_platform_commands); builder_cache_checksum_strv (cache, self->cleanup_platform); for (l = self->expanded_modules; l != NULL; l = l->next) { BuilderModule *m = l->data; builder_module_checksum_for_platform_cleanup (m, cache, context); } } static void builder_manifest_checksum_for_platform_finish (BuilderManifest *self, BuilderCache *cache, BuilderContext *context) { builder_cache_checksum_strv (cache, self->cleanup_platform_commands); } gboolean builder_manifest_download (BuilderManifest *self, gboolean update_vcs, const char *only_module, BuilderContext *context, GError **error) { const char *stop_at = builder_context_get_stop_at (context); GList *l; g_print ("Downloading sources\n"); for (l = self->expanded_modules; l != NULL; l = l->next) { BuilderModule *m = l->data; const char *name = builder_module_get_name (m); if (only_module && strcmp (name, only_module) != 0) continue; if (stop_at != NULL && strcmp (name, stop_at) == 0) { g_print ("Stopping at module %s\n", stop_at); return TRUE; } if (!builder_module_download_sources (m, update_vcs, context, error)) return FALSE; } return TRUE; } static gboolean setup_context (BuilderManifest *self, BuilderContext *context, GError **error) { builder_context_set_options (context, self->build_options); builder_context_set_global_cleanup (context, (const char **) self->cleanup); builder_context_set_global_cleanup_platform (context, (const char **) self->cleanup_platform); if (self->build_runtime && self->build_extension) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Can't build both a runtime and an extension"); return FALSE; } builder_context_set_build_runtime (context, self->build_runtime); builder_context_set_build_extension (context, self->build_extension); builder_context_set_separate_locales (context, self->separate_locales); return TRUE; } gboolean builder_manifest_build_shell (BuilderManifest *self, BuilderContext *context, const char *modulename, GError **error) { GList *l; BuilderModule *found = NULL; if (!builder_context_enable_rofiles (context, error)) return FALSE; if (!setup_context (self, context, error)) return FALSE; for (l = self->expanded_modules; l != NULL; l = l->next) { BuilderModule *m = l->data; const char *name = builder_module_get_name (m); if (strcmp (name, modulename) == 0) { found = m; break; } } if (found == NULL) return flatpak_fail (error, "Can't find module %s", modulename); if (!builder_module_build (found, NULL, context, TRUE, error)) return FALSE; return TRUE; } gboolean builder_manifest_build (BuilderManifest *self, BuilderCache *cache, BuilderContext *context, GError **error) { const char *stop_at = builder_context_get_stop_at (context); GList *l; if (!setup_context (self, context, error)) return FALSE; g_print ("Starting build of %s\n", self->id ? self->id : "app"); for (l = self->expanded_modules; l != NULL; l = l->next) { BuilderModule *m = l->data; g_autoptr(GPtrArray) changes = NULL; const char *name = builder_module_get_name (m); g_autofree char *stage = g_strdup_printf ("build-%s", name); if (stop_at != NULL && strcmp (name, stop_at) == 0) { g_print ("Stopping at module %s\n", stop_at); return TRUE; } if (!builder_module_should_build (m)) { g_print ("Skipping module %s (no sources)\n", name); continue; } builder_module_checksum (m, cache, context); if (!builder_cache_lookup (cache, stage)) { g_autofree char *body = g_strdup_printf ("Built %s\n", name); if (!builder_module_ensure_writable (m, cache, context, error)) return FALSE; if (!builder_context_enable_rofiles (context, error)) return FALSE; if (!builder_module_build (m, cache, context, FALSE, error)) return FALSE; if (!builder_context_disable_rofiles (context, error)) return FALSE; if (!builder_cache_commit (cache, body, error)) return FALSE; } else { g_print ("Cache hit for %s, skipping build\n", name); } changes = builder_cache_get_changes (cache, error); if (changes == NULL) return FALSE; builder_module_set_changes (m, changes); builder_module_update (m, context, error); } return TRUE; } static gboolean command (GFile *app_dir, char **env_vars, char **extra_args, const char *commandline, GError **error) { g_autoptr(GPtrArray) args = NULL; int i; args = g_ptr_array_new_with_free_func (g_free); g_ptr_array_add (args, g_strdup ("flatpak")); g_ptr_array_add (args, g_strdup ("build")); g_ptr_array_add (args, g_strdup ("--die-with-parent")); g_ptr_array_add (args, g_strdup ("--nofilesystem=host")); if (extra_args) { for (i = 0; extra_args[i] != NULL; i++) g_ptr_array_add (args, g_strdup (extra_args[i])); } if (env_vars) { for (i = 0; env_vars[i] != NULL; i++) g_ptr_array_add (args, g_strdup_printf ("--env=%s", env_vars[i])); } g_ptr_array_add (args, g_file_get_path (app_dir)); g_ptr_array_add (args, g_strdup ("/bin/sh")); g_ptr_array_add (args, g_strdup ("-c")); g_ptr_array_add (args, g_strdup (commandline)); g_ptr_array_add (args, NULL); return builder_maybe_host_spawnv (NULL, NULL, 0, error, (const char * const *)args->pdata); } typedef gboolean (*ForeachFileFunc) (BuilderManifest *self, int source_parent_fd, const char *source_name, const char *full_dir, const char *rel_dir, struct stat *stbuf, gboolean *found, int depth, GError **error); static gboolean foreach_file_helper (BuilderManifest *self, ForeachFileFunc func, int source_parent_fd, const char *source_name, const char *full_dir, const char *rel_dir, gboolean *found, int depth, GError **error) { g_auto(GLnxDirFdIterator) source_iter = {0}; struct dirent *dent; g_autoptr(GError) my_error = NULL; if (!glnx_dirfd_iterator_init_at (source_parent_fd, source_name, FALSE, &source_iter, &my_error)) { if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) return TRUE; g_propagate_error (error, g_steal_pointer (&my_error)); return FALSE; } while (TRUE) { struct stat stbuf; if (!glnx_dirfd_iterator_next_dent (&source_iter, &dent, NULL, error)) return FALSE; if (dent == NULL) break; if (fstatat (source_iter.fd, dent->d_name, &stbuf, AT_SYMLINK_NOFOLLOW) == -1) { if (errno == ENOENT) { continue; } else { glnx_set_error_from_errno (error); return FALSE; } } if (S_ISDIR (stbuf.st_mode)) { g_autofree char *child_dir = g_build_filename (full_dir, dent->d_name, NULL); g_autofree char *child_rel_dir = g_build_filename (rel_dir, dent->d_name, NULL); if (!foreach_file_helper (self, func, source_iter.fd, dent->d_name, child_dir, child_rel_dir, found, depth + 1, error)) return FALSE; } if (!func (self, source_iter.fd, dent->d_name, full_dir, rel_dir, &stbuf, found, depth, error)) return FALSE; } return TRUE; } static gboolean foreach_file (BuilderManifest *self, ForeachFileFunc func, gboolean *found, GFile *root, GError **error) { return foreach_file_helper (self, func, AT_FDCWD, flatpak_file_get_path_cached (root), flatpak_file_get_path_cached (root), "", found, 0, error); } static gboolean rename_icon_cb (BuilderManifest *self, int source_parent_fd, const char *source_name, const char *full_dir, const char *rel_dir, struct stat *stbuf, gboolean *found, int depth, GError **error) { if (g_str_has_prefix (source_name, self->rename_icon)) { if (S_ISREG (stbuf->st_mode) && depth == 3 && (g_str_has_prefix (source_name + strlen (self->rename_icon), ".") || g_str_has_prefix (source_name + strlen (self->rename_icon), "-symbolic."))) { const char *extension = source_name + strlen (self->rename_icon); g_autofree char *new_name = g_strconcat (self->id, extension, NULL); int res; *found = TRUE; g_print ("%s icon %s/%s to %s/%s\n", self->copy_icon ? "Copying" : "Renaming", rel_dir, source_name, rel_dir, new_name); if (self->copy_icon) res = linkat (source_parent_fd, source_name, source_parent_fd, new_name, AT_SYMLINK_FOLLOW); else res = renameat (source_parent_fd, source_name, source_parent_fd, new_name); if (res != 0) { g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "Can't rename icon %s/%s", rel_dir, source_name); return FALSE; } } else { if (!S_ISREG (stbuf->st_mode)) g_debug ("%s/%s matches 'rename-icon', but not a regular file", full_dir, source_name); else if (depth != 3) g_debug ("%s/%s matches 'rename-icon', but not at depth 3", full_dir, source_name); else g_debug ("%s/%s matches 'rename-icon', but name does not continue with '.' or '-symbolic.'", full_dir, source_name); } } return TRUE; } static int cmpstringp (const void *p1, const void *p2) { return strcmp (*(char * const *) p1, *(char * const *) p2); } static gboolean appstream_compose (GFile *app_dir, GError **error, ...) { g_autoptr(GPtrArray) args = NULL; const gchar *arg; va_list ap; args = g_ptr_array_new_with_free_func (g_free); g_ptr_array_add (args, g_strdup ("flatpak")); g_ptr_array_add (args, g_strdup ("build")); g_ptr_array_add (args, g_strdup ("--die-with-parent")); g_ptr_array_add (args, g_strdup ("--nofilesystem=host")); g_ptr_array_add (args, g_file_get_path (app_dir)); g_ptr_array_add (args, g_strdup ("appstream-compose")); va_start (ap, error); while ((arg = va_arg (ap, const gchar *))) g_ptr_array_add (args, g_strdup (arg)); g_ptr_array_add (args, NULL); va_end (ap); if (!builder_maybe_host_spawnv (NULL, NULL, 0, error, (const char * const *)args->pdata)) { g_prefix_error (error, "ERROR: appstream-compose failed: "); return FALSE; } return TRUE; } static char ** strcatv (char **strv1, char **strv2) { if (strv1 == NULL && strv2 == NULL) return NULL; if (strv1 == NULL) return g_strdupv (strv2); if (strv2 == NULL) return g_strdupv (strv1); unsigned len1 = g_strv_length (strv1); unsigned len2 = g_strv_length (strv2); char **retval = g_new (char *, len1 + len2 + 1); unsigned ix; for (ix = 0; ix < len1; ix++) retval[ix] = g_strdup (strv1[ix]); for (ix = 0; ix < len2; ix++) retval[len1 + ix] = g_strdup (strv2[ix]); retval[len1 + len2] = NULL; return retval; } static gboolean rewrite_appdata (GFile *file, const char *license, GError **error) { g_autofree gchar *data = NULL; gsize data_len; g_autoptr(xmlDoc) doc = NULL; xml_autofree xmlChar *xmlbuff = NULL; int buffersize; xmlNode *root_element, *component_node; if (!g_file_load_contents (file, NULL, &data, &data_len, NULL, error)) return FALSE; doc = xmlReadMemory (data, data_len, NULL, NULL, 0); if (doc == NULL) return flatpak_fail (error, _("Error parsing appstream")); root_element = xmlDocGetRootElement (doc); for (component_node = root_element; component_node; component_node = component_node->next) { xmlNode *sub_node = NULL; xmlNode *license_node = NULL; if (component_node->type != XML_ELEMENT_NODE || strcmp ((char *)component_node->name, "component") != 0) continue; for (sub_node = component_node->children; sub_node; sub_node = sub_node->next) { if (sub_node->type != XML_ELEMENT_NODE || strcmp ((char *)sub_node->name, "project_license") != 0) continue; license_node = sub_node; break; } if (license_node) xmlNodeSetContent(license_node, (xmlChar *)license); else xmlNewChild(component_node, NULL, (xmlChar *)"project_license", (xmlChar *)license); } xmlDocDumpFormatMemory (doc, &xmlbuff, &buffersize, 1); if (!g_file_set_contents (flatpak_file_get_path_cached (file), (gchar *)xmlbuff, buffersize, error)) return FALSE; return TRUE; } static GFile * builder_manifest_find_appdata_file (BuilderManifest *self, GFile *app_root) { /* We order these so that share/appdata/XXX.appdata.xml if found first, as this is the target name, and apps may have both, which will cause issues with the rename. */ const char *extensions[] = { ".appdata.xml", ".metainfo.xml", }; const char *dirs[] = { "share/appdata", "share/metainfo", }; g_autoptr(GFile) source = NULL; int i, j; for (j = 0; j < G_N_ELEMENTS (dirs); j++) { g_autoptr(GFile) appdata_dir = g_file_resolve_relative_path (app_root, dirs[j]); for (i = 0; i < G_N_ELEMENTS (extensions); i++) { g_autofree char *basename = NULL; if (self->rename_appdata_file != NULL) basename = g_strdup (self->rename_appdata_file); else basename = g_strconcat (self->id, extensions[i], NULL); source = g_file_get_child (appdata_dir, basename); if (g_file_query_exists (source, NULL)) return g_steal_pointer (&source); } } return NULL; } gboolean builder_manifest_cleanup (BuilderManifest *self, BuilderCache *cache, BuilderContext *context, GError **error) { g_autoptr(GFile) app_root = NULL; GList *l; g_auto(GStrv) env = NULL; g_autoptr(GFile) appdata_file = NULL; g_autoptr(GFile) appdata_source = NULL; int i; builder_manifest_checksum_for_cleanup (self, cache, context); if (!builder_cache_lookup (cache, "cleanup")) { g_autoptr(GHashTable) to_remove_ht = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); g_autofree char **keys = NULL; GFile *app_dir = NULL; guint n_keys; g_print ("Cleaning up\n"); if (!builder_context_enable_rofiles (context, error)) return FALSE; /* Call after enabling rofiles */ app_dir = builder_context_get_app_dir (context); if (self->cleanup_commands) { g_auto(GStrv) build_args = builder_options_get_build_args (self->build_options, context, error); if (!build_args) return FALSE; env = builder_options_get_env (self->build_options, context); for (i = 0; self->cleanup_commands[i] != NULL; i++) { if (!command (app_dir, env, build_args, self->cleanup_commands[i], error)) return FALSE; } } for (l = self->expanded_modules; l != NULL; l = l->next) { BuilderModule *m = l->data; builder_module_cleanup_collect (m, FALSE, context, to_remove_ht); } keys = (char **) g_hash_table_get_keys_as_array (to_remove_ht, &n_keys); qsort (keys, n_keys, sizeof (char *), cmpstringp); /* Iterate in reverse to remove leafs first */ for (i = n_keys - 1; i >= 0; i--) { g_autoptr(GError) my_error = NULL; g_autoptr(GFile) f = g_file_resolve_relative_path (app_dir, keys[i]); g_print ("Removing %s\n", keys[i]); if (!g_file_delete (f, NULL, &my_error)) { if (!g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) && !g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_EMPTY)) { g_propagate_error (error, g_steal_pointer (&my_error)); return FALSE; } } } app_root = g_file_get_child (app_dir, "files"); appdata_source = builder_manifest_find_appdata_file (self, app_root); if (appdata_source) { /* We always use the old name / dir, in case the runtime has older appdata tools */ g_autoptr(GFile) appdata_dir = g_file_resolve_relative_path (app_root, "share/appdata"); g_autofree char *appdata_basename = g_strdup_printf ("%s.appdata.xml", self->id); appdata_file = g_file_get_child (appdata_dir, appdata_basename); if (!g_file_equal (appdata_source, appdata_file)) { g_autofree char *src_basename = g_file_get_basename (appdata_source); g_print ("Renaming %s to share/appdata/%s\n", src_basename, appdata_basename); if (!flatpak_mkdir_p (appdata_dir, NULL, error)) return FALSE; if (!g_file_move (appdata_source, appdata_file, 0, NULL, NULL, NULL, error)) return FALSE; } if (self->appdata_license != NULL && self->appdata_license[0] != 0) { if (!rewrite_appdata (appdata_file, self->appdata_license, error)) return FALSE; } } if (self->rename_desktop_file != NULL) { g_autoptr(GFile) applications_dir = g_file_resolve_relative_path (app_root, "share/applications"); g_autoptr(GFile) src = g_file_get_child (applications_dir, self->rename_desktop_file); g_autofree char *desktop_basename = g_strdup_printf ("%s.desktop", self->id); g_autoptr(GFile) dest = g_file_get_child (applications_dir, desktop_basename); g_print ("Renaming %s to %s\n", self->rename_desktop_file, desktop_basename); if (!g_file_move (src, dest, 0, NULL, NULL, NULL, error)) return FALSE; if (appdata_file != NULL) { FlatpakXml *n_id; FlatpakXml *n_root; FlatpakXml *n_text; g_autoptr(FlatpakXml) xml_root = NULL; g_autoptr(GInputStream) in = NULL; g_autoptr(GString) new_contents = NULL; in = (GInputStream *) g_file_read (appdata_file, NULL, error); if (!in) return FALSE; xml_root = flatpak_xml_parse (in, FALSE, NULL, error); if (!xml_root) return FALSE; /* replace component/id */ n_root = flatpak_xml_find (xml_root, "component", NULL); if (!n_root) n_root = flatpak_xml_find (xml_root, "application", NULL); if (!n_root) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "no node"); return FALSE; } n_id = flatpak_xml_find (n_root, "id", NULL); if (n_id) { n_text = n_id->first_child; if (n_text && g_strcmp0 (n_text->text, self->rename_desktop_file) == 0) { g_free (n_text->text); n_text->text = g_strdup (self->id); } } /* replace any optional launchable */ n_id = flatpak_xml_find (n_root, "launchable", NULL); if (n_id) { n_text = n_id->first_child; if (n_text && g_strcmp0 (n_text->text, self->rename_desktop_file) == 0) { g_free (n_text->text); n_text->text = g_strdup (desktop_basename); } } new_contents = g_string_new (""); flatpak_xml_to_string (xml_root, new_contents); if (!g_file_set_contents (flatpak_file_get_path_cached (appdata_file), new_contents->str, new_contents->len, error)) return FALSE; } } if (self->rename_icon) { gboolean found_icon = FALSE; g_autoptr(GFile) icons_dir = g_file_resolve_relative_path (app_root, "share/icons"); if (!foreach_file (self, rename_icon_cb, &found_icon, icons_dir, error)) return FALSE; if (!found_icon) { g_autofree char *icon_path = g_file_get_path (icons_dir); g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "icon %s not found below %s", self->rename_icon, icon_path); return FALSE; } } if (self->rename_icon || self->desktop_file_name_prefix || self->desktop_file_name_suffix || self->rename_desktop_file) { g_autoptr(GFile) applications_dir = g_file_resolve_relative_path (app_root, "share/applications"); g_autofree char *desktop_basename = g_strdup_printf ("%s.desktop", self->id); g_autoptr(GFile) desktop = g_file_get_child (applications_dir, desktop_basename); g_autoptr(GKeyFile) keyfile = g_key_file_new (); g_autofree char *desktop_contents = NULL; gsize desktop_size; g_auto(GStrv) desktop_keys = NULL; g_print ("Rewriting contents of %s\n", desktop_basename); if (!g_file_load_contents (desktop, NULL, &desktop_contents, &desktop_size, NULL, error)) { g_autofree char *desktop_path = g_file_get_path (desktop); g_prefix_error (error, "Can't load desktop file %s: ", desktop_path); return FALSE; } if (!g_key_file_load_from_data (keyfile, desktop_contents, desktop_size, G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS, error)) return FALSE; if (self->rename_desktop_file) { g_auto(GStrv) old_renames = g_key_file_get_string_list (keyfile, G_KEY_FILE_DESKTOP_GROUP, "X-Flatpak-RenamedFrom", NULL, NULL); const char **new_renames = NULL; int old_rename_len = 0; int new_rename_len = 0; if (old_renames) old_rename_len = g_strv_length (old_renames); new_renames = g_new (const char *, old_rename_len + 2); for (i = 0; i < old_rename_len; i++) new_renames[new_rename_len++] = old_renames[i]; new_renames[new_rename_len++] = self->rename_desktop_file; new_renames[new_rename_len] = NULL; g_key_file_set_string_list (keyfile, G_KEY_FILE_DESKTOP_GROUP, "X-Flatpak-RenamedFrom", new_renames, new_rename_len); } desktop_keys = g_key_file_get_keys (keyfile, G_KEY_FILE_DESKTOP_GROUP, NULL, NULL); if (self->rename_icon) { g_autofree char *original_icon_name = g_key_file_get_string (keyfile, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_ICON, NULL); g_key_file_set_string (keyfile, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_ICON, self->id); /* Also rename localized version of the Icon= field */ for (i = 0; desktop_keys[i]; i++) { /* Only rename untranslated icon names */ if (g_str_has_prefix (desktop_keys[i], "Icon[")) { g_autofree char *icon_name = g_key_file_get_string (keyfile, G_KEY_FILE_DESKTOP_GROUP, desktop_keys[i], NULL); if (strcmp (icon_name, original_icon_name) == 0) g_key_file_set_string (keyfile, G_KEY_FILE_DESKTOP_GROUP, desktop_keys[i], self->id); } } } if (self->desktop_file_name_suffix || self->desktop_file_name_prefix) { for (i = 0; desktop_keys[i]; i++) { if (strcmp (desktop_keys[i], "Name") == 0 || g_str_has_prefix (desktop_keys[i], "Name[")) { g_autofree char *name = g_key_file_get_string (keyfile, G_KEY_FILE_DESKTOP_GROUP, desktop_keys[i], NULL); if (name) { g_autofree char *new_name = g_strdup_printf ("%s%s%s", self->desktop_file_name_prefix ? self->desktop_file_name_prefix : "", name, self->desktop_file_name_suffix ? self->desktop_file_name_suffix : ""); g_key_file_set_string (keyfile, G_KEY_FILE_DESKTOP_GROUP, desktop_keys[i], new_name); } } } } g_free (desktop_contents); desktop_contents = g_key_file_to_data (keyfile, &desktop_size, error); if (desktop_contents == NULL) return FALSE; if (!g_file_set_contents (flatpak_file_get_path_cached (desktop), desktop_contents, desktop_size, error)) return FALSE; } if (self->appstream_compose && appdata_file != NULL) { g_autofree char *basename_arg = g_strdup_printf ("--basename=%s", self->id); g_print ("Running appstream-compose\n"); if (!appstream_compose (app_dir, error, self->build_runtime ? "--prefix=/usr" : "--prefix=/app", "--origin=flatpak", basename_arg, self->id, NULL)) return FALSE; } if (!builder_context_disable_rofiles (context, error)) return FALSE; if (!builder_cache_commit (cache, "Cleanup", error)) return FALSE; } else { g_print ("Cache hit for cleanup, skipping\n"); } return TRUE; } static char * maybe_format_extension_tag (const char *extension_tag) { if (extension_tag != NULL) return g_strdup_printf ("tag=%s\n", extension_tag); return g_strdup (""); } gboolean builder_manifest_finish (BuilderManifest *self, BuilderCache *cache, BuilderContext *context, GError **error) { g_autoptr(GFile) manifest_file = NULL; g_autoptr(GFile) debuginfo_dir = NULL; g_autoptr(GFile) sources_dir = NULL; g_autoptr(GFile) locale_parent_dir = NULL; g_autofree char *json = NULL; g_autoptr(GPtrArray) args = NULL; g_autoptr(GPtrArray) inherit_extensions = NULL; int i; GList *l; builder_manifest_checksum_for_finish (self, cache, context); if (!builder_cache_lookup (cache, "finish")) { GFile *app_dir = NULL; g_autoptr(GPtrArray) sub_ids = g_ptr_array_new_with_free_func (g_free); g_autofree char *ref = NULL; g_print ("Finishing app\n"); builder_set_term_title (_("Finishing %s"), self->id); if (!builder_context_enable_rofiles (context, error)) return FALSE; /* Call after enabling rofiles */ app_dir = builder_context_get_app_dir (context); ref = flatpak_compose_ref (!self->build_runtime && !self->build_extension, builder_manifest_get_id (self), builder_manifest_get_branch (self, context), builder_context_get_arch (context)); if (self->metadata) { GFile *base_dir = builder_context_get_base_dir (context); g_autoptr(GFile) dest_metadata = g_file_get_child (app_dir, "metadata"); g_autoptr(GFile) src_metadata = g_file_resolve_relative_path (base_dir, self->metadata); g_autofree char *contents = NULL; gsize length; if (!g_file_get_contents (flatpak_file_get_path_cached (src_metadata), &contents, &length, error)) return FALSE; if (!g_file_set_contents (flatpak_file_get_path_cached (dest_metadata), contents, length, error)) return FALSE; } if ((self->inherit_extensions && self->inherit_extensions[0] != NULL) || (self->inherit_sdk_extensions && self->inherit_sdk_extensions[0] != NULL)) { g_autoptr(GFile) metadata = g_file_get_child (app_dir, "metadata"); g_autoptr(GKeyFile) keyfile = g_key_file_new (); g_autoptr(GKeyFile) base_keyfile = g_key_file_new (); g_autofree char *arch_option = NULL; const char *parent_id = NULL; const char *parent_version = NULL; g_autofree char *base_metadata = NULL; arch_option = g_strdup_printf ("--arch=%s", builder_context_get_arch (context)); if (self->base != NULL && *self->base != 0) { parent_id = self->base; parent_version = builder_manifest_get_base_version (self); } else { parent_id = self->sdk; parent_version = builder_manifest_get_runtime_version (self); } base_metadata = flatpak (NULL, "info", arch_option, "--show-metadata", parent_id, parent_version, NULL); if (base_metadata == NULL) return flatpak_fail (error, "Inherit extensions specified, but could not get metadata for parent %s version %s", parent_id, parent_version); if (!g_key_file_load_from_data (base_keyfile, base_metadata, -1, G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS, error)) { g_prefix_error (error, "Can't load metadata file: "); return FALSE; } if (!g_key_file_load_from_file (keyfile, flatpak_file_get_path_cached (metadata), G_KEY_FILE_KEEP_COMMENTS|G_KEY_FILE_KEEP_TRANSLATIONS, error)) { g_prefix_error (error, "Can't load metadata file: "); return FALSE; } inherit_extensions = g_ptr_array_new (); for (i = 0; self->inherit_extensions != NULL && self->inherit_extensions[i] != NULL; i++) g_ptr_array_add (inherit_extensions, self->inherit_extensions[i]); for (i = 0; self->inherit_sdk_extensions != NULL && self->inherit_sdk_extensions[i] != NULL; i++) g_ptr_array_add (inherit_extensions, self->inherit_sdk_extensions[i]); for (i = 0; i < inherit_extensions->len; i++) { const char *extension = inherit_extensions->pdata[i]; g_autofree char *group = g_strconcat (FLATPAK_METADATA_GROUP_PREFIX_EXTENSION, extension, NULL); g_auto(GStrv) keys = NULL; int j; if (!g_key_file_has_group (base_keyfile, group)) return flatpak_fail (error, "Can't find inherited extension point %s", extension); keys = g_key_file_get_keys (base_keyfile, group, NULL, error); if (keys == NULL) return FALSE; for (j = 0; keys[j] != NULL; j++) { g_autofree char *value = g_key_file_get_value (base_keyfile, group, keys[j], error); if (value == NULL) return FALSE; g_key_file_set_value (keyfile, group, keys[j], value); } if (!g_key_file_has_key (keyfile, group, FLATPAK_METADATA_KEY_VERSION, NULL) && !g_key_file_has_key (keyfile, group, FLATPAK_METADATA_KEY_VERSIONS, NULL)) g_key_file_set_value (keyfile, group, FLATPAK_METADATA_KEY_VERSION, parent_version); } if (!g_key_file_save_to_file (keyfile, flatpak_file_get_path_cached (metadata), error)) { g_prefix_error (error, "Can't save metadata.platform: "); return FALSE; } } if (self->command) { g_autoptr(GFile) files_dir = g_file_resolve_relative_path (app_dir, "files"); g_autoptr(GFile) command_file = NULL; if (!g_path_is_absolute (self->command)) { g_autoptr(GFile) bin_dir = g_file_resolve_relative_path (files_dir, "bin"); command_file = g_file_get_child (bin_dir, self->command); } else if (g_str_has_prefix (self->command, "/app/")) command_file = g_file_resolve_relative_path (files_dir, self->command + strlen ("/app/")); if (command_file != NULL && !g_file_query_exists (command_file, NULL)) { const char *help = ""; if (strchr (self->command, ' ')) help = ". Use a shell wrapper for passing arguments"; g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Command '%s' not found%s", self->command, help); return FALSE; } } args = g_ptr_array_new_with_free_func (g_free); g_ptr_array_add (args, g_strdup ("flatpak")); g_ptr_array_add (args, g_strdup ("build-finish")); if (self->command) g_ptr_array_add (args, g_strdup_printf ("--command=%s", self->command)); if (self->finish_args) { for (i = 0; self->finish_args[i] != NULL; i++) g_ptr_array_add (args, g_strdup (self->finish_args[i])); } for (l = self->add_build_extensions; l != NULL; l = l->next) builder_extension_add_remove_args (l->data, args); for (l = self->add_extensions; l != NULL; l = l->next) builder_extension_add_finish_args (l->data, args); for (l = self->expanded_modules; l != NULL; l = l->next) { BuilderModule *m = l->data; builder_module_finish_sources (m, args, context); } g_ptr_array_add (args, g_file_get_path (app_dir)); g_ptr_array_add (args, NULL); if (!flatpak_spawnv (NULL, NULL, G_SUBPROCESS_FLAGS_NONE, error, (const gchar * const *) args->pdata)) return FALSE; json = builder_manifest_serialize (self); if (self->build_runtime) manifest_file = g_file_resolve_relative_path (app_dir, "usr/manifest.json"); else manifest_file = g_file_resolve_relative_path (app_dir, "files/manifest.json"); if (g_file_query_exists (manifest_file, NULL)) { /* Move existing base manifest aside */ g_autoptr(GFile) manifest_dir = g_file_get_parent (manifest_file); g_autoptr(GFile) old_manifest = NULL; int ver = 0; do { g_autofree char *basename = g_strdup_printf ("manifest-base-%d.json", ++ver); g_clear_object (&old_manifest); old_manifest = g_file_get_child (manifest_dir, basename); } while (g_file_query_exists (old_manifest, NULL)); if (!g_file_move (manifest_file, old_manifest, 0, NULL, NULL, NULL, error)) return FALSE; } if (!g_file_set_contents (flatpak_file_get_path_cached (manifest_file), json, strlen (json), error)) return FALSE; if (self->build_runtime) { debuginfo_dir = g_file_resolve_relative_path (app_dir, "usr/lib/debug"); locale_parent_dir = g_file_resolve_relative_path (app_dir, "usr/" LOCALES_SEPARATE_DIR); } else { debuginfo_dir = g_file_resolve_relative_path (app_dir, "files/lib/debug"); locale_parent_dir = g_file_resolve_relative_path (app_dir, "files/" LOCALES_SEPARATE_DIR); } sources_dir = g_file_resolve_relative_path (app_dir, "sources"); if (self->separate_locales && g_file_query_exists (locale_parent_dir, NULL)) { g_autoptr(GFile) metadata_file = NULL; g_autofree char *extension_contents = NULL; g_autoptr(GFileOutputStream) output = NULL; g_autoptr(GFile) metadata_locale_file = NULL; g_autofree char *metadata_contents = NULL; g_autofree char *locale_id = builder_manifest_get_locale_id (self); metadata_file = g_file_get_child (app_dir, "metadata"); extension_contents = g_strdup_printf ("\n" "[Extension %s]\n" "directory=%s\n" "autodelete=true\n" "locale-subset=true\n", locale_id, LOCALES_SEPARATE_DIR); output = g_file_append_to (metadata_file, G_FILE_CREATE_NONE, NULL, error); if (output == NULL) return FALSE; if (!g_output_stream_write_all (G_OUTPUT_STREAM (output), extension_contents, strlen (extension_contents), NULL, NULL, error)) return FALSE; metadata_locale_file = g_file_get_child (app_dir, "metadata.locale"); metadata_contents = g_strdup_printf ("[Runtime]\n" "name=%s\n" "\n" "[ExtensionOf]\n" "ref=%s\n", locale_id, ref); if (!g_file_set_contents (flatpak_file_get_path_cached (metadata_locale_file), metadata_contents, strlen (metadata_contents), error)) return FALSE; g_ptr_array_add (sub_ids, g_strdup (locale_id)); } if (g_file_query_exists (debuginfo_dir, NULL)) { g_autoptr(GFile) metadata_file = NULL; g_autoptr(GFile) metadata_debuginfo_file = NULL; g_autofree char *metadata_contents = NULL; g_autofree char *extension_contents = NULL; g_autoptr(GFileOutputStream) output = NULL; g_autofree char *debug_id = builder_manifest_get_debug_id (self); metadata_file = g_file_get_child (app_dir, "metadata"); metadata_debuginfo_file = g_file_get_child (app_dir, "metadata.debuginfo"); extension_contents = g_strdup_printf ("\n" "[Extension %s]\n" "directory=lib/debug\n" "autodelete=true\n" "no-autodownload=true\n", debug_id); output = g_file_append_to (metadata_file, G_FILE_CREATE_NONE, NULL, error); if (output == NULL) return FALSE; if (!g_output_stream_write_all (G_OUTPUT_STREAM (output), extension_contents, strlen (extension_contents), NULL, NULL, error)) return FALSE; metadata_contents = g_strdup_printf ("[Runtime]\n" "name=%s\n" "\n" "[ExtensionOf]\n" "ref=%s\n", debug_id, ref); if (!g_file_set_contents (flatpak_file_get_path_cached (metadata_debuginfo_file), metadata_contents, strlen (metadata_contents), error)) return FALSE; g_ptr_array_add (sub_ids, g_strdup (debug_id)); } for (l = self->add_extensions; l != NULL; l = l->next) { BuilderExtension *e = l->data; g_autofree char *extension_metadata_name = NULL; g_autoptr(GFile) metadata_extension_file = NULL; g_autofree char *metadata_contents = NULL; g_autofree char *extension_tag_opt = NULL; if (!builder_extension_is_bundled (e)) continue; extension_tag_opt = maybe_format_extension_tag (builder_manifest_get_extension_tag (self)); extension_metadata_name = g_strdup_printf ("metadata.%s", builder_extension_get_name (e)); metadata_extension_file = g_file_get_child (app_dir, extension_metadata_name); metadata_contents = g_strdup_printf ("[Runtime]\n" "name=%s\n" "\n" "[ExtensionOf]\n" "ref=%s\n" "%s", builder_extension_get_name (e), ref, extension_tag_opt); if (!g_file_set_contents (flatpak_file_get_path_cached (metadata_extension_file), metadata_contents, strlen (metadata_contents), error)) return FALSE; g_ptr_array_add (sub_ids, g_strdup (builder_extension_get_name (e))); } if (sub_ids->len > 0) { g_autoptr(GFile) metadata_file = NULL; g_autoptr(GFileOutputStream) output = NULL; g_autoptr(GString) extension_contents = g_string_new ("\n" "[Build]\n"); g_string_append (extension_contents, FLATPAK_METADATA_KEY_BUILD_EXTENSIONS"="); for (i = 0; i < sub_ids->len; i++) { g_string_append (extension_contents, (const char *)sub_ids->pdata[i]); g_string_append (extension_contents, ";"); } metadata_file = g_file_get_child (app_dir, "metadata"); output = g_file_append_to (metadata_file, G_FILE_CREATE_NONE, NULL, error); if (output == NULL) return FALSE; if (!g_output_stream_write_all (G_OUTPUT_STREAM (output), extension_contents->str, extension_contents->len, NULL, NULL, error)) return FALSE; } if (!builder_context_disable_rofiles (context, error)) return FALSE; if (!builder_cache_commit (cache, "Finish", error)) return FALSE; } else { g_print ("Cache hit for finish, skipping\n"); } return TRUE; } /* Creates the platform directory based on the base platform (with * locales moved in place if needed), and the metadata.platform file * for it */ static gboolean builder_manifest_create_platform_base (BuilderManifest *self, BuilderCache *cache, BuilderContext *context, GError **error) { builder_manifest_checksum_for_platform_base (self, cache, context); if (!builder_cache_lookup (cache, "platform-base")) { GFile *app_dir = NULL; g_autoptr(GFile) platform_dir = NULL; g_autoptr(GPtrArray) args = NULL; int i; g_print ("Creating platform based on %s\n", self->runtime); builder_set_term_title (_("Creating platform base for %s"), self->id); if (!builder_context_enable_rofiles (context, error)) return FALSE; /* Call after enabling rofiles */ app_dir = builder_context_get_app_dir (context); platform_dir = g_file_get_child (app_dir, "platform"); args = g_ptr_array_new_with_free_func (g_free); g_ptr_array_add (args, g_strdup ("flatpak")); g_ptr_array_add (args, g_strdup ("build-init")); g_ptr_array_add (args, g_strdup ("--update")); g_ptr_array_add (args, g_strdup ("--writable-sdk")); g_ptr_array_add (args, g_strdup ("--sdk-dir=platform")); g_ptr_array_add (args, g_strdup_printf ("--arch=%s", builder_context_get_arch (context))); for (i = 0; self->platform_extensions != NULL && self->platform_extensions[i] != NULL; i++) { const char *ext = self->platform_extensions[i]; g_ptr_array_add (args, g_strdup_printf ("--sdk-extension=%s", ext)); } g_ptr_array_add (args, g_file_get_path (app_dir)); g_ptr_array_add (args, g_strdup (self->id)); g_ptr_array_add (args, g_strdup (self->runtime)); g_ptr_array_add (args, g_strdup (self->runtime)); g_ptr_array_add (args, g_strdup (builder_manifest_get_runtime_version (self))); g_ptr_array_add (args, NULL); if (!flatpak_spawnv (NULL, NULL, G_SUBPROCESS_FLAGS_NONE, error, (const gchar * const *) args->pdata)) return FALSE; if (self->separate_locales) { if (!builder_migrate_locale_dirs (platform_dir, error)) return FALSE; } if (self->metadata_platform) { GFile *base_dir = builder_context_get_base_dir (context); g_autoptr(GFile) dest_metadata = g_file_get_child (app_dir, "metadata.platform"); g_autoptr(GFile) src_metadata = g_file_resolve_relative_path (base_dir, self->metadata_platform); g_autofree char *contents = NULL; gsize length; if (!g_file_get_contents (flatpak_file_get_path_cached (src_metadata), &contents, &length, error)) return FALSE; if (!g_file_set_contents (flatpak_file_get_path_cached (dest_metadata), contents, length, error)) return FALSE; } else { g_autoptr(GFile) metadata = g_file_get_child (app_dir, "metadata"); g_autoptr(GFile) dest_metadata = g_file_get_child (app_dir, "metadata.platform"); g_autoptr(GKeyFile) keyfile = g_key_file_new (); g_auto(GStrv) groups = NULL; int j; if (!g_key_file_load_from_file (keyfile, flatpak_file_get_path_cached (metadata), G_KEY_FILE_KEEP_COMMENTS|G_KEY_FILE_KEEP_TRANSLATIONS, error)) { g_prefix_error (error, "Can't load metadata file: "); return FALSE; } g_key_file_set_string (keyfile, FLATPAK_METADATA_GROUP_RUNTIME, FLATPAK_METADATA_KEY_NAME, self->id_platform); groups = g_key_file_get_groups (keyfile, NULL); for (j = 0; groups[j] != NULL; j++) { const char *ext; if (!g_str_has_prefix (groups[j], FLATPAK_METADATA_GROUP_PREFIX_EXTENSION)) continue; ext = groups[j] + strlen (FLATPAK_METADATA_GROUP_PREFIX_EXTENSION); if (g_str_has_prefix (ext, self->id) || (self->inherit_sdk_extensions && g_strv_contains ((const char * const *)self->inherit_sdk_extensions, ext))) { g_key_file_remove_group (keyfile, groups[j], NULL); } } if (!g_key_file_save_to_file (keyfile, flatpak_file_get_path_cached (dest_metadata), error)) { g_prefix_error (error, "Can't save metadata.platform: "); return FALSE; } } if (!builder_context_disable_rofiles (context, error)) return FALSE; if (!builder_cache_commit (cache, "Created platform base", error)) return FALSE; } else { g_print ("Cache hit for create platform base, skipping\n"); } return TRUE; } /* Run the prepare-platform commands, then layer on top all the * changes from the sdk build, except any new files mentioned by * cleanup-platform */ static gboolean builder_manifest_prepare_platform (BuilderManifest *self, BuilderCache *cache, BuilderContext *context, GError **error) { builder_manifest_checksum_for_platform_prepare (self, cache, context); if (!builder_cache_lookup (cache, "platform-prepare")) { GFile *app_dir = NULL; g_autoptr(GFile) platform_dir = NULL; g_autoptr(GHashTable) to_remove_ht = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); g_autoptr(GPtrArray) changes = NULL; GList *l; int i; g_print ("Preparing platform with new changes\n"); builder_set_term_title (_("Preparing platform for %s"), self->id); if (!builder_context_enable_rofiles (context, error)) return FALSE; /* Call after enabling rofiles */ app_dir = builder_context_get_app_dir (context); platform_dir = g_file_get_child (app_dir, "platform"); if (self->prepare_platform_commands) { g_auto(GStrv) env = builder_options_get_env (self->build_options, context); g_auto(GStrv) build_args = builder_options_get_build_args (self->build_options, context, error); if (!build_args) return FALSE; char *platform_args[] = { "--sdk-dir=platform", "--metadata=metadata.platform", NULL }; g_auto(GStrv) extra_args = strcatv (build_args, platform_args); for (i = 0; self->prepare_platform_commands[i] != NULL; i++) { if (!command (app_dir, env, extra_args, self->prepare_platform_commands[i], error)) return FALSE; } } for (l = self->expanded_modules; l != NULL; l = l->next) { BuilderModule *m = l->data; builder_module_cleanup_collect (m, TRUE, context, to_remove_ht); } /* This returns both additions and removals */ changes = builder_cache_get_all_changes (cache, error); if (changes == NULL) return FALSE; for (i = 0; i < changes->len; i++) { const char *changed = g_ptr_array_index (changes, i); g_autoptr(GFile) src = NULL; g_autoptr(GFile) dest = NULL; g_autoptr(GFileInfo) info = NULL; g_autoptr(GError) my_error = NULL; if (!g_str_has_prefix (changed, "usr/")) continue; if (g_str_has_prefix (changed, "usr/lib/debug/") && !g_str_equal (changed, "usr/lib/debug/app")) continue; src = g_file_resolve_relative_path (app_dir, changed); dest = g_file_resolve_relative_path (platform_dir, changed + strlen ("usr/")); info = g_file_query_info (src, "standard::type,standard::symlink-target", G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, &my_error); if (info == NULL && !g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) { g_propagate_error (error, g_steal_pointer (&my_error)); return FALSE; } g_clear_error (&my_error); if (info == NULL) { /* File was removed from sdk, remove from platform also if it exists there */ if (!g_file_delete (dest, NULL, &my_error) && !g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) { g_propagate_error (error, g_steal_pointer (&my_error)); return FALSE; } continue; } if (g_hash_table_contains (to_remove_ht, changed)) { g_print ("Ignoring %s\n", changed); continue; } if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) { if (!flatpak_mkdir_p (dest, NULL, error)) return FALSE; } else { g_autoptr(GFile) dest_parent = g_file_get_parent (dest); if (!flatpak_mkdir_p (dest_parent, NULL, error)) return FALSE; if (!g_file_delete (dest, NULL, &my_error) && !g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) { g_propagate_error (error, g_steal_pointer (&my_error)); return FALSE; } g_clear_error (&my_error); if (g_file_info_get_file_type (info) == G_FILE_TYPE_SYMBOLIC_LINK) { if (!g_file_make_symbolic_link (dest, g_file_info_get_symlink_target (info), NULL, error)) return FALSE; } else { g_autofree char *src_path = g_file_get_path (src); g_autofree char *dest_path = g_file_get_path (dest); if (link (src_path, dest_path)) { glnx_set_error_from_errno (error); return FALSE; } } } } if (!builder_context_disable_rofiles (context, error)) return FALSE; if (!builder_cache_commit (cache, "Prepared platform", error)) return FALSE; } else { g_print ("Cache hit for prepare platform, skipping\n"); } return TRUE; } /* Run the cleanup-platform-commands (which we want to run in a new cache state to ensure it gets freshly zeroed mtimes) * and other finishing touches that need to happen after that, such as adding separate locale metadata. */ static gboolean builder_manifest_finish_platform (BuilderManifest *self, BuilderCache *cache, BuilderContext *context, GError **error) { builder_manifest_checksum_for_platform_finish (self, cache, context); if (!builder_cache_lookup (cache, "platform-finish")) { GFile *app_dir = NULL; g_autoptr(GFile) platform_dir = NULL; g_autoptr(GFile) locale_dir = NULL; g_autofree char *ref = NULL; g_autoptr(GPtrArray) sub_ids = g_ptr_array_new_with_free_func (g_free); int i; g_print ("Finishing platform\n"); builder_set_term_title (_("Finishing up platform for %s"), self->id); if (!builder_context_enable_rofiles (context, error)) return FALSE; /* Call after enabling rofiles */ app_dir = builder_context_get_app_dir (context); platform_dir = g_file_get_child (app_dir, "platform"); locale_dir = g_file_resolve_relative_path (platform_dir, LOCALES_SEPARATE_DIR); ref = flatpak_compose_ref (!self->build_runtime && !self->build_extension, builder_manifest_get_id_platform (self), builder_manifest_get_branch (self, context), builder_context_get_arch (context)); if (self->cleanup_platform_commands) { g_auto(GStrv) env = builder_options_get_env (self->build_options, context); g_auto(GStrv) build_args = builder_options_get_build_args (self->build_options, context, error); if (!build_args) return FALSE; char *platform_args[] = { "--sdk-dir=platform", "--metadata=metadata.platform", NULL }; g_auto(GStrv) extra_args = strcatv (build_args, platform_args); for (i = 0; self->cleanup_platform_commands[i] != NULL; i++) { if (!command (app_dir, env, extra_args, self->cleanup_platform_commands[i], error)) return FALSE; } } if (self->separate_locales && g_file_query_exists (locale_dir, NULL)) { g_autoptr(GFile) metadata_file = NULL; g_autofree char *extension_contents = NULL; g_autoptr(GFileOutputStream) output = NULL; g_autoptr(GFile) metadata_locale_file = NULL; g_autofree char *metadata_contents = NULL; g_autofree char *locale_id = builder_manifest_get_locale_id_platform (self); metadata_file = g_file_get_child (app_dir, "metadata.platform"); extension_contents = g_strdup_printf ("\n" "[Extension %s]\n" "directory=%s\n" "autodelete=true\n" "locale-subset=true\n", locale_id, LOCALES_SEPARATE_DIR); if (!flatpak_break_hardlink (metadata_file, error)) return FALSE; output = g_file_append_to (metadata_file, G_FILE_CREATE_NONE, NULL, error); if (output == NULL) return FALSE; if (!g_output_stream_write_all (G_OUTPUT_STREAM (output), extension_contents, strlen (extension_contents), NULL, NULL, error)) return FALSE; metadata_locale_file = g_file_get_child (app_dir, "metadata.platform.locale"); metadata_contents = g_strdup_printf ("[Runtime]\n" "name=%s\n" "\n" "[ExtensionOf]\n" "ref=%s\n", locale_id, ref); if (!g_file_set_contents (flatpak_file_get_path_cached (metadata_locale_file), metadata_contents, strlen (metadata_contents), error)) return FALSE; g_ptr_array_add (sub_ids, g_strdup (locale_id)); } if (sub_ids->len > 0) { g_autoptr(GFile) metadata_file = NULL; g_autoptr(GFileOutputStream) output = NULL; g_autoptr(GString) extension_contents = g_string_new ("\n" "[Build]\n"); g_string_append (extension_contents, FLATPAK_METADATA_KEY_BUILD_EXTENSIONS"="); for (i = 0; i < sub_ids->len; i++) { g_string_append (extension_contents, (const char *)sub_ids->pdata[i]); g_string_append (extension_contents, ";"); } metadata_file = g_file_get_child (app_dir, "metadata.platform"); if (!flatpak_break_hardlink (metadata_file, error)) return FALSE; output = g_file_append_to (metadata_file, G_FILE_CREATE_NONE, NULL, error); if (output == NULL) return FALSE; if (!g_output_stream_write_all (G_OUTPUT_STREAM (output), extension_contents->str, extension_contents->len, NULL, NULL, error)) return FALSE; } if (!builder_context_disable_rofiles (context, error)) return FALSE; if (!builder_cache_commit (cache, "Platform finish", error)) return FALSE; } else { g_print ("Cache hit for platform finish, skipping\n"); } return TRUE; } gboolean builder_manifest_create_platform (BuilderManifest *self, BuilderCache *cache, BuilderContext *context, GError **error) { if (!self->build_runtime || self->id_platform == NULL) return TRUE; if (!builder_manifest_create_platform_base (self, cache, context, error)) return FALSE; if (!builder_manifest_prepare_platform (self, cache, context, error)) return FALSE; if (!builder_manifest_finish_platform (self, cache, context, error)) return FALSE; return TRUE; } gboolean builder_manifest_bundle_sources (BuilderManifest *self, const char *json, BuilderCache *cache, BuilderContext *context, GError **error) { builder_manifest_checksum_for_bundle_sources (self, cache, context); if (!builder_cache_lookup (cache, "bundle-sources")) { g_autofree char *sources_id = builder_manifest_get_sources_id (self); GFile *app_dir; g_autoptr(GFile) metadata_sources_file = NULL; g_autoptr(GFile) metadata = NULL; g_autoptr(GFile) json_dir = NULL; g_autofree char *manifest_filename = NULL; g_autoptr(GFile) manifest_file = NULL; g_autofree char *metadata_contents = NULL; g_autoptr(GKeyFile) metadata_keyfile = g_key_file_new (); g_autoptr(GPtrArray) subs = g_ptr_array_new (); g_auto(GStrv) old_subs = NULL; gsize i; GList *l; g_print ("Bundling sources\n"); builder_set_term_title (_("Bunding sources for %s"), self->id); if (!builder_context_enable_rofiles (context, error)) return FALSE; app_dir = builder_context_get_app_dir (context); metadata_sources_file = g_file_get_child (app_dir, "metadata.sources"); metadata_contents = g_strdup_printf ("[Runtime]\n" "name=%s\n", sources_id); if (!g_file_set_contents (flatpak_file_get_path_cached (metadata_sources_file), metadata_contents, strlen (metadata_contents), error)) return FALSE; json_dir = g_file_resolve_relative_path (app_dir, "sources/manifest"); if (!flatpak_mkdir_p (json_dir, NULL, error)) return FALSE; manifest_filename = g_strconcat (self->id, ".json", NULL); manifest_file = g_file_get_child (json_dir, manifest_filename); if (!g_file_set_contents (flatpak_file_get_path_cached (manifest_file), json, strlen (json), error)) return FALSE; for (l = self->expanded_modules; l != NULL; l = l->next) { BuilderModule *m = l->data; if (!builder_module_bundle_sources (m, context, error)) return FALSE; } metadata = g_file_get_child (app_dir, "metadata"); if (!g_key_file_load_from_file (metadata_keyfile, flatpak_file_get_path_cached (metadata), G_KEY_FILE_KEEP_COMMENTS|G_KEY_FILE_KEEP_TRANSLATIONS, error)) { g_prefix_error (error, "Can't load main metadata file: "); return FALSE; } old_subs = g_key_file_get_string_list (metadata_keyfile, "Build", "built-extensions", NULL, NULL); for (i = 0; old_subs != NULL && old_subs[i] != NULL; i++) g_ptr_array_add (subs, old_subs[i]); g_ptr_array_add (subs, sources_id); g_key_file_set_string_list (metadata_keyfile, FLATPAK_METADATA_GROUP_BUILD, FLATPAK_METADATA_KEY_BUILD_EXTENSIONS, (const char * const *)subs->pdata, subs->len); if (!g_key_file_save_to_file (metadata_keyfile, flatpak_file_get_path_cached (metadata), error)) { g_prefix_error (error, "Can't save metadata.platform: "); return FALSE; } if (!builder_context_disable_rofiles (context, error)) return FALSE; if (!builder_cache_commit (cache, "Bundled sources", error)) return FALSE; } else { g_print ("Cache hit for bundle-sources, skipping\n"); } return TRUE; } gboolean builder_manifest_show_deps (BuilderManifest *self, BuilderContext *context, GError **error) { g_autoptr(GHashTable) names = g_hash_table_new (g_str_hash, g_str_equal); GList *l; if (!expand_modules (context, self->modules, &self->expanded_modules, names, error)) return FALSE; for (l = self->expanded_modules; l != NULL; l = l->next) { BuilderModule *module = l->data; if (!builder_module_show_deps (module, context, error)) return FALSE; } return TRUE; } static gboolean builder_manifest_install_single_dep (const char *ref, const char *remote, gboolean opt_user, const char *opt_installation, GError **error) { g_autoptr(GPtrArray) args = NULL; args = g_ptr_array_new_with_free_func (g_free); g_ptr_array_add (args, g_strdup ("flatpak")); add_installation_args (args, opt_user, opt_installation); g_ptr_array_add (args, g_strdup ("install")); g_ptr_array_add (args, g_strdup (remote)); g_ptr_array_add (args, g_strdup ("-y")); if (flatpak_version_check (1, 2, 0)) g_ptr_array_add (args, g_strdup ("--noninteractive")); g_ptr_array_add (args, g_strdup (ref)); g_ptr_array_add (args, NULL); if (!builder_maybe_host_spawnv (NULL, NULL, 0, error, (const char * const *)args->pdata)) { g_autofree char *commandline = flatpak_quote_argv ((const char **)args->pdata); g_prefix_error (error, "running `%s`: ", commandline); return FALSE; } else { return TRUE; } } static gboolean builder_manifest_update_single_dep (const char *ref, gboolean opt_user, const char *opt_installation, GError **error) { g_autoptr(GPtrArray) args = NULL; args = g_ptr_array_new_with_free_func (g_free); g_ptr_array_add (args, g_strdup ("flatpak")); add_installation_args (args, opt_user, opt_installation); g_ptr_array_add (args, g_strdup ("update")); g_ptr_array_add (args, g_strdup ("--subpath=")); g_ptr_array_add (args, g_strdup ("-y")); if (flatpak_version_check (1, 2, 0)) g_ptr_array_add (args, g_strdup ("--noninteractive")); g_ptr_array_add (args, g_strdup (ref)); g_ptr_array_add (args, NULL); if (!builder_maybe_host_spawnv (NULL, NULL, 0, error, (const char * const *)args->pdata)) { g_autofree char *commandline = flatpak_quote_argv ((const char **)args->pdata); g_prefix_error (error, "running `%s`: ", commandline); return FALSE; } else { return TRUE; } } static gboolean builder_manifest_install_dep (BuilderManifest *self, BuilderContext *context, char *const *remotes, gboolean opt_user, const char *opt_installation, const char *runtime, const char *version, gboolean opt_yes, GError **error) { g_autofree char *ref = NULL; g_autofree char *commit = NULL; g_autoptr(GError) first_error = NULL; if (version == NULL) version = builder_manifest_get_runtime_version (self); ref = flatpak_build_untyped_ref (runtime, version, builder_context_get_arch (context)); commit = flatpak_info (opt_user, opt_installation, "--show-commit", ref, NULL); if (commit != NULL) { g_print("Updating %s\n", ref); if (builder_manifest_update_single_dep(ref, opt_user, opt_installation, error)) { return TRUE; } } else { gboolean multiple_remotes = (*(remotes+1) != NULL); for (const char *remote = *remotes; remote != NULL; remote = *(++remotes)) { g_autoptr(GError) current_error = NULL; if (multiple_remotes) { g_print("Trying to install %s from %s\n", ref, remote); } else { g_print("Installing %s from %s\n", ref, remote); } if (builder_manifest_install_single_dep (ref, remote, opt_user, opt_installation, ¤t_error)) { return TRUE; } else { gboolean fatal_error = current_error->domain != G_SPAWN_EXIT_ERROR; if (first_error == NULL) { first_error = g_steal_pointer(¤t_error); } if (fatal_error) { break; } } } } *error = g_steal_pointer(&first_error); return FALSE; } static gboolean builder_manifest_install_extension_deps (BuilderManifest *self, BuilderContext *context, const char *runtime, const char *runtime_version, char **runtime_extensions, char * const *remotes, gboolean opt_user, const char *opt_installation, gboolean opt_yes, GError **error) { g_autofree char *runtime_ref = flatpak_build_runtime_ref (runtime, runtime_version, builder_context_get_arch (context)); g_autofree char *metadata = NULL; g_autoptr(GKeyFile) keyfile = g_key_file_new (); int i; if (runtime_extensions == NULL) return TRUE; metadata = flatpak_info (opt_user, opt_installation, "--show-metadata", runtime_ref, NULL); if (metadata == NULL) return FALSE; if (!g_key_file_load_from_data (keyfile, metadata, -1, 0, error)) return FALSE; for (i = 0; runtime_extensions != NULL && runtime_extensions[i] != NULL; i++) { g_autofree char *extension_group = g_strdup_printf ("Extension %s", runtime_extensions[i]); g_autofree char *extension_version = NULL; if (!g_key_file_has_group (keyfile, extension_group)) { g_autofree char *base = g_strdup (runtime_extensions[i]); char *dot = strrchr (base, '.'); if (dot != NULL) *dot = 0; g_free (extension_group); extension_group = g_strdup_printf ("Extension %s", base); if (!g_key_file_has_group (keyfile, extension_group)) return flatpak_fail (error, "Unknown extension '%s' in runtime\n", runtime_extensions[i]); } extension_version = g_key_file_get_string (keyfile, extension_group, "version", NULL); if (extension_version == NULL) extension_version = g_strdup (runtime_version); g_print ("Dependency Extension: %s %s\n", runtime_extensions[i], extension_version); if (!builder_manifest_install_dep (self, context, remotes, opt_user, opt_installation, runtime_extensions[i], extension_version, opt_yes, error)) return FALSE; } return TRUE; } gboolean builder_manifest_install_deps (BuilderManifest *self, BuilderContext *context, char * const *remotes, gboolean opt_user, const char *opt_installation, gboolean opt_yes, GError **error) { GList *l; const char *sdk = NULL; const char *sdk_branch = NULL; g_auto(GStrv) sdk_parts = g_strsplit (self->sdk, "/", 3); if (g_strv_length (sdk_parts) >= 3) { sdk = sdk_parts[0]; sdk_branch = sdk_parts[2]; } else { sdk = self->sdk; sdk_branch = builder_manifest_get_runtime_version (self); } /* Sdk */ g_print ("Dependency Sdk: %s %s\n", sdk, sdk_branch); if (!builder_manifest_install_dep (self, context, remotes, opt_user, opt_installation, sdk, sdk_branch, opt_yes, error)) return FALSE; /* Runtime */ g_print ("Dependency Runtime: %s %s\n", self->runtime, builder_manifest_get_runtime_version (self)); if (!builder_manifest_install_dep (self, context, remotes, opt_user, opt_installation, self->runtime, builder_manifest_get_runtime_version (self), opt_yes, error)) return FALSE; if (self->base) { g_print ("Dependency Base: %s %s\n", self->base, builder_manifest_get_base_version (self)); if (!builder_manifest_install_dep (self, context, remotes, opt_user, opt_installation, self->base, builder_manifest_get_base_version (self), opt_yes, error)) return FALSE; } if (!builder_manifest_install_extension_deps (self, context, sdk, sdk_branch, self->sdk_extensions, remotes, opt_user, opt_installation, opt_yes, error)) return FALSE; if (!builder_manifest_install_extension_deps (self, context, self->runtime, builder_manifest_get_runtime_version (self), self->platform_extensions, remotes, opt_user, opt_installation, opt_yes, error)) return FALSE; for (l = self->add_build_extensions; l != NULL; l = l->next) { BuilderExtension *extension = l->data; const char *name = builder_extension_get_name (extension); const char *version = builder_extension_get_version (extension); if (name == NULL || version == NULL) continue; g_print ("Dependency Extension: %s %s\n", name, version); if (!builder_manifest_install_dep (self, context, remotes, opt_user, opt_installation, name, version, opt_yes, error)) return FALSE; } return TRUE; } gboolean builder_manifest_run (BuilderManifest *self, BuilderContext *context, FlatpakContext *arg_context, char **argv, int argc, gboolean log_session_bus, gboolean log_system_bus, GError **error) { g_autoptr(GPtrArray) args = NULL; g_autofree char *commandline = NULL; g_autofree char *build_dir_path = NULL; g_autofree char *ccache_dir_path = NULL; g_auto(GStrv) env = NULL; g_auto(GStrv) build_args = NULL; int i; if (!builder_context_enable_rofiles (context, error)) return FALSE; if (!flatpak_mkdir_p (builder_context_get_build_dir (context), NULL, error)) return FALSE; args = g_ptr_array_new_with_free_func (g_free); g_ptr_array_add (args, g_strdup ("flatpak")); g_ptr_array_add (args, g_strdup ("build")); g_ptr_array_add (args, g_strdup ("--with-appdir")); build_dir_path = g_file_get_path (builder_context_get_build_dir (context)); /* We're not sure what we're building here, so lets set both the /run/build and /run/build-runtime dirs to the build dirs */ g_ptr_array_add (args, g_strdup_printf ("--bind-mount=/run/build=%s", build_dir_path)); g_ptr_array_add (args, g_strdup_printf ("--bind-mount=/run/build-runtime=%s", build_dir_path)); if (g_file_query_exists (builder_context_get_ccache_dir (context), NULL)) { ccache_dir_path = g_file_get_path (builder_context_get_ccache_dir (context)); g_ptr_array_add (args, g_strdup_printf ("--bind-mount=/run/ccache=%s", ccache_dir_path)); } build_args = builder_options_get_build_args (self->build_options, context, error); if (build_args == NULL) return FALSE; for (i = 0; build_args[i] != NULL; i++) g_ptr_array_add (args, g_strdup (build_args[i])); env = builder_options_get_env (self->build_options, context); if (env) { for (i = 0; env[i] != NULL; i++) g_ptr_array_add (args, g_strdup_printf ("--env=%s", env[i])); } /* Just add something so that we get the default rules (own our own id) */ g_ptr_array_add (args, g_strdup ("--talk-name=org.freedesktop.DBus")); if (log_session_bus) g_ptr_array_add (args, g_strdup ("--log-session-bus")); if (log_system_bus) g_ptr_array_add (args, g_strdup ("--log-system-bus")); /* Inherit all finish args except --filesystem and some that * build doesn't understand so the command gets the same access * as the final app */ if (self->finish_args) { for (i = 0; self->finish_args[i] != NULL; i++) { const char *arg = self->finish_args[i]; if (!g_str_has_prefix (arg, "--filesystem") && !g_str_has_prefix (arg, "--extension") && !g_str_has_prefix (arg, "--sdk") && !g_str_has_prefix (arg, "--runtime") && !g_str_has_prefix (arg, "--command") && !g_str_has_prefix (arg, "--extra-data") && !g_str_has_prefix (arg, "--require-version")) g_ptr_array_add (args, g_strdup (arg)); } } flatpak_context_to_args (arg_context, args); g_ptr_array_add (args, g_file_get_path (builder_context_get_app_dir (context))); for (i = 0; i < argc; i++) g_ptr_array_add (args, g_strdup (argv[i])); g_ptr_array_add (args, NULL); commandline = flatpak_quote_argv ((const char **) args->pdata); g_debug ("Running '%s'", commandline); if (flatpak_is_in_sandbox ()) { if (builder_host_spawnv (NULL, NULL, G_SUBPROCESS_FLAGS_STDIN_INHERIT, NULL, (const gchar * const *)args->pdata)) exit (1); else exit (0); } else { if (execvp ((char *) args->pdata[0], (char **) args->pdata) == -1) { g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno), "Unable to start flatpak build"); return FALSE; } } /* Not reached */ return TRUE; } char ** builder_manifest_get_exclude_dirs (BuilderManifest *self) { g_autoptr(GPtrArray) dirs = NULL; GList *l; dirs = g_ptr_array_new (); for (l = self->add_extensions; l != NULL; l = l->next) { BuilderExtension *e = l->data; if (builder_extension_is_bundled (e)) g_ptr_array_add (dirs, g_strdup (builder_extension_get_directory (e))); } g_ptr_array_add (dirs, NULL); return (char **)g_ptr_array_free (g_steal_pointer (&dirs), FALSE); }