Add linux packaging

This commit is contained in:
Nikita Lisitsa 2023-07-18 20:23:34 +03:00
parent 54b78e6c66
commit b33ef168ea
13 changed files with 433 additions and 35 deletions

View file

@ -0,0 +1,21 @@
<?xml version='1.0'?>
<manifest xmlns:a='http://schemas.android.com/apk/res/android' package='psemek.app' a:versionCode='0' a:versionName='0'>
<uses-sdk
a:minSdkVersion="26"
a:targetSdkVersion="34"/>
<uses-feature
a:glEsVersion="0x00030002"
a:required="true"/>
<application
a:label='APPLICATION_NAME'>
<activity
a:name='psemek.app.MainActivity'
a:exported="true"
a:screenOrientation="landscape">
<intent-filter>
<category a:name='android.intent.category.LAUNCHER'/>
<action a:name='android.intent.action.MAIN'/>
</intent-filter>
</activity>
</application>
</manifest>

Binary file not shown.

View file

@ -0,0 +1,94 @@
package psemek.app;
import android.app.Activity;
import android.app.ActionBar;
import android.content.Context;
import android.opengl.GLSurfaceView;
import android.opengl.GLSurfaceView.Renderer;
import android.view.WindowInsetsController;
import android.view.WindowInsets.Type;
import android.view.MotionEvent;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.opengl.GLES30;
import android.os.Bundle;
import java.lang.System;
public class MainActivity extends Activity {
class RendererImpl implements Renderer {
@Override
public void onSurfaceCreated(GL10 gl10, EGLConfig config) {
MainActivity.this.nativeApp.init();
}
@Override
public void onSurfaceChanged(GL10 gl10, int width, int height) {
MainActivity.this.nativeApp.resize(width, height);
}
@Override
public void onDrawFrame(GL10 gl10) {
MainActivity.this.nativeApp.drawFrame();
}
}
class ViewImpl extends GLSurfaceView {
private final RendererImpl renderer;
public ViewImpl(Context context) {
super(context);
setEGLContextClientVersion(3);
setEGLConfigChooser(8, 8, 8, 8, 24, 8);
renderer = new RendererImpl();
setRenderer(renderer);
setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
}
@Override
public boolean onTouchEvent(MotionEvent e) {
if (e.getAction() == MotionEvent.ACTION_DOWN) {
MainActivity.this.nativeApp.touch((int)e.getX(), (int)e.getY());
}
return true;
}
}
private PsemekApplication nativeApp;
private ViewImpl view;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
System.loadLibrary("boost_random");
System.loadLibrary("TARGET_NAME");
ActionBar actionBar = getActionBar();
if (actionBar != null) {
actionBar.hide();
}
nativeApp = new PsemekApplication(this.getAssets());
view = new ViewImpl(this);
setContentView(view);
WindowInsetsController windowInsetsController = view.getWindowInsetsController();
if (windowInsetsController != null) {
windowInsetsController.hide(Type.systemBars());
windowInsetsController.setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
}
}
@Override
protected void onDestroy() {
nativeApp.destroy();
super.onDestroy();
}
}

View file

