Skip to content

Premake generator for ndk-build Android.mk with support for compiling various languages for multiple ABIs

License

BSD-3-Clause, Unlicense licenses found

Licenses found

BSD-3-Clause
LICENSE.txt
Unlicense
UNLICENSE.txt
Notifications You must be signed in to change notification settings

Triang3l/premake-androidndk

premake-androidndk

A module for generation of ndk-build Android.mk files for Premake 5.

  • Providing the same level of multi-ABI building capabilities as hand-written Android.mk files, allowing for building for multiple ABIs from one ndk-build invocation and supporting ABI filtering for most of the project settings.
  • Supporting all languages accepted by ndk-build — C, C++, the GNU Assembler, Yasm, RenderScript, as well as prebuilt libraries and external Android.mk files.
  • Focused on supporting as many Premake project settings as possible, with attention to coverage of edge cases such as allowed characters.
  • Preferring exposing Android.mk settings through existing Premake project settings over adding new ones where semantically appropriate to allow porting of projects from other targets with minimal changes.

Available under the Unlicense, or, similar to Premake, the BSD 3-Clause “New” or “Revised” License.

Usage

Generating Android.mk files

Clone or download the module, and require("path/to/premake-androidndk/androidndk") in your workspace's Premake script. Use the androidndk action to perform generation.

All projects — which represent ndk-build modules — in the application must be located in a single workspace, as a workspace corresponds to an Application.mk file and the root Android.mk.

Add Android platform definitions to the platforms of your workspace or the projects in it. This is especially important in two cases — if your workspace targets other operating systems alongside Android, and if you want to specify ABI-specific settings. Using configurations for this purpose also works, but generally it's recommended to use the configuration axis for build flavors such as debug/release, while doing all platform and architecture filtering via the platform axis — and due to the nature of specifying platforms and configurations to be built by ndk-build for Android.mk files generated by this module, mixing the two may result in complicated setup and unnecessary duplication of arguments you'll need to pass to ndk-build.

Specify the system as "android" in your Android platforms and configurations. While this module doesn't check the value of system anywhere internally, if it's not specified, Premake will assume that the host OS is the target, which may be undesirable — for instance, build libraries may not have the "lib" prefix if the system is selected as "windows".

If you need to specify settings that apply only to certain ABIs, you can specify separate platforms (or configurations) with different architecture values. The supported architectures are "ARM" (corresponds to the armeabi-v7a ABI), "ARM64" (arm64-v8a ABI), "x86" (x86 ABI) and "x86_64" (x86_64 ABI).

For example:

configurations({"Debug", "Release")
platforms({"Windows-x86_64", "Android-ARM64", "Android-x86_64"})
filter({"platforms:Windows-*"})
  system("windows")
  systemversion("10.0")
filter({"platforms:Android-*"})
  system("android")
  systemversion("24")
  cppstl("c++")
filter({"platforms:*-x86_64"})
  architecture("x86_64")
filter({"platforms:*-ARM64"})
  architecture("ARM64")
filter({})

It's also possible to specify both architecture-specific and architecture-agnostic (with a nil or "universal" architecture) platforms in the same project. In this case, the architecture specialization will be chosen when ndk-build is building for an ABI for which one available, and the architecture-independent settings will be used as a fallback if one is not:

platforms({"AndroidOther", "AndroidARMv7", "AndroidARMv8"})
filter({"platforms:AndroidARMv7"})
  architecture("ARM")
filter({"platforms:AndroidARMv8"})
  architecture("ARM64")
filter({"architecture:ARM or ARM64"})
  files({"neonmath.cpp"})
filter({})
-- `armeabi-v7a` ABI will use the AndroidARMv7 platform with neonmath.cpp.
-- `arm64-v8a` ABI will use the AndroidARMv8 platform with neonmath.cpp.
-- `x86` and `x86_64` ABIs will use the AndroidOther platform without neonmath.cpp.

Note that deprecated ABIs such as armeabi, armeabi-v7a-hard, mips, mips64 are not supported. armeabi-v7a builds by default will have NEON enabled and will use the Thumb instruction set for release configurations, this can be changed via settings listed in the section below.

It is important to consider that Premake settings such as architecture are assigned to configuration–platform pairs. So, if you want to use filter({"architecture:"}), you must create a platform or a configuration for that architecture, otherwise it will not work.

To exclude a project from building for certain ABIs, you can set its kind to None with the required filter.

Most settings allow GNU make variable expansion and function call passthrough, so you can use environment variables, or variables and functions provided by ndk-build like $(TARGET_ARCH_ABI), in your projects, and they will be expanded at build time. You need to be careful not to reference variables or functions that may result in disallowed characters in the specific context, such as whitespaces in settings that end up being written to a list variable, however — it's not possible for the module to validate the value in this case. To use the dollar sign as a character, specify $$ instead. Paths starting with a dollar sign (even if it's $$ denoting an escaped raw $ character) are treated as absolute by Premake, allowing for the usage of paths relative to directories such as $(NDK_ROOT). If you want to override this behavior, explicitly prefix the path with ./.

Usage of prebuilt libraries and external Android.mk files

Normally, this module provides functionality for building of projects from source code.

However, it also exposes the prebuilt library functionality of ndk-build. This is the recommended way of including non-system library binaries in the workspace and linking projects against them, as it lets ndk-build properly ensure that the library is copied into the destination directory properly and that it's loaded from the correct path. ndk-build will generate warnings if the project is linked against non-system libraries via system links or via linkoptions.

In addition, it's also possible to create a project that will use an external Android.mk file instead of its own settings (certain settings still must be correctly specified for linkage purposes, however).

