#45 Support building extensions
Opened 3 months ago by yselkowitz. Modified 8 days ago
yselkowitz/flatpak-module-tools extensions  into  main

@@ -46,8 +46,6 @@ 

  Requires: python3-koji

  Requires: python3-networkx

  Requires: python3-requests-toolbelt

- # for pkg_resources

- Requires: python3-setuptools

  Requires: python3-solv

  

  %description

@@ -128,7 +128,7 @@ 

      @property

      def release(self):

          """Bare number for the operating system release (e.g. 39 for Fedora 39)"""

-         if self.flatpak_spec.build_runtime:

+         if self.flatpak_spec.build_runtime or self.flatpak_spec.build_extension:

              runtime_version = self.flatpak_spec.branch

          else:

              runtime_version = self.runtime_info.version
@@ -151,15 +151,15 @@ 

              local_repo_path = self.local_repo

  

          if for_container:

-             if self.flatpak_spec.build_runtime:

-                 repos.append(self.runtime_package_repo.dnf_config(priority=10))

+             if self.flatpak_spec.build_runtime or self.flatpak_spec.build_extension:

+                 repos.append(self.runtime_package_repo.dnf_config(priority=20))

              else:

                  repos.append(self.runtime_package_repo.dnf_config(

                      priority=10, includepkgs=self.runtime_packages

                  ))

                  repos.append(self.app_package_repo.dnf_config(priority=20))

          else:

-             if self.flatpak_spec.build_runtime:

+             if self.flatpak_spec.build_runtime or self.flatpak_spec.build_extension:

                  raise NotImplementedError("Runtime package building is not implemented")

              repos.append(self.app_build_repo.dnf_config(priority=20))

  
@@ -198,7 +198,7 @@ 

  

      @cached_property

      def nvr(self):

-         if self.flatpak_spec.build_runtime:

+         if self.flatpak_spec.build_runtime or self.flatpak_spec.build_extension:

              name = self.container_spec.flatpak.component or self.container_spec.flatpak.name

              return f"{name}-{self.container_spec.flatpak.branch}-1"

          else:

@@ -71,6 +71,7 @@ 

  

      async def wait_for_event(self, event):

          if self.last_repo_event < event:

+             self.session.repo.request(self.tag, min_event=event)

              while True:

                  next_repo_event = await self.get_next_repo_event()

                  if next_repo_event >= event:

file modified
+2 -2
@@ -218,7 +218,7 @@ 

          target=target

      )

  

-     if not container_spec.flatpak.build_runtime:

+     if not container_spec.flatpak.build_runtime and not container_spec.flatpak.build_extension:

          rpm_builder = RpmBuilder(build_context, workdir=paths.workdir)

          rpm_builder.check(include_localrepo=False, allow_outdated=allow_outdated)

  
@@ -289,7 +289,7 @@ 

          local_runtime=local_runtime, target=target

      )

  

-     if not container_spec.flatpak.build_runtime:

+     if not container_spec.flatpak.build_runtime and not container_spec.flatpak.build_extension:

          rpm_builder = RpmBuilder(build_context, workdir=paths.workdir)

          rpm_builder.check(include_localrepo=True, allow_outdated=allow_outdated)

  

@@ -2,8 +2,8 @@ 

  import os

  from typing import Optional

  

+ import importlib.resources

  import koji

- import pkg_resources

  import re

  import yaml

  
@@ -168,7 +168,7 @@ 

  

      def read(self):

          default_config_file = \

-             pkg_resources.resource_stream('flatpak_module_tools', 'config.yaml')

+             importlib.resources.open_binary('flatpak_module_tools', 'config.yaml')

          self._read_config_file(default_config_file)

  

          for config_file in self._iter_config_files():

@@ -245,6 +245,14 @@ 

                                   mounts=mounts, enable_network=True)

  

      def _cleanup_tree(self, builder: FlatpakBuilder):

+         extdirs = []

+         for ext in self.context.flatpak_spec.add_extensions:

+             extdirs += [os.path.join(self.executor.installroot,

+                                      "usr" if self.context.flatpak_spec.build_runtime else "app",

+                                      ext.directory)]

+         if extdirs:

+             self.executor.check_call(["mkdir", "-p"] + extdirs)

+ 

          script = builder.get_cleanup_script()

          if not script or script.strip() == "":

              return
@@ -277,7 +285,7 @@ 

          info(f"    wrote {outname_base}.config.json")

  

      def _create_rpm_manifest(self, outname_base: Path):

-         if self.context.flatpak_spec.build_runtime:

+         if self.context.flatpak_spec.build_runtime or self.context.flatpak_spec.build_extension:

              restrict_to = None

          else:

              restrict_to = self.executor.absolute_installroot / "app"
@@ -294,7 +302,7 @@ 

  

          self.executor = executor

  

-         if self.context.flatpak_spec.build_runtime:

+         if self.context.flatpak_spec.build_runtime or self.context.flatpak_spec.build_extension:

              runtime_info = None

          else:

              runtime_info = self.context.runtime_info
@@ -369,7 +377,7 @@ 

      def assemble(self, *,

                   installroot: Path, workdir: Path, resultdir: Path):

  

-         if self.context.flatpak_spec.build_runtime:

+         if self.context.flatpak_spec.build_runtime or self.context.flatpak_spec.build_extension:

              runtimever = self.context.nvr.rsplit('-', 2)[1]

          else:

              runtimever = self.context.runtime_info.version