@ -0,0 +1,136 @@
package psemek.app;
import android.util.Log;
import android.content.res.AssetManager;
import android.media.AudioTrack;
import android.media.AudioAttributes;
import android.media.AudioFormat;
import java.io.InputStream;
import java.io.IOException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.Arrays;
import java.lang.Thread;
public class PsemekApplication {
private AudioTrack audioTrack;
private Thread audioThread;
private long nativeApp;
private static native void setupLogging();
private static native void setAssetManager(AssetManager assetManager);
private static native long createNativeApp();
private static native void destroyNativeApp(long ptr);
private static native void resizeNative(long ptr, int width, int height);
private static native void touchNative(long ptr, int x, int y);
private static native void drawFrameNative(long ptr);
private static native int audioFrequencyNative();
private static native int audioGetSamples(float buffer[], int sampleOffset, int sampleCount);
private class StreamEventCallbackImpl extends AudioTrack.StreamEventCallback {
private float buffer[];
public StreamEventCallbackImpl() {
super();
buffer = new float[1024];
}
@Override
public void onDataRequest(AudioTrack track, int sizeInFrames) {
Log.e("psemek", "Requested audio " + sizeInFrames);
int sizeInSamples = sizeInFrames * 2;
if (buffer.length < sizeInSamples) {
buffer = Arrays.copyOf(buffer, sizeInSamples);
}
int samples = PsemekApplication.audioGetSamples(buffer, 0, buffer.length);
track.write(buffer, 0, samples, AudioTrack.WRITE_BLOCKING);
}
}
private class AudioThreadImpl extends Thread {
private float buffer[];
public AudioThreadImpl(int bufferSizeInFrames) {
super("audio");
buffer = new float[bufferSizeInFrames * 2];
}
@Override
public void run() {
while (true) {
int samples = PsemekApplication.audioGetSamples(buffer, 0, buffer.length);
PsemekApplication.this.audioTrack.write(buffer, 0, samples, AudioTrack.WRITE_BLOCKING);
try {
Thread.sleep(1);
}
catch (InterruptedException e) {}
}
}
}
public PsemekApplication(AssetManager assetManager) {
setupLogging();
setAssetManager(assetManager);
AudioAttributes audioAttributes = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_GAME)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build();
AudioFormat audioFormat = new AudioFormat.Builder()
.setEncoding(AudioFormat.ENCODING_PCM_FLOAT)
.setSampleRate(audioFrequencyNative())
.setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
.build();
int bufferSize = AudioTrack.getMinBufferSize(audioFrequencyNative(), AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_FLOAT);
audioTrack = new AudioTrack.Builder()
.setAudioAttributes(audioAttributes)
.setAudioFormat(audioFormat)
.setBufferSizeInBytes(bufferSize)
.setTransferMode(AudioTrack.MODE_STREAM)
.setPerformanceMode(AudioTrack.PERFORMANCE_MODE_LOW_LATENCY)
.build();
audioThread = new AudioThreadImpl(audioTrack.getBufferCapacityInFrames());
audioThread.start();
audioTrack.play();
}
public void init() {
nativeApp = createNativeApp();
}
public void resize(int width, int height) {
resizeNative(nativeApp, width, height);
}
public void touch(int x, int y)
{
touchNative(nativeApp, x, y);
}
public void drawFrame() {
if (nativeApp != 0) {
drawFrameNative(nativeApp);
}
}
public void destroy() {
audioThread.stop();
if (nativeApp != 0)
destroyNativeApp(nativeApp);
nativeApp = 0;
}
}

View file

