Skip to content

Commit 31d9506

Browse files
committed
Support installing files from file sources
This adds a new install stage to BuilderSourceFile that copies files to a specified directory within the appdir just before post-install for sources that have opted into using it (currently only the file source and no-op for everyone else). Additionally, the file source now has a install-dir and install-mode property. The install-dir property assumes the install location is relative to FLATPAK_DEST. If the destination file already exists, the install is skipped with a warning. If install-mode is specified as a valid octal permission string, the permissions are applied on the file otherwise the file retains its original permissions. Fixes: #54
1 parent d4312b7 commit 31d9506

File tree

12 files changed

+242
-0
lines changed

12 files changed

+242
-0
lines changed

NEWS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ TBD
88
* Prevent writing duplicate groups to metadata file
99
* Disable all filesystem access in flatpak-builder --run sandbox
1010
* Add ability to set custom fusermount path
11+
* Support installing files from file sources
1112

1213
Changes in 1.4.6
1314
================

data/flatpak-manifest.schema.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1019,6 +1019,15 @@
10191019
"dest-filename": {
10201020
"description": "Filename to for the downloaded file, defaults to the basename of url.",
10211021
"type": "string"
1022+
},
1023+
"install-dir": {
1024+
"description": "Directory relative to FLATPAK_DEST where the file will be installed. The directory is created if absent and the install is skipped with a warning if the file exists in the target path.",
1025+
"type": "string"
1026+
},
1027+
"install-mode": {
1028+
"description": "Optional octal permissions applied to the installed file. Requires install-dir. If this is absent, the original permissions of the file are preserved.",
1029+
"type": "string",
1030+
"pattern": "^[0-7]{3,4}$"
10221031
}
10231032
},
10241033
"patternProperties": {

doc/flatpak-manifest.xml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -729,6 +729,28 @@
729729
<term><option>dest-filename</option> (string)</term>
730730
<listitem><para>Filename to for the downloaded file, defaults to the basename of url.</para></listitem>
731731
</varlistentry>
732+
<varlistentry>
733+
<term><option>install-dir</option> (string)</term>
734+
<listitem>
735+
<para>
736+
Install the file into this directory relative to <literal>FLATPAK_DEST</literal>.
737+
The directory will be created if it doesn't exist and the install will be
738+
skipped with a warning if the file already exists in the target path.
739+
</para>
740+
</listitem>
741+
</varlistentry>
742+
<varlistentry>
743+
<term><option>install-mode</option> (string)</term>
744+
<listitem>
745+
<para>
746+
Optional octal permissions to apply to the installed file,
747+
such as <literal>0755</literal>. This option requires
748+
<option>install-dir</option> to be set. If this is not
749+
set the original permissions on the file will be
750+
preserved.
751+
</para>
752+
</listitem>
753+
</varlistentry>
732754
</variablelist>
733755
</refsect3>
734756
<refsect3>

src/builder-module.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2211,6 +2211,20 @@ builder_module_build_helper (BuilderModule *self,
22112211
}
22122212
}
22132213

2214+
for (GList *l = self->sources; l != NULL; l = l->next)
2215+
{
2216+
BuilderSource *source = l->data;
2217+
2218+
if (!builder_source_is_enabled (source, context))
2219+
continue;
2220+
2221+
if (!builder_source_install (source, source_dir, context, error))
2222+
{
2223+
g_prefix_error (error, "module %s: ", self->name);
2224+
return FALSE;
2225+
}
2226+
}
2227+
22142228
/* Run unit tests */
22152229

22162230
if (self->run_tests && builder_context_get_run_tests (context))

src/builder-source-file.c

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ struct BuilderSourceFile
4646
char *dest_filename;
4747
char *http_referer;
4848
gboolean disable_http_decompression;
49+
char *install_dir;
50+
char *install_mode;
4951
};
5052