To create a project using a prebuilt library or an external Android.mk file, specify just one .a, .so or .mk file and no other source files in the files setting for the desired configurations/platforms. Also, you must specify the correct kind of the project — "SharedLib" for .so, "StaticLib" for .a, or the kind that matches the actual build script included in the external Android.mk.

Projects using an external Android.mk files have special rules regarding their usage, specifically:

  • The external Android.mk must contain settings for only at most one project (module).
  • The kind of the project must match the build script used in the external Android.mk — "SharedLib" for BUILD_SHARED_LIBRARY or PREBUILT_SHARED_LIBRARY, "StaticLib" for BUILD_STATIC_LIBRARY or PREBUILT_STATIC_LIBRARY, "ConsoleApp" for BUILD_EXECUTABLE. Do not use the "Makefile" kind — it has a completely different purpose, and is not supported by this module. The module must know the actual kind for linkage.
  • The name of the project must be the same as LOCAL_MODULE in the external Android.mk.

The following settings have effect when used in a prebuilt library project or an external Android.mk project:

  • configurations
  • flags
    • "ExcludeFromBuild"
    • "LinkTimeOptimization" (for static libraries, if any object files in it are built with -flto)
  • linkoptions (the only supported options are -u or -Wl,--undefined exported from static libraries)
  • links (must match LOCAL_SHARED_LIBRARIES and LOCAL_STATIC_LIBRARIES/LOCAL_WHOLE_STATIC_LIBRARIES for external Android.mk projects referencing other Premake projects, external or not; also, for static libraries, must include the system libraries from LOCAL_EXPORT_LDLIBS)
  • location (or basedir if not provided)
  • kind
  • platforms
  • project (must match LOCAL_MODULE in the external Android.mk)
  • wholelib

For example, to add the Android Native App Glue to your workspace, you can create a project with the following settings:

project("android_native_app_glue")
  kind("StaticLib")
  files({"$(NDK_ROOT)/sources/android/native_app_glue/Android.mk"})
  links({"log", "android"})
  linkoptions({"-u ANativeActivity_onCreate"})

RenderScript library linkage

To link projects with RenderScript sources, you may need to add some of these prebuilt library projects to your workspace (most importantly "RScpp_static") and to specify them in links of the projects that use RenderScript:

project("RSSupport")
  kind("SharedLib")
  files({"$(RENDERSCRIPT_TOOLCHAIN_PREBUILT_ROOT)/platform/$(TARGET_ARCH)/libRSSupport.so"})
project("RSSupportIO")
  kind("SharedLib")
  files({"$(RENDERSCRIPT_TOOLCHAIN_PREBUILT_ROOT)/platform/$(TARGET_ARCH)/libRSSupportIO.so"})
project("blasV8")
  kind("SharedLib")
  files({"$(RENDERSCRIPT_TOOLCHAIN_PREBUILT_ROOT)/platform/$(TARGET_ARCH)/libblasV8.so"})
project("RScpp_static")
  kind("StaticLib")
  files({"$(RENDERSCRIPT_TOOLCHAIN_PREBUILT_ROOT)/platform/$(TARGET_ARCH)/libRScpp_static.a"})

Invoking ndk-build

Setting up applicaton settings

The basic setup is different depending on whether you're using the externalNativeBuild functionality of Gradle to build the application.

The module generates an Application.mk file containing basic settings for the entire application, with some support for Premake configuration and platform filtering that may be useful, for instance, for choosing APP_DEBUG and APP_OPTIM based on the selected configuration. However, as ABI filtering is not available in Application.mk, it will be more coarse than for settings that go to Android.mk — it is assumed that all Application.mk-level settings should not depend on the target ABI, you can see the exact list of the settings that go to Application.mk in the list of supported project settings.

If you're launching ndk-build as a custom task, or not using Gradle at all, you need to specify the path to the Application.mk in the ndk-build command arguments, as NDK_APPLICATION_MK:=path/to/WORKSPACE_NAME.Application.mk, where WORKSPACE_NAME is the name of the Premake workspace for the application. The Application.mk file is written to the location directory of the workspace.

Gradle's externalNativeBuild, however, causes most Application.mk settings to be ignored and overridden by the settings specified in the Gradle script itself. Because of this, using the Application.mk file generated by this module may not be absolutely necessary, but for correctness, you should still set the NDK_APPLICATION_MK variable in ndk-build arguments, otherwise it will try to use the Application.mk from the default path — it's better to express the intentions explicitly than to rely on the file not currently existing. However, you'll need to specify the correct settings in the Gradle script manually anyway. Because Gradle scripts contain settings not only for NDK, but also for all aspects of the application, and since the layout of configurations — build types and product flavors — may vary greatly between applications, Gradle setup is out of the scope of this module.

Here is an example of configuring the application settings relevant to the usage of files generated by this module:

