gboolean
flatpak_builtin_build_init (int argc, char **argv, GCancellable *cancellable, GError **error)
{
  g_autoptr(GOptionContext) context = NULL;
  g_autoptr(GFile) var_deploy_files = NULL;
  g_autoptr(GFile) base = NULL;
  g_autoptr(GFile) files_dir = NULL;
  g_autoptr(GFile) usr_dir = NULL;
  g_autoptr(GFile) var_dir = NULL;
  g_autoptr(GFile) var_tmp_dir = NULL;
  g_autoptr(GFile) var_run_dir = NULL;
  g_autoptr(GFile) metadata_file = NULL;
  g_autoptr(GString) metadata_contents = NULL;
  g_autoptr(GError) my_error = NULL;
  const char *app_id;
  const char *directory;
  const char *sdk;
  const char *runtime;
  const char *branch = "master";
  g_autofree char *runtime_ref = NULL;
  g_autofree char *var_ref = NULL;
  g_autofree char *sdk_ref = NULL;
  int i;

  context = g_option_context_new (_("DIRECTORY APPNAME SDK RUNTIME [BRANCH] - Initialize a directory for building"));
  g_option_context_set_translation_domain (context, GETTEXT_PACKAGE);

  if (!flatpak_option_context_parse (context, options, &argc, &argv, FLATPAK_BUILTIN_FLAG_NO_DIR, NULL, cancellable, error))
    return FALSE;

  if (argc < 5)
    return usage_error (context, _("RUNTIME must be specified"), error);

  if (argc > 6)
    return usage_error (context, _("Too many arguments"), error);

  directory = argv[1];
  app_id = argv[2];
  sdk = argv[3];
  runtime = argv[4];
  if (argc >= 6)
    branch = argv[5];

  if (!flatpak_is_valid_name (app_id, &my_error))
    return flatpak_fail (error, _("'%s' is not a valid application name: %s"), app_id, my_error->message);

  if (!flatpak_is_valid_name (runtime, &my_error))
    return flatpak_fail (error, _("'%s' is not a valid runtime name: %s"), runtime, my_error->message);

  if (!flatpak_is_valid_name (sdk, &my_error))
    return flatpak_fail (error, _("'%s' is not a valid sdk name: %s"), sdk, my_error->message);

  if (!flatpak_is_valid_branch (branch, &my_error))
    return flatpak_fail (error, _("'%s' is not a valid branch name: %s"), branch, my_error->message);

  runtime_ref = flatpak_build_untyped_ref (runtime, branch, opt_arch);
  sdk_ref = flatpak_build_untyped_ref (sdk, branch, opt_arch);

  base = g_file_new_for_commandline_arg (directory);

  if (!flatpak_mkdir_p (base, cancellable, error))
    return FALSE;

  files_dir = g_file_get_child (base, "files");
  if (opt_sdk_dir)
    usr_dir = g_file_get_child (base, opt_sdk_dir);
  else
    usr_dir = g_file_get_child (base, "usr");
  var_dir = g_file_get_child (base, "var");
  var_tmp_dir = g_file_get_child (var_dir, "tmp");
  var_run_dir = g_file_get_child (var_dir, "run");
  metadata_file = g_file_get_child (base, "metadata");

  if (!opt_update &&
      g_file_query_exists (files_dir, cancellable))
    return flatpak_fail (error, _("Build directory %s already initialized"), directory);

  if (opt_writable_sdk)
    {
      g_autofree char *full_sdk_ref = g_strconcat ("runtime/", sdk_ref, NULL);
      g_autoptr(GFile) sdk_deploy_files = NULL;
      g_autoptr(FlatpakDeploy) sdk_deploy = NULL;

      sdk_deploy = flatpak_find_deploy_for_ref (full_sdk_ref, cancellable, error);
      if (sdk_deploy == NULL)
        return FALSE;

      if (!flatpak_rm_rf (usr_dir, NULL, &my_error))
        {
          if (!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);
        }

      sdk_deploy_files = flatpak_deploy_get_files (sdk_deploy);
      if (!flatpak_cp_a (sdk_deploy_files, usr_dir, FLATPAK_CP_FLAGS_NO_CHOWN, cancellable, error))
        return FALSE;

      if (opt_sdk_extensions &&
          !copy_extensions (sdk_deploy, branch, opt_sdk_extensions, usr_dir, cancellable, error))
        return FALSE;
    }

  if (opt_var)
    {
      var_ref = flatpak_build_runtime_ref (opt_var, branch, opt_arch);

      var_deploy_files = flatpak_find_files_dir_for_ref (var_ref, cancellable, error);
      if (var_deploy_files == NULL)
        return FALSE;
    }

  if (opt_update)
    return TRUE;

  if (!g_file_make_directory (files_dir, cancellable, error))
    return FALSE;

  if (opt_base)
    {
      const char *base_branch;
      g_autofree char *base_ref = NULL;
      g_autoptr(GFile) base_deploy_files = NULL;
      g_autoptr(FlatpakDeploy) base_deploy = NULL;

      base_branch = opt_base_version ? opt_base_version : "master";
      base_ref = flatpak_build_app_ref (opt_base, base_branch, opt_arch);
      base_deploy = flatpak_find_deploy_for_ref (base_ref, cancellable, error);
      if (base_deploy == NULL)
        return FALSE;

      base_deploy_files = flatpak_deploy_get_files (base_deploy);
      if (!flatpak_cp_a (base_deploy_files, files_dir,
                         FLATPAK_CP_FLAGS_MERGE | FLATPAK_CP_FLAGS_NO_CHOWN,
                         cancellable, error))
        return FALSE;


      if (opt_base_extensions &&
          !copy_extensions (base_deploy, base_branch, opt_base_extensions, files_dir, cancellable, error))
        return FALSE;
    }

  if (var_deploy_files)
    {
      if (!flatpak_cp_a (var_deploy_files, var_dir, FLATPAK_CP_FLAGS_NONE, cancellable, error))
        return FALSE;
    }
  else
    {
      if (!g_file_make_directory (var_dir, cancellable, error))
        return FALSE;
    }

  if (!flatpak_mkdir_p (var_tmp_dir, cancellable, error))
    return FALSE;

  if (!g_file_query_exists (var_run_dir, cancellable) &&
      !g_file_make_symbolic_link (var_run_dir, "/run", cancellable, error))
    return FALSE;


  metadata_contents = g_string_new ("[Application]\n");
  g_string_append_printf (metadata_contents,
                          "name=%s\n"
                          "runtime=%s\n"
                          "sdk=%s\n",
                          app_id, runtime_ref, sdk_ref);
  if (opt_tags != NULL)
    {
      g_string_append (metadata_contents, "tags=");
      for (i = 0; opt_tags[i] != NULL; i++)
        {
          g_string_append (metadata_contents, opt_tags[i]);
          g_string_append_c (metadata_contents, ';');
        }
      g_string_append_c (metadata_contents, '\n');
    }

  if (!g_file_replace_contents (metadata_file,
                                metadata_contents->str, metadata_contents->len, NULL, FALSE,
                                G_FILE_CREATE_REPLACE_DESTINATION,
                                NULL, cancellable, error))
    return FALSE;

  return TRUE;
}
Esempio n. 2
0
gboolean
flatpak_builtin_install (int argc, char **argv, GCancellable *cancellable, GError **error)
{
  g_autoptr(GOptionContext) context = NULL;
  g_autoptr(GPtrArray) dirs = NULL;
  g_autoptr(FlatpakDir) dir = NULL;
  g_autofree char *remote = NULL;
  g_autofree char *remote_url = NULL;
  char **prefs = NULL;
  int i, n_prefs;
  g_autofree char *target_branch = NULL;
  g_autofree char *default_branch = NULL;
  FlatpakKinds kinds;
  g_autoptr(FlatpakTransaction) transaction = NULL;
  g_autoptr(FlatpakDir) dir_with_remote = NULL;
  gboolean auto_remote = FALSE;

  context = g_option_context_new (_("[LOCATION/REMOTE] [REF…] - Install applications or runtimes"));
  g_option_context_set_translation_domain (context, GETTEXT_PACKAGE);

  if (!flatpak_option_context_parse (context, options, &argc, &argv,
                                     FLATPAK_BUILTIN_FLAG_ALL_DIRS | FLATPAK_BUILTIN_FLAG_OPTIONAL_REPO,
                                     &dirs, cancellable, error))
    return FALSE;

  /* Start with the default or specified dir, this is fine for opt_bundle or opt_from */
  dir = g_object_ref (g_ptr_array_index (dirs, 0));

  if (!opt_bundle && !opt_from && argc >= 2)
    {
      if (flatpak_file_arg_has_suffix (argv[1], ".flatpakref"))
        opt_from = TRUE;
      if (flatpak_file_arg_has_suffix (argv[1], ".flatpak"))
        opt_bundle = TRUE;
    }

  if (opt_bundle)
    return install_bundle (dir, context, argc, argv, cancellable, error);

  if (opt_from)
    return install_from (dir, context, argc, argv, cancellable, error);

  if (argc < 2)
    return usage_error (context, _("At least one REF must be specified"), error);

  if (argc == 2)
    auto_remote = TRUE;

  kinds = flatpak_kinds_from_bools (opt_app, opt_runtime);

  if (!opt_noninteractive)
    g_print (_("Looking for matches…\n"));

  if (!auto_remote &&
      (g_path_is_absolute (argv[1]) ||
       g_str_has_prefix (argv[1], "./")))
    {
      g_autoptr(GFile) remote_file = g_file_new_for_commandline_arg (argv[1]);
      remote_url = g_file_get_uri (remote_file);
      remote = g_strdup (remote_url);
    }
  else
    {
      g_autoptr(GError) local_error = NULL;

      /* If the remote was used, and no single dir was specified, find which
       * one based on the remote. If the remote isn't found assume it's a ref
       * and we should auto-detect the remote. */
      if (!auto_remote &&
          !flatpak_resolve_duplicate_remotes (dirs, argv[1], &dir_with_remote, cancellable, &local_error))
        {
          if (g_error_matches (local_error, FLATPAK_ERROR, FLATPAK_ERROR_REMOTE_NOT_FOUND))
            {
              auto_remote = TRUE;
            }
          else
            {
              g_propagate_error (error, g_steal_pointer (&local_error));
              return FALSE;
            }
        }

      if (!auto_remote)
        {
          remote = g_strdup (argv[1]);
          g_clear_object (&dir);
          dir = g_object_ref (dir_with_remote);
        }
      else
        {
          g_autoptr(GPtrArray) remote_dir_pairs = NULL;
          RemoteDirPair *chosen_pair = NULL;

          remote_dir_pairs = g_ptr_array_new_with_free_func ((GDestroyNotify) remote_dir_pair_free);

          /* Search all remotes for a matching ref. This is imperfect
           * because it only takes the first specified ref into account and
           * doesn't distinguish between an exact match and a fuzzy match, but
           * that's okay because the user will be asked to confirm the remote
           */
          for (i = 0; i < dirs->len; i++)
            {
              FlatpakDir *this_dir = g_ptr_array_index (dirs, i);
              g_auto(GStrv) remotes = NULL;
              guint j = 0;

              remotes = flatpak_dir_list_remotes (this_dir, cancellable, error);
              if (remotes == NULL)
                return FALSE;

              for (j = 0; remotes[j] != NULL; j++)
                {
                  const char *this_remote = remotes[j];
                  g_autofree char *this_default_branch = NULL;
                  g_autofree char *id = NULL;
                  g_autofree char *arch = NULL;
                  g_autofree char *branch = NULL;
                  FlatpakKinds matched_kinds;
                  g_auto(GStrv) refs = NULL;
                  g_autoptr(GError) local_error = NULL;

                  if (flatpak_dir_get_remote_disabled (this_dir, this_remote) ||
                      flatpak_dir_get_remote_noenumerate (this_dir, this_remote))
                    continue;

                  this_default_branch = flatpak_dir_get_remote_default_branch (this_dir, this_remote);

                  flatpak_split_partial_ref_arg_novalidate (argv[1], kinds, opt_arch, target_branch,
                                                            &matched_kinds, &id, &arch, &branch);

                  if (opt_no_pull)
                    refs = flatpak_dir_find_local_refs (this_dir, this_remote, id, branch, this_default_branch, arch,
                                                        flatpak_get_default_arch (),
                                                        matched_kinds, FIND_MATCHING_REFS_FLAGS_FUZZY,
                                                        cancellable, &local_error);
                  else
                    refs = flatpak_dir_find_remote_refs (this_dir, this_remote, id, branch, this_default_branch, arch,
                                                         flatpak_get_default_arch (),
                                                         matched_kinds, FIND_MATCHING_REFS_FLAGS_FUZZY,
                                                         cancellable, &local_error);

                  if (refs == NULL)
                    {
                      g_warning ("An error was encountered searching remote ‘%s’ for ‘%s’: %s", this_remote, argv[1], local_error->message);
                      continue;
                    }

                  if (g_strv_length (refs) == 0)
                    continue;
                  else
                    {
                      RemoteDirPair *pair = remote_dir_pair_new (this_remote, this_dir);
                      g_ptr_array_add (remote_dir_pairs, pair);
                    }
                }
            }

            if (remote_dir_pairs->len == 0)
              return flatpak_fail (error, _("No remote refs found similar to ‘%s’"), argv[1]);

            if (!flatpak_resolve_matching_remotes (opt_yes, remote_dir_pairs, argv[1], &chosen_pair, error))
              return FALSE;

            remote = g_strdup (chosen_pair->remote_name);
            g_clear_object (&dir);
            dir = g_object_ref (chosen_pair->dir);
        }
    }

  if (auto_remote)
    {
      prefs = &argv[1];
      n_prefs = argc - 1;
    }
  else
    {
      prefs = &argv[2];
      n_prefs = argc - 2;
    }

  /* Backwards compat for old "REMOTE NAME [BRANCH]" argument version */
  if (argc == 4 && flatpak_is_valid_name (argv[2], NULL) && looks_like_branch (argv[3]))
    {
      target_branch = g_strdup (argv[3]);
      n_prefs = 1;
    }

  default_branch = flatpak_dir_get_remote_default_branch (dir, remote);

  if (opt_noninteractive)
    transaction = flatpak_quiet_transaction_new (dir, error);
  else
    transaction = flatpak_cli_transaction_new (dir, opt_yes, TRUE, error);
  if (transaction == NULL)
    return FALSE;

  flatpak_transaction_set_no_pull (transaction, opt_no_pull);
  flatpak_transaction_set_no_deploy (transaction, opt_no_deploy);
  flatpak_transaction_set_disable_static_deltas (transaction, opt_no_static_deltas);
  flatpak_transaction_set_disable_dependencies (transaction, opt_no_deps);
  flatpak_transaction_set_disable_related (transaction, opt_no_related);
  flatpak_transaction_set_reinstall (transaction, opt_reinstall);

  for (i = 0; i < n_prefs; i++)
    {
      const char *pref = prefs[i];
      FlatpakKinds matched_kinds;
      g_autofree char *id = NULL;
      g_autofree char *arch = NULL;
      g_autofree char *branch = NULL;
      g_autofree char *ref = NULL;
      g_auto(GStrv) refs = NULL;
      guint refs_len;
      g_autoptr(GError) local_error = NULL;

      flatpak_split_partial_ref_arg_novalidate (pref, kinds, opt_arch, target_branch,
                                                &matched_kinds, &id, &arch, &branch);

      /* We used _novalidate so that the id can be partial, but we can still validate the branch */
      if (branch != NULL && !flatpak_is_valid_branch (branch, &local_error))
        return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_REF, _("Invalid branch %s: %s"), branch, local_error->message);

      if (opt_no_pull)
        refs = flatpak_dir_find_local_refs (dir, remote, id, branch, default_branch, arch,
                                           flatpak_get_default_arch (),
                                           matched_kinds, FIND_MATCHING_REFS_FLAGS_FUZZY,
                                           cancellable, error);
      else
        refs = flatpak_dir_find_remote_refs (dir, remote, id, branch, default_branch, arch,
                                             flatpak_get_default_arch (),
                                             matched_kinds, FIND_MATCHING_REFS_FLAGS_FUZZY,
                                             cancellable, error);
      if (refs == NULL)
        return FALSE;

      refs_len = g_strv_length (refs);
      if (refs_len == 0)
        {
          if (opt_no_pull)
            g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("Nothing matches %s in local repository for remote %s"), id, remote);
          else
            g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("Nothing matches %s in remote %s"), id, remote);
          return FALSE;
        }

      if (!flatpak_resolve_matching_refs (remote, dir, opt_yes, refs, id, &ref, error))
        return FALSE;

      if (!flatpak_transaction_add_install (transaction, remote, ref, (const char **)opt_subpaths, error))
        {
          if (!g_error_matches (*error, FLATPAK_ERROR, FLATPAK_ERROR_ALREADY_INSTALLED))
            return FALSE;

          g_printerr (_("Skipping: %s\n"), (*error)->message);
          g_clear_error (error);
        }
    }

  if (!flatpak_transaction_run (transaction, cancellable, error))
    {
      if (g_error_matches (*error, FLATPAK_ERROR, FLATPAK_ERROR_ABORTED))
        {
          g_clear_error (error);
          return TRUE;
        }

      return FALSE;
    }

  return TRUE;
}
gboolean
flatpak_builtin_build_sign (int argc, char **argv, GCancellable *cancellable, GError **error)
{
  g_autoptr(GOptionContext) context = NULL;
  g_autoptr(GFile) repofile = NULL;
  g_autoptr(OstreeRepo) repo = NULL;
  const char *location;
  const char *branch;
  const char *id;
  g_autofree char *commit_checksum = NULL;
  g_autofree char *ref = NULL;
  char **iter;

  context = g_option_context_new ("LOCATION ID [BRANCH] - Create a repository from a build directory");

  if (!flatpak_option_context_parse (context, options, &argc, &argv, FLATPAK_BUILTIN_FLAG_NO_DIR, NULL, cancellable, error))
    return FALSE;

  if (argc < 3)
    {
      usage_error (context, "LOCATION and DIRECTORY must be specified", error);
      return FALSE;
    }

  location = argv[1];
  id = argv[2];

  if (argc >= 4)
    branch = argv[3];
  else
    branch = "master";

  if (!flatpak_is_valid_name (id))
    return flatpak_fail (error, "'%s' is not a valid name", id);

  if (!flatpak_is_valid_branch (branch))
    return flatpak_fail (error, "'%s' is not a valid branch name", branch);

  if (opt_gpg_key_ids == NULL)
    return flatpak_fail (error, "No gpg key ids specified");

  if (opt_runtime)
    ref = flatpak_build_runtime_ref (id, branch, opt_arch);
  else
    ref = flatpak_build_app_ref (id, branch, opt_arch);

  repofile = g_file_new_for_commandline_arg (location);
  repo = ostree_repo_new (repofile);

  if (!ostree_repo_open (repo, cancellable, error))
    return FALSE;

  if (!ostree_repo_resolve_rev (repo, ref, TRUE, &commit_checksum, error))
    return FALSE;

  for (iter = opt_gpg_key_ids; iter && *iter; iter++)
    {
      const char *keyid = *iter;

      if (!ostree_repo_sign_commit (repo,
                                    commit_checksum,
                                    keyid,
                                    opt_gpg_homedir,
                                    cancellable,
                                    error))
        return FALSE;
    }

  return TRUE;
}
gboolean
flatpak_builtin_build_init (int argc, char **argv, GCancellable *cancellable, GError **error)
{
  g_autoptr(GOptionContext) context = NULL;
  g_autoptr(GFile) var_deploy_base = NULL;
  g_autoptr(GFile) var_deploy_files = NULL;
  g_autoptr(GFile) base = NULL;
  g_autoptr(GFile) files_dir = NULL;
  g_autoptr(GFile) usr_dir = NULL;
  g_autoptr(GFile) var_dir = NULL;
  g_autoptr(GFile) var_tmp_dir = NULL;
  g_autoptr(GFile) var_run_dir = NULL;
  g_autoptr(GFile) metadata_file = NULL;
  g_autoptr(GString) metadata_contents = NULL;
  const char *app_id;
  const char *directory;
  const char *sdk;
  const char *runtime;
  const char *branch = "master";
  g_autofree char *runtime_ref = NULL;
  g_autofree char *var_ref = NULL;
  g_autofree char *sdk_ref = NULL;
  int i;

  context = g_option_context_new ("DIRECTORY APPNAME SDK RUNTIME [BRANCH] - Initialize a directory for building");

  if (!flatpak_option_context_parse (context, options, &argc, &argv, FLATPAK_BUILTIN_FLAG_NO_DIR, NULL, cancellable, error))
    return FALSE;

  if (argc < 5)
    return usage_error (context, "RUNTIME must be specified", error);

  directory = argv[1];
  app_id = argv[2];
  sdk = argv[3];
  runtime = argv[4];
  if (argc >= 6)
    branch = argv[5];

  if (!flatpak_is_valid_name (app_id))
    return flatpak_fail (error, "'%s' is not a valid application name", app_id);

  if (!flatpak_is_valid_name (runtime))
    return flatpak_fail (error, "'%s' is not a valid runtime name", runtime);

  if (!flatpak_is_valid_name (sdk))
    return flatpak_fail (error, "'%s' is not a valid sdk name", sdk);

  if (!flatpak_is_valid_branch (branch))
    return flatpak_fail (error, "'%s' is not a valid branch name", branch);

  runtime_ref = flatpak_build_untyped_ref (runtime, branch, opt_arch);
  sdk_ref = flatpak_build_untyped_ref (sdk, branch, opt_arch);

  base = g_file_new_for_commandline_arg (directory);

  if (!gs_file_ensure_directory (base, TRUE, cancellable, error))
    return FALSE;

  files_dir = g_file_get_child (base, "files");
  if (opt_sdk_dir)
    usr_dir = g_file_get_child (base, opt_sdk_dir);
  else
    usr_dir = g_file_get_child (base, "usr");
  var_dir = g_file_get_child (base, "var");
  var_tmp_dir = g_file_get_child (var_dir, "tmp");
  var_run_dir = g_file_get_child (var_dir, "run");
  metadata_file = g_file_get_child (base, "metadata");

  if (!opt_update &&
      g_file_query_exists (files_dir, cancellable))
    return flatpak_fail (error, "Build directory %s already initialized", directory);

  if (opt_writable_sdk)
    {
      g_autofree char *full_sdk_ref = g_strconcat ("runtime/", sdk_ref, NULL);
      g_autoptr(GError) my_error = NULL;
      g_autoptr(GFile) sdk_deploy_files = NULL;
      g_autoptr(FlatpakDeploy) sdk_deploy = NULL;

      sdk_deploy = flatpak_find_deploy_for_ref (full_sdk_ref, cancellable, error);
      if (sdk_deploy == NULL)
        return FALSE;

      if (!gs_shutil_rm_rf (usr_dir, NULL, &my_error))
        {
          if (!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);
        }

      sdk_deploy_files = flatpak_deploy_get_files (sdk_deploy);
      if (!flatpak_cp_a (sdk_deploy_files, usr_dir, FLATPAK_CP_FLAGS_NO_CHOWN, cancellable, error))
        return FALSE;

      if (opt_sdk_extensions)
        {
          g_autoptr(GKeyFile) metakey = flatpak_deploy_get_metadata (sdk_deploy);
          GList *extensions = NULL, *l;

          /* We leak this on failure, as we have no autoptr for deep lists.. */
          extensions = flatpak_list_extensions (metakey,
                                                opt_arch,
                                                branch);

          for (i = 0; opt_sdk_extensions[i] != NULL; i++)
            {
              const char *requested_extension = opt_sdk_extensions[i];
              gboolean found = FALSE;

              for (l = extensions; l != NULL; l = l->next)
                {
                  FlatpakExtension *ext = l->data;

                  if (strcmp (ext->installed_id, requested_extension) == 0 ||
                      strcmp (ext->id, requested_extension) == 0)
                    {
                      g_autoptr(GFile) ext_deploy_dir = flatpak_find_deploy_dir_for_ref (ext->ref, cancellable, NULL);
                      if (ext_deploy_dir != NULL)
                        {
                          g_autoptr(GFile) ext_deploy_files = g_file_get_child (ext_deploy_dir, "files");
                          g_autoptr(GFile) target = g_file_resolve_relative_path (usr_dir, ext->directory);
                          g_autoptr(GFile) target_parent = g_file_get_parent (target);

                          if (!gs_file_ensure_directory (target_parent, TRUE, cancellable, error))
                            return FALSE;

                          /* An extension overrides whatever is there before, so we clean up first */
                          if (!gs_shutil_rm_rf (target, cancellable, error))
                            return FALSE;

                          if (!flatpak_cp_a (ext_deploy_files, target, FLATPAK_CP_FLAGS_NO_CHOWN, cancellable, error))
                            return FALSE;

                          found = TRUE;
                        }
                      else
                        {
                          g_list_free_full (extensions, (GDestroyNotify) flatpak_extension_free);
                          return flatpak_fail (error, "Requested extension %s not installed\n", requested_extension);
                        }
                    }
                }

              if (!found)
                return flatpak_fail (error, "No extension %s in sdk\n", requested_extension);
            }
          g_list_free_full (extensions, (GDestroyNotify) flatpak_extension_free);
        }
    }

  if (opt_var)
    {
      var_ref = flatpak_build_runtime_ref (opt_var, branch, opt_arch);

      var_deploy_base = flatpak_find_deploy_dir_for_ref (var_ref, cancellable, error);
      if (var_deploy_base == NULL)
        return FALSE;

      var_deploy_files = g_file_get_child (var_deploy_base, "files");
    }

  if (opt_update)
    return TRUE;

  if (!g_file_make_directory (files_dir, cancellable, error))
    return FALSE;

  if (var_deploy_files)
    {
      if (!gs_shutil_cp_a (var_deploy_files, var_dir, cancellable, error))
        return FALSE;
    }
  else
    {
      if (!g_file_make_directory (var_dir, cancellable, error))
        return FALSE;
    }

  if (!gs_file_ensure_directory (var_tmp_dir, FALSE, cancellable, error))
    return FALSE;

  if (!g_file_query_exists (var_run_dir, cancellable) &&
      !g_file_make_symbolic_link (var_run_dir, "/run", cancellable, error))
    return FALSE;


  metadata_contents = g_string_new ("[Application]\n");
  g_string_append_printf (metadata_contents,
                          "name=%s\n"
                          "runtime=%s\n"
                          "sdk=%s\n",
                          app_id, runtime_ref, sdk_ref);
  if (opt_tags != NULL)
    {
      g_string_append (metadata_contents, "tags=");
      for (i = 0; opt_tags[i] != NULL; i++)
        {
          g_string_append (metadata_contents, opt_tags[i]);
          g_string_append_c (metadata_contents, ';');
        }
      g_string_append_c (metadata_contents, '\n');
    }

  if (!g_file_replace_contents (metadata_file,
                                metadata_contents->str, metadata_contents->len, NULL, FALSE,
                                G_FILE_CREATE_REPLACE_DESTINATION,
                                NULL, cancellable, error))
    return FALSE;

  return TRUE;
}