From 81cbc3fa15e98daf50769bd343f75b8edbd71f08 Mon Sep 17 00:00:00 2001 From: Brendan Szymanski Date: Wed, 7 Nov 2018 21:33:36 -0500 Subject: [PATCH] Flatpak support (#4383) * Initial flatpak support * Fix compatibility list directory * Hard-code SSH mount location * Add workaround documentation * Change SSH repo directory * Change SSH repo directory (again) * Fix variable name * Remove temporary testing branch placeholder * Use a flatpak-specific docker image * Enable network access during the flatpak build so we can download compatibility list the right way * Fix flatpak tag support * Fix typo * Use cloned git for the build * Change SSH repo location * Disable shallow git cloning, needed for tagged building --- .gitignore | 4 + .travis.yml | 12 ++ .travis/linux-flatpak/build.sh | 4 + .travis/linux-flatpak/deps.sh | 4 + .travis/linux-flatpak/docker.sh | 35 +++++ .travis/linux-flatpak/finish.sh | 9 ++ .travis/linux-flatpak/generate-data.sh | 142 ++++++++++++++++++++ .travis/linux-flatpak/travis-ci-flatpak.env | 9 ++ keys.tar.enc | Bin 0 -> 10256 bytes 9 files changed, 219 insertions(+) create mode 100755 .travis/linux-flatpak/build.sh create mode 100755 .travis/linux-flatpak/deps.sh create mode 100755 .travis/linux-flatpak/docker.sh create mode 100755 .travis/linux-flatpak/finish.sh create mode 100644 .travis/linux-flatpak/generate-data.sh create mode 100644 .travis/linux-flatpak/travis-ci-flatpak.env create mode 100644 keys.tar.enc diff --git a/.gitignore b/.gitignore index 9e11b5d754..37591363a2 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,7 @@ Thumbs.db # Python files *.pyc + +# Flatpak generated files +.flatpak-builder/ +repo/ diff --git a/.travis.yml b/.travis.yml index 54b7a90919..402c9538eb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -61,6 +61,18 @@ matrix: script: "./.travis/linux-mingw/build.sh" after_success: "./.travis/linux-mingw/upload.sh" cache: ccache + - if: repo =~ ^.*\/(citra-canary|citra-nightly)$ AND tag IS present + git: + depth: false + os: linux + env: NAME="flatpak build" + sudo: required + dist: trusty + services: docker + cache: ccache + install: "./.travis/linux-flatpak/deps.sh" + script: "./.travis/linux-flatpak/build.sh" + after_script: "./.travis/linux-flatpak/finish.sh" deploy: provider: releases diff --git a/.travis/linux-flatpak/build.sh b/.travis/linux-flatpak/build.sh new file mode 100755 index 0000000000..91fba01aac --- /dev/null +++ b/.travis/linux-flatpak/build.sh @@ -0,0 +1,4 @@ +#!/bin/bash -ex +mkdir -p "$HOME/.ccache" +# Configure docker and call the script that generates application data and build scripts +docker run --env-file .travis/common/travis-ci.env --env-file .travis/linux-flatpak/travis-ci-flatpak.env -v $(pwd):/citra -v "$HOME/.ccache":/root/.ccache -v "$HOME/.ssh":/root/.ssh --privileged citraemu/build-environments:linux-flatpak /bin/bash -ex /citra/.travis/linux-flatpak/generate-data.sh diff --git a/.travis/linux-flatpak/deps.sh b/.travis/linux-flatpak/deps.sh new file mode 100755 index 0000000000..b8fd4974cf --- /dev/null +++ b/.travis/linux-flatpak/deps.sh @@ -0,0 +1,4 @@ +#!/bin/sh -ex + +# Download the docker image that contains flatpak build dependencies +docker pull citraemu/build-environments:linux-flatpak diff --git a/.travis/linux-flatpak/docker.sh b/.travis/linux-flatpak/docker.sh new file mode 100755 index 0000000000..31e2bb777a --- /dev/null +++ b/.travis/linux-flatpak/docker.sh @@ -0,0 +1,35 @@ +#!/bin/bash -ex + +# Converts "citra-emu/citra-nightly" to "citra-nightly" +REPO_NAME=$(echo $TRAVIS_REPO_SLUG | cut -d'/' -f 2) +CITRA_SRC_DIR="/citra" +BUILD_DIR="$CITRA_SRC_DIR/build" +REPO_DIR="$CITRA_SRC_DIR/repo" +STATE_DIR="$CITRA_SRC_DIR/.flatpak-builder" +KEYS_ARCHIVE="/tmp/keys.tar" +SSH_DIR="/upload" +SSH_KEY="/tmp/ssh.key" +GPG_KEY="/tmp/gpg.key" + +# Extract keys +openssl aes-256-cbc -K $FLATPAK_ENC_K -iv $FLATPAK_ENC_IV -in "$CITRA_SRC_DIR/keys.tar.enc" -out "$KEYS_ARCHIVE" -d +tar -C /tmp -xvf $KEYS_ARCHIVE + +# Configure SSH keys +eval "$(ssh-agent -s)" +chmod -R 600 "$HOME/.ssh" +chown -R root "$HOME/.ssh" +chmod 600 "$SSH_KEY" +ssh-add "$SSH_KEY" +echo "[$FLATPAK_SSH_HOSTNAME]:$FLATPAK_SSH_PORT,[$(dig +short $FLATPAK_SSH_HOSTNAME)]:$FLATPAK_SSH_PORT $FLATPAK_SSH_PUBLIC_KEY" > ~/.ssh/known_hosts + +# Configure GPG keys +gpg2 --import "$GPG_KEY" + +# Mount our flatpak repository +mkdir -p "$REPO_DIR" +sshfs "$FLATPAK_SSH_USER@$FLATPAK_SSH_HOSTNAME:$SSH_DIR" "$REPO_DIR" -C -p "$FLATPAK_SSH_PORT" -o IdentityFile="$SSH_KEY" + +# Build the citra flatpak +flatpak-builder -v --jobs=4 --ccache --force-clean --state-dir="$STATE_DIR" --gpg-sign="$FLATPAK_GPG_PUBLIC_KEY" --repo="$REPO_DIR" "$BUILD_DIR" "/tmp/org.citra.$REPO_NAME.json" +flatpak build-update-repo "$REPO_DIR" -v --generate-static-deltas --gpg-sign="$FLATPAK_GPG_PUBLIC_KEY" diff --git a/.travis/linux-flatpak/finish.sh b/.travis/linux-flatpak/finish.sh new file mode 100755 index 0000000000..c9f0c07311 --- /dev/null +++ b/.travis/linux-flatpak/finish.sh @@ -0,0 +1,9 @@ +#!/bin/bash -ex + +CITRA_SRC_DIR="/citra" +REPO_DIR="$CITRA_SRC_DIR/repo" + +# When the script finishes, unmount the repository and delete sensitive files, +# regardless of whether the build passes or fails +umount "$REPO_DIR" +rm -rf "$REPO_DIR" "/tmp/*" diff --git a/.travis/linux-flatpak/generate-data.sh b/.travis/linux-flatpak/generate-data.sh new file mode 100644 index 0000000000..d8ca90aab5 --- /dev/null +++ b/.travis/linux-flatpak/generate-data.sh @@ -0,0 +1,142 @@ +#!/bin/bash -ex +# This script generates the appdata.xml and org.citra.$REPO_NAME.json files +# needed to define application metadata and build citra depending on what version +# of citra we're building (nightly or canary) + +# Converts "citra-emu/citra-nightly" to "citra-nightly" +REPO_NAME=$(echo $TRAVIS_REPO_SLUG | cut -d'/' -f 2) +# Converts "citra-nightly" to "Citra Nightly" +REPO_NAME_FRIENDLY=$(echo $REPO_NAME | sed -e 's/-/ /g' -e 's/\b\(.\)/\u\1/g') + +# Generate the correct appdata.xml for the version of Citra we're building +cat > /tmp/appdata.xml < + + org.citra.$REPO_NAME.desktop + $REPO_NAME_FRIENDLY + Nintendo 3DS emulator + CC0-1.0 + GPL-2.0 + +

Citra is an experimental open-source Nintendo 3DS emulator/debugger written in C++. It is written with portability in mind, with builds actively maintained for Windows, Linux and macOS.

+

Citra emulates a subset of 3DS hardware and therefore is useful for running/debugging homebrew applications, and it is also able to run many commercial games! Some of these do not run at a playable state, but we are working every day to advance the project forward. (Playable here means compatibility of at least "Okay" on our game compatibility list.)

+
+ https://citra-emu.org/ + https://citra-emu.org/donate/ + https://github.com/citra-emu/citra/issues + https://citra-emu.org/wiki/faq/ + https://citra-emu.org/wiki/home/ + https://raw.githubusercontent.com/citra-emu/citra-web/master/site/static/images/screenshots/01-Super%20Mario%203D%20Land.jpg + https://raw.githubusercontent.com/citra-emu/citra-web/master/site/static/images/screenshots/02-Mario%20Kart%207.jpg + https://raw.githubusercontent.com/citra-emu/citra-web/master/site/static/images/screenshots/28-The%20Legend%20of%20Zelda%20Ocarina%20of%20Time%203D.jpg + https://raw.githubusercontent.com/citra-emu/citra-web/master/site/static/images/screenshots/35-Pok%C3%A9mon%20ORAS.png + + Games + Emulator + +
+EOF + +# Generate the citra flatpak manifest, appending certain variables depending on +# whether we're building nightly or canary. +cat > /tmp/org.citra.$REPO_NAME.json <> /app/share/applications/citra.desktop", + "install -Dm644 ../dist/citra.svg /app/share/icons/hicolor/scalable/apps/citra.svg", + "install -Dm644 ../dist/icon.png /app/share/icons/hicolor/512x512/apps/citra.png", + "mv /app/share/mime/packages/citra.xml /app/share/mime/packages/org.citra.$REPO_NAME.xml", + "sed 's/citra/org.citra.citra-nightly/g' -i /app/share/mime/packages/org.citra.$REPO_NAME.xml", + "install -m644 --target-directory=/app/lib /usr/lib/sdk/gcc7/lib/libstdc++.so*" + ], + "sources": [ + { + "type": "git", + "url": "https://github.com/citra-emu/$REPO_NAME.git", + "branch": "$TRAVIS_BRANCH", + "disable-shallow-clone": true + }, + { + "type": "file", + "path": "/tmp/appdata.xml" + } + ] + } + ] +} +EOF + +# Call the script to build citra +/bin/bash -ex /citra/.travis/linux-flatpak/docker.sh diff --git a/.travis/linux-flatpak/travis-ci-flatpak.env b/.travis/linux-flatpak/travis-ci-flatpak.env new file mode 100644 index 0000000000..0823d91c83 --- /dev/null +++ b/.travis/linux-flatpak/travis-ci-flatpak.env @@ -0,0 +1,9 @@ +# Flatpak specific environment variables +FLATPAK_ENC_IV +FLATPAK_ENC_K +FLATPAK_GPG_PUBLIC_KEY +FLATPAK_SSH_HOSTNAME +FLATPAK_SSH_LOCATION +FLATPAK_SSH_PORT +FLATPAK_SSH_PUBLIC_KEY +FLATPAK_SSH_USER diff --git a/keys.tar.enc b/keys.tar.enc new file mode 100644 index 0000000000000000000000000000000000000000..6a8c1484e5720cc05baa3892d97c41e940587b09 GIT binary patch literal 10256 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