├── data ├── templates │ ├── ubuntu │ ├── debian │ │ ├── base.html │ │ ├── static │ │ │ ├── img │ │ │ ├── js │ │ │ └── css │ │ │ │ ├── highlight.css │ │ │ │ └── style.css │ │ └── main.html │ └── default │ │ ├── static │ │ ├── img │ │ │ ├── asgen.png │ │ │ ├── favicon.png │ │ │ ├── cpt-nogui.png │ │ │ └── no-image.png │ │ └── css │ │ │ ├── highlight.css │ │ │ └── style.css │ │ ├── metainfo_index.html │ │ ├── base.html │ │ ├── metainfo_page.html │ │ ├── issues_index.html │ │ ├── issues_page.html │ │ ├── main.html │ │ └── sections_index.html ├── .gitignore ├── meson.build └── org.freedesktop.appstream.generator.metainfo.xml ├── contrib ├── setup │ ├── .gitignore │ ├── package.json │ ├── meson-install-templates.sh │ ├── build_js.sh │ └── package-lock.json ├── subprojects │ ├── .gitignore │ ├── inja.wrap │ ├── nlohmann_json.wrap │ └── backward-cpp.wrap ├── cleanup-cruft.sh.example └── update-metadata.sh.example ├── MAINTAINERS ├── tests ├── samples │ ├── sample-video.mkv │ ├── appstream-logo.png │ ├── debian │ │ └── dists │ │ │ └── chromodoris │ │ │ └── main │ │ │ ├── Contents-amd64.gz │ │ │ └── binary-amd64 │ │ │ └── Packages.gz │ ├── sample-video-credit.txt │ ├── extra-metainfo │ │ └── modifications.json │ └── rpmmd │ │ └── 26 │ │ └── Workstation │ │ └── x86_64 │ │ └── os │ │ └── repodata │ │ ├── 891148a3ce31e0695aa00ee72a7730e6978b049357956c81a605396453cc200a-other.xml.gz │ │ ├── 9e6f400df7895e52984c987d7fea543cc9ca9a87f43d5652a51f44241aaeb03a-primary.xml.gz │ │ ├── 359c4a0d13731581be86f571f3a672e82fe52f914f00929243f17c16da820326-filelists.xml.gz │ │ └── repomd.xml ├── ci │ ├── run-tests.sh │ ├── Dockerfile-debian-stable │ ├── Dockerfile-debian-testing │ ├── Dockerfile-fedora-latest │ ├── run-build.sh │ ├── ci-install-extern.sh │ ├── install-deps-rpm.sh │ └── install-deps-deb.sh ├── meson.build ├── tests-engine.cpp ├── tests-backend-misc.cpp └── tests-icons.cpp ├── .gitignore ├── src ├── backends │ ├── dummy │ │ ├── meson.build │ │ ├── pkgindex.h │ │ ├── pkgindex.cpp │ │ ├── dummypkg.h │ │ └── dummypkg.cpp │ ├── ubuntu │ │ ├── meson.build │ │ ├── ubupkgindex.h │ │ ├── ubupkg.h │ │ └── ubupkgindex.cpp │ ├── freebsd │ │ ├── meson.build │ │ ├── fbsdpkgindex.h │ │ ├── fbsdpkg.h │ │ └── fbsdpkg.cpp │ ├── archlinux │ │ ├── meson.build │ │ ├── listfile.h │ │ ├── listfile.cpp │ │ ├── alpkgindex.h │ │ ├── alpkg.h │ │ └── alpkg.cpp │ ├── rpmmd │ │ ├── meson.build │ │ ├── rpmutils.h │ │ ├── rpmutils.cpp │ │ ├── rpmpkgindex.h │ │ ├── rpmpkg.h │ │ └── rpmpkg.cpp │ ├── alpinelinux │ │ ├── meson.build │ │ ├── apkpkg.h │ │ ├── apkpkgindex.h │ │ ├── apkindexutils.h │ │ └── apkpkg.cpp │ ├── debian │ │ ├── meson.build │ │ ├── tagfile.h │ │ ├── debutils.h │ │ ├── debpkgindex.h │ │ ├── debpkg.h │ │ └── tagfile.cpp │ ├── meson.build │ └── interfaces.cpp ├── yaml-utils.h ├── dataunits.h ├── meson.build ├── hintregistry.h ├── cptmodifiers.h ├── datainjectpkg.h ├── extractor.h ├── downloader.h ├── logging.h ├── contentsstore.h ├── zarchive.h ├── logging.cpp └── reportgenerator.h ├── docs ├── index.md ├── meson.build └── usage.md ├── .github ├── dependabot.yml └── workflows │ ├── codeql.yml │ ├── coverity.yml │ └── build-test.yml ├── TODO ├── .editorconfig ├── meson_options.txt ├── RELEASE ├── .clang-format ├── README.md ├── autoformat.py └── meson.build /data/templates/ubuntu: -------------------------------------------------------------------------------- 1 | debian -------------------------------------------------------------------------------- /contrib/setup/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /data/templates/debian/base.html: -------------------------------------------------------------------------------- 1 | ../default/base.html -------------------------------------------------------------------------------- /MAINTAINERS: -------------------------------------------------------------------------------- 1 | Matthias Klumpp 2 | E-mail: mak@debian.org 3 | -------------------------------------------------------------------------------- /data/templates/debian/static/img: -------------------------------------------------------------------------------- 1 | ../../default/static/img/ -------------------------------------------------------------------------------- /data/templates/debian/static/js: -------------------------------------------------------------------------------- 1 | ../../default/static/js -------------------------------------------------------------------------------- /data/templates/debian/static/css/highlight.css: -------------------------------------------------------------------------------- 1 | ../../../default/static/css/highlight.css -------------------------------------------------------------------------------- /contrib/subprojects/.gitignore: -------------------------------------------------------------------------------- 1 | inja/ 2 | packagecache/ 3 | backward-cpp-*/ 4 | .wraplock 5 | -------------------------------------------------------------------------------- /tests/samples/sample-video.mkv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ximion/appstream-generator/HEAD/tests/samples/sample-video.mkv -------------------------------------------------------------------------------- /tests/samples/appstream-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ximion/appstream-generator/HEAD/tests/samples/appstream-logo.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .dub 3 | docs.json 4 | dub.selections.json 5 | __dummy.html 6 | *.o 7 | *.obj 8 | *.so 9 | *.a 10 | -------------------------------------------------------------------------------- /contrib/subprojects/inja.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | directory = inja 3 | url = https://github.com/pantor/inja.git 4 | revision = v3.4.0 5 | -------------------------------------------------------------------------------- /data/.gitignore: -------------------------------------------------------------------------------- 1 | templates/default/static/js/jquery/ 2 | templates/default/static/js/flot/ 3 | templates/default/static/js/highlight/ 4 | -------------------------------------------------------------------------------- /data/templates/default/static/img/asgen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ximion/appstream-generator/HEAD/data/templates/default/static/img/asgen.png -------------------------------------------------------------------------------- /data/templates/default/static/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ximion/appstream-generator/HEAD/data/templates/default/static/img/favicon.png -------------------------------------------------------------------------------- /data/templates/default/static/img/cpt-nogui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ximion/appstream-generator/HEAD/data/templates/default/static/img/cpt-nogui.png -------------------------------------------------------------------------------- /data/templates/default/static/img/no-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ximion/appstream-generator/HEAD/data/templates/default/static/img/no-image.png -------------------------------------------------------------------------------- /src/backends/dummy/meson.build: -------------------------------------------------------------------------------- 1 | 2 | backend_dummy_src = files( 3 | 'dummypkg.cpp', 4 | 'pkgindex.cpp', 5 | ) 6 | 7 | backends_src += backend_dummy_src 8 | -------------------------------------------------------------------------------- /src/backends/ubuntu/meson.build: -------------------------------------------------------------------------------- 1 | 2 | backend_ubuntu_src = files( 3 | 'ubupkg.cpp', 4 | 'ubupkgindex.cpp', 5 | ) 6 | 7 | backends_src += backend_ubuntu_src 8 | -------------------------------------------------------------------------------- /src/backends/freebsd/meson.build: -------------------------------------------------------------------------------- 1 | 2 | backend_freebsd_src = files( 3 | 'fbsdpkg.cpp', 4 | 'fbsdpkgindex.cpp', 5 | ) 6 | 7 | backends_src += backend_freebsd_src 8 | -------------------------------------------------------------------------------- /src/backends/archlinux/meson.build: -------------------------------------------------------------------------------- 1 | 2 | backend_arch_src = files( 3 | 'alpkg.cpp', 4 | 'alpkgindex.cpp', 5 | 'listfile.cpp', 6 | ) 7 | 8 | backends_src += backend_arch_src 9 | -------------------------------------------------------------------------------- /src/backends/rpmmd/meson.build: -------------------------------------------------------------------------------- 1 | 2 | backend_rpmmd_src = files( 3 | 'rpmpkg.cpp', 4 | 'rpmpkgindex.cpp', 5 | 'rpmutils.cpp', 6 | ) 7 | 8 | backends_src += backend_rpmmd_src 9 | -------------------------------------------------------------------------------- /contrib/setup/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "flat": true, 3 | "dependencies": { 4 | "highlightjs": "^9.10.0", 5 | "jquery": "^3.3.1", 6 | "jquery-flot": "^0.8.3" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # AppStream Generator Docs 2 | 3 | For general information check out the [README file](../README.md) 4 | 5 | See [usage.md](usage.md) for general usage information. 6 | -------------------------------------------------------------------------------- /tests/samples/debian/dists/chromodoris/main/Contents-amd64.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ximion/appstream-generator/HEAD/tests/samples/debian/dists/chromodoris/main/Contents-amd64.gz -------------------------------------------------------------------------------- /src/backends/alpinelinux/meson.build: -------------------------------------------------------------------------------- 1 | 2 | backend_alpine_src = files( 3 | 'apkindexutils.cpp', 4 | 'apkpkg.cpp', 5 | 'apkpkgindex.cpp', 6 | ) 7 | 8 | backends_src += backend_alpine_src 9 | -------------------------------------------------------------------------------- /tests/samples/debian/dists/chromodoris/main/binary-amd64/Packages.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ximion/appstream-generator/HEAD/tests/samples/debian/dists/chromodoris/main/binary-amd64/Packages.gz -------------------------------------------------------------------------------- /src/backends/debian/meson.build: -------------------------------------------------------------------------------- 1 | 2 | backend_debian_src = files( 3 | 'tagfile.cpp', 4 | 'debpkg.cpp', 5 | 'debpkgindex.cpp', 6 | 'debutils.cpp', 7 | ) 8 | 9 | backends_src += backend_debian_src 10 | -------------------------------------------------------------------------------- /src/backends/meson.build: -------------------------------------------------------------------------------- 1 | 2 | backends_src = [] 3 | 4 | 5 | subdir('dummy') 6 | subdir('debian') 7 | subdir('ubuntu') 8 | subdir('alpinelinux') 9 | subdir('archlinux') 10 | subdir('rpmmd') 11 | subdir('freebsd') 12 | -------------------------------------------------------------------------------- /tests/samples/sample-video-credit.txt: -------------------------------------------------------------------------------- 1 | The "Dawn City" footage was authored by Dmitry Tolsty under a royalty-free, free-for-commercial, no-attribution license. 2 | See https://pixabay.com/users/badzhoo-5607787/ for more information about the creator. 3 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | - package-ecosystem: "npm" 8 | directory: "/" 9 | schedule: 10 | interval: "monthly" 11 | -------------------------------------------------------------------------------- /tests/samples/extra-metainfo/modifications.json: -------------------------------------------------------------------------------- 1 | { 2 | "Remove": [ 3 | "com.example.removed" 4 | ], 5 | 6 | "InjectCustom": { 7 | "org.example.newdata": { 8 | "earth": "moon", 9 | "mars": "phobos", 10 | "saturn": "thrym" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/samples/rpmmd/26/Workstation/x86_64/os/repodata/891148a3ce31e0695aa00ee72a7730e6978b049357956c81a605396453cc200a-other.xml.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ximion/appstream-generator/HEAD/tests/samples/rpmmd/26/Workstation/x86_64/os/repodata/891148a3ce31e0695aa00ee72a7730e6978b049357956c81a605396453cc200a-other.xml.gz -------------------------------------------------------------------------------- /tests/samples/rpmmd/26/Workstation/x86_64/os/repodata/9e6f400df7895e52984c987d7fea543cc9ca9a87f43d5652a51f44241aaeb03a-primary.xml.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ximion/appstream-generator/HEAD/tests/samples/rpmmd/26/Workstation/x86_64/os/repodata/9e6f400df7895e52984c987d7fea543cc9ca9a87f43d5652a51f44241aaeb03a-primary.xml.gz -------------------------------------------------------------------------------- /tests/samples/rpmmd/26/Workstation/x86_64/os/repodata/359c4a0d13731581be86f571f3a672e82fe52f914f00929243f17c16da820326-filelists.xml.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ximion/appstream-generator/HEAD/tests/samples/rpmmd/26/Workstation/x86_64/os/repodata/359c4a0d13731581be86f571f3a672e82fe52f914f00929243f17c16da820326-filelists.xml.gz -------------------------------------------------------------------------------- /contrib/setup/meson-install-templates.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | cd "$MESON_SOURCE_ROOT" 5 | 6 | echo "Installing templates..." 7 | install -d "${DESTDIR}/${MESON_INSTALL_PREFIX}/share/appstream/templates" 8 | cp -dpru --no-preserve=ownership data/templates/* -t "${DESTDIR}/${MESON_INSTALL_PREFIX}/share/appstream/templates" 9 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | = AppStream Generator TODO List = 2 | 3 | === Known issues === 4 | 5 | * No persistent problems at time :-) 6 | 7 | === Planned Features === 8 | 9 | * Use feature from the upcoming libappstream-compose 10 | 11 | === Whishlist / Random Ideas === 12 | 13 | * Add an icon-cache so we don't render SVG icons in themes multiple times. 14 | -------------------------------------------------------------------------------- /tests/ci/run-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # This script is supposed to run inside the AppStream Generator container 4 | # on the CI system. 5 | # 6 | set -e 7 | export LANG=C.UTF-8 8 | 9 | ROOT_DIR=$(pwd) 10 | 11 | set -v 12 | 13 | 14 | # 15 | # Test already built project 16 | # 17 | cd build 18 | 19 | # Run tests 20 | meson test -v --print-errorlogs 21 | 22 | # Test install 23 | DESTDIR=/tmp/install-ninja ninja install 24 | 25 | cd $ROOT_DIR 26 | -------------------------------------------------------------------------------- /tests/ci/Dockerfile-debian-stable: -------------------------------------------------------------------------------- 1 | # 2 | # Docker file for AppStream Generator CI tests 3 | # 4 | FROM debian:trixie 5 | 6 | # prepare 7 | RUN mkdir -p /build/ci/ 8 | 9 | # install build dependencies 10 | COPY install-deps-deb.sh /build/ci/ 11 | RUN chmod +x /build/ci/install-deps-deb.sh && /build/ci/install-deps-deb.sh 12 | 13 | # install 3rd-party stuff 14 | COPY ci-install-extern.sh /build/ci/ 15 | RUN chmod +x /build/ci/ci-install-extern.sh && /build/ci/ci-install-extern.sh 16 | 17 | # finish 18 | WORKDIR /build 19 | -------------------------------------------------------------------------------- /tests/ci/Dockerfile-debian-testing: -------------------------------------------------------------------------------- 1 | # 2 | # Docker file for AppStream Generator CI tests 3 | # 4 | FROM debian:testing 5 | 6 | # prepare 7 | RUN mkdir -p /build/ci/ 8 | 9 | # install build dependencies 10 | COPY install-deps-deb.sh /build/ci/ 11 | RUN chmod +x /build/ci/install-deps-deb.sh && /build/ci/install-deps-deb.sh 12 | 13 | # install 3rd-party stuff 14 | COPY ci-install-extern.sh /build/ci/ 15 | RUN chmod +x /build/ci/ci-install-extern.sh && /build/ci/ci-install-extern.sh 16 | 17 | # finish 18 | WORKDIR /build 19 | -------------------------------------------------------------------------------- /tests/ci/Dockerfile-fedora-latest: -------------------------------------------------------------------------------- 1 | # 2 | # Docker file for AppStream Generator CI tests 3 | # 4 | FROM registry.fedoraproject.org/fedora:latest 5 | 6 | # prepare 7 | RUN mkdir -p /build/ci/ 8 | 9 | # install build dependencies 10 | COPY install-deps-rpm.sh /build/ci/ 11 | RUN chmod +x /build/ci/install-deps-rpm.sh && /build/ci/install-deps-rpm.sh 12 | 13 | # install 3rd-party stuff 14 | COPY ci-install-extern.sh /build/ci/ 15 | RUN chmod +x /build/ci/ci-install-extern.sh && /build/ci/ci-install-extern.sh 16 | 17 | # finish 18 | WORKDIR /build 19 | -------------------------------------------------------------------------------- /contrib/subprojects/nlohmann_json.wrap: -------------------------------------------------------------------------------- 1 | [wrap-file] 2 | directory = nlohmann_json-3.12.0 3 | lead_directory_missing = true 4 | source_url = https://github.com/nlohmann/json/releases/download/v3.12.0/include.zip 5 | source_filename = nlohmann_json-3.12.0.zip 6 | source_hash = b8cb0ef2dd7f57f18933997c9934bb1fa962594f701cd5a8d3c2c80541559372 7 | source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/nlohmann_json_3.12.0-1/nlohmann_json-3.12.0.zip 8 | wrapdb_version = 3.12.0-1 9 | 10 | [provide] 11 | nlohmann_json = nlohmann_json_dep 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # See https://editorconfig.org/ 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | trim_trailing_whitespace = true 7 | charset = utf-8 8 | indent_style = space 9 | indent_size = 4 10 | 11 | [*.d] 12 | max_line_length = 120 13 | dfmt_brace_style = knr 14 | dfmt_soft_max_line_length = 100 15 | dfmt_align_switch_statements = false 16 | dfmt_space_before_function_parameters = true 17 | dfmt_keep_line_breaks = true 18 | 19 | [*.yml] 20 | indent_style = space 21 | indent_size = 2 22 | 23 | [*.xml] 24 | indent_style = space 25 | indent_size = 2 26 | -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Options for AppStream Generator 3 | # 4 | 5 | option('download-js', 6 | type: 'boolean', 7 | value: true, 8 | description: 'Download JavaScript with NPM automatically.' 9 | ) 10 | 11 | option('backward', 12 | type : 'boolean', 13 | value : true, 14 | description : 'Use stack trace pretty-printing using backward-cpp.' 15 | ) 16 | 17 | option('maintainer', 18 | type : 'boolean', 19 | value : false, 20 | description : 'Compile in maintainer mode (use strict compiler flags, e.g. -Werror)' 21 | ) 22 | -------------------------------------------------------------------------------- /docs/meson.build: -------------------------------------------------------------------------------- 1 | # Meson definition for AppStream Generator Documentation 2 | 3 | # make manual page 4 | xsltproc = find_program('xsltproc') 5 | custom_target('man-asgen', 6 | input: 'appstream-generator.1.xml', 7 | output: 'appstream-generator.1', 8 | install: true, 9 | install_dir: join_paths(get_option('mandir'), 'man1'), 10 | command: [ 11 | xsltproc, 12 | '--nonet', 13 | '--stringparam', 'man.output.quietly', '1', 14 | '--stringparam', 'funcsynopsis.style', 'ansi', 15 | '--stringparam', 'man.th.extra1.suppress', '1', 16 | '-o', '@OUTPUT@', 17 | 'http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl', 18 | '@INPUT@' 19 | ] 20 | ) 21 | -------------------------------------------------------------------------------- /tests/ci/run-build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # This script is supposed to run inside the AppStream Generator container 4 | # on the CI system. 5 | # 6 | set -e 7 | export LANG=C.UTF-8 8 | 9 | ROOT_DIR=$(pwd) 10 | 11 | echo "C compiler: $CC" 12 | echo "C++ compiler: $CXX" 13 | set -v 14 | $CXX --version 15 | meson --version 16 | 17 | build_type=debugoptimized 18 | with_backward=true 19 | if [ "$1" = "codeql" ]; then 20 | build_type=debug 21 | fi; 22 | if [ "$1" = "coverity" ]; then 23 | build_type=debug 24 | with_backward=false 25 | fi; 26 | 27 | # 28 | # Build Project 29 | # 30 | mkdir -p build && cd build 31 | meson setup --buildtype=$build_type \ 32 | -Ddownload-js=true \ 33 | -Dbackward=$with_backward \ 34 | .. 35 | ninja 36 | -------------------------------------------------------------------------------- /contrib/subprojects/backward-cpp.wrap: -------------------------------------------------------------------------------- 1 | [wrap-file] 2 | directory = backward-cpp-1.6 3 | source_url = https://github.com/bombela/backward-cpp/archive/refs/tags/v1.6.tar.gz 4 | source_filename = backward-cpp-1.6.tar.gz 5 | source_hash = c654d0923d43f1cea23d086729673498e4741fb2457e806cfaeaea7b20c97c10 6 | patch_filename = backward-cpp_1.6-6_patch.zip 7 | patch_url = https://wrapdb.mesonbuild.com/v2/backward-cpp_1.6-6/get_patch 8 | patch_hash = 2c952611584971cdcb04b5c4aeca3401f1fba9544e6203e08b933fd9c21427fc 9 | source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/backward-cpp_1.6-6/backward-cpp-1.6.tar.gz 10 | wrapdb_version = 1.6-6 11 | 12 | [provide] 13 | backward-cpp = backward_dep 14 | backward-cpp-interface = backward_interface_dep 15 | -------------------------------------------------------------------------------- /contrib/setup/build_js.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | if [ -n "$MESON_SOURCE_ROOT" ]; then 5 | cd "$MESON_SOURCE_ROOT/contrib/setup/" 6 | fi 7 | 8 | NPM="npm" 9 | if [ ! -z "$1" ] 10 | then 11 | NPM=$1 12 | fi 13 | 14 | $NPM ci --ignore-scripts 15 | 16 | JS_TARGET=../../data/templates/default/static/js 17 | [ ! -d "$JS_TARGET" ] && mkdir $JS_TARGET 18 | 19 | [ ! -d "$JS_TARGET/jquery" ] && mkdir $JS_TARGET/jquery 20 | install node_modules/jquery/dist/*.min.js -t $JS_TARGET/jquery 21 | 22 | [ ! -d "$JS_TARGET/flot" ] && mkdir $JS_TARGET/flot 23 | install node_modules/jquery-flot/jquery.flot*.js -t $JS_TARGET/flot 24 | 25 | [ ! -d "$JS_TARGET/highlight" ] && mkdir $JS_TARGET/highlight 26 | install node_modules/highlightjs/*.js -t $JS_TARGET/highlight 27 | -------------------------------------------------------------------------------- /tests/ci/ci-install-extern.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Build & install AppStream Generator dependencies 4 | # 5 | set -e 6 | set -x 7 | 8 | # 9 | # This script is *only* intended to be run in a CI container. 10 | # 11 | 12 | mkdir /tmp/build 13 | 14 | # build & install the current Git snapshot of AppStream 15 | cd /tmp/build && \ 16 | git clone --depth=1 https://github.com/ximion/appstream.git 17 | mkdir /tmp/build/appstream/build 18 | cd /tmp/build/appstream/build && \ 19 | meson setup --prefix=/usr \ 20 | -Dmaintainer=true \ 21 | -Dapt-support=true \ 22 | -Dcompose=true \ 23 | -Dapidocs=false \ 24 | .. 25 | cd /tmp/build/appstream/build && \ 26 | ninja && ninja install 27 | 28 | # cleanup 29 | rm -rf /tmp/build 30 | -------------------------------------------------------------------------------- /contrib/cleanup-cruft.sh.example: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Script cleaning up the AppStream metadata pool and cache. 4 | # This script should be run by a cronjob (e.g. every week). 5 | # 6 | 7 | set -e 8 | set -o pipefail 9 | set -u 10 | 11 | WORKSPACE_DIR="/srv/appstream/workspace" 12 | 13 | # only run one instance of the script 14 | LOCKFILE="$WORKSPACE_DIR/.lock" 15 | cleanup() { 16 | rm -f "$LOCKFILE" 17 | } 18 | 19 | if ! lockfile -r8 $LOCKFILE; then 20 | echo "aborting AppStream metadata cleanup because $LOCKFILE has already been locked" 21 | exit 0 22 | fi 23 | trap cleanup 0 24 | 25 | # Start logging 26 | logdir="$WORKSPACE_DIR/logs/`date "+%Y/%m"`" 27 | mkdir -p $logdir 28 | NOW=`date "+%d_%H%M"` 29 | LOGFILE="$logdir/${NOW}_cleanup.log" 30 | exec >> "$LOGFILE" 2>&1 31 | 32 | cd $WORKSPACE_DIR 33 | 34 | # Cleanup superseded data 35 | appstream-generator cleanup 36 | 37 | # finish logging 38 | exec > /dev/null 2>&1 39 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | schedule: 9 | - cron: '36 8 * * 5' 10 | 11 | jobs: 12 | analyze: 13 | name: Analyze 14 | runs-on: ubuntu-24.04 15 | env: 16 | CC: gcc-14 17 | CXX: g++-14 18 | 19 | strategy: 20 | fail-fast: false 21 | 22 | steps: 23 | - name: Checkout repository 24 | uses: actions/checkout@v6 25 | 26 | - name: Initialize CodeQL 27 | uses: github/codeql-action/init@v4 28 | with: 29 | languages: cpp 30 | 31 | - name: Create Build Environment 32 | run: sudo ./tests/ci/install-deps-deb.sh 33 | 34 | - name: Make & Install 3rd-party 35 | run: sudo ./tests/ci/ci-install-extern.sh 36 | 37 | - name: Build & Test 38 | run: ./tests/ci/run-build.sh codeql 39 | 40 | - name: Perform CodeQL Analysis 41 | uses: github/codeql-action/analyze@v4 42 | -------------------------------------------------------------------------------- /data/meson.build: -------------------------------------------------------------------------------- 1 | 2 | # data 3 | install_data('asgen-hints.json', install_dir: 'share/appstream') 4 | install_data('hicolor-theme-index.theme', install_dir: 'share/appstream') 5 | 6 | ascli_exe = find_program('appstreamcli', required: true) 7 | 8 | metainfo_filename = 'org.freedesktop.appstream.generator.metainfo.xml' 9 | metainfo_with_relinfo = custom_target('add-metainfo-releases', 10 | input : ['../NEWS', files(metainfo_filename)], 11 | output : [metainfo_filename], 12 | command : [ascli_exe, 'news-to-metainfo', '--limit=6', '@INPUT0@', '@INPUT1@', '@OUTPUT@'], 13 | install : true, 14 | install_dir : join_paths (get_option ('datadir'), 'metainfo') 15 | ) 16 | 17 | if ascli_exe.found() 18 | # Validate our MetaInfo file 19 | test('asgen-validate_metainfo', 20 | ascli_exe, 21 | args: ['validate', 22 | '--no-net', '--pedantic', 23 | files(metainfo_filename)] 24 | ) 25 | endif 26 | 27 | # templates 28 | #install_subdir('data/templates/', install_dir: 'share/appstream') # FIXME: Doesn't handle dir symlinks correctly 29 | meson.add_install_script(source_root + '/contrib/setup/meson-install-templates.sh') 30 | -------------------------------------------------------------------------------- /data/templates/default/metainfo_index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Components in {{suite}}/{{section}}{% endblock %} 4 | 5 | {% block header_content %} 6 | ⇦ | 7 | Components summary for {{suite}}/{{section}} 8 | {% endblock %} 9 | 10 | {% block float_right %} 11 | Last updated on: {{time}} 12 | {% endblock %} 13 | 14 | {% block content %} 15 |