@@ -400,7 +408,7 @@ 

  

          installroot = Path("/contents")

  

-         if self.context.flatpak_spec.build_runtime:

+         if self.context.flatpak_spec.build_runtime or self.context.flatpak_spec.build_extension:

              runtimever = self.context.nvr.rsplit('-', 2)[1]

          else:

              runtimever = self.context.runtime_info.version

@@ -79,6 +79,36 @@ 

          return self._get(key, type_convert, default)

  

      @overload

+     def _get_uint(self, key: str, default: Literal[Option.REQUIRED]) -> int:

+         ...

+ 

+     @overload

+     def _get_uint(self, key: str) -> int:

+         ...

+ 

+     @overload

+     def _get_uint(self, key: str, default: int) -> int:

+         ...

+ 

+     @overload

+     def _get_uint(self, key: str, default: None) -> Optional[int]:

+         ...

+ 

+     def _get_uint(

+             self, key: str, default: Union[Literal[Option.REQUIRED], int, None] = Option.REQUIRED

+     ) -> Optional[int]:

+         def type_convert(val):

+             if isinstance(val, (str, int, float)):

+                 ret = int(val)

+                 if ret < 0:

+                     raise ValidationError(f"{self.path}: {key} must be a non-negative integer")

+                 return ret

+             else:

+                 raise ValidationError(f"{self.path}: {key} must be a non-negative integer")

+ 

+         return self._get(key, type_convert, default)

+ 

+     @overload

      def _get_str_list(self, key: str,

                        default: Literal[Option.REQUIRED], allow_scalar=False) -> List[str]:

          ...
@@ -136,7 +166,66 @@ 

                  self.platforms = None

  

  

+ class ExtensionSpec(BaseSpec):

+     def __init__(self, path, extname, yaml_object):

+         super().__init__(path + "/" + extname, yaml_object)

+         # string, mandatory

+         self.directory = self._get_str('directory')

+         self.args = [f"--extension={extname}=directory={self.directory}"]

+         # string, optional

+         for arg in ('version', 'versions', 'add-ld-path', 'merge-dirs', 'download-if',

+                     'autoprune-unless', 'enable-if', 'subdirectory-suffix'):

+             val = self._get_str(arg, None)

+             if val:

+                 self.args += [f"--extension={extname}={arg}={val}"]

+         # boolean, optional

+         for arg in ('subdirectories', 'no-autodownload', 'autodelete'):

+             val = self._get_bool(arg, False)

+             if val:

+                 self.args += [f"--extension={extname}={arg}=true"]

+ 

+ 

+ class ExtraDataSpec(BaseSpec):

+     def __init__(self, path, yaml_object):

+         super().__init__(path, yaml_object)

+         name = self._get_str('filename')

+         uri = self._get_str('url')

+         checksum = self._get_str('sha256')

+         size = self._get_uint('size')

+         installed_size = self._get_uint('installed-size', 0)

+         self.variant_data =  [(name.encode("UTF-8") + b'\x00',

+                               int.from_bytes(size.to_bytes(8, byteorder="little", signed=False), byteorder="big"),

+                               int.from_bytes(installed_size.to_bytes(8, byteorder="little", signed=False), byteorder="big"),

+                               bytes.fromhex(checksum),

+                               uri)]

+         installed_size_arg = str(installed_size) if installed_size > 0 else ""

+         self.finish_args = [f"--extra-data={name}:{checksum}:{size}:{installed_size_arg}:{uri}"]

+         self.only_arches = self._get_str_list('only-arches', None)

+ 

+ 

  class FlatpakSpec(BaseSpec):

+     def _get_extension_items(self, key, default) -> List["ExtensionSpec"]:

+         def type_convert(val):

+             if isinstance(val, dict) and all(isinstance(v, dict) for i, v in val.items()):

+                 return [

+                     ExtensionSpec(f"{self.path}", i, v) for i, v in val.items()

+                 ]

+             else:

+                 raise ValidationError(f"{self.path}: {key} must be a mapping of mappings")

+ 

+         return self._get(key, type_convert, default)

+ 

+     def _get_extra_data_list(self, key, default) -> List["ExtraDataSpec"]:

+         def type_convert(val):

+             if isinstance(val, List) and all(isinstance(v, dict) for v in val):

+                 return [

+                     ExtraDataSpec(f"{self.path}/{i}", v) for i, v in enumerate(val)

+                 ]

+             else:

+                 raise ValidationError(f"{self.path}: {key} must be a list of mappings")

+ 

+         return self._get(key, type_convert, default)

+ 

      def _get_package_list(self, key, default) -> List["PackageSpec"]:

          def type_convert(val):

              if isinstance(val, List) and all(isinstance(v, (str, dict)) for v in val):
@@ -151,6 +240,7 @@ 

      def __init__(self, path, flatpak_yaml):

          super().__init__(path, flatpak_yaml)

  

+         self.add_extensions = self._get_extension_items('add-extensions', [])

          self.app_id = self._get_str("id")

          self.appdata_license = self._get_str('appdata-license', None)

          self.appstream_compose = self._get_bool('appstream-compose', True)
@@ -159,6 +249,7 @@ 

          self.base_image = self._get_str('base_image', None)

          self.branch = self._get_str('branch', 'stable')

          self.build_runtime = self._get_bool('build-runtime', False)

+         self.build_extension = self._get_bool('build-extension', False)

          self.cleanup_commands = self._get_str('cleanup-commands', None)

          self.command = self._get_str('command', None)

          self.component = self._get_str('component', None)
