[evolution] Misc: Generate appdata <releases/> from the NEWS file
- From: Milan Crha <mcrha src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [evolution] Misc: Generate appdata <releases/> from the NEWS file
- Date: Sat, 30 Oct 2021 06:21:10 +0000 (UTC)
commit 14a84aa4df5607604d28acc9d333ac100a122241
Author: Milan Crha <mcrha redhat com>
Date: Sat Oct 30 08:20:19 2021 +0200
Misc: Generate appdata <releases/> from the NEWS file
This makes it easier to populate the release information
in the appdata file.
CMakeLists.txt | 40 ++++
data/CMakeLists.txt | 9 +
data/org.gnome.Evolution.appdata.xml.in.in | 3 +-
news-to-appdata.c | 373 +++++++++++++++++++++++++++++
4 files changed, 423 insertions(+), 2 deletions(-)
---
diff --git a/CMakeLists.txt b/CMakeLists.txt
index f2c757d045..e6ef6aaea3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -718,6 +718,46 @@ if(ENABLE_MARKDOWN)
set(HAVE_MARKDOWN ON)
endif(ENABLE_MARKDOWN)
+# ******************************
+# news-to-appdata tool
+# ******************************
+
+add_executable(news-to-appdata
+ news-to-appdata.c)
+
+target_compile_options(news-to-appdata PUBLIC
+ ${GNOME_PLATFORM_CFLAGS}
+)
+
+target_include_directories(news-to-appdata PUBLIC
+ ${GNOME_PLATFORM_INCLUDE_DIRS}
+)
+
+target_link_libraries(news-to-appdata
+ ${GNOME_PLATFORM_LDFLAGS}
+)
+
+set(CMAKE_REQUIRED_FLAGS ${GNOME_PLATFORM_CFLAGS})
+set(CMAKE_REQUIRED_INCLUDES ${GNOME_PLATFORM_INCLUDE_DIRS})
+set(CMAKE_REQUIRED_LIBRARIES ${GNOME_PLATFORM_LDFLAGS})
+file(TO_NATIVE_PATH "${CMAKE_BINARY_DIR}/appdata-releases.txt" _output_filename)
+file(TO_NATIVE_PATH "${CMAKE_SOURCE_DIR}/NEWS" _news_filename)
+CHECK_C_SOURCE_RUNS("#define BUILD_RUN 1
+ #define BUILD_OUTPUT \"${_output_filename}\"
+ #define BUILD_TYPE \"${APPDATA_RELEASE_TYPE}\"
+ #define BUILD_VERSION \"${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}\"
+ #define BUILD_NEWS_FILE \"${_news_filename}\"
+ #include \"${CMAKE_SOURCE_DIR}/news-to-appdata.c\"" _news_to_appdata_result)
+unset(_news_filename)
+unset(_output_filename)
+unset(CMAKE_REQUIRED_LIBRARIES)
+unset(CMAKE_REQUIRED_INCLUDES)
+unset(CMAKE_REQUIRED_FLAGS)
+
+if(NOT "${_news_to_appdata_result}" EQUAL "1")
+ message(FATAL_ERROR "Failed to run news-to-appdata")
+endif(NOT "${_news_to_appdata_result}" EQUAL "1")
+
# Generate the ${PROJECT_NAME}-config.h file
CONFIGURE_FILE(${CMAKE_SOURCE_DIR}/config.h.in ${CMAKE_BINARY_DIR}/${PROJECT_NAME}-config.h)
diff --git a/data/CMakeLists.txt b/data/CMakeLists.txt
index 2c40549a91..ceb4d7adab 100644
--- a/data/CMakeLists.txt
+++ b/data/CMakeLists.txt
@@ -30,6 +30,15 @@ endif("${VERSION_SUBSTRING}" STREQUAL "")
string(TIMESTAMP APPDATA_RELEASE_BUILD_DATE "%Y-%m-%d")
+if(EXISTS "${CMAKE_BINARY_DIR}/appdata-releases.txt")
+ file(READ ${CMAKE_BINARY_DIR}/appdata-releases.txt APPDATA_RELEASES)
+endif(EXISTS "${CMAKE_BINARY_DIR}/appdata-releases.txt")
+
+if("${APPDATA_RELEASES}" STREQUAL "")
+ message(WARNING "Failed to extract release information from the NEWS file, falling back to generic
information")
+ set(APPDATA_RELEASES " <release version=\"${APPDATA_RELEASE_VERSION}\"
date=\"${APPDATA_RELEASE_BUILD_DATE}\" time=\"${APPDATA_RELEASE_TYPE}\"/>\n")
+endif("${APPDATA_RELEASES}" STREQUAL "")
+
configure_file(org.gnome.Evolution.appdata.xml.in.in
org.gnome.Evolution.appdata.xml.in
@ONLY
diff --git a/data/org.gnome.Evolution.appdata.xml.in.in b/data/org.gnome.Evolution.appdata.xml.in.in
index ff263b5478..3547bb2226 100644
--- a/data/org.gnome.Evolution.appdata.xml.in.in
+++ b/data/org.gnome.Evolution.appdata.xml.in.in
@@ -44,8 +44,7 @@
<project_group>GNOME</project_group>
<translation type="gettext">evolution</translation>
<releases>
- <release version="@APPDATA_RELEASE_VERSION@" type="@APPDATA_RELEASE_TYPE@"
date="@APPDATA_RELEASE_BUILD_DATE@"/>
- </releases>
+@APPDATA_RELEASES@ </releases>
<content_rating type="oars-1.1">
<content_attribute id="social-chat">intense</content_attribute>
<content_attribute id="social-contacts">intense</content_attribute>
diff --git a/news-to-appdata.c b/news-to-appdata.c
new file mode 100644
index 0000000000..f8466fea33
--- /dev/null
+++ b/news-to-appdata.c
@@ -0,0 +1,373 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2021 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include <locale.h>
+#include <stdio.h>
+#include <errno.h>
+#include <glib.h>
+
+static gint
+extract_ver_number (const gchar **pstr)
+{
+ const gchar *str = *pstr;
+ gint num = 0;
+
+ if (*str == 0)
+ return num;
+
+ while (*str && *str != '.') {
+ num = num * 10 + (*str) - '0';
+ str++;
+ }
+
+ if (*str == '.')
+ str++;
+
+ *pstr = str;
+
+ return num;
+}
+
+static gint
+cmp_version_str (const gchar *ver1,
+ const gchar *ver2)
+{
+ gint num1 = 0, num2 = 0;
+
+ while (*ver1 && *ver2 && num1 == num2) {
+ num1 = extract_ver_number (&ver1);
+ num2 = extract_ver_number (&ver2);
+ }
+
+ return num1 - num2;
+}
+
+typedef struct _ESection {
+ gchar *header;
+ GSList *items; /* gchar * */
+} ESection;
+
+static void
+e_section_free (gpointer ptr)
+{
+ ESection *section = ptr;
+
+ if (section) {
+ g_free (section->header);
+ g_slist_free_full (section->items, g_free);
+ g_free (section);
+ }
+}
+
+typedef struct _EVersion {
+ gint order;
+ gchar *project_name;
+ gchar *version;
+ gchar *date;
+ GSList *sections; /* ESection * */
+} EVersion;
+
+static void
+e_version_free (gpointer ptr)
+{
+ EVersion *version = ptr;
+
+ if (version) {
+ g_free (version->project_name);
+ g_free (version->version);
+ g_free (version->date);
+ g_slist_free_full (version->sections, e_section_free);
+ g_free (version);
+ }
+}
+
+static gboolean
+extract_versions (GSList **pversions,
+ gint order,
+ const gchar *read_version,
+ const gchar *filename)
+{
+ gchar *content = NULL, **lines;
+ gboolean res = TRUE;
+ gint ii, n_read = 0;
+ GError *error = NULL;
+
+ if (!g_file_get_contents (filename, &content, NULL, &error)) {
+ g_printerr ("news-to-appdata: Failed to read '%s': %s\n", filename, error ? error->message :
"Unknown error");
+ g_clear_error (&error);
+ return FALSE;
+ }
+
+ lines = g_strsplit (content, "\n", -1);
+
+ for (ii = 0; lines[ii] && res; ii++) {
+ /* Version separator, the previous line contains the version information header */
+ if (ii > 0 && g_str_has_prefix (lines[ii], "-----")) {
+ gchar **info;
+
+ info = g_strsplit (lines[ii - 1], " ", -1);
+
+ if (g_strv_length (info) == 3) {
+ EVersion *version;
+ ESection *current_section = NULL;
+ GString *paragraph = NULL;
+
+ if (cmp_version_str (read_version, info[1]) > 0) {
+ g_strfreev (info);
+ break;
+ }
+
+ version = g_new0 (EVersion, 1);
+ version->order = order;
+ version->project_name = info[0];
+ version->version = info[1];
+ version->date = info[2];
+
+ *pversions = g_slist_prepend (*pversions, version);
+ n_read++;
+
+ g_clear_pointer (&info, g_free);
+
+ for (ii++; lines[ii] && res; ii++) {
+ gchar *line = lines[ii];
+
+ /* Empty line ends the section */
+ if (!*line) {
+ current_section = NULL;
+
+ if (paragraph) {
+ ESection *section;
+
+ section = g_new0 (ESection, 1);
+ section->header = g_string_free (paragraph, FALSE);
+
+ version->sections = g_slist_prepend
(version->sections, section);
+
+ paragraph = NULL;
+ }
+ /* Starts a free paragraph section */
+ } else if (*line == '*') {
+ if (paragraph) {
+ g_printerr ("news-to-appdata: Unexpected start of a
free paragraph section when reading one at line %d of '%s'\n", ii, filename);
+ res = FALSE;
+ } else {
+ paragraph = g_string_new (g_strstrip (line + 1));
+ }
+ /* Continues the free paragraph section */
+ } else if (*line == ' ') {
+ if (paragraph) {
+ g_string_append_c (paragraph, ' ');
+ g_string_append (paragraph, g_strstrip (line));
+ } else {
+ g_printerr ("news-to-appdata: Unexpected free
paragraph section continuation at line %d of '%s'\n", ii, filename);
+ res = FALSE;
+ }
+ /* Section item */
+ } else if (*line == '\t') {
+ if (current_section) {
+ current_section->items = g_slist_prepend
(current_section->items, g_strdup (line + 1));
+ } else {
+ g_printerr ("news-to-appdata: Unexpected section item
at line %d of '%s'\n", ii, filename);
+ res = FALSE;
+ }
+ /* Maybe the next version information header, stop reading here */
+ } else if (lines[ii + 1] && g_str_has_prefix (lines[ii + 1],
"-----")) {
+ break;
+ /* Anything else is a new section header */
+ } else {
+ current_section = g_new0 (ESection, 1);
+ current_section->header = g_strdup (line);
+ version->sections = g_slist_prepend (version->sections,
current_section);
+ }
+ }
+
+ if (paragraph)
+ g_string_free (paragraph, TRUE);
+
+ ii--;
+
+ if (res) {
+ GSList *slink;
+
+ version->sections = g_slist_reverse (version->sections);
+
+ for (slink = version->sections; slink; slink = g_slist_next (slink)) {
+ ESection *section = slink->data;
+
+ section->items = g_slist_reverse (section->items);
+ }
+ }
+ } else {
+ g_printerr ("news-to-appdata: Version info line should contain 3 parts, but
it has %d; at line %d of '%s'\n",
+ g_strv_length (info), ii - 1, filename);
+ res = FALSE;
+ }
+
+ g_strfreev (info);
+ }
+ }
+
+ g_strfreev (lines);
+ g_free (content);
+
+ if (res && !n_read) {
+ g_printerr ("news-to-appdata: No version information for '%s' found in '%s'\n", read_version,
filename);
+#ifndef BUILD_RUN
+ res = FALSE;
+#endif
+ }
+
+ return res;
+}
+
+static gint
+sort_versions_cb (gconstpointer ptr1,
+ gconstpointer ptr2)
+{
+ EVersion *ver1 = (EVersion *) ptr1, *ver2 = (EVersion *) ptr2;
+ gint res;
+
+ res = cmp_version_str (ver1->version, ver2->version);
+
+ if (!res) {
+ res = ver1->order - ver2->order;
+ } else {
+ /* Sort in reverse order, highest version first */
+ res *= -1;
+ }
+
+ return res;
+}
+
+gint
+main (gint argc,
+ const gchar *argv[])
+{
+ FILE *output;
+ GSList *vlink, *versions = NULL; /* EVersion */
+ const gchar *release_type, *last_version = NULL, *output_filename;
+ gboolean multiple_projects;
+ gint ii;
+
+#ifdef BUILD_RUN
+ const gchar *margv[] = {
+ "news-to-appdata",
+ BUILD_OUTPUT,
+ BUILD_TYPE,
+ BUILD_VERSION,
+ BUILD_NEWS_FILE
+ };
+
+ argc = G_N_ELEMENTS (margv);
+ argv = margv;
+#endif
+
+ setlocale (LC_ALL, "");
+
+ if (argc == 1 || (argc == 2 && g_strcmp0 (argv[1], "--help") == 0)) {
+ g_print ("news-to-appdata: Converts NEWS entries into appdata <release/> content\n");
+ g_print ("Usage: news-to-appdata <output> <type> <version> <NEWS> [<version> <NEWS> ...]\n");
+ g_print ("Arguments:\n");
+ g_print (" <output> - path to output file, empty string for stdout\n");
+ g_print (" <type> - build type, like \"stable\" or \"development\"\n");
+ g_print (" <version> - version down to filter the NEWS file for, like 3.40\n");
+ g_print (" <NEWS> - path to the NEWS file to extract the information from\n");
+
+ return argc == 1 ? -1 : 0;
+ }
+
+ if (argc < 5) {
+ g_printerr ("news-to-appdata: Expects at least four arguments\n");
+ return -3;
+ }
+
+ if ((argc - 3) % 2 != 0) {
+ g_printerr ("news-to-appdata: Expects pairs of the arguments (<version> <NEWS>)\n");
+ return -4;
+ }
+
+ output_filename = argv[1];
+ release_type = argv[2];
+ multiple_projects = argc - 3 > 2;
+
+ for (ii = 3; ii < argc; ii += 2) {
+ const gchar *version, *filename;
+
+ version = argv[ii];
+ filename = argv[ii + 1];
+
+ if (!extract_versions (&versions, ii, version, filename)) {
+ g_slist_free_full (versions, e_version_free);
+ return -5;
+ }
+ }
+
+ versions = g_slist_sort (versions, sort_versions_cb);
+
+ if (*output_filename) {
+ output = fopen (output_filename, "w+b");
+
+ if (!output) {
+ g_printerr ("news-to-appdata: Failed to open '%s' for writing: %s\n",
output_filename, g_strerror (errno));
+ g_slist_free_full (versions, e_version_free);
+ return -6;
+ }
+ } else
+ output = stdout;
+
+ for (vlink = versions; vlink; vlink = g_slist_next (vlink)) {
+ EVersion *version = vlink->data;
+ GSList *slink;
+
+ if (g_strcmp0 (version->version, last_version) != 0) {
+ if (last_version) {
+ fprintf (output, " </description>\n");
+ fprintf (output, " </release>\n");
+ }
+
+ /* Uses the release data of the first noticed version */
+ fprintf (output, " <release version=\"%s\" date=\"%s\" type=\"%s\">\n",
version->version, version->date, release_type);
+ fprintf (output, " <description>\n");
+
+ last_version = version->version;
+ }
+
+ for (slink = version->sections; slink; slink = g_slist_next (slink)) {
+ ESection *section = slink->data;
+
+ if (multiple_projects && section->items)
+ fprintf (output, " <p>%s %s</p>\n", version->project_name,
section->header);
+ else
+ fprintf (output, " <p>%s</p>\n", section->header);
+
+ if (section->items) {
+ GSList *ilink;
+
+ fprintf (output, " <ul>\n");
+
+ for (ilink = section->items; ilink; ilink = g_slist_next (ilink)) {
+ const gchar *item = ilink->data;
+
+ fprintf (output, " <li>%s</li>\n", item);
+ }
+
+ fprintf (output, " </ul>\n");
+ }
+ }
+ }
+
+ if (last_version) {
+ fprintf (output, " </description>\n");
+ fprintf (output, " </release>\n");
+ }
+
+ if (output != stdout)
+ fclose (output);
+
+ g_slist_free_full (versions, e_version_free);
+
+ return 0;
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]