android {
    // The Android NDK version to build the application with.
    // If omitted, the latest installed version will be used.
    ndkVersion '23.0.7599858'

    // Per-configuration Gradle settings.
    // May be specified for defaultConfig, as well as for buildTypes and productFlavors.
    defaultConfig {
        // Minimum Android SDK version supported by the application.
        // Corresponds to APP_PLATFORM := android- in Application.mk (Premake `systemversion`).
        minSdkVersion 14

        externalNativeBuild {
            ndkBuild {
                // See com.android.build.api.dsl.ExternalNativeNdkBuildOptions documentation.

                // Application.mk path - specifying the correct one is recommended.
                arguments 'NDK_APPLICATION_MK:=path/to/WORKSPACE_NAME.Application.mk',
                // Premake platforms to build for - see the section about configuration and platform selection below.
                        'PREMAKE_ANDROIDNDK_PLATFORMS:=Android-ARM',
                        'PREMAKE_ANDROIDNDK_PLATFORMS+=Android-ARM64'
            }
        }

        ndk {
            // See com.android.build.api.dsl.Ndk documentation for more useful options such as `jobs`.

            // The list of the ABIs to build this application for.
            // Corresponds to APP_ABI in Application.mk.
            // Should match the actual list of ABIs supported by at least some of the projects in the workspace.
            // If omitted, ndk-build will try to build for all ABIs supported by the used NDK version.
            // In this case, you should provide an architecture-agnostic platform/configuration for the projects.
            // It may be fine not to specify this if you target only a subset of APIs, but ndk-build will be emitting warnings.
            abiFilters 'armeabi-v7a', 'arm64-v8a'

            // The C++ STL to use.
            // Corresponds to APP_STL in Application.mk.
            // If omitted, APP_STL (Premake `cppstl` and `staticruntime`) from the Application.mk will be used.
            // If that's not specified too, the default STL for the current API version will be selected.
            stl 'c++_static'
        }
    }

    buildTypes {
        release {
            debuggable false

            externalNativeBuild {
                ndkBuild {
                    // Premake configurations to build for - see the section about configuration and platform selection below.
                    arguments 'PREMAKE_ANDROIDNDK_CONFIGURATIONS:=Release'
                }
            }
        }

        debug {
            debuggable true

            externalNativeBuild {
                ndkBuild {
                    // Premake configurations to build for - see the section about configuration and platform selection below.
                    arguments 'PREMAKE_ANDROIDNDK_CONFIGURATIONS:=Debug'
                }
            }
        }
    }

    externalNativeBuild {
        ndkBuild {
            // See com.android.build.api.dsl.NdkBuild documentation.
            // This is required to initiate native code building at all.

            // The Android.mk file to use - the generated workspace Android.mk in the `location` of the workspace.
            // Corresponds to APP_BUILD_SCRIPT in Application.mk.
            path file('path/to/WORKSPACE_NAME.wks.Android.mk')
        }
    }
}

Specifying Premake configurations and platforms to build

Because a single ndk-build invocation builds for multiple ABIs, the usage of the generated files is slightly unusual compared to most build systems targeted by Premake where only a single configuration and platform pair is selected for building.

Instead of letting you select just one configuration and platform, this module allows you to execute ndk-build for multiple platforms and configurations.

For this purpose, the module provides two variables that you need to set in the arguments of the ndk-build invocation (externalNativeBuild.ndkBuild.arguments for a Gradle build configuration):

  • PREMAKE_ANDROIDNDK_CONFIGURATIONS — Premake configurations to build, at least one must be specified.
  • PREMAKE_ANDROIDNDK_PLATFORMS — Premake platforms to build for. Optional, if none are specified, will be building for all platforms specified in any project.

To set a variable to just one value, you can use the VARIABLE:=value syntax of the ndk-build argument. For multiple values, you need to provide each in a separate argument as VARIABLE+=value (preferably the first with := still to ensure it's overwritten if there's an environment variable with the same name). The Gradle example above shows how you can enable building for multiple platforms representing different ABIs, and for a configuration depending on the Gradle build type.

This, however, does not mean that you can create debug and release builds at once — rather, this architecture is designed specifically to provide a way for building for multiple ABIs as usual while being able to use an architecture filter in Premake. Because of this, you must ensure that for every ABI, only at most one configuration–platform pair will be selected for every project. An ambiguous selection of platforms and configurations will result in an undefined behavior.

Platform-agnostic projects — those that have no platforms inherited from the workspace or defined — will be built regardless of the value of PREMAKE_ANDROIDNDK_PLATFORMS (only configuration selection will be used for them).

Supported settings

Please carefully verify that the settings you're using in your projects are handled in a way that's supported by this module and by ndk-build itself. Most importantly, please check the settings that are listed as per-file or per-extension here, especially if you have source files of different languages in your projects.

Per-file or per-configuration/per-platform for a project, ABI-filtered

There is a small number of settings for which it's possible to specify the value for individual source files using the files filter.

  • armisa (new) = "A32" / "T32" (default)
    • ARMv7 instruction set to use. By default, Thumb ("T32") will be used for release builds. Debug builds, however, always use A32.
    • At the project configuration scope, it controls LOCAL_ARM_MODE.
    • At the file scope, it can be used to build the specified files for A32 in a Thumb project, but not vice versa, by adding the .arm suffix to the file in LOCAL_SRC_FILES. Alternatively, you can use the .arm suffix (if needed, as a part of .arm.neon, but not .neon.arm) directly in the files setting, which will take precedence.
  • flags
    • ExcludeFromBuild
      • Also supported for excluding entire projects on specified configurations/platforms.
  • vectorextensions (for the "ARM" architecture) = "ARMv7" (new) / "NEON" (default, new)
    • Whether to allow the compiler to generate instructions from the NEON SIMD instruction set as part of its optimizations. Specifying "ARMv7" (analogous to "IA32" on x86) disables NEON, specifying "NEON" enables it. By default, for consistency with NDK versions starting with r21, NEON will be enabled, even if the project doesn't explicitly specify vectorextensions (so on pre-r21, NEON will be used for Premake projects too).
    • At the project configuration scope, it controls LOCAL_ARM_NEON.
    • At the file scope, it can be used to build the specified files with NEON code generation in a project with NEON disabled, but not vice versa, by adding the .neon suffix to the file in LOCAL_SRC_FILES. Alternatively, you can use the .neon suffix (if needed, as a part of .arm.neon, but not .neon.arm) directly in the files setting, which will take precedence.
    • For the allowed values on other architectures, see the per-configuration/per-platform settings section.

Per-language or per-configuration/per-platform for a project, ABI-filtered

While ndk-build doesn't provide a way to specify compiler flags and most other settings for individual source files, it supports multiple source languages with different compilers, which accept different build options. Premake, however, exposes most build settings under language-agnostic names.