5153
typedef struct
@@ -67,6 +69,8 @@ enum {
6769
PROP_MIRROR_URLS,
6870
PROP_HTTP_REFERER,
6971
PROP_DISABLE_HTTP_DECOMPRESSION,
72+
PROP_INSTALL_DIR,
73+
PROP_INSTALL_MODE,
7074
LAST_PROP
7175
};
7276

@@ -84,6 +88,8 @@ builder_source_file_finalize (GObject *object)
8488
g_free (self->dest_filename);
8589
g_free (self->http_referer);
8690
g_strfreev (self->mirror_urls);
91+
g_free (self->install_dir);
92+
g_free (self->install_mode);
8793

8894
G_OBJECT_CLASS (builder_source_file_parent_class)->finalize (object);
8995
}
@@ -138,6 +144,14 @@ builder_source_file_get_property (GObject *object,
138144
g_value_set_boolean (value, self->disable_http_decompression);
139145
break;
140146

147+
case PROP_INSTALL_DIR:
148+
g_value_set_string (value, self->install_dir);
149+
break;
150+
151+
case PROP_INSTALL_MODE:
152+
g_value_set_string (value, self->install_mode);
153+
break;
154+
141155
default:
142156
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
143157
}
@@ -216,6 +230,16 @@ builder_source_file_set_property (GObject *object,
216230
self->disable_http_decompression = g_value_get_boolean (value);
217231
break;
218232

233+
case PROP_INSTALL_DIR:
234+
g_free (self->install_dir);
235+
self->install_dir = g_value_dup_string (value);
236+
break;
237+
238+
case PROP_INSTALL_MODE:
239+
g_free (self->install_mode);
240+
self->install_mode = g_value_dup_string (value);
241+
break;
242+
219243
default:
220244
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
221245
}
@@ -231,6 +255,20 @@ builder_source_file_validate (BuilderSource *source,
231255
strchr (self->dest_filename, '/') != NULL)
232256
return flatpak_fail (error, "No slashes allowed in dest-filename, use dest property for directory");
233257

258+
if (self->install_dir != NULL && g_path_is_absolute (self->install_dir))
259+
return flatpak_fail (error, "install-dir must be relative to FLATPAK_DEST");
260+
261+
if (self->install_mode != NULL)
262+
{
263+
char *end;
264+
guint64 mode = g_ascii_strtoull (self->install_mode, &end, 8);
265+
if (*end != '\0' || mode > 07777)
266+
return flatpak_fail (error, "install-mode must be a valid octal permission string");
267+
}
268+
269+
if (self->install_mode != NULL && self->install_dir == NULL)
270+
return flatpak_fail (error, "install-mode requires install-dir to be set");
271+
234272
return TRUE;
235273
}
236274

@@ -552,6 +590,86 @@ builder_source_file_extract (BuilderSource *source,
552590
return TRUE;
553591
}
554592

