Skip to content

Commit

Permalink
AppImage: AppImage builder
Browse files Browse the repository at this point in the history
  • Loading branch information
mensinda committed Aug 5, 2021
1 parent a450f8c commit 2d84e5a
Show file tree
Hide file tree
Showing 26 changed files with 1,307 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ __pycache__
.DS_Store
*~
*.swp
meson.runtime
packagecache
/MANIFEST
/build
/appimage
/dist
/meson.egg-info

Expand Down
18 changes: 18 additions & 0 deletions mesonbuild/msetup.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,16 @@
import sys, stat
import datetime
import os.path
import os
import platform
import cProfile as profile
import argparse
import tempfile
import shutil
import glob
import subprocess

from pathlib import Path

from . import environment, interpreter, mesonlib
from . import build
Expand Down Expand Up @@ -74,6 +78,20 @@ def __init__(self, options: argparse.Namespace) -> None:
options.sourcedir,
options.reconfigure,
options.wipe)
# Detect if we are running from the AppImage runtime. Then self-extract
# and relaunch with the extracted binaries
if 'APPIMAGE' in os.environ:
rc = subprocess.run([os.environ['APPIMAGE'], '--runtime-setup', self.build_dir]).returncode
if rc == 0:
del os.environ['APPIMAGE'] # avoid infinite loops
del os.environ['APPDIR']
meson_exe = Path(self.build_dir) / 'meson-runtime' / 'fakebin' / 'meson'
meson_exe = meson_exe.resolve()
os.execve(meson_exe.as_posix(), [meson_exe.as_posix(), *sys.argv[1:]], os.environ)
# execve never returns. See `man 3 exec`.

mlog.warning('Failed to self extract', mlog.bold(os.environ['APPIMAGE']), 'to', mlog.bold(options.builddir), f'(rc = {rc})')

if options.wipe:
# Make a copy of the cmd line file to make sure we can always
# restore that file if anything bad happens. For example if
Expand Down
9 changes: 9 additions & 0 deletions packaging/appimage/apprun/.clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
AlignConsecutiveAssignments: 'true'
AlignConsecutiveDeclarations: 'true'
AlignOperands: 'true'
AlignTrailingComments: 'true'
PointerAlignment: Left
UseTab: Never