To specify which compiler should receive the needed value, you can use file extension filters.

Because the module supports .arm, .neon and .arm.neon suffixes, as well as many C++ extensions, it's not recommended to list file extensions manually. Instead, built-in filter constants provided by the module should be preferred, as they include the ARM suffixes and all the supported extensions.

For including a specific language, the following variables are available (note that they must be used directly, and if needed, via .. concatenation — %{} string interpolation will not locate them):

  • premake.modules.androidndk.filefilters.as for the GNU Assembler (GAS)
  • premake.modules.androidndk.filefilters.asm for Yasm
  • premake.modules.androidndk.filefilters.c for C
  • premake.modules.androidndk.filefilters.cpp for C++
  • premake.modules.androidndk.filefilters.rs for RenderScript

Each of those filters is a single string which already has the "files:" prefix (for instance, premake.modules.androidndk.filefilters.c is "files:**.c or files:**.c.arm or files:**.c.neon or files:**.c.arm.neon").

To exclude a language from a file filter, filefilters also includes filters prefixed with not, such as premake.modules.androidndk.filefilters.notc. Unlike the inclusive filters, the exclusive are tables, because Premake exposes the logical and as separate table elements. premake.modules.androidndk.filefilters.notc is { "files:not **.c", "files:not **.c.arm", "files:not **.c.neon", "files:not **.c.arm.neon" }, for example. Premake filter tables can be nested, you don't need to perform flattening manually if you want to combine the language filters with additional terms.

In addition, raw combinations of extensions and ARM suffixes are available in filefilters with the extensions suffix, such as premake.modules.androidndk.filefilters.cextensions, which is { ".c", ".c.arm", ".c.neon", ".c.arm.neon" } (without the ** recursive wildcard). You can do your own processing of them using functions like table.translate and table.concat, for instance, to attach all the possible extensions to a specific file name.

Note that ndk-build treats the uppercase .C extension as C++. To avoid imposing additional constraints, this module allows using the .C extension for C++, even though other Premake actions may treat it as C, especially considering that path.hasextension in Premake is case-insensitive. However, Premake filters are case-insensitive, and "files:**.c" or "files:**.C" will match both C .c and C++ .C sources, leading to the specified settings being used for both C and C++. Therefore, it's recommended to completely avoid using the uppercase .C extension. The .C extension is also not included in the premake.modules.androidndk.filefilters constants for this reason.

It is important that you must not try to derive a language filter from an extension filter because the language setting has a "project" scope in Premake rather than "config", and thus it doesn't support file-specific or even configuration- or platform-specific overrides, in addition to not having allowed values for GAS, Yasm and RenderScript, so the following will not work:

filter(premake.modules.androidndk.cfilefilter)
  language("C")  -- This WILL NOT WORK!
filter(premake.modules.androidndk.cppfilefilter)
  language("C++")  -- This WILL NOT WORK!
filter("language:C")
  defines({"MY_NULL=((void *)0)"})
filter("language:C++")
  defines({"MY_NULL=0"})

In addition, if the NoPCH flag is not enabled in the project for the configuration–platform pair, and the file specified in pchheader is also listed in files, any file-filtered settings for it will be treated as C++ settings.