593+
static gboolean
594+
builder_source_file_install (BuilderSource *source,
595+
GFile *build_dir,
596+
BuilderContext *context,
597+
GError **error)
598+
{
599+
BuilderSourceFile *self = BUILDER_SOURCE_FILE (source);
600+
const char *filename;
601+
g_autofree char *basename = NULL;
602+
g_autofree char *dst_path = NULL;
603+
g_autoptr(GFile) src = NULL;
604+
g_autoptr(GFile) install_dir = NULL;
605+
g_autoptr(GFile) dst = NULL;
606+
g_autoptr(GFile) dest_dir = NULL;
607+
GFile *app_dir = NULL;
608+
609+
if (self->install_dir == NULL || self->install_dir[0] == '\0')
610+
return TRUE;
611+
612+
if (self->dest_filename)
613+
filename = self->dest_filename;
614+
else
615+
{
616+
gboolean is_local, is_inline;
617+
g_autoptr(GFile) source_file = get_source_file (self, context, &is_local, &is_inline, error);
618+
if (source_file == NULL)
619+
return FALSE;
620+
basename = g_file_get_basename (source_file);
621+
filename = basename;
622+
}
623+
624+
app_dir = builder_context_get_app_dir (context);
625+
dest_dir = builder_context_get_build_runtime (context)
626+
? g_file_get_child (app_dir, "usr")
627+
: g_file_get_child (app_dir, "files");
628+
install_dir = g_file_resolve_relative_path (dest_dir, self->install_dir);
629+
630+
src = g_file_get_child (build_dir, filename);
631+
dst = g_file_get_child (install_dir, filename);
632+
dst_path = g_file_get_path (dst);
633+
634+
if (!flatpak_file_query_exists_nofollow (src))
635+
return flatpak_fail (error, "Source file '%s' not found", filename);
636+
637+
if (!flatpak_mkdir_p (install_dir, NULL, error))
638+
return FALSE;
639+
640+
if (flatpak_file_query_exists_nofollow (dst))
641+
{
642+
g_printerr ("Warning: %s already exists, skipping install\n", dst_path);
643+
}
644+
else
645+
{
646+
if (!g_file_copy (src, dst, G_FILE_COPY_NONE,
647+
NULL, NULL, NULL, error))
648+
return FALSE;
649+
650+
if (self->install_mode)
651+
{
652+
guint32 mode = (guint32) g_ascii_strtoull (self->install_mode, NULL, 8);
653+
654+
if (!g_file_set_attribute (dst,
655+
G_FILE_ATTRIBUTE_UNIX_MODE,
656+
G_FILE_ATTRIBUTE_TYPE_UINT32,
657+
&mode,
658+
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
659+
NULL,
660+
error))
661+
return FALSE;
662+
}
663+
664+
if (self->install_mode)
665+
g_print ("Installed %s to %s with permissions %s\n", filename, dst_path, self->install_mode);
666+
else
667+
g_print ("Installed %s to %s with default permissions\n", filename, dst_path);
668+
}
669+
670+
return TRUE;
671+
}
672+
555673
static gboolean
556674
builder_source_file_bundle (BuilderSource *source,
557675
BuilderContext *context,
@@ -655,6 +773,8 @@ builder_source_file_checksum (BuilderSource *source,
655773
builder_cache_checksum_compat_str (cache, self->sha512);
656774
builder_cache_checksum_str (cache, self->dest_filename);
657775
builder_cache_checksum_compat_strv (cache, self->mirror_urls);
776+
builder_cache_checksum_compat_str (cache, self->install_dir);
777+
builder_cache_checksum_compat_str (cache, self->install_mode);
658778
}
659779

660780
static void
@@ -670,6 +790,7 @@ builder_source_file_class_init (BuilderSourceFileClass *klass)
670790
source_class->show_deps = builder_source_file_show_deps;
671791
source_class->download = builder_source_file_download;
672792
source_class->extract = builder_source_file_extract;
793+
source_class->install = builder_source_file_install;
673794
source_class->bundle = builder_source_file_bundle;
674795
source_class->update = builder_source_file_update;
675796
source_class->checksum = builder_source_file_checksum;
@@ -747,6 +868,20 @@ builder_source_file_class_init (BuilderSourceFileClass *klass)
747868
"",
748869
FALSE,
749870
G_PARAM_READWRITE));
871+
g_object_class_install_property (object_class,
872+
PROP_INSTALL_DIR,
873+
g_param_spec_string ("install-dir",
874+
"",
875+
"",
876+
NULL,
877+
G_PARAM_READWRITE));
878+
g_object_class_install_property (object_class,
879+
PROP_INSTALL_MODE,
880+
g_param_spec_string ("install-mode",
881+
"",
882+
"",
883+
NULL,
884+
G_PARAM_READWRITE));
750885
}
751886

752887
static void

src/builder-source.c

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,15 @@ builder_source_real_extract (BuilderSource *self,
164164
return FALSE;
165165
}
166166

