flatpaker 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
flatpaker/__init__.py ADDED
@@ -0,0 +1,15 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright © 2024 Dylan Baker
3
+
4
+ """Utilities to convert various kinds of native binaries into flatpaks.
5
+
6
+ Current support includes Ren'Py and some versions of RPGMaker (MV and MZ, when
7
+ they have Linux packages).
8
+
9
+ Attempts to make some optimizations of the packages, such as recompiling
10
+ bytecode, and patches various games to honor XDG variables, so that flatpaks
11
+ don't need access to the user home directory. This increases security and is
12
+ generally beneficial for end users.
13
+ """
14
+
15
+ __version__ = "0.0.1"
flatpaker/config.py ADDED
@@ -0,0 +1,38 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright © 2024 Dylan Baker
3
+
4
+ from __future__ import annotations
5
+ import os
6
+ import typing
7
+
8
+ from flatpaker.util import tomllib
9
+
10
+ if typing.TYPE_CHECKING:
11
+
12
+ Common = typing.TypedDict(
13
+ 'Common',
14
+ {
15
+ 'gpg-key': str,
16
+ 'repo': str,
17
+ },
18
+ total=False,
19
+ )
20
+
21
+ class Config(typing.TypedDict):
22
+ common: Common
23
+
24
+
25
+ def load_config() -> Config:
26
+ root = os.environ.get('XDG_CONFIG_HOME', os.path.expanduser('~/.config'))
27
+ conf = os.path.join(root, 'flatpaker', 'config.toml')
28
+ raw: typing.Dict[str, typing.Any]
29
+ if os.path.exists(conf):
30
+ with open(conf, 'rb') as f:
31
+ raw = tomllib.load(f)
32
+ assert isinstance(raw, dict), 'invalid config file?'
33
+ else:
34
+ raw = {}
35
+
36
+ if 'common' not in raw:
37
+ raw['common'] = {}
38
+ return typing.cast('Config', raw)
flatpaker/entry.py ADDED
@@ -0,0 +1,58 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright © 2022-2024 Dylan Baker
3
+
4
+ from __future__ import annotations
5
+ import argparse
6
+ import importlib
7
+ import pathlib
8
+ import typing
9
+
10
+ import flatpaker.config
11
+ import flatpaker.util
12
+
13
+ if typing.TYPE_CHECKING:
14
+ JsonWriterImpl = typing.Callable[[flatpaker.util.Description, pathlib.Path, str, pathlib.Path, pathlib.Path], None]
15
+
16
+ class ImplMod(typing.Protocol):
17
+
18
+ write_rules: JsonWriterImpl
19
+
20
+
21
+ def select_impl(name: typing.Literal['renpy', 'rpgmaker']) -> JsonWriterImpl:
22
+ mod = typing.cast('ImplMod', importlib.import_module(name, 'flatpaker.impl'))
23
+ assert hasattr(mod, 'write_rules'), 'should be good enough'
24
+ return mod.write_rules
25
+
26
+
27
+ def main() -> None:
28
+ config = flatpaker.config.load_config()
29
+ parser = argparse.ArgumentParser()
30
+ parser.add_argument('description', help="A Toml description file")
31
+ parser.add_argument(
32
+ '--repo',
33
+ default=config['common'].get('repo', 'repo'),
34
+ action='store',
35
+ help='a flatpak repo to put the result in')
36
+ parser.add_argument(
37
+ '--gpg',
38
+ default=config['common'].get('gpg-key'),
39
+ action='store',
40
+ help='A GPG key to sign the output to when writing to a repo')
41
+ parser.add_argument('--export', action='store_true', help='Export to the provided repo')
42
+ parser.add_argument('--install', action='store_true', help="Install for the user (useful for testing)")
43
+ parser.add_argument('--no-cleanup', action='store_false', dest='cleanup', help="don't delete the temporary directory")
44
+ args = typing.cast('flatpaker.util.Arguments', parser.parse_args())
45
+ # Don't use type for this because it swallows up the exception
46
+ description = flatpaker.util.load_description(args.description)
47
+
48
+ # TODO: This could be common
49
+ appid = f"{description['common']['reverse_url']}.{flatpaker.util.sanitize_name(description['common']['name'])}"
50
+
51
+ write_build_rules = select_impl(description['common']['engine'])
52
+
53
+ with flatpaker.util.tmpdir(description['common']['name'], args.cleanup) as d:
54
+ wd = pathlib.Path(d)
55
+ desktop_file = flatpaker.util.create_desktop(description, wd, appid)
56
+ appdata_file = flatpaker.util.create_appdata(description, wd, appid)
57
+ write_build_rules(description, wd, appid, desktop_file, appdata_file)
58
+ flatpaker.util.build_flatpak(args, wd, appid)
File without changes
@@ -0,0 +1,191 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright © 2022-2024 Dylan Baker
3
+
4
+ from __future__ import annotations
5
+ import json
6
+ import os
7
+ import pathlib
8
+ import textwrap
9
+ import typing
10
+
11
+ from flatpaker import util
12
+
13
+
14
+ def _create_game_sh(use_x11: bool) -> str:
15
+ lines: typing.List[str] = [
16
+ '#!/usr/bin/env sh',
17
+ '',
18
+ 'export RENPY_PERFORMANCE_TEST=0',
19
+ ]
20
+
21
+ if not use_x11:
22
+ lines.append('export SDL_VIDEODRIVER=wayland')
23
+
24
+ lines.extend([
25
+ 'cd /app/lib/game',
26
+ 'exec sh *.sh',
27
+ ])
28
+
29
+ return '\n'.join(lines)
30
+
31
+
32
+ def quote(s: str) -> str:
33
+ return f'"{s}"'
34
+
35
+
36
+ def bd_game(description: util.Description) -> typing.Dict[str, typing.Any]:
37
+ sh = _create_game_sh(description.get('workarounds', {}).get('use_x11', True))
38
+ return {
39
+ 'buildsystem': 'simple',
40
+ 'name': 'game_sh',
41
+ 'sources': [],
42
+ 'build-commands': [
43
+ 'mkdir -p /app/bin',
44
+ f"echo '{sh}' > /app/bin/game.sh",
45
+ 'chmod +x /app/bin/game.sh'
46
+ ],
47
+ }
48
+
49
+
50
+ def bd_build_commands(description: util.Description) -> typing.List[str]:
51
+ commands: typing.List[str] = [
52
+ 'mkdir -p /app/lib/game',
53
+
54
+ # install the main game files
55
+ 'mv *.sh *.py renpy game lib /app/lib/game/',
56
+
57
+ # Move archives that have not been strippped as they would conflict
58
+ # with the main source archive
59
+ 'cp -r */game/* /app/lib/game/game/ || true',
60
+ ]
61
+
62
+ # Insert these commands before any rpy and py files are compiled
63
+ for p in description.get('sources', {}).get('files', []):
64
+ dest = os.path.join('/app/lib/game', p.get('dest', 'game'))
65
+ commands.extend([
66
+ f'mkdir -p {os.path.dirname(dest)}',
67
+ f'mv {p["path"].name} {dest}',
68
+ ])
69
+
70
+ commands.extend([
71
+ # Patch the game to not require sandbox access
72
+ '''sed -i 's@"~/.renpy/"@os.environ.get("XDG_DATA_HOME", "~/.local/share") + "/"@g' /app/lib/game/*.py''',
73
+
74
+ # Recompile all of the rpy files
75
+ textwrap.dedent('''
76
+ pushd /app/lib/game;
77
+ script="$PWD/$(ls *.sh)";
78
+ dirs="$(find . -type f -name '*.rpy' -printf '%h\\0' | sort -zu | sed -z 's@$@ @')";
79
+ for d in $dirs; do
80
+ bash $script $d compile --keep-orphan-rpyc;
81
+ done;
82
+ popd;
83
+ '''),
84
+
85
+ # Recompile all python py files, so we can remove the py files
86
+ # form the final distribution
87
+ #
88
+ # Use -f to force the files mtimes to be updated, otherwise
89
+ # flatpak-builder will delete them as "stale"
90
+ #
91
+ # Use -b for python3 to allow us to delete the .py files
92
+ # I have run into a couple of python2 based ren'py programs that lack
93
+ # the python infrastructure to run with -m, so we'll just open code it to
94
+ # make it more portable
95
+ textwrap.dedent('''
96
+ pushd /app/lib/game;
97
+ if [ -d "lib/py3-linux-x86_64" ]; then
98
+ lib/py3-linux-x86_64/python -m compileall -b -f . || exit 1;
99
+ else
100
+ lib/linux-x86_64/python -c 'import compileall; compileall.main()' -f . || exit 1;
101
+ fi;
102
+ popd;
103
+ ''')
104
+ ])
105
+
106
+ return commands
107
+
108
+
109
+ def bd_icon(description: util.Description, appid: str) -> typing.Dict[str, typing.Any]:
110
+ icon_src = '/app/lib/game/game/gui/window_icon.png'
111
+ icon_dst = f'/app/share/icons/hicolor/256x256/apps/{appid}.png'
112
+ # Must at least be before the appdata is generated
113
+
114
+ _icon_install_cmd: str
115
+ if description.get('workarounds', {}).get('icon_is_webp'):
116
+ _icon_install_cmd = f'dwebp {icon_src} -o {icon_dst}'
117
+ else:
118
+ _icon_install_cmd = f'cp {icon_src} {icon_dst}'
119
+
120
+ return {
121
+ 'buildsystem': 'simple',
122
+ 'name': 'icon',
123
+ 'sources': [],
124
+ 'build-commands': [
125
+ 'mkdir -p /app/share/icons/hicolor/256x256/apps/',
126
+ _icon_install_cmd,
127
+ ],
128
+ }
129
+
130
+
131
+ def write_rules(description: util.Description, workdir: pathlib.Path, appid: str, desktop_file: pathlib.Path, appdata_file: pathlib.Path) -> None:
132
+ sources = util.extract_sources(description)
133
+
134
+ # TODO: typing requires more thought
135
+ modules: typing.List[typing.Dict[str, typing.Any]] = [
136
+ {
137
+ 'buildsystem': 'simple',
138
+ 'name': util.sanitize_name(description['common']['name']),
139
+ 'sources': sources,
140
+ 'build-commands': bd_build_commands(description),
141
+ 'cleanup': [
142
+ '*.exe',
143
+ '*.app',
144
+ '*.rpyc.bak',
145
+ '*.txt',
146
+ '*.rpy',
147
+ '/lib/game/lib/*darwin-*',
148
+ '/lib/game/lib/*windows-*',
149
+ '/lib/game/lib/*-i686',
150
+ ],
151
+ },
152
+ ]
153
+ if not description.get('workarounds', {}).get('icon', False):
154
+ modules.append(bd_icon(description, appid))
155
+ modules.extend([
156
+ bd_game(description),
157
+ util.bd_desktop(desktop_file),
158
+ util.bd_appdata(appdata_file),
159
+ ])
160
+
161
+ if description.get('workarounds', {}).get('use_x11', True):
162
+ finish_args = ['--socket=x11']
163
+ else:
164
+ finish_args = ['--socket=wayland', '--socket=fallback-x11']
165
+
166
+ struct = {
167
+ 'sdk': 'org.freedesktop.Sdk',
168
+ 'runtime': 'org.freedesktop.Platform',
169
+ 'runtime-version': util.RUNTIME_VERSION,
170
+ 'id': appid,
171
+ 'build-options': {
172
+ 'no-debuginfo': True,
173
+ 'strip': False
174
+ },
175
+ 'command': 'game.sh',
176
+ 'finish-args': [
177
+ *finish_args,
178
+ '--socket=pulseaudio',
179
+ '--device=dri',
180
+ ],
181
+ 'modules': modules,
182
+ 'cleanup-commands': [
183
+ "find /app/lib/game/game -name '*.py' -delete",
184
+ "find /app/lib/game/lib -name '*.py' -delete",
185
+ "find /app/lib/game/renpy -name '*.py' -delete",
186
+ 'find /app/lib/game -name __pycache__ -print | xargs -n1 rm -vrf',
187
+ ]
188
+ }
189
+
190
+ with (pathlib.Path(workdir) / f'{appid}.json').open('w') as f:
191
+ json.dump(struct, f, indent=4)
@@ -0,0 +1,75 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright © 2022-2024 Dylan Baker
3
+
4
+ from __future__ import annotations
5
+ import json
6
+ import pathlib
7
+ import typing
8
+
9
+ import flatpaker.util as util
10
+
11
+
12
+ def write_rules(description: util.Description, workdir: pathlib.Path, appid: str, desktop_file: pathlib.Path, appdata_file: pathlib.Path) -> None:
13
+ sources = util.extract_sources(description)
14
+
15
+ # TODO: typing requires more thought
16
+ modules: typing.List[typing.Dict[str, typing.Any]] = [
17
+ {
18
+ 'buildsystem': 'simple',
19
+ 'name': util.sanitize_name(description['common']['name']),
20
+ 'sources': sources,
21
+ 'build-commands': [
22
+ 'mkdir -p /app/share/icons/hicolor/256x256/apps/',
23
+ f'mv icon/*.png /app/share/icons/hicolor/256x256/apps/{appid}.png',
24
+ 'rm -r icon',
25
+
26
+ # the main executable usually isn't executable
27
+ 'chmod +x nw',
28
+
29
+ # Likewise, but seem to only exist for RPGMaker MZ, not MV
30
+ '[[ -f "chrome_crashpad_handler" ]] && chmod +x chrome_crashpad_handler',
31
+ '[[ -f "nacl_helper" ]] && chmod +x nacl_helper',
32
+
33
+ # install the main game files
34
+ 'mkdir -p /app/lib/game',
35
+ 'mv * /app/lib/game/',
36
+ ],
37
+ 'cleanup': [
38
+ '*.desktop', # is incorrect
39
+ ],
40
+ },
41
+ {
42
+ 'buildsystem': 'simple',
43
+ 'name': 'game_sh',
44
+ 'sources': [],
45
+ 'build-commands': [
46
+ 'mkdir -p /app/bin',
47
+ 'echo \'exec /app/lib/game/nw\' > /app/bin/game.sh',
48
+ 'chmod +x /app/bin/game.sh',
49
+ ],
50
+ },
51
+ util.bd_desktop(desktop_file),
52
+ util.bd_appdata(appdata_file),
53
+ ]
54
+
55
+ # TODO: share this somehow?
56
+ struct = {
57
+ 'sdk': 'org.freedesktop.Sdk',
58
+ 'runtime': 'org.freedesktop.Platform',
59
+ 'runtime-version': util.RUNTIME_VERSION,
60
+ 'id': appid,
61
+ 'build-options': {
62
+ 'no-debuginfo': True,
63
+ 'strip': False
64
+ },
65
+ 'command': 'game.sh',
66
+ 'finish-args': [
67
+ '--socket=pulseaudio',
68
+ '--socket=x11',
69
+ '--device=dri',
70
+ ],
71
+ 'modules': modules,
72
+ }
73
+
74
+ with (pathlib.Path(workdir) / f'{appid}.json').open('w') as f:
75
+ json.dump(struct, f)
flatpaker/util.py ADDED
@@ -0,0 +1,263 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright © 2022-2024 Dylan Baker
3
+
4
+ from __future__ import annotations
5
+ from xml.etree import ElementTree as ET
6
+ import contextlib
7
+ import hashlib
8
+ import pathlib
9
+ import shutil
10
+ import subprocess
11
+ import tempfile
12
+ import textwrap
13
+ import typing
14
+
15
+ try:
16
+ import tomllib as tomllib
17
+ except ImportError:
18
+ import tomli as tomllib # type: ignore[import-not-found,no-redef]
19
+
20
+ if typing.TYPE_CHECKING:
21
+ from typing_extensions import NotRequired
22
+
23
+ class Arguments(typing.Protocol):
24
+ description: str
25
+ repo: str
26
+ gpg: typing.Optional[str]
27
+ install: bool
28
+ export: bool
29
+ cleanup: bool
30
+
31
+ class _Common(typing.TypedDict):
32
+
33
+ reverse_url: str
34
+ name: str
35
+ engine: typing.Literal['renpy', 'rpgmaker']
36
+ categories: NotRequired[typing.List[str]]
37
+
38
+ class _AppData(typing.TypedDict):
39
+
40
+ summary: str
41
+ description: str
42
+ content_rating: NotRequired[typing.Dict[str, typing.Literal['none', 'mild', 'moderate', 'intense']]]
43
+ releases: NotRequired[typing.Dict[str, str]]
44
+ license: NotRequired[str]
45
+
46
+ class _Workarounds(typing.TypedDict, total=False):
47
+ icon: bool
48
+ icon_is_webp: bool
49
+ use_x11: bool
50
+
51
+ class Archive(typing.TypedDict):
52
+
53
+ path: pathlib.Path
54
+ strip_components: NotRequired[int]
55
+
56
+ class File(typing.TypedDict):
57
+
58
+ path: pathlib.Path
59
+ dest: NotRequired[str]
60
+
61
+ class Sources(typing.TypedDict):
62
+
63
+ archives: typing.List[Archive]
64
+ files: NotRequired[typing.List[File]]
65
+ patches: NotRequired[typing.List[Archive]]
66
+
67
+ class Description(typing.TypedDict):
68
+
69
+ common: _Common
70
+ appdata: _AppData
71
+ workarounds: NotRequired[_Workarounds]
72
+ sources: NotRequired[Sources]
73
+
74
+ RUNTIME_VERSION = "24.08"
75
+
76
+
77
+ def _subelem(elem: ET.Element, tag: str, text: typing.Optional[str] = None, **extra: str) -> ET.Element:
78
+ new = ET.SubElement(elem, tag, extra)
79
+ new.text = text
80
+ return new
81
+
82
+
83
+ def extract_sources(description: Description) -> typing.List[typing.Dict[str, object]]:
84
+ sources: typing.List[typing.Dict[str, object]] = []
85
+
86
+ if 'sources' in description:
87
+ for a in description['sources']['archives']:
88
+ sources.append({
89
+ 'path': a['path'].as_posix(),
90
+ 'sha256': sha256(a['path']),
91
+ 'type': 'archive',
92
+ 'strip-components': a.get('strip_components', 1),
93
+ })
94
+ for source in description['sources'].get('files', []):
95
+ p = source['path']
96
+ sources.append({
97
+ 'path': p.as_posix(),
98
+ 'sha256': sha256(p),
99
+ 'type': 'file',
100
+ })
101
+ for a in description['sources'].get('patches', []):
102
+ sources.append({
103
+ 'type': 'patch',
104
+ 'path': a['path'].as_posix(),
105
+ 'strip-components': a.get('strip_components', 1),
106
+ })
107
+
108
+ return sources
109
+
110
+
111
+ def create_appdata(description: Description, workdir: pathlib.Path, appid: str) -> pathlib.Path:
112
+ p = workdir / f'{appid}.metainfo.xml'
113
+
114
+ root = ET.Element('component', type="desktop-application")
115
+ _subelem(root, 'id', appid)
116
+ _subelem(root, 'name', description['common']['name'])
117
+ _subelem(root, 'summary', description['appdata']['summary'])
118
+ _subelem(root, 'metadata_license', 'CC0-1.0')
119
+ _subelem(root, 'project_license', description['appdata'].get('license', 'LicenseRef-Proprietary'))
120
+
121
+ recommends = ET.SubElement(root, 'recommends')
122
+ for c in ['pointing', 'keyboard', 'touch', 'gamepad']:
123
+ _subelem(recommends, 'control', c)
124
+
125
+ requires = ET.SubElement(root, 'requires')
126
+ _subelem(requires, 'display_length', '360', compare="ge")
127
+ _subelem(requires, 'internet', 'offline-only')
128
+
129
+ categories = ET.SubElement(root, 'categories')
130
+ for c in ['Game'] + description['common'].get('categories', []):
131
+ _subelem(categories, 'category', c)
132
+
133
+ desc = ET.SubElement(root, 'description')
134
+ _subelem(desc, 'p', description['appdata']['description'])
135
+ _subelem(root, 'launchable', f'{appid}.desktop', type="desktop-id")
136
+
137
+ # There is an oars-1.1, but it doesn't appear to be supported by KDE
138
+ # discover yet
139
+ if 'content_rating' in description['appdata']:
140
+ cr = ET.SubElement(root, 'content_rating', type="oars-1.0")
141
+ for k, r in description['appdata']['content_rating'].items():
142
+ _subelem(cr, 'content_attribute', r, id=k)
143
+
144
+ if 'releases' in description['appdata']:
145
+ cr = ET.SubElement(root, 'releases')
146
+ for date, version in description['appdata']['releases'].items():
147
+ _subelem(cr, 'release', version=version, date=date)
148
+
149
+ tree = ET.ElementTree(root)
150
+ ET.indent(tree)
151
+ tree.write(p, encoding='utf-8', xml_declaration=True)
152
+
153
+ return p
154
+
155
+
156
+ def create_desktop(description: Description, workdir: pathlib.Path, appid: str) -> pathlib.Path:
157
+ p = workdir / f'{appid}.desktop'
158
+ with p.open('w') as f:
159
+ f.write(textwrap.dedent(f'''\
160
+ [Desktop Entry]
161
+ Name={description['common']['name']}
162
+ Exec=game.sh
163
+ Type=Application
164
+ Categories={';'.join(['Game'] + description['common'].get('categories', []))};
165
+ '''))
166
+ if description.get('workarounds', {}).get('icon', True):
167
+ f.write(f'Icon={appid}')
168
+
169
+ return p
170
+
171
+
172
+ def sha256(path: pathlib.Path) -> str:
173
+ with path.open('rb') as f:
174
+ return hashlib.sha256(f.read()).hexdigest()
175
+
176
+
177
+ def sanitize_name(name: str) -> str:
178
+ """Replace invalid characters in a name with valid ones."""
179
+ return name \
180
+ .replace(' ', '_') \
181
+ .replace("&", '_') \
182
+ .replace(':', '') \
183
+ .replace("'", '')
184
+
185
+
186
+ def build_flatpak(args: Arguments, workdir: pathlib.Path, appid: str) -> None:
187
+ build_command: typing.List[str] = [
188
+ 'flatpak-builder', '--force-clean', '--install-deps-from=flathub', '--user', 'build',
189
+ (workdir / f'{appid}.json').absolute().as_posix(),
190
+ ]
191
+
192
+ if args.export:
193
+ build_command.extend(['--repo', args.repo])
194
+ if args.gpg:
195
+ build_command.extend(['--gpg-sign', args.gpg])
196
+ if args.install:
197
+ build_command.extend(['--install'])
198
+
199
+ subprocess.run(build_command)
200
+
201
+
202
+ def load_description(name: str) -> Description:
203
+ relpath = pathlib.Path(name).parent.absolute()
204
+ with open(name, 'rb') as f:
205
+ d = typing.cast('Description', tomllib.load(f))
206
+
207
+ # Fixup relative paths
208
+ if 'sources' in d:
209
+ for a in d['sources']['archives']:
210
+ a['path'] = relpath / a['path']
211
+ if 'files' in d['sources']:
212
+ for s in d['sources']['files']:
213
+ s['path'] = relpath / s['path']
214
+ if 'patches' in d['sources']:
215
+ for a in d['sources']['patches']:
216
+ a['path'] = relpath / a['path']
217
+
218
+ return d
219
+
220
+
221
+ @contextlib.contextmanager
222
+ def tmpdir(name: str, cleanup: bool = True) -> typing.Iterator[pathlib.Path]:
223
+ tdir = pathlib.Path(tempfile.gettempdir()) / name
224
+ tdir.mkdir(parents=True, exist_ok=True)
225
+ yield tdir
226
+ if cleanup:
227
+ shutil.rmtree(tdir)
228
+
229
+
230
+ def bd_desktop(file_: pathlib.Path) -> typing.Dict[str, typing.Any]:
231
+ return {
232
+ 'buildsystem': 'simple',
233
+ 'name': 'desktop_file',
234
+ 'sources': [
235
+ {
236
+ 'path': file_.as_posix(),
237
+ 'sha256': sha256(file_),
238
+ 'type': 'file',
239
+ }
240
+ ],
241
+ 'build-commands': [
242
+ 'mkdir -p /app/share/applications',
243
+ f'cp {file_.name} /app/share/applications',
244
+ ],
245
+ }
246
+
247
+
248
+ def bd_appdata(file_: pathlib.Path) -> typing.Dict[str, typing.Any]:
249
+ return {
250
+ 'buildsystem': 'simple',
251
+ 'name': 'appdata_file',
252
+ 'sources': [
253
+ {
254
+ 'path': file_.as_posix(),
255
+ 'sha256': sha256(file_),
256
+ 'type': 'file',
257
+ }
258
+ ],
259
+ 'build-commands': [
260
+ 'mkdir -p /app/share/metainfo',
261
+ f'cp {file_.name} /app/share/metainfo',
262
+ ],
263
+ }
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Dylan Baker
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,150 @@
1
+ Metadata-Version: 2.1
2
+ Name: flatpaker
3
+ Version: 0.0.1
4
+ Summary: Utilities to convert various kinds of native binaries into flatpaks.
5
+ Keywords: flatpak,renpy,rpgmaker
6
+ Author-email: Dylan Baker <dylan@pnwbakers.com>
7
+ Requires-Python: >=3.9
8
+ Description-Content-Type: text/markdown
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Environment :: Console
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3 :: Only
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: System :: Archiving :: Packaging
19
+ Requires-Dist: tomli; python_version<"3.11"
20
+
21
+ # flatpaker
22
+
23
+ Script to mostly automate creating flatpaks from published Ren'Py and Linux
24
+ builds of RPGMaker MV and MZ. open to additional support
25
+
26
+ ## What is it?
27
+
28
+ It's a script that automatically handles much of the task of generating a
29
+ flatpak for pre-built projects, including adding patches or mods. You
30
+ write a small, simple toml file, fetch the sources, and get a ready to publish
31
+ flatpak.
32
+
33
+ It currently automatically does the following automatically:
34
+
35
+ - Generates an appstream xml file
36
+ - Generates a .desktop file
37
+ - Extracts an icon from the game source, and installs it
38
+ - patches the game to honor $XDG_DATA_HOME for storing game data inside the sandbox (instead of needing $HOME access)
39
+ - sets up the sandbox to allow audio and display, but nothing else
40
+ - recompiles the program when mods are applied
41
+ - strips .rpy files to save space (keeping the rpyc files)
42
+ - strips windows and macos specific files
43
+ - allows local install or publishing to a repo
44
+
45
+ ## Why?
46
+
47
+ I like playing Ren'Py games sometimes. I also don't always trust random
48
+ pre-compiled binaries from the internet. Flatpak provides a nice, convenient
49
+ way to sandbox applications. It also makes supporting Steam Deck and Fedora
50
+ immutable a breeze. But generating flatpaks by hand is a lot of work, especially
51
+ when most of the process will be exactly the same for every renpy project.
52
+
53
+ ## How do I use it?
54
+
55
+ 1. Download the compressed project
56
+ 2. Download any mods or addons (optional)
57
+ 3. Write a toml description
58
+ 4. run the program
59
+
60
+ ### Toml Format
61
+
62
+ ```toml
63
+ [common]
64
+ name = 'Game or VN' # use properly formatted name like "The Cool Adventures of Bob", or "Bob's Quest 7: Lawnmower Confusion"
65
+ reverse_url = 'com.example.JDoe' # name will be appended
66
+ # "Game" is added automatically
67
+ # used freedesktop menu categories. see: https://specifications.freedesktop.org/menu-spec/latest/apas02.html
68
+ categories = ['Simulation']
69
+ engine = ['renpy'] # Or 'rpgmaker'
70
+
71
+ [appdata]
72
+ summary = "A short summary, one sentence or so."
73
+ description = """
74
+ A longer description.
75
+
76
+ probably on multiple \
77
+ lines
78
+ """
79
+
80
+ # This is an optional value for the license of the renpy project itself.
81
+ # If unset it defaults to LicenseRef-Proprietary.
82
+ # if you have specific terms which are not an Open Source license, you can use the form:
83
+ # LicenseRef-Proprietary=https://www.example.com/my-license
84
+ # See: https://spdx.org/specifications for more information
85
+ license = "SPDX identifier"
86
+
87
+ [appdata.content_rating]
88
+ # optional
89
+ # Uses OARS specifications. See: https://hughsie.github.io/oars/
90
+ # keys should be ids, and the values are must be a rating (as a string):
91
+ # none, mild, moderate, or intense
92
+ language-profanity = "mild"
93
+
94
+ [appdata.releases]
95
+ # optional
96
+ # in the form "date = version"
97
+ "2023-01-01" = "1.0.0"
98
+
99
+ # Optional, alternatively may be passed on teh command line
100
+ [[sources.archives]]
101
+ # path must be set if this is provided
102
+ path = "relative to toml or absolute path"
103
+
104
+ # Optional, defaults to 1. How many directory levels to remove from this component
105
+ strip_comonents = 2
106
+
107
+ # Optional, cannot be set from command line
108
+ [[sources.patches]]
109
+ # path must be set if this is provided
110
+ path = "relative to toml or absolute path"
111
+
112
+ # Optional, defaults to 1. How many directory levels to remove from this component
113
+ strip_comonents = 2
114
+
115
+ # Optional, cannot be set from command line
116
+ [[sources.files]]
117
+ # path must be set if this is provided
118
+ path = "relative to toml or absolute path"
119
+
120
+ # Optional, if set the file will be installed to this name
121
+ # Does not have to be set for .rpy files that go in the game root directory
122
+ dest = "where to install"
123
+ ```
124
+
125
+ ### Configuration
126
+
127
+ Some options can be given on the command line or via a configuration file.
128
+ That file must be written to `$XDG_CONFIG_HOME/flatpaker/config.toml` (if unset
129
+ `$XDG_CONFIG_HOME` defaults to `~/.config`).
130
+
131
+ ```toml
132
+ [common]
133
+ # A gpg private key to sign with, overwritten by the --gpg option
134
+ gpg-key = "0x123456789"
135
+
136
+ # The absolute path to a repo to write to. overwritten by the --repo option
137
+ repo = "/path/to/a/repo/to/export"
138
+ ```
139
+
140
+
141
+ ## What is required?
142
+
143
+ - python 3.11 or a modern version of python3 with tomli
144
+ - flatpak-builder
145
+
146
+ ### Schema
147
+
148
+ A Json based schema is provided, which can be used with VSCode's EvenBetterToml
149
+ extension. It may be useful elsewhere.
150
+
@@ -0,0 +1,12 @@
1
+ flatpaker/__init__.py,sha256=PsGR5gs45aoJvPD1NHYiyPzrqGJjlynQgcPzUULvCZs,534
2
+ flatpaker/config.py,sha256=z3GFLh3It1qM1W_Brds1AjFojPDjqswPrxdGxkp7T94,882
3
+ flatpaker/entry.py,sha256=u4o6U3rz75b-5M8pRJ0SeW5V3zXSYoGvcyeY8Jvq18c,2370
4
+ flatpaker/util.py,sha256=Ulx6VKzVNEz_v2U5x5ORZnX6gaN0190H_WYSTk6JV8I,8105
5
+ flatpaker/impl/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ flatpaker/impl/renpy.py,sha256=LooyGP4qM8-uwEtkRSpqAyxgEa805_wabc6D1SnfsAU,6076
7
+ flatpaker/impl/rpgmaker.py,sha256=Kvy6Mb_iOjD-AmUfJphyvFUnJzIBxWYoGoEIAsrx7kk,2392
8
+ flatpaker-0.0.1.dist-info/entry_points.txt,sha256=_6R4HPhjK68ou2LtVSgm26ltyMaQF4tCQT-L9ymdOgQ,50
9
+ flatpaker-0.0.1.dist-info/LICENSE,sha256=QqvRDZzvUrvgv6CnNh5LdWCnoF19JuPUYzE-Qs8pNFY,1068
10
+ flatpaker-0.0.1.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
11
+ flatpaker-0.0.1.dist-info/METADATA,sha256=awJgom54TVC5npmCQob03mQNnOLw0YsV1w0HJHmXAe0,5149
12
+ flatpaker-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: flit 3.9.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ flatpaker=flatpaker.entry:main
3
+