With some exceptions, most language-specific settings can be set independently for each language supported by this module.

  • buildoptions (C, C++, GAS, Yasm, RenderScript)
    • One build option may contain one or multiple compiler arguments separated with whitespaces.
    • Double quotation marks (") may be used to specify a single compiler argument containing whitespaces.
    • If double quotation marks need to be escaped, they need to be prefixed with \, as "\\\"" according to Lua string literal escaping rules.
    • The backslash character (\) itself also needs to be escaped with another \, as "\\\\", so the module can distinguish between a backslash used for escaping the double quote character and an actual backslash character.
    • All other characters after GNU make $ variable or function reference expansion are allowed, and other shell-interpreted characters will be escaped automatically at build time.
    • Because of the way the values are gathered from the used filters, duplicate buildoptions are eliminated. For this reason, always specify multiple-argument options as { "-prefix value1", "-prefix value2" }, not { "-prefix", "value1", "-prefix", "value2" } (as the latter will become "-prefix value1 value2").
    • See the documentation for the "LinkTimeOptimization" flag here for details about the handling of "-flto=…".
  • cdialect (C)
  • cppdialect (C++)
  • flags
    • "FatalCompileWarnings" (C, C++, Yasm)
    • "NoBufferSecurityCheck" (C, C++)
    • "ShadowedVariables" (C, C++)
    • "UndefinedIdentifiers" (C, C++)
  • defines (C, C++, Yasm)
    • All defines are passed to the compiler before undefines even if they're specified both within and without an extension filter.
  • disablewarnings (C, C++, Yasm)
  • enablewarnings (C, C++, Yasm)
  • fatalwarnings (C, C++, Yasm)
    • For Yasm, this is treated as enablewarnings.
  • floatingpoint (C, C++)
    • "Strict" is treated as "Default".
  • floatingpointexceptions (C, C++)
  • forceincludes (C, C++, Yasm)
    • For C and C++, double quotation mark characters (") are disallowed, as they would terminate the #include "path" statement inserted by the compiler.
    • For Yasm, ", ', ; characters are disallowed.
  • includedirs (C, C++, GAS and Yasm combined, RenderScript separately)
    • C, C++, GAS and Yasm use the same list of include directories (that is written to LOCAL_C_INCLUDES).
    • All includedirs are passed to the compiler before sysincludedirs.
    • Whitespaces and #, $ (after GNU make reference expansion) characters are disallowed.
  • inlinesvisibility (C, C++)
    • Unlike in other Premake actions using premake.clang or premake.gcc, this is also supported for C, not only C++.
  • omitframepointer (C, C++)
    • To use the Address Sanitizer, this needs to be set to Off — see the Address Sanitizer guide on Android Developers.
  • strictaliasing (C, C++)
  • sysincludedirs (C, C++, GAS and Yasm combined, RenderScript separately)
    • See includedirs.
  • undefines (C, C++, Yasm)
    • All defines are passed to the compiler before undefines even if they're specified both within and without an extension filter.
  • unsignedchar (C, C++)
  • visibility (C, C++)
    • Unlike in other Premake actions using premake.clang or premake.gcc, this is also supported for C, not only C++.
  • warnings (C, C++, Yasm)
    • For Yasm, only "Off" has effect — other values are treated as warnings enabled as normal.

Per-configuration/per-platform for a project, ABI-filtered

  • architecture
    • If an architecture is specified, the settings for this configuration–platform pair will be used will be used while building for the respective ABI if the configuration and the platform are selected for building via PREMAKE_ANDROIDNDK_CONFIGURATIONS and PREMAKE_ANDROIDNDK_PLATFORMS. In this case, the architecture filter may be used to provide ABI-specific settings.
      • "ARM" for the armeabi-v7a ABI.
      • "ARM64" for the arm64-v8a ABI.
      • "x86" for the x86 ABI.
      • "x86_64" for the x86_64 ABI.
    • Leave unspecified or set to "universal" to provide fallback settings that will be used for the project for ABIs without a specialization.
    • Generally architecture should be specified under a platforms filter, or, depending on your configuration structure, under a configurations one. See the “Usage” section for more information ABI filtering of settings.
  • exceptionhandling = "Off" / "On" (default)
  • files
    • For a built Premake project:
      • The following languages are supported:
        • C (.c)
        • C++ (.cc, .cp, .cxx, .cpp, .CPP, .c++, .C)
          • The list of allowed extensions is broader than the default for Premake v5.0.0-alpha16, but matches the default for the NDK r23).
          • Note that while ndk-build (and therefore this setting) is case-sensitive, Premake filters are not. Therefore, a files:**.c or a files:**.C filter will cause both C .c and C++ .C to pass, and extension-filtered language-specific settings such as buildoptions intended only for C++ will be used for C as well if both are present in the project, for example — see the section about per-language settings. It's better to avoid using the .C extension completely.
        • GNU Assembler (.s, .S)
        • Yasm (.asm)
          • "x86" and "x86_64" architectures only — will be ignored automatically while building for other ABIs, including in architecture-agnostic projects.
        • RenderScript (.rs, .fs)
      • .arm, .neon and .arm.neon (but not .neon.arm) suffixes are allowed — see the documentation for per-file armisa and vectorextensions settings, which can be used as an alternative (using file-filtered armisa and vectorextensions is recommended instead so filters like "files:**.c" can still be used rather than "files:**.c or **.c.arm or **.c.neon or **.c.arm.neon", however — though for extension filtering, constants like premake.modules.androidndk.filefilters.c are recommended, which include the .arm and .neon suffixes).
      • Whitespaces, backtick, and ", #, $ (after GNU make reference expansion), &, ', (, ), ,, ;, <, >, | characters are disallowed. The backslash is allowed as a path separator on Windows, but disallowed on Linux.
    • To create a prebuilt library project or to use an external Android.mk file, specify only one .so (for the "SharedLib" kind), .a (for the "StaticLib" kind) or .mk file, and no other source files. See the “Usage” section for more information about the required and supported settings for prebuilt and external projects.
      • In prebuilt library paths, whitespaces, and ", #, $ (after GNU make reference expansion), %, &, *, ;, <, >, ?, | characters are disallowed on Windows. On other operating systems, backtick, ', (, ), \ characters are not supported.
      • In external Android.mk paths, whitespaces are not disallowed.
    • All irrelevant file types (such as headers) will be ignored automatically.
  • flags
    • "ExcludeFromBuild"
      • Also supported for excluding individual source files from building.
    • "FatalLinkWarnings"
      • For shared libraries and executables only.
      • Unlike in ndk-build itself, fatal link warnings are disabled by default (LOCAL_DISABLE_FATAL_LINKER_WARNINGS := true).
    • "LinkTimeOptimization"
      • If used in a static library, shared libraries and executables linked against it will also be linked with link-time optimization (the transitivity ensured for this options, however, doesn't apply to building of source files).
      • It's possible to override the default link-time optimization type by specifying the desired link-time optimizations type, such as -flto=thin, in both buildoptions and linkoptions. The specific -flto= type will also be propagated from linkoptions of static library dependencies (though in this case, all linked static libraries must have the same LTO type, and static library dependency analysis only checks linkoptions, not buildoptions).
    • "NoPCH"
      • Causes pchheader to be ignored — see pchheader for more information.
  • formatstringchecks (new) = "Off" / "On" (default)
    • Corresponds to LOCAL_DISABLE_FORMAT_STRING_CHECKS (but enabling instead of disabling), whether to check printf format strings in the sources.
  • isaextensions
    • This should be used only in libraries with code used conditionally based on the information provided by cpuid, or for emulation purposes, otherwise it may prevent the app from working on physical devices.
    • All "x86" and "x86_64" architecture isaextensions normally supported by Premake actions targeting Clang are supported.
  • libdirs
    • Search paths for library binaries specific directly via links.
    • Not recommended as using library binaries directly via linker arguments is discouraged by ndk-build — prefer using prebuilt library projects, and see links for more information.
    • All libdirs gathered from the project and its static library dependencies are passed to the linker before syslibdirs.
  • linkoptions
    • Most options are supported only in shared libraries and executables, but there are some exceptions which are exported from static libraries as well.
    • See buildoptions for information about passing multiple arguments, characters that need to be escaped manually, deduplication, disallowed characters.
    • Going to Clang++ rather than directly to LLD — -Wl may be needed for many options.
    • Unlike in other Premake actions, some parsing is done:
      • Options prefixed with -l, -L (with or without -Wl), -Wl,--library, -Wl,--library-path will go to LOCAL_LDLIBS instead of LOCAL_LDFLAGS, and will be exported from static libraries to dependent shared libraries, similarly to links. This may be useful if you have a project named the same as a system library, but you want to link against that system library (for instance, -llog if there is a project named "log"). See links and libdirs for more information.
      • Options prefixed with -u (with or without -Wl) or -Wl,--undefined will also be exported from static libraries transitively to allow preserving “unused” symbols imported from static libraries in the dependent shared libraries, primarily JNI native function implementations in static libraries — similar to wholelib, but more granular, for individual symbols.
      • See the documentation for the "LinkTimeOptimization" flag here for details about the handling of "-flto=…".
  • links
    • Premake projects and system libraries to link against — usable in all kinds of projects (in shared libraries and executables, as well as in static libraries for transitive linkage).
    • If a shared library or an executable is linked against a static library project, it will automatically be linked against all system libraries referenced by its static library dependencies, even if static library dependencies are chained via links. Transitivity across shared libraries is not provided, however, as it's not necessary and may be undesirable — instead of using LOCAL_EXPORT_LDLIBS, the module traverses the dependency tree by itself. This is done for consistency with other Premake actions such as Visual Studio.
    • See wholelib and wholelibs for information about importing static libraries as whole archives.
    • If you have a project named the same as a system library, but you still want to link against the system library, you can use a buildoptions entry with the -l prefix (for instance, -llog if there is a project named "log").
    • Generally shouldn't be used for anything but linking to other projects and to the NDK system libraries — ndk-build will generate warnings for non-system libraries. Prebuilt library projects (see the “Usage” section) should be preferred for linking against non-system library binaries directly. However, the module still provides the functionality for linking against arbitrary library files:
      • Full paths, libdirs and syslibdirs can be used for specifying search paths (however, there's no way to link against two libraries with the same name under different paths).
      • Both short library names and full file names are allowed. If the file name ends with .a or .so, it's treated as a full name — the "lib" prefix must be explicitly specified if needed.
      • The ":" prefix can be added to the file name explicitly to force handling of it as a full file name.
  • kind
    • "SharedLib" or "StaticLib" for a built or a prebuilt shared or static library.
    • "ConsoleApp" for an executable.
    • "None" to exclude the project from building for the current configuration/platform.
    • Note that "Makefile" is not a supported option — an external Android.mk file must be used through files, and the project for it must have the kind that matches the actual ndk-build script included by it.
    • For more information about the usage of prebuilt libraries and external Android.mk files, see the section about them in “Usage”.
  • renderscriptcompatibility (new) = "Off" (default) / "On"
    • Corresponds to LOCAL_RENDERSCRIPT_COMPATIBILITY.
  • renderscriptincludedirsoverride (new) = "Off" (default) / "On"
    • Corresponds to the usage of LOCAL_RENDERSCRIPT_INCLUDES_OVERRIDE instead of LOCAL_RENDERSCRIPT_INCLUDES, whether to ignore the NDK built-in platform and toolchain RenderScript include search paths.
  • pchheader
    • Suffixes such as .arm and .neon are not supported — handling of ARMv7 ISA overrides is performed internally in ndk-build.
    • Whitespaces, backtick, and ", #, $ (after GNU make reference expansion), &, ', (, ), ,, ;, <, =, >, | characters are disallowed (same as in files for sources, but additionally =). The backslash is allowed as a path separator on Windows, but disallowed on Linux.
    • ndk-build treats the precompiled header as C++ code. The module will configure variables necessary for building C++ code (such as LOCAL_CPPFLAGS) if the precompiled header is present, however, if you have any C++-specific settings with a file extension filter, you need to provide at least one source file with the respective extension, otherwise this module will not be able to access those settings. If the file specified in pchheader is listed among files, any file-filtered settings applied to it will also be treated as C++ settings.
    • Ignored if the "NoPCH" flag is enabled for the project configuration/platform.
  • rtti = "Off" / "On" (default)
  • targetprefix, targetname, targetsuffix
    • Combined into LOCAL_MODULE_FILENAME.
    • Overriding the binary path or extension is not supported.
    • Whitespaces and #, $ (after GNU make reference expansion), %, /, ;, | characters are disallowed.
    • Only removing the lib prefix works when invoking ndk-build from Gradle as of Gradle 7.0.2 because it gathers the names of the targets to build from LOCAL_MODULE_FILENAME instead of LOCAL_MODULE. If executing ndk-build manually without a list of targets, or with a correctly specified list of them, other values are supported.
  • undefinedsymbols (new) = "Off" (default) / "On"
    • For shared libraries and executables only.
    • Corresponds to LOCAL_ALLOW_UNDEFINED_SYMBOLS, if enabled, errors will not be thrown for undefined symbols, instead, the symbols will be resolved at runtime.
  • shortcommands (new) = "Off" (default) / "On"
    • Corresponds to LOCAL_SHORT_COMMANDS, whether to execute build commands from a file rather than directly via the shell, as if the command line is too long, it may not fit in the 8191 character limit on Windows.
  • syslibdirs
    • See libdirs.
  • system
    • Must be "android" for the correct library name prefix ("lib").
  • thinarchive (new) = "Off" (default) / "On"
    • For static libraries only.
    • Corresponds to LOCAL_THIN_ARCHIVE, whether to store only object file paths instead of the object themselves in the static library being built.
  • vectorextensions (for the "x86" and "x86_64" architectures)
    • This should be used only in libraries with code used conditionally based on the information provided by cpuid, or for emulation purposes, otherwise it may prevent the app from working on physical devices.
    • Allowed values on the "x86" architecture: "SSE4.1", "SSE4.2" (new), "AVX", "AVX2" (SSSE3 and below are required by Android and are always supported).
    • Allowed values on the "x86_64" architecture: "AVX", "AVX2" (SSE4.2 and below are required by Android and are always supported).
    • For the allowed values on the "ARM" architecture, see the per-file settings section.
  • wholelib (new) = "Off" (default) / "On"
    • For static libraries only.
    • Whether all object files from this static library should be included in the shared libraries (but not executables) referencing it (and this library will be added to LOCAL_WHOLE_STATIC_LIBRARIES instead of LOCAL_STATIC_LIBRARIES). This is useful, for instance, when exporting JNI native function implementations from a static library.
    • This setting is for exporting the requirement that the static library must be linked as a whole archive — for the equivalent for importing, see wholelibs (a static library will be linked via LOCAL_WHOLE_STATIC_LIBRARIES rather than LOCAL_STATIC_LIBRARIES if it enables wholelib for itself, or if it's specified in wholelibs of the project importing it — you only need either to specify wholelib in the dependency or to add it to wholelibs of the dependent projects; though specifying both the same time is fine, that's redundant).
  • wholelibs (new)
    • List of static library project names among the links of the project to link as whole archives.
    • For more information about whole static libraries, see wholelib — this setting provides the same functionality, but for importing static library dependencies as whole archives (only one of wholelib or wholelibs is enough).
    • If a project is listed in wholelibs, it still needs to be added to links to be linked — this setting is merely a modifier, and therefore it's fine to just list all static library projects that need to be linked as whole archives once for the entire workspace (though wholelib is more suited for this purpose).

Per-configuration/per-platform for a workspace, not ABI-filtered

These settings have effect on APP variables in the Application.mk rather than LOCAL variables in projects Android.mk files.

There is a single Application.mk file for the entire workspace, and the same values of the APP variables are used regardless of the current target ABI. Thus, for all configurations and platforms selected in PREMAKE_ANDROIDNDK_CONFIGURATIONS and PREMAKE_ANDROIDNDK_PLATFORMS, the values of these settings must be the same for:

  • All projects in the workspace;
  • All target ABIs (architectures).

For all selected configurations (which are visited in an undefined order), the module tries to locate the value in:

  1. The workspace itself.
  2. The startproject.
  3. For all selected platforms, visited in an undefined order, platform-specific projects, also in an undefined order.
  4. Platform-agnostic projects, in an undefined order.

You can use, for instance, optimize("Off") in the "Debug" configuration, and optimize("On") in the "Release" one, as long as you build the workspace with either PREMAKE_ANDROIDNDK_CONFIGURATIONS:=Debug or PREMAKE_ANDROIDNDK_CONFIGURATIONS:=Release, but not both at once. However, you can't disable optimizations for specific projects in the "Release" configuration in this case.

  • cppstl (new) = "Default" / "c++" / "none" / "system"
    • The C++ standard library to use.
    • NDK versions before r18 also support "gabi++", "gnustl", "stlport".
    • Use staticruntime to specify whether to link against the static standard library or against the shared one.
  • optimize
    • Handled in a way that approximates the behavior of other Premake actions.
    • Unspecified, "Off", "Debug" result in APP_OPTIM := debug.
    • Anything else ("Size", "Speed", "Full") results in APP_OPTIM := release, and also force-disables APP_DEBUG.
  • symbols
    • Handled in a way that approximates the behavior of other Premake actions.
    • Ignored if optimize is set to a value that results in optimization enabled.
    • Unspecified, "Off", "Default" result in APP_DEBUG := true.
    • Anything else ("On", "FastLink", "Full") results in APP_DEBUG := false.
  • staticruntime = "Default" / "Off" / "On" (default)
    • Whether to use the static C++ standard library (cppstl) as opposed to a shared one.
    • Static by default, consistent with the default CMake behavior (where c++_static is the default standard library).
    • Ignored for "none" and "system" standard libraries.
  • systemversion
    • Minimum Android SDK version required by the application, as a string representation of a number (corresponds to APP_PLATFORM := android-systemversion).

Per-project or per-workspace

  • basedir
  • configurations
    • Whitespaces and %, (, ), , characters are disallowed (configuration and platform names are used in GNU make list function calls).
  • location
    • The directory where the Android.mk file for the project or the Application.mk and the root Android.mk for the workspace will be placed (if provided — otherwise basedir will be used).
    • For projects, it is heavily recommended to set this to a folder that doesn't contain header files. This is the path that will be used as LOCAL_PATH, and ndk-build internally adds LOCAL_PATH as the last include directory, causing LOCAL_PATH to be treated as a more “system” include search path than the system paths themselves. This causes issues when the LOCAL_PATH directory contains header files named the same as C and C++ standard library headers — the LLVM libc++ uses #include_next in a few files (such as math.h), causing project's files to included from system headers; this is especially dangerous if the local "math.h" itself #includes <math.h>.
  • platforms
    • See configurations.
  • project
    • Whitespaces and #, $, %, /, :, ;, =, | characters are disallowed.
    • GNU make variable or function references ($) are disallowed.
  • startproject
    • The start project will have a higher priority when locating setting values for Application.mk variables.
  • workspace
    • Whitespaces and #, $ characters are disallowed.
    • GNU make variable or function references ($) are disallowed.

Future work, contributing and omissions

Several features were left temporarily or intentionally unimplemented in the current version of the module, however, they may be useful in certain cases.

If your project depends on something missing from the module, feel free to open an issue, and we'll discuss how the needed functionality can be added to the module in a convenient and flexible way — or submit a pull request with the implementation!

Note that the general preference is to use existing Premake settings wherever possible — potentially twisting them and adding the necessary constraints while still trying to keep the original semantic concepts — instead of adding new ones. Don't hesitate to exploit filters, to interpret a variable in different ways depending on the target ABI and overall context, to use any opportunity to make the module more compatible with existing projects for other targets. However, if new settings need to be added, prefer using kind = "boolean" settings rather than flags as the former have three states, making it easier to provide default behavior, especially if the new settings are promoted to the Premake core or used in other modules in the future, potentially on platforms with different defaults.

If you're adding functionality that involves strings in a new place in the generated Android.mk and Application.mk files, make sure to check which non-letter ASCII characters, including different types of whitespaces, are supported by ndk-build in the specific context, especially if it's a file name or a path, on both Linux (which allows any characters in paths) and Windows (including UNC paths), with attention to backslash-escaping of shell-interpreted characters (see androidndk.shellEscapedCharactersPostQuotes), but not only them. All user input must be passed through p.esc, preferably early (and functions expecting output of p.esc as arguments must be named with the PostEsc suffix), to ensure the integrity of GNU make variable or function references (also make sure that paths including $ references are handled correctly as absolute or as relative depending on the context, especially when joining — usually paths beginning with $ should be considered absolute, and that's how Premake itself treats them, while a relative path needs to start with ./$ in this case) and of escaping of the comment character #. After p.esc, the invalid character check may be done for the non-reference part of the string if needed — see the usage androidndk.staticallyHasPatternPostEsc (build-time validation would be an overkill if there are references in the string, but to catch potential errors when porting projects from other platforms, generation-time checks are useful). In many places, strings are used in shell commands invoked by ndk-build — see how checkers and escapers starting with androidndk.shellEscape are used in the module; also shell character escaping needs to be done at build time, not at Premake invocation time, since it must be performed after expanding GNU make references. Also, if you're passing user input to a GNU make function, use an intermediate variable (see androidndk.getTemporaryVariableReference), as the user-provided string may contain brackets or commas, which would break the parsing of the function call.

The following functionality has been omitted currently, but may be nice to have for reasons like compatibility:

  • Unit tests. This is a large module with complex input handling with a large number of edge cases for many settings that, in some cases, depend on the kind of the project, the target ABIs, source languages used in the project — written in a dynamically-typed language. Many errors and typos may occur. Tests verifying both supported and disallowed input need to be added — covering platform-dependent and platform-agnostic projects, ABI-dependent and ABI-agnostic projects, different kinds of binaries, prebuilt libraries, external Android.mk projects, source files written in various languages with extension filters and .arm and .neon suffixes, link settings and transitivity of them across chains of static libraries where needed, GNU make variable and function references, disallowed characters, shell-interpreted character escaping, and other cases handled by this module.
  • Building for deprecated ABIs removed from the NDK — armeabi, armeabi-v7a-hard, mips, mips64. While simply adding a new architecture is trivial (though for choosing armeabi-v7a-hard, a special setting for the floating-point argument convention may be more suitable), that wouldn't imply complete support for the details of them — such as instruction set extensions (like the MXU vector extensions for MIPS), hardware floating-point unit support. MIPS-powered Android devices are extremely rare, and ARMv5 deprecation began in Android 4.0, having been completed in Android 4.4.
  • Support for obsolete toolchains removed from the NDK, such as GCC, as well as toolchain version selection (as of r23, the NDK only offers one version of the Clang toolchain).
  • Clang-Tidy — needs to be integrated into the architecture of Premake itself.
  • Post-processing of the generated machine code and assembly source files via LOCAL_FILTER_ASM — needs to be added in a way friendly to the Premake architecture.
  • Compatibility with the Visual Studio actions for Android (the built-in android module). It defines many settings in ways that aren't consistent with the Premake core itself, as well as adding redundant options:
    • Values are generally lowercase, while the Premake core uses PascalCase internally.
    • The additions to the list of the allowed architecture values are chaotic. For armeabi, it allows both "arm" and "armv5" (which are registered as separate options rather than aliases of each other), while for armeabi-v7a, it uses "armv7". Premake itself provides an "ARM" option, which, given the removal of ARMv5 support from the NDK, is more likely to be expected to correspond to ARMv7 as opposed to ARMv5. arm64-v8a is exposed as "aarch64" instead of "ARM64" used in the Premake core.
    • ARMv5 and ARMv7 instruction sets are presented as two options: a "Thumb" flag, and a "thumbmode" setting, with one of the allowed values being "disabled" rather than "Default" commonly used in Premake.
    • stl is a new setting in it, but it uses misleading names. The "gnustl" and "c++" standard libraries provided by ndk-build are called "gnu" and "libc++" instead, which is likely not what users expect. For "system", the value "none" is used — however, the NDK has both "system" (which is deprecated) and "none", the difference between the two being new and delete being supported in "system", while "none" providing no C++ standard library functionality at all. The name stl also doesn't imply C++ directly, which may cause issues if Premake gets support for a different language that also has the standard library referred to by the same acronym — unlike settings like buildoptions, this may be even workspace-level (and in ndk-build, it is), and thus the value is not file- or extension-filterable.
    • androidapilevel is redundant due to the existence of systemversion.
    • The "posix" system tag is not added for Android.
  • The “call array” architecture is not used in the code as certain settings are handled in multiple places in different ways, and various kinds of preprocessing are done. Replacing parts of the generation code in project scripts is unlikely to be useful or convenient for this reason.

About

Premake generator for ndk-build Android.mk with support for compiling various languages for multiple ABIs

Resources

License

BSD-3-Clause, Unlicense licenses found

Licenses found

BSD-3-Clause
LICENSE.txt
Unlicense
UNLICENSE.txt

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages