Skip to content

Commit 1b22657

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 after 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 1b22657

File tree

12 files changed

+245
-1
lines changed

12 files changed

+245
-1
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: 11 additions & 1 deletion
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, e.g. '755' or '0755'. Requires install-dir. If absent, the original permissions of the file are preserved.",
1029+
"type": "string",
1030+
"pattern": "^[0-7]{3,4}$"
10221031
}
10231032
},
10241033
"patternProperties": {
@@ -1037,7 +1046,8 @@
10371046
{ "required": [ "sha1" ] },
10381047
{ "required": [ "md5" ] }
10391048
]
1040-
}
1049+
},
1050+
"install-mode": { "required": [ "install-dir" ] }
10411051
},
10421052
"additionalProperties": false
10431053
},

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> or <literal>755</literal>.
748+
This option requires <option>install-dir</option> to be set. If
749+
this is not 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: 136 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,87 @@ 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+
if (!g_file_has_prefix (install_dir, dest_dir))
631+
return flatpak_fail (error, "install-dir cannot escape the install prefix");
632+
633+
src = g_file_get_child (build_dir, filename);
634+
dst = g_file_get_child (install_dir, filename);
635+
dst_path = g_file_get_path (dst);
636+
637+
if (!flatpak_file_query_exists_nofollow (src))
638+
return flatpak_fail (error, "Source file '%s' not found", filename);
639+
640+
if (!flatpak_mkdir_p (install_dir, NULL, error))
641+
return FALSE;
642+
643+
if (flatpak_file_query_exists_nofollow (dst))
644+
g_printerr ("Warning: %s already exists, skipping install\n", dst_path);
645+
else
646+
{
647+
if (!g_file_copy (src, dst, G_FILE_COPY_NOFOLLOW_SYMLINKS,
648+
NULL, NULL, NULL, error))
649+
return FALSE;
650+
651+
if (self->install_mode)
652+
{
653+
guint32 mode = (guint32) g_ascii_strtoull (self->install_mode, NULL, 8);
654+
655+
if (!g_file_set_attribute (dst,
656+
G_FILE_ATTRIBUTE_UNIX_MODE,
657+
G_FILE_ATTRIBUTE_TYPE_UINT32,
658+
&mode,
659+
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
660+
NULL,
661+
error))
662+
return FALSE;
663+
}
664+
665+
if (self->install_mode)
666+
g_print ("Installed %s to %s with permissions %s\n", filename, dst_path, self->install_mode);
667+
else
668+
g_print ("Installed %s to %s with default permissions\n", filename, dst_path);
669+
}
670+
671+
return TRUE;
672+
}
673+
555674
static gboolean
556675
builder_source_file_bundle (BuilderSource *source,
557676
BuilderContext *context,
@@ -655,6 +774,8 @@ builder_source_file_checksum (BuilderSource *source,
655774
builder_cache_checksum_compat_str (cache, self->sha512);
656775
builder_cache_checksum_str (cache, self->dest_filename);
657776
builder_cache_checksum_compat_strv (cache, self->mirror_urls);
777+
builder_cache_checksum_compat_str (cache, self->install_dir);
778+
builder_cache_checksum_compat_str (cache, self->install_mode);
658779
}
659780

660781
static void
@@ -670,6 +791,7 @@ builder_source_file_class_init (BuilderSourceFileClass *klass)
670791
source_class->show_deps = builder_source_file_show_deps;
671792
source_class->download = builder_source_file_download;
672793
source_class->extract = builder_source_file_extract;
794+
source_class->install = builder_source_file_install;
673795
source_class->bundle = builder_source_file_bundle;
674796
source_class->update = builder_source_file_update;
675797
source_class->checksum = builder_source_file_checksum;
@@ -747,6 +869,20 @@ builder_source_file_class_init (BuilderSourceFileClass *klass)
747869
"",
748870
FALSE,
749871
G_PARAM_READWRITE));
872+
g_object_class_install_property (object_class,
873+
PROP_INSTALL_DIR,
874+
g_param_spec_string ("install-dir",
875+
"",
876+
"",
877+
NULL,
878+
G_PARAM_READWRITE));
879+
g_object_class_install_property (object_class,
880+
PROP_INSTALL_MODE,
881+
g_param_spec_string ("install-mode",
882+
"",
883+
"",
884+
NULL,
885+
G_PARAM_READWRITE));
750886
}
751887

752888
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": [

0 commit comments

Comments
 (0)