Metadata for {{suite}}/{{section}}

16 | 17 | {% for summary in summaries %} 18 |

{{summary.maintainer}}

19 | 35 | {% endfor %} 36 | {% endblock %} 37 | -------------------------------------------------------------------------------- /contrib/update-metadata.sh.example: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Example script for updating AppStream metadata using appstream-generator. 4 | # This script can easily be run by a cronjob. 5 | # 6 | 7 | set -e 8 | set -o pipefail 9 | set -u 10 | 11 | SUITES="sid stretch" 12 | 13 | WORKSPACE_DIR="/srv/appstream/workspace" 14 | PUBLIC_DIR="/srv/appstream/public" 15 | 16 | # only run one instance of the script 17 | LOCKFILE="$WORKSPACE_DIR/.lock" 18 | cleanup() { 19 | rm -f "$LOCKFILE" 20 | } 21 | 22 | if ! lockfile -r8 $LOCKFILE; then 23 | echo "aborting AppStream metadata extraction because $LOCKFILE has already been locked" 24 | exit 0 25 | fi 26 | trap cleanup 0 27 | 28 | # Start logging 29 | logdir="$WORKSPACE_DIR/logs/`date "+%Y/%m"`" 30 | mkdir -p $logdir 31 | NOW=`date "+%d_%H%M"` 32 | LOGFILE="$logdir/${NOW}.log" 33 | exec >> "$LOGFILE" 2>&1 34 | 35 | cd $WORKSPACE_DIR 36 | 37 | # generate fresh metadata 38 | for suite in $SUITES; do 39 | appstream-generator process $suite 40 | done 41 | 42 | # Sync updated data to public directory 43 | rsync -ak --delete-after --link-dest="$PUBLIC_DIR/" "$WORKSPACE_DIR/export/" "$PUBLIC_DIR/" 44 | 45 | # finish logging 46 | exec > /dev/null 2>&1 47 | -------------------------------------------------------------------------------- /RELEASE: -------------------------------------------------------------------------------- 1 | AppStream Generator Release Notes 2 | 3 | 1. Write NEWS entries for AppStream Generator in the same format as usual. 4 | 5 | git shortlog v0.10.1.. | grep -i -v trivial | grep -v Merge > NEWS.new 6 | 7 | -------------------------------------------------------------------------------- 8 | Version 0.10.2 9 | ~~~~~~~~~~~~~~ 10 | Released: 2025-xx-xx 11 | 12 | Notes: 13 | 14 | Features: 15 | 16 | Bugfixes: 17 | 18 | Miscellaneous: 19 | 20 | Contributors: 21 | -------------------------------------------------------------------------------- 22 | 23 | 2. Commit changes in Git: 24 | 25 | git commit -a -m "Release version 0.10.2" 26 | git tag -s -f -m "Release 0.10.2" v0.10.2 27 | git push --tags 28 | git push 29 | 30 | 3. Do post release version bump in meson.build, RELEASE 31 | 32 | 4. Commit trivial changes: 33 | 34 | git commit -a -m "trivial: post release version bump" 35 | git push 36 | 37 | 5. Send an email to appstream@lists.freedesktop.org 38 | 39 | ================================================= 40 | AppStream Generator 0.10.2 released! 41 | 42 | Tarballs available here: https://github.com/ximion/appstream-generator/releases 43 | 44 | ================================================= 45 | -------------------------------------------------------------------------------- /data/templates/default/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | AppStream Report for {{project_name}} - {% block title %}{% endblock %} 11 | 12 | 13 | 14 | 15 | {% block head_extra %}{% endblock %} 16 | 17 | 18 | 19 |
20 |
21 | {% block header_content %}{% endblock %} 22 |
23 |
24 | {% block float_right %}{% endblock %} 25 |
26 |
27 | 28 |
29 | {% block content %}{% endblock %} 30 |
31 | 32 |
33 | 34 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /data/templates/default/metainfo_page.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}{{package_name}} in {{suite}}/{{section}}{% endblock %} 4 | 5 | {% block head_extra %} 6 | 7 | 8 | {% endblock %} 9 | 10 | {% block header_content %} 11 | ⇦ | 12 | {{package_name}} [{{section}}] 13 | {% endblock %} 14 | 15 | {% block float_right %} 16 | Last updated on: {{time}} 17 | {% endblock %} 18 | 19 | {% block content %} 20 |

Metadata for {{package_name}} in {{section}}

21 | 22 |
23 | {% for cpt in cpts %} 24 |

25 | {{cpt.component_id}} 26 | {% for arch in cpt.architectures %} 27 | ⚙ {{arch.arch}} 28 | {% endfor %} 29 |

