| |
@@ -28,6 +28,8 @@
|
| |
from textwrap import dedent
|
| |
from xml.etree import ElementTree
|
| |
|
| |
+ from gi.repository import GLib
|
| |
+
|
| |
from .container_spec import FlatpakSpec
|
| |
from .utils import Arch, RuntimeInfo
|
| |
|
| |
@@ -181,10 +183,19 @@
|
| |
|
| |
tree.write(self.appdata_file, encoding="UTF-8", xml_declaration=True)
|
| |
|
| |
- def _rename_icon(self):
|
| |
- if not self.spec.rename_icon:
|
| |
+ def _rename_mime_file(self):
|
| |
+ if not self.spec.rename_mime_file:
|
| |
return
|
| |
|
| |
+ mimepkgs_dir = os.path.join(self.app_root, "share", "mime", "packages")
|
| |
+ src = os.path.join(mimepkgs_dir, self.spec.rename_mime_file)
|
| |
+ mime_basename = self.spec.app_id + ".xml"
|
| |
+ dest = os.path.join(mimepkgs_dir, mime_basename)
|
| |
+
|
| |
+ self.log.info("Renaming %s to %s", self.spec.rename_mime_file, mime_basename)
|
| |
+ os.rename(src, dest)
|
| |
+
|
| |
+ def _rename_icon_helper(self, icon, mime_icon=False):
|
| |
found_icon = False
|
| |
icons_dir = os.path.join(self.app_root, "share/icons")
|
| |
|
| |
@@ -193,15 +204,18 @@
|
| |
depth = relative.count("/")
|
| |
|
| |
for source_file in filenames:
|
| |
- if source_file.startswith(self.spec.rename_icon):
|
| |
+ if source_file.startswith(icon):
|
| |
source_path = os.path.join(full_dir, source_file)
|
| |
is_file = os.path.isfile(source_path)
|
| |
- extension = source_file[len(self.spec.rename_icon):]
|
| |
+ extension = source_file[len(icon):]
|
| |
|
| |
if is_file and depth == 3 and (extension.startswith(".") or
|
| |
extension.startswith("-symbolic")):
|
| |
found_icon = True
|
| |
- new_name = self.spec.app_id + extension
|
| |
+ if mime_icon:
|
| |
+ new_name = self.spec.app_id + "." + source_file
|
| |
+ else:
|
| |
+ new_name = self.spec.app_id + extension
|
| |
|
| |
self.log.info("%s icon %s/%s to %s/%s",
|
| |
"Copying" if self.spec.copy_icon else "Renaming",
|
| |
@@ -215,18 +229,27 @@
|
| |
os.rename(source_path, dest_path)
|
| |
else:
|
| |
if not is_file:
|
| |
- self.log.debug("%s/%s matches 'rename-icon', but not a regular file",
|
| |
- full_dir, source_file)
|
| |
+ self.log.debug("%s/%s matches '%s', but not a regular file",
|
| |
+ full_dir, source_file, icon)
|
| |
elif depth != 3:
|
| |
- self.log.debug("%s/%s matches 'rename-icon', but not at depth 3",
|
| |
- full_dir, source_file)
|
| |
+ self.log.debug("%s/%s matches '%s', but not at depth 3",
|
| |
+ full_dir, source_file, icon)
|
| |
else:
|
| |
- self.log.debug("%s/%s matches 'rename-icon', but name does not "
|
| |
+ self.log.debug("%s/%s matches '%s', but name does not "
|
| |
"continue with '.' or '-symbolic.'",
|
| |
- full_dir, source_file)
|
| |
+ full_dir, source_file, icon)
|
| |
|
| |
if not found_icon:
|
| |
- raise RuntimeError(f"icon {self.spec.rename_icon} not found below {icons_dir}")
|
| |
+ raise RuntimeError(f"icon {icon} not found below {icons_dir}")
|
| |
+
|
| |
+
|
| |
+ def _rename_icon(self):
|
| |
+ if self.spec.rename_icon:
|
| |
+ self._rename_icon_helper(self.spec.rename_icon)
|
| |
+
|
| |
+ def _rename_mime_icons(self):
|
| |
+ for icon in self.spec.rename_mime_icons:
|
| |
+ self._rename_icon_helper(icon, mime_icon=True)
|
| |
|
| |
def _rewrite_desktop_file(self):
|
| |
if not (
|
| |
@@ -272,6 +295,42 @@
|
| |
with open(desktop, "w") as f:
|
| |
cp.write(f, space_around_delimiters=False)
|
| |
|
| |
+ def _rewrite_mime_file(self):
|
| |
+ if not self.spec.rename_mime_icons:
|
| |
+ return
|
| |
+
|
| |
+ xmlns = 'http://www.freedesktop.org/standards/shared-mime-info'
|
| |
+ xmlns_ = '{' + xmlns + '}'
|
| |
+
|
| |
+ mime_file = os.path.join(self.app_root, "share/mime/packages", self.spec.app_id + ".xml")
|
| |
+ self.log.debug("Rewriting contents of %s", mime_file)
|
| |
+ try:
|
| |
+ tree = ElementTree.parse(mime_file)
|
| |
+ root = tree.getroot()
|
| |
+ mime_modified = False
|
| |
+ for mime_type in root.findall(xmlns_ + 'mime-type'):
|
| |
+ icon = mime_type.find(xmlns_ + 'icon')
|
| |
+ if icon is not None:
|
| |
+ icon_name = icon.get('name')
|
| |
+ icon.set('name', self.spec.app_id + "." + icon_name)
|
| |
+ mime_modified = True
|
| |
+ else:
|
| |
+ icon = mime_type.find(xmlns_ + 'generic-icon')
|
| |
+ if icon is not None:
|
| |
+ icon_name = icon.get('name')
|
| |
+ icon.set('name', self.spec.app_id + "." + icon_name)
|
| |
+ mime_modified = True
|
| |
+ else:
|
| |
+ icon = ElementTree.SubElement(mime_type, 'generic-icon')
|
| |
+ icon_name = mime_type.get('type').replace('/', '-')
|
| |
+ icon.set('name', self.spec.app_id + "." + icon_name)
|
| |
+ mime_modified = True
|
| |
+ if mime_modified:
|
| |
+ ElementTree.register_namespace("", xmlns)
|
| |
+ tree.write(mime_file, encoding="UTF-8", xml_declaration=True)
|
| |
+ except:
|
| |
+ pass
|
| |
+
|
| |
def _compose_appstream(self):
|
| |
if not self.spec.appstream_compose or not self.appdata_file:
|
| |
return
|
| |
@@ -286,13 +345,17 @@
|
| |
def process(self):
|
| |
self._process_appdata_file()
|
| |
self._rename_desktop_file()
|
| |
+ self._rename_mime_file()
|
| |
self._rename_icon()
|
| |
+ self._rename_mime_icons()
|
| |
self._rewrite_desktop_file()
|
| |
+ self._rewrite_mime_file()
|
| |
self._compose_appstream()
|
| |
|
| |
|
| |
class BaseFlatpakSourceInfo(ABC):
|
| |
runtime: bool
|
| |
+ extension: bool
|
| |
spec: FlatpakSpec
|
| |
|
| |
@abstractmethod
|
| |
@@ -335,6 +398,8 @@
|
| |
# A runtime module must have a 'runtime' profile, but can have other
|
| |
# profiles for SDKs, minimal runtimes, etc.
|
| |
self.runtime = 'runtime' in base_module.mmd.get_profile_names()
|
| |
+ # Extension support not implemented for module backend
|
| |
+ self.extension = False
|
| |
|
| |
if profile is None:
|
| |
profile = 'runtime' if self.runtime else 'default'
|
| |
@@ -502,11 +567,12 @@
|
| |
def __init__(self, spec: FlatpakSpec, runtime_info: Optional[RuntimeInfo]):
|
| |
if spec.build_runtime and runtime_info:
|
| |
raise RuntimeError("runtime_info can only be set for an application")
|
| |
- if not spec.build_runtime and not runtime_info:
|
| |
+ if not spec.build_runtime and not spec.build_extension and not runtime_info:
|
| |
raise RuntimeError("runtime_info must be set for an application")
|
| |
self.spec = spec
|
| |
self.runtime = spec.build_runtime
|
| |
self.runtime_info = runtime_info
|
| |
+ self.extension = spec.build_extension
|
| |
|
| |
def precheck(self):
|
| |
pass
|
| |
@@ -753,7 +819,7 @@
|
| |
|
| |
def get_components(self, manifest):
|
| |
all_components = self._get_components(manifest)
|
| |
- if self.source.runtime:
|
| |
+ if self.source.runtime or self.source.extension:
|
| |
image_components = all_components
|
| |
else:
|
| |
image_components = self.source.filter_app_manifest(all_components)
|
| |
@@ -767,9 +833,15 @@
|
| |
if spec.finish_args:
|
| |
# shlex.split(None) reads from standard input, so avoid that
|
| |
finish_args = shlex.split(spec.finish_args, comments=True)
|
| |
- if spec.command and not self.source.runtime:
|
| |
+ for ext in spec.add_extensions:
|
| |
+ finish_args += ext.args
|
| |
+ for extdata in spec.extra_data:
|
| |
+ if not extdata.only_arches or self.arch.flatpak in extdata.only_arches:
|
| |
+ finish_args += extdata.finish_args
|
| |
+ if spec.command and not self.source.runtime and not self.source.extension:
|
| |
finish_args = ['--command', spec.command] + finish_args
|
| |
|
| |
+ self.log.debug("Calling flatpak build-finish %s %s", " ".join(finish_args), builddir)
|
| |
subprocess.check_call(['flatpak', 'build-finish'] + finish_args + [builddir])
|
| |
|
| |
def _create_repo(self):
|
| |
@@ -805,14 +877,15 @@
|
| |
'runtime_id': runtime_id,
|
| |
'sdk_id': sdk_id,
|
| |
'arch': self.arch.flatpak,
|
| |
+ 'runtime_version': spec.runtime_version or branch,
|
| |
'branch': branch
|
| |
}
|
| |
|
| |
METADATA_TEMPLATE = dedent("""\
|
| |
[Runtime]
|
| |
name={id}
|
| |
- runtime={runtime_id}/{arch}/{branch}
|
| |
- sdk={sdk_id}/{arch}/{branch}
|
| |
+ runtime={runtime_id}/{arch}/{runtime_version}
|
| |
+ sdk={sdk_id}/{arch}/{runtime_version}
|
| |
""")
|
| |
|
| |
with open(os.path.join(builddir, 'metadata'), 'w') as f:
|
| |
@@ -842,6 +915,15 @@
|
| |
commit_args += ['--add-metadata-string',
|
| |
'ostree.endoflife-rebase=' + spec.end_of_life_rebase]
|
| |
|
| |
+ extra_data_variant_data = []
|
| |
+ for extdata in spec.extra_data:
|
| |
+ if not extdata.only_arches or self.arch.flatpak in extdata.only_arches:
|
| |
+ extra_data_variant_data += extdata.variant_data
|
| |
+ if extra_data_variant_data:
|
| |
+ extra_data_variant = GLib.Variant("a(ayttays)", extra_data_variant_data)
|
| |
+ commit_args += ['--add-metadata',
|
| |
+ 'xa.extra-data-sources=' + extra_data_variant.print_(True)]
|
| |
+
|
| |
subprocess.check_call(['ostree', 'commit'] + commit_args)
|
| |
subprocess.check_call(['ostree', 'summary', '-u', '--repo', repo])
|
| |
|
| |
@@ -1052,7 +1134,7 @@
|
| |
def build_container(self, tarred_filesystem: str, tar_outfile: bool = True):
|
| |
outfile = os.path.join(self.workdir, 'flatpak-oci-image')
|
| |
|
| |
- if self.source.runtime:
|
| |
+ if self.source.runtime or self.source.extension:
|
| |
ref_name = self._create_runtime_oci(tarred_filesystem, outfile)
|
| |
else:
|
| |
ref_name = self._create_app_oci(tarred_filesystem, outfile)
|
| |
Since extensions will generally require reorganizing files to fit the necessary layout, and because (at least application) extensions may be mounted to an arbitrary location, only runtime packages are used in building extensions. Extension container.yaml need to include at least
build-extension
,runtime-version
, andbranch
, should listpackages
as normal, and then include acleanup-commands
to move the correct files to the necessary layout under /app.