Mercurial > sat_docs
comparison flatpak/build_manifest.py @ 139:d36a68e396d5
flatpak: set appdata release version for dev version:
appdata file can now use a template (which has the same name with "_tpl_" prefix), which
is used in dev version to automatically set the release with the right Mercurial
revision/date.
Comment from org.salutatoi.Cagou.appdata.xml has been moved as first child of
<component>, so it is not lost while parsing the file.
author | Goffi <goffi@goffi.org> |
---|---|
date | Sun, 23 Jun 2019 17:34:10 +0200 |
parents | 274af514a5cf |
children | e23e414987d7 |
comparison
equal
deleted
inserted
replaced
138:274af514a5cf | 139:d36a68e396d5 |
---|---|
6 from typing import List | 6 from typing import List |
7 from dataclasses import dataclass | 7 from dataclasses import dataclass |
8 import hashlib | 8 import hashlib |
9 from ftplib import FTP | 9 from ftplib import FTP |
10 from urllib.parse import urlparse | 10 from urllib.parse import urlparse |
11 from textwrap import dedent | |
11 import sys | 12 import sys |
12 import os | 13 import os |
13 import json | 14 import json |
14 import time | 15 import time |
15 import argparse | 16 import argparse |
16 import shutil | 17 import shutil |
17 from packaging.version import parse as parse_version | 18 from packaging.version import parse as parse_version |
18 import requests | 19 import requests |
20 from lxml import etree | |
19 | 21 |
20 | 22 |
21 CACHE_LIMIT = 3600 * 24 | 23 CACHE_LIMIT = 3600 * 24 |
22 PYTHON_DEP_OVERRIDE = { | 24 PYTHON_DEP_OVERRIDE = { |
23 "lxml": { | 25 "lxml": { |
83 "--filesystem=home" | 85 "--filesystem=home" |
84 ] | 86 ] |
85 } | 87 } |
86 SHOW_REQUIRES_HEADER = 'Requires: ' | 88 SHOW_REQUIRES_HEADER = 'Requires: ' |
87 SETTINGS_KEY = '_build_settings' | 89 SETTINGS_KEY = '_build_settings' |
90 APPDATA_RELEASE_DEV_TEXT = dedent("""\ | |
91 This is a development version, used as a preview. | |
92 Please note that it is incomplete and it probably contains bugs. | |
93 """) | |
94 OVERWRITE_WARNING = "{} already exists, do you want to overwrite it (y/N)? " | |
88 | 95 |
89 | 96 |
90 @dataclass | 97 @dataclass |
91 class Package: | 98 class Package: |
92 name: str | 99 name: str |
114 build_group = parser.add_argument_group('building', 'options used to generate the manifest') | 121 build_group = parser.add_argument_group('building', 'options used to generate the manifest') |
115 export_group = parser.add_argument_group('export', 'otions used to building files') | 122 export_group = parser.add_argument_group('export', 'otions used to building files') |
116 | 123 |
117 # build group | 124 # build group |
118 build_group.add_argument('-f', '--force', action="store_true", | 125 build_group.add_argument('-f', '--force', action="store_true", |
119 help="force overwritting of existing manifest") | 126 help="force overwritting of existing manifest and appdata file") |
120 build_group.add_argument('--ignore-cache', action='append', default=[], | 127 build_group.add_argument('--ignore-cache', action='append', default=[], |
121 help='ignore the cache of this step ("all" to ignore all caches)') | 128 help='ignore the cache of this step ("all" to ignore all caches)') |
122 build_group.add_argument( | 129 build_group.add_argument( |
123 '--deps-dir', | 130 '--deps-dir', |
124 help="use this directory to build_group python dependencies (it won't be deleted at " | 131 help="use this directory to build_group python dependencies (it won't be deleted at " |
363 # we use this name to easily ignore cache (with stem) while avoiding | 370 # we use this name to easily ignore cache (with stem) while avoiding |
364 # conflict if we have 2 URLs with the same stem | 371 # conflict if we have 2 URLs with the same stem |
365 step_name = f"{stem}__{url}" | 372 step_name = f"{stem}__{url}" |
366 | 373 |
367 if step_message is None: | 374 if step_message is None: |
368 step_messate = f"generating module for {stem}" | 375 step_message = f"generating module for {stem}" |
369 | 376 |
370 print_step(step_message) | 377 print_step(step_message) |
371 cache = get_cache(step_name) | 378 cache = get_cache(step_name) |
372 if cache is not None: | 379 if cache is not None: |
373 return cache | 380 return cache |
443 | 450 |
444 for data in reqs_data: | 451 for data in reqs_data: |
445 resolve_requirements(data, deps_map, deps, indent+1) | 452 resolve_requirements(data, deps_map, deps, indent+1) |
446 | 453 |
447 deps.append(package) | 454 deps.append(package) |
455 | |
456 | |
457 def get_hg_id_date(path): | |
458 """Get short identifier and date of current commit from given Mercurial repository | |
459 | |
460 version is retrieve with `hg id`, a "+" is appended after shortrev if it has | |
461 been modified. | |
462 @param path(str, Path): path to the repository | |
463 @return(tuple(str, date)): found revision + iso date | |
464 """ | |
465 hg_cplted = subprocess.run( | |
466 ["hg", "id", "--template", "{id|short}{dirty}\n{date|isodate}", path], | |
467 capture_output=True, text=True) | |
468 hg_cplted.check_returncode() | |
469 return hg_cplted.stdout.split('\n') | |
448 | 470 |
449 | 471 |
450 def get_cache_dir(): | 472 def get_cache_dir(): |
451 """Return path to directory to use for cache""" | 473 """Return path to directory to use for cache""" |
452 return Path(f"cache_{app_id}") | 474 return Path(f"cache_{app_id}") |
583 dep = Package(name=name_canonical, | 605 dep = Package(name=name_canonical, |
584 version=version, | 606 version=version, |
585 hash_=dep_hash, | 607 hash_=dep_hash, |
586 url=version_json['url'], | 608 url=version_json['url'], |
587 requirements=requirements, | 609 requirements=requirements, |
588 # extra_requirements=extra_requirements, | |
589 ) | 610 ) |
590 | 611 |
591 deps_map[name_canonical] = dep | 612 deps_map[name_canonical] = dep |
592 print(f"found dependency: {dep.name} {dep.version}") | 613 print(f"found dependency: {dep.name} {dep.version}") |
593 | 614 |
790 step_name = f"icon__{icon}", | 811 step_name = f"icon__{icon}", |
791 step_message = "retrieving application icon", | 812 step_message = "retrieving application icon", |
792 ) | 813 ) |
793 | 814 |
794 | 815 |
816 def generate_appdata_from_template(template_file): | |
817 appdata_file = Path(f"{app_id}.appdata.xml") | |
818 if appdata_file.exists() and not args.force: | |
819 confirm = input(OVERWRITE_WARNING.format(appdata_file)) | |
820 if confirm != 'y': | |
821 print("manifest building cancelled") | |
822 sys.exit(0) | |
823 parser = etree.XMLParser(remove_blank_text=True) | |
824 tree = etree.parse(template_file, parser) | |
825 root = tree.getroot() | |
826 if args.version == 'dev': | |
827 print("addind release data for dev version") | |
828 releases_elt = root.find('releases') | |
829 if releases_elt is None: | |
830 raise ValueError( | |
831 "<releases/> element is missing in appdata template, please add it") | |
832 release_elt = etree.SubElement( | |
833 releases_elt, | |
834 "release", | |
835 {'type': 'development', | |
836 'version': dev_version_rev, | |
837 'date': dev_version_date}, | |
838 ) | |
839 description_elt = etree.SubElement(release_elt, 'description') | |
840 text_lines = APPDATA_RELEASE_DEV_TEXT.strip().split('\n') | |
841 for text in text_lines: | |
842 etree.SubElement(description_elt, 'p').text = text | |
843 print(f"release data added for this version ({dev_version_rev})") | |
844 | |
845 with open(appdata_file, "wb") as f: | |
846 f.write(etree.tostring(root, encoding="utf-8", xml_declaration=True, | |
847 pretty_print=True)) | |
848 | |
849 return appdata_file.as_posix() | |
850 | |
851 | |
795 def get_app_metadata(): | 852 def get_app_metadata(): |
796 desktop_file = build_settings.get('desktop_file') | 853 desktop_file = build_settings.get('desktop_file') |
797 appdata_file = build_settings.get('appdata_file') | 854 appdata_file = build_settings.get('appdata_file') |
798 if desktop_file is None and app_data_file is None: | 855 if desktop_file is None and appdata_file is None: |
799 return | 856 return |
800 | 857 |
801 print_step("retrieving application metadata") | 858 print_step("retrieving application metadata") |
802 # we don't use cache here to be sure to have always up-to-date files | 859 # we don't use cache here to be sure to have always up-to-date files |
803 | 860 |
809 filename = desktop_file, | 866 filename = desktop_file, |
810 dest=f"/app/share/applications/{app_id}.desktop", | 867 dest=f"/app/share/applications/{app_id}.desktop", |
811 )) | 868 )) |
812 | 869 |
813 if appdata_file is not None: | 870 if appdata_file is not None: |
871 if appdata_file.startswith('_tpl_'): | |
872 print("found appdata template, we now use it to generate the file") | |
873 appdata_file = generate_appdata_from_template(appdata_file) | |
814 print("generating module for appdata metadata") | 874 print("generating module for appdata metadata") |
815 data.extend(file_upload( | 875 data.extend(file_upload( |
816 filename = appdata_file, | 876 filename = appdata_file, |
817 dest=f"/app/share/metainfo/{app_id}.appdata.xml", | 877 dest=f"/app/share/metainfo/{app_id}.appdata.xml", |
818 )) | 878 )) |
831 package_file = Path(f"{app_id}.json") | 891 package_file = Path(f"{app_id}.json") |
832 | 892 |
833 print(f"generating manifest for {app_id} ({args.version})") | 893 print(f"generating manifest for {app_id} ({args.version})") |
834 | 894 |
835 if package_file.exists() and not args.force: | 895 if package_file.exists() and not args.force: |
836 confirm = input( | 896 confirm = input(OVERWRITE_WARNING.format(package_file)) |
837 f"{package_file} already exists, do you want to overwritte it (y/N)? ") | |
838 if confirm != 'y': | 897 if confirm != 'y': |
839 print("manifest building cancelled") | 898 print("manifest building cancelled") |
840 sys.exit(0) | 899 sys.exit(0) |
841 | 900 |
842 tpl_file = Path(f"_tpl_{package_file}") | 901 tpl_file = Path(f"_tpl_{package_file}") |
850 | 909 |
851 build_settings = template.pop(SETTINGS_KEY, {}) | 910 build_settings = template.pop(SETTINGS_KEY, {}) |
852 if "setup_requirements" in build_settings: | 911 if "setup_requirements" in build_settings: |
853 PYTHON_SETUP_REQUIREMENTS.extend(build_settings["setup_requirements"]) | 912 PYTHON_SETUP_REQUIREMENTS.extend(build_settings["setup_requirements"]) |
854 main_package = canonical(build_settings.get('package', args.name)) | 913 main_package = canonical(build_settings.get('package', args.name)) |
855 if args.version == 'dev' and 'dev_repos' in build_settings: | 914 if args.version == 'dev': |
915 if 'dev_repos' not in build_settings: | |
916 raise NotImplementedError( | |
917 "dev version can currently only be handled with a dev repostiory " | |
918 "(dev_repos)") | |
856 dev_repos = build_settings['dev_repos'] | 919 dev_repos = build_settings['dev_repos'] |
857 main_package_source = cache_from_repos() | 920 main_package_source = cache_from_repos() |
921 dev_version_rev, dev_version_date = get_hg_id_date(main_package_source) | |
858 else: | 922 else: |
859 main_package_source = main_package | 923 main_package_source = main_package |
860 | 924 |
861 manifest = {} | 925 manifest = {} |
862 manifest['app-id'] = app_id | 926 manifest['app-id'] = app_id |
901 | 965 |
902 # desktop file and appdata file | 966 # desktop file and appdata file |
903 modules.extend(get_app_metadata()) | 967 modules.extend(get_app_metadata()) |
904 | 968 |
905 # now the app itself | 969 # now the app itself |
906 if args.version == 'dev' and 'dev_repos' in build_settings: | 970 if args.version == 'dev': |
907 # mercurial is needed for dev version to install but also to | 971 # mercurial is needed for dev version to install but also to |
908 # retrieve revision used | 972 # retrieve revision used |
909 modules.extend(get_python_package("mercurial")) | 973 modules.extend(get_python_package("mercurial")) |
910 modules.extend(get_repos_module()) | 974 modules.extend(get_repos_module()) |
911 else: | 975 else: |