@@ -167,12 +258,15 @@ 

          self.desktop_file_name_suffix = self._get_str('desktop-file-name-suffix', None)

          self.end_of_life = self._get_str('end-of-life', None)

          self.end_of_life_rebase = self._get_str('end-of-life-rebase', None)

+         self.extra_data = self._get_extra_data_list('extra-data', [])

          self.finish_args = self._get_str('finish-args', None)

          self.name = self._get_str('name', None)

          self.packages = self._get_package_list('packages', [])

          self.rename_appdata_file = self._get_str('rename-appdata-file', None)

          self.rename_desktop_file = self._get_str('rename-desktop-file', None)

+         self.rename_mime_file = self._get_str('rename-mime-file', None)

          self.rename_icon = self._get_str('rename-icon', None)

+         self.rename_mime_icons = self._get_str_list('rename-mime-icons', [])

          self.runtime = self._get_str('runtime', None)

          self.runtime_name = self._get_str('runtime-name', None)

          self.runtime_version = self._get_str('runtime-version', None)
@@ -250,7 +344,7 @@ 

                      )

                  )

          else:

-             if self.flatpak.build_runtime:

+             if self.flatpak.build_runtime or self.flatpak.build_extension:

                  required_attrs = NEW_STYLE_ATTRS_RUNTME

              else:

                  required_attrs = NEW_STYLE_ATTRS

@@ -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)

@@ -42,16 +42,21 @@ 

  

  

  def _load_flathub_manifest(search_term):

-     response = requests.get("https://flathub.org/api/v1/apps")

-     response.raise_for_status()

-     apps = response.json()

- 

      matches = []

-     search_lower = search_term.lower()

-     for app in apps:

-         if (search_lower in app['flatpakAppId'].lower() or

-                 search_lower in app['name'].lower()):

-             matches.append((app['flatpakAppId'], app['name']))

+     # first, see if the complete id was passed

+     if search_term.count('.') >= 2:

+         response = requests.get(f"https://flathub.org/api/v2/summary/{search_term}")

+         if response.status_code == 200:

+             matches.append((search_term, search_term))

+ 

+     if len(matches) == 0:

+         search_data = {'query': search_term.lower()}

+         response = requests.post("https://flathub.org/api/v2/search", data=json.dumps(search_data))

+         response.raise_for_status()

+         apps = response.json()

+ 

+         for app in apps['hits']:

+             matches.append((app['app_id'], app['name']))

  

      if len(matches) > 1:

          max_id_len = max([len(app_id) for app_id, _ in matches])
@@ -96,6 +101,16 @@ 

          app_id = manifest.get('app-id')

          if app_id is None:

              app_id = manifest['id']

+         if runtime_name is None:

+             flathub_runtime = manifest['runtime']

+             flathub_runtime_version = manifest['runtime-version']

+             if flathub_runtime == 'org.kde.Platform' and flathub_runtime_version.split('.')[0] == '6':

+                 runtime_name = 'flatpak-kde6-runtime'

+             elif flathub_runtime == 'org.kde.Platform' and flathub_runtime_version.split('.')[0] == '5':

+                 runtime_name = 'flatpak-kde5-runtime'

+             else:

+                 runtime_name = 'flatpak-runtime'