@ -41,7 +41,7 @@ namespace psemek::app
std::unique_ptr<io::istream> open_resource(std::filesystem::path const & relative_path)
{
log::error() << "Opening resource " << relative_path;
log::info() << "Opening resource " << relative_path;
auto asset = AAssetManager_open(assetManager, relative_path.c_str(), AASSET_MODE_STREAMING);
if (!asset)

View file

@ -30,7 +30,13 @@ function(psemek_package_output_path target outvar)
message(FATAL "psemek_package_output_path must only be called during packaging")
endif()
set(${outvar} "${CMAKE_CURRENT_LIST_DIR}/${PSEMEK_PACKAGE_OUTPUT_PATH}/${target}${PSEMEK_PACKAGE_VERSION_SUFFIX}-${PSEMEK_PACKAGE_SUFFIX}.zip" PARENT_SCOPE)
if(ANDROID)
set(_PACKAGE_EXTENSION apk)
else()
set(_PACKAGE_EXTENSION zip)
endif()
set(${outvar} "${CMAKE_CURRENT_LIST_DIR}/${PSEMEK_PACKAGE_OUTPUT_PATH}/${target}${PSEMEK_PACKAGE_VERSION_SUFFIX}-${PSEMEK_PACKAGE_SUFFIX}.${_PACKAGE_EXTENSION}" PARENT_SCOPE)
endfunction()
function(psemek_add_executable_impl target is_application)
@ -71,12 +77,17 @@ function(psemek_add_executable_impl target is_application)
)
endif()
if(NOT ANDROID)
psemek_package_output_path(${target} _OUTPUT_PATH)
if(NOT ANDROID)
add_custom_command(TARGET ${target} POST_BUILD
COMMAND echo Packaging target ${target} into ${_OUTPUT_PATH}
COMMAND zip -v "${_OUTPUT_PATH}" -j $<TARGET_FILE:${target}> ${PSEMEK_PACKAGE_COPY_FILES}
COMMAND echo Packaged target ${target} into ${_OUTPUT_PATH}
)
else()
add_custom_command(TARGET ${target} POST_BUILD
COMMAND ${PSEMEK_PACKAGE_HELPER} "${target}" "${PSEMEK_APPLICATION_NAME}" "${_OUTPUT_PATH}"
)
endif()
endif()
@ -151,10 +162,11 @@ function(psemek_package_files target)
if(PSEMEK_PACKAGE_MODE)
if(PSEMEK_PACKAGE_TARGET)
if(ANDROID)
add_custom_command(TARGET ${target} POST_BUILD
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
COMMAND ${PSEMEK_COPY_FILES} ${ARGN}
)
else()
psemek_package_output_path(${target} _OUTPUT_PATH)
add_custom_command(TARGET ${target} POST_BUILD
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
COMMAND zip -v "${_OUTPUT_PATH}" -r ${ARGN}

View file

@ -0,0 +1,62 @@
FROM ubuntu:22.04
# Install tools
RUN apt-get update && apt-get upgrade -y
RUN apt-get install -y \
build-essential cmake git default-jre openjdk-19-jdk \
libxext-dev libgl-dev \
wget zip zstd \
libpng-dev libboost-all-dev \
libxi-dev libxrender-dev
# Set user
RUN useradd -u 1000 -U -d /home -M worker
RUN chown -R worker:worker /home /usr/local
USER worker
# Install android sdkmanager
RUN mkdir -v /home/sdk
WORKDIR /home/sdk
RUN wget https://dl.google.com/android/repository/commandlinetools-linux-9477386_latest.zip && unzip commandlinetools-linux-9477386_latest.zip && rm commandlinetools-linux-9477386_latest.zip
RUN mv -v cmdline-tools latest
RUN mkdir -v cmdline-tools
RUN mv -v latest cmdline-tools
# Install sdk
RUN yes | cmdline-tools/latest/bin/sdkmanager "build-tools;34.0.0" "platforms;android-34"
# Install ndk separately
RUN wget https://dl.google.com/android/repository/android-ndk-r26-beta1-linux.zip && unzip android-ndk-r26-beta1-linux.zip && rm android-ndk-r26-beta1-linux.zip
RUN mkdir -v ndk
RUN mv -v android-ndk-r26-beta1 ndk/26.0.10404224-beta1
# Env variables
ENV SDK_ROOT=/home/sdk
ENV BUILD_TOOLS_ROOT=${SDK_ROOT}/build-tools/34.0.0
ENV NDK_ROOT=${SDK_ROOT}/ndk/26.0.10404224-beta1
ENV PLATFORM_ROOT=${SDK_ROOT}/platforms/android-34
ENV PNG_ROOT=/home/png/install
ENV BOOST_ROOT=/home/boost/install
# Build libpng
RUN mkdir -v /home/png
WORKDIR /home/png
RUN git clone https://github.com/glennrp/libpng.git -b libpng16 --depth 1 source
RUN mkdir -v build install
RUN cmake -S source -B build -DCMAKE_INSTALL_PREFIX=install/arm64-v8a -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE="${NDK_ROOT}/build/cmake/android.toolchain.cmake" \
-DANDROID_USE_LEGACY_TOOLCHAIN_FILE=ON -DANDROID_PLATFORM=34 -DANDROID_STL=c++_shared -DANDROID_CPP_FEATURES="rtti exceptions" -DANDROID_ABI=arm64-v8a
RUN cmake --build build -t install -j
# Build boost
RUN mkdir -v /home/boost
WORKDIR /home/boost
RUN wget https://boostorg.jfrog.io/artifactory/main/release/1.82.0/source/boost_1_82_0.tar.gz && tar xvf boost_1_82_0.tar.gz && rm boost_1_82_0.tar.gz
RUN mv -v boost_1_82_0 source
RUN cd source && ./bootstrap.sh --with-libraries=random --prefix=../install/arm64-v8a
RUN mkdir -p build install
RUN echo "using clang : android : ${NDK_ROOT}/toolchains/llvm/prebuilt/linux-x86_64/bin/clang++ --target=aarch64-none-linux-android34 --sysroot=${NDK_ROOT}/toolchains/llvm/prebuilt/linux-x86_64/sysroot ;" > user-config.jam
RUN cd source && ./b2 toolset=clang-android target-os=android architecture=arm variant=release link=shared threading=single cxxflags=-fPIC --user-config=../user-config.jam --build-dir=../build install
# Finalize
WORKDIR /home
COPY package.sh package-helper.sh copy-files.sh ./

5
package/android/copy-files.sh Executable file
View file

@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -e
cp -rv "$@" /home/app/assets/

View file

@ -0,0 +1,12 @@
#!/usr/bin/env bash
set -e
if [ "$#" -ne 3 ]; then
echo "Usage: package-helper <target-name> <application-name> <apk-path>"
exit -1
fi
echo "$1" > /home/target-name
echo "$2" > /home/application-name
echo "$3" > /home/apk-name

57
package/android/package.sh Executable file
View file

@ -0,0 +1,57 @@
#!/usr/bin/env bash
set -e
mkdir build-host tools
cmake -S source -B build-host -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=tools -DPSEMEK_PACKAGE_MODE=ON -DPSEMEK_PACKAGE_HOST=ON -DPSEMEK_BACKEND=OFF
cmake --build build-host -t install -j
cp -r source/psemek/libs/android/app .
mkdir -p app/assets
mkdir -p build-target/arm64-v8a
cmake -S source -B build-target/arm64-v8a/build \
-DCMAKE_BUILD_TYPE=Release -DANDROID_USE_LEGACY_TOOLCHAIN_FILE=ON -DANDROID_PLATFORM=34 -DANDROID_STL=c++_shared -DANDROID_CPP_FEATURES="rtti exceptions" \
-DANDROID_ABI=arm64-v8a -DCMAKE_TOOLCHAIN_FILE="${NDK_ROOT}/build/cmake/android.toolchain.cmake" \
-DCMAKE_FIND_ROOT_PATH="${BOOST_ROOT}/arm64-v8a;${PNG_ROOT}/arm64-v8a;$(pwd)/tools" -DCMAKE_INSTALL_PREFIX="build-target/arm64-v8a/install" \
-DPSEMEK_BACKEND=ANDROID -DPSEMEK_PACKAGE_MODE=ON -DPSEMEK_PACKAGE_TARGET=ON -DPSEMEK_PACKAGE_TOOLS_PATH="" -DPSEMEK_PACKAGE_HELPER=$(pwd)/package-helper.sh -DPSEMEK_COPY_FILES=$(pwd)/copy-files.sh
cmake --build build-target/arm64-v8a/build -t install -j
mkdir -p app/lib/arm64-v8a
cp -v build-target/arm64-v8a/install/lib/*.so app/lib/arm64-v8a/
cp -v ${NDK_ROOT}/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/aarch64-linux-android/libc++_shared.so app/lib/arm64-v8a/
cp -v ${BOOST_ROOT}/arm64-v8a/lib/*.so app/lib/arm64-v8a/
cp -v ${PNG_ROOT}/arm64-v8a/lib/*.so app/lib/arm64-v8a/
cd app
cp ./AndroidManifest.xml.in ./AndroidManifest.xml
sed -i -e "s/APPLICATION_NAME/$(cat ../application-name)/g" ./AndroidManifest.xml
export TARGET_NAME=$(cat ../target-name)
export APK_NAME=$(cat ../apk-name)
cp src/psemek/app/MainActivity.java.in src/psemek/app/MainActivity.java
sed -i -e "s/TARGET_NAME/${TARGET_NAME}/g" src/psemek/app/MainActivity.java
mkdir -p dex bin
${BUILD_TOOLS_ROOT}/aapt package -f -m -J ./src -M ./AndroidManifest.xml -I ${PLATFORM_ROOT}/android.jar
javac -d obj -classpath src -classpath ${PLATFORM_ROOT}/android.jar src/psemek/app/*.java
${BUILD_TOOLS_ROOT}/d8 --output ./dex obj/psemek/app/*.class
${BUILD_TOOLS_ROOT}/aapt package -f -m -F ./bin/${TARGET_NAME}.unaligned.apk -M ./AndroidManifest.xml -I ${PLATFORM_ROOT}/android.jar
zip -r ./bin/${TARGET_NAME}.unaligned.apk assets
${BUILD_TOOLS_ROOT}/aapt add ./bin/${TARGET_NAME}.unaligned.apk lib/arm64-v8a/*
cd dex
${BUILD_TOOLS_ROOT}/aapt add ../bin/${TARGET_NAME}.unaligned.apk classes.dex
cd ..
${BUILD_TOOLS_ROOT}/zipalign -f 4 ./bin/${TARGET_NAME}.unaligned.apk "${APK_NAME}"
${BUILD_TOOLS_ROOT}/apksigner sign --ks psemek.keystore --ks-pass "pass:${PSEMEK_KEYSTORE_PASSWORD}" "${APK_NAME}"
echo "Packaged target ${TARGET_NAME} into ${APK_NAME}"

27
package/bin/psemek-package Executable file
View file

@ -0,0 +1,27 @@
#!/usr/bin/env bash
set -e
if [ "$#" -ne 2 ]; then
echo "Usage: psemek-package <platform> <project-dir>"
echo "Supported platforms:"
echo " linux"
echo " windows"
echo " android"
exit 0
fi
case ${1} in
linux) ;;
windows) ;;
android) ;;
*)
echo "Unknown platform: ${1}"
exit -1
esac
PROJECT_DIR=`realpath "${2}"`
docker run -u 1000 -v "${PROJECT_DIR}":/home/source -e PSEMEK_KEYSTORE_PASSWORD=${PSEMEK_KEYSTORE_PASSWORD} lisyarus/psemek:package-${1} /home/package.sh
echo Packaging finished

View file

@ -1,14 +0,0 @@
#!/usr/bin/env bash
set -e
if [ "$#" -ne 1 ]; then
echo "Usage: psemek-package-linux <project-dir>"
exit 0
fi
PROJECT_DIR=`realpath "${1}"`
docker run -u 1000 -v "${PROJECT_DIR}":/home/source lisyarus/psemek:package-linux /home/package.sh
echo Packaging finished

View file

@ -1,14 +0,0 @@
#!/usr/bin/env bash
set -e
if [ "$#" -ne 1 ]; then
echo "Usage: psemek-package-win <project-dir>"
exit 0
fi
PROJECT_DIR=`realpath "${1}"`
docker run -u 1000 -v "${PROJECT_DIR}":/home/source lisyarus/psemek:package-win /home/package.sh
echo Packaging finished