flatpak-builder/src/builder-main.c

1217 lines
46 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters!

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

/*
* Copyright © 2015 Red Hat, Inc
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors:
* Alexander Larsson <alexl@redhat.com>
*/
#include "config.h"
#include <locale.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <glib/gi18n.h>
#include <gio/gio.h>
#include "libglnx/libglnx.h"
#include "builder-flatpak-utils.h"
#include "builder-manifest.h"
#include "builder-utils.h"
#include "builder-git.h"
static gboolean opt_verbose;
static gboolean opt_version;
static gboolean opt_run;
static gboolean opt_disable_cache;
static gboolean opt_disable_tests;
static gboolean opt_disable_rofiles;
static gboolean opt_download_only;
static gboolean opt_no_shallow_clone;
static gboolean opt_bundle_sources;
static gboolean opt_build_only;
static gboolean opt_finish_only;
static gboolean opt_export_only;
static gboolean opt_show_deps;
static gboolean opt_show_manifest;
static gboolean opt_disable_download;
static gboolean opt_disable_updates;
static gboolean opt_ccache;
static gboolean opt_require_changes;
static gboolean opt_keep_build_dirs;
static gboolean opt_delete_build_dirs;
static gboolean opt_force_clean;
static gboolean opt_allow_missing_runtimes;
static gboolean opt_sandboxed;
static gboolean opt_rebuild_on_sdk_change;
static gboolean opt_skip_if_unchanged;
static gboolean opt_install;
static char *opt_state_dir;
static char *opt_from_git;
static char *opt_from_git_branch;
static char *opt_stop_at;
static char *opt_build_shell;
static char *opt_arch;
static char *opt_default_branch;
static char *opt_repo;
static char *opt_subject;
static char *opt_body;
static char *opt_collection_id = NULL;
static int opt_token_type = -1;
static char *opt_gpg_homedir;
static char **opt_key_ids;
static char **opt_sources_dirs;
static char **opt_sources_urls;
static char **opt_add_tags;
static char **opt_remove_tags;
static int opt_jobs;
static char *opt_mirror_screenshots_url;
static char **opt_install_deps_from;
static gboolean opt_install_deps_only;
static gboolean opt_user;
static char *opt_installation;
static gboolean opt_log_session_bus;
static gboolean opt_log_system_bus;
static gboolean opt_yes;
static GOptionEntry entries[] = {
{ "verbose", 'v', 0, G_OPTION_ARG_NONE, &opt_verbose, "Print debug information during command processing", NULL },
{ "version", 0, 0, G_OPTION_ARG_NONE, &opt_version, "Print version information and exit", NULL },
{ "arch", 0, 0, G_OPTION_ARG_STRING, &opt_arch, "Architecture to build for (must be host compatible)", "ARCH" },
{ "default-branch", 0, 0, G_OPTION_ARG_STRING, &opt_default_branch, "Change the default branch", "BRANCH" },
{ "add-tag", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_add_tags, "Add a tag to the build", "TAG"},
{ "remove-tag", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_remove_tags, "Remove a tag from the build", "TAG"},
{ "run", 0, 0, G_OPTION_ARG_NONE, &opt_run, "Run a command in the build directory (see --run --help)", NULL },
{ "ccache", 0, 0, G_OPTION_ARG_NONE, &opt_ccache, "Use ccache", NULL },
{ "disable-cache", 0, 0, G_OPTION_ARG_NONE, &opt_disable_cache, "Disable cache lookups", NULL },
{ "disable-tests", 0, 0, G_OPTION_ARG_NONE, &opt_disable_tests, "Don't run tests", NULL },
{ "disable-rofiles-fuse", 0, 0, G_OPTION_ARG_NONE, &opt_disable_rofiles, "Disable rofiles-fuse use", NULL },
{ "disable-download", 0, 0, G_OPTION_ARG_NONE, &opt_disable_download, "Don't download any new sources", NULL },
{ "disable-updates", 0, 0, G_OPTION_ARG_NONE, &opt_disable_updates, "Only download missing sources, never update to latest vcs version", NULL },
{ "download-only", 0, 0, G_OPTION_ARG_NONE, &opt_download_only, "Only download sources, don't build", NULL },
{ "bundle-sources", 0, 0, G_OPTION_ARG_NONE, &opt_bundle_sources, "Bundle module sources as runtime", NULL },
{ "extra-sources", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_sources_dirs, "Add a directory of sources specified by SOURCE-DIR, multiple uses of this option possible", "SOURCE-DIR"},
{ "extra-sources-url", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_sources_urls, "Add a url of sources specified by SOURCE-URL multiple uses of this option possible", "SOURCE-URL"},
{ "build-only", 0, 0, G_OPTION_ARG_NONE, &opt_build_only, "Stop after build, don't run clean and finish phases", NULL },
{ "finish-only", 0, 0, G_OPTION_ARG_NONE, &opt_finish_only, "Only run clean and finish and export phases", NULL },
{ "export-only", 0, 0, G_OPTION_ARG_NONE, &opt_export_only, "Only run export phase", NULL },
{ "allow-missing-runtimes", 0, 0, G_OPTION_ARG_NONE, &opt_allow_missing_runtimes, "Don't fail if runtime and sdk missing", NULL },
{ "show-deps", 0, 0, G_OPTION_ARG_NONE, &opt_show_deps, "List the dependencies of the json file (see --show-deps --help)", NULL },
{ "show-manifest", 0, 0, G_OPTION_ARG_NONE, &opt_show_manifest, "Print out the manifest file in standard json format (see --show-manifest --help)", NULL },
{ "require-changes", 0, 0, G_OPTION_ARG_NONE, &opt_require_changes, "Don't create app dir or export if no changes", NULL },
{ "keep-build-dirs", 0, 0, G_OPTION_ARG_NONE, &opt_keep_build_dirs, "Don't remove build directories after install", NULL },
{ "delete-build-dirs", 0, 0, G_OPTION_ARG_NONE, &opt_delete_build_dirs, "Always remove build directories, even after build failure", NULL },
{ "repo", 0, 0, G_OPTION_ARG_STRING, &opt_repo, "Repo to export into", "DIR"},
{ "subject", 's', 0, G_OPTION_ARG_STRING, &opt_subject, "One line subject (passed to build-export)", "SUBJECT" },
{ "body", 'b', 0, G_OPTION_ARG_STRING, &opt_body, "Full description (passed to build-export)", "BODY" },
{ "collection-id", 0, 0, G_OPTION_ARG_STRING, &opt_collection_id, "Collection ID (passed to build-export)", "COLLECTION-ID" },
{ "token-type", 0, 0, G_OPTION_ARG_INT, &opt_token_type, "Set type of token needed to install this commit (passed to build-export)", "VAL" },
{ "gpg-sign", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_key_ids, "GPG Key ID to sign the commit with", "KEY-ID"},
{ "gpg-homedir", 0, 0, G_OPTION_ARG_STRING, &opt_gpg_homedir, "GPG Homedir to use when looking for keyrings", "HOMEDIR"},
{ "force-clean", 0, 0, G_OPTION_ARG_NONE, &opt_force_clean, "Erase previous contents of DIRECTORY", NULL },
{ "sandbox", 0, 0, G_OPTION_ARG_NONE, &opt_sandboxed, "Enforce sandboxing, disabling build-args", NULL },
{ "stop-at", 0, 0, G_OPTION_ARG_STRING, &opt_stop_at, "Stop building at this module (implies --build-only)", "MODULENAME"},
{ "jobs", 0, 0, G_OPTION_ARG_INT, &opt_jobs, "Number of parallel jobs to build (default=NCPU)", "JOBS"},
{ "rebuild-on-sdk-change", 0, 0, G_OPTION_ARG_NONE, &opt_rebuild_on_sdk_change, "Rebuild if sdk changes", NULL },
{ "skip-if-unchanged", 0, 0, G_OPTION_ARG_NONE, &opt_skip_if_unchanged, "Don't do anything if the json didn't change", NULL },
{ "build-shell", 0, 0, G_OPTION_ARG_STRING, &opt_build_shell, "Extract and prepare sources for module, then start build shell", "MODULENAME"},
{ "from-git", 0, 0, G_OPTION_ARG_STRING, &opt_from_git, "Get input files from git repo", "URL"},
{ "from-git-branch", 0, 0, G_OPTION_ARG_STRING, &opt_from_git_branch, "Branch to use in --from-git", "BRANCH"},
{ "mirror-screenshots-url", 0, 0, G_OPTION_ARG_STRING, &opt_mirror_screenshots_url, "Download and rewrite screenshots to match this url", "URL"},
{ "install", 0, 0, G_OPTION_ARG_NONE, &opt_install, "Install if build succeeds", NULL},
{ "install-deps-from", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_install_deps_from, "Install build dependencies from this remote", "REMOTE"},
{ "install-deps-only", 0, 0, G_OPTION_ARG_NONE, &opt_install_deps_only, "Stop after installing dependencies"},
{ "user", 0, 0, G_OPTION_ARG_NONE, &opt_user, "Install dependencies in user installations", NULL },
{ "system", 0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &opt_user, "Install dependencies in system-wide installations (default)", NULL },
{ "installation", 0, 0, G_OPTION_ARG_STRING, &opt_installation, "Install dependencies in a specific system-wide installation", "NAME" },
{ "state-dir", 0, 0, G_OPTION_ARG_FILENAME, &opt_state_dir, "Use this directory for state instead of .flatpak-builder", "PATH" },
{ "assumeyes", 'y', 0, G_OPTION_ARG_NONE, &opt_yes, N_("Automatically answer yes for all questions"), NULL },
{ "no-shallow-clone", 0, 0, G_OPTION_ARG_NONE, &opt_no_shallow_clone, "Don't use shallow clones when mirroring git repos", NULL },
{ NULL }
};
static GOptionEntry run_entries[] = {
{ "verbose", 'v', 0, G_OPTION_ARG_NONE, &opt_verbose, "Print debug information during command processing", NULL },
{ "arch", 0, 0, G_OPTION_ARG_STRING, &opt_arch, "Architecture to build for (must be host compatible)", "ARCH" },
{ "run", 0, 0, G_OPTION_ARG_NONE, &opt_run, "Run a command in the build directory", NULL },
{ "log-session-bus", 0, 0, G_OPTION_ARG_NONE, &opt_log_session_bus, N_("Log session bus calls"), NULL },
{ "log-system-bus", 0, 0, G_OPTION_ARG_NONE, &opt_log_system_bus, N_("Log system bus calls"), NULL },
{ "ccache", 0, 0, G_OPTION_ARG_NONE, &opt_ccache, "Use ccache", NULL },
{ NULL }
};
static GOptionEntry show_deps_entries[] = {
{ "verbose", 'v', 0, G_OPTION_ARG_NONE, &opt_verbose, "Print debug information during command processing", NULL },
{ "show-deps", 0, 0, G_OPTION_ARG_NONE, &opt_show_deps, "List the dependencies of the json file", NULL },
{ NULL }
};
static GOptionEntry show_manifest_entries[] = {
{ "verbose", 'v', 0, G_OPTION_ARG_NONE, &opt_verbose, "Print debug information during command processing", NULL },
{ "show-manifest", 0, 0, G_OPTION_ARG_NONE, &opt_show_manifest, "Print out the manifest file in standard json format", NULL },
{ NULL }
};
static void
message_handler (const gchar *log_domain,
GLogLevelFlags log_level,
const gchar *message,
gpointer user_data)
{
/* Make this look like normal console output */
if (log_level & G_LOG_LEVEL_DEBUG)
g_printerr ("FB: %s\n", message);
else
g_printerr ("%s: %s\n", g_get_prgname (), message);
}
static int
usage (GOptionContext *context, const char *message)
{
g_autofree gchar *help = g_option_context_get_help (context, TRUE, NULL);
g_printerr ("%s\n", message);
g_printerr ("%s", help);
return 1;
}
static const char skip_arg[] = "skip";
static gboolean
do_export (BuilderContext *build_context,
GError **error,
gboolean runtime,
const gchar *location,
const gchar *directory,
char **exclude_dirs,
const gchar *branch,
const gchar *collection_id,
gint32 token_type,
...)
{
va_list ap;
const char *arg;
int i;
g_autoptr(GPtrArray) args = NULL;
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-export"));
g_ptr_array_add (args, g_strdup_printf ("--arch=%s", builder_context_get_arch (build_context)));
if (runtime)
g_ptr_array_add (args, g_strdup ("--runtime"));
if (opt_subject)
g_ptr_array_add (args, g_strdup_printf ("--subject=%s", opt_subject));
if (opt_body)
g_ptr_array_add (args, g_strdup_printf ("--body=%s", opt_body));
if (opt_gpg_homedir)
g_ptr_array_add (args, g_strdup_printf ("--gpg-homedir=%s", opt_gpg_homedir));
for (i = 0; opt_key_ids != NULL && opt_key_ids[i] != NULL; i++)
g_ptr_array_add (args, g_strdup_printf ("--gpg-sign=%s", opt_key_ids[i]));
if (collection_id)
g_ptr_array_add (args, g_strdup_printf ("--collection-id=%s", collection_id));
if (token_type >= 0)
g_ptr_array_add (args, g_strdup_printf ("--token-type=%d", token_type));
/* Additional flags. */
va_start (ap, token_type);
while ((arg = va_arg (ap, const gchar *)))
if (arg != skip_arg)
g_ptr_array_add (args, g_strdup ((gchar *) arg));
va_end (ap);
if (exclude_dirs)
{
for (i = 0; exclude_dirs[i] != NULL; i++)
g_ptr_array_add (args, g_strdup_printf ("--exclude=/%s/*", exclude_dirs[i]));
}
/* Mandatory positional arguments. */
g_ptr_array_add (args, g_strdup (location));
g_ptr_array_add (args, g_strdup (directory));
g_ptr_array_add (args, g_strdup (branch));
g_ptr_array_add (args, NULL);
return flatpak_spawnv (NULL, NULL, G_SUBPROCESS_FLAGS_NONE, error,
(const gchar * const *) args->pdata);
}
static gboolean
do_install (BuilderContext *build_context,
const gchar *repodir,
const gchar *id,
const gchar *branch,
GError **error)
{
g_autofree char *ref = NULL;
g_autoptr(GPtrArray) args = NULL;
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 ("install"));
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"));
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 ("--reinstall"));
ref = flatpak_build_untyped_ref (id, branch,
builder_context_get_arch (build_context));
g_ptr_array_add (args, g_strdup (repodir));
g_ptr_array_add (args, g_strdup (ref));
g_ptr_array_add (args, NULL);
return flatpak_spawnv (NULL, NULL, G_SUBPROCESS_FLAGS_NONE, error,
(const gchar * const *) args->pdata);
}
static gboolean
git (char **output,
GError **error,
...)
{
gboolean res;
va_list ap;
if (output != NULL)
*output = NULL;
va_start (ap, error);
res = flatpak_spawn (NULL, output, 0, error, "git", ap);
va_end (ap);
if (output != NULL &&
(*output != NULL && *output[0] == '\0'))
{
g_free (*output);
*output = NULL;
}
return res;
}
static char *
trim_linefeed (char *str)
{
guint len;
g_return_val_if_fail (str != NULL, NULL);
len = strlen (str);
str[len] = '\0';
return str;
}
static void
git_init_email (void)
{
char *user, *email;
/* Have an email for author and committer */
if (!git (&email, NULL, "config", "--get", "user.email", NULL) ||
email == NULL)
email = g_strdup ("flatpak-builder-commit@flatpak.org");
else
email = trim_linefeed (email);
g_setenv ("GIT_AUTHOR_EMAIL", email, FALSE);
g_setenv ("GIT_COMMITTER_EMAIL", email, FALSE);
g_free (email);
/* Have a "real name" for author and committer */
if (!git (&user, NULL, "config", "--get", "user.name", NULL) ||
user == NULL)
user = g_strdup ("Flatpak git committer");
else
user = trim_linefeed (user);
g_setenv ("GIT_AUTHOR_NAME", user, FALSE);
g_setenv ("GIT_COMMITTER_NAME", user, FALSE);
g_free (user);
}
int
main (int argc,
char **argv)
{
g_autofree const char *old_env = NULL;
g_autoptr(GError) error = NULL;
g_autoptr(BuilderManifest) manifest = NULL;
g_autoptr(GOptionContext) context = NULL;
const char *app_dir_path = NULL, *manifest_rel_path;
g_autofree gchar *manifest_contents = NULL;
g_autofree gchar *manifest_sha256 = NULL;
g_autofree gchar *old_manifest_sha256 = NULL;
g_autoptr(BuilderContext) build_context = NULL;
g_autoptr(GFile) base_dir = NULL;
g_autoptr(GFile) manifest_file = NULL;
g_autoptr(GFile) app_dir = NULL;
g_autoptr(BuilderCache) cache = NULL;
g_autofree char *cache_branch = NULL;
g_autofree char *escaped_cache_branch = NULL;
g_autoptr(GFileEnumerator) dir_enum = NULL;
g_autoptr(GFileEnumerator) dir_enum2 = NULL;
g_autofree char *cwd = NULL;
g_autoptr(GFile) cwd_dir = NULL;
GFileInfo *next = NULL;
const char *platform_id = NULL;
g_autofree char **orig_argv = NULL;
gboolean is_run = FALSE;
gboolean is_show_deps = FALSE;
gboolean is_show_manifest = FALSE;
gboolean app_dir_is_empty = FALSE;
gboolean prune_unused_stages = FALSE;
g_autoptr(FlatpakContext) arg_context = NULL;
g_autoptr(FlatpakTempDir) cleanup_manifest_dir = NULL;
g_autofree char *manifest_basename = NULL;
g_autoptr(GFile) export_repo = NULL;
int i, first_non_arg, orig_argc;
int argnr;
char *p;
struct stat statbuf;
setlocale (LC_ALL, "");
g_log_set_handler (NULL, G_LOG_LEVEL_MESSAGE, message_handler, NULL);
g_set_prgname (argv[0]);
/* avoid gvfs (http://bugzilla.gnome.org/show_bug.cgi?id=526454) */
old_env = g_strdup (g_getenv ("GIO_USE_VFS"));
g_setenv ("GIO_USE_VFS", "local", TRUE);
g_vfs_get_default ();
if (old_env)
g_setenv ("GIO_USE_VFS", old_env, TRUE);
else
g_unsetenv ("GIO_USE_VFS");
/* Work around libsoup/glib race condition, as per:
https://bugzilla.gnome.org/show_bug.cgi?id=796031 and
https://bugzilla.gnome.org/show_bug.cgi?id=674885#c87 */
g_type_ensure (G_TYPE_SOCKET_FAMILY);
g_type_ensure (G_TYPE_SOCKET_TYPE);
g_type_ensure (G_TYPE_SOCKET_PROTOCOL);
g_type_ensure (G_TYPE_SOCKET_ADDRESS);
orig_argv = g_memdup (argv, sizeof (char *) * argc);
orig_argc = argc;
first_non_arg = 1;
for (i = 1; i < argc; i++)
{
if (argv[i][0] != '-')
break;
first_non_arg = i + 1;
if (strcmp (argv[i], "--run") == 0)
is_run = TRUE;
if (strcmp (argv[i], "--show-deps") == 0)
is_show_deps = TRUE;
if (strcmp (argv[i], "--show-manifest") == 0)
is_show_manifest = TRUE;
}
if (is_run)
{
context = g_option_context_new ("DIRECTORY MANIFEST COMMAND [args] - Run command in build sandbox");
g_option_context_add_main_entries (context, run_entries, NULL);
arg_context = flatpak_context_new ();
g_option_context_add_group (context, flatpak_context_get_options (arg_context));
/* We drop the post-command part from the args, these go with the command in the sandbox */
argc = MIN (first_non_arg + 3, argc);
}
else if (is_show_deps)
{
context = g_option_context_new ("MANIFEST - Show manifest dependencies");
g_option_context_add_main_entries (context, show_deps_entries, NULL);
}
else if (is_show_manifest)
{
context = g_option_context_new ("MANIFEST - Show manifest");
g_option_context_add_main_entries (context, show_manifest_entries, NULL);
}
else
{
context = g_option_context_new ("DIRECTORY MANIFEST - Build manifest");
g_option_context_add_main_entries (context, entries, NULL);
}
if (!g_option_context_parse (context, &argc, &argv, &error))
{
g_printerr ("Option parsing failed: %s\n", error->message);
return 1;
}
if (opt_version)
{
g_print ("%s\n", PACKAGE_STRING);
exit (EXIT_SUCCESS);
}
if (opt_verbose)
g_log_set_handler (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, message_handler, NULL);
argnr = 1;
if (!is_show_deps && !is_show_manifest)
{
if (argc == argnr)
return usage (context, "DIRECTORY must be specified");
app_dir_path = argv[argnr++];
}
if (argc == argnr)
return usage (context, "MANIFEST must be specified");
manifest_rel_path = argv[argnr++];
manifest_basename = g_path_get_basename (manifest_rel_path);
if (opt_collection_id != NULL &&
!ostree_validate_collection_id (opt_collection_id, &error))
{
g_printerr ("%s is not a valid collection ID: %s", opt_collection_id, error->message);
return 1;
}
if (opt_token_type < -1
#if G_MAXINT > 0x7fffffff
|| opt_token_type > G_MAXINT32
#endif
)
{
g_printerr ("--token-type value must be a 32 bit integer >= 0\n");
return 1;
}
if (app_dir_path)
app_dir = g_file_new_for_path (app_dir_path);
cwd = g_get_current_dir ();
cwd_dir = g_file_new_for_path (cwd);
build_context = builder_context_new (cwd_dir, app_dir, opt_state_dir);
builder_context_set_use_rofiles (build_context, !opt_disable_rofiles);
builder_context_set_run_tests (build_context, !opt_disable_tests);
builder_context_set_no_shallow_clone (build_context, opt_no_shallow_clone);
builder_context_set_keep_build_dirs (build_context, opt_keep_build_dirs);
builder_context_set_delete_build_dirs (build_context, opt_delete_build_dirs);
builder_context_set_sandboxed (build_context, opt_sandboxed);
builder_context_set_jobs (build_context, opt_jobs);
builder_context_set_rebuild_on_sdk_change (build_context, opt_rebuild_on_sdk_change);
builder_context_set_bundle_sources (build_context, opt_bundle_sources);
git_init_email ();
if (opt_sources_dirs)
{
g_autoptr(GPtrArray) sources_dirs = NULL;
sources_dirs = g_ptr_array_new_with_free_func (g_object_unref);
for (i = 0; opt_sources_dirs != NULL && opt_sources_dirs[i] != NULL; i++)
{
GFile *file = g_file_new_for_commandline_arg (opt_sources_dirs[i]);
g_ptr_array_add (sources_dirs, file);
}
builder_context_set_sources_dirs (build_context, sources_dirs);
}
if (opt_sources_urls)
{
g_autoptr(GPtrArray) sources_urls = NULL;
sources_urls = g_ptr_array_new_with_free_func ((GDestroyNotify)soup_uri_free);
for (i = 0; opt_sources_urls[i] != NULL; i++)
{
if (!g_str_has_suffix (opt_sources_urls[i], "/"))
{
g_autofree gchar *tmp = opt_sources_urls[i];
opt_sources_urls[i] = g_strdup_printf ("%s/", tmp);
}
SoupURI *uri = soup_uri_new (opt_sources_urls[i]);
if (uri == NULL)
{
g_printerr ("Invalid URL '%s'", opt_sources_urls[i]);
return 1;
}
g_ptr_array_add (sources_urls, uri);
}
builder_context_set_sources_urls (build_context, sources_urls);
}
if (opt_arch)
builder_context_set_arch (build_context, opt_arch);
if (opt_stop_at)
{
opt_build_only = TRUE;
builder_context_set_stop_at (build_context, opt_stop_at);
}
if (!builder_context_set_enable_ccache (build_context, opt_ccache, &error))
{
g_printerr ("Can't initialize ccache use: %s\n", error->message);
return 1;
}
if (opt_from_git)
{
g_autofree char *manifest_dirname = g_path_get_dirname (manifest_rel_path);
const char *git_branch = opt_from_git_branch ? opt_from_git_branch : "master";
g_autoptr(GFile) build_subdir = NULL;
build_subdir = builder_context_allocate_build_subdir (build_context, manifest_basename, &error);
if (build_subdir == NULL)
{
g_printerr ("Can't check out manifest repo: %s\n", error->message);
return 1;
}
cleanup_manifest_dir = g_object_ref (build_subdir);
int mirror_flags = FLATPAK_GIT_MIRROR_FLAGS_MIRROR_SUBMODULES;
if (opt_disable_updates)
{
mirror_flags |= FLATPAK_GIT_MIRROR_FLAGS_UPDATE;
}
if (!builder_git_mirror_repo (opt_from_git,
NULL,
mirror_flags,
git_branch, build_context, &error))
{
g_printerr ("Can't clone manifest repo: %s\n", error->message);
return 1;
}
if (!builder_git_checkout (opt_from_git,
git_branch,
build_subdir,
build_context,
&error))
{
g_printerr ("Can't check out manifest repo: %s\n", error->message);
return 1;
}
manifest_file = g_file_get_child (build_subdir, manifest_rel_path);
base_dir = g_file_resolve_relative_path (build_subdir, manifest_dirname);
}
else
{
manifest_file = g_file_new_for_path (manifest_rel_path);
base_dir = g_file_get_parent (manifest_file);
}
builder_context_set_base_dir (build_context, base_dir);
if (!g_file_get_contents (flatpak_file_get_path_cached (manifest_file), &manifest_contents, NULL, &error))
{
g_printerr ("Can't load '%s': %s\n", manifest_rel_path, error->message);
return 1;
}
if (stat (flatpak_file_get_path_cached (manifest_file), &statbuf) == 0)
builder_context_set_source_date_epoch (build_context, (gint64)statbuf.st_mtime);
manifest_sha256 = g_compute_checksum_for_string (G_CHECKSUM_SHA256, manifest_contents, -1);
if (opt_skip_if_unchanged)
{
old_manifest_sha256 = builder_context_get_checksum_for (build_context, manifest_basename);
if (old_manifest_sha256 != NULL && strcmp (manifest_sha256, old_manifest_sha256) == 0)
{
g_print ("No changes to manifest, skipping\n");
return 42;
}
}
/* Can't push this as user data to the demarshalling :/ */
builder_manifest_set_demarshal_base_dir (builder_context_get_base_dir (build_context));
manifest = (BuilderManifest *) builder_gobject_from_data (BUILDER_TYPE_MANIFEST, manifest_rel_path,
manifest_contents, &error);
builder_manifest_set_demarshal_base_dir (NULL);
if (manifest == NULL)
{
g_printerr ("Can't parse '%s': %s\n", manifest_rel_path, error->message);
return 1;
}
if (opt_remove_tags)
builder_manifest_remove_tags (manifest, (const char **)opt_remove_tags);
if (opt_add_tags)
builder_manifest_add_tags (manifest, (const char **)opt_add_tags);
if (opt_default_branch)
builder_context_set_default_branch (build_context, opt_default_branch);
if (opt_collection_id)
builder_manifest_set_default_collection_id (manifest, opt_collection_id);
if (opt_token_type >= 0)
builder_manifest_set_default_token_type (manifest, (gint32)opt_token_type);
if (is_run && argc == 3)
return usage (context, "Program to run must be specified");
if (opt_show_deps && !is_show_deps)
return usage (context, "Can't use --show-deps after a non-option");
if (opt_run && !is_run)
return usage (context, "Can't use --run after a non-option");
if (is_show_deps)
{
if (!builder_manifest_show_deps (manifest, build_context, &error))
{
g_printerr ("Error calculating deps: %s\n", error->message);
return 1;
}
return 0;
}
if (is_show_manifest)
{
g_autofree char *json = builder_manifest_serialize (manifest);
g_print ("%s\n", json);
return 0;
}
if (opt_install_deps_from != NULL)
{
if (!builder_manifest_install_deps (manifest, build_context, opt_install_deps_from, opt_user, opt_installation,
opt_yes, &error))
{
g_printerr ("Error installing deps: %s\n", error->message);
return 1;
}
if (opt_install_deps_only)
return 0;
}
app_dir_is_empty = !g_file_query_exists (app_dir, NULL) ||
directory_is_empty (app_dir_path);
if (is_run)
{
g_assert (opt_run);
if (app_dir_is_empty)
{
g_printerr ("App dir '%s' is empty or doesn't exist.\n", app_dir_path);
return 1;
}
if (!builder_manifest_run (manifest, build_context, arg_context,
orig_argv + first_non_arg + 2,
orig_argc - first_non_arg - 2,
opt_log_session_bus, opt_log_system_bus,
&error))
{
g_printerr ("Error running %s: %s\n", argv[3], error->message);
return 1;
}
return 0;
}
g_assert (!opt_run);
g_assert (!opt_show_deps);
if (opt_export_only || opt_finish_only || opt_build_shell)
{
if (app_dir_is_empty)
{
g_printerr ("App dir '%s' is empty or doesn't exist.\n", app_dir_path);
return 1;
}
}
else
{
if (!app_dir_is_empty)
{
if (opt_force_clean)
{
g_print ("Emptying app dir '%s'\n", app_dir_path);
if (!flatpak_rm_rf (app_dir, NULL, &error))
{
g_printerr ("Couldn't empty app dir '%s': %s",
app_dir_path, error->message);
return 1;
}
}
else
{
g_printerr ("App dir '%s' is not empty. Please delete "
"the existing contents or use --force-clean.\n", app_dir_path);
return 1;
}
}
}
/* Verify that cache and build dir is on same filesystem */
if (!opt_download_only)
{
g_autofree char *state_path = g_file_get_path (builder_context_get_state_dir (build_context));
g_autoptr(GFile) app_parent = g_file_get_parent (builder_context_get_app_dir (build_context));
g_autofree char *app_parent_path = g_file_get_path (app_parent);
struct stat buf1, buf2;
if (stat (app_parent_path, &buf1) == 0 && stat (state_path, &buf2) == 0 &&
buf1.st_dev != buf2.st_dev)
{
g_printerr ("The state dir (%s) is not on the same filesystem as the target dir (%s)\n",
state_path, app_parent_path);
return 1;
}
}
if (!builder_context_set_checksum_for (build_context, manifest_basename, manifest_sha256, &error))
{
g_printerr ("Failed to set checksum for %s: %s\n", manifest_basename, error->message);
return 1;
}
if (!builder_manifest_start (manifest, opt_download_only, opt_allow_missing_runtimes, build_context, &error))
{
g_printerr ("Failed to init: %s\n", error->message);
return 1;
}
if (!opt_finish_only &&
!opt_export_only &&
!opt_disable_download &&
!builder_manifest_download (manifest, !opt_disable_updates, opt_build_shell, build_context, &error))
{
g_printerr ("Failed to download sources: %s\n", error->message);
return 1;
}
if (opt_download_only)
return 0;
if (opt_build_shell)
{
if (!builder_manifest_build_shell (manifest, build_context, opt_build_shell, &error))
{
g_printerr ("Failed to setup module: %s\n", error->message);
return 1;
}
return 0;
}
if (opt_state_dir)
{
/* If the state dir can be shared we need to use a global identifier for the key */
g_autofree char *manifest_path = g_file_get_path (manifest_file);
cache_branch = g_strconcat (builder_context_get_arch (build_context), "-", manifest_path + 1, NULL);
}
else
cache_branch = g_strconcat (builder_context_get_arch (build_context), "-", manifest_basename, NULL);
escaped_cache_branch = g_uri_escape_string (cache_branch, "", TRUE);
for (p = escaped_cache_branch; *p; p++)
{
if (*p == '%')
*p = '_';
}
cache = builder_cache_new (build_context, app_dir, escaped_cache_branch);
if (!builder_cache_open (cache, &error))
{
g_printerr ("Error opening cache: %s\n", error->message);
return 1;
}
if (opt_disable_cache) /* This disables *lookups*, but we still build the cache */
builder_cache_disable_lookups (cache);
builder_manifest_checksum (manifest, cache, build_context);
if (!opt_finish_only && !opt_export_only)
{
if (!builder_cache_lookup (cache, "init"))
{
g_autofree char *body =
g_strdup_printf ("Initialized %s\n",
builder_manifest_get_id (manifest));
if (!builder_manifest_init_app_dir (manifest, cache, build_context, &error))
{
g_printerr ("Error: %s\n", error->message);
return 1;
}
if (!builder_cache_commit (cache, body, &error))
{
g_printerr ("Error: %s\n", error->message);
return 1;
}
}
if (!builder_manifest_build (manifest, cache, build_context, &error))
{
g_printerr ("Error: %s\n", error->message);
return 1;
}
}
if (!opt_build_only && !opt_export_only)
{
if (!builder_manifest_cleanup (manifest, cache, build_context, &error))
{
g_printerr ("Error: %s\n", error->message);
return 1;
}
if (!builder_manifest_finish (manifest, cache, build_context, &error))
{
g_printerr ("Error: %s\n", error->message);
return 1;
}
if (!builder_manifest_create_platform (manifest, cache, build_context, &error))
{
g_printerr ("Error: %s\n", error->message);
return 1;
}
if (builder_context_get_bundle_sources (build_context) &&
!builder_manifest_bundle_sources (manifest, manifest_contents, cache, build_context, &error))
{
g_printerr ("Error: %s\n", error->message);
return 1;
}
}
if (!opt_require_changes && !opt_export_only)
builder_cache_ensure_checkout (cache);
if (opt_mirror_screenshots_url && !opt_export_only)
{
g_autofree char *screenshot_subdir = g_strdup_printf ("%s-%s", builder_manifest_get_id (manifest),
builder_manifest_get_branch (manifest, build_context));
g_autofree char *url = g_build_filename (opt_mirror_screenshots_url, screenshot_subdir, NULL);
g_autofree char *xml_relpath = g_strdup_printf ("files/share/app-info/xmls/%s.xml.gz", builder_manifest_get_id (manifest));
g_autoptr(GFile) xml = g_file_resolve_relative_path (app_dir, xml_relpath);
g_autoptr(GFile) cache = flatpak_build_file (builder_context_get_state_dir (build_context), "screenshots-cache", NULL);
g_autoptr(GFile) screenshots = flatpak_build_file (app_dir, "screenshots", NULL);
g_autoptr(GFile) screenshots_sub = flatpak_build_file (screenshots, screenshot_subdir, NULL);
g_autofree char *fs_app_dir = g_strdup_printf ("--filesystem=%s", flatpak_file_get_path_cached (app_dir));
g_autofree char *fs_cache = g_strdup_printf ("--filesystem=%s", flatpak_file_get_path_cached (cache));
const char *argv[] = {
"flatpak",
"build",
"--die-with-parent",
"--nofilesystem=host",
fs_app_dir,
fs_cache,
"--share=network",
flatpak_file_get_path_cached (app_dir),
"appstream-util",
"mirror-screenshots",
flatpak_file_get_path_cached (xml),
url,
flatpak_file_get_path_cached (cache),
flatpak_file_get_path_cached (screenshots_sub),
NULL
};
g_print ("Mirroring screenshots from appdata\n");
builder_set_term_title (_("Mirroring screenshots"));
if (!flatpak_mkdir_p (screenshots, NULL, &error))
{
g_printerr ("Error creating screenshot dir: %s\n", error->message);
return 1;
}
if (g_file_query_exists (xml, NULL))
{
if (!flatpak_mkdir_p (cache, NULL, &error))
{
g_printerr ("Error creating screenshot cache dir: %s\n", error->message);
return 1;
}
if (!flatpak_break_hardlink (xml, &error))
{
g_printerr ("Error mirroring screenshots: %s\n", error->message);
return 1;
}
if (!builder_maybe_host_spawnv (NULL,
NULL,
0,
&error,
argv))
{
g_printerr ("Error mirroring screenshots: %s\n", error->message);
return 1;
}
}
g_print ("Saved screenshots in %s\n", flatpak_file_get_path_cached (screenshots));
}
if (!opt_build_only &&
(opt_repo || opt_install) &&
(opt_export_only || builder_cache_has_checkout (cache)))
{
g_autoptr(GFile) debuginfo_metadata = NULL;
g_autoptr(GFile) sourcesinfo_metadata = NULL;
g_auto(GStrv) exclude_dirs = builder_manifest_get_exclude_dirs (manifest);
GList *l;
if (opt_repo)
export_repo = g_file_new_for_path (opt_repo);
else if (opt_install)
export_repo = g_object_ref (builder_context_get_cache_dir (build_context));
g_print ("Exporting %s to repo\n", builder_manifest_get_id (manifest));
builder_set_term_title (_("Exporting to repository"));
if (!do_export (build_context, &error,
FALSE,
flatpak_file_get_path_cached (export_repo),
app_dir_path, exclude_dirs, builder_manifest_get_branch (manifest, build_context),
builder_manifest_get_collection_id (manifest),
builder_manifest_get_token_type (manifest),
"--exclude=/lib/debug/*",
"--include=/lib/debug/app",
builder_context_get_separate_locales (build_context) ? "--exclude=/share/runtime/locale/*/*" : skip_arg,
NULL))
{
g_printerr ("Export failed: %s\n", error->message);
return 1;
}
/* Export regular locale extensions */
dir_enum = g_file_enumerate_children (app_dir, "standard::name,standard::type",
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
NULL, NULL);
while (dir_enum != NULL &&
(next = g_file_enumerator_next_file (dir_enum, NULL, NULL)))
{
g_autoptr(GFileInfo) child_info = next;
const char *name = g_file_info_get_name (child_info);
g_autofree char *metadata_arg = NULL;
g_autofree char *files_arg = NULL;
g_autofree char *locale_id = builder_manifest_get_locale_id (manifest);
if (strcmp (name, "metadata.locale") == 0)
g_print ("Exporting %s to repo\n", locale_id);
else
continue;
metadata_arg = g_strdup_printf ("--metadata=%s", name);
files_arg = g_strconcat (builder_context_get_build_runtime (build_context) ? "--files=usr" : "--files=files",
"/share/runtime/locale/", NULL);
if (!do_export (build_context, &error, TRUE,
flatpak_file_get_path_cached (export_repo),
app_dir_path, NULL, builder_manifest_get_branch (manifest, build_context),
builder_manifest_get_collection_id (manifest),
builder_manifest_get_token_type (manifest),
metadata_arg,
files_arg,
NULL))
{
g_printerr ("Export failed: %s\n", error->message);
return 1;
}
}
/* Export debug extensions */
debuginfo_metadata = g_file_get_child (app_dir, "metadata.debuginfo");
if (g_file_query_exists (debuginfo_metadata, NULL))
{
g_autofree char *debug_id = builder_manifest_get_debug_id (manifest);
g_print ("Exporting %s to repo\n", debug_id);
if (!do_export (build_context, &error, TRUE,
flatpak_file_get_path_cached (export_repo),
app_dir_path, NULL, builder_manifest_get_branch (manifest, build_context),
builder_manifest_get_collection_id (manifest),
builder_manifest_get_token_type (manifest),
"--metadata=metadata.debuginfo",
builder_context_get_build_runtime (build_context) ? "--files=usr/lib/debug" : "--files=files/lib/debug",
NULL))
{
g_printerr ("Export failed: %s\n", error->message);
return 1;
}
}
for (l = builder_manifest_get_add_extensions (manifest); l != NULL; l = l->next)
{
BuilderExtension *e = l->data;
const char *extension_id = NULL;
g_autofree char *metadata_arg = NULL;
g_autofree char *files_arg = NULL;
if (!builder_extension_is_bundled (e))
continue;
extension_id = builder_extension_get_name (e);
g_print ("Exporting %s to repo\n", extension_id);
metadata_arg = g_strdup_printf ("--metadata=metadata.%s", extension_id);
files_arg = g_strdup_printf ("--files=%s/%s",
builder_context_get_build_runtime (build_context) ? "usr" : "files",
builder_extension_get_directory (e));
if (!do_export (build_context, &error, TRUE,
flatpak_file_get_path_cached (export_repo),
app_dir_path, NULL, builder_manifest_get_branch (manifest, build_context),
builder_manifest_get_collection_id (manifest),
builder_manifest_get_token_type (manifest),
metadata_arg, files_arg,
NULL))
{
g_printerr ("Export failed: %s\n", error->message);
return 1;
}
}
/* Export sources extensions */
sourcesinfo_metadata = g_file_get_child (app_dir, "metadata.sources");
if (g_file_query_exists (sourcesinfo_metadata, NULL))
{
g_autofree char *sources_id = builder_manifest_get_sources_id (manifest);
g_print ("Exporting %s to repo\n", sources_id);
if (!do_export (build_context, &error, TRUE,
flatpak_file_get_path_cached (export_repo),
app_dir_path, NULL, builder_manifest_get_branch (manifest, build_context),
builder_manifest_get_collection_id (manifest),
builder_manifest_get_token_type (manifest),
"--metadata=metadata.sources",
"--files=sources",
NULL))
{
g_printerr ("Export failed: %s\n", error->message);
return 1;
}
}
/* Export platform */
platform_id = builder_manifest_get_id_platform (manifest);
if (builder_context_get_build_runtime (build_context) &&
platform_id != NULL)
{
g_print ("Exporting %s to repo\n", platform_id);
if (!do_export (build_context, &error, TRUE,
flatpak_file_get_path_cached (export_repo),
app_dir_path, NULL, builder_manifest_get_branch (manifest, build_context),
builder_manifest_get_collection_id (manifest),
builder_manifest_get_token_type (manifest),
"--metadata=metadata.platform",
"--files=platform",
builder_context_get_separate_locales (build_context) ? "--exclude=/share/runtime/locale/*/*" : skip_arg,
NULL))
{
g_printerr ("Export failed: %s\n", error->message);
return 1;
}
}
/* Export platform locales */
dir_enum2 = g_file_enumerate_children (app_dir, "standard::name,standard::type",
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
NULL, NULL);
while (dir_enum2 != NULL &&
(next = g_file_enumerator_next_file (dir_enum2, NULL, NULL)))
{
g_autoptr(GFileInfo) child_info = next;
const char *name = g_file_info_get_name (child_info);
g_autofree char *metadata_arg = NULL;
g_autofree char *files_arg = NULL;
g_autofree char *locale_id = builder_manifest_get_locale_id_platform (manifest);
if (strcmp (name, "metadata.platform.locale") == 0)
g_print ("Exporting %s to repo\n", locale_id);
else
continue;
metadata_arg = g_strdup_printf ("--metadata=%s", name);
files_arg = g_strconcat ("--files=platform/share/runtime/locale/", NULL);
if (!do_export (build_context, &error, TRUE,
flatpak_file_get_path_cached (export_repo),
app_dir_path, NULL, builder_manifest_get_branch (manifest, build_context),
builder_manifest_get_collection_id (manifest),
builder_manifest_get_token_type (manifest),
metadata_arg,
files_arg,
NULL))
{
g_printerr ("Export failed: %s\n", error->message);
return 1;
}
}
}
if (opt_install)
{
/* We may end here with a NULL export repo if --require-changes was
passed and there were no changes, do nothing in that case */
if (export_repo == NULL)
g_printerr ("NOTE: No export due to --require-changes, ignoring --install\n");
else if (!do_install (build_context, flatpak_file_get_path_cached (export_repo),
builder_manifest_get_id (manifest),
builder_manifest_get_branch (manifest, build_context),
&error))
{
g_printerr ("Install failed: %s\n", error->message);
return 1;
}
}
if (!opt_finish_only && !opt_export_only)
prune_unused_stages = TRUE;
if (!builder_gc (cache, prune_unused_stages, &error))
{
g_warning ("Failed to GC build cache: %s", error->message);
g_clear_error (&error);
}
return 0;
}