30 | 31 |
32 | Icon 33 |
34 | 35 |
36 |
{{cpt.metadata}}
37 |
38 | {% endfor %} 39 | 40 |
41 | 42 | {% endblock %} 43 | -------------------------------------------------------------------------------- /contrib/setup/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "setup", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "dependencies": { 8 | "highlightjs": "^9.10.0", 9 | "jquery": "^3.3.1", 10 | "jquery-flot": "^0.8.3" 11 | } 12 | }, 13 | "node_modules/highlightjs": { 14 | "version": "9.16.2", 15 | "resolved": "https://registry.npmjs.org/highlightjs/-/highlightjs-9.16.2.tgz", 16 | "integrity": "sha512-FK1vmMj8BbEipEy8DLIvp71t5UsC7n2D6En/UfM/91PCwmOpj6f2iu0Y0coRC62KSRHHC+dquM2xMULV/X7NFg==", 17 | "deprecated": "Use the 'highlight.js' package instead https://npm.im/highlight.js", 18 | "license": "BSD-3-Clause" 19 | }, 20 | "node_modules/jquery": { 21 | "version": "3.7.1", 22 | "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", 23 | "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==", 24 | "license": "MIT" 25 | }, 26 | "node_modules/jquery-flot": { 27 | "version": "0.8.3", 28 | "resolved": "https://registry.npmjs.org/jquery-flot/-/jquery-flot-0.8.3.tgz", 29 | "integrity": "sha512-IkQCsA5t55Aubu8iove/X/KL34rdZTsDyx/bylC7F320N2bwsJhJe3Np8orsx1L8FEOoMDfNe3/pSb3xXKLxeQ==", 30 | "deprecated": "flot has been abandoned" 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/backends/archlinux/listfile.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2025 Matthias Klumpp 3 | * 4 | * Licensed under the GNU Lesser General Public License Version 3 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the license, or 9 | * (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this software. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | namespace ASGenerator 28 | { 29 | 30 | class ListFile 31 | { 32 | public: 33 | ListFile(); 34 | 35 | void loadData(const std::vector &data); 36 | 37 | std::string getEntry(const std::string &name); 38 | 39 | private: 40 | std::unordered_map m_entries; 41 | }; 42 | 43 | } // namespace ASGenerator 44 | -------------------------------------------------------------------------------- /tests/ci/install-deps-rpm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Install AppStream Generator build dependencies 4 | # 5 | set -e 6 | set -x 7 | 8 | # update caches 9 | dnf makecache 10 | 11 | # install dependencies 12 | dnf --assumeyes --quiet --setopt=install_weak_deps=False install \ 13 | curl \ 14 | gdb \ 15 | gcc \ 16 | gcc-c++ \ 17 | git-core \ 18 | meson \ 19 | gettext \ 20 | gnupg \ 21 | gperf \ 22 | docbook-dtds \ 23 | docbook-style-xsl \ 24 | libasan \ 25 | libstemmer-devel \ 26 | libubsan \ 27 | libunwind-devel \ 28 | 'pkgconfig(cairo)' \ 29 | 'pkgconfig(freetype2)' \ 30 | 'pkgconfig(fontconfig)' \ 31 | 'pkgconfig(gdk-pixbuf-2.0)' \ 32 | 'pkgconfig(glib-2.0)' \ 33 | 'pkgconfig(gobject-2.0)' \ 34 | 'pkgconfig(gio-2.0)' \ 35 | 'pkgconfig(glibd-2.0)' \ 36 | 'pkgconfig(gobject-introspection-1.0)' \ 37 | 'pkgconfig(libarchive)' \ 38 | 'pkgconfig(libcurl)' \ 39 | 'pkgconfig(librsvg-2.0)' \ 40 | 'pkgconfig(libxml-2.0)' \ 41 | 'pkgconfig(libsystemd)' \ 42 | 'pkgconfig(xmlb)' \ 43 | 'pkgconfig(lmdb)' \ 44 | 'pkgconfig(pango)' \ 45 | 'pkgconfig(libfyaml)' \ 46 | 'pkgconfig(tbb)' \ 47 | 'pkgconfig(catch2)' \ 48 | sed \ 49 | xmlto \ 50 | itstool \ 51 | diffutils \ 52 | /usr/bin/ffmpeg \ 53 | /usr/bin/node \ 54 | /usr/bin/xsltproc \ 55 | /usr/bin/npm 56 | -------------------------------------------------------------------------------- /data/templates/default/issues_index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Hints summary for {{suite}}/{{section}}{% endblock %} 4 | 5 | {% block header_content %} 6 | ⇦ | 7 | Hints summary for {{suite}}/{{section}} 8 | {% endblock %} 9 | 10 | {% block float_right %} 11 | Last updated on: {{time}} 12 | {% endblock %} 13 | 14 | {% block content %} 15 |

Metadata processing hints found for {{suite}}/{{section}}

16 | 17 | {% for summary in summaries %} 18 |

{{summary.maintainer}}

19 |
    20 | {% for package in summary.packages %} 21 |
  • 22 | 23 | {{package.pkgname}}  24 | 25 | {% if package.info_count > 0 %} 26 | Infos: {{package.info_count}} 27 | {% endif %} 28 | {% if package.warning_count > 0 %} 29 | Warnings: {{package.warning_count}} 30 | {% endif %} 31 | {% if package.error_count > 0 %} 32 | Errors: {{package.error_count}} 33 | {% endif %} 34 | 35 |
  • 36 | {% endfor %} 37 |
38 | {% endfor %} 39 | {% endblock %} 40 | -------------------------------------------------------------------------------- /src/backends/rpmmd/rpmutils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2025 Matthias Klumpp 3 | * 4 | * Licensed under the GNU Lesser General Public License Version 3 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the license, or 9 | * (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this software. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | 24 | namespace ASGenerator 25 | { 26 | 27 | class Downloader; 28 | 29 | /** 30 | * If URL is remote, download it, otherwise use it verbatim. 31 | * 32 | * Returns: Path to the file, which is guaranteed to exist. 33 | * 34 | * Params: 35 | * url = First part of the address, i.e. 36 | * "http://ftp.debian.org/debian/" or "/srv/mirrors/debian/" 37 | * destLocation = If the file is remote, the directory to save it under, 38 | * which is created if necessary. 39 | */ 40 | std::string downloadIfNecessary( 41 | const std::string &url, 42 | const std::string &destLocation, 43 | Downloader *downloader = nullptr); 44 | 45 | } // namespace ASGenerator 46 | -------------------------------------------------------------------------------- /.github/workflows/coverity.yml: -------------------------------------------------------------------------------- 1 | name: Coverity Scan 2 | 3 | permissions: 4 | contents: read 5 | 6 | on: 7 | workflow_dispatch: 8 | schedule: 9 | - cron: '0 3 1,15 * *' 10 | 11 | jobs: 12 | coverity: 13 | if: github.repository == 'ximion/appstream-generator' 14 | runs-on: ubuntu-24.04 15 | env: 16 | CC: gcc-14 17 | CXX: g++-14 18 | steps: 19 | - uses: actions/checkout@v6 20 | with: 21 | fetch-tags: true 22 | fetch-depth: 0 23 | 24 | - name: Create Build Environment 25 | run: sudo ./tests/ci/install-deps-deb.sh 26 | 27 | - name: Make & Install 3rd-party 28 | run: sudo ./tests/ci/ci-install-extern.sh 29 | 30 | - name: Create Version String 31 | id: version 32 | run: | 33 | git_commit=$(git rev-parse --short HEAD) 34 | git_current_tag=$(git describe --tags --abbrev=0 2>/dev/null || echo v1.0.0) 35 | git_commit_no=$(git rev-list --count "${git_current_tag}..HEAD" 2>/dev/null) 36 | git_version_full=${git_current_tag#v}; git_version_full=${git_version_full//-/.} 37 | if [ "$git_commit_no" -gt 0 ]; then 38 | git_version_full+=".git$git_commit_no" 39 | fi 40 | 41 | echo "Using version string: $git_version_full" 42 | echo "git_version_full=$git_version_full" >> $GITHUB_OUTPUT 43 | 44 | - name: Coverity 45 | uses: vapier/coverity-scan-action@346291b388d0a99b2d82c8d46f5088d0fc494844 46 | with: 47 | email: ${{ secrets.COVERITY_SCAN_EMAIL }} 48 | token: ${{ secrets.COVERITY_SCAN_TOKEN }} 49 | version: ${{ steps.version.outputs.git_version_full }} 50 | command: './tests/ci/run-build.sh coverity' 51 | -------------------------------------------------------------------------------- /tests/samples/rpmmd/26/Workstation/x86_64/os/repodata/repomd.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1498315710 4 | 5 | 9e6f400df7895e52984c987d7fea543cc9ca9a87f43d5652a51f44241aaeb03a 6 | 98e300b6683be9c9e959ece52969ee32c0171cc9d922325c57af58ca2c9e4a65 7 | 8 | 1498315710 9 | 2285 10 | 9537 11 | 12 | 13 | 359c4a0d13731581be86f571f3a672e82fe52f914f00929243f17c16da820326 14 | 37006a808d524721a0e42ac402cffe6ef9c503478985569ec52bf0ed4315695f 15 | 16 | 1498315710 17 | 4188 18 | 49117 19 | 20 | 21 | 891148a3ce31e0695aa00ee72a7730e6978b049357956c81a605396453cc200a 22 | 84e64f9167328898522844619599d962dc33c3334c529b5bb512e85e850a0f1b 23 | 24 | 1498315710 25 | 745 26 | 2499 27 | 28 | 29 | -------------------------------------------------------------------------------- /data/templates/default/static/css/highlight.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | github.com style (c) Vasily Polovnyov 4 | 5 | */ 6 | 7 | .hljs { 8 | display: block; 9 | overflow-x: auto; 10 | padding: 0.5em; 11 | color: #333; 12 | background: #f5f5f5; 13 | } 14 | 15 | .hljs-comment, 16 | .hljs-quote { 17 | color: #998; 18 | font-style: italic; 19 | } 20 | 21 | .hljs-keyword, 22 | .hljs-selector-tag, 23 | .hljs-subst { 24 | color: #333; 25 | font-weight: bold; 26 | } 27 | 28 | .hljs-number, 29 | .hljs-literal, 30 | .hljs-variable, 31 | .hljs-template-variable, 32 | .hljs-tag .hljs-attr { 33 | color: #008080; 34 | } 35 | 36 | .hljs-string, 37 | .hljs-doctag { 38 | color: #d14; 39 | } 40 | 41 | .hljs-title, 42 | .hljs-section, 43 | .hljs-selector-id { 44 | color: #900; 45 | font-weight: bold; 46 | } 47 | 48 | .hljs-subst { 49 | font-weight: normal; 50 | } 51 | 52 | .hljs-type, 53 | .hljs-class .hljs-title { 54 | color: #458; 55 | font-weight: bold; 56 | } 57 | 58 | .hljs-tag, 59 | .hljs-name, 60 | .hljs-attribute { 61 | color: #000080; 62 | font-weight: normal; 63 | } 64 | 65 | .hljs-regexp, 66 | .hljs-link { 67 | color: #009926; 68 | } 69 | 70 | .hljs-symbol, 71 | .hljs-bullet { 72 | color: #990073; 73 | } 74 | 75 | .hljs-built_in, 76 | .hljs-builtin-name { 77 | color: #0086b3; 78 | } 79 | 80 | .hljs-meta { 81 | color: #999; 82 | font-weight: bold; 83 | } 84 | 85 | .hljs-deletion { 86 | background: #fdd; 87 | } 88 | 89 | .hljs-addition { 90 | background: #dfd; 91 | } 92 | 93 | .hljs-emphasis { 94 | font-style: italic; 95 | } 96 | 97 | .hljs-strong { 98 | font-weight: bold; 99 | } 100 | -------------------------------------------------------------------------------- /tests/ci/install-deps-deb.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Install AppStream Generator build dependencies 4 | # 5 | set -e 6 | set -x 7 | 8 | export DEBIAN_FRONTEND=noninteractive 9 | 10 | # update caches 11 | apt-get update -qq 12 | 13 | # install build essentials 14 | apt-get install -yq \ 15 | eatmydata \ 16 | build-essential \ 17 | gdb \ 18 | gcc \ 19 | g++ \ 20 | git 21 | 22 | # install dependencies 23 | eatmydata apt-get install -yq --no-install-recommends \ 24 | meson \ 25 | gettext \ 26 | gobject-introspection \ 27 | gtk-doc-tools \ 28 | xsltproc \ 29 | docbook-xsl \ 30 | docbook-xml \ 31 | libgirepository1.0-dev \ 32 | libglib2.0-dev \ 33 | libstemmer-dev \ 34 | libxml2-dev \ 35 | libfyaml-dev \ 36 | libxmlb-dev \ 37 | libcurl4-gnutls-dev \ 38 | libsystemd-dev \ 39 | gperf \ 40 | itstool 41 | 42 | . /etc/os-release 43 | if [ "$ID" = "ubuntu" ]; then 44 | catch2_dep="catch2" 45 | eatmydata apt-get install -yq --no-install-recommends g++-14 gcc-14 46 | else 47 | catch2_dep="libcatch2-dev" 48 | fi; 49 | 50 | eatmydata apt-get install -yq --no-install-recommends \ 51 | liblmdb-dev \ 52 | libarchive-dev \ 53 | libpango1.0-dev \ 54 | libtbb-dev \ 55 | libbackward-cpp-dev \ 56 | libunwind-dev \ 57 | $catch2_dep 58 | eatmydata apt-get install -yq libglibd-2.0-dev || true 59 | 60 | eatmydata apt-get install -yq --no-install-recommends \ 61 | libgdk-pixbuf-2.0-dev \ 62 | librsvg2-dev \ 63 | libcairo2-dev \ 64 | libfontconfig1-dev \ 65 | libpango1.0-dev 66 | 67 | # install misc stuff 68 | eatmydata apt-get install -yq --no-install-recommends \ 69 | curl \ 70 | gnupg \ 71 | ffmpeg \ 72 | npm 73 | -------------------------------------------------------------------------------- /src/yaml-utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018-2025 Matthias Klumpp 3 | * 4 | * Licensed under the GNU Lesser General Public License Version 3 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the license, or 9 | * (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this software. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | namespace ASGenerator 28 | { 29 | 30 | namespace Yaml 31 | { 32 | 33 | using YDocumentPtr = std::unique_ptr; 34 | 35 | YDocumentPtr parseDocument(const std::string &yamlData, bool forceJson = false); 36 | fy_node *documentRoot(YDocumentPtr &doc); 37 | 38 | std::string nodeStrValue(fy_node *node, std::string defaultValue = {}); 39 | int64_t nodeIntValue(fy_node *node, int64_t defaultValue = 0); 40 | bool nodeBoolValue(fy_node *node, bool defaultValue = false); 41 | std::vector nodeArrayValues(fy_node *node); 42 | fy_node *nodeByKey(fy_node *mapping, const std::string &key); 43 | 44 | YDocumentPtr createDocument(); 45 | 46 | std::string libfyamlVersion() noexcept; 47 | 48 | } // namespace Yaml 49 | 50 | } // namespace ASGenerator 51 | -------------------------------------------------------------------------------- /src/dataunits.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2025 Matthias Klumpp 3 | * 4 | * Licensed under the GNU Lesser General Public License Version 3 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the license, or 9 | * (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this software. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | 28 | #include "contentsstore.h" 29 | #include "backends/interfaces.h" 30 | 31 | G_BEGIN_DECLS 32 | 33 | #define ASG_TYPE_PACKAGE_UNIT (asg_package_unit_get_type()) 34 | G_DECLARE_FINAL_TYPE(AsgPackageUnit, asg_package_unit, ASG, PACKAGE_UNIT, AscUnit) 35 | 36 | #define ASG_TYPE_LOCALE_UNIT (asg_locale_unit_get_type()) 37 | G_DECLARE_FINAL_TYPE(AsgLocaleUnit, asg_locale_unit, ASG, LOCALE_UNIT, AscUnit) 38 | 39 | /** 40 | * Create a new package unit for a given package. 41 | */ 42 | AsgPackageUnit *asg_package_unit_new(std::shared_ptr pkg); 43 | 44 | /** 45 | * Create a new locale unit with contents store and package list. 46 | */ 47 | AsgLocaleUnit *asg_locale_unit_new( 48 | std::shared_ptr cstore, 49 | std::vector> pkgList); 50 | 51 | G_END_DECLS 52 | -------------------------------------------------------------------------------- /src/backends/dummy/pkgindex.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2025 Matthias Klumpp 3 | * 4 | * Licensed under the GNU Lesser General Public License Version 3 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the license, or 9 | * (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this software. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "../interfaces.h" 28 | #include "dummypkg.h" 29 | 30 | namespace ASGenerator 31 | { 32 | 33 | class DummyPackageIndex : public PackageIndex 34 | { 35 | private: 36 | std::unordered_map>> m_pkgCache; 37 | 38 | public: 39 | DummyPackageIndex(const std::string &dir); 40 | 41 | void release() override; 42 | 43 | std::vector> packagesFor( 44 | const std::string &suite, 45 | const std::string §ion, 46 | const std::string &arch, 47 | bool withLongDescs = true) override; 48 | 49 | std::shared_ptr packageForFile( 50 | const std::string &fname, 51 | const std::string &suite = "", 52 | const std::string §ion = "") override; 53 | 54 | bool hasChanges( 55 | std::shared_ptr dstore, 56 | const std::string &suite, 57 | const std::string §ion, 58 | const std::string &arch) override; 59 | }; 60 | 61 | } // namespace ASGenerator 62 | -------------------------------------------------------------------------------- /tests/meson.build: -------------------------------------------------------------------------------- 1 | 2 | # Miscellaneous tests 3 | tests_misc = executable( 4 | 'tests-misc', 5 | 'tests-misc.cpp', 6 | dependencies: [catch2_dep, asgen_lib_dep], 7 | include_directories: [src_dir], 8 | ) 9 | test('Miscellaneous', tests_misc) 10 | 11 | # Database/Cache tests 12 | tests_db = executable( 13 | 'tests-db', 14 | 'tests-db.cpp', 15 | dependencies: [catch2_dep, asgen_lib_dep], 16 | include_directories: [src_dir], 17 | ) 18 | test('Databases', tests_db) 19 | 20 | # Network-dependent tests 21 | tests_net = executable( 22 | 'tests-net', 23 | 'tests-net.cpp', 24 | dependencies: [catch2_dep, asgen_lib_dep], 25 | include_directories: [src_dir], 26 | ) 27 | test('Network', tests_net, args: ['--allow-running-no-tests']) 28 | 29 | # Report generator tests 30 | tests_report = executable( 31 | 'tests-report', 32 | 'tests-report.cpp', 33 | dependencies: [catch2_dep, asgen_lib_dep], 34 | include_directories: [src_dir], 35 | ) 36 | test('ReportGenerator', tests_report) 37 | 38 | # Icon handler tests 39 | tests_icons = executable( 40 | 'tests-icons', 41 | 'tests-icons.cpp', 42 | dependencies: [catch2_dep, asgen_lib_dep], 43 | include_directories: [src_dir], 44 | ) 45 | test('IconHandler', tests_icons) 46 | 47 | # Engine tests 48 | tests_engine = executable( 49 | 'tests-engine', 50 | 'tests-engine.cpp', 51 | dependencies: [catch2_dep, asgen_lib_dep], 52 | include_directories: [src_dir], 53 | ) 54 | test('Engine', tests_engine) 55 | 56 | # Debian backend tests 57 | tests_backend_debian = executable( 58 | 'tests-backend-debian', 59 | 'tests-backend-debian.cpp', 60 | dependencies: [catch2_dep, asgen_lib_dep], 61 | include_directories: [src_dir], 62 | ) 63 | test('Backend - Debian', tests_backend_debian) 64 | 65 | # Debian backend tests 66 | tests_backend_misc = executable( 67 | 'tests-backend-misc', 68 | 'tests-backend-misc.cpp', 69 | dependencies: [catch2_dep, asgen_lib_dep], 70 | include_directories: [src_dir], 71 | ) 72 | test('Backend - Misc Tests', tests_backend_misc) 73 | -------------------------------------------------------------------------------- /src/backends/debian/tagfile.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2025 Matthias Klumpp 3 | * 4 | * Licensed under the GNU Lesser General Public License Version 3 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the license, or 9 | * (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this software. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | namespace ASGenerator 28 | { 29 | 30 | /** 31 | * Parser for Debian's RFC2822-style metadata. 32 | */ 33 | class TagFile 34 | { 35 | private: 36 | std::vector m_content; 37 | std::size_t m_pos; 38 | std::unordered_map m_currentBlock; 39 | std::string m_fname; 40 | 41 | void readCurrentBlockData(); 42 | 43 | public: 44 | TagFile(); 45 | 46 | void open(const std::string &fname, bool compressed = true); 47 | 48 | const std::string &fname() const 49 | { 50 | return m_fname; 51 | } 52 | 53 | void load(const std::string &data); 54 | 55 | void first(); 56 | 57 | bool nextSection(); 58 | 59 | bool eof() const; 60 | 61 | std::string readField(const std::string &fieldName, const std::string &defaultValue = "") const; 62 | 63 | bool hasField(const std::string &fieldName) const; 64 | 65 | const std::unordered_map ¤tBlock() const 66 | { 67 | return m_currentBlock; 68 | } 69 | }; 70 | 71 | } // namespace ASGenerator 72 | -------------------------------------------------------------------------------- /src/backends/rpmmd/rpmutils.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2025 Matthias Klumpp 3 | * 4 | * Licensed under the GNU Lesser General Public License Version 3 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the license, or 9 | * (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this software. If not, see . 18 | */ 19 | 20 | #include "rpmutils.h" 21 | 22 | #include 23 | #include 24 | 25 | #include "../../logging.h" 26 | #include "../../downloader.h" 27 | #include "../../utils.h" 28 | 29 | namespace fs = std::filesystem; 30 | 31 | namespace ASGenerator 32 | { 33 | 34 | std::string downloadIfNecessary(const std::string &url, const std::string &destLocation, Downloader *downloader) 35 | { 36 | if (downloader == nullptr) 37 | downloader = &Downloader::get(); 38 | 39 | if (Utils::isRemote(url)) { 40 | const std::string destFileName = (fs::path(destLocation) / fs::path(url).filename()).string(); 41 | try { 42 | fs::create_directories(destLocation); 43 | downloader->downloadFile(url, destFileName); 44 | return destFileName; 45 | } catch (const std::exception &e) { 46 | logDebug("Unable to download: {}", e.what()); 47 | throw std::runtime_error(std::format("Could not obtain file {}", url)); 48 | } 49 | } else { 50 | if (fs::exists(url)) 51 | return url; 52 | } 53 | 54 | throw std::runtime_error(std::format("Could not obtain file {}", url)); 55 | } 56 | 57 | } // namespace ASGenerator 58 | -------------------------------------------------------------------------------- /src/backends/dummy/pkgindex.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2025 Matthias Klumpp 3 | * 4 | * Licensed under the GNU Lesser General Public License Version 3 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the license, or 9 | * (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this software. If not, see . 18 | */ 19 | 20 | #include "pkgindex.h" 21 | 22 | #include "../../logging.h" 23 | 24 | namespace ASGenerator 25 | { 26 | 27 | DummyPackageIndex::DummyPackageIndex(const std::string &dir) 28 | { 29 | // Constructor doesn't need to do anything for dummy backend 30 | } 31 | 32 | void DummyPackageIndex::release() 33 | { 34 | m_pkgCache.clear(); 35 | } 36 | 37 | std::vector> DummyPackageIndex::packagesFor( 38 | const std::string &suite, 39 | const std::string §ion, 40 | const std::string &arch, 41 | bool withLongDescs) 42 | { 43 | std::vector> packages; 44 | packages.push_back(std::make_shared("test", "1.0", "amd64")); 45 | return packages; 46 | } 47 | 48 | std::shared_ptr DummyPackageIndex::packageForFile( 49 | const std::string &fname, 50 | const std::string &suite, 51 | const std::string §ion) 52 | { 53 | // FIXME: not implemented 54 | return nullptr; 55 | } 56 | 57 | bool DummyPackageIndex::hasChanges( 58 | std::shared_ptr dstore, 59 | const std::string &suite, 60 | const std::string §ion, 61 | const std::string &arch) 62 | { 63 | return true; 64 | } 65 | 66 | } // namespace ASGenerator 67 | -------------------------------------------------------------------------------- /src/backends/archlinux/listfile.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2025 Matthias Klumpp 3 | * 4 | * Licensed under the GNU Lesser General Public License Version 3 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the license, or 9 | * (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this software. If not, see . 18 | */ 19 | 20 | #include "listfile.h" 21 | 22 | #include "../../utils.h" 23 | 24 | namespace ASGenerator 25 | { 26 | 27 | ListFile::ListFile() {} 28 | 29 | void ListFile::loadData(const std::vector &data) 30 | { 31 | std::string dataStr(data.begin(), data.end()); 32 | auto content = Utils::splitString(dataStr, '\n'); 33 | 34 | std::string blockName; 35 | for (const auto &line : content) { 36 | if (line.starts_with("%") && line.ends_with("%")) { 37 | blockName = line.substr(1, line.length() - 2); 38 | continue; 39 | } 40 | 41 | if (line.empty()) { 42 | blockName.clear(); 43 | continue; 44 | } 45 | 46 | if (!blockName.empty()) { 47 | auto it = m_entries.find(blockName); 48 | if (it != m_entries.end()) { 49 | it->second += "\n" + line; 50 | } else { 51 | m_entries[blockName] = line; 52 | } 53 | } 54 | } 55 | } 56 | 57 | std::string ListFile::getEntry(const std::string &name) 58 | { 59 | auto it = m_entries.find(name); 60 | if (it != m_entries.end()) 61 | return it->second; 62 | 63 | return {}; 64 | } 65 | 66 | } // namespace ASGenerator 67 | -------------------------------------------------------------------------------- /data/templates/default/issues_page.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Issues for {{package_name}} in {{suite}}/{{section}}{% endblock %} 4 | 5 | {% block float_right %} 6 | Last updated on: {{time}} 7 | {% endblock %} 8 | 9 | {% block header_content %} 10 | ⇦ | 11 | {{package_name}} [{{section}}] 12 | {% endblock %} 13 | 14 | {% block content %} 15 |

Hints for {{package_name}} in {{section}}

16 | 17 |
18 | 19 | {% for entry in entries %} 20 |

21 | {{entry.component_id}} 22 | {% for arch in entry.architectures %} 23 | ⚙ {{arch.arch}} 24 | {% endfor %} 25 |

26 | 27 | {% if entry.has_errors %} 28 |
29 |

Errors

30 |
    31 | {% for error in entry.errors %} 32 |
  • 33 | {{error.error_tag}}
    34 | {{error.error_description}} 35 |
  • 36 | {% endfor %} 37 |
38 |
39 | {% endif %} 40 | 41 | {% if entry.has_warnings %} 42 |
43 |

Warnings

44 |
    45 | {% for warning in entry.warnings %} 46 |
  • 47 | {{warning.warning_tag}}
    48 | {{warning.warning_description}} 49 |
  • 50 | {% endfor %} 51 |
52 |
53 | {% endif %} 54 | 55 | {% if entry.has_infos %} 56 |
57 |

Hints

58 |
    59 | {% for info in entry.infos %} 60 |
  • 61 | {{info.info_tag}}
    62 | {{info.info_description}} 63 |
  • 64 | {% endfor %} 65 |
66 |
67 | {% endif %} 68 | 69 | {% endfor %} 70 | 71 |
72 | {% endblock %} 73 | -------------------------------------------------------------------------------- /data/org.freedesktop.appstream.generator.metainfo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | org.freedesktop.appstream.generator 5 | FSFAP 6 | LGPL-3.0+ 7 | 8 | AppStream Generator 9 | A fast AppStream metadata generator 10 | 11 | 12 |

13 | AppStream is a metadata specification which permits software components to provide information about themselves 14 | to automated systems and end-users before the software is actually installed. 15 |

16 |

17 | The appstream-generator tool generates AppStream metadata from the repositories of a software distribution. 18 | It currently supports the following repository formats / distributions: Debian, Ubuntu, Arch Linux, RPM-MD (Fedora, Mageia). 19 |

20 |

21 | The generator will produce AppStream catalog metadata files in the AppStream YAML or XML format to be shipped 22 | to users, as well as a detailed HTML report about found components and HTML and JSON reports on issues detected 23 | with the scanned metadata. It reads .desktop files as well as metainfo files, renders fonts, scales images, caches 24 | screenshots etc. to produce high-quality metadata for AppStream based software centers to consume. 25 | Usually, appstream-generator is integrated with the existing software build & packaging workflow of 26 | a distribution. 27 |

28 |
29 | 30 | https://github.com/ximion/appstream-generator 31 | https://github.com/ximion/appstream-generator/blob/master/docs/index.md 32 | https://github.com/ximion/appstream-generator/issues 33 | 34 | Freedesktop 35 | 36 | Matthias Klumpp 37 | 38 | 39 | 40 | appstream-generator 41 | 42 | 43 | 44 |
45 | -------------------------------------------------------------------------------- /src/meson.build: -------------------------------------------------------------------------------- 1 | 2 | subdir('backends') 3 | 4 | asgencpp_src = files( 5 | 'config.cpp', 6 | 'contentsstore.cpp', 7 | 'cptmodifiers.cpp', 8 | 'datainjectpkg.cpp', 9 | 'datastore.cpp', 10 | 'dataunits.cpp', 11 | 'downloader.cpp', 12 | 'engine.cpp', 13 | 'extractor.cpp', 14 | 'hintregistry.cpp', 15 | 'iconhandler.cpp', 16 | 'logging.cpp', 17 | 'reportgenerator.cpp', 18 | 'result.cpp', 19 | 'utils.cpp', 20 | 'yaml-utils.cpp', 21 | 'zarchive.cpp', 22 | 'backends/interfaces.cpp', 23 | ) 24 | 25 | asgencpp_hdr = files( 26 | 'config.h', 27 | 'contentsstore.h', 28 | 'cptmodifiers.h', 29 | 'datainjectpkg.h', 30 | 'datastore.h', 31 | 'dataunits.h', 32 | 'downloader.h', 33 | 'engine.h', 34 | 'extractor.h', 35 | 'hintregistry.h', 36 | 'iconhandler.h', 37 | 'logging.h', 38 | 'reportgenerator.h', 39 | 'result.h', 40 | 'utils.h', 41 | 'yaml-utils.h', 42 | 'zarchive.h', 43 | 'backends/interfaces.h', 44 | ) 45 | 46 | conf_data = configuration_data() 47 | conf_data.set_quoted('INSTALL_PREFIX', get_option('prefix')) 48 | conf_data.set_quoted('DATADIR', join_paths(get_option('prefix'), get_option('datadir'), 'appstream')) 49 | conf_data.set_quoted('ASGEN_VERSION', asgen_version) 50 | conf_data.set('HAVE_BACKWARD', backward_dep.found()) 51 | defines_h = configure_file( 52 | output: 'defines.h', 53 | configuration: conf_data 54 | ) 55 | 56 | asgen_deps = [ 57 | glib_dep, 58 | appstream_dep, 59 | ascompose_dep, 60 | fyaml_dep, 61 | lmdb_dep, 62 | archive_dep, 63 | curl_dep, 64 | tbb_dep, 65 | icu_dep, 66 | inja_dep, 67 | libxml2_dep, 68 | ] 69 | 70 | asgen_args = [ 71 | '-DI_KNOW_THE_APPSTREAM_COMPOSE_API_IS_SUBJECT_TO_CHANGE' 72 | ] 73 | 74 | asgen_lib = static_library( 75 | 'asgenlib', 76 | [asgencpp_src, asgencpp_hdr, defines_h, backends_src], 77 | dependencies: asgen_deps, 78 | cpp_args: asgen_args, 79 | ) 80 | 81 | asgen_lib_dep = declare_dependency( 82 | link_with: asgen_lib, 83 | include_directories: [src_dir], 84 | dependencies: asgen_deps, 85 | compile_args: asgen_args, 86 | ) 87 | 88 | asgen_exe = executable('appstream-generator', 89 | ['main.cpp'], 90 | dependencies: [ 91 | asgen_lib_dep, 92 | backward_deps, 93 | ], 94 | install: true 95 | ) 96 | -------------------------------------------------------------------------------- /src/backends/ubuntu/ubupkgindex.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2020 Canonical Ltd 3 | * Author: Iain Lane 4 | * 5 | * Licensed under the GNU Lesser General Public License Version 3 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the license, or 10 | * (at your option) any later version. 11 | * 12 | * This software is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with this software. If not, see . 19 | */ 20 | 21 | #pragma once 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include "../debian/debpkgindex.h" 30 | #include "ubupkg.h" 31 | 32 | namespace ASGenerator 33 | { 34 | 35 | class UbuntuPackageIndex : public DebianPackageIndex 36 | { 37 | public: 38 | explicit UbuntuPackageIndex(const std::string &dir); 39 | 40 | void release() override; 41 | 42 | std::vector> packagesFor( 43 | const std::string &suite, 44 | const std::string §ion, 45 | const std::string &arch, 46 | bool withLongDescs = true) override; 47 | 48 | std::shared_ptr packageForFile( 49 | const std::string &fname, 50 | const std::string &suite = "", 51 | const std::string §ion = "") override; 52 | 53 | protected: 54 | // Make tmpDir accessible to this class 55 | using DebianPackageIndex::m_tmpDir; 56 | 57 | std::shared_ptr newPackage(const std::string &name, const std::string &ver, const std::string &arch) 58 | override; 59 | 60 | private: 61 | std::shared_ptr m_langpacks; 62 | 63 | // holds the IDs of suite/section/arch combinations where we scanned language packs 64 | std::unordered_set m_checkedLangPacks; 65 | }; 66 | 67 | } // namespace ASGenerator 68 | -------------------------------------------------------------------------------- /.github/workflows/build-test.yml: -------------------------------------------------------------------------------- 1 | name: Build Test 2 | 3 | permissions: 4 | contents: read 5 | 6 | on: [push, pull_request] 7 | 8 | jobs: 9 | build-debian-testing: 10 | name: Debian Testing 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v6 15 | 16 | - name: Create Build Environment 17 | run: cd tests/ci/ && podman build -t asgen -f ./Dockerfile-debian-testing . 18 | 19 | - name: Build 20 | run: podman run -a stdout -a stderr -e CXX=g++ -v `pwd`:/build asgen 21 | ./tests/ci/run-build.sh 22 | 23 | - name: Tests 24 | run: podman run -a stdout -a stderr -e CXX=g++ -v `pwd`:/build asgen 25 | ./tests/ci/run-tests.sh 26 | 27 | 28 | build-debian-stable: 29 | name: Debian Stable 30 | runs-on: ubuntu-latest 31 | 32 | steps: 33 | - uses: actions/checkout@v6 34 | 35 | - name: Create Build Environment 36 | run: cd tests/ci/ && podman build -t asgen -f ./Dockerfile-debian-stable . 37 | 38 | - name: Build 39 | run: podman run -a stdout -a stderr -e CXX=g++ -v `pwd`:/build asgen 40 | ./tests/ci/run-build.sh 41 | 42 | - name: Tests 43 | run: podman run -a stdout -a stderr -e CXX=g++ -v `pwd`:/build asgen 44 | ./tests/ci/run-tests.sh 45 | 46 | 47 | build-fedora-latest: 48 | name: Fedora Latest 49 | runs-on: ubuntu-latest 50 | 51 | steps: 52 | - uses: actions/checkout@v6 53 | 54 | - name: Create Build Environment 55 | run: cd tests/ci/ && podman build -t asgen -f ./Dockerfile-fedora-latest . 56 | 57 | - name: Build 58 | run: podman run -a stdout -a stderr -e CXX=g++ -v `pwd`:/build asgen 59 | ./tests/ci/run-build.sh 60 | 61 | - name: Tests 62 | run: podman run -a stdout -a stderr -e CXX=g++ -v `pwd`:/build asgen 63 | ./tests/ci/run-tests.sh 64 | 65 | 66 | build-ubuntu-lts: 67 | name: Ubuntu LTS 68 | runs-on: ubuntu-24.04 69 | env: 70 | CC: gcc-14 71 | CXX: g++-14 72 | steps: 73 | - uses: actions/checkout@v6 74 | 75 | - name: Create Build Environment 76 | run: sudo ./tests/ci/install-deps-deb.sh 77 | 78 | - name: Make & Install 3rd-party 79 | run: sudo ./tests/ci/ci-install-extern.sh 80 | 81 | - name: Build 82 | run: ./tests/ci/run-build.sh 83 | 84 | - name: Tests 85 | run: ./tests/ci/run-tests.sh 86 | -------------------------------------------------------------------------------- /src/backends/debian/debutils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2025 Matthias Klumpp 3 | * Copyright (C) The APT development team. 4 | * Copyright (C) 2016 Canonical Ltd 5 | * Author(s): Iain Lane 6 | * 7 | * Licensed under the GNU Lesser General Public License Version 3 8 | * 9 | * This program is free software: you can redistribute it and/or modify 10 | * it under the terms of the GNU Lesser General Public License as published by 11 | * the Free Software Foundation, either version 3 of the license, or 12 | * (at your option) any later version. 13 | * 14 | * This software is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | * GNU Lesser General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU Lesser General Public License 20 | * along with this software. If not, see . 21 | */ 22 | 23 | #pragma once 24 | 25 | #include 26 | 27 | namespace ASGenerator 28 | { 29 | 30 | class Downloader; 31 | 32 | /** 33 | * If prefix is remote, download the first of (prefix + suffix).{xz,bz2,gz}, 34 | * otherwise check if any of (prefix + suffix).{xz,bz2,gz} exists. 35 | * 36 | * Returns: Path to the file, which is guaranteed to exist. 37 | * 38 | * Params: 39 | * prefix = First part of the address, i.e. 40 | * "http://ftp.debian.org/debian/" or "/srv/mirrors/debian/" 41 | * destPrefix = If the file is remote, the directory to save it under, 42 | * which is created if necessary. 43 | * suffix = the rest of the address, so that (prefix + 44 | * suffix).format({xz,bz2,gz}) is a full path or URL, i.e. 45 | * "dists/unstable/main/binary-i386/Packages.%s". The suffix must 46 | * contain exactly one "%s"; this function is only suitable for 47 | * finding `.xz`, `.bz2` and `.gz` files. 48 | */ 49 | std::string downloadIfNecessary( 50 | const std::string &prefix, 51 | const std::string &destPrefix, 52 | const std::string &suffix, 53 | Downloader *downloader = nullptr); 54 | 55 | /** 56 | * Compare two Debian-style version numbers. 57 | */ 58 | int compareVersions(const std::string &a, const std::string &b); 59 | 60 | } // namespace ASGenerator 61 | -------------------------------------------------------------------------------- /src/backends/archlinux/alpkgindex.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2025 Matthias Klumpp 3 | * 4 | * Licensed under the GNU Lesser General Public License Version 3 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the license, or 9 | * (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this software. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "../interfaces.h" 28 | #include "../../utils.h" 29 | #include "alpkg.h" 30 | #include "listfile.h" 31 | 32 | namespace ASGenerator 33 | { 34 | 35 | class ArchPackageIndex : public PackageIndex 36 | { 37 | public: 38 | explicit ArchPackageIndex(const std::string &dir); 39 | 40 | void release() override; 41 | 42 | std::vector> packagesFor( 43 | const std::string &suite, 44 | const std::string §ion, 45 | const std::string &arch, 46 | bool withLongDescs = true) override; 47 | 48 | std::shared_ptr packageForFile( 49 | const std::string &fname, 50 | const std::string &suite = "", 51 | const std::string §ion = "") override; 52 | 53 | bool hasChanges( 54 | std::shared_ptr dstore, 55 | const std::string &suite, 56 | const std::string §ion, 57 | const std::string &arch) override; 58 | 59 | private: 60 | fs::path m_rootDir; 61 | std::unordered_map>> m_pkgCache; 62 | 63 | void setPkgDescription(std::shared_ptr pkg, const std::string &pkgDesc); 64 | std::vector> loadPackages( 65 | const std::string &suite, 66 | const std::string §ion, 67 | const std::string &arch); 68 | }; 69 | 70 | } // namespace ASGenerator 71 | -------------------------------------------------------------------------------- /data/templates/default/main.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Start{% endblock %} 4 | 5 | {% block header_content %} 6 | AppStream data hints for {{project_name}} 7 | {% endblock %} 8 | 9 | {% block content %} 10 |
11 | 12 |

Welcome!

13 |

Welcome to the AppStream Generator HTML pages!

14 |

15 | These pages exist to provide a user-friendly view on the issues discovered by the 16 | AppStream metadata generator while extracting metadata from packages in the {{project_name}} archive. 17 | They can also be used to take a look at the raw metadata, to spot possible problems 18 | with the data itself or the generation process. 19 |

20 | 21 |

Select a suite

22 | {% for suite in suites %} 23 |

{{suite.suite}}

24 | {% endfor %} 25 |
26 | {% for oldsuite in oldsuites %} 27 |

{{oldsuite.suite}}

28 | {% endfor %} 29 | 30 |

31 | AppStream Generator Logo 32 |

What is AppStream?

33 |

34 | AppStream is a cross-distro XML format to provide metadata for software components and to assign unique identifiers to software.
35 | In {{project_name}}, we parse all XML provided by upstream projects as well as other metadata (.desktop-files, ...), and compile a single 36 | metadata file from it, which is then shipped to users. 37 |

38 |

39 | The generated metadata can for example be used by software centers like GNOME Software or KDE Discover to display a user-friendly application-centric 40 | view on the package archive.
41 | It can also be used by other software to find missing plugins, codecs, fonts, etc. or simply by users to install software on any Linux distribution 42 | without knowing the exact package name. 43 |

44 | 45 |

More information

46 |

47 | The offical AppStream specification can be found at freedesktop.org. 48 |

49 |

50 | You can find the source-code of the AppStream Generator here. 51 |

52 | 53 |
54 | {% endblock %} 55 | -------------------------------------------------------------------------------- /src/backends/dummy/dummypkg.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2025 Matthias Klumpp 3 | * 4 | * Licensed under the GNU Lesser General Public License Version 3 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the license, or 9 | * (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this software. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "../interfaces.h" 28 | 29 | namespace ASGenerator 30 | { 31 | 32 | class DummyPackage : public Package 33 | { 34 | private: 35 | std::string m_name; 36 | std::string m_version; 37 | std::string m_arch; 38 | std::string m_maintainer; 39 | std::unordered_map m_description; 40 | std::string m_testPkgFilename; 41 | PackageKind m_kind; 42 | std::vector m_contents; 43 | 44 | public: 45 | DummyPackage(const std::string &pname, const std::string &pver, const std::string &parch); 46 | 47 | std::string name() const override; 48 | std::string ver() const override; 49 | std::string arch() const override; 50 | std::string maintainer() const override; 51 | 52 | const std::unordered_map &description() const override; 53 | 54 | std::string getFilename() override; 55 | void setFilename(const std::string &fname); 56 | 57 | void setMaintainer(const std::string &maint); 58 | 59 | const std::vector &contents() override; 60 | 61 | std::vector getFileData(const std::string &fname) override; 62 | 63 | void finish() override; 64 | 65 | PackageKind kind() const noexcept override; 66 | void setKind(PackageKind v) noexcept; 67 | 68 | void setDescription(const std::string &text, const std::string &locale); 69 | }; 70 | 71 | } // namespace ASGenerator 72 | -------------------------------------------------------------------------------- /src/hintregistry.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2025 Matthias Klumpp 3 | * 4 | * Licensed under the GNU Lesser General Public License Version 3 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the license, or 9 | * (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this software. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | namespace ASGenerator 28 | { 29 | 30 | /** 31 | * Each issue hint type has a severity assigned to it: 32 | * 33 | * ERROR: A fatal error which resulted in the component being excluded from the final metadata. 34 | * WARNING: An issue which did not prevent generating meaningful data, but which is still serious 35 | * and should be fixed (warning of this kind usually result in less data). 36 | * INFO: Information, no immediate action needed (but will likely be an issue later). 37 | * PEDANTIC: Information which may improve the data, but could also be ignored. 38 | */ 39 | 40 | /** 41 | * Definition of an issue hint. 42 | */ 43 | struct HintDefinition { 44 | std::string tag; /// Unique issue tag 45 | AsIssueSeverity severity; /// Issue severity 46 | std::string explanation; /// Explanation template 47 | }; 48 | 49 | /** 50 | * Load all issue hints from file and register them globally. 51 | */ 52 | void loadHintsRegistry(); 53 | 54 | /** 55 | * Save information about all hint templates we know about to a JSON file. 56 | */ 57 | void saveHintsRegistryToJsonFile(const std::string &fname); 58 | 59 | /** 60 | * Retrieve hint definition for a given tag. 61 | */ 62 | HintDefinition retrieveHintDef(const char *tag); 63 | 64 | /** 65 | * Convert a hint to JSON format. 66 | */ 67 | std::string hintToJsonString(const std::string &tag, const std::unordered_map &vars); 68 | 69 | } // namespace ASGenerator 70 | -------------------------------------------------------------------------------- /src/backends/freebsd/fbsdpkgindex.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023-2025 Serenity Cyber Security, LLC 3 | * Author: Gleb Popov 4 | * 5 | * Licensed under the GNU Lesser General Public License Version 3 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the license, or 10 | * (at your option) any later version. 11 | * 12 | * This software is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with this software. If not, see . 19 | */ 20 | 21 | #pragma once 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "../interfaces.h" 32 | #include "../../utils.h" 33 | #include "fbsdpkg.h" 34 | 35 | namespace ASGenerator 36 | { 37 | 38 | class FreeBSDPackageIndex : public PackageIndex 39 | { 40 | public: 41 | explicit FreeBSDPackageIndex(const std::string &dir); 42 | 43 | void release() override; 44 | 45 | std::vector> packagesFor( 46 | const std::string &suite, 47 | const std::string §ion, 48 | const std::string &arch, 49 | bool withLongDescs = true) override; 50 | 51 | std::shared_ptr packageForFile( 52 | const std::string &fname, 53 | const std::string &suite = "", 54 | const std::string §ion = "") override; 55 | 56 | bool hasChanges( 57 | std::shared_ptr dstore, 58 | const std::string &suite, 59 | const std::string §ion, 60 | const std::string &arch) override; 61 | 62 | private: 63 | fs::path m_rootDir; 64 | std::unordered_map>> m_pkgCache; 65 | std::mutex m_cacheMutex; // Thread safety for cache access 66 | 67 | std::vector> loadPackages( 68 | const std::string &suite, 69 | const std::string §ion, 70 | const std::string &arch); 71 | }; 72 | 73 | } // namespace ASGenerator 74 | -------------------------------------------------------------------------------- /src/cptmodifiers.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021-2025 Matthias Klumpp 3 | * 4 | * Licensed under the GNU Lesser General Public License Version 3 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the license, or 9 | * (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this software. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "config.h" 29 | 30 | namespace ASGenerator 31 | { 32 | 33 | class GeneratorResult; 34 | 35 | /** 36 | * Helper class to provide information about repository-specific metadata modifications. 37 | * Instances of this class must be thread safe. 38 | */ 39 | class InjectedModifications 40 | { 41 | public: 42 | InjectedModifications(); 43 | ~InjectedModifications(); 44 | 45 | void loadForSuite(std::shared_ptr suite); 46 | 47 | bool hasRemovedComponents() const; 48 | 49 | /** 50 | * Test if component was marked for deletion. 51 | */ 52 | bool isComponentRemoved(const std::string &cid) const; 53 | 54 | /** 55 | * Get injected custom data entries. 56 | */ 57 | std::optional> injectedCustomData(const std::string &cid) const; 58 | 59 | void addRemovalRequestsToResult(GeneratorResult *gres) const; 60 | 61 | // Delete copy constructor and assignment operator 62 | InjectedModifications(const InjectedModifications &) = delete; 63 | InjectedModifications &operator=(const InjectedModifications &) = delete; 64 | 65 | private: 66 | std::unordered_map m_removedComponents; 67 | std::unordered_map> m_injectedCustomData; 68 | 69 | bool m_hasRemovedCpts; 70 | bool m_hasInjectedCustom; 71 | 72 | mutable std::shared_mutex m_mutex; 73 | }; 74 | 75 | } // namespace ASGenerator 76 | -------------------------------------------------------------------------------- /docs/usage.md: -------------------------------------------------------------------------------- 1 | # AppStream Generator Usage 2 | 3 | ## How to use 4 | 5 | ### Generating distro metadata 6 | To generate AppStream distribution metadata for your repository, create a local 7 | mirror of the repository first. 8 | Then create a new folder, and write a `asgen-config.json` configuration file for the 9 | metadata generator. Details on the file and an example can be found in [the asgen-config docs](asgen-config.md). 10 | 11 | After the config file has been written, you can generate the metadata as follows: 12 | ```Bash 13 | cd /srv/asgen/workspace # path where the asgen-config.json file is located 14 | appstream-generator process chromodoris # replace "chromodoris" with the name of the suite you want to analyze 15 | ``` 16 | The generator is assuming you have enough memory and disk space on your machine to cache stuff. 17 | Resulting metadata will be placed in `export/data/`, machine-readable issue-hints can be found in `export/hints/` and the processed screenshots and icons are located in `export/media/`. 18 | 19 | In order to drop old packages and cruft from the databases, you should run 20 | ```Bash 21 | appstream-generator cleanup 22 | ``` 23 | every once in a while. This will drop all superseded packages and data from the caches. 24 | 25 | If you do not want to `cd` into the workspace directory, you can also use the `--workspace|-w` flag to define a workspace. 26 | 27 | ### Validating metadata 28 | You can validate the resulting metadata using the AppStream client tools. 29 | Use `appstreamcli validate .xml.gz` for XML metadata, and `dep11-validate .yml.gz` for YAML. This will check the files for mistakes and compliance with the specification. 30 | 31 | Keep in mind that the generator will always generate spec-compliant metadata, but might - depending on the input - produce data which has smaller flaws (e.g. formatting issues in the long descriptions). In these cases, issue-hints will have been emitted, so the package maintainers can address the metadata issues. 32 | 33 | ## Troubleshooting 34 | 35 | ### Memory Usage 36 | The `appstream-generator` will not hesitate to use RAM, and also decide to use lots of it if enough is available. This is especially true when scanning new packages for their contents and storing the information in the LMDB database. 37 | Ideally make sure that you are running a 64bit system with at least 4GB of RAM if you want to use the generator properly. 38 | For the generator, speed matters more than RAM usage. You can use cgroups to limit the amount of memory the generator uses. 39 | 40 | ### Profiling 41 | TODO 42 | -------------------------------------------------------------------------------- /tests/tests-engine.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019-2025 Matthias Klumpp 3 | * 4 | * SPDX-License-Identifier: LGPL-3.0-or-later 5 | */ 6 | 7 | #define CATCH_CONFIG_MAIN 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | #include "logging.h" 14 | #include "utils.h" 15 | #include "config.h" 16 | #include "engine.h" 17 | 18 | using namespace ASGenerator; 19 | 20 | static struct TestSetup { 21 | TestSetup() 22 | { 23 | setVerbose(true); 24 | } 25 | } testSetup; 26 | 27 | TEST_CASE("Engine with test data", "[engine][integration]") 28 | { 29 | auto tempDir = fs::temp_directory_path() / std::format("asgen-test-{}", Utils::randomString(8)); 30 | fs::create_directories(tempDir); 31 | auto samplesDir = Utils::getTestSamplesDir(); 32 | 33 | SECTION("Test init with Debian backend") 34 | { 35 | auto debianSamplesDir = samplesDir / "debian"; 36 | 37 | auto &config = Config::get(); 38 | config.setWorkspaceDir("/tmp"); 39 | config.backend = Backend::Debian; 40 | config.archiveRoot = debianSamplesDir.string(); 41 | 42 | REQUIRE_NOTHROW([]() { 43 | Engine engine; 44 | }()); 45 | } 46 | 47 | fs::remove_all(tempDir); 48 | } 49 | 50 | TEST_CASE("Engine package info functionality", "[engine]") 51 | { 52 | auto tempDir = fs::temp_directory_path() / std::format("asgen-test-{}", Utils::randomString(8)); 53 | fs::create_directories(tempDir); 54 | 55 | auto &config = Config::get(); 56 | config.backend = Backend::Dummy; 57 | config.setWorkspaceDir("/tmp"); 58 | config.archiveRoot = (Utils::getTestSamplesDir() / "debian").string(); 59 | 60 | Engine engine; 61 | 62 | SECTION("Print package info with invalid ID format") 63 | { 64 | // Test with malformed package ID (should return false) 65 | REQUIRE_FALSE(engine.printPackageInfo("invalid-package-id")); 66 | REQUIRE_FALSE(engine.printPackageInfo("too/many/slashes/here")); 67 | REQUIRE_FALSE(engine.printPackageInfo("notEnoughSlashes")); 68 | } 69 | 70 | SECTION("Print package info with valid ID format") 71 | { 72 | // Test with properly formatted package ID (even if package doesn't exist) 73 | // This should return true as the format is correct, even if no data is found 74 | REQUIRE(engine.printPackageInfo("package/1.0.0/amd64")); 75 | REQUIRE(engine.printPackageInfo("test-pkg/2.1.0/i386")); 76 | } 77 | 78 | fs::remove_all(tempDir); 79 | } 80 | -------------------------------------------------------------------------------- /tests/tests-backend-misc.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019-2025 Matthias Klumpp 3 | * 4 | * SPDX-License-Identifier: LGPL-3.0-or-later 5 | */ 6 | 7 | #define CATCH_CONFIG_MAIN 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "logging.h" 19 | #include "utils.h" 20 | 21 | #include "backends/archlinux/listfile.h" 22 | #include "backends/rpmmd/rpmpkgindex.h" 23 | 24 | using namespace ASGenerator; 25 | 26 | static struct TestSetup { 27 | TestSetup() 28 | { 29 | setVerbose(true); 30 | } 31 | } testSetup; 32 | 33 | TEST_CASE("ListFile parsing", "[backend][archlinux]") 34 | { 35 | SECTION("Parse Arch Linux package format") 36 | { 37 | const std::string testData = R"(%FILENAME% 38 | a2ps-4.14-6-x86_64.pkg.tar.xz 39 | 40 | %NAME% 41 | a2ps 42 | 43 | %VERSION% 44 | 4.14-6 45 | 46 | %DESC% 47 | An Any to PostScript filter 48 | 49 | %CSIZE% 50 | 629320 51 | 52 | %MULTILINE% 53 | Blah1 54 | BLUBB2 55 | EtcEtcEtc3 56 | 57 | %SHA256SUM% 58 | a629a0e0eca0d96a97eb3564f01be495772439df6350600c93120f5ac7f3a1b5)"; 59 | 60 | ListFile lf; 61 | std::vector testDataBytes(testData.begin(), testData.end()); 62 | lf.loadData(testDataBytes); 63 | 64 | // Test single-line entries 65 | REQUIRE(lf.getEntry("FILENAME") == "a2ps-4.14-6-x86_64.pkg.tar.xz"); 66 | REQUIRE(lf.getEntry("VERSION") == "4.14-6"); 67 | REQUIRE(lf.getEntry("NAME") == "a2ps"); 68 | REQUIRE(lf.getEntry("DESC") == "An Any to PostScript filter"); 69 | REQUIRE(lf.getEntry("CSIZE") == "629320"); 70 | 71 | // Test multiline entry 72 | REQUIRE(lf.getEntry("MULTILINE") == "Blah1\nBLUBB2\nEtcEtcEtc3"); 73 | 74 | // Test SHA256SUM entry 75 | REQUIRE(lf.getEntry("SHA256SUM") == "a629a0e0eca0d96a97eb3564f01be495772439df6350600c93120f5ac7f3a1b5"); 76 | 77 | // Test non-existent entry 78 | REQUIRE(lf.getEntry("NONEXISTENT").empty()); 79 | } 80 | } 81 | 82 | TEST_CASE("RPMPackageIndex", "[backend][rpmmd]") 83 | { 84 | SECTION("Load RPM packages from test repository") 85 | { 86 | auto samplesDir = Utils::getTestSamplesDir(); 87 | auto rpmmdDir = samplesDir / "rpmmd"; 88 | 89 | RPMPackageIndex pi(rpmmdDir.string()); 90 | auto pkgs = pi.packagesFor("26", "Workstation", "x86_64"); 91 | 92 | REQUIRE(pkgs.size() == 4); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/backends/archlinux/alpkg.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2025 Matthias Klumpp 3 | * 4 | * Licensed under the GNU Lesser General Public License Version 3 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the license, or 9 | * (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this software. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "../interfaces.h" 29 | 30 | namespace ASGenerator 31 | { 32 | 33 | class ArchiveDecompressor; 34 | 35 | class ArchPackage : public Package 36 | { 37 | public: 38 | ArchPackage(); 39 | ~ArchPackage() override = default; 40 | 41 | std::string name() const override; 42 | void setName(const std::string &val); 43 | 44 | std::string ver() const override; 45 | void setVersion(const std::string &val); 46 | 47 | std::string arch() const override; 48 | void setArch(const std::string &val); 49 | 50 | const std::unordered_map &description() const override; 51 | 52 | void setFilename(const std::string &fname); 53 | std::string getFilename() override; 54 | 55 | std::string maintainer() const override; 56 | void setMaintainer(const std::string &maint); 57 | 58 | void setDescription(const std::string &text, const std::string &locale); 59 | 60 | std::vector getFileData(const std::string &fname) override; 61 | 62 | const std::vector &contents() override; 63 | void setContents(const std::vector &c); 64 | 65 | void finish() override; 66 | 67 | private: 68 | std::string m_pkgname; 69 | std::string m_pkgver; 70 | std::string m_pkgarch; 71 | std::string m_pkgmaintainer; 72 | std::unordered_map m_desc; 73 | std::string m_pkgFname; 74 | 75 | std::vector m_contentsL; 76 | std::unique_ptr m_archive; 77 | }; 78 | 79 | } // namespace ASGenerator 80 | -------------------------------------------------------------------------------- /src/backends/rpmmd/rpmpkgindex.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2025 Matthias Klumpp 3 | * 4 | * Licensed under the GNU Lesser General Public License Version 3 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the license, or 9 | * (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this software. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include "../interfaces.h" 31 | #include "rpmpkg.h" 32 | 33 | namespace fs = std::filesystem; 34 | 35 | namespace ASGenerator 36 | { 37 | 38 | class RPMPackageIndex : public PackageIndex 39 | { 40 | public: 41 | explicit RPMPackageIndex(const std::string &dir); 42 | ~RPMPackageIndex(); 43 | 44 | void release() override; 45 | 46 | std::vector> packagesFor( 47 | const std::string &suite, 48 | const std::string §ion, 49 | const std::string &arch, 50 | bool withLongDescs = true) override; 51 | 52 | std::shared_ptr packageForFile( 53 | const std::string &fname, 54 | const std::string &suite = "", 55 | const std::string §ion = "") override; 56 | 57 | bool hasChanges( 58 | std::shared_ptr dstore, 59 | const std::string &suite, 60 | const std::string §ion, 61 | const std::string &arch) override; 62 | 63 | private: 64 | fs::path m_rootDir; 65 | fs::path m_tmpRootDir; 66 | std::unordered_map>> m_pkgCache; 67 | mutable std::mutex m_cacheMutex; // Thread safety for cache access 68 | 69 | void setPkgDescription(std::shared_ptr pkg, const std::string &pkgDesc); 70 | std::vector> loadPackages( 71 | const std::string &suite, 72 | const std::string §ion, 73 | const std::string &arch); 74 | }; 75 | 76 | } // namespace ASGenerator 77 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: LLVM 3 | IndentWidth: 4 4 | --- 5 | Language: Cpp 6 | Standard: C++11 7 | ColumnLimit: 120 8 | BreakBeforeBraces: Linux 9 | PointerAlignment: Right 10 | AlignAfterOpenBracket: AlwaysBreak 11 | AllowAllParametersOfDeclarationOnNextLine: false 12 | AlwaysBreakBeforeMultilineStrings: true 13 | BreakBeforeBinaryOperators: NonAssignment 14 | AlignArrayOfStructures: Left 15 | 16 | # format C++11 braced lists like function calls 17 | Cpp11BracedListStyle: true 18 | 19 | # do not put a space before C++11 braced lists 20 | SpaceBeforeCpp11BracedList: false 21 | 22 | # no namespace indentation to keep indent level low 23 | NamespaceIndentation: None 24 | 25 | # we use template< without space. 26 | SpaceAfterTemplateKeyword: false 27 | 28 | # Always break after template declaration 29 | AlwaysBreakTemplateDeclarations: true 30 | 31 | # macros for which the opening brace stays attached. 32 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH, forever, Q_FOREVER, QBENCHMARK, QBENCHMARK_ONCE ] 33 | 34 | # keep lambda formatting multi-line if not empty 35 | AllowShortLambdasOnASingleLine: Empty 36 | 37 | # return types should not be on their own lines 38 | AlwaysBreakAfterReturnType: None 39 | PenaltyReturnTypeOnItsOwnLine: 1000 40 | AlwaysBreakAfterDefinitionReturnType: None 41 | 42 | # Break constructor initializers before the colon and after the commas, 43 | # and never put the all in one line. 44 | BreakConstructorInitializers: BeforeColon 45 | BreakInheritanceList: BeforeColon 46 | PackConstructorInitializers: Never 47 | 48 | # Place ternary operators after line breaks 49 | BreakBeforeTernaryOperators: true 50 | 51 | # No own indentation level for access modifiers 52 | IndentAccessModifiers: false 53 | AccessModifierOffset: -4 54 | 55 | # Add empty line only when access modifier starts a new logical block. 56 | EmptyLineBeforeAccessModifier: LogicalBlock 57 | 58 | # Only merge empty functions. 59 | AllowShortFunctionsOnASingleLine: Empty 60 | 61 | # Don't indent case labels. 62 | IndentCaseLabels: false 63 | 64 | # No space after C-style cast 65 | SpaceAfterCStyleCast: false 66 | 67 | # Never pack arguments or parameters 68 | BinPackArguments: false 69 | BinPackParameters: false 70 | 71 | # Avoid breaking around an assignment operator 72 | PenaltyBreakAssignment: 150 73 | 74 | # Left-align newline escapes, e.g. in macros 75 | AlignEscapedNewlines: Left 76 | 77 | # Enums should be one entry per line 78 | AllowShortEnumsOnASingleLine: false 79 | 80 | # we want consecutive macros to be aligned 81 | AlignConsecutiveMacros: true 82 | 83 | # never sort includes, only regroup (in rare cases) 84 | IncludeBlocks: Regroup 85 | SortIncludes: Never 86 | -------------------------------------------------------------------------------- /src/backends/freebsd/fbsdpkg.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023-2025 Serenity Cyber Security, LLC 3 | * Author: Gleb Popov 4 | * 5 | * Licensed under the GNU Lesser General Public License Version 3 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the license, or 10 | * (at your option) any later version. 11 | * 12 | * This software is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with this software. If not, see . 19 | */ 20 | 21 | #pragma once 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "../interfaces.h" 32 | #include "../../utils.h" 33 | 34 | namespace ASGenerator 35 | { 36 | 37 | class ArchiveDecompressor; 38 | 39 | class FreeBSDPackage : public Package 40 | { 41 | public: 42 | FreeBSDPackage(const std::string &pkgRoot, const nlohmann::json &j); 43 | ~FreeBSDPackage() override = default; 44 | 45 | std::string name() const override; 46 | std::string ver() const override; 47 | std::string arch() const override; 48 | std::string maintainer() const override; 49 | std::string getFilename() override; 50 | 51 | const std::unordered_map &summary() const override; 52 | const std::unordered_map &description() const override; 53 | 54 | std::vector getFileData(const std::string &fname) override; 55 | 56 | const std::vector &contents() override; 57 | 58 | void finish() override; 59 | 60 | PackageKind kind() const noexcept override; 61 | 62 | private: 63 | nlohmann::json m_pkgjson; 64 | fs::path m_pkgFname; 65 | PackageKind m_kind; 66 | std::unique_ptr m_pkgArchive; 67 | std::vector m_contentsL; 68 | 69 | // Mutable cache members for lazy initialization of summary/description 70 | mutable std::unordered_map m_summaryCache; 71 | mutable std::unordered_map m_descriptionCache; 72 | 73 | mutable std::mutex m_mutex; 74 | }; 75 | 76 | } // namespace ASGenerator 77 | -------------------------------------------------------------------------------- /src/datainjectpkg.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018-2025 Matthias Klumpp 3 | * 4 | * Licensed under the GNU Lesser General Public License Version 3 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the license, or 9 | * (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this software. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "backends/interfaces.h" 28 | 29 | namespace ASGenerator 30 | { 31 | 32 | /** 33 | * Fake package which has the sole purpose of allowing easy injection of local 34 | * data that does not reside in packages. 35 | */ 36 | class DataInjectPackage final : public Package 37 | { 38 | public: 39 | DataInjectPackage(const std::string &pname, const std::string &parch, const std::string &prefix); 40 | 41 | std::string name() const override; 42 | std::string ver() const override; 43 | std::string arch() const override; 44 | PackageKind kind() const noexcept override; 45 | const std::unordered_map &description() const override; 46 | std::string getFilename() override; 47 | std::string maintainer() const override; 48 | void setMaintainer(const std::string &maint); 49 | 50 | const std::string &dataLocation() const; 51 | void setDataLocation(const std::string &value); 52 | 53 | const std::string &archDataLocation() const; 54 | void setArchDataLocation(const std::string &value); 55 | 56 | const std::vector &contents() override; 57 | std::vector getFileData(const std::string &fname) override; 58 | void finish() override; 59 | 60 | private: 61 | std::string m_pkgname; 62 | std::string m_pkgarch; 63 | std::string m_pkgmaintainer; 64 | std::unordered_map m_desc; 65 | std::unordered_map m_contents; 66 | std::string m_fakePrefix; 67 | std::string m_dataLocation; 68 | std::string m_archDataLocation; 69 | 70 | mutable std::vector m_contentsVector; 71 | }; 72 | 73 | } // namespace ASGenerator 74 | -------------------------------------------------------------------------------- /src/backends/alpinelinux/apkpkg.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020-2025 Rasmus Thomsen 3 | * Copyright (C) 2016-2025 Matthias Klumpp 4 | * 5 | * Licensed under the GNU Lesser General Public License Version 3 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the license, or 10 | * (at your option) any later version. 11 | * 12 | * This software is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with this software. If not, see . 19 | */ 20 | 21 | #pragma once 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include "../interfaces.h" 30 | #include "../../utils.h" 31 | 32 | namespace ASGenerator 33 | { 34 | 35 | class ArchiveDecompressor; 36 | 37 | class AlpinePackage : public Package 38 | { 39 | public: 40 | AlpinePackage(const std::string &pkgname, const std::string &pkgver, const std::string &pkgarch); 41 | ~AlpinePackage() override = default; 42 | 43 | std::string name() const override; 44 | void setName(const std::string &val); 45 | 46 | std::string ver() const override; 47 | void setVersion(const std::string &val); 48 | 49 | std::string arch() const override; 50 | void setArch(const std::string &val); 51 | 52 | const std::unordered_map &description() const override; 53 | 54 | void setFilename(const std::string &fname); 55 | std::string getFilename() override; 56 | 57 | std::string maintainer() const override; 58 | void setMaintainer(const std::string &maint); 59 | 60 | void setDescription(const std::string &text, const std::string &locale); 61 | 62 | std::vector getFileData(const std::string &fname) override; 63 | 64 | const std::vector &contents() override; 65 | void setContents(const std::vector &c); 66 | 67 | void finish() override; 68 | 69 | private: 70 | std::string m_pkgname; 71 | std::string m_pkgver; 72 | std::string m_pkgarch; 73 | std::string m_pkgmaintainer; 74 | std::unordered_map m_desc; 75 | std::string m_pkgFname; 76 | fs::path m_localPkgFName; 77 | fs::path m_tmpDir; 78 | 79 | std::vector m_contentsL; 80 | std::unique_ptr m_archive; 81 | 82 | mutable std::mutex m_mutex; 83 | }; 84 | 85 | } // namespace ASGenerator 86 | -------------------------------------------------------------------------------- /src/backends/rpmmd/rpmpkg.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2025 Matthias Klumpp 3 | * 4 | * Licensed under the GNU Lesser General Public License Version 3 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the license, or 9 | * (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this software. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "../interfaces.h" 29 | #include "../../utils.h" 30 | 31 | namespace ASGenerator 32 | { 33 | 34 | class ArchiveDecompressor; 35 | 36 | class RPMPackage : public Package 37 | { 38 | public: 39 | RPMPackage(); 40 | ~RPMPackage() override = default; 41 | 42 | std::string name() const override; 43 | void setName(const std::string &val); 44 | 45 | std::string ver() const override; 46 | void setVersion(const std::string &val); 47 | 48 | std::string arch() const override; 49 | void setArch(const std::string &val); 50 | 51 | const std::unordered_map &description() const override; 52 | const std::unordered_map &summary() const override; 53 | 54 | std::string getFilename() override; 55 | void setFilename(const std::string &fname); 56 | 57 | std::string maintainer() const override; 58 | void setMaintainer(const std::string &maint); 59 | 60 | void setDescription(const std::string &text, const std::string &locale); 61 | void setSummary(const std::string &text, const std::string &locale); 62 | 63 | std::vector getFileData(const std::string &fname) override; 64 | 65 | const std::vector &contents() override; 66 | void setContents(const std::vector &c); 67 | 68 | void finish() override; 69 | 70 | private: 71 | std::string m_pkgname; 72 | std::string m_pkgver; 73 | std::string m_pkgarch; 74 | std::string m_pkgmaintainer; 75 | std::unordered_map m_desc; 76 | std::unordered_map m_summ; 77 | std::string m_pkgFname; 78 | fs::path m_localPkgFname; 79 | 80 | std::vector m_contentsL; 81 | std::unique_ptr m_archive; 82 | 83 | mutable std::mutex m_mutex; 84 | }; 85 | 86 | } // namespace ASGenerator 87 | -------------------------------------------------------------------------------- /src/backends/alpinelinux/apkpkgindex.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020-2025 Rasmus Thomsen 3 | * 4 | * Licensed under the GNU Lesser General Public License Version 3 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the license, or 9 | * (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this software. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "../interfaces.h" 28 | #include "../../utils.h" 29 | #include "apkpkg.h" 30 | 31 | namespace ASGenerator 32 | { 33 | 34 | struct ApkIndexEntry { 35 | std::string pkgname; 36 | std::string pkgversion; 37 | std::string arch; 38 | std::string archiveName; 39 | std::string maintainer; 40 | std::string pkgdesc; 41 | }; 42 | 43 | class AlpinePackageIndex : public PackageIndex 44 | { 45 | public: 46 | explicit AlpinePackageIndex(const std::string &dir); 47 | 48 | void release() override; 49 | 50 | std::vector> packagesFor( 51 | const std::string &suite, 52 | const std::string §ion, 53 | const std::string &arch, 54 | bool withLongDescs = true) override; 55 | 56 | std::shared_ptr packageForFile( 57 | const std::string &fname, 58 | const std::string &suite = "", 59 | const std::string §ion = "") override; 60 | 61 | bool hasChanges( 62 | std::shared_ptr dstore, 63 | const std::string &suite, 64 | const std::string §ion, 65 | const std::string &arch) override; 66 | 67 | private: 68 | fs::path m_rootDir; 69 | fs::path m_tmpDir; 70 | std::unordered_map>> m_pkgCache; 71 | 72 | void setPkgDescription(std::shared_ptr pkg, const std::string &pkgDesc); 73 | std::vector> loadPackages( 74 | const std::string &suite, 75 | const std::string §ion, 76 | const std::string &arch); 77 | std::vector parseApkIndex(const std::string &indexString); 78 | std::string downloadIfNecessary( 79 | const std::string &apkRootPath, 80 | const std::string &tmpDir, 81 | const std::string &fileName, 82 | const std::string &cacheFileName); 83 | }; 84 | 85 | } // namespace ASGenerator 86 | -------------------------------------------------------------------------------- /src/backends/dummy/dummypkg.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2025 Matthias Klumpp 3 | * 4 | * Licensed under the GNU Lesser General Public License Version 3 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the license, or 9 | * (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this software. If not, see . 18 | */ 19 | 20 | #include "dummypkg.h" 21 | 22 | #include "../../logging.h" 23 | 24 | namespace ASGenerator 25 | { 26 | 27 | DummyPackage::DummyPackage(const std::string &pname, const std::string &pver, const std::string &parch) 28 | : m_name(pname), 29 | m_version(pver), 30 | m_arch(parch), 31 | m_kind(PackageKind::Physical), 32 | m_contents({"NOTHING1", "NOTHING2"}) 33 | { 34 | } 35 | 36 | std::string DummyPackage::name() const 37 | { 38 | return m_name; 39 | } 40 | 41 | std::string DummyPackage::ver() const 42 | { 43 | return m_version; 44 | } 45 | 46 | std::string DummyPackage::arch() const 47 | { 48 | return m_arch; 49 | } 50 | 51 | std::string DummyPackage::maintainer() const 52 | { 53 | return m_maintainer; 54 | } 55 | 56 | const std::unordered_map &DummyPackage::description() const 57 | { 58 | return m_description; 59 | } 60 | 61 | std::string DummyPackage::getFilename() 62 | { 63 | return m_testPkgFilename; 64 | } 65 | 66 | void DummyPackage::setFilename(const std::string &fname) 67 | { 68 | m_testPkgFilename = fname; 69 | } 70 | 71 | void DummyPackage::setMaintainer(const std::string &maint) 72 | { 73 | m_maintainer = maint; 74 | } 75 | 76 | const std::vector &DummyPackage::contents() 77 | { 78 | return m_contents; 79 | } 80 | 81 | std::vector DummyPackage::getFileData(const std::string &fname) 82 | { 83 | if (fname == "TEST") { 84 | return {'N', 'O', 'T', 'H', 'I', 'N', 'G'}; 85 | } 86 | return {}; 87 | } 88 | 89 | void DummyPackage::finish() 90 | { 91 | // No-op for dummy package 92 | } 93 | 94 | PackageKind DummyPackage::kind() const noexcept 95 | { 96 | return m_kind; 97 | } 98 | 99 | void DummyPackage::setKind(PackageKind v) noexcept 100 | { 101 | m_kind = v; 102 | } 103 | 104 | void DummyPackage::setDescription(const std::string &text, const std::string &locale) 105 | { 106 | m_description[locale] = text; 107 | } 108 | 109 | } // namespace ASGenerator 110 | -------------------------------------------------------------------------------- /src/backends/ubuntu/ubupkg.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2020 Canonical Ltd 3 | * Author: Iain Lane 4 | * 5 | * Licensed under the GNU Lesser General Public License Version 3 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the license, or 10 | * (at your option) any later version. 11 | * 12 | * This software is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with this software. If not, see . 19 | */ 20 | 21 | #pragma once 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include "../debian/debpkg.h" 31 | 32 | namespace ASGenerator 33 | { 34 | 35 | class UbuntuPackage; 36 | 37 | /** 38 | * A helper class that provides functions to work with language packs 39 | * used in Ubuntu. 40 | */ 41 | class LanguagePackProvider 42 | { 43 | public: 44 | explicit LanguagePackProvider(const fs::path &globalTmpDir); 45 | 46 | void addLanguagePacks(const std::vector> &langpacks); 47 | void clear(); 48 | std::unordered_map getTranslations(const std::string &domain, const std::string &text); 49 | 50 | private: 51 | std::vector> m_langpacks; 52 | fs::path m_globalTmpDir; 53 | fs::path m_langpackDir; 54 | fs::path m_localeDir; 55 | std::string m_localedefExe; 56 | std::vector m_langpackLocales; 57 | 58 | mutable std::mutex m_mutex; 59 | 60 | void extractLangpacks(); 61 | std::unordered_map getTranslationsPrivate( 62 | const std::string &domain, 63 | const std::string &text); 64 | }; 65 | 66 | /** 67 | * Ubuntu package - extends Debian package with language pack support 68 | */ 69 | class UbuntuPackage : public DebPackage 70 | { 71 | public: 72 | UbuntuPackage( 73 | const std::string &pname, 74 | const std::string &pver, 75 | const std::string &parch, 76 | std::shared_ptr l10nTexts = nullptr); 77 | 78 | void setLanguagePackProvider(std::shared_ptr provider); 79 | 80 | std::unordered_map getDesktopFileTranslations( 81 | GKeyFile *desktopFile, 82 | const std::string &text) override; 83 | 84 | bool hasDesktopFileTranslations() const override; 85 | 86 | private: 87 | std::shared_ptr m_langpackProvider; 88 | }; 89 | 90 | } // namespace ASGenerator 91 | -------------------------------------------------------------------------------- /src/extractor.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2025 Matthias Klumpp 3 | * 4 | * Licensed under the GNU Lesser General Public License Version 3 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the license, or 9 | * (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this software. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | #include "config.h" 27 | #include "datastore.h" 28 | #include "iconhandler.h" 29 | #include "result.h" 30 | #include "backends/interfaces.h" 31 | #include "cptmodifiers.h" 32 | #include "dataunits.h" 33 | 34 | namespace ASGenerator 35 | { 36 | 37 | /** 38 | * Class for extracting AppStream metadata from packages. 39 | */ 40 | class DataExtractor 41 | { 42 | public: 43 | DataExtractor( 44 | std::shared_ptr db, 45 | std::shared_ptr iconHandler, 46 | AsgLocaleUnit *localeUnit, 47 | std::shared_ptr modInjInfo = nullptr, 48 | const std::string &prefix = {}); 49 | ~DataExtractor(); 50 | 51 | /** 52 | * Process a package and extract AppStream metadata from it. 53 | * 54 | * @param pkg The package to process 55 | * @return GeneratorResult containing the extraction results 56 | */ 57 | GeneratorResult processPackage(std::shared_ptr pkg); 58 | 59 | // Delete copy constructor and assignment operator 60 | DataExtractor(const DataExtractor &) = delete; 61 | DataExtractor &operator=(const DataExtractor &) = delete; 62 | 63 | private: 64 | Config *m_conf; 65 | AscCompose *m_compose; 66 | DataType m_dtype; 67 | std::shared_ptr m_dstore; 68 | std::shared_ptr m_iconh; 69 | std::shared_ptr m_modInj; 70 | AsgLocaleUnit *m_l10nUnit; 71 | 72 | /** 73 | * Helper function for early asgen-specific metadata manipulation. 74 | * This is a C callback function used by AppStream Compose. 75 | */ 76 | static void checkMetadataIntermediate(AscResult *cres, const AscUnit *cunit, void *userData); 77 | 78 | /** 79 | * Helper function for translating desktop file text. 80 | * This is a C callback function for desktop entry translation. 81 | */ 82 | static GPtrArray *translateDesktopTextCallback(GKeyFile *dePtr, const char *text, void *userData); 83 | }; 84 | 85 | } // namespace ASGenerator 86 | -------------------------------------------------------------------------------- /src/backends/archlinux/alpkg.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2025 Matthias Klumpp 3 | * 4 | * Licensed under the GNU Lesser General Public License Version 3 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the license, or 9 | * (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this software. If not, see . 18 | */ 19 | 20 | #include "alpkg.h" 21 | 22 | #include "../../logging.h" 23 | #include "../../zarchive.h" 24 | 25 | namespace ASGenerator 26 | { 27 | 28 | ArchPackage::ArchPackage() 29 | : m_archive(std::make_unique()) 30 | { 31 | } 32 | 33 | std::string ArchPackage::name() const 34 | { 35 | return m_pkgname; 36 | } 37 | 38 | void ArchPackage::setName(const std::string &val) 39 | { 40 | m_pkgname = val; 41 | } 42 | 43 | std::string ArchPackage::ver() const 44 | { 45 | return m_pkgver; 46 | } 47 | 48 | void ArchPackage::setVersion(const std::string &val) 49 | { 50 | m_pkgver = val; 51 | } 52 | 53 | std::string ArchPackage::arch() const 54 | { 55 | return m_pkgarch; 56 | } 57 | 58 | void ArchPackage::setArch(const std::string &val) 59 | { 60 | m_pkgarch = val; 61 | } 62 | 63 | const std::unordered_map &ArchPackage::description() const 64 | { 65 | return m_desc; 66 | } 67 | 68 | void ArchPackage::setFilename(const std::string &fname) 69 | { 70 | m_pkgFname = fname; 71 | } 72 | 73 | std::string ArchPackage::getFilename() 74 | { 75 | return m_pkgFname; 76 | } 77 | 78 | std::string ArchPackage::maintainer() const 79 | { 80 | return m_pkgmaintainer; 81 | } 82 | 83 | void ArchPackage::setMaintainer(const std::string &maint) 84 | { 85 | m_pkgmaintainer = maint; 86 | } 87 | 88 | void ArchPackage::setDescription(const std::string &text, const std::string &locale) 89 | { 90 | m_desc[locale] = text; 91 | } 92 | 93 | std::vector ArchPackage::getFileData(const std::string &fname) 94 | { 95 | if (!m_archive->isOpen()) 96 | m_archive->open(getFilename()); 97 | 98 | return m_archive->readData(fname); 99 | } 100 | 101 | const std::vector &ArchPackage::contents() 102 | { 103 | return m_contentsL; 104 | } 105 | 106 | void ArchPackage::setContents(const std::vector &c) 107 | { 108 | m_contentsL = c; 109 | } 110 | 111 | void ArchPackage::finish() 112 | { 113 | // No-op for Arch Linux package 114 | } 115 | 116 | } // namespace ASGenerator 117 | -------------------------------------------------------------------------------- /src/backends/alpinelinux/apkindexutils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020-2025 Rasmus Thomsen 3 | * 4 | * Licensed under the GNU Lesser General Public License Version 3 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the license, or 9 | * (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this software. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | #include "../../downloader.h" 27 | 28 | namespace ASGenerator 29 | { 30 | 31 | /** 32 | * Struct representing a block inside of an APKINDEX. Each block, separated by 33 | * a newline, contains information about exactly one package. 34 | */ 35 | struct ApkIndexBlock { 36 | std::string arch; 37 | std::string maintainer; 38 | std::string pkgname; 39 | std::string pkgversion; 40 | std::string pkgdesc; 41 | 42 | std::string archiveName() const 43 | { 44 | return std::format("{}-{}.apk", pkgname, pkgversion); 45 | } 46 | }; 47 | 48 | /** 49 | * Range for looping over the contents of an APKINDEX, block by block. 50 | */ 51 | class ApkIndexBlockRange 52 | { 53 | public: 54 | explicit ApkIndexBlockRange(const std::string &contents); 55 | 56 | const ApkIndexBlock &front() const; 57 | bool empty() const; 58 | void popFront(); 59 | 60 | // Iterator interface for range-based for loops 61 | class iterator 62 | { 63 | private: 64 | ApkIndexBlockRange *m_range; 65 | bool m_isEnd; 66 | 67 | public: 68 | explicit iterator(ApkIndexBlockRange *range, bool isEnd = false); 69 | 70 | const ApkIndexBlock &operator*() const; 71 | const ApkIndexBlock *operator->() const; 72 | 73 | iterator &operator++(); 74 | bool operator!=(const iterator &other) const; 75 | }; 76 | 77 | iterator begin(); 78 | iterator end(); 79 | 80 | private: 81 | std::vector m_lines; 82 | std::size_t m_lineDelta; 83 | ApkIndexBlock m_currentBlock; 84 | bool m_empty; 85 | 86 | void getNextBlock(); 87 | void setCurrentBlock(const std::string &key, const std::string &value); 88 | }; 89 | 90 | /** 91 | * Download APK index file if necessary 92 | */ 93 | std::string downloadIfNecessary( 94 | const std::string &apkRootPath, 95 | const std::string &tmpDir, 96 | const std::string &fileName, 97 | const std::string &cacheFileName); 98 | 99 | } // namespace ASGenerator 100 | -------------------------------------------------------------------------------- /src/backends/interfaces.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2025 Matthias Klumpp 3 | * 4 | * Licensed under the GNU Lesser General Public License Version 3 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the license, or 9 | * (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this software. If not, see . 18 | */ 19 | 20 | #include "interfaces.h" 21 | 22 | #include 23 | #include 24 | 25 | namespace ASGenerator 26 | { 27 | 28 | // GStreamer implementation 29 | GStreamer::GStreamer() 30 | : m_decoders(), 31 | m_encoders(), 32 | m_elements(), 33 | m_uriSinks(), 34 | m_uriSources() 35 | { 36 | } 37 | 38 | GStreamer::GStreamer( 39 | const std::vector &decoders, 40 | const std::vector &encoders, 41 | const std::vector &elements, 42 | const std::vector &uriSinks, 43 | const std::vector &uriSources) 44 | : m_decoders(decoders), 45 | m_encoders(encoders), 46 | m_elements(elements), 47 | m_uriSinks(uriSinks), 48 | m_uriSources(uriSources) 49 | { 50 | } 51 | 52 | bool GStreamer::isNotEmpty() const noexcept 53 | { 54 | return !( 55 | m_decoders.empty() && m_encoders.empty() && m_elements.empty() && m_uriSinks.empty() && m_uriSources.empty()); 56 | } 57 | 58 | PackageKind Package::kind() const noexcept 59 | { 60 | return PackageKind::Physical; 61 | } 62 | 63 | const std::unordered_map &Package::summary() const 64 | { 65 | static const std::unordered_map empty_map; 66 | return empty_map; 67 | } 68 | 69 | std::optional Package::gst() const 70 | { 71 | return std::nullopt; 72 | } 73 | 74 | std::unordered_map Package::getDesktopFileTranslations( 75 | GKeyFile *desktopFile, 76 | const std::string &text) 77 | { 78 | return {}; 79 | } 80 | 81 | bool Package::hasDesktopFileTranslations() const 82 | { 83 | return false; 84 | } 85 | 86 | // Package implementation 87 | const std::string &Package::id() const 88 | { 89 | if (m_pkid.empty()) 90 | m_pkid = std::format("{}/{}/{}", name(), ver(), arch()); 91 | 92 | return m_pkid; 93 | } 94 | 95 | bool Package::isValid() const 96 | { 97 | return !name().empty() && !ver().empty() && !arch().empty(); 98 | } 99 | 100 | std::string Package::toString() const 101 | { 102 | return id(); 103 | } 104 | 105 | std::string PackageIndex::dataPrefix() const 106 | { 107 | return "/usr"; 108 | } 109 | 110 | } // namespace ASGenerator 111 | -------------------------------------------------------------------------------- /src/backends/debian/debpkgindex.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2025 Matthias Klumpp 3 | * 4 | * Licensed under the GNU Lesser General Public License Version 3 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the license, or 9 | * (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this software. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "../interfaces.h" 28 | #include "../../utils.h" 29 | #include "debpkg.h" 30 | 31 | namespace ASGenerator 32 | { 33 | 34 | class DebianPackageIndex : public PackageIndex 35 | { 36 | public: 37 | explicit DebianPackageIndex(const std::string &dir); 38 | 39 | void release() override; 40 | 41 | std::vector> packagesFor( 42 | const std::string &suite, 43 | const std::string §ion, 44 | const std::string &arch, 45 | bool withLongDescs = true) override; 46 | 47 | std::shared_ptr packageForFile( 48 | const std::string &fname, 49 | const std::string &suite = "", 50 | const std::string §ion = "") override; 51 | 52 | bool hasChanges( 53 | std::shared_ptr dstore, 54 | const std::string &suite, 55 | const std::string §ion, 56 | const std::string &arch) override; 57 | 58 | protected: 59 | fs::path m_tmpDir; 60 | 61 | /** 62 | * Convert a Debian package description to a description 63 | * that looks nice-ish in AppStream clients. 64 | */ 65 | std::string packageDescToAppStreamDesc(const std::vector &lines); 66 | 67 | void loadPackageLongDescs( 68 | std::unordered_map> &pkgs, 69 | const std::string &suite, 70 | const std::string §ion); 71 | 72 | std::string getIndexFile(const std::string &suite, const std::string §ion, const std::string &arch); 73 | 74 | virtual std::shared_ptr newPackage( 75 | const std::string &name, 76 | const std::string &ver, 77 | const std::string &arch); 78 | 79 | std::vector> loadPackages( 80 | const std::string &suite, 81 | const std::string §ion, 82 | const std::string &arch, 83 | bool withLongDescs = true); 84 | 85 | std::vector findTranslations(const std::string &suite, const std::string §ion); 86 | 87 | private: 88 | std::string m_rootDir; 89 | std::unordered_map>> m_pkgCache; 90 | 91 | // index of localized text for a specific package name 92 | std::unordered_map> m_l10nTextIndex; 93 | std::unordered_map m_indexChanged; 94 | }; 95 | 96 | } // namespace ASGenerator 97 | -------------------------------------------------------------------------------- /data/templates/debian/main.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Start{% endblock %} 4 | 5 | {% block header_content %} 6 | AppStream data hints for {{project_name}} 7 | {% endblock %} 8 | 9 | {% block content %} 10 |
11 | 12 |

Welcome!

13 |

Welcome to the AppStream Generator HTML pages!

14 |

15 | These pages exist to provide a user-friendly view on the issues discovered by the 16 | AppStream metadata generator while extracting metadata from packages in the {{project_name}} archive. 17 | They can also be used to take a look at the raw metadata, to spot possible problems 18 | with the data itself or the generation process. 19 |

20 | 21 |

Select a suite

22 | {% for suite in suites %} 23 |

{{suite.suite}}

24 | {% endfor %} 25 |
26 | {% for oldsuite in oldsuites %} 27 |

{{oldsuite.suite}}

28 | {% endfor %} 29 | 30 |

31 | AppStream Generator Logo 32 | 33 |

What is AppStream and DEP-11?

34 |

35 | AppStream is a cross-distro XML format to provide metadata for software components and to assign unique identifiers to software.
36 | In Debian, we parse all XML provided by upstream projects as well as other metadata (.desktop-files, ...), and compile a single YAML 37 | metadata file from it, which is then shipped to users via APT. 38 |

39 |

40 | While the official AppStream specification is based on XML, Debian uses a YAML version of the format for easier use in existing 41 | scripts and for better archive integration. This format is called DEP-11, and initially had a much wider scope in enhancing 42 | archive metadata than AppStream had. Today AppStream covers that as well, and DEP-11 is only a YAML version of AppStream. 43 |

44 |

45 | The generated metadata can for example be used by software centers like GNOME Software or KDE Discover to display a user-friendly application-centric 46 | view on the package archive.
47 | It can also be used by other software to find missing plugins, codecs, fonts, etc. or simply by users to install software on any Linux distribution 48 | without knowing the exact package name. 49 |

50 | 51 |

More information

52 |

53 | See AppStream @ wiki.d.o for information about AppStream integration and usage in Debian.
54 | The offical AppStream specification can be found at freedesktop.org, a description 55 | of the DEP-11 YAML format is hosted there as well. 56 |

57 |

58 | You can find the source-code of the AppStream Generator here. 59 |

60 |

61 | Log files of the generator runs are stored in logs/, machine-readable issue hints can be found in hints/, 62 | valid metadata is located in data/ and all exported media is made available via media/. 63 |

64 | 65 |
66 | {% endblock %} 67 | -------------------------------------------------------------------------------- /src/downloader.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019-2025 Matthias Klumpp 3 | * 4 | * Licensed under the GNU Lesser General Public License Version 3 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the license, or 9 | * (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this software. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | namespace ASGenerator 29 | { 30 | 31 | class DownloadException : public std::exception 32 | { 33 | public: 34 | explicit DownloadException(const std::string &message); 35 | const char *what() const noexcept override; 36 | 37 | private: 38 | std::string m_message; 39 | }; 40 | 41 | /** 42 | * Download data via HTTP. Based on cURL. 43 | */ 44 | class Downloader 45 | { 46 | public: 47 | /** 48 | * Get thread-local singleton instance 49 | */ 50 | static Downloader &get(); 51 | 52 | Downloader(); 53 | 54 | /** 55 | * Download to file stream and return last-modified time if available 56 | */ 57 | std::optional download( 58 | const std::string &url, 59 | std::ofstream &dFile, 60 | std::uint32_t maxTryCount = 4); 61 | 62 | /** 63 | * Download to memory and return data as byte vector 64 | */ 65 | std::vector download(const std::string &url, std::uint32_t maxTryCount = 4); 66 | 67 | /** 68 | * Download `url` to `dest`. 69 | * 70 | * Params: 71 | * url = The URL to download. 72 | * dest = The location for the downloaded file. 73 | * maxTryCount = Number of times to attempt the download. 74 | */ 75 | void downloadFile(const std::string &url, const std::string &dest, std::uint32_t maxTryCount = 4); 76 | 77 | /** 78 | * Download `url` and return a string with its contents. 79 | * 80 | * Params: 81 | * url = The URL to download. 82 | * maxTryCount = Number of times to retry on timeout. 83 | */ 84 | std::string downloadText(const std::string &url, std::uint32_t maxTryCount = 4); 85 | 86 | /** 87 | * Download `url` and return a string array of lines. 88 | * 89 | * Params: 90 | * url = The URL to download. 91 | * maxTryCount = Number of times to retry on timeout. 92 | */ 93 | std::vector downloadTextLines(const std::string &url, std::uint32_t maxTryCount = 4); 94 | 95 | private: 96 | const std::string userAgent; 97 | const std::string caInfo; 98 | 99 | // thread local instance 100 | static thread_local std::unique_ptr instance_; 101 | 102 | std::optional downloadInternal( 103 | const std::string &url, 104 | std::ofstream &dest, 105 | std::uint32_t maxTryCount = 5); 106 | }; 107 | 108 | } // namespace ASGenerator 109 | -------------------------------------------------------------------------------- /src/backends/freebsd/fbsdpkg.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2023-2025 Serenity Cyber Security, LLC 3 | * Author: Gleb Popov 4 | * 5 | * Licensed under the GNU Lesser General Public License Version 3 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the license, or 10 | * (at your option) any later version. 11 | * 12 | * This software is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with this software. If not, see . 19 | */ 20 | 21 | #include "fbsdpkg.h" 22 | 23 | #include 24 | #include 25 | 26 | #include "../../logging.h" 27 | #include "../../zarchive.h" 28 | #include "../../config.h" 29 | 30 | namespace ASGenerator 31 | { 32 | 33 | FreeBSDPackage::FreeBSDPackage(const std::string &pkgRoot, const nlohmann::json &j) 34 | : m_pkgjson(j), 35 | m_kind(PackageKind::Physical) 36 | { 37 | m_pkgFname = fs::path(pkgRoot) / m_pkgjson["repopath"].get(); 38 | m_pkgArchive = std::make_unique(); 39 | } 40 | 41 | std::string FreeBSDPackage::name() const 42 | { 43 | return m_pkgjson["name"].get(); 44 | } 45 | 46 | std::string FreeBSDPackage::ver() const 47 | { 48 | return m_pkgjson["version"].get(); 49 | } 50 | 51 | std::string FreeBSDPackage::arch() const 52 | { 53 | return m_pkgjson["arch"].get(); 54 | } 55 | 56 | std::string FreeBSDPackage::maintainer() const 57 | { 58 | return m_pkgjson["maintainer"].get(); 59 | } 60 | 61 | std::string FreeBSDPackage::getFilename() 62 | { 63 | return m_pkgFname; 64 | } 65 | 66 | const std::unordered_map &FreeBSDPackage::summary() const 67 | { 68 | if (m_summaryCache.empty()) 69 | m_summaryCache["en"] = m_pkgjson["comment"].get(); 70 | 71 | return m_summaryCache; 72 | } 73 | 74 | const std::unordered_map &FreeBSDPackage::description() const 75 | { 76 | if (m_descriptionCache.empty()) 77 | m_descriptionCache["en"] = m_pkgjson["desc"].get(); 78 | 79 | return m_descriptionCache; 80 | } 81 | 82 | std::vector FreeBSDPackage::getFileData(const std::string &fname) 83 | { 84 | std::lock_guard lock(m_mutex); 85 | if (!m_pkgArchive->isOpen()) { 86 | m_pkgArchive->open(m_pkgFname, Config::get().getTmpDir() / fs::path(m_pkgFname).filename()); 87 | m_pkgArchive->setOptimizeRepeatedReads(true); 88 | } 89 | 90 | return m_pkgArchive->readData(fname); 91 | } 92 | 93 | const std::vector &FreeBSDPackage::contents() 94 | { 95 | if (!m_contentsL.empty()) 96 | return m_contentsL; 97 | 98 | if (!m_pkgArchive->isOpen()) 99 | m_pkgArchive->open(getFilename()); 100 | 101 | m_contentsL = m_pkgArchive->readContents(); 102 | return m_contentsL; 103 | } 104 | 105 | void FreeBSDPackage::finish() 106 | { 107 | // No-op for FreeBSD package 108 | } 109 | 110 | PackageKind FreeBSDPackage::kind() const noexcept 111 | { 112 | return m_kind; 113 | } 114 | 115 | } // namespace ASGenerator 116 | -------------------------------------------------------------------------------- /src/logging.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2025 Matthias Klumpp 3 | * 4 | * Licensed under the GNU Lesser General Public License Version 3 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the license, or 9 | * (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this software. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | namespace ASGenerator 27 | { 28 | 29 | enum class LogSeverity { 30 | DEBUG, 31 | INFO, 32 | WARNING, 33 | ERROR 34 | }; 35 | 36 | void setVerbose(bool verbose) noexcept; 37 | bool isVerbose() noexcept; 38 | 39 | constexpr std::string_view logSeverityToString(LogSeverity severity) noexcept; 40 | 41 | // Base logging function that handles the actual output 42 | void logMessageImpl(LogSeverity severity, const std::string &message); 43 | 44 | template 45 | void logMessage(LogSeverity severity, std::string_view fmt, Args &&...args) 46 | { 47 | std::string formatted_msg; 48 | if constexpr (sizeof...(Args) > 0) 49 | formatted_msg = std::vformat(fmt, std::make_format_args(args...)); 50 | else 51 | formatted_msg = std::string{fmt}; 52 | logMessageImpl(severity, formatted_msg); 53 | } 54 | 55 | template 56 | inline void logDebug(std::string_view fmt, Args &&...args) 57 | { 58 | if (isVerbose()) 59 | logMessage(LogSeverity::DEBUG, fmt, std::forward(args)...); 60 | } 61 | 62 | template 63 | inline void logInfo(std::string_view fmt, Args &&...args) 64 | { 65 | logMessage(LogSeverity::INFO, fmt, std::forward(args)...); 66 | } 67 | 68 | template 69 | inline void logWarning(std::string_view fmt, Args &&...args) 70 | { 71 | logMessage(LogSeverity::WARNING, fmt, std::forward(args)...); 72 | } 73 | 74 | template 75 | inline void logError(std::string_view fmt, Args &&...args) 76 | { 77 | logMessage(LogSeverity::ERROR, fmt, std::forward(args)...); 78 | } 79 | 80 | // Convenience overloads for simple string messages (no template arguments) 81 | inline void logDebug(std::string_view msg) 82 | { 83 | if (isVerbose()) 84 | logMessageImpl(LogSeverity::DEBUG, std::string{msg}); 85 | } 86 | 87 | inline void logInfo(std::string_view msg) 88 | { 89 | logMessageImpl(LogSeverity::INFO, std::string{msg}); 90 | } 91 | 92 | inline void logWarning(std::string_view msg) 93 | { 94 | logMessageImpl(LogSeverity::WARNING, std::string{msg}); 95 | } 96 | 97 | inline void logError(std::string_view msg) 98 | { 99 | logMessageImpl(LogSeverity::ERROR, std::string{msg}); 100 | } 101 | 102 | /** 103 | * Print a header box with the given title to stdout. 104 | * @param title Title text 105 | */ 106 | void printHeaderBox(std::string_view title); 107 | 108 | /** 109 | * Print a section box with the given title to stdout. 110 | * @param title Title text 111 | */ 112 | void printSectionBox(std::string_view title); 113 | 114 | } // namespace ASGenerator 115 | -------------------------------------------------------------------------------- /src/backends/ubuntu/ubupkgindex.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2020 Canonical Ltd 3 | * Author: Iain Lane 4 | * 5 | * Licensed under the GNU Lesser General Public License Version 3 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the license, or 10 | * (at your option) any later version. 11 | * 12 | * This software is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with this software. If not, see . 19 | */ 20 | 21 | #include "ubupkgindex.h" 22 | 23 | #include 24 | #include 25 | 26 | #include "../../logging.h" 27 | 28 | namespace ASGenerator 29 | { 30 | 31 | UbuntuPackageIndex::UbuntuPackageIndex(const std::string &dir) 32 | : DebianPackageIndex(dir) 33 | { 34 | /* 35 | * UbuntuPackage needs to extract the langpacks, so we give it an array 36 | * of langpacks. There is a small overhead when computing this array 37 | * which might be unnecessary if no processed packages are using 38 | * langpacks, but otherwise we need to keep a reference to all packages 39 | * around, which is very expensive. 40 | */ 41 | m_langpacks = std::make_shared(m_tmpDir); 42 | } 43 | 44 | void UbuntuPackageIndex::release() 45 | { 46 | DebianPackageIndex::release(); 47 | m_checkedLangPacks.clear(); 48 | 49 | // replace with fresh, empty provider 50 | m_langpacks = std::make_shared(m_tmpDir); 51 | } 52 | 53 | std::shared_ptr UbuntuPackageIndex::newPackage( 54 | const std::string &name, 55 | const std::string &ver, 56 | const std::string &arch) 57 | { 58 | auto ubuntuPkg = std::make_shared(name, ver, arch); 59 | ubuntuPkg->setLanguagePackProvider(m_langpacks); 60 | return std::static_pointer_cast(ubuntuPkg); 61 | } 62 | 63 | std::vector> UbuntuPackageIndex::packagesFor( 64 | const std::string &suite, 65 | const std::string §ion, 66 | const std::string &arch, 67 | bool withLongDescs) 68 | { 69 | auto pkgs = DebianPackageIndex::packagesFor(suite, section, arch, withLongDescs); 70 | 71 | const std::string ssaId = std::format("{}/{}/{}", suite, section, arch); 72 | if (m_checkedLangPacks.contains(ssaId)) 73 | return pkgs; // no need to scan for language packs, we already did that 74 | 75 | // scan for language packs and add them to the data provider 76 | std::vector> langpackPkgs; 77 | langpackPkgs.reserve(32); 78 | 79 | for (const auto &pkg : pkgs) { 80 | if (pkg->name().starts_with("language-pack-")) { 81 | // Cast to UbuntuPackage 82 | auto ubuntuPkg = std::dynamic_pointer_cast(pkg); 83 | if (ubuntuPkg) 84 | langpackPkgs.push_back(std::move(ubuntuPkg)); 85 | } 86 | } 87 | 88 | m_langpacks->addLanguagePacks(langpackPkgs); 89 | m_checkedLangPacks.insert(ssaId); 90 | 91 | return pkgs; 92 | } 93 | 94 | std::shared_ptr UbuntuPackageIndex::packageForFile( 95 | const std::string &fname, 96 | const std::string &suite, 97 | const std::string §ion) 98 | { 99 | // FIXME: not implemented 100 | return nullptr; 101 | } 102 | 103 | } // namespace ASGenerator 104 | -------------------------------------------------------------------------------- /src/contentsstore.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2025 Matthias Klumpp 3 | * 4 | * Licensed under the GNU Lesser General Public License Version 3 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the license, or 9 | * (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this software. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | namespace ASGenerator 30 | { 31 | 32 | class Config; 33 | 34 | /** 35 | * Contains a cache about available files in packages. 36 | * This is useful for finding icons and for quickly 37 | * re-scanning packages which may become interesting later. 38 | */ 39 | class ContentsStore 40 | { 41 | public: 42 | ContentsStore(const std::string &prefixPath = ""); 43 | ~ContentsStore(); 44 | 45 | void open(const std::string &dir); 46 | void open(const Config &conf); 47 | void close(); 48 | 49 | /** 50 | * Drop a package-id from the contents cache. 51 | */ 52 | void removePackage(const std::string &pkid); 53 | 54 | bool packageExists(const std::string &pkid); 55 | 56 | void addContents(const std::string &pkid, const std::vector &contents); 57 | 58 | std::unordered_map getContentsMap(const std::vector &pkids); 59 | std::unordered_map getIconFilesMap(const std::vector &pkids); 60 | 61 | /** 62 | * We make the assumption here that all locale for a given domain are in one package. 63 | * Otherwise this global search will get even more insane. 64 | */ 65 | std::unordered_map getLocaleMap(const std::vector &pkids); 66 | 67 | std::vector getContents(const std::string &pkid); 68 | std::vector getIcons(const std::string &pkid); 69 | std::vector getLocaleFiles(const std::string &pkid); 70 | 71 | std::unordered_set getPackageIdSet(); 72 | 73 | void removePackages(const std::unordered_set &pkidSet); 74 | 75 | void sync(); 76 | 77 | // Delete copy constructor and assignment operator 78 | ContentsStore(const ContentsStore &) = delete; 79 | ContentsStore &operator=(const ContentsStore &) = delete; 80 | 81 | private: 82 | MDB_env *dbEnv; 83 | MDB_dbi dbContents{0}; 84 | MDB_dbi dbIcons{0}; 85 | MDB_dbi dbLocale{0}; 86 | 87 | bool m_opened; 88 | std::mutex m_mutex; 89 | 90 | std::vector m_knownIconPaths; 91 | 92 | void checkError(int rc, const std::string &msg); 93 | MDB_val makeDbValue(const std::string &data); 94 | MDB_txn *newTransaction(unsigned int flags = 0); 95 | void commitTransaction(MDB_txn *txn); 96 | void quitTransaction(MDB_txn *txn); 97 | bool pathIsIconLocation(const std::string &path) const; 98 | 99 | std::unordered_map getFilesMap( 100 | const std::vector &pkids, 101 | MDB_dbi dbi, 102 | bool useBaseName = false); 103 | 104 | std::vector getContentsList(const std::string &pkid, MDB_dbi dbi); 105 | }; 106 | 107 | } // namespace ASGenerator 108 | -------------------------------------------------------------------------------- /src/zarchive.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2025 Matthias Klumpp 3 | * 4 | * Licensed under the GNU Lesser General Public License Version 3 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the license, or 9 | * (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this software. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | struct archive; 31 | 32 | namespace ASGenerator 33 | { 34 | 35 | namespace fs = std::filesystem; 36 | 37 | enum class ArchiveType { 38 | GZIP, 39 | XZ, 40 | ZSTD 41 | }; 42 | 43 | std::string decompressFile(const std::string &fname); 44 | std::string decompressData(const std::vector &data); 45 | 46 | class ArchiveDecompressor 47 | { 48 | public: 49 | struct ArchiveEntry { 50 | std::string fname; 51 | std::vector data; 52 | }; 53 | 54 | ArchiveDecompressor() = default; 55 | ~ArchiveDecompressor(); 56 | void open(const std::string &fname, const fs::path &tmpDir = fs::path()); 57 | bool isOpen() const; 58 | void close(); 59 | 60 | /** 61 | * If this is set to true, and the archive is large, it will be extracted to a 62 | * temporary location and entries read from there. This avoids repeatedly 63 | * seeking through the archive to extract data if readData() and extractFileTo() 64 | * are used a lot. Repeated seeking is slower than temporary extraction. 65 | * 66 | * @param enable Enable or disable optimization for repeated reads. 67 | */ 68 | void setOptimizeRepeatedReads(bool enable); 69 | 70 | bool extractFileTo(const std::string &fname, const std::string &fdest); 71 | void extractArchive(const std::string &dest); 72 | std::vector readData(const std::string &fname); 73 | std::vector extractFilesByRegex(const std::regex &re, const std::string &destdir); 74 | std::vector readContents(); 75 | std::generator read(); 76 | 77 | private: 78 | std::string m_archiveFname; 79 | fs::path m_tmpDir; 80 | bool m_canExtractToTmp = false; 81 | bool m_tmpDirOwned = false; 82 | bool m_optimizeRepeatedReads = false; 83 | bool m_isExtractedToTmp = false; 84 | 85 | bool pathMatches(const std::string &path1, const std::string &path2) const; 86 | std::vector readEntry(struct archive *ar); 87 | void extractEntryTo(struct archive *ar, const std::string &fname); 88 | struct archive *openArchive(); 89 | bool tmpExtractIfPossible(); 90 | void cleanupTempDirectory(); 91 | size_t getArchiveSize() const; 92 | }; 93 | 94 | void compressAndSave(const std::vector &data, const std::string &fname, ArchiveType atype); 95 | 96 | class ArchiveCompressor 97 | { 98 | public: 99 | explicit ArchiveCompressor(ArchiveType type); 100 | ~ArchiveCompressor(); 101 | void open(const std::string &fname); 102 | bool isOpen() const; 103 | void close(); 104 | void addFile(const std::string &fname, const std::optional &dest = std::nullopt); 105 | 106 | private: 107 | std::string archiveFname; 108 | struct archive *ar = nullptr; 109 | bool closed = true; 110 | std::mutex m_mutex; 111 | }; 112 | 113 | } // namespace ASGenerator 114 | -------------------------------------------------------------------------------- /src/logging.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2025 Matthias Klumpp 3 | * 4 | * Licensed under the GNU Lesser General Public License Version 3 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the license, or 9 | * (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this software. If not, see . 18 | */ 19 | 20 | #include "logging.h" 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | namespace ASGenerator 32 | { 33 | 34 | // helper to avoid the "static initialization order fiasco" 35 | static std::atomic_bool &verboseFlag() noexcept 36 | { 37 | static std::atomic_bool flag{false}; 38 | return flag; 39 | } 40 | 41 | void setVerbose(bool verbose) noexcept 42 | { 43 | verboseFlag().store(verbose, std::memory_order_relaxed); 44 | } 45 | 46 | bool isVerbose() noexcept 47 | { 48 | return verboseFlag().load(std::memory_order_relaxed); 49 | } 50 | 51 | constexpr std::string_view logSeverityToString(LogSeverity severity) noexcept 52 | { 53 | switch (severity) { 54 | case LogSeverity::DEBUG: 55 | return "DEBUG"; 56 | case LogSeverity::INFO: 57 | return "INFO"; 58 | case LogSeverity::WARNING: 59 | return "WARNING"; 60 | case LogSeverity::ERROR: 61 | return "ERROR"; 62 | default: 63 | return "UNKNOWN"; 64 | } 65 | } 66 | 67 | void logMessageImpl(LogSeverity severity, const std::string &message) 68 | { 69 | // Use synchronized output stream for thread safety 70 | std::osyncstream sync_out{std::cout}; 71 | 72 | auto now = std::chrono::system_clock::now(); 73 | auto time_t = std::chrono::system_clock::to_time_t(now); 74 | auto tm = *std::localtime(&time_t); 75 | 76 | std::ostringstream time_stream; 77 | time_stream << std::put_time(&tm, "%Y-%m-%d %H:%M:%S"); 78 | 79 | sync_out << time_stream.str() << " - " << logSeverityToString(severity) << ": " << message << '\n'; 80 | } 81 | 82 | static void printTextbox( 83 | std::string_view title, 84 | std::string_view tl, 85 | std::string_view hline, 86 | std::string_view tr, 87 | std::string_view vline, 88 | std::string_view bl, 89 | std::string_view br) 90 | { 91 | const auto tlen = title.length(); 92 | const auto hline_count = 10 + tlen; 93 | 94 | std::string output; 95 | output.reserve(128 + tlen + hline_count * hline.length() * 2); // Rough estimate 96 | 97 | // Top line 98 | output += '\n'; 99 | output += tl; 100 | for (size_t i = 0; i < hline_count; ++i) 101 | output += hline; 102 | output += tr; 103 | output += '\n'; 104 | 105 | // Middle line with title 106 | output += vline; 107 | output += " "; 108 | output += title; 109 | output += std::string(8, ' '); 110 | output += vline; 111 | output += '\n'; 112 | 113 | // Bottom line 114 | output += bl; 115 | for (size_t i = 0; i < hline_count; ++i) 116 | output += hline; 117 | output += br; 118 | output += '\n'; 119 | 120 | std::cout.write(output.data(), output.size()); 121 | std::cout.flush(); 122 | } 123 | 124 | void printHeaderBox(std::string_view title) 125 | { 126 | printTextbox(title, "╔", "═", "╗", "║", "╚", "╝"); 127 | } 128 | 129 | void printSectionBox(std::string_view title) 130 | { 131 | printTextbox(title, "┌", "─", "┐", "│", "└", "┘"); 132 | } 133 | 134 | } // namespace ASGenerator 135 | -------------------------------------------------------------------------------- /tests/tests-icons.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019-2025 Matthias Klumpp 3 | * 4 | * SPDX-License-Identifier: LGPL-3.0-or-later 5 | */ 6 | 7 | #define CATCH_CONFIG_MAIN 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include "utils.h" 21 | #include "logging.h" 22 | #include "iconhandler.h" 23 | 24 | using namespace ASGenerator; 25 | 26 | static struct TestSetup { 27 | TestSetup() 28 | { 29 | setVerbose(true); 30 | } 31 | } testSetup; 32 | 33 | TEST_CASE("IconHandler", "[IconHandler]") 34 | { 35 | auto hicolorThemeIndex = Utils::getDataPath("hicolor-theme-index.theme"); 36 | 37 | // Read theme index data 38 | std::vector indexData; 39 | std::ifstream f(hicolorThemeIndex, std::ios::binary); 40 | REQUIRE(f.is_open()); 41 | 42 | f.seekg(0, std::ios::end); 43 | indexData.resize(f.tellg()); 44 | f.seekg(0, std::ios::beg); 45 | f.read(reinterpret_cast(indexData.data()), indexData.size()); 46 | 47 | auto theme = std::make_unique("hicolor", indexData); 48 | 49 | // Test matching icon filenames for accessories-calculator 48x48 50 | for (const auto &fname : theme->matchingIconFilenames("accessories-calculator", ImageSize(48))) { 51 | bool valid = false; 52 | if (fname.starts_with("/usr/share/icons/hicolor/48x48/")) 53 | valid = true; 54 | if (fname.starts_with("/usr/share/icons/hicolor/scalable/")) 55 | valid = true; 56 | REQUIRE(valid); 57 | 58 | // Check if icon is allowed format 59 | bool formatAllowed = IconHandler::iconAllowed(fname); 60 | if (fname.ends_with(".ico")) 61 | REQUIRE_FALSE(formatAllowed); 62 | else 63 | REQUIRE(formatAllowed); 64 | } 65 | 66 | // Test matching icon filenames for accessories-text-editor 192x192 67 | for (const auto &fname : theme->matchingIconFilenames("accessories-text-editor", ImageSize(192))) { 68 | bool validPath = false; 69 | if (fname.starts_with("/usr/share/icons/hicolor/192x192/")) 70 | validPath = true; 71 | if (fname.starts_with("/usr/share/icons/hicolor/256x256/")) 72 | validPath = true; 73 | if (fname.starts_with("/usr/share/icons/hicolor/512x512/")) 74 | validPath = true; 75 | if (fname.starts_with("/usr/share/icons/hicolor/scalable/")) 76 | validPath = true; 77 | 78 | REQUIRE(validPath); 79 | } 80 | } 81 | 82 | TEST_CASE("Theme parsing", "[Theme]") 83 | { 84 | auto hicolorThemeIndex = Utils::getDataPath("hicolor-theme-index.theme"); 85 | REQUIRE(std::filesystem::exists(hicolorThemeIndex)); 86 | 87 | std::vector indexData; 88 | std::ifstream f(hicolorThemeIndex, std::ios::binary); 89 | REQUIRE(f.is_open()); 90 | 91 | f.seekg(0, std::ios::end); 92 | indexData.resize(f.tellg()); 93 | f.seekg(0, std::ios::beg); 94 | f.read(reinterpret_cast(indexData.data()), indexData.size()); 95 | 96 | auto theme = std::make_unique("hicolor", indexData); 97 | 98 | REQUIRE(theme->name() == "hicolor"); 99 | REQUIRE_FALSE(theme->directories().empty()); 100 | 101 | // Test that we can find directories that match various sizes 102 | bool found16x16Match = false; 103 | bool found48x48Match = false; 104 | 105 | for (const auto &dir : theme->directories()) { 106 | // Check if we can find a directory that matches 16x16 (base size) 107 | if (theme->directoryMatchesSize(dir, ImageSize(16), false)) { 108 | found16x16Match = true; 109 | } 110 | // Check if we can find a directory that matches 48x48 111 | if (theme->directoryMatchesSize(dir, ImageSize(48), false)) { 112 | found48x48Match = true; 113 | } 114 | } 115 | 116 | REQUIRE(found16x16Match); 117 | REQUIRE(found48x48Match); 118 | } 119 | -------------------------------------------------------------------------------- /data/templates/default/sections_index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Data for the {{suite}} suite{% endblock %} 4 | 5 | {% block header_content %} 6 | ⇦ | 7 | AppStream data for {{project_name}}/{{suite}} 8 | {% endblock %} 9 | 10 | {% block head_extra %} 11 | 12 | 13 | 14 | 15 | 16 | 28 | {% endblock %} 29 | 30 | {% block float_right %} 31 | Last updated on: {{time}} 32 | {% endblock %} 33 | 34 | {% block content %} 35 |

Select an archive section

36 | 37 |

Sections

38 | {% for section in sections %} 39 |

{{section.section}}

40 | {% endfor %} 41 | 42 |

Health of suite "{{suite}}"

43 | 44 |
45 |
46 | {% for section in sections %} 47 |
48 |
{{section.section}}
49 |
50 |
51 | {% endfor %} 52 |
53 |
54 | 55 | 94 | 129 | {% endblock %} 130 | -------------------------------------------------------------------------------- /src/backends/alpinelinux/apkpkg.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020-2025 Rasmus Thomsen 3 | * Copyright (C) 2016-2025 Matthias Klumpp 4 | * 5 | * Licensed under the GNU Lesser General Public License Version 3 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Lesser General Public License as published by 9 | * the Free Software Foundation, either version 3 of the license, or 10 | * (at your option) any later version. 11 | * 12 | * This software is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Lesser General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Lesser General Public License 18 | * along with this software. If not, see . 19 | */ 20 | 21 | #include "apkpkg.h" 22 | 23 | #include 24 | #include 25 | 26 | #include "../../config.h" 27 | #include "../../downloader.h" 28 | #include "../../utils.h" 29 | #include "../../zarchive.h" 30 | 31 | namespace fs = std::filesystem; 32 | 33 | namespace ASGenerator 34 | { 35 | 36 | AlpinePackage::AlpinePackage(const std::string &pkgname, const std::string &pkgver, const std::string &pkgarch) 37 | : m_pkgname(pkgname), 38 | m_pkgver(pkgver), 39 | m_pkgarch(pkgarch), 40 | m_archive(std::make_unique()) 41 | { 42 | const auto &conf = Config::get(); 43 | m_tmpDir = conf.getTmpDir() / std::format("{}-{}_{}", name(), ver(), arch()); 44 | } 45 | 46 | std::string AlpinePackage::name() const 47 | { 48 | return m_pkgname; 49 | } 50 | 51 | void AlpinePackage::setName(const std::string &val) 52 | { 53 | m_pkgname = val; 54 | } 55 | 56 | std::string AlpinePackage::ver() const 57 | { 58 | return m_pkgver; 59 | } 60 | 61 | void AlpinePackage::setVersion(const std::string &val) 62 | { 63 | m_pkgver = val; 64 | } 65 | 66 | std::string AlpinePackage::arch() const 67 | { 68 | return m_pkgarch; 69 | } 70 | 71 | void AlpinePackage::setArch(const std::string &val) 72 | { 73 | m_pkgarch = val; 74 | } 75 | 76 | const std::unordered_map &AlpinePackage::description() const 77 | { 78 | return m_desc; 79 | } 80 | 81 | void AlpinePackage::setFilename(const std::string &fname) 82 | { 83 | m_pkgFname = fname; 84 | } 85 | 86 | std::string AlpinePackage::getFilename() 87 | { 88 | if (!m_localPkgFName.empty()) 89 | return m_localPkgFName; 90 | 91 | if (Utils::isRemote(m_pkgFname)) { 92 | std::lock_guard lock(m_mutex); 93 | auto &dl = Downloader::get(); 94 | const auto path = m_tmpDir / fs::path(m_pkgFname).filename(); 95 | dl.downloadFile(m_pkgFname, path.string()); 96 | m_localPkgFName = path.string(); 97 | return m_localPkgFName; 98 | } else { 99 | m_localPkgFName = m_pkgFname; 100 | return m_localPkgFName; 101 | } 102 | } 103 | 104 | std::string AlpinePackage::maintainer() const 105 | { 106 | return m_pkgmaintainer; 107 | } 108 | 109 | void AlpinePackage::setMaintainer(const std::string &maint) 110 | { 111 | m_pkgmaintainer = maint; 112 | } 113 | 114 | void AlpinePackage::setDescription(const std::string &text, const std::string &locale) 115 | { 116 | m_desc[locale] = text; 117 | } 118 | 119 | std::vector AlpinePackage::getFileData(const std::string &fname) 120 | { 121 | if (!m_archive->isOpen()) 122 | m_archive->open(getFilename()); 123 | 124 | return m_archive->readData(fname); 125 | } 126 | 127 | const std::vector &AlpinePackage::contents() 128 | { 129 | if (!m_contentsL.empty()) 130 | return m_contentsL; 131 | 132 | ArchiveDecompressor ad; 133 | ad.open(getFilename()); 134 | m_contentsL = ad.readContents(); 135 | 136 | return m_contentsL; 137 | } 138 | 139 | void AlpinePackage::setContents(const std::vector &c) 140 | { 141 | m_contentsL = c; 142 | } 143 | 144 | void AlpinePackage::finish() 145 | { 146 | // No-op for Alpine package 147 | } 148 | 149 | } // namespace ASGenerator 150 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AppStream Generator 2 | 3 | 4 | AppStream is an effort to provide additional metadata and unique IDs for all software available in a Linux system. 5 | This repository contains the server-side of the AppStream infrastructure, a tool to generate metadata from distribution packages. You can find out more about AppStream collection metadata at [Freedesktop](https://www.freedesktop.org/software/appstream/docs/chap-CollectionData.html). 6 | 7 | The AppStream generator is currently primarily used by Debian, but is written in a distribution agnostic way. Backends only need to implement [two interfaces](src/backends/interfaces.h) to be ready. 8 | 9 | If you are looking for the AppStream client-tools, the [AppStream repository](https://github.com/ximion/appstream) is where you want to go. 10 | 11 | 12 | ## Install from Flathub 13 | 14 | You can install an up-to-date version of AppStream Generator from [Flathub](https://flathub.org) if you just 15 | want to quickly test the software with your repository: 16 | ```ShellSession 17 | # Add Flathub remote 18 | sudo flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo 19 | # Install appstream-generator 20 | flatpak install org.freedesktop.appstream.generator 21 | 22 | # Run appstream-generator 23 | flatpak run org.freedesktop.appstream.generator --help 24 | ``` 25 | 26 | You can use AppStream Generator as [documented](docs/usage.md), but you will need to replace all 27 | `appstream-generator` commands with `flatpak run org.freedesktop.appstream.generator` and may need 28 | to set the workspace as absolute path using `-w` instead of relying on autodetection. 29 | 30 | 31 | ## Usage 32 | 33 | Take a look at the [docs/](docs/index.md) directory in the source tree for information on how to use the generator and write configuration files for it. 34 | 35 | 36 | ## Development 37 | ![Build Test](https://github.com/ximion/appstream-generator/workflows/Build%20Test/badge.svg) 38 | 39 | ### Build dependencies 40 | 41 | This project requires a C++23-capable compiler, GCC >= 14 or Clang >= 18 is recommended. 42 | 43 | The following libraries and tools are required to build the generator: 44 | * Meson (>= 1.0) [1] 45 | * AppStream [2] 46 | * libarchive (>= 3.2) [3] 47 | * LMDB [4] 48 | * Curl 49 | * Cairo 50 | * GdkPixbuf 2.0 51 | * RSvg 2.0 52 | * FreeType 53 | * Fontconfig 54 | * Pango 55 | * Inja [5] 56 | * Catch2 [6] 57 | * oneAPI TBB [7] 58 | * NPM (optional) [8] 59 | 60 | [1]: http://mesonbuild.com/ 61 | [2]: https://github.com/ximion/appstream 62 | [3]: https://libarchive.org/ 63 | [4]: https://symas.com/lmdb/ 64 | [5]: https://github.com/pantor/inja 65 | [6]: https://github.com/catchorg/Catch2 66 | [7]: https://uxlfoundation.github.io/oneTBB/ 67 | [8]: https://github.com/npm/cli 68 | 69 | On Debian and derivatives of it, all build requirements can be installed using the following command: 70 | ```ShellSession 71 | sudo apt install meson g++ \ 72 | libappstream-dev libappstream-compose-dev libsoup2.4-dev libarchive-dev \ 73 | libgdk-pixbuf2.0-dev librsvg2-dev libcairo2-dev libfreetype-dev libfontconfig1-dev \ 74 | libpango1.0-dev liblmdb-dev libtbb-dev libcatch2-dev \ 75 | npm 76 | ``` 77 | 78 | ### Build instructions 79 | 80 | To build the tool with Meson, create a `build` subdirectory, change into it and run `meson .. && ninja` to build. 81 | In summary: 82 | 83 | ```ShellSession 84 | $ mkdir build && cd build 85 | $ meson -Ddownload-js=true .. 86 | $ ninja 87 | $ sudo ninja install 88 | ``` 89 | 90 | We support several options to be set to influence the build. Change into the build directory and run `mesonconf` to see them all. 91 | 92 | You might want to perform an optimized debug build by passing `--buildtype=debugoptimized` to `meson` or just do a release build straight 93 | away with `--buildtype=release` in case you want to use the resulting binaries productively. By default, the build happens without optimizations 94 | which slows down the generator. 95 | 96 | 97 | ## Hacking 98 | 99 | Pull-requests and patches are very welcome! Using C++23 features is encouraged, if sensible. 100 | Make sure your code compiles in maintainer mode, and format your changes to adhere to the project's coding style. 101 | To help with the latter we provide the `autoformat.py` helper script to format code via *clang-format*. 102 | -------------------------------------------------------------------------------- /src/reportgenerator.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2022 Matthias Klumpp 3 | * 4 | * Licensed under the GNU Lesser General Public License Version 3 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the license, or 9 | * (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this software. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include 29 | #include 30 | 31 | #include "config.h" 32 | #include "datastore.h" 33 | #include "backends/interfaces.h" 34 | 35 | namespace ASGenerator 36 | { 37 | 38 | class ReportGenerator 39 | { 40 | public: 41 | explicit ReportGenerator(DataStore *db); 42 | ~ReportGenerator() = default; 43 | 44 | void processFor( 45 | const std::string &suiteName, 46 | const std::string §ion, 47 | const std::vector> &pkgs); 48 | void updateIndexPages(); 49 | void exportStatistics(); 50 | 51 | // Delete copy constructor and assignment operator 52 | ReportGenerator(const ReportGenerator &) = delete; 53 | ReportGenerator &operator=(const ReportGenerator &) = delete; 54 | 55 | // Public structs for testing access 56 | struct HintTag { 57 | std::string tag; 58 | std::string message; 59 | }; 60 | 61 | struct HintEntry { 62 | std::string identifier; 63 | std::vector archs; 64 | std::vector errors; 65 | std::vector warnings; 66 | std::vector infos; 67 | }; 68 | 69 | struct MetadataEntry { 70 | AsComponentKind kind; 71 | std::string identifier; 72 | std::vector archs; 73 | std::string data; 74 | std::string iconName; 75 | }; 76 | 77 | struct PkgSummary { 78 | std::string pkgname; 79 | std::vector cpts; 80 | int infoCount = 0; 81 | int warningCount = 0; 82 | int errorCount = 0; 83 | }; 84 | 85 | struct DataSummary { 86 | // maintainer -> package -> summary 87 | std::unordered_map> pkgSummaries; 88 | // package -> component_id -> hint_entry 89 | std::unordered_map> hintEntries; 90 | // package -> version -> gcid -> entry 91 | std::unordered_map>> 92 | mdataEntries; 93 | 94 | int64_t totalMetadata = 0; 95 | int64_t totalInfos = 0; 96 | int64_t totalWarnings = 0; 97 | int64_t totalErrors = 0; 98 | }; 99 | 100 | // Public methods for testing access 101 | void setupInjaContext(inja::json &context); 102 | void renderPage(const std::string &pageID, const std::string &exportName, const inja::json &context); 103 | void renderPagesFor(const std::string &suiteName, const std::string §ion, const DataSummary &dsum); 104 | DataSummary preprocessInformation( 105 | const std::string &suiteName, 106 | const std::string §ion, 107 | const std::vector> &pkgs); 108 | void saveStatistics(const std::string &suiteName, const std::string §ion, const DataSummary &dsum); 109 | 110 | private: 111 | DataStore *m_dstore; 112 | Config *m_conf; 113 | 114 | fs::path m_htmlExportDir; 115 | fs::path m_templateDir; 116 | fs::path m_defaultTemplateDir; 117 | 118 | fs::path m_mediaPoolDir; 119 | std::string m_mediaPoolUrl; 120 | 121 | std::string m_versionInfo; 122 | 123 | inja::Environment m_injaEnv; 124 | }; 125 | 126 | } // namespace ASGenerator 127 | -------------------------------------------------------------------------------- /src/backends/debian/debpkg.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2025 Matthias Klumpp 3 | * 4 | * Licensed under the GNU Lesser General Public License Version 3 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the license, or 9 | * (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this software. If not, see . 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include "../interfaces.h" 30 | #include "../../utils.h" 31 | #include "tagfile.h" 32 | 33 | namespace ASGenerator 34 | { 35 | 36 | class ArchiveDecompressor; 37 | 38 | /** 39 | * Helper class for simple deduplication of package descriptions 40 | * between packages of different architectures in memory. 41 | */ 42 | class DebPackageLocaleTexts 43 | { 44 | public: 45 | std::unordered_map summary; ///< map of localized package short summaries 46 | std::unordered_map description; ///< map of localized package descriptions 47 | 48 | void setDescription(const std::string &text, const std::string &locale); 49 | void setSummary(const std::string &text, const std::string &locale); 50 | 51 | private: 52 | mutable std::mutex m_mutex; 53 | }; 54 | 55 | /** 56 | * Representation of a Debian binary package 57 | */ 58 | class DebPackage : public Package 59 | { 60 | public: 61 | DebPackage( 62 | const std::string &pname, 63 | const std::string &pver, 64 | const std::string &parch, 65 | std::shared_ptr l10nTexts = nullptr); 66 | ~DebPackage() override; 67 | 68 | // Package interface implementation 69 | std::string name() const override; 70 | std::string ver() const override; 71 | std::string arch() const override; 72 | std::string maintainer() const override; 73 | 74 | const std::unordered_map &description() const override; 75 | const std::unordered_map &summary() const override; 76 | 77 | std::string getFilename() override; 78 | const std::vector &contents() override; 79 | std::vector getFileData(const std::string &fname) override; 80 | 81 | void cleanupTemp() override; 82 | void finish() override; 83 | 84 | std::optional gst() const override; 85 | 86 | // Debian-specific methods 87 | void setName(const std::string &s); 88 | void setVersion(const std::string &s); 89 | void setArch(const std::string &s); 90 | void setMaintainer(const std::string &maint); 91 | void setFilename(const std::string &fname); 92 | void setGst(const GStreamer &gst); 93 | 94 | void updateTmpDirPath(); 95 | void setDescription(const std::string &text, const std::string &locale); 96 | void setSummary(const std::string &text, const std::string &locale); 97 | void setLocalizedTexts(std::shared_ptr l10nTexts); 98 | 99 | std::shared_ptr localizedTexts(); 100 | 101 | void extractPackage(const std::string &dest = ""); 102 | std::unique_ptr readControlInformation(); 103 | 104 | private: 105 | std::string m_pkgname; 106 | std::string m_pkgver; 107 | std::string m_pkgarch; 108 | std::string m_pkgmaintainer; 109 | std::shared_ptr m_descTexts; 110 | std::optional m_gstreamer; 111 | 112 | bool m_contentsRead; 113 | std::vector m_contentsL; 114 | 115 | fs::path m_tmpDir; 116 | std::unique_ptr m_controlArchive; 117 | std::unique_ptr m_dataArchive; 118 | 119 | std::string m_debFname; 120 | fs::path m_localDebFname; 121 | 122 | mutable std::mutex m_mutex; 123 | 124 | ArchiveDecompressor &openPayloadArchive(); 125 | ArchiveDecompressor &openControlArchive(); 126 | }; 127 | 128 | } // namespace ASGenerator 129 | -------------------------------------------------------------------------------- /autoformat.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Copyright (C) 2015-2022 Matthias Klumpp 4 | # 5 | # SPDX-License-Identifier: LGPL-2.1+ 6 | # 7 | # Format all AppStream source code in-place. 8 | # 9 | 10 | import os 11 | import sys 12 | import shutil 13 | import fnmatch 14 | import subprocess 15 | import tempfile 16 | from glob import glob 17 | 18 | INCLUDE_LOCATIONS = [ 19 | 'autoformat.py', 20 | 'src', 21 | 'tests', 22 | ] 23 | 24 | EXCLUDE_MATCH = [] 25 | 26 | EXTRA_STYLE_RULES_FOR = [] 27 | 28 | 29 | def format_cpp_sources(sources, style_fname=None, extra_styles: list[str] = None): 30 | """Format C/C++ sources with clang-format.""" 31 | 32 | if not sources: 33 | return 34 | 35 | command = ['clang-format', '-i'] 36 | 37 | if extra_styles: 38 | style_rules = [] 39 | if style_fname: 40 | with open(style_fname, 'r') as f: 41 | style_rules = [l.strip() for l in f.readlines()] 42 | 43 | with tempfile.NamedTemporaryFile(mode='w') as fp: 44 | style_rules.extend(extra_styles) 45 | fp.write('\n'.join(style_rules)) 46 | fp.flush() 47 | 48 | command.append('--style=file:{}'.format(fp.name)) 49 | command.extend(sources) 50 | subprocess.run(command, check=True) 51 | return 52 | 53 | if style_fname: 54 | command.append('--style=file:{}'.format(style_fname)) 55 | 56 | command.extend(sources) 57 | subprocess.run(command, check=True) 58 | 59 | 60 | def format_python_sources(sources): 61 | """Format Python sources with Black.""" 62 | 63 | command = [ 64 | 'black', 65 | '-S', # no string normalization 66 | '-l', 67 | '100', # line length 68 | '-t', 69 | 'py311', # minimum Python target 70 | ] 71 | command.extend(sources) 72 | subprocess.run(command, check=True) 73 | 74 | 75 | def run(current_dir, args): 76 | # check for tools 77 | if not shutil.which('clang-format'): 78 | print( 79 | 'The `clang-format` formatter is not installed. Please install it to continue!', 80 | file=sys.stderr, 81 | ) 82 | return 1 83 | if not shutil.which('black'): 84 | print( 85 | 'The `black` formatter is not installed. Please install it to continue!', 86 | file=sys.stderr, 87 | ) 88 | return 1 89 | 90 | # if no include directories are explicitly specified, we read all locations 91 | if not INCLUDE_LOCATIONS: 92 | INCLUDE_LOCATIONS.append('.') 93 | 94 | # collect sources 95 | cpp_sources = [] 96 | cpp_style_matches = [[]] * len(EXTRA_STYLE_RULES_FOR) 97 | py_sources = [] 98 | for il_path_base in INCLUDE_LOCATIONS: 99 | il_path = os.path.join(current_dir, il_path_base) 100 | if os.path.isfile(il_path): 101 | candidates = [il_path] 102 | else: 103 | candidates = glob(il_path + '/**/*', recursive=True) 104 | 105 | for filename in candidates: 106 | skip = False 107 | for exclude in EXCLUDE_MATCH: 108 | if fnmatch.fnmatch(filename, exclude): 109 | skip = True 110 | break 111 | if skip: 112 | continue 113 | 114 | if filename.endswith(('.c', '.cpp', '.h', '.hpp')): 115 | cpp_sources.append(filename) 116 | 117 | for i, er in enumerate(EXTRA_STYLE_RULES_FOR): 118 | for pattern in er[1]: 119 | if fnmatch.fnmatch(filename, pattern): 120 | cpp_style_matches[i].append(filename) 121 | break 122 | 123 | elif filename.endswith('.py'): 124 | py_sources.append(filename) 125 | 126 | # format 127 | format_python_sources(py_sources) 128 | format_cpp_sources(cpp_sources) 129 | 130 | for i, er in enumerate(EXTRA_STYLE_RULES_FOR): 131 | format_cpp_sources( 132 | cpp_style_matches[i], 133 | os.path.join(current_dir, '.clang-format'), 134 | er[0], 135 | ) 136 | 137 | return 0 138 | 139 | 140 | if __name__ == '__main__': 141 | thisfile = __file__ 142 | if not os.path.isabs(thisfile): 143 | thisfile = os.path.normpath(os.path.join(os.getcwd(), thisfile)) 144 | thisdir = os.path.normpath(os.path.join(os.path.dirname(thisfile))) 145 | os.chdir(thisdir) 146 | 147 | sys.exit(run(thisdir, sys.argv[1:])) 148 | -------------------------------------------------------------------------------- /src/backends/debian/tagfile.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2025 Matthias Klumpp 3 | * 4 | * Licensed under the GNU Lesser General Public License Version 3 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the license, or 9 | * (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this software. If not, see . 18 | */ 19 | 20 | #include "tagfile.h" 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "../../logging.h" 28 | #include "../../zarchive.h" 29 | #include "../../utils.h" 30 | 31 | namespace ASGenerator 32 | { 33 | 34 | TagFile::TagFile() 35 | : m_pos(0) 36 | { 37 | m_currentBlock.clear(); 38 | } 39 | 40 | void TagFile::open(const std::string &fname, bool compressed) 41 | { 42 | m_fname = fname; 43 | 44 | if (compressed) { 45 | auto data = decompressFile(fname); 46 | load(data); 47 | } else { 48 | std::ifstream file(fname); 49 | if (!file.is_open()) 50 | throw std::runtime_error(std::format("Could not open file: {}", fname)); 51 | 52 | std::ostringstream buffer; 53 | buffer << file.rdbuf(); 54 | load(buffer.str()); 55 | } 56 | } 57 | 58 | void TagFile::load(const std::string &data) 59 | { 60 | m_content = Utils::splitString(data, '\n'); 61 | m_pos = 0; 62 | readCurrentBlockData(); 63 | } 64 | 65 | void TagFile::first() 66 | { 67 | m_pos = 0; 68 | readCurrentBlockData(); 69 | } 70 | 71 | void TagFile::readCurrentBlockData() 72 | { 73 | m_currentBlock.clear(); 74 | const auto clen = m_content.size(); 75 | 76 | for (auto i = m_pos; i < clen; i++) { 77 | if (m_content[i].empty()) 78 | break; 79 | 80 | // check whether we are in a multiline value field, and just skip forward in that case 81 | if (m_content[i].starts_with(" ")) 82 | continue; 83 | 84 | const auto separatorIndex = m_content[i].find(':'); 85 | if (separatorIndex == std::string::npos || separatorIndex == 0) 86 | continue; 87 | 88 | auto fieldName = m_content[i].substr(0, separatorIndex); 89 | auto fieldData = m_content[i].substr(separatorIndex + 1); 90 | 91 | // remove whitespace 92 | fieldData = Utils::trimString(fieldData); 93 | 94 | // check if we have a multiline field 95 | for (auto j = i + 1; j < clen; j++) { 96 | if (m_content[j].empty()) 97 | break; 98 | if (!m_content[j].starts_with(" ")) 99 | break; 100 | 101 | // we have a multiline field 102 | auto data = m_content[j].substr(1); // remove the leading space 103 | if (data == ".") 104 | fieldData += "\n"; // just a dot means empty line 105 | else 106 | fieldData += "\n" + data; 107 | i = j; // skip forward 108 | } 109 | 110 | m_currentBlock[fieldName] = std::move(fieldData); 111 | } 112 | } 113 | 114 | bool TagFile::nextSection() 115 | { 116 | const auto clen = m_content.size(); 117 | 118 | // find next section 119 | auto i = m_pos; 120 | for (; i < clen; i++) { 121 | if (m_content[i].empty()) { 122 | i++; 123 | break; 124 | } 125 | } 126 | 127 | if (i >= clen) 128 | return false; 129 | 130 | m_pos = i; 131 | readCurrentBlockData(); 132 | return !m_currentBlock.empty(); 133 | } 134 | 135 | bool TagFile::eof() const 136 | { 137 | return m_pos >= m_content.size(); 138 | } 139 | 140 | std::string TagFile::readField(const std::string &fieldName, const std::string &defaultValue) const 141 | { 142 | const auto it = m_currentBlock.find(fieldName); 143 | if (it != m_currentBlock.end()) 144 | return it->second; 145 | return defaultValue; 146 | } 147 | 148 | bool TagFile::hasField(const std::string &fieldName) const 149 | { 150 | return m_currentBlock.find(fieldName) != m_currentBlock.end(); 151 | } 152 | 153 | } // namespace ASGenerator 154 | -------------------------------------------------------------------------------- /src/backends/rpmmd/rpmpkg.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016-2025 Matthias Klumpp 3 | * 4 | * Licensed under the GNU Lesser General Public License Version 3 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation, either version 3 of the license, or 9 | * (at your option) any later version. 10 | * 11 | * This software is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this software. If not, see . 18 | */ 19 | 20 | #include "rpmpkg.h" 21 | 22 | #include 23 | #include 24 | 25 | #include "../../config.h" 26 | #include "../../logging.h" 27 | #include "../../zarchive.h" 28 | #include "../../downloader.h" 29 | #include "../../utils.h" 30 | 31 | namespace ASGenerator 32 | { 33 | 34 | RPMPackage::RPMPackage() 35 | : m_archive(std::make_unique()) 36 | { 37 | } 38 | 39 | std::string RPMPackage::name() const 40 | { 41 | return m_pkgname; 42 | } 43 | 44 | void RPMPackage::setName(const std::string &val) 45 | { 46 | m_pkgname = val; 47 | } 48 | 49 | std::string RPMPackage::ver() const 50 | { 51 | return m_pkgver; 52 | } 53 | 54 | void RPMPackage::setVersion(const std::string &val) 55 | { 56 | m_pkgver = val; 57 | } 58 | 59 | std::string RPMPackage::arch() const 60 | { 61 | return m_pkgarch; 62 | } 63 | 64 | void RPMPackage::setArch(const std::string &val) 65 | { 66 | m_pkgarch = val; 67 | } 68 | 69 | const std::unordered_map &RPMPackage::description() const 70 | { 71 | return m_desc; 72 | } 73 | 74 | const std::unordered_map &RPMPackage::summary() const 75 | { 76 | return m_summ; 77 | } 78 | 79 | std::string RPMPackage::getFilename() 80 | { 81 | if (!m_localPkgFname.empty()) 82 | return m_localPkgFname; 83 | 84 | if (Utils::isRemote(m_pkgFname)) { 85 | std::lock_guard lock(m_mutex); 86 | const auto &conf = Config::get(); 87 | auto &dl = Downloader::get(); 88 | const fs::path path = conf.getTmpDir() 89 | / std::format( 90 | "{}-{}_{}_{}", name(), ver(), arch(), fs::path(m_pkgFname).filename().string()); 91 | dl.downloadFile(m_pkgFname, path); 92 | m_localPkgFname = path; 93 | return m_localPkgFname; 94 | } else { 95 | m_localPkgFname = m_pkgFname; 96 | return m_pkgFname; 97 | } 98 | } 99 | 100 | void RPMPackage::setFilename(const std::string &fname) 101 | { 102 | m_pkgFname = fname; 103 | } 104 | 105 | std::string RPMPackage::maintainer() const 106 | { 107 | return m_pkgmaintainer; 108 | } 109 | 110 | void RPMPackage::setMaintainer(const std::string &maint) 111 | { 112 | m_pkgmaintainer = maint; 113 | } 114 | 115 | void RPMPackage::setDescription(const std::string &text, const std::string &locale) 116 | { 117 | m_desc[locale] = text; 118 | } 119 | 120 | void RPMPackage::setSummary(const std::string &text, const std::string &locale) 121 | { 122 | m_summ[locale] = text; 123 | } 124 | 125 | std::vector RPMPackage::getFileData(const std::string &fname) 126 | { 127 | std::lock_guard lock(m_mutex); 128 | if (!m_archive->isOpen()) { 129 | const auto pkgFilename = getFilename(); 130 | m_archive->open(pkgFilename, Config::get().getTmpDir() / fs::path(pkgFilename).filename()); 131 | m_archive->setOptimizeRepeatedReads(true); 132 | } 133 | 134 | return m_archive->readData(fname); 135 | } 136 | 137 | const std::vector &RPMPackage::contents() 138 | { 139 | return m_contentsL; 140 | } 141 | 142 | void RPMPackage::setContents(const std::vector &c) 143 | { 144 | m_contentsL = c; 145 | } 146 | 147 | void RPMPackage::finish() 148 | { 149 | std::lock_guard lock(m_mutex); 150 | 151 | if (m_archive->isOpen()) 152 | m_archive->close(); 153 | 154 | try { 155 | if (Utils::isRemote(m_pkgFname) && fs::exists(m_localPkgFname)) { 156 | fs::remove(m_localPkgFname); 157 | m_localPkgFname.clear(); 158 | } 159 | } catch (const std::exception &e) { 160 | // we ignore any error 161 | logDebug("Unable to remove temporary package: {} ({})", m_localPkgFname.string(), e.what()); 162 | } 163 | } 164 | 165 | } // namespace ASGenerator 166 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('AppStream Generator', 'cpp', 2 | meson_version : '>=1.0', 3 | default_options : ['c_std=c23', 'cpp_std=c++23'], 4 | subproject_dir : 'contrib/subprojects', 5 | license : 'LGPL-3.0+', 6 | version : '0.10.2' 7 | ) 8 | 9 | asgen_version = meson.project_version() 10 | 11 | source_root = meson.project_source_root() 12 | build_root = meson.project_build_root() 13 | cxx = meson.get_compiler('cpp') 14 | 15 | fs = import('fs') 16 | 17 | # 18 | # Dependencies 19 | # 20 | src_dir = include_directories('src/') 21 | 22 | glib_dep = dependency('glib-2.0', version: '>= 2.80') 23 | appstream_dep = dependency('appstream', version: '>= 1.1.0') 24 | ascompose_dep = dependency('appstream-compose', version: '>= 1.1.0') 25 | lmdb_dep = dependency('lmdb', version: '>= 0.9.22') 26 | archive_dep = dependency('libarchive', version: '>= 3.2') 27 | curl_dep = dependency('libcurl') 28 | fyaml_dep = dependency('libfyaml', version: '>= 0.8') 29 | tbb_dep = dependency('tbb') 30 | icu_dep = dependency('icu-uc') 31 | inja_dep = dependency('inja', fallback: ['inja', 'inja_dep'], default_options: ['build_tests=false']) 32 | libxml2_dep = dependency('libxml-2.0') # for rpmmd 33 | catch2_dep = dependency('catch2-with-main') 34 | 35 | backward_deps = [] 36 | backward_dep = dependency('', required: false) 37 | if get_option('backward') 38 | backward_dep = dependency('backward-cpp', fallback: ['backward-cpp', 'backward_dep'], required: false) 39 | if backward_dep.found() 40 | message('Using backward-cpp for stack traces') 41 | libunwind_dep = dependency('libunwind') 42 | backward_deps = [backward_dep, libunwind_dep] 43 | endif 44 | endif 45 | 46 | # Ensure we have a compiler that can handle the C++23 features we need 47 | if cxx.get_id() == 'gcc' 48 | if not cxx.version().version_compare('>=14.0') 49 | error('GCC 14 or newer is required to build this project') 50 | endif 51 | elif cxx.get_id() == 'clang' 52 | if not cxx.version().version_compare('>=18.0') 53 | error('Clang 18 or newer is required to build this project') 54 | endif 55 | endif 56 | 57 | # 58 | # Compiler flags 59 | # 60 | add_project_arguments('-D_POSIX_C_SOURCE=201710L', language: 'c') 61 | add_project_arguments('-D_POSIX_C_SOURCE=201710L', language: 'cpp') 62 | 63 | if get_option('maintainer') 64 | maintainer_c_args = [ 65 | '-Werror', 66 | '-Wall', 67 | '-Wextra', 68 | '-Wcast-align', 69 | '-Wno-uninitialized', 70 | '-Wempty-body', 71 | '-Wformat-security', 72 | '-Winit-self', 73 | '-Wnull-dereference', 74 | '-Winline', 75 | '-Wmaybe-uninitialized', 76 | ] 77 | maintainer_cpp_args = [ 78 | '-Wsuggest-final-methods' 79 | ] 80 | 81 | add_project_arguments(maintainer_c_args, language: 'c') 82 | add_project_arguments([maintainer_c_args, maintainer_cpp_args], language: 'cpp') 83 | endif 84 | 85 | # a few compiler warning/error flags we always want enabled 86 | add_project_arguments( 87 | '-Werror=shadow', 88 | '-Werror=empty-body', 89 | '-Werror=missing-prototypes', 90 | '-Werror=implicit-function-declaration', 91 | '-Werror=missing-declarations', 92 | '-Werror=return-type', 93 | '-Werror=int-conversion', 94 | '-Werror=incompatible-pointer-types', 95 | '-Werror=misleading-indentation', 96 | '-Werror=format-security', 97 | 98 | '-Wno-missing-field-initializers', 99 | '-Wno-error=missing-field-initializers', 100 | '-Wno-unused-parameter', 101 | '-Wno-error=unused-parameter', 102 | language: 'c' 103 | ) 104 | add_project_arguments( 105 | cxx.get_supported_arguments([ 106 | '-Werror=empty-body', 107 | '-Werror=pointer-arith', 108 | '-Werror=missing-declarations', 109 | '-Werror=return-type', 110 | '-Werror=misleading-indentation', 111 | '-Werror=format-security', 112 | #'-Werror=suggest-override', -- we can't enable this, Inja is missing overrides 113 | 114 | '-Wno-missing-field-initializers', 115 | '-Wno-error=missing-field-initializers', 116 | '-Wno-unused-parameter', 117 | '-Wno-error=unused-parameter', 118 | ]), 119 | language: 'cpp' 120 | ) 121 | 122 | # 123 | # Download JS stuff and additional sources if we couldn't find them 124 | # 125 | if get_option('download-js') 126 | npm_exe = find_program('npm') 127 | if not fs.is_dir(source_root / 'data' / 'templates' / 'default' / 'static' / 'js') 128 | message('Downloading JavaScript libraries...') 129 | getjs_cmd = run_command([source_root + '/contrib/setup/build_js.sh', npm_exe], check: false) 130 | if getjs_cmd.returncode() != 0 131 | error('Unable to download JavaScript files with NPM:\n' + getjs_cmd.stdout() + getjs_cmd.stderr()) 132 | endif 133 | endif 134 | endif 135 | 136 | # 137 | # Subdirs 138 | # 139 | subdir('src') 140 | subdir('data') 141 | subdir('tests') 142 | subdir('docs') 143 | -------------------------------------------------------------------------------- /data/templates/debian/static/css/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-size: 100%; 3 | -webkit-text-size-adjust: 100%; 4 | -ms-text-size-adjust:100%; 5 | height:100%; 6 | } 7 | 8 | body { 9 | border-sizing: border-box; 10 | font-family: Cantarell,"Helvetica Neue",Helvetica,Arial,sans-serif; 11 | font-size: 14px; 12 | line-height: 20px; 13 | color: #333333; 14 | margin: 0; 15 | height: 93%; 16 | } 17 | 18 | a { 19 | color: #337ab7; 20 | text-decoration: none; 21 | background-color: transparent; 22 | } 23 | 24 | .headbar { 25 | border-radius: 4px; 26 | display: block; 27 | border: 1px solid transparent; 28 | margin-bottom: 20px; 29 | min-height: 50px; 30 | position: relative; 31 | 32 | background-color: #f8f8f8; 33 | border-color: #e7e7e7; 34 | border-width: 0 0 1px; 35 | z-index: 1000; 36 | border-radius: 0; 37 | margin-bottom: 14px; 38 | } 39 | 40 | .headbar-content { 41 | font-size: 18px; 42 | line-height: 20px; 43 | padding: 15px; 44 | float: left; 45 | } 46 | 47 | .headbar-content-right { 48 | font-size: 14px; 49 | line-height: 20px; 50 | padding: 15px; 51 | float: right; 52 | } 53 | 54 | .content { 55 | padding: 0em 1em 0em 1em; 56 | } 57 | 58 | .wrapper { 59 | width: 60%; 60 | } 61 | 62 | .wrapper hr { 63 | border-top: none; 64 | border-bottom: 1px solid #819eb7; 65 | margin-bottom: 1em; 66 | } 67 | 68 | img.fit { 69 | max-width: 99%; 70 | max-height: 99%; 71 | } 72 | 73 | hr { 74 | border-top: none; 75 | border-bottom: 1px solid #d70a53; 76 | margin-bottom: 1em; 77 | } 78 | 79 | footer { 80 | text-align: center; 81 | margin-top: 1em; 82 | } 83 | 84 | span.avoidwrap { display: inline-block; } 85 | 86 | .infobox { 87 | border-color: #eee; 88 | border-image: none; 89 | border-radius: 3px; 90 | border-style: solid; 91 | border-width: 1px 1px 1px 5px; 92 | margin: 20px 0; 93 | padding: 20px; 94 | } 95 | 96 | .infobox h2 { 97 | margin-bottom: 5px; 98 | margin-top: 0; 99 | } 100 | 101 | .infobox p:last-child { 102 | margin-bottom: 0 103 | } 104 | 105 | .infobox-hint { 106 | border-left-color: #1b809e; 107 | } 108 | 109 | .infobox-hint h2 { 110 | color: #1b809e; 111 | } 112 | 113 | .infobox-error { 114 | border-left-color: #ce4844; 115 | } 116 | 117 | .infobox-error h2 { 118 | color: #ce4844; 119 | } 120 | 121 | .infobox-warning { 122 | border-left-color: #aa6708; 123 | } 124 | 125 | .infobox-warning h2 { 126 | color: #aa6708; 127 | } 128 | 129 | /* label styles copied from Bootstrap */ 130 | .label { 131 | border-radius: 0.25em; 132 | color: #fff; 133 | display: inline; 134 | font-size: 75%; 135 | font-weight: 700; 136 | line-height: 1; 137 | padding: 0.2em 0.6em 0.3em; 138 | text-align: center; 139 | vertical-align: baseline; 140 | white-space: nowrap; 141 | } 142 | 143 | .label-info { 144 | background-color: #5bc0de; 145 | } 146 | 147 | .label-warning { 148 | background-color: #f0ad4e; 149 | } 150 | 151 | .label-error { 152 | background-color: #d9534f; 153 | } 154 | 155 | .label-neutral { 156 | background-color: #777; 157 | } 158 | 159 | .overviewlisting a { 160 | color: #000000; 161 | text-decoration: none; 162 | } 163 | 164 | .overviewlisting li { 165 | padding: 2px 4px 2px; 166 | } 167 | 168 | code { 169 | background-color: #f9f2f4; 170 | border-radius: 4px; 171 | color: #c7254e; 172 | font-size: 90%; 173 | padding: 2px 4px; 174 | } 175 | 176 | .well{ 177 | min-height:20px; 178 | padding:19px; 179 | margin-bottom:20px; 180 | background-color:#f5f5f5; 181 | border:1px solid #e3e3e3; 182 | border-radius:4px; 183 | -webkit-box-shadow: 184 | inset 0 1px 1px rgba(0,0,0,.05); 185 | box-shadow:inset 0 1px 1px rgba(0,0,0,.05) 186 | } 187 | 188 | .progress { 189 | background-color: #f5f5f5; 190 | border-radius: 4px; 191 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1) inset; 192 | height: 20px; 193 | margin-bottom: 20px; 194 | overflow: hidden; 195 | } 196 | 197 | .progress-bar { 198 | background-color: #337ab7; 199 | box-shadow: 0 -1px 0 rgba(0, 0, 0, 0.15) inset; 200 | color: #fff; 201 | float: left; 202 | font-size: 12px; 203 | height: 100%; 204 | line-height: 20px; 205 | text-align: center; 206 | transition: width 0.6s ease 0s; 207 | width: 0; 208 | } 209 | 210 | .progress-bar-blue { 211 | background-color: #5bc0de; 212 | } 213 | 214 | .progress-bar-green { 215 | background-color: #5cb85c; 216 | } 217 | 218 | .progress-bar-yellow { 219 | background-color: #f0ad4e; 220 | } 221 | 222 | .progress-bar-red { 223 | background-color: #d9534f; 224 | } 225 | 226 | .sr-only { 227 | border: 0 none; 228 | clip: rect(0px, 0px, 0px, 0px); 229 | height: 1px; 230 | margin: -1px; 231 | overflow: hidden; 232 | padding: 0; 233 | position: absolute; 234 | width: 1px; 235 | } 236 | 237 | .permalink { 238 | font-size: 75%; 239 | color: #999; 240 | line-height: 100%; 241 | font-weight: normal; 242 | text-decoration: none; 243 | } 244 | -------------------------------------------------------------------------------- /data/templates/default/static/css/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-size: 100%; 3 | -webkit-text-size-adjust: 100%; 4 | -ms-text-size-adjust:100%; 5 | height:100%; 6 | } 7 | 8 | body { 9 | border-sizing: border-box; 10 | font-family: Cantarell,"Helvetica Neue",Helvetica,Arial,sans-serif; 11 | font-size: 14px; 12 | line-height: 20px; 13 | color: #333333; 14 | margin: 0; 15 | height: 93%; 16 | } 17 | 18 | a { 19 | color: #337ab7; 20 | text-decoration: none; 21 | background-color: transparent; 22 | } 23 | 24 | .headbar { 25 | border-radius: 4px; 26 | display: block; 27 | border: 1px solid transparent; 28 | margin-bottom: 20px; 29 | min-height: 50px; 30 | position: relative; 31 | 32 | background-color: #f8f8f8; 33 | border-color: #e7e7e7; 34 | border-width: 0 0 1px; 35 | z-index: 1000; 36 | border-radius: 0; 37 | margin-bottom: 14px; 38 | } 39 | 40 | .headbar-content { 41 | font-size: 18px; 42 | line-height: 20px; 43 | padding: 15px; 44 | float: left; 45 | } 46 | 47 | .headbar-content-right { 48 | font-size: 14px; 49 | line-height: 20px; 50 | padding: 15px; 51 | float: right; 52 | } 53 | 54 | .content { 55 | padding: 0em 1em 0em 1em; 56 | } 57 | 58 | .wrapper { 59 | width: 60%; 60 | } 61 | 62 | .wrapper hr { 63 | border-top: none; 64 | border-bottom: 1px solid #819eb7; 65 | margin-bottom: 1em; 66 | } 67 | 68 | img.fit { 69 | max-width: 99%; 70 | max-height: 99%; 71 | } 72 | 73 | hr { 74 | border-top: none; 75 | border-bottom: 1px solid #0a630d; 76 | margin-bottom: 1em; 77 | } 78 | 79 | footer { 80 | text-align: center; 81 | margin-top: 1em; 82 | } 83 | 84 | span.avoidwrap { display: inline-block; } 85 | 86 | .infobox { 87 | border-color: #eee; 88 | border-image: none; 89 | border-radius: 3px; 90 | border-style: solid; 91 | border-width: 1px 1px 1px 5px; 92 | margin: 20px 0; 93 | padding: 20px; 94 | } 95 | 96 | .infobox h2 { 97 | margin-bottom: 5px; 98 | margin-top: 0; 99 | } 100 | 101 | .infobox p:last-child { 102 | margin-bottom: 0 103 | } 104 | 105 | .infobox-hint { 106 | border-left-color: #1b809e; 107 | } 108 | 109 | .infobox-hint h2 { 110 | color: #1b809e; 111 | } 112 | 113 | .infobox-error { 114 | border-left-color: #ce4844; 115 | } 116 | 117 | .infobox-error h2 { 118 | color: #ce4844; 119 | } 120 | 121 | .infobox-warning { 122 | border-left-color: #aa6708; 123 | } 124 | 125 | .infobox-warning h2 { 126 | color: #aa6708; 127 | } 128 | 129 | /* label styles copied from Bootstrap */ 130 | .label { 131 | border-radius: 0.25em; 132 | color: #fff; 133 | display: inline; 134 | font-size: 75%; 135 | font-weight: 700; 136 | line-height: 1; 137 | padding: 0.2em 0.6em 0.3em; 138 | text-align: center; 139 | vertical-align: baseline; 140 | white-space: nowrap; 141 | } 142 | 143 | .label-info { 144 | background-color: #5bc0de; 145 | } 146 | 147 | .label-warning { 148 | background-color: #f0ad4e; 149 | } 150 | 151 | .label-error { 152 | background-color: #d9534f; 153 | } 154 | 155 | .label-neutral { 156 | background-color: #777; 157 | } 158 | 159 | .overviewlisting a { 160 | color: #000000; 161 | text-decoration: none; 162 | } 163 | 164 | .overviewlisting li { 165 | padding: 2px 4px 2px; 166 | } 167 | 168 | code { 169 | background-color: #f9f2f4; 170 | border-radius: 4px; 171 | color: #c7254e; 172 | font-size: 90%; 173 | padding: 2px 4px; 174 | } 175 | 176 | .well{ 177 | min-height:20px; 178 | padding:19px; 179 | margin-bottom:20px; 180 | background-color:#f5f5f5; 181 | border:1px solid #e3e3e3; 182 | border-radius:4px; 183 | -webkit-box-shadow: 184 | inset 0 1px 1px rgba(0,0,0,.05); 185 | box-shadow:inset 0 1px 1px rgba(0,0,0,.05) 186 | } 187 | 188 | .progress { 189 | background-color: #f5f5f5; 190 | border-radius: 4px; 191 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1) inset; 192 | height: 20px; 193 | margin-bottom: 20px; 194 | overflow: hidden; 195 | } 196 | 197 | .progress-bar { 198 | background-color: #337ab7; 199 | box-shadow: 0 -1px 0 rgba(0, 0, 0, 0.15) inset; 200 | color: #fff; 201 | float: left; 202 | font-size: 12px; 203 | height: 100%; 204 | line-height: 20px; 205 | text-align: center; 206 | transition: width 0.6s ease 0s; 207 | width: 0; 208 | } 209 | 210 | .progress-bar-blue { 211 | background-color: #5bc0de; 212 | } 213 | 214 | .progress-bar-green { 215 | background-color: #5cb85c; 216 | } 217 | 218 | .progress-bar-yellow { 219 | background-color: #f0ad4e; 220 | } 221 | 222 | .progress-bar-red { 223 | background-color: #d9534f; 224 | } 225 | 226 | .sr-only { 227 | border: 0 none; 228 | clip: rect(0px, 0px, 0px, 0px); 229 | height: 1px; 230 | margin: -1px; 231 | overflow: hidden; 232 | padding: 0; 233 | position: absolute; 234 | width: 1px; 235 | } 236 | 237 | .permalink { 238 | font-size: 75%; 239 | color: #999; 240 | line-height: 100%; 241 | font-weight: normal; 242 | text-decoration: none; 243 | } 244 | --------------------------------------------------------------------------------