...
2 changes: 2 additions & 0 deletions packaging/appimage/apprun/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/*build*
!meson.build
93 changes: 93 additions & 0 deletions packaging/appimage/apprun/common.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#include "common.h"

#include <libgen.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int g_verbose;

void info_autofill_paths(AppRunInfo_t* info, const char* exe_name) {
if (!info) {
DIE("invalid argument to info_autofill_paths");
}

if (!info->appdir && exe_name) {
// First sanity check that we are run in an environment set by AppRun
info->appdir = getenv("APPDIR");
if (!info->appdir) {
LOG("Wrapper %s run without AppRun", exe_name);
info->appdir = dirname(realpath("/proc/self/exe", NULL));

// Calculate the appdir root
int components = 1;
for (const char* c = FAKEBIN; *c; c++) {
if (*c == '/') {
components++;
}
}

for (int i = 0; i < components; i++) {
info->appdir = dirname(info->appdir);
}
}
}

if (!info->appdir) {
DIE("info->appdir was not set or could not be computed");
}

info->path = absolute(info, FAKEBIN);
info->ld_linux = absolute(info, "usr/lib/ld-linux.so");
info->pythonhome = absolute(info, "usr");

if (exe_name) {
char* bin_dir = absolute(info, "usr/bin");
info->exe_path = absolute_raw(bin_dir, exe_name);
free(bin_dir);
}
}

void logArgs(char** args) {
if (!g_verbose) {
return;
}

int counter = 0;
printf("\nArguments:\n");
counter = 0;
while (1) {
if (!args[counter]) {
break;
}
printf(" %2d: %s\n", counter, args[counter]);
counter++;
}

printf("\n");
fflush(stdout);
}

char* absolute_raw(const char *base, const char* relpath) {
const size_t absLen = strlen(base) + strlen(relpath) + 2;
char* abs = malloc(sizeof(char) * absLen);
snprintf(abs, absLen, "%s/%s", base, relpath);
return abs;
}

char* absolute(AppRunInfo_t* info, const char* relpath) {
return absolute_raw(info->appdir, relpath);
}

void envPrepend(const char* var, const char* val) {
char* curr = getenv(var);
if (!curr) {
setenv(var, val, 1);
return;
}

const size_t len = strlen(var) + strlen(val) + strlen(curr) + 3;
char* res = malloc(sizeof(char) * len);
snprintf(res, len, "%s=%s:%s", var, val, curr);
putenv(res);
}
42 changes: 42 additions & 0 deletions packaging/appimage/apprun/common.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#pragma once

// Some global defines to enable functions
#define _XOPEN_SOURCE 500
#define _DEFAULT_SOURCE
#define _BSD_SOURCE
#define _GNU_SOURCE

extern int g_verbose;

#define LOG(fmt, ...) \
if (g_verbose) { \
printf(fmt "\n", ##__VA_ARGS__); \
fflush(stdout); \
}

#define DIE(fmt, ...) \
{ \
fprintf(stderr, "\x1b[31;1mFATAL ERROR:\x1b[0;1m " fmt "\x1b[0m\n", \
##__VA_ARGS__); \
fflush(stderr); \
exit(1); \
}

typedef struct AppRunInfo {
char* appdir;
char* appimage_path;

// basic path inf
char* exe_path;
char* path;
char* ld_linux;
char* pythonhome;
} AppRunInfo_t;

void info_autofill_paths(AppRunInfo_t* inf, const char* exe_name);

char* absolute_raw(const char *base, const char* relpath);
char* absolute(AppRunInfo_t* inf, const char* relpath);
void envPrepend(const char* var, const char* val);

void logArgs(char** args);
42 changes: 42 additions & 0 deletions packaging/appimage/apprun/meson.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
project('apprun', ['c'],
default_options: [
'warning_level=3',
'optimization=s',
'werror=true',
'debug=false',
'strip=true',
'b_lto=true',
'prefix=/',
],
version: '1.0',
)

fakebin_dir = 'fakebin'
c_flags = [
'-Wno-pedantic',
'-DAPPRUN_VERSION="@0@"'.format(meson.project_version()),
'-DFAKEBIN="@0@"'.format(fakebin_dir),
]
link_flags = ['-static']

common = static_library('common', ['common.c'], c_args : c_flags)

wrappers = {
'meson': ['-DIS_PYTHON_SCRIPT=1'],
'ninja': [],
#'cmake': [],
'pkgconf': [],
'python3': [],
}

foreach name, extra_flags : wrappers
base_flags = ['-DREAL_EXE="' + name + '"']

executable(name, ['wrapper.c'],
link_with: [common],
c_args: c_flags + base_flags + extra_flags,
link_args: link_flags,
install: true,
install_dir: fakebin_dir
)
endforeach
77 changes: 77 additions & 0 deletions packaging/appimage/apprun/wrapper.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#include "common.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

// Assume that IS_PYTHON_SCRIPT is either 0 or 1
#ifndef IS_PYTHON_SCRIPT
#define IS_PYTHON_SCRIPT 0
#endif

#ifndef STATICALLY_LINKED
#define STATICALLY_LINKED 0
#endif

int main(int argc, char* argv[]) {
AppRunInfo_t info;
char* args[argc + 3];
int counter;

g_verbose = 0;

memset(&info, 0, sizeof(info));
memset(&args, 0, sizeof(args));

// Check for verbose output
char* verbose = getenv("VERBOSE");
if (verbose && verbose[0] != '0') {
g_verbose = 1;
}

info_autofill_paths(&info, REAL_EXE);

LOG("Meson exe wrapper " APPRUN_VERSION);
LOG("Running " REAL_EXE);
LOG("Extracted AppDir: %s", info.appdir);
LOG("Real exe location: %s", info.exe_path);
LOG("PATH fragment: %s", info.path);
LOG("Is Python script: %d", IS_PYTHON_SCRIPT);
LOG("Statically linked: %d", STATICALLY_LINKED);

// Set the commandline arguments for exeve
// Again, IS_PYTHON_SCRIPT and STATICALLY_LINKED must be either 0 or 1
counter = 2 + IS_PYTHON_SCRIPT - STATICALLY_LINKED;
for (int i = 1; i < argc; i++) {
args[counter++] = argv[i];
}

args[counter++] = NULL;

#if IS_PYTHON_SCRIPT
args[0] = info.ld_linux;
args[1] = absolute(&info, "usr/bin/python3");
args[2] = info.exe_path;
#elif STATICALLY_LINKED
args[0] = info.exe_path;
#else
args[0] = info.ld_linux;
args[1] = info.exe_path;
#endif

// Set the env vars
envPrepend("PATH", info.path);
setenv("PYTHONHOME", info.pythonhome, 1);
putenv("PYTHONDONTWRITEBYTECODE=1");

logArgs(args);

if (execv(args[0], args) != 0) {
DIE("execv failed");
}

// We are technically leaking memory here, but all memory is freed once the
// program exits anyway...
return 0;
}
Loading

0 comments on commit 2d84e5a

Please sign in to comment.