floooh / sokol-zig

Zig bindings for the sokol headers (https://github.com/floooh/sokol)
zlib License
341 stars 46 forks source link

sokol_defines.h: fix for Android #37

Closed 20kano closed 1 year ago

floooh commented 1 year ago

Did you manage to get something working on Android?

20kano commented 1 year ago

Yes. It worked just by modifying this part.

.
├── android_res
│   ├── drawable-hdpi
│   │   └── ic_launcher.png
│   ├── drawable-ldpi
│   │   └── ic_launcher.png
│   ├── drawable-mdpi
│   │   └── ic_launcher.png
│   └── drawable-xhdpi
│       └── ic_launcher.png
├── build.zig
└── src
    ├── sgl.zig
    └── sokol -> /link/to/sokol-zig/src/sokol

sgl.zig

//------------------------------------------------------------------------------
//  sgl.zig
//
//  sokol_gl.h / sokol.sgl sample program.
//------------------------------------------------------------------------------
const sokol = @import("sokol/sokol.zig");

// No modification.

export fn sokol_main() sapp.Desc {
    return .{
        .init_cb = init,
        .frame_cb = frame,
        .cleanup_cb = cleanup,
        .width = 512,
        .height = 512,
        .sample_count = 4,
        .icon = .{ .sokol_default = true },
        .window_title = "sgl.zig",
        .logger = .{ .func = slog.func },
    };
}

Only for my environment though..

build.zig

const std = @import("std");

pub fn build(b: *std.Build) void {
    build_android(b, "src/sgl.zig", .{
        .app_name = "SokolAndroidZig",
        .app_pkg = "com.example.sokolandroidzig",
        .lib_name = "sokolandroidzig"
    });
}

const AndroidBuildParams = struct {
    app_name: []const u8,
    lib_name: []const u8,
    app_pkg: []const u8,
    sdk: []const u8 = "/opt/android-sdk",
    api_level: []const u8 = "23",

    pub fn platform(comptime abp: AndroidBuildParams) []const u8 {
        return abp.sdk ++ "/platforms/android-" ++ abp.api_level ++ "/android.jar";
    }

    pub fn build_tools(comptime abp: AndroidBuildParams, comptime bin: []const u8) []const u8{
        return abp.sdk ++ "/build-tools/30.0.3/" ++ bin;
    }

    pub fn ndk(comptime abp: AndroidBuildParams) []const u8 {
        return  abp.sdk ++  "/ndk/25.1.8937393";
    }

    pub fn llvm(comptime abp: AndroidBuildParams) []const u8 {
        return abp.ndk() ++ "/toolchains/llvm/prebuilt/linux-x86_64";
    }

    pub fn ndk_sysroot(comptime abp: AndroidBuildParams) []const u8 {
        return abp.llvm() ++ "/sysroot";
    }

    pub fn cc(comptime abp: AndroidBuildParams) []const u8 {
        return abp.llvm() ++ "/bin/armv7a-linux-androideabi" ++ abp.api_level ++ "-clang";
    }

    pub fn cxx(comptime abp: AndroidBuildParams) []const u8 {
        return abp.llvm() ++ "/bin/armv7a-linux-androideabi" ++ abp.api_level ++ "-clang++";
    }

    pub fn ar(comptime abp: AndroidBuildParams) []const u8 {
        return  abp.llvm() ++ "/bin/llvm-ar";
    }

    pub const ANDROID_RES_DIR = "android_res/";
    pub const BUILD_DIR = "build_android";
    pub const BUILD_JAVA_GEN_DIR = BUILD_DIR ++ "/gen"; // R.java
    pub const BUILD_JAVA_OBJ_DIR = BUILD_DIR ++ "/obj";
    pub const BUILD_APK_DIR = BUILD_DIR ++ "/apk";
    pub const BUILD_APK_LIB_DIR = BUILD_APK_DIR ++ "/lib/armeabi-v7a";

    fn getTarget() std.zig.CrossTarget {
        return .{
            .cpu_arch = .arm,
            .os_tag = .linux,
            .abi = .android,
            .cpu_model = .baseline,
            .cpu_features_add = std.Target.arm.featureSet(&.{.v7a}),
        };
    }
};

fn build_android(
    b: *std.Build,
    root_source_file: []const u8,
    comptime abp: AndroidBuildParams
) void{
    const dir = std.fs.cwd();
    dir.makePath(AndroidBuildParams.BUILD_JAVA_GEN_DIR) catch unreachable;
    dir.makePath(AndroidBuildParams.BUILD_JAVA_OBJ_DIR) catch unreachable;
    dir.makePath(AndroidBuildParams.BUILD_APK_DIR) catch unreachable;
    dir.makePath(AndroidBuildParams.BUILD_APK_LIB_DIR) catch unreachable;

    compile(b, abp, root_source_file);
    const package_step = package(b, abp);
    const deploy_step = deploy(b, package_step, abp);

    const build_run = b.step("run", "install apk to device and start app.");
    build_run.dependOn(&deploy_step.step);
}

fn build_sokol(
    b: *std.Build,
    optimize: std.builtin.Mode,
    target: std.zig.CrossTarget,
    comptime prefix_path: []const u8,
    comptime android: AndroidBuildParams,
) *std.Build.CompileStep {
    var lib = b.addStaticLibrary(.{
        .name = "sokol",
        .optimize = optimize,
        .target = target,
    });
    const sokol_path = prefix_path ++ "/c/";
    // std.debug.print("sokol_path = {s}\n", .{sokol_path});
    const csources = [_][]const u8 {
        "sokol_app.c",
        "sokol_gfx.c",
        "sokol_gl.c",
        "sokol_log.c",
        "sokol_debugtext.c",
        // "sokol_fontstash.c",
    };
    lib.linkLibC();

    inline for (csources) |csrc| {
        lib.addCSourceFile(sokol_path ++ csrc, &[_][]const u8{
            "-DIMPL", "-D__ANDROID__", "-DSOKOL_GLES3", "-DSOKOL_DEBUG",
        });
    }
    // 'asm/types.h'
    lib.addIncludePath(comptime android.ndk_sysroot() ++ "/usr/include/arm-linux-androideabi");
    setAndroidLibC(b, lib, android.ndk_sysroot(), android.api_level);
    lib.linkSystemLibrary("android");
    lib.linkSystemLibrary("GLESv3");
    lib.linkSystemLibrary("EGL");
    lib.linkSystemLibrary("log");

    return lib;
}

fn setAndroidLibC(
    b: *std.Build,
    lib: *std.Build.CompileStep,
    comptime ndk_sysroot: []const u8,
    comptime api_level: []const u8
) void {
    const s = "include_dir=" ++ ndk_sysroot ++ "/usr/include\n" ++
        "sys_include_dir=" ++ ndk_sysroot ++ "/usr/include\n" ++
        "crt_dir=" ++ ndk_sysroot ++ "/usr/lib/arm-linux-androideabi/" ++ api_level ++ "\n" ++
        "msvc_lib_dir=\n" ++
        "kernel32_lib_dir=\n" ++
        "gcc_dir=\n";
    // std.debug.print("{s}\n",.{s});
    const fname = "android-" ++ api_level ++ "-armeabi.conf";
    const step = b.addWriteFile(fname, s);
    lib.setLibCFile(step.getFileSource(fname) orelse unreachable);
    lib.libc_file.?.addStepDependencies(&lib.step);
    lib.linkLibC();
}

fn compile(
    b: *std.Build,
    comptime abp: AndroidBuildParams,
    root_source_file: []const u8
) void {
    const mode = b.standardOptimizeOption(.{});
    const target = AndroidBuildParams.getTarget();
    // apk/lib/arm/xxxx.so はsheared-library
    const so = b.addSharedLibrary(.{
        .name = abp.lib_name,
        .root_source_file = .{.path = root_source_file},
        .optimize = mode,
        .target = target,       
    }); 

    setAndroidLibC(b, so, abp.ndk_sysroot(), abp.api_level);

    const sokol_lib = build_sokol(b, mode, target, "src/sokol", abp);
    so.linkLibrary(sokol_lib);

    so.install();
}

fn package(b: *std.Build, comptime abp: AndroidBuildParams) *std.Build.RunStep {
    const copy_lib = b.addSystemCommand(&[_][]const u8{
        "cp",
        b.fmt("zig-out/lib/lib{s}.so", .{abp.lib_name}),
        b.fmt("{s}/lib{s}.so", .{AndroidBuildParams.BUILD_APK_LIB_DIR, abp.lib_name})
    });
    copy_lib.step.dependOn(b.getInstallStep());

    const manifest_step = b.addWriteFile("AndroidManifest.xml", blk: {
        var buf = std.ArrayList(u8).init(b.allocator);
        errdefer buf.deinit();
        var writer = buf.writer();
        writer.print(
            \\<manifest xmlns:android="http://schemas.android.com/apk/res/android"
            \\  package="{s}"
            \\  android:versionCode="1"
            \\  android:versionName="1.0">
            \\  <uses-sdk android:minSdkVersion="{s}" android:targetSdkVersion="{s}"/>
            \\  <uses-feature android:glEsVersion="0x00030000" />
            \\  <application
            \\      android:label="{s}"
            \\      android:hasCode="false"
            \\      android:debuggable="true"
            \\      android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
            \\      <activity
            \\          android:name="android.app.NativeActivity"
            \\          android:label="{s}"
            \\          android:configChanges="orientation|screenSize|keyboard|keyboardHidden"
            \\          android:exported="true"
            \\          android:launchMode="singleTask"
            \\          android:screenOrientation="fullUser">
            \\          <meta-data android:name="android.app.lib_name" android:value="{s}"/>
            \\          <intent-filter>
            \\              <action android:name="android.intent.action.MAIN"/>
            \\              <category android:name="android.intent.category.LAUNCHER"/>
            \\          </intent-filter>
            \\      </activity>
            \\  </application>
            \\</manifest>
            , .{abp.app_pkg, abp.api_level, abp.api_level, abp.app_name, abp.app_name, abp.lib_name}) catch unreachable;
        break :blk buf.toOwnedSlice() catch unreachable;
    });

    const gen_res = b.addSystemCommand(&[_][]const u8{
        abp.build_tools("aapt") , "package", "-v", "-f",
        "-m", "-J", AndroidBuildParams.BUILD_JAVA_GEN_DIR,
        "-S", AndroidBuildParams.ANDROID_RES_DIR,
        "-I", abp.platform(),
    });
    gen_res.addArg("-M");
    gen_res.addFileSourceArg(manifest_step.getFileSource("AndroidManifest.xml").?);
    gen_res.step.dependOn(&copy_lib.step);

    const len = abp.app_pkg.len;
    var PKG_DIR: [len]u8 = undefined;
    _ = std.mem.replace(u8, abp.app_pkg, ".", "/", PKG_DIR[0..]);

    const javac = b.addSystemCommand(&[_][]const u8{
        "javac", "-verbose", "-source", "1.7", "-target", "1.7",
        "-bootclasspath", abp.platform(),
        "-d", AndroidBuildParams.BUILD_JAVA_OBJ_DIR,
        b.fmt("{s}/{s}/R.java", .{AndroidBuildParams.BUILD_JAVA_GEN_DIR, PKG_DIR}),
    });
    javac.step.dependOn(&gen_res.step);

    const dx = b.addSystemCommand(&[_][]const u8{
        abp.build_tools("dx"), "--verbose", "--dex",
        b.fmt("--output={s}/classes.dex", .{AndroidBuildParams.BUILD_APK_DIR}),
        AndroidBuildParams.BUILD_JAVA_OBJ_DIR
    });
    dx.step.dependOn(&javac.step);

    const packageApk = b.addSystemCommand(&[_][]const u8{
        abp.build_tools("aapt"), "package", "-v", "-f",
        "-S", AndroidBuildParams.ANDROID_RES_DIR,
        "-I", abp.platform(),
        "-F", b.fmt("{s}/{s}.unaligned.apk", .{AndroidBuildParams.BUILD_DIR, abp.app_name}),
        // AndroidBuildParams.BUILD_APK_DIR,
    });
    packageApk.addArg("-M");
    packageApk.addFileSourceArg(manifest_step.getFileSource("AndroidManifest.xml").?);
    packageApk.addArg(AndroidBuildParams.BUILD_APK_DIR);
    packageApk.step.dependOn(&dx.step);

    const addLibToApk = b.addSystemCommand(&[_][]const u8{
        abp.build_tools("aapt"), "add", "-v",
        b.fmt("{s}/{s}.unaligned.apk", .{AndroidBuildParams.BUILD_DIR, abp.app_name}),
        b.fmt("{s}/lib/armeabi-v7a/lib{s}.so", .{AndroidBuildParams.BUILD_APK_DIR, abp.lib_name})
    });
    addLibToApk.step.dependOn(&packageApk.step);

    const zipalign = b.addSystemCommand(&[_][]const u8{
        abp.build_tools("zipalign"), "-v", "-f", "4",
        b.fmt("{s}/{s}.unaligned.apk", .{AndroidBuildParams.BUILD_DIR, abp.app_name}),
        b.fmt("{s}/{s}.apk", .{AndroidBuildParams.BUILD_DIR, abp.app_name})
    });
    zipalign.step.dependOn(&addLibToApk.step);

    const signApk = b.addSystemCommand(&[_][]const u8{
        abp.build_tools("apksigner"), "sign", "-v",
        "--ks", "/home/example/.android/debug.keystore",
        "--v4-signing-enabled", "false",
        "--ks-pass", "pass:android",
        b.fmt("{s}/{s}.apk", .{AndroidBuildParams.BUILD_DIR, abp.app_name})
    });
    signApk.step.dependOn(&zipalign.step);

    const cleanup = b.addSystemCommand(&[_][]const u8{
        "rm",
        b.fmt("{s}/{s}.unaligned.apk", .{AndroidBuildParams.BUILD_DIR, abp.app_name}),
    });
    cleanup.step.dependOn(&signApk.step);

    return cleanup;
}

fn deploy(
    b: *std.Build,
    package_step: *std.Build.RunStep,
    comptime abp: AndroidBuildParams
) *std.Build.RunStep {
    const installApk = b.addSystemCommand(&[_][]const u8{
        "adb", "install", "-r", b.fmt("{s}/{s}.apk", .{AndroidBuildParams.BUILD_DIR, abp.app_name})
    });
    installApk.step.dependOn(&package_step.step);

    const startApp = b.addSystemCommand(&[_][]const u8{
        "adb", "shell", "am", "start", "--activity-clear-top", "-n",
        b.fmt("{s}/android.app.NativeActivity", .{abp.app_pkg})
    });
    startApp.step.dependOn(&installApk.step);

    return startApp;
}

However, I do not know how to do memory management.

floooh commented 1 year ago

Ok merged. Many thanks! (also for the build.zig, very interesting)