From 825ef52f9752e234fb1a64041a10214be6572acf Mon Sep 17 00:00:00 2001 From: Kornelius Rohrschneider Date: Wed, 24 Jul 2024 16:03:18 +0200 Subject: [PATCH] Added support for .metainfo.xml files Previously, appimagetool had expected a .appdata.xml file, although that format had been deprecated and .metainfo.xml should be used for new projects. This has been fixed. Support for .metainfo.xml files has been added. appimagetool now accepts both .metainfo.xml and .appdata.xml files. A warning has also been added when using a .appdata.xml file that the file extension should be changed to .metainfo.xml. Additionally, the link to the generator hadn't worked anymore. It has been replaced with a working link to an AppStream generator. --- src/appimagetool.c | 141 ++++++++++++++++++++++++++------------------- 1 file changed, 82 insertions(+), 59 deletions(-) diff --git a/src/appimagetool.c b/src/appimagetool.c index a4132649..5f930bb4 100644 --- a/src/appimagetool.c +++ b/src/appimagetool.c @@ -1,19 +1,19 @@ /************************************************************************** - * + * * Copyright (c) 2004-19 Simon Peter - * + * * All Rights Reserved. - * + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - * + * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -21,7 +21,7 @@ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. - * + * **************************************************************************/ #ident "AppImage by Simon Peter, http://appimage.org/" @@ -65,7 +65,7 @@ extern char runtime[]; extern unsigned int runtime_len; #endif -enum fARCH { +enum fARCH { fARCH_i386, fARCH_x86_64, fARCH_arm, @@ -105,7 +105,7 @@ int sfs_ls(char* image) { sqfs_err err = SQFS_OK; sqfs_traverse trv; sqfs fs; - + ssize_t fs_offset = appimage_get_elf_size(image); // error check @@ -114,7 +114,7 @@ int sfs_ls(char* image) { if ((err = sqfs_open_image(&fs, image, fs_offset))) die("sqfs_open_image error"); - + if ((err = sqfs_traverse_open(&trv, &fs, sqfs_inode_root(&fs)))) die("sqfs_traverse_open error"); while (sqfs_traverse_next(&trv, &err)) { @@ -125,12 +125,12 @@ int sfs_ls(char* image) { if (err) die("sqfs_traverse_next error"); sqfs_traverse_close(&trv); - + sqfs_fd_close(fs.fd); return 0; } -/* Generate a squashfs filesystem using mksquashfs on the $PATH +/* Generate a squashfs filesystem using mksquashfs on the $PATH * execlp(), execvp(), and execvpe() search on the $PATH */ int sfs_mksquashfs(char *source, char *destination, int offset) { pid_t pid = fork(); @@ -146,13 +146,13 @@ int sfs_mksquashfs(char *source, char *destination, int offset) { perror("sfs_mksquashfs waitpid() failed"); return(-1); } - + int retcode = WEXITSTATUS(status); if (retcode) { fprintf(stderr, "mksquashfs (pid %d) exited with code %d\n", pid, retcode); return(-1); } - + return 0; } else { // we are the child @@ -272,7 +272,7 @@ int validate_desktop_file(char *file) { } /* Generate a squashfs filesystem -* The following would work if we link to mksquashfs.o after we renamed +* The following would work if we link to mksquashfs.o after we renamed * main() to mksquashfs_main() in mksquashfs.c but we don't want to actually do * this because squashfs-tools is not under a permissive license * i *nt sfs_mksquashfs(char *source, char *destination) { @@ -291,19 +291,19 @@ int validate_desktop_file(char *file) { static void replacestr(char *line, const char *search, const char *replace) { char *sp = NULL; - + if ((sp = strstr(line, search)) == NULL) { return; } int search_len = strlen(search); int replace_len = strlen(replace); int tail_len = strlen(sp+search_len); - + memmove(sp+replace_len,sp+search_len,tail_len+1); memcpy(sp, replace, replace_len); - + /* Do it recursively again until no more work to do */ - + if ((sp = strstr(line, search))) { replacestr(line, search, replace); } @@ -447,7 +447,7 @@ gchar* find_first_matching_file_nonrecursive(const gchar *real_path, const gchar } g_dir_close(dir); } - else { + else { g_warning("%s: %s", real_path, g_strerror(errno)); } return NULL; @@ -477,7 +477,7 @@ bool readFile(char* filename, int* size, char** buffer) { fread(indata, fsize, 1, f); fclose(f); *size = (int)fsize; - *buffer = indata; + *buffer = indata; return TRUE; } @@ -542,7 +542,7 @@ int main (int argc, char *argv[]) { - /* Parse Travis CI environment variables. + /* Parse Travis CI environment variables. * https://docs.travis-ci.com/user/environment-variables/#Default-Environment-Variables * TRAVIS_COMMIT: The commit that the current build is testing. * TRAVIS_REPO_SLUG: The slug (in form: owner_name/repo_name) of the repository currently being built. @@ -563,18 +563,18 @@ main (int argc, char *argv[]) /* Parse GitLab CI environment variables. * https://docs.gitlab.com/ee/ci/variables/#predefined-variables-environment-variables * echo "${CI_PROJECT_URL}/-/jobs/artifacts/${CI_COMMIT_REF_NAME}/raw/QtQuickApp-x86_64.AppImage?job=${CI_JOB_NAME}" - */ + */ char* CI_PROJECT_URL; CI_PROJECT_URL = getenv("CI_PROJECT_URL"); char* CI_COMMIT_REF_NAME; CI_COMMIT_REF_NAME = getenv("CI_COMMIT_REF_NAME"); // The branch or tag name for which project is built char* CI_JOB_NAME; CI_JOB_NAME = getenv("CI_JOB_NAME"); // The name of the job as defined in .gitlab-ci.yml - + /* Parse OWD environment variable. * If it is available then cd there. It is the original CWD prior to running AppRun */ char* owd_env = NULL; - owd_env = getenv("OWD"); + owd_env = getenv("OWD"); if(NULL!=owd_env){ int ret; ret = chdir(owd_env); @@ -583,13 +583,13 @@ main (int argc, char *argv[]) exit(1); } } - + GError *error = NULL; GOptionContext *context; // initialize help text of argument sprintf(_exclude_file_desc, "Uses given file as exclude file for mksquashfs, in addition to %s.", APPIMAGEIGNORE); - + context = g_option_context_new ("SOURCE [DESTINATION] - Generate, extract, and inspect AppImages"); g_option_context_add_main_entries (context, entries, NULL); // g_option_context_add_group (context, gtk_get_option_group (TRUE)); @@ -646,16 +646,16 @@ main (int argc, char *argv[]) g_print("WARNING: gpg2 or gpg command is missing, please install it if you want to create digital signatures\n"); if(! g_find_program_in_path ("sha256sum") && ! g_find_program_in_path ("shasum")) g_print("WARNING: sha256sum or shasum command is missing, please install it if you want to create digital signatures\n"); - + if(!&remaining_args[0]) die("SOURCE is missing"); - + /* If in list mode */ if (list){ sfs_ls(remaining_args[0]); exit(0); } - + /* If the first argument is a directory, then we assume that we should package it */ if (g_file_test(remaining_args[0], G_FILE_TEST_IS_DIR)) { /* Parse VERSION environment variable. @@ -709,7 +709,7 @@ main (int argc, char *argv[]) char *destination; char source[PATH_MAX]; realpath(remaining_args[0], source); - + /* Check if *.desktop file is present in source AppDir */ gchar *desktop_file = find_first_matching_file_nonrecursive(source, "*.desktop"); if(desktop_file == NULL){ @@ -732,7 +732,7 @@ main (int argc, char *argv[]) die(".desktop file cannot be parsed"); if (!get_desktop_entry(kf, "Categories")) die(".desktop file is missing a Categories= key"); - + if(verbose){ fprintf (stderr,"Name: %s\n", get_desktop_entry(kf, "Name")); fprintf (stderr,"Icon: %s\n", get_desktop_entry(kf, "Icon")); @@ -765,10 +765,10 @@ main (int argc, char *argv[]) char app_name_for_filename[PATH_MAX]; sprintf(app_name_for_filename, "%s", get_desktop_entry(kf, "Name")); replacestr(app_name_for_filename, " ", "_"); - + if(verbose) fprintf (stderr,"App name for filename: %s\n", app_name_for_filename); - + if (remaining_args[1]) { destination = remaining_args[1]; } else { @@ -821,10 +821,10 @@ main (int argc, char *argv[]) fprintf (stderr, "%s\n", example_path); exit(1); } - + /* Check if .DirIcon is present in source AppDir */ gchar *diricon_path = g_build_filename(source, ".DirIcon", NULL); - + if (! g_file_test(diricon_path, G_FILE_TEST_EXISTS)){ fprintf (stderr, "Deleting pre-existing .DirIcon\n"); g_unlink(diricon_path); @@ -835,20 +835,43 @@ main (int argc, char *argv[]) if(res) die("Could not symlink .DirIcon"); } - + /* Check if AppStream upstream metadata is present in source AppDir */ if(! no_appstream){ - char application_id[PATH_MAX]; - sprintf (application_id, "%s", basename(desktop_file)); - replacestr(application_id, ".desktop", ".appdata.xml"); - gchar *appdata_path = g_build_filename(source, "/usr/share/metainfo/", application_id, NULL); - if (! g_file_test(appdata_path, G_FILE_TEST_IS_REGULAR)){ - fprintf (stderr, "WARNING: AppStream upstream metadata is missing, please consider creating it\n"); - fprintf (stderr, " in usr/share/metainfo/%s\n", application_id); - fprintf (stderr, " Please see https://www.freedesktop.org/software/appstream/docs/chap-Quickstart.html#sect-Quickstart-DesktopApps\n"); - fprintf (stderr, " for more information or use the generator at http://output.jsbin.com/qoqukof.\n"); - } else { - fprintf (stderr, "AppStream upstream metadata found in usr/share/metainfo/%s\n", application_id); + bool appstream_found = false; + char *appstream_filename; + gchar **appstream_path; + + char metainfo_filename[PATH_MAX]; + sprintf(metainfo_filename, "%s", basename(desktop_file)); + replacestr(metainfo_filename, ".desktop", ".metainfo.xml"); + gchar *metainfo_path = g_build_filename(source, "/usr/share/metainfo/", metainfo_filename, NULL); + if (!g_file_test(metainfo_path, G_FILE_TEST_IS_REGULAR)) { + char appdata_filename[PATH_MAX]; + sprintf(appdata_filename, "%s", basename(desktop_file)); + replacestr(appdata_filename, ".desktop", ".appdata.xml"); + gchar *appdata_path = g_build_filename(source, "/usr/share/metainfo/", appdata_filename, NULL); + if (!g_file_test(appdata_path, G_FILE_TEST_IS_REGULAR)) { + fprintf(stderr, "WARNING: AppStream upstream metadata is missing, please consider creating it\n"); + fprintf(stderr, " in usr/share/metainfo/%s\n", metainfo_filename); + fprintf(stderr, " Please see https://www.freedesktop.org/software/appstream/docs/chap-Quickstart.html#sect-Quickstart-DesktopApps\n"); + fprintf(stderr, " for more information or use the generator at https://docs.appimage.org/packaging-guide/optional/appstream.html.\n"); + } else { + appstream_found = true; + appstream_filename = &appdata_filename; + appstream_path = &appdata_path; + fprintf(stderr, "WARNING: The appstream upstream metadata file should be named %s.\n", metainfo_filename); + fprintf(stderr, " .appdata.xml is a legacy file ending and shouldn't be used in new AppImages anymore.\n"); + fprintf(stderr, " See https://www.freedesktop.org/software/appstream/docs/sect-Metadata-Application.html.\n"); + } + } else { + appstream_found = true; + appstream_filename = &metainfo_filename; + appstream_path = &metainfo_path; + } + + if (appstream_found) { + fprintf (stderr, "AppStream upstream metadata found in usr/share/metainfo/%s\n", *appstream_filename); /* Use ximion's appstreamcli to make sure that desktop file and appdata match together */ if(g_find_program_in_path ("appstreamcli")) { char *args[] = { @@ -868,7 +891,7 @@ main (int argc, char *argv[]) char *args[] = { "appstream-util", "validate-relax", - appdata_path, + *appstream_path, NULL }; g_print("Trying to validate AppStream information with the appstream-util tool\n"); @@ -879,7 +902,7 @@ main (int argc, char *argv[]) } } } - + /* Upstream mksquashfs can currently not start writing at an offset, * so we need a patched one. https://github.com/plougher/squashfs-tools/pull/13 * should hopefully change that. */ @@ -904,11 +927,11 @@ main (int argc, char *argv[]) } if (verbose) printf("Size of the embedded runtime: %d bytes\n", size); - + int result = sfs_mksquashfs(source, destination, size); if(result != 0) die("sfs_mksquashfs error"); - + fprintf (stderr, "Embedding ELF...\n"); FILE *fpdst = fopen(destination, "rb+"); if (fpdst == NULL) { @@ -926,7 +949,7 @@ main (int argc, char *argv[]) printf("Could not set executable bit, aborting\n"); exit(1); } - + if(bintray_user != NULL){ if(bintray_repo != NULL){ char buf[1024]; @@ -935,7 +958,7 @@ main (int argc, char *argv[]) printf("%s\n", updateinformation); } } - + /* If the user has not provided update information but we know this is a Travis CI build, * then fill in update information based on TRAVIS_REPO_SLUG */ if(guess_update_information){ @@ -953,7 +976,7 @@ main (int argc, char *argv[]) if(zsyncmake_path){ char buf[1024]; gchar **parts = g_strsplit (travis_repo_slug, "/", 2); - /* https://github.com/AppImage/AppImageSpec/blob/master/draft.md#github-releases + /* https://github.com/AppImage/AppImageSpec/blob/master/draft.md#github-releases * gh-releases-zsync|probono|AppImages|latest|Subsurface*-x86_64.AppImage.zsync */ gchar *channel = "continuous"; if(travis_tag != NULL){ @@ -983,7 +1006,7 @@ main (int argc, char *argv[]) } } } - + /* If updateinformation was provided, then we check and embed it */ if(updateinformation != NULL){ if(!g_str_has_prefix(updateinformation,"zsync|")) @@ -991,14 +1014,14 @@ main (int argc, char *argv[]) if(!g_str_has_prefix(updateinformation,"gh-releases-zsync|")) if(!g_str_has_prefix(updateinformation,"pling-v1-zsync|")) die("The provided updateinformation is not in a recognized format"); - + gchar **ui_type = g_strsplit_set(updateinformation, "|", -1); - + if(verbose) printf("updateinformation type: %s\n", ui_type[0]); /* TODO: Further checking of the updateinformation */ - - + + unsigned long ui_offset = 0; unsigned long ui_length = 0;