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/