+ 

          yml = NoSortMapping({

              'flatpak': NoSortMapping({

                  'id': app_id,
@@ -114,7 +129,9 @@ 

                      'desktop-file-name-suffix',

                      'rename-appdata-file',

                      'rename-desktop-file',

+                     'rename-mime-file',

                      'rename-icon',

+                     'rename-mime-icons',

                      'copy-icon']:

              if key in manifest:

                  yml['flatpak'][key] = manifest[key]
@@ -129,6 +146,9 @@ 

          command = pkg

          branch = 'f' + str(runtime_version)

  

+         if runtime_name is None:

+             runtime_name = 'flatpak-runtime'

+ 

          container_yaml = dedent(f'''\

              flatpak:

                  # Derived from the project's domain name
@@ -177,9 +197,6 @@ 

                  raise click.ClickException(f"{output_containerspec} exists."

                                             f" Pass --force to overwrite.")

  

-         if runtime_name is None:

-             runtime_name = 'flatpak-runtime'

- 

          if runtime_version is None:

              response = requests.get("https://bodhi.fedoraproject.org/releases/?state=current")

              response.raise_for_status()

@@ -8,6 +8,7 @@ 

  

  import click

  import koji

+ from koji_cli.lib import activate_session

  import networkx

  

  from .build_scheduler import KojiBuildScheduler, MockBuildScheduler
@@ -196,7 +197,10 @@ 

                  source_tag_infos = source_session.listTagged(

                      source_tag, latest=True, inherit=True, package=package

                  )

-                 source_version_info = StrippedVersionInfo.from_dict(source_tag_infos[0])

+                 if source_tag_infos:

+                     source_version_info = StrippedVersionInfo.from_dict(source_tag_infos[0])

+                 else:

+                     source_version_info = None

  

                  ok = False

  
@@ -205,7 +209,7 @@ 

                  )

                  if package_tag_infos:

                      package_version_info = StrippedVersionInfo.from_dict(package_tag_infos[0])

-                     if allow_outdated or package_version_info >= source_version_info:

+                     if allow_outdated or not source_version_info or package_version_info >= source_version_info:

                          ok = True

  

                          create_event = package_tag_infos[0]["create_event"]
@@ -217,13 +221,13 @@ 

  

                  local_version_info = localrepo_package_versions.get(package)

                  if local_version_info:

-                     if allow_outdated or local_version_info >= source_version_info:

+                     if allow_outdated or not source_version_info or local_version_info >= source_version_info:

                          ok = True

  

                  display_table.append((

                      ok,

                      package,

-                     str(source_version_info),

+                     str(source_version_info) if source_version_info else "",

                      str(package_version_info) if package_version_info else "",

                      str(local_version_info) if local_version_info else "",

                      "(forced)" if package in manual_packages else "",
@@ -380,10 +384,18 @@ 

          if wait_for_event >= 0:

              with Status("Waiting for repository with necessary packages"):

                  session = self.profile.koji_session

+                 first = True

                  while True:

                      repo_info = session.getRepo(package_tag, dist=package_dist_repo)

                      if repo_info["create_event"] >= wait_for_event:

                          break

+                     if first and not package_dist_repo:

+                         # dist repos are automatically created through the

+                         # tag2distrepo plugin, so we don't need to request them

+                         # (and can't - it's not currently supported by repo.request)

+                         activate_session(session, self.profile.koji_options)

+                         self.session.repo.request(package_tag, min_event=wait_for_event)

+                         first = False

                      time.sleep(20)

  

      def _prompt_for_rebuild(self, manual_packages: Collection[str],
@@ -482,9 +494,19 @@ 

          if not to_build:

              return

  

-         latest_builds = self._get_latest_builds(to_build)

- 

-         build_after = self._compute_build_order(latest_builds, include_localrepo=True)

+         try:

+             latest_builds = self._get_latest_builds(to_build)

+             build_after = self._compute_build_order(latest_builds, include_localrepo=True)

+         except Exception as e:

+             if not manual_packages and not auto:

+                 # all packages are manual_repos, just build what has been passed,

+                 # without taking responsibility for the build order

+                 latest_builds = repo_map

+                 build_after = {

+                     repo.name: set() for repo in manual_repos

+                 }

+             else:

+                 raise e

  

          mock_cfg = make_mock_cfg(

              arch=self.arch,

file modified
+5 -3
@@ -13,6 +13,7 @@ 

  

  dependencies = [

      "click",

+     "PyGObject",

      "pyyaml",

      "requests",

      "rpm",
@@ -32,7 +33,6 @@ 

      "koji",

      "networkx",

      "requests-toolbelt",

-     "setuptools",

      "solv",

  ]

  
@@ -41,12 +41,10 @@ 

      "jinja2",

      "koji",

      "networkx",

-     "PyGObject",

      "pytest",

      "pytest-cov",

      "responses",

      "rpm",

-     "setuptools",

  ]

  

  [tool.setuptools]
@@ -65,6 +63,10 @@ 

  

  [tool.pytest.ini_options]

  addopts = "--cov=flatpak_module_tools --cov-report=term-missing --cov-report=html"

+ markers = [

+     "container_yaml",

+     "needs_metadata"

+ ]

  testpaths = [

      "tests",

  ]

@@ -0,0 +1,32 @@ 

+ {

+     "estimatedTotalHits": 2,

+     "hits": [

+         {

+             "app_id": "org.gnome.Crosswords",

+             "arches": [

+                 "aarch64",

+                 "x86_64"

+             ],

+             "id": "org_gnome_Crosswords",

+             "name": "Crosswords",

+             "project_license": "GPL-3.0-or-later",

+             "runtime": "org.gnome.Platform/x86_64/46",

+             "summary": "Solve crossword puzzles",

+             "type": "desktop-application"

+         },

+         {

+             "app_id": "org.gnome.Crosswords.Editor",

+             "arches": [

+                 "aarch64",

+                 "x86_64"

+             ],

+             "id": "org_gnome_Crosswords_Editor",

+             "name": "Crossword Editor",

+             "project_license": "GPL-3.0-or-later",

+             "runtime": "org.gnome.Platform/x86_64/46",

+             "summary": "Create crossword puzzles",

+             "type": "desktop-application"

+         }

+     ],

+     "query": "crossword"

+ }

@@ -0,0 +1,6 @@ 

+ {

+     "estimatedTotalHits": 0,

+     "hits": [

+     ],

+     "query": "foobar"

+ }

@@ -1,7 +1,19 @@ 

- [{"flatpakAppId":"org.gnome.eog",

-  "name":"Eye of GNOME",

-  "summary":"Browse and rotate images",

-  "iconDesktopUrl":"/repo/appstream/x86_64/icons/128x128/org.gnome.eog.png"},

- {"flatpakAppId":"org.gnome.FeedReader",

-  "name":"FeedReader","summary":"RSS client for various webservices",

-  "iconDesktopUrl":"/repo/appstream/x86_64/icons/128x128/org.gnome.FeedReader.png"}]

+ {

+     "estimatedTotalHits": 1,

+     "hits": [

+         {

+             "app_id": "org.gnome.eog",

+             "arches": [

+                 "aarch64",

+                 "x86_64"

+             ],

+             "id": "org_gnome_eog",

+             "name": "Eye of GNOME",

+             "project_license": "GPL-2.0+ and GFDL-1.3",

+             "runtime": "org.gnome.Platform/x86_64/46",

+             "summary": "Browse and rotate images",

+             "type": "desktop-application"

+         }

+     ],

+     "query": "eog"

+ }

file modified
+1 -1
@@ -210,7 +210,7 @@ 

              baseurl=https://kojifiles.example.com/repos/f39-flatpak-runtime-packages/latest/$basearch/

              enabled=1

              skip_if_unavailable=False

-             priority=10

+             priority=20

              """)

      ]

  

@@ -93,6 +93,43 @@ 

      command: eog

  """

  

+ EXTENSION_CONTAINER_YAML = """\

+ flatpak:

+     id: org.fedoraproject.Platform.openh264

+     build-extension: true

+     runtime: org.fedoraproject.Platform

+     runtime-version: f41

+     sdk: org.fedoraproject.Sdk

+     name: openh264

+     component: openh264-flatpak

+     branch: 2.4.1

+     packages:

+     - noopenh264

+     extra-data:

+     - filename: openh264-2.4.1-2.fc41.aarch64.rpm

+       url: https://codecs.fedoraproject.org/openh264/41/aarch64/os/Packages/o/openh264-2.4.1-2.fc41.aarch64.rpm

+       sha256: bf8617eaca86dd1cc6fc61cfa3d08da09b0cbd6f6a3318174d58c33b479bd512

+       size: 393795

+       only-arches:

+       - aarch64

+     - filename: openh264-2.4.1-2.fc41.ppc64le.rpm

+       url: https://codecs.fedoraproject.org/openh264/41/ppc64le/os/Packages/o/openh264-2.4.1-2.fc41.ppc64le.rpm

+       sha256: a585ddfaccb3fdc336e532978002273b8c71132092a189f1496a80c841fd92ac

+       size: 425361

+       only-arches:

+       - ppc64le

+     - filename: openh264-2.4.1-2.fc41.x86_64.rpm

+       url: https://codecs.fedoraproject.org/openh264/41/x86_64/os/Packages/o/openh264-2.4.1-2.fc41.x86_64.rpm

+       sha256: afe17383b2d009aa671332ff9f5ef0a94bd756e086efa6a60c3fe073a7289cd1

+       size: 430302

+       only-arches:

+       - x86_64

+     cleanup-commands: |

+         mkdir -p /app/bin

+         echo -e "#!/bin/sh\\nbsdtar --strip-components=3 -xf *.rpm\\nrm -f *.rpm" > /app/bin/apply_extra

+         chmod +x /app/bin/apply_extra

+ """

+ 

  RUNTIME_CONTAINER_YAML = """\

  flatpak:

      id: org.fedoraproject.Platform
@@ -101,6 +138,15 @@ 

      component: flatpak-runtime

      branch: f39

      sdk: org.fedoraproject.Sdk

+     add-extensions:

+         org.fedoraproject.Platform.hunspell:

+             directory: share/myspell/dicts

+             versions: stable;f39

+         org.fedoraproject.Platform.openh264:

+             directory: lib64/openh264

+             version: '2.4.1'

+             add-ld-path: extra

+             autodelete: true

      finish-args: >-

          --env=GI_TYPELIB_PATH=/app/lib64/girepository-1.0

          --env=GST_PLUGIN_SYSTEM_PATH=/app/lib64/gstreamer-1.0:/usr/lib64/gstreamer-1.0
@@ -169,6 +215,29 @@ 

      assert not spec.platforms.includes_platform("aarch64")

  

  

+ def test_extension_container_spec(tmp_path):

+     spec = make_spec(tmp_path, EXTENSION_CONTAINER_YAML)

+     assert spec.flatpak.app_id == "org.fedoraproject.Platform.openh264"

+     assert spec.flatpak.branch == "2.4.1"

+     assert spec.flatpak.build_extension is True

+     assert spec.flatpak.component == "openh264-flatpak"

+     assert spec.flatpak.name == "openh264"

+     assert spec.flatpak.runtime == "org.fedoraproject.Platform"

+     assert spec.flatpak.runtime_version == "f41"

+     assert spec.flatpak.sdk == "org.fedoraproject.Sdk"

+     assert spec.flatpak.extra_data[0].finish_args == [

+         "--extra-data=openh264-2.4.1-2.fc41.aarch64.rpm:bf8617eaca86dd1cc6fc61cfa3d08da09b0cbd6f6a3318174d58c33b479bd512:393795::https://codecs.fedoraproject.org/openh264/41/aarch64/os/Packages/o/openh264-2.4.1-2.fc41.aarch64.rpm",

+     ]

+     assert spec.flatpak.extra_data[0].only_arches == ["aarch64"]

+     assert spec.flatpak.extra_data[1].finish_args == [

+         "--extra-data=openh264-2.4.1-2.fc41.ppc64le.rpm:a585ddfaccb3fdc336e532978002273b8c71132092a189f1496a80c841fd92ac:425361::https://codecs.fedoraproject.org/openh264/41/ppc64le/os/Packages/o/openh264-2.4.1-2.fc41.ppc64le.rpm",

+     ]

+     assert spec.flatpak.extra_data[1].only_arches == ["ppc64le"]

+     assert spec.flatpak.extra_data[2].finish_args == [

+         "--extra-data=openh264-2.4.1-2.fc41.x86_64.rpm:afe17383b2d009aa671332ff9f5ef0a94bd756e086efa6a60c3fe073a7289cd1:430302::https://codecs.fedoraproject.org/openh264/41/x86_64/os/Packages/o/openh264-2.4.1-2.fc41.x86_64.rpm",

+     ]

+     assert spec.flatpak.extra_data[2].only_arches == ["x86_64"]

+ 

  def test_runtime_container_spec(tmp_path):

      spec = make_spec(tmp_path, RUNTIME_CONTAINER_YAML)

  
@@ -182,6 +251,18 @@ 

          "--env=GI_TYPELIB_PATH=/app/lib64/girepository-1.0 "

          "--env=GST_PLUGIN_SYSTEM_PATH=/app/lib64/gstreamer-1.0:/usr/lib64/gstreamer-1.0"

      )

+     assert spec.flatpak.add_extensions[0].args == [

+         "--extension=org.fedoraproject.Platform.hunspell=directory=share/myspell/dicts",

+         "--extension=org.fedoraproject.Platform.hunspell=versions=stable;f39",

+     ]

+     assert spec.flatpak.add_extensions[0].directory == "share/myspell/dicts"

+     assert spec.flatpak.add_extensions[1].args == [

+         "--extension=org.fedoraproject.Platform.openh264=directory=lib64/openh264",

+         "--extension=org.fedoraproject.Platform.openh264=version=2.4.1",

+         "--extension=org.fedoraproject.Platform.openh264=add-ld-path=extra",

+         "--extension=org.fedoraproject.Platform.openh264=autodelete=true",

+     ]

+     assert spec.flatpak.add_extensions[1].directory == "lib64/openh264"

      assert spec.flatpak.cleanup_commands == dedent("""\

          mv -f /usr/bin/flatpak-xdg-email /usr/bin/xdg-email

          mv -f /usr/bin/flatpak-xdg-open /usr/bin/xdg-open

@@ -175,7 +175,7 @@ 

  

  @pytest.fixture

  def runtime_module(R):

-     runtime_mmd = Modulemd.ModuleStream.read_string(FLATPAK_RUNTIME_MMD, True)

+     runtime_mmd = Modulemd.read_packager_string(FLATPAK_RUNTIME_MMD)

      yield ModuleInfo(runtime_mmd.get_module_name(),

                       runtime_mmd.get_stream_name(),

                       runtime_mmd.get_version(),
@@ -188,7 +188,7 @@ 

  def testapp_module(arch, R):

      testapp_mmd = TESTAPP_MMD.replace("@ARCH@", arch.rpm)

  

-     testapp_mmd = Modulemd.ModuleStream.read_string(testapp_mmd, True)

+     testapp_mmd = Modulemd.read_packager_string(testapp_mmd)

      yield ModuleInfo(testapp_mmd.get_module_name(),

                       testapp_mmd.get_stream_name(),

                       testapp_mmd.get_version(),

file modified
+18 -10
@@ -20,6 +20,12 @@ 

  with open(os.path.join(testfiles_dir, 'apps.json')) as f:

      APPS_JSON = f.read()

  

+ with open(os.path.join(testfiles_dir, 'apps-multiple.json')) as f:

+     APPS_MULTIPLE_JSON = f.read()

+ 

+ with open(os.path.join(testfiles_dir, 'apps-noent.json')) as f:

+     APPS_NOENT_JSON = f.read()

+ 

  with open(os.path.join(testfiles_dir, 'eog.yaml')) as f:

      EOG_YAML = f.read()

  
@@ -82,21 +88,23 @@ 

  

      @responses.activate

      @pytest.mark.needs_metadata

-     @pytest.mark.parametrize(('search_term', 'extension', 'expected_error'),

+     @pytest.mark.parametrize(('search_term', 'response_body', 'extension', 'expected_error'),

                               [

-                                  ('org.gnome.eog', 'yaml', None),

-                                  ('org.gnome.eog', 'yml', None),

-                                  ('org.gnome.eog', 'json', None),

-                                  ('eYe of gNome', 'yaml', None),

-                                  ('org.gnome', 'yaml',

+                                  ('org.gnome.eog', APPS_JSON, 'yaml', None),

+                                  ('org.gnome.eog', APPS_JSON, 'yml', None),

+                                  ('org.gnome.eog', APPS_JSON, 'json', None),

+                                  ('eYe of gNome', APPS_JSON, 'yaml', None),

+                                  ('org.gnome', APPS_MULTIPLE_JSON, 'yaml',

                                    'Multiple matches found on flathub.org'),

-                                  ('notexist', 'yaml',

+                                  ('notexist', APPS_NOENT_JSON, 'yaml',

                                    'No match found on flathub.org'),

                               ])

-     def test_flatpak_from_flathub(self, search_term, extension,

+     def test_flatpak_from_flathub(self, search_term, response_body, extension,

                                    expected_error):

-         responses.add(responses.GET, 'https://flathub.org/api/v1/apps',

-                       body=APPS_JSON, content_type='application/json')

+         responses.add(responses.POST, 'https://flathub.org/api/v2/search',

+                       body=response_body, content_type='application/json')

+         responses.add(responses.GET, 'https://flathub.org/api/v2/summary/org.gnome.eog',

+                       status=200)

          responses.add(responses.GET, 'https://bodhi.fedoraproject.org/releases/?state=current',

                        body=RELEASES_JSON, content_type='application/json')

  

@@ -112,7 +112,7 @@ 

              }

              return (200, headers, BufferedReader(RawBytesReader(compressed_data)))

  

-         super().__init__(method=method, url=url, callback=callback, stream=True, **kwargs)

+         super().__init__(method=method, url=url, callback=callback, **kwargs)

  

  

  @pytest.mark.parametrize("compress_type,extension", [
@@ -141,7 +141,8 @@ 

          responses.GET,

          f"https://repos.example.com/basic/ppc64le/repodata/HASH-primary.xml{extension}",

          body_str=BASIC_PRIMARY_XML,

-         compress_type=compress_type

+         compress_type=compress_type,

+         match=[responses.matchers.request_kwargs_matcher({"stream": True})]

      ))

  

      # basic operation - find highest version

file modified
+2 -2
@@ -59,7 +59,7 @@ 

      )

  

      subprocess.check_call([

-         "rpm", "--root", root, "-Uvh", testrpm, testrpm_epoch, testrpm_usr

+         "unshare", "--mount", "--user", "--map-root-user", "rpm", "--root", root, "-Uvh", testrpm, testrpm_epoch, testrpm_usr

      ])

  

      # We don't necessarily expect GPG keys to be imported into the roots we
@@ -69,7 +69,7 @@ 

          f.write(FEDORA_GPG_KEY_RAWHIDE_X86_64)

  

      subprocess.check_call([

-         "rpm", "--root", root, "--import", gpg_key

+         "unshare", "--mount", "--user", "--map-root-user", "rpm", "--root", root, "--import", gpg_key

      ])

  

      return root

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, and branch, should list packages as normal, and then include a cleanup-commands to move the correct files to the necessary layout under /app.

3 new commits added

  • Support building extensions
  • Allow local RPMs in local runtime builds
  • Fix for Koji 1.35
2 months ago

1 new commit added

  • Support rename-mime-file
2 months ago

4 new commits added

  • Support rename-mime-file
  • Support building extensions
  • Allow local RPMs in local runtime builds
  • Fix for Koji 1.35
2 months ago

1 new commit added

  • Support add-extensions
2 months ago

5 new commits added

  • Support add-extensions
  • Support rename-mime-file
  • Support building extensions
  • Allow local RPMs in local runtime builds
  • Fix for Koji 1.35
2 months ago

1 new commit added

  • Support rename-mime-icons
2 months ago

1 new commit added

  • Update generator for Flathub v2 API
2 months ago

7 new commits added

  • Update generator for Flathub v2 API
  • Support rename-mime-icons
  • Support add-extensions
  • Support rename-mime-file
  • Support building extensions
  • Allow local RPMs in local runtime builds
  • Fix for Koji 1.35
2 months ago

Example:

flatpak:
    id: org.fedoraproject.Platform.GL.default
    build-extension: true
    runtime-version: f41
    name: f41/mesa
    component: mesa-flatpak
    branch: f41
    packages:
    - mesa-filesystem
    - mesa-libd3d
    - mesa-libEGL
    - mesa-libgbm
    - mesa-libGL
    - mesa-libglapi
    - mesa-libOpenCL
    - mesa-libTeflon
    - mesa-dri-drivers
    - mesa-va-drivers
    - mesa-vdpau-drivers
    - mesa-vulkan-drivers
    # basic dependencies
    - libdrm
    - libxshmfence
    - lm_sensors-libs
    # OpenCL dependencies
    - clang-libs
    - libclc
    - llvm-libs
    - spirv-llvm-translator
    # for cleanup-commands (part of runtime)
    - coreutils-single
    - findutils
    - sed
    cleanup-commands: |
        mkdir -p /app/lib /app/share
        for l in libclang-cpp libdrm_* libEGL_mesa libgallium-* libgbm libglapi \
                 libGLX_mesa libLLVM libLLVMSPIRVLib libMesaOpenCL libRusticlOpenCL \
                 libteflon libVkLayer_MESA* libpowervr_rogue libvulkan_* \
                 libsensors libxshmfence ; \
            do mv /usr/lib64/$l.so* /app/lib/; \
        done
        mv /usr/lib64/{d3d,dri,gallium-pipe,vdpau}/ /app/lib/
        mv /usr/share/{drirc.d,glvnd,vulkan}/ /etc/OpenCL /app/share/
        ln -s lib/vdpau share/glvnd share/OpenCL share/vulkan /app/
        sed -i -e 's|/usr/lib64/|/usr/lib64/GL/default/lib/|' /app/share/vulkan/icd.d/*
        find /app -print

Another example:

flatpak:
    id: org.fedoraproject.Platform.hunspell
    build-extension: true
    runtime-version: f41
    name: hunspell
    component: hunspell-flatpak
    branch: stable
    packages:
    - hunspell-af
    - hunspell-ak
    - hunspell-am
    - hunspell-ar
    - hunspell-as
    - hunspell-ast
    - hunspell-az
    - hunspell-be
    - hunspell-ber
    - hunspell-bg
    - hunspell-bn
    - hunspell-br
    - hunspell-ca
    - hunspell-cop
    - hunspell-cs
    - hunspell-csb
    - hunspell-cv
    - hunspell-cy
    - hunspell-da
    - hunspell-de
    - hunspell-dsb
    - hunspell-ee
    - hunspell-el
    - hunspell-en
    - hunspell-eo
    - hunspell-es
    - hunspell-et
    - hunspell-eu
    - hunspell-fa
    - hunspell-fj
    - hunspell-fo
    - hunspell-fr
    - hunspell-fur
    - hunspell-fy
    - hunspell-ga
    - hunspell-gd
    - hunspell-gl
    - hunspell-grc
    - hunspell-gu
    - hunspell-gv
    - hunspell-haw
    - hunspell-he
    - hunspell-hi
    - hunspell-hil
    - hunspell-hr
    - hunspell-hsb
    - hunspell-ht
    - hunspell-hu
    - hunspell-ky
    - hunspell-ia
    - hunspell-id
    - hunspell-is
    - hunspell-it
    - hunspell-kk
    - hunspell-km
    - hunspell-kn
    - hunspell-ko
    - hunspell-ku
    - hunspell-ky
    - hunspell-la
    - hunspell-lb
    - hunspell-ln
    - hunspell-lt
    - hunspell-lv
    - hunspell-mai
    - hunspell-mg
    - hunspell-mi
    - hunspell-mk
    - hunspell-ml
    - hunspell-mn
    - hunspell-mos
    - hunspell-mr
    - hunspell-ms
    - hunspell-mt
    - hunspell-nb
    - hunspell-nds
    - hunspell-ne
    - hunspell-nl
    - hunspell-nn
    - hunspell-nr
    - hunspell-nso
    - hunspell-ny
    - hunspell-oc
    - hunspell-om
    - hunspell-or
    - hunspell-pa
    - hunspell-pl
    - hunspell-pt
    - hunspell-pt-BR
    - hunspell-qu
    - hunspell-quh
    - hunspell-ro
    - hunspell-ru
    - hunspell-rw
    - hunspell-sc
    - hunspell-se
    - hunspell-shs
    - hunspell-si
    - hunspell-sk
    - hunspell-sl
    - hunspell-sk
    - hunspell-sl
    - hunspell-smj
    - hunspell-so
    - hunspell-sq
    - hunspell-sr
    - hunspell-ss
    - hunspell-st
    - hunspell-sv
    - hunspell-sw
    - hunspell-ta
    - hunspell-te
    - hunspell-tet
    - hunspell-th
    - hunspell-tk
    - hunspell-ti
    - hunspell-tn
    - hunspell-tpi
    - hunspell-tr
    - hunspell-ts
    - hunspell-uk
    - hunspell-ur
    - hunspell-uz
    - hunspell-ve
    - hunspell-vi
    - hunspell-wa
    - hunspell-xh
    - hunspell-yi
    - hunspell-zu
    # for cleanup-commands (part of runtime)
    - coreutils-single
    - findutils
    cleanup-commands: |
        mkdir -p /app
        mv /usr/share/hunspell/* /app/

SDK extension example:

flatpak:
    id: org.fedoraproject.Sdk.Extension.golang
    build-extension: true
    runtime-version: f41
    name: f41/golang
    component: golang-flatpak
    branch: f41
    packages:
    - golang
    - golang-bin
    - golang-docs
    - golang-misc
    - golang-src
    # for cleanup-commands (part of runtime)
    - coreutils-single
    - findutils
    cleanup-commands: |
        mkdir -p /app
        mv /usr/lib/golang/* /app/
        echo -e "#!/bin/sh\nexport GOROOT=/usr/lib/sdk/golang\nexport PATH=\$PATH:/usr/lib/sdk/golang/bin" > /app/enable.sh
        chmod +x /app/enable.sh

8 new commits added

  • Support extra-data
  • Update generator for Flathub v2 API
  • Support rename-mime-icons
  • Support add-extensions
  • Support rename-mime-file
  • Support building extensions
  • Allow local RPMs in local runtime builds
  • Fix for Koji 1.35
a month ago

8 new commits added

  • Support extra-data
  • Update generator for Flathub v2 API
  • Support rename-mime-icons
  • Support add-extensions
  • Support rename-mime-file
  • Support building extensions
  • Allow local RPMs in local runtime builds
  • Fix for Koji 1.35
a month ago

8 new commits added

  • Support extra-data
  • Update generator for Flathub v2 API
  • Support rename-mime-icons
  • Support add-extensions
  • Support rename-mime-file
  • Support building extensions
  • Allow local RPMs in local runtime builds
  • Fix for Koji 1.35
a month ago

This is somewhat behind the FCT PR; will FCT be replacing FMT in Fedora immediately, or should I resync this backport?

8 new commits added

  • Support extra-data
  • Update generator for Flathub v2 API
  • Support rename-mime-icons
  • Support add-extensions
  • Support rename-mime-file
  • Support building extensions
  • Allow local RPMs in local runtime builds
  • Fix for Koji 1.35
8 days ago

rebased onto 3ad77c7

8 days ago

4 new commits added

  • Fix test_rpm_utils with RPM 4.20
  • generator: set runtime-name based on the flathub runtime used
  • RpmBuilder: allow local repo builds of packages not yet in Koji
  • RpmBuilder: allow builds to proceed without a mainline Koji build
8 days ago

4 new commits added

  • tests: avoid deprecated responses syntax
  • tests: avoid deprecated Modulemd methods
  • tests: register custom markers
  • Drop pkg_resources usage
8 days ago

This is back in sync with the FCT merge request, which now includes a bunch of warning fixes in the testsuite.

16 new commits added

  • tests: avoid deprecated responses syntax
  • tests: avoid deprecated Modulemd methods
  • tests: register custom markers
  • Drop pkg_resources usage
  • Fix test_rpm_utils with RPM 4.20
  • generator: set runtime-name based on the flathub runtime used
  • RpmBuilder: allow local repo builds of packages not yet in Koji
  • RpmBuilder: allow builds to proceed without a mainline Koji build
  • Support extra-data
  • Update generator for Flathub v2 API
  • Support rename-mime-icons
  • Support add-extensions
  • Support rename-mime-file
  • Support building extensions
  • Allow local RPMs in local runtime builds
  • When waiting for repositories, explictly request them
8 days ago