summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRichard Hughes <richard@hughsie.com>2019-03-20 14:24:03 +0000
committerRichard Hughes <richard@hughsie.com>2019-03-20 17:25:11 +0000
commit2eb9b8caf9ca22ac914b63f34fea5dd6da6f4e76 (patch)
tree6100a42b746b03dc25b80eb1b43ba4f97ab27097
parenta8598744d2c41329cc803ae5a91fdc0e7aa1c16d (diff)
downloadappstream-glib-2eb9b8caf9ca22ac914b63f34fea5dd6da6f4e76.tar.gz
Modernize the validation requirements
* Require <content_rating> for any desktop or console application * Make <release> mandatory when validating * Further relax style requirements * Require description for desktop an console apps This changes a lot of things that are now best practice for all apps, and required to be included in Flathub. Use `appstream-util validate-relax` to ignore some new failures and use `appstream-util validate-strict` to bring them back. Fixes: https://github.com/hughsie/appstream-glib/issues/263
-rw-r--r--data/tests/broken.appdata.xml4
-rw-r--r--data/tests/intltool.appdata.xml.in13
-rw-r--r--data/tests/success.appdata.xml13
-rw-r--r--libappstream-glib/as-app-validate.c155
-rw-r--r--libappstream-glib/as-self-test.c28
5 files changed, 165 insertions, 48 deletions
diff --git a/data/tests/broken.appdata.xml b/data/tests/broken.appdata.xml
index 126d419..f7a5386 100644
--- a/data/tests/broken.appdata.xml
+++ b/data/tests/broken.appdata.xml
@@ -9,11 +9,11 @@
<summary>Observe power management.</summary>
<description>
<ul>
- <li>One</li>
+ <li>On</li>
<li>two.</li>
</ul>
<p xml:lang="C">
- This application is awesome
+ Awesome!
</p>
<p>
This application is awesome
diff --git a/data/tests/intltool.appdata.xml.in b/data/tests/intltool.appdata.xml.in
index 6033cc6..1c6cec0 100644
--- a/data/tests/intltool.appdata.xml.in
+++ b/data/tests/intltool.appdata.xml.in
@@ -30,4 +30,17 @@
<translation type="gettext">gnome-power-manager</translation>
<url type="homepage">http://www.gnome.org/projects/gnome-power-manager/</url>
<updatecontact>richard_at_hughsie.com</updatecontact>
+ <content_rating type="oars-1.1" />
+ <releases>
+ <release date="2019-03-11" version="3.32.0">
+ <description>
+ <p>
+ This is the first stable release for GNOME 3.32.
+ </p>
+ <ul>
+ <li>Appstream parsing is completely rewritten and now uses the new libxmlb library, instead of appstream-glib</li>
+ </ul>
+ </description>
+ </release>
+ </releases>
</application>
diff --git a/data/tests/success.appdata.xml b/data/tests/success.appdata.xml
index e1ca97f..248b9e6 100644
--- a/data/tests/success.appdata.xml
+++ b/data/tests/success.appdata.xml
@@ -42,4 +42,17 @@
<url type="homepage">http://www.gnome.org/projects/gnome-power-manager/</url>
<updatecontact>richard_at_hughsie.com</updatecontact>
<project_group>GNOME</project_group>
+ <content_rating type="oars-1.1" />
+ <releases>
+ <release date="2019-03-11" version="3.32.0">
+ <description>
+ <p>
+ This is the first stable release for GNOME 3.32.
+ </p>
+ <ul>
+ <li>Appstream parsing is completely rewritten and now uses the new libxmlb library, instead of appstream-glib</li>
+ </ul>
+ </description>
+ </release>
+ </releases>
</application>
diff --git a/libappstream-glib/as-app-validate.c b/libappstream-glib/as-app-validate.c
index 742b968..3ed6d40 100644
--- a/libappstream-glib/as-app-validate.c
+++ b/libappstream-glib/as-app-validate.c
@@ -147,10 +147,16 @@ as_app_validate_has_first_word_capital (AsAppValidateHelper *helper, const gchar
static void
as_app_validate_description_li (const gchar *text, AsAppValidateHelper *helper)
{
- gboolean require_sentence_case = TRUE;
+ gboolean require_sentence_case = FALSE;
guint str_len;
- guint length_li_max = 100;
- guint length_li_min = 20;
+ guint length_li_max = 500;
+ guint length_li_min = 3;
+
+ /* make the requirements more strict */
+ if ((helper->flags & AS_APP_VALIDATE_FLAG_STRICT) > 0) {
+ require_sentence_case = TRUE;
+ length_li_max = 250;
+ }
/* relax the requirements a bit */
if ((helper->flags & AS_APP_VALIDATE_FLAG_RELAX) > 0) {
@@ -180,7 +186,7 @@ as_app_validate_description_li (const gchar *text, AsAppValidateHelper *helper)
"<li> is too long [%s] maximum is %u chars",
text, length_li_max);
}
- if (ai_app_validate_fullstop_ending (text)) {
+ if (require_sentence_case && ai_app_validate_fullstop_ending (text)) {
ai_app_validate_add (helper,
AS_PROBLEM_KIND_STYLE_INCORRECT,
"<li> cannot end in '.' [%s]", text);
@@ -202,9 +208,9 @@ as_app_validate_description_li (const gchar *text, AsAppValidateHelper *helper)
static void
as_app_validate_description_para (const gchar *text, AsAppValidateHelper *helper)
{
- gboolean require_sentence_case = TRUE;
- guint length_para_max = 600;
- guint length_para_min = 50;
+ gboolean require_sentence_case = FALSE;
+ guint length_para_max = 1000;
+ guint length_para_min = 10;
guint str_len;
/* empty */
@@ -215,11 +221,15 @@ as_app_validate_description_para (const gchar *text, AsAppValidateHelper *helper
return;
}
+ /* make the requirements more strict */
+ if ((helper->flags & AS_APP_VALIDATE_FLAG_STRICT) > 0) {
+ require_sentence_case = TRUE;
+ }
+
/* relax the requirements a bit */
if ((helper->flags & AS_APP_VALIDATE_FLAG_RELAX) > 0) {
- length_para_max = 1000;
- length_para_min = 10;
- require_sentence_case = FALSE;
+ length_para_max = 2000;
+ length_para_min = 5;
}
/* previous was short */
@@ -261,7 +271,8 @@ as_app_validate_description_para (const gchar *text, AsAppValidateHelper *helper
AS_PROBLEM_KIND_STYLE_INCORRECT,
"<p> requires sentence case [%s]", text);
}
- if (text[str_len - 1] != '.' &&
+ if (require_sentence_case &&
+ text[str_len - 1] != '.' &&
text[str_len - 1] != '!' &&
text[str_len - 1] != ':') {
ai_app_validate_add (helper,
@@ -277,7 +288,7 @@ as_app_validate_description_list (const gchar *text,
gboolean allow_short_para,
AsAppValidateHelper *helper)
{
- guint length_para_before_list = 300;
+ guint length_para_before_list = 20;
/* relax the requirements a bit */
if ((helper->flags & AS_APP_VALIDATE_FLAG_RELAX) > 0) {
@@ -689,8 +700,17 @@ as_app_validate_icons (AsApp *app, AsAppValidateHelper *helper)
/* just check the default icon */
icon = as_app_get_icon_default (app);
- if (icon == NULL)
+ if (icon == NULL) {
+ AsFormat *fmt = as_app_get_format_default (app);
+ if (fmt != NULL &&
+ as_format_get_kind (fmt) == AS_FORMAT_KIND_APPSTREAM &&
+ as_app_get_kind (app) == AS_APP_KIND_DESKTOP) {
+ ai_app_validate_add (helper,
+ AS_PROBLEM_KIND_TAG_MISSING,
+ "desktop application has no icon");
+ }
return;
+ }
/* check the content is correct */
icon_kind = as_icon_get_kind (icon);
@@ -746,7 +766,7 @@ as_app_validate_screenshots (AsApp *app, AsAppValidateHelper *helper)
AsScreenshot *ss;
GPtrArray *screenshots;
gboolean screenshot_has_default = FALSE;
- guint number_screenshots_max = 5;
+ guint number_screenshots_max = 25;
guint number_screenshots_min = 1;
guint i;
@@ -759,6 +779,8 @@ as_app_validate_screenshots (AsApp *app, AsAppValidateHelper *helper)
/* firmware does not need screenshots */
if (as_app_get_kind (app) == AS_APP_KIND_FIRMWARE ||
as_app_get_kind (app) == AS_APP_KIND_DRIVER ||
+ as_app_get_kind (app) == AS_APP_KIND_RUNTIME ||
+ as_app_get_kind (app) == AS_APP_KIND_ADDON ||
as_app_get_kind (app) == AS_APP_KIND_LOCALIZATION)
number_screenshots_min = 0;
@@ -812,16 +834,21 @@ as_app_validate_release (AsApp *app,
{
const gchar *tmp;
guint64 timestamp;
- guint number_para_max = 3;
+ guint number_para_max = 10;
guint number_para_min = 1;
gboolean required_timestamp = TRUE;
/* relax the requirements a bit */
if ((helper->flags & AS_APP_VALIDATE_FLAG_RELAX) > 0) {
- number_para_max = 4;
+ number_para_max = 20;
required_timestamp = FALSE;
}
+ /* make the requirements more strict */
+ if ((helper->flags & AS_APP_VALIDATE_FLAG_STRICT) > 0) {
+ number_para_max = 4;
+ }
+
/* check version */
tmp = as_release_get_version (release);
if (tmp == NULL) {
@@ -907,6 +934,7 @@ as_app_validate_releases (AsApp *app, AsAppValidateHelper *helper, GError **erro
{
GPtrArray *releases;
AsFormat *format;
+ gboolean require_release = FALSE;
/* only for AppData */
format = as_app_get_format_default (app);
@@ -914,7 +942,20 @@ as_app_validate_releases (AsApp *app, AsAppValidateHelper *helper, GError **erro
as_format_get_kind (format) != AS_FORMAT_KIND_METAINFO)
return TRUE;
+ /* only for desktop and console apps */
+ if (as_app_get_kind (app) == AS_APP_KIND_DESKTOP ||
+ as_app_get_kind (app) == AS_APP_KIND_CONSOLE) {
+ require_release = TRUE;
+ }
+
+ /* require releases */
releases = as_app_get_releases (app);
+ if (require_release && releases->len == 0) {
+ ai_app_validate_add (helper,
+ AS_PROBLEM_KIND_TAG_MISSING,
+ "<release> required");
+ return TRUE;
+ }
for (guint i = 0; i < releases->len; i++) {
AsRelease *release = g_ptr_array_index (releases, i);
if (!as_app_validate_release (app, release, helper, error))
@@ -1149,22 +1190,24 @@ as_app_validate (AsApp *app, guint32 flags, GError **error)
gboolean require_appstream_spec_only = FALSE;
gboolean require_contactdetails = TRUE;
gboolean require_copyright = FALSE;
+ gboolean require_description = FALSE;
gboolean require_project_license = FALSE;
- gboolean require_sentence_case = TRUE;
+ gboolean require_sentence_case = FALSE;
gboolean require_translations = FALSE;
gboolean require_url = TRUE;
gboolean require_content_license = TRUE;
gboolean require_name = TRUE;
gboolean require_translation = TRUE;
- gboolean require_content_rating = TRUE;
+ gboolean require_content_rating = FALSE;
+ gboolean require_name_shorter_than_summary = FALSE;
gboolean validate_license = TRUE;
gboolean ret;
- guint length_name_max = 30;
+ guint length_name_max = 60;
guint length_name_min = 3;
- guint length_summary_max = 100;
+ guint length_summary_max = 200;
guint length_summary_min = 8;
- guint number_para_max = 4;
- guint number_para_min = 2;
+ guint number_para_max = 10;
+ guint number_para_min = 1;
guint str_len;
g_autoptr(GList) keys = NULL;
g_autoptr(AsAppValidateHelper) helper = g_new0 (AsAppValidateHelper, 1);
@@ -1179,6 +1222,13 @@ as_app_validate (AsApp *app, guint32 flags, GError **error)
return NULL;
}
+ /* only for desktop and console apps */
+ if (as_app_get_kind (app) == AS_APP_KIND_DESKTOP ||
+ as_app_get_kind (app) == AS_APP_KIND_CONSOLE) {
+ require_content_rating = TRUE;
+ require_description = TRUE;
+ }
+
/* relax the requirements a bit */
if ((flags & AS_APP_VALIDATE_FLAG_RELAX) > 0) {
length_name_max = 100;
@@ -1187,7 +1237,7 @@ as_app_validate (AsApp *app, guint32 flags, GError **error)
require_content_license = FALSE;
validate_license = FALSE;
require_url = FALSE;
- number_para_max = 10;
+ number_para_max = 20;
number_para_min = 1;
require_sentence_case = FALSE;
require_translation = FALSE;
@@ -1210,6 +1260,10 @@ as_app_validate (AsApp *app, guint32 flags, GError **error)
require_project_license = TRUE;
require_content_license = TRUE;
require_appstream_spec_only = TRUE;
+ require_sentence_case = TRUE;
+ require_name_shorter_than_summary = TRUE;
+ number_para_min = 2;
+ number_para_max = 4;
}
/* addons don't need such a long description */
@@ -1299,6 +1353,43 @@ as_app_validate (AsApp *app, guint32 flags, GError **error)
}
}
+ /* categories */
+ if (as_format_get_kind (format) == AS_FORMAT_KIND_APPSTREAM &&
+ as_app_get_kind (app) == AS_APP_KIND_DESKTOP) {
+ GPtrArray *categories = as_app_get_categories (app);
+ guint nr_toplevel_cats = 0;
+ const gchar *cats[] = { "AudioVideo",
+ "Development",
+ "Education",
+ "Game",
+ "Graphics",
+ "Network",
+ "Office",
+ "Science",
+ "Settings",
+ "System",
+ "Utility",
+ NULL };
+ for (guint i = 0; i < categories->len; i++) {
+ const gchar *cat = g_ptr_array_index (categories, i);
+ for (guint j = 0; cats[j] != NULL; j++) {
+ if (g_strcmp0 (cats[j], cat) == 0)
+ nr_toplevel_cats++;
+ }
+ }
+ if (nr_toplevel_cats == 0) {
+ ai_app_validate_add (helper,
+ AS_PROBLEM_KIND_TAG_MISSING,
+ "<category> must include main categories "
+ "from the desktop entry spec");
+ } else if (nr_toplevel_cats > 3) {
+ ai_app_validate_add (helper,
+ AS_PROBLEM_KIND_TAG_MISSING,
+ "too many main <category> types: %u",
+ nr_toplevel_cats);
+ }
+ }
+
/* translation */
if (require_translation &&
as_format_get_kind (format) == AS_FORMAT_KIND_APPDATA &&
@@ -1441,12 +1532,12 @@ as_app_validate (AsApp *app, guint32 flags, GError **error)
}
/* games require a content rating */
- if (require_content_rating && as_app_has_category (app, "Game")) {
+ if (require_content_rating) {
GPtrArray *ratings = as_app_get_content_ratings (app);
if (ratings->len == 0) {
ai_app_validate_add (helper,
AS_PROBLEM_KIND_TAG_MISSING,
- "<content_rating> required for game "
+ "<content_rating> required "
"[use https://odrs.gnome.org/oars]");
}
}
@@ -1544,7 +1635,8 @@ as_app_validate (AsApp *app, guint32 flags, GError **error)
"<summary> is too long [%s] maximum is %u chars",
summary, length_summary_max);
}
- if (ai_app_validate_fullstop_ending (summary)) {
+ if (require_sentence_case &&
+ ai_app_validate_fullstop_ending (summary)) {
ai_app_validate_add (helper,
AS_PROBLEM_KIND_STYLE_INCORRECT,
"<summary> cannot end in '.' [%s]",
@@ -1568,14 +1660,21 @@ as_app_validate (AsApp *app, guint32 flags, GError **error)
AS_PROBLEM_KIND_TAG_MISSING,
"<summary> is not present");
}
- if (summary != NULL && name != NULL &&
+ if (require_name_shorter_than_summary &&
+ summary != NULL && name != NULL &&
strlen (summary) < strlen (name)) {
ai_app_validate_add (helper,
AS_PROBLEM_KIND_STYLE_INCORRECT,
"<summary> is shorter than <name>");
}
description = as_app_get_description (app, "C");
- if (description != NULL) {
+ if (description == NULL) {
+ if (require_description) {
+ ai_app_validate_add (helper,
+ AS_PROBLEM_KIND_TAG_MISSING,
+ "<description> required");
+ }
+ } else {
ret = as_app_validate_description (description,
helper,
number_para_min,
diff --git a/libappstream-glib/as-self-test.c b/libappstream-glib/as-self-test.c
index 1758bb8..b65d523 100644
--- a/libappstream-glib/as-self-test.c
+++ b/libappstream-glib/as-self-test.c
@@ -2251,22 +2251,16 @@ as_test_app_validate_file_bad_func (void)
as_test_app_validate_check (probs, AS_PROBLEM_KIND_STYLE_INCORRECT,
"<name> cannot end in '.'");
as_test_app_validate_check (probs, AS_PROBLEM_KIND_STYLE_INCORRECT,
- "<summary> cannot end in '.'");
- as_test_app_validate_check (probs, AS_PROBLEM_KIND_STYLE_INCORRECT,
"Not enough <screenshot> tags");
as_test_app_validate_check (probs, AS_PROBLEM_KIND_STYLE_INCORRECT,
"<li> is too short");
as_test_app_validate_check (probs, AS_PROBLEM_KIND_STYLE_INCORRECT,
- "<li> cannot end in '.'");
- as_test_app_validate_check (probs, AS_PROBLEM_KIND_STYLE_INCORRECT,
"<ul> cannot start a description");
as_test_app_validate_check (probs, AS_PROBLEM_KIND_STYLE_INCORRECT,
"<ul> cannot start a description");
as_test_app_validate_check (probs, AS_PROBLEM_KIND_STYLE_INCORRECT,
"<p> should not start with 'This application'");
as_test_app_validate_check (probs, AS_PROBLEM_KIND_STYLE_INCORRECT,
- "<p> does not end in '.|:|!'");
- as_test_app_validate_check (probs, AS_PROBLEM_KIND_STYLE_INCORRECT,
"<p> is too short");
as_test_app_validate_check (probs, AS_PROBLEM_KIND_STYLE_INCORRECT,
"<p> cannot contain a hyperlink");
@@ -2279,10 +2273,6 @@ as_test_app_validate_file_bad_func (void)
"<release> has no version");
as_test_app_validate_check (probs, AS_PROBLEM_KIND_ATTRIBUTE_MISSING,
"<release> has no timestamp");
- as_test_app_validate_check (probs, AS_PROBLEM_KIND_STYLE_INCORRECT,
- "<p> requires sentence case");
- as_test_app_validate_check (probs, AS_PROBLEM_KIND_STYLE_INCORRECT,
- "<li> requires sentence case");
as_test_app_validate_check (probs, AS_PROBLEM_KIND_TAG_MISSING,
"<translation> not specified");
as_test_app_validate_check (probs, AS_PROBLEM_KIND_TAG_INVALID,
@@ -2291,11 +2281,9 @@ as_test_app_validate_file_bad_func (void)
"<release> version was duplicated");
as_test_app_validate_check (probs, AS_PROBLEM_KIND_ATTRIBUTE_INVALID,
"<release> timestamp is in the future");
- as_test_app_validate_check (probs, AS_PROBLEM_KIND_TAG_MISSING,
- "<content_rating> required for game");
as_test_app_validate_check (probs, AS_PROBLEM_KIND_MARKUP_INVALID,
"<id> has invalid character");
- g_assert_cmpint (probs->len, ==, 34);
+ g_assert_cmpint (probs->len, ==, 23);
/* again, harder */
probs2 = as_app_validate (app, AS_APP_VALIDATE_FLAG_STRICT, &error);
@@ -2303,7 +2291,7 @@ as_test_app_validate_file_bad_func (void)
g_assert (probs2 != NULL);
as_test_app_validate_check (probs2, AS_PROBLEM_KIND_TAG_INVALID,
"XML data contains unknown tag");
- g_assert_cmpint (probs2->len, ==, 40);
+ g_assert_cmpint (probs2->len, ==, 35);
}
static void
@@ -2448,7 +2436,7 @@ as_test_app_validate_style_func (void)
_as_app_add_format_kind (app, AS_FORMAT_KIND_APPDATA);
as_app_set_metadata_license (app, "BSD");
as_app_set_project_license (app, "GPL-2.0+");
- as_app_set_name (app, "C", "Test app name that is very log indeed.");
+ as_app_set_name (app, "C", "Test app name that is very long indeed although Rob forced me to relax the requirements.");
as_app_set_comment (app, "C", "Awesome");
as_app_set_update_contact (app, "someone_who_cares@upstream_project.org");
@@ -2475,13 +2463,17 @@ as_test_app_validate_style_func (void)
"<summary> is too short");
as_test_app_validate_check (probs, AS_PROBLEM_KIND_STYLE_INCORRECT,
"Not enough <screenshot> tags");
- as_test_app_validate_check (probs, AS_PROBLEM_KIND_STYLE_INCORRECT,
- "<summary> is shorter than <name>");
as_test_app_validate_check (probs, AS_PROBLEM_KIND_TAG_MISSING,
"<url> is not present");
as_test_app_validate_check (probs, AS_PROBLEM_KIND_TAG_MISSING,
"<translation> not specified");
- g_assert_cmpint (probs->len, ==, 11);
+ as_test_app_validate_check (probs, AS_PROBLEM_KIND_TAG_MISSING,
+ "<content_rating> required");
+ as_test_app_validate_check (probs, AS_PROBLEM_KIND_TAG_MISSING,
+ "<release> required");
+ as_test_app_validate_check (probs, AS_PROBLEM_KIND_TAG_MISSING,
+ "<description> required");
+ g_assert_cmpint (probs->len, ==, 13);
}
static void