167+
static gboolean
168+
builder_source_real_install (BuilderSource *self,
169+
GFile *build_dir,
170+
BuilderContext *context,
171+
GError **error)
172+
{
173+
return TRUE;
174+
}
175+
167176
static gboolean
168177
builder_source_real_bundle (BuilderSource *self,
169178
BuilderContext *context,
@@ -194,6 +203,7 @@ builder_source_class_init (BuilderSourceClass *klass)
194203
klass->show_deps = builder_source_real_show_deps;
195204
klass->download = builder_source_real_download;
196205
klass->extract = builder_source_real_extract;
206+
klass->install = builder_source_real_install;
197207
klass->bundle = builder_source_real_bundle;
198208
klass->update = builder_source_real_update;
199209

@@ -415,6 +425,19 @@ builder_source_extract (BuilderSource *self,
415425
return class->extract (self, real_dest, source_dir, build_options, context, error);
416426
}
417427

428+
gboolean
429+
builder_source_install (BuilderSource *self,
430+
GFile *build_dir,
431+
BuilderContext *context,
432+
GError **error)
433+
{
434+
BuilderSourceClass *class;
435+
436+
class = BUILDER_SOURCE_GET_CLASS (self);
437+
438+
return class->install (self, build_dir, context, error);
439+
}
440+
418441
gboolean
419442
builder_source_bundle (BuilderSource *self,
420443
BuilderContext *context,

src/builder-source.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ typedef struct
6363
BuilderOptions *build_options,
6464
BuilderContext *context,
6565
GError **error);
66+
gboolean (* install)(BuilderSource *self,
67+
GFile *build_dir,
68+
BuilderContext *context,
69+
GError **error);
6670
gboolean (* bundle)(BuilderSource *self,
6771
BuilderContext *context,
6872
GError **error);
@@ -99,6 +103,10 @@ gboolean builder_source_extract (BuilderSource *self,
99103
BuilderOptions *build_options,
100104
BuilderContext *context,
101105
GError **error);
106+
gboolean builder_source_install (BuilderSource *self,
107+
GFile *build_dir,
108+
BuilderContext *context,
109+
GError **error);
102110
gboolean builder_source_bundle (BuilderSource *self,
103111
BuilderContext *context,
104112
GError **error);

tests/test-builder.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ cp $(dirname $0)/source2.json include1/include2/
6969
cp $(dirname $0)/data2 include1/include2/
7070
cp $(dirname $0)/data2.patch include1/include2/
7171
echo "MY LICENSE" > ./LICENSE
72+
touch ./foobar
7273

7374
for MANIFEST in test.json test.yaml test-rename.json test-rename-appdata.json ; do
7475
echo "building manifest $MANIFEST" >&2
@@ -106,6 +107,13 @@ for MANIFEST in test.json test.yaml test-rename.json test-rename-appdata.json ;
106107

107108
assert_file_has_content appdir/files/share/licenses/org.test.Hello2/test/LICENSE '^MY LICENSE$'
108109

110+
assert_has_file appdir/files/share/icons/hicolor/64x64/apps/foobar
111+
PERMS=$(stat -c "%a" appdir/files/share/icons/hicolor/64x64/apps/foobar)
112+
if [ "$PERMS" != "755" ]; then
113+
echo "not ok install-dir+install-mode: expected 755, got $PERMS"
114+
exit 1
115+
fi
116+
109117
echo "ok build"
110118
done
111119

tests/test-rename-appdata.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,12 @@
8282
"dest-filename": "hello2.sh",
8383
"commands": [ "echo \"Hello world2, from a sandbox\"" ]
8484
},
85+
{
86+
"type": "file",
87+
"path": "foobar",
88+
"install-dir": "share/icons/hicolor/64x64/apps",
89+
"install-mode": "0755"
90+
},
8591
{
8692
"type": "shell",
8793
"commands": [

tests/test-rename.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,12 @@
8383
"dest-filename": "hello2.sh",
8484
"commands": [ "echo \"Hello world2, from a sandbox\"" ]
8585
},
86+
{
87+
"type": "file",
88+
"path": "foobar",
89+
"install-dir": "share/icons/hicolor/64x64/apps",
90+
"install-mode": "0755"
91+
},
8692
{
8793
"type": "shell",
8894
"commands": [

0 commit comments

Comments
 (0)