diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c2c6942 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,21 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 4 + +[*.md] +# double whitespace at end of line +# denotes a line break in Markdown +trim_trailing_whitespace = false + +[*.{json,yml,yaml,xml,json.tmpl}] +indent_size = 2 diff --git a/org.libretro.RetroArch.json.tmpl b/org.libretro.RetroArch.json.tmpl new file mode 100644 index 0000000..b51ba4d --- /dev/null +++ b/org.libretro.RetroArch.json.tmpl @@ -0,0 +1,207 @@ +{ + "app-id": "org.libretro.RetroArch", + "runtime": "org.kde.Platform", + "runtime-version": "5.15-21.08", + "sdk": "org.kde.Sdk", + "command": "retroarch", + "rename-desktop-file": "retroarch.desktop", + "rename-icon": "retroarch", + "finish-args": [ + "--socket=x11", + "--socket=wayland", + "--socket=pulseaudio", + "--share=ipc", + "--share=network", + "--device=all", + "--filesystem=host", + "--filesystem=home", + "--allow=multiarch", + "--talk-name=org.freedesktop.ScreenSaver", + "--talk-name=org.freedesktop.PowerManagement.Inhibit", + "--talk-name=org.freedesktop.login1", + "--filesystem=xdg-run/app/com.discordapp.Discord:create" + ], + "modules": [ + { + "name": "retroarch", + "config-opts": [ + "--enable-dbus" + ], + "make-args": [ + "GLOBAL_CONFIG_DIR=${FLATPAK_DEST}/etc" + ], + "sources": [ + { + "type": "git", + "url": "https://github.com/libretro/RetroArch.git", + "tag": "%%RA_VERSION_TAG%%" + }, + { + "type": "file", + "path": "org.libretro.RetroArch.appdata.xml" + }, + { + "type": "file", + "path": "retroarch.cfg" + } + ], + "post-install": [ + "mkdir -p ${FLATPAK_DEST}/share/icons/hicolor/scalable/apps/", + "mv ${FLATPAK_DEST}/share/pixmaps/retroarch.svg ${FLATPAK_DEST}/share/icons/hicolor/scalable/apps/", + "rmdir ${FLATPAK_DEST}/share/pixmaps/", + "mkdir -p ${FLATPAK_DEST}/etc", + "sed s:@prefix@:${FLATPAK_DEST}:g retroarch.cfg > ${FLATPAK_DEST}/etc/retroarch.cfg", + "mkdir -p ${FLATPAK_DEST}/share/appdata", + "cp org.libretro.RetroArch.appdata.xml ${FLATPAK_DEST}/share/appdata" + ], + "modules": [ + "libpng/libpng-1.6.35.json", + "nvidia-cg-toolkit/nvidia-cg-toolkit-3.1.0013.json", + "shared-modules/SDL/SDL-1.2.15.json", + "shared-modules/SDL/SDL_image-1.2.12.json", + "shared-modules/SDL/SDL_mixer-1.2.12.json", + "shared-modules/SDL/SDL_net-1.2.8.json", + "shared-modules/SDL/SDL_ttf-2.0.11.json", + "shared-modules/libusb/libusb.json", + "shared-modules/gudev/gudev.json", + "libbz2/libbz2-1.0.8.json", + "xrandr/xrandr-1.5.1.json", + "libaio/libaio-0.3.112.json", + "shared-modules/glu/glu-9.json", + "libdecor/libdecor-0.1.0.json" + ] + }, + { + "name": "retroarch-filters-video", + "subdir": "gfx/video_filters", + "make-install-args": [ + "PREFIX=${FLATPAK_DEST}" + ], + "sources": [ + { + "type": "git", + "url": "https://github.com/libretro/RetroArch.git", + "tag": "%%RA_VERSION_TAG%%" + } + ] + }, + { + "name": "retroarch-filters-audio", + "subdir": "libretro-common/audio/dsp_filters", + "make-install-args": [ + "PREFIX=${FLATPAK_DEST}" + ], + "sources": [ + { + "type": "git", + "url": "https://github.com/libretro/RetroArch.git", + "tag": "%%RA_VERSION_TAG%%" + } + ] + }, + { + "name": "retroarch-assets", + "make-install-args": [ + "PREFIX=${FLATPAK_DEST}" + ], + "sources": [ + { + "type": "git", + "url": "https://github.com/libretro/retroarch-assets.git", + "commit": "%%retroarch-assets_GIT_REF%%" + } + ] + }, + { + "name": "libretro-database", + "make-install-args": [ + "PREFIX=${FLATPAK_DEST}" + ], + "sources": [ + { + "type": "git", + "url": "https://github.com/libretro/libretro-database.git", + "commit": "%%libretro-database_GIT_REF%%" + } + ] + }, + { + "name": "libretro-core-info", + "make-install-args": [ + "PREFIX=${FLATPAK_DEST}" + ], + "sources": [ + { + "type": "git", + "url": "https://github.com/libretro/libretro-core-info.git", + "commit": "%%libretro-core-info_GIT_REF%%" + } + ] + }, + { + "name": "retroarch-joypad-autoconfig", + "make-install-args": [ + "PREFIX=${FLATPAK_DEST}" + ], + "sources": [ + { + "type": "git", + "url": "https://github.com/libretro/retroarch-joypad-autoconfig.git", + "commit": "%%retroarch-joypad-autoconfig_GIT_REF%%" + } + ] + }, + { + "name": "common-shaders", + "make-install-args": [ + "PREFIX=${FLATPAK_DEST}" + ], + "sources": [ + { + "type": "git", + "url": "https://github.com/libretro/common-shaders.git", + "commit": "%%common-shaders_GIT_REF%%" + } + ] + }, + { + "name": "slang-shaders", + "make-install-args": [ + "PREFIX=${FLATPAK_DEST}" + ], + "sources": [ + { + "type": "git", + "url": "https://github.com/libretro/slang-shaders.git", + "commit": "%%slang-shaders_GIT_REF%%" + } + ] + }, + { + "name": "glsl-shaders", + "make-install-args": [ + "PREFIX=${FLATPAK_DEST}" + ], + "sources": [ + { + "type": "git", + "url": "https://github.com/libretro/glsl-shaders.git", + "commit": "%%glsl-shaders_GIT_REF%%" + } + ] + }, + { + "name": "common-overlays", + "make-install-args": [ + "PREFIX=${FLATPAK_DEST}" + ], + "sources": [ + { + "type": "git", + "url": "https://github.com/libretro/common-overlays.git", + "commit": "%%common-overlays_GIT_REF%%" + } + ] + } + ] +} diff --git a/release.sh b/release.sh new file mode 100755 index 0000000..0fc2167 --- /dev/null +++ b/release.sh @@ -0,0 +1,227 @@ +#!/bin/sh +# SPDX-License-Identifier: Unlicense +APP_ID="org.libretro.RetroArch" +MANIFEST_FILE="$APP_ID.json" +MANIFEST_TMPL_FILE="$MANIFEST_FILE.tmpl" +APPSTREAM_FILE="$APP_ID.appdata.xml" +CMD="$0" + +print_help_text() { + cat << EOF +release.sh - Release a new version of this project + +Usage: $CMD [OPTION...] VERSION + +Options: + --dry-run Only output what would be done, but don't actually do + anything + --git-tag=TAG Git tag to pull from libretro repositories. In case this + distribution caries a different versioning scheme or needs a + rebuild to fix downstream specific issues + (eg. upstream v1.10.0 -> 1.10.0.1 downstream) + --no-push Don't push changes to remote git repository at the end + -v --verbose Output debug information (-vv very verbose) + -h --help Print this help text and exit + +Examples: + $CMD 1.10.0 + $CMD --git-tag=v1.10.0 1.10.0.hotfix.1 +EOF + + exit 0 +} + +log_error() { printf '%s\n' "$(date -I'seconds') ERROR $*" >&2; } +log_warn() { printf '%s\n' "$(date -I'seconds') WARN $*" >&2; } +log_info() { printf '%s\n' "$(date -I'seconds') INFO $*"; } +log_debug() { [ "$is_verbose" ] && printf '%s\n' "$(date -I'seconds') DEBUG $*"; } +log_trace() { [ "$is_vv" ] && printf '%s\n' "$(date -I'seconds') TRACE $*"; } +abort() { log_error "$*"; exit 1; } +unknown_option() { abort "Unknown option $*. Run $CMD --help"; } + +get_argv() { + [ "$argc" ] || argc=0 + argc=$(($argc + 1)) + + case "$argc" in + 1) next_version="$1" + next_version_tag="v$next_version" ;; + *) abort "Too many arguments. Run $CMD --help" ;; + esac +} + +read_arguments() { + for arg in "$@"; do + case "$arg" in + --dry-run) is_dry_run=1 ;; + --git-tag=*) git_tag="${arg#'--git-tag='}" ;; + --no-push) is_no_push=1 ;; + --verbose|-v) is_verbose=1 ;; + -vv) is_vv=1; is_verbose=1 ;; + --help|-h) print_help_text ;; + -*) unknown_option "$arg" ;; + *) get_argv "$arg" ;; + esac + done + + [ "$next_version" ] || abort "Missing VERSION argument. Run $CMD --help" + [ "$git_tag" ] || git_tag="$next_version_tag" + + log_debug "is_dry_run=$is_dry_run" + log_debug "is_no_push=$is_no_push" + log_debug "is_verbose=$is_verbose" + log_debug "is_vv=$is_vv" + log_debug "git_tag=$git_tag" + log_debug "next_version=$next_version" + log_debug "next_version_tag=$next_version_tag" +} + +# Escapes text for literal use inside regular expressions +regex_escape() { + printf '%s' "$(printf '%s' "$*" | sed -E 's/[+*?^$.[{}()|\/\\]|]/\\&/g')" +} + +validate_arguments() { + local version="$(regex_escape "$next_version")" + local pattern="]+\bversion=\"$version\"[^>]*>" + log_debug "validate_arguments() escaped version=$version" + log_debug "validate_arguments() escaped pattern=$pattern" + local version_exists="$(grep -E -c -m 1 "$pattern" "$APPSTREAM_FILE")" + log_debug "validate_arguments() version_exists=$version_exists" + + if [ "$version_exists" -gt 0 ]; then + if [ "$is_verbose" ]; then + local matches="$(grep -EnH "$pattern" "$APPSTREAM_FILE")" + log_debug "$matches" + fi + + abort "Version $next_version already exists in the AppStream file" + fi +} + +check_dependencies() { + for dep in "$@"; do + if ! [ -x "$(command -v "$dep")" ]; then + log_error "$dep is not installed" + missing=1 + fi + done + + if [ "$missing" ]; then + abort "Aborted due to missing dependencies. Make sure all dependencies are available in the PATH" + fi +} + +check_dependencies cat date git grep sed wget +read_arguments "$@" +validate_arguments + +# Escapes text for use as replacement string in sed's 's' command +sed_replacement_escape() { + printf '%s' "$(printf '%s' "$*" | sed -E 's/[\\|/&]/\\&/g')" +} + +# Escapes text for use in JSON string. +# Only do the minimum escaping for our use-case, not control characters. +json_string_escape() { + printf '%s' "$(printf '%s' "$*" | sed -E 's/[\\"]/\\&/g')" +} + +# Renders a template from a file with simple string substitution +render_tmpl() { + local tmpl_file="$1" + local RA_VERSION_TAG="$(sed_replacement_escape "$(json_string_escape "$2")")" + log_debug "render_tmpl() escaped RA_VERSION_TAG=$RA_VERSION_TAG" + local result="$(cat "$tmpl_file")" + local result="$(printf '%s' "$result" | sed -E "s/%%RA_VERSION_TAG%%/$RA_VERSION_TAG/g")" + + for repo in retroarch-assets libretro-database libretro-core-info retroarch-joypad-autoconfig \ + common-shaders slang-shaders glsl-shaders common-overlays; do + local commit_json=$(wget -q -O- --header='accept: application/json' \ + "https://api.github.com/repos/libretro/$repo/commits?per_page=1&page=1") + local commit_sha=$(printf '%s' "$commit_json" | grep -E -o -m 1 '"sha"\s*:\s*"[0-9a-fA-F]{40}"') + local commit_sha=${commit_sha%'"'} + local commit_sha=${commit_sha#'"sha"'*:*'"'} + [ "$commit_sha" ] || abort "render_tmpl() null commit_sha for repo $repo. May have reached GitHub's rate limit. Try again in one hour" + log_debug "render_tmpl() commit_sha=$commit_sha repo=$repo" + local result="$(printf '%s' "$result" | sed -E "s/%%${repo}_GIT_REF%%/$commit_sha/g")" + done + + [ "$result" ] || abort "render_tmpl() result is null" + render_tmpl_result="$result" +} + +log_info "Rendering template from $MANIFEST_TMPL_FILE with VERSION_TAG=$git_tag to file $MANIFEST_FILE" +render_tmpl "$MANIFEST_TMPL_FILE" "$git_tag" + +log_trace "CMD: printf '%s' \"${render_tmpl_result}\" > \"$MANIFEST_FILE\"" +if [ "$is_dry_run" ]; then + log_info "DRY-RUN CMD: printf '%s' \"\$render_tmpl_result\" > \"$MANIFEST_FILE\"" +else + printf '%s\n' "$render_tmpl_result" > "$MANIFEST_FILE" || abort "Failed to write file $MANIFEST_FILE" +fi + +# Escapes text for use in XML attributes +xml_attr_escape() { + local result="$*" + local result="$(printf '%s' "$result" | sed -E 's/&/\&/g')" # & must be first + local result="$(printf '%s' "$result" | sed -E 's/"/\"/g')" + local result="$(printf '%s' "$result" | sed -E 's//\>/g')" + local result="$(printf '%s' "$result" | sed -E "s/'/\'/g")" + printf '%s' "$result" +} + +# Adds a new release tag to AppStream metadata xml +render_appstream() { + local file="$1" + local version="$(sed_replacement_escape "$(xml_attr_escape "$2")")" + local date="$(sed_replacement_escape "$(xml_attr_escape "$3")")" + log_debug "render_appstream() escaped version=$version" + log_debug "render_appstream() escaped date=$date" + local pattern='^\s*$' + local result="$(cat "$file")" + # Use `1,/$pattern/` to match only once. Not strictly necessary here, but safer + local result="$(printf '%s' "$result" | sed -E "1,/$pattern/ s/$pattern/&\n\ + /")" + + [ "$result" ] || abort "render_appstream() result is null" + render_appstream_result="$result" +} + +current_date="$(date -I'date')" +log_info "Adding release version=\"$next_version\" date=\"$current_date\" to file $APPSTREAM_FILE" +render_appstream "$APPSTREAM_FILE" "$next_version" "$current_date" + +log_trace "CMD: printf '%s' \"${render_appstream_result}\" > \"$APPSTREAM_FILE\"" +if [ "$is_dry_run" ]; then + log_info "DRY-RUN CMD: printf '%s' \"\$render_appstream_result\" > \"$APPSTREAM_FILE\"" +else + printf '%s\n' "$render_appstream_result" > "$APPSTREAM_FILE" || abort "Failed to write file $APPSTREAM_FILE" +fi + +log_info "Committing changes to git repository: \"Release $next_version_tag\"" +if [ "$is_dry_run" ]; then + log_info "DRY-RUN CMD: git commit -a -m \"Release $next_version_tag\"" +else + git commit -a -m "Release $next_version_tag" || abort "Failed to commit changes to git repository" +fi + +log_info "Creating git tag: $next_version_tag" +if [ "$is_dry_run" ]; then + log_info "DRY-RUN CMD: git tag \"$next_version_tag\"" +else + git tag "$next_version_tag" || abort "Failed to create git tag" +fi + +if [ ! "$is_no_push" ]; then + log_info "Pushing changes to remote git repository" + if [ "$is_dry_run" ]; then + log_info "DRY-RUN CMD: git push origin" + log_info "DRY-RUN CMD: git push origin \"refs/tags/${next_version_tag}:refs/tags/${next_version_tag}\"" + else + git push origin || abort "Failed to push commits to remote git repository" + git push origin "refs/tags/${next_version_tag}:refs/tags/${next_version_tag}" \ + || abort "Failed to push tag to remote git repository" + fi +fi