diff --git a/data/flatpak-manifest.schema.json b/data/flatpak-manifest.schema.json index 6e24a423..6172c1fd 100644 --- a/data/flatpak-manifest.schema.json +++ b/data/flatpak-manifest.schema.json @@ -470,6 +470,14 @@ "type": "string" } }, + "license-files": { + "description": "Array of paths to LICENSE files of the module", + "type": "array", + "items": { + "description": "LICENSE file of the module", + "type": "string" + } + }, "modules": { "description": "An array of objects specifying the modules to be built in order. String members in the array are interpreted as the name of a separate json or yaml file that contains a module.", "type": "array", diff --git a/doc/flatpak-manifest.xml b/doc/flatpak-manifest.xml index 8020ca06..f1be0d0f 100644 --- a/doc/flatpak-manifest.xml +++ b/doc/flatpak-manifest.xml @@ -625,6 +625,10 @@ (array of strings) Array of commands to run during the tests. + + (array of strings) + Array of paths to LICENSE files of the module. + (array of objects or strings) An array of objects specifying nested modules to be built before this one. diff --git a/src/builder-manifest.c b/src/builder-manifest.c index 74d1074f..bcea7b3d 100644 --- a/src/builder-manifest.c +++ b/src/builder-manifest.c @@ -2101,7 +2101,7 @@ builder_manifest_build_shell (BuilderManifest *self, if (found == NULL) return flatpak_fail (error, "Can't find module %s", modulename); - if (!builder_module_build (found, NULL, context, TRUE, error)) + if (!builder_module_build (found, self->id, NULL, context, TRUE, error)) return FALSE; return TRUE; @@ -2150,7 +2150,7 @@ builder_manifest_build (BuilderManifest *self, return FALSE; if (!builder_context_enable_rofiles (context, error)) return FALSE; - if (!builder_module_build (m, cache, context, FALSE, error)) + if (!builder_module_build (m, self->id, cache, context, FALSE, error)) return FALSE; if (!builder_context_disable_rofiles (context, error)) return FALSE; diff --git a/src/builder-module.c b/src/builder-module.c index a3cc9f8a..d079f884 100644 --- a/src/builder-module.c +++ b/src/builder-module.c @@ -73,6 +73,7 @@ struct BuilderModule GList *modules; char **build_commands; char **test_commands; + char **license_files; }; typedef struct @@ -117,6 +118,7 @@ enum { PROP_MODULES, PROP_BUILD_COMMANDS, PROP_TEST_COMMANDS, + PROP_LICENSE_FILES, LAST_PROP }; @@ -162,6 +164,7 @@ builder_module_finalize (GObject *object) g_list_free_full (self->modules, g_object_unref); g_strfreev (self->build_commands); g_strfreev (self->test_commands); + g_strfreev (self->license_files); if (self->changes) g_ptr_array_unref (self->changes); @@ -299,6 +302,10 @@ builder_module_get_property (GObject *object, g_value_set_boolean (value, self->run_tests); break; + case PROP_LICENSE_FILES: + g_value_set_boxed (value, self->license_files); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } @@ -476,6 +483,12 @@ builder_module_set_property (GObject *object, self->run_tests = g_value_get_boolean (value); break; + case PROP_LICENSE_FILES: + tmp = self->license_files; + self->license_files = g_strdupv (g_value_get_boxed (value)); + g_strfreev (tmp); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } @@ -701,6 +714,14 @@ builder_module_class_init (BuilderModuleClass *klass) "", FALSE, G_PARAM_READWRITE)); + g_object_class_install_property (object_class, + PROP_LICENSE_FILES, + g_param_spec_boxed ("license-files", + "", + "", + G_TYPE_STRV, + G_PARAM_READWRITE)); + } static void @@ -1502,12 +1523,223 @@ find_file_with_extension (GFile *dir, } static gboolean -builder_module_build_helper (BuilderModule *self, - BuilderCache *cache, - BuilderContext *context, - GFile *source_dir, - gboolean run_shell, - GError **error) +find_defined_license_files (GStrv license_files, + GFile *source_dir, + GPtrArray *files, + GError **error) +{ + for (size_t i = 0; license_files[i] != NULL; i++) + { + const char *license_file_path = license_files[i]; + g_autoptr(GFile) license_file = + g_file_resolve_relative_path (source_dir, license_file_path); + g_autofree char *rel_path = NULL; + GFileType file_type; + + rel_path = g_file_get_relative_path (source_dir, license_file); + if (rel_path == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "License path outside the source directory"); + return FALSE; + } + + if (!g_file_query_exists (license_file, NULL)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "License file \"%s\" does not exist", rel_path); + return FALSE; + } + + file_type = g_file_query_file_type (license_file, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + NULL); + if (file_type != G_FILE_TYPE_REGULAR) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "License file \"%s\" is not a regular file", rel_path); + return FALSE; + } + + g_ptr_array_add (files, g_steal_pointer (&license_file)); + } + + return TRUE; +} + +static const char *default_licence_file_patterns[] = { + "COPYING", + "COPYRIGHT", + "Copyright", + "copyright", + "LICEN", + "Licen", + "licen", + NULL +}; + +static gboolean +find_default_license_files (BuilderModule *self, + GFile *source_dir, + GPtrArray *files, + GError **error) +{ + g_autoptr(GFileEnumerator) dir_enum = NULL; + GFileInfo *next; + g_autoptr(GError) my_error = NULL; + + dir_enum = g_file_enumerate_children (source_dir, "standard::name,standard::type", + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + NULL, error); + if (!dir_enum) + return FALSE; + + while ((next = g_file_enumerator_next_file (dir_enum, NULL, &my_error))) + { + g_autoptr(GFileInfo) child_info = next; + const char *name = g_file_info_get_name (child_info); + + if (g_file_info_get_file_type (child_info) != G_FILE_TYPE_REGULAR) + continue; + + for (size_t i = 0; default_licence_file_patterns[i] != NULL; i++) + { + const char *pattern = default_licence_file_patterns[i]; + g_autoptr(GFile) license_file = NULL; + + if (!g_str_has_prefix (name, pattern)) + continue; + + license_file = g_file_get_child (source_dir, name); + g_ptr_array_add (files, g_steal_pointer (&license_file)); + break; + } + } + + if (my_error != NULL) + { + g_propagate_error (error, g_steal_pointer (&my_error)); + return FALSE; + } + + return TRUE; +} + +static gboolean +find_license_files (BuilderModule *self, + GFile *source_dir, + GPtrArray **license_files_out, + GError **error) +{ + g_autoptr(GPtrArray) license_files = + g_ptr_array_new_with_free_func (g_object_unref); + + if (self->license_files) + { + if (!find_defined_license_files (self->license_files, + source_dir, + license_files, + error)) + return FALSE; + } + else + { + if (!find_default_license_files (self, + source_dir, + license_files, + error)) + return FALSE; + } + + if (license_files_out) + *license_files_out = g_steal_pointer (&license_files); + return TRUE; +} + +static GFile * +get_license_dst_file (GFile *license_dir, + GFile *license_file) +{ + g_autofree char *license_file_name = g_file_get_basename (license_file); + const char *candidate = license_file_name; + int i = 1; + + while (TRUE) + { + g_autoptr(GFile) dst = NULL; + g_autofree char *owned_candidate = NULL; + + if (!candidate) + { + owned_candidate = g_strdup_printf ("LICENSE_%u", i); + candidate = owned_candidate; + i++; + } + + dst = g_file_get_child (license_dir, candidate); + if (!g_file_query_exists (dst, NULL)) + return g_steal_pointer (&dst); + + candidate = NULL; + } +} + +static gboolean +builder_module_install_licenses (BuilderModule *self, + const char *id, + GFile *source_dir, + GFile *app_dir, + GError **error) +{ + g_autoptr(GPtrArray) license_files = NULL; + g_autoptr(GFile) license_dir = NULL; + + if (!find_license_files (self, source_dir, &license_files, error)) + return FALSE; + + if (license_files->len == 0) + return TRUE; + + license_dir = g_file_new_build_filename (g_file_get_path (app_dir), + "files/share/licenses", + id, + self->name, + NULL); + if (!flatpak_mkdir_p (license_dir, NULL, error)) + return FALSE; + + for (size_t i = 0; i < license_files->len; i++) + { + GFile *license_file = license_files->pdata[i]; + g_autoptr(GFile) dst = NULL; + g_autofree char *rel_path = NULL; + + rel_path = g_file_get_relative_path (source_dir, license_file); + g_assert (rel_path != NULL); + + g_print ("Recording license file %s\n", rel_path); + + dst = get_license_dst_file (license_dir, license_file); + + if (!g_file_copy (license_file, dst, + G_FILE_COPY_OVERWRITE, + NULL, + NULL, NULL, + error)) + return FALSE; + } + + return TRUE; +} + +static gboolean +builder_module_build_helper (BuilderModule *self, + const char *id, + BuilderCache *cache, + BuilderContext *context, + GFile *source_dir, + gboolean run_shell, + GError **error) { GFile *app_dir = builder_context_get_app_dir (context); g_autofree char *make_j = NULL; @@ -1777,10 +2009,10 @@ builder_module_build_helper (BuilderModule *self, libdir = builder_options_get_libdir (self->build_options, context); if (meson) - { - /* Meson's setup command is now meson setup */ + { + /* Meson's setup command is now meson setup */ g_ptr_array_add (configure_args_arr, g_strdup ("setup")); - } + } if (cmake || cmake_ninja) { @@ -1918,10 +2150,6 @@ builder_module_build_helper (BuilderModule *self, return FALSE; } - /* Post installation scripts */ - - builder_set_term_title (_("Post-Install %s"), self->name); - if (builder_context_get_separate_locales (context)) { g_autoptr(GFile) root_dir = NULL; @@ -1938,6 +2166,13 @@ builder_module_build_helper (BuilderModule *self, } } + if (!builder_module_install_licenses (self, id, source_dir, app_dir, error)) + return FALSE; + + /* Post installation scripts */ + + builder_set_term_title (_("Post-Install %s"), self->name); + if (self->post_install) { for (i = 0; self->post_install[i] != NULL; i++) @@ -2009,11 +2244,12 @@ builder_module_build_helper (BuilderModule *self, } gboolean -builder_module_build (BuilderModule *self, - BuilderCache *cache, - BuilderContext *context, - gboolean run_shell, - GError **error) +builder_module_build (BuilderModule *self, + const char *id, + BuilderCache *cache, + BuilderContext *context, + gboolean run_shell, + GError **error) { g_autoptr(GFile) source_dir = NULL; g_autoptr(GFile) build_parent_dir = NULL; @@ -2051,7 +2287,7 @@ builder_module_build (BuilderModule *self, return FALSE; } - res = builder_module_build_helper (self, cache, context, source_dir, run_shell, error); + res = builder_module_build_helper (self, id, cache, context, source_dir, run_shell, error); /* Clean up build dir */ diff --git a/src/builder-module.h b/src/builder-module.h index f621db29..e7cd474d 100644 --- a/src/builder-module.h +++ b/src/builder-module.h @@ -77,11 +77,12 @@ gboolean builder_module_ensure_writable (BuilderModule *self, BuilderCache *cache, BuilderContext *context, GError **error); -gboolean builder_module_build (BuilderModule *self, - BuilderCache *cache, - BuilderContext *context, - gboolean run_shell, - GError **error); +gboolean builder_module_build (BuilderModule *self, + const char *id, + BuilderCache *cache, + BuilderContext *context, + gboolean run_shell, + GError **error); gboolean builder_module_update (BuilderModule *self, BuilderContext *context, GError **error); diff --git a/tests/test-builder.sh b/tests/test-builder.sh index cd9d8ba0..1ca2ac3e 100755 --- a/tests/test-builder.sh +++ b/tests/test-builder.sh @@ -61,6 +61,7 @@ cp $(dirname $0)/module2.yaml include1/include2/ cp $(dirname $0)/source2.json include1/include2/ cp $(dirname $0)/data2 include1/include2/ cp $(dirname $0)/data2.patch include1/include2/ +echo "MY LICENSE" > ./LICENSE for MANIFEST in test.json test.yaml test-rename.json test-rename-appdata.json ; do echo "building manifest $MANIFEST" >&2 @@ -96,6 +97,8 @@ for MANIFEST in test.json test.yaml test-rename.json test-rename-appdata.json ; ${FLATPAK} build appdir /app/bin/hello2.sh > hello_out2 assert_file_has_content hello_out2 '^Hello world2, from a sandbox$' + assert_file_has_content appdir/files/share/licenses/org.test.Hello2/test/LICENSE '^MY LICENSE$' + echo "ok build" done diff --git a/tests/test-rename-appdata.json b/tests/test-rename-appdata.json index 9169f312..83650d8a 100644 --- a/tests/test-rename-appdata.json +++ b/tests/test-rename-appdata.json @@ -73,6 +73,10 @@ "type": "file", "path": "Hello.xml" }, + { + "type": "file", + "path": "LICENSE" + }, { "type": "script", "dest-filename": "hello2.sh", diff --git a/tests/test-rename.json b/tests/test-rename.json index c13b85fa..6b171982 100644 --- a/tests/test-rename.json +++ b/tests/test-rename.json @@ -74,6 +74,10 @@ "type": "file", "path": "Hello.xml" }, + { + "type": "file", + "path": "LICENSE" + }, { "type": "script", "dest-filename": "hello2.sh", diff --git a/tests/test.json b/tests/test.json index a1d128b9..532a13d8 100644 --- a/tests/test.json +++ b/tests/test.json @@ -71,6 +71,10 @@ "type": "file", "path": "org.test.Hello.xml" }, + { + "type": "file", + "path": "LICENSE" + }, { "type": "shell", "commands": [ diff --git a/tests/test.yaml b/tests/test.yaml index 699d58cf..734523c8 100644 --- a/tests/test.yaml +++ b/tests/test.yaml @@ -36,6 +36,7 @@ modules: make-args: [BAR=2] make-install-args: [BAR=3] build-commands: ['echo foo > /app/out'] + license-files: ['mytest/LICENSE'] sources: - type: file path: test-configure @@ -52,6 +53,9 @@ modules: path: org.test.Hello.appdata.xml - type: file path: org.test.Hello.xml + - type: file + path: LICENSE + dest: mytest - type: shell commands: - mkdir /app/cleanup/