Repository: mesonbuild/meson
Pull request #13767: android: Added android_usecase kwargs to executable
Author: login: sp1ritCS
Created: 2024-10-08T23:04:30Z | Merged: 2025-04-17T16:01:22Z

This is the idea I've came up with in regards to https://github.com/mesonbuild/meson/discussions/13758

It doesn't do anything regarding application glue right now, as I'm unsure if and how to implement it, but I thought about it some more and belive having it would simplify things quite a bit.

---

By setting android_usecase to `application`, the executable gets actually built as a shared library instead of an executable. This makes it possible to use an application within an android application process.

https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/7555/ 


Diff:
diff --git a/docs/yaml/functions/executable.yaml b/docs/yaml/functions/executable.yaml
index abbc5feee909..df71b79fadc5 100644
--- a/docs/yaml/functions/executable.yaml
+++ b/docs/yaml/functions/executable.yaml
@@ -21,6 +21,17 @@ varargs_inherit: _build_target_base
 kwargs_inherit: _build_target_base
 
 kwargs:
+  android_exe_type:
+    type: str
+    default: "'executable'"
+    since: 1.8.0
+    description: |
+      Specifies the intended target of the executable. This can either be
+      `executable`, if the intended usecase is to run the executable using
+      fork + exec, or `application` if the executable is supposed to be
+      loaded as shared object by the android runtime.
+
+
   export_dynamic:
     type: bool
     since: 0.45.0
diff --git a/mesonbuild/build.py b/mesonbuild/build.py
index e94f75faa1f3..ad392c16a307 100644
--- a/mesonbuild/build.py
+++ b/mesonbuild/build.py
@@ -112,7 +112,7 @@ class DFeatures(TypedDict):
     rust_kwargs |
     cs_kwargs)
 
-known_exe_kwargs = known_build_target_kwargs | {'implib', 'export_dynamic', 'pie', 'vs_module_defs'}
+known_exe_kwargs = known_build_target_kwargs | {'implib', 'export_dynamic', 'pie', 'vs_module_defs', 'android_exe_type'}
 known_shlib_kwargs = known_build_target_kwargs | {'version', 'soversion', 'vs_module_defs', 'darwin_versions', 'rust_abi'}
 known_shmod_kwargs = known_build_target_kwargs | {'vs_module_defs', 'rust_abi'}
 known_stlib_kwargs = known_build_target_kwargs | {'pic', 'prelink', 'rust_abi'}
@@ -1996,6 +1996,7 @@ def __init__(
         super().__init__(name, subdir, subproject, for_machine, sources, structured_sources, objects,
                          environment, compilers, kwargs)
         self.win_subsystem = kwargs.get('win_subsystem') or 'console'
+        assert kwargs.get('android_exe_type') is None or kwargs.get('android_exe_type') in {'application', 'executable'}
         # Check for export_dynamic
         self.export_dynamic = kwargs.get('export_dynamic', False)
         if not isinstance(self.export_dynamic, bool):
@@ -2043,6 +2044,8 @@ def post_init(self) -> None:
             else:
                 self.suffix = machine.get_exe_suffix()
         self.filename = self.name
+        if self.prefix:
+            self.filename = self.prefix + self.filename
         if self.suffix:
             self.filename += '.' + self.suffix
         self.outputs[0] = self.filename
diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py
index 2cd272db39ff..d3bf86df3e4a 100644
--- a/mesonbuild/interpreter/interpreter.py
+++ b/mesonbuild/interpreter/interpreter.py
@@ -54,6 +54,7 @@
     CT_BUILD_BY_DEFAULT,
     CT_INPUT_KW,
     CT_INSTALL_DIR_KW,
+    _EXCLUSIVE_EXECUTABLE_KWS,
     EXECUTABLE_KWS,
     JAR_KWS,
     LIBRARY_KWS,
@@ -1816,12 +1817,24 @@ def func_dependency(self, node: mparser.BaseNode, args: T.Tuple[T.List[str]], kw
     def func_disabler(self, node, args, kwargs):
         return Disabler()
 
+    def _strip_exe_specific_kwargs(self, kwargs: kwtypes.Executable) -> kwtypes._BuildTarget:
+        kwargs = kwargs.copy()
+        for exe_kwarg in _EXCLUSIVE_EXECUTABLE_KWS:
+            del kwargs[exe_kwarg.name]
+        return kwargs
+
     @permittedKwargs(build.known_exe_kwargs)
     @typed_pos_args('executable', str, varargs=SOURCES_VARARGS)
     @typed_kwargs('executable', *EXECUTABLE_KWS, allow_unknown=True)
     def func_executable(self, node: mparser.BaseNode,
                         args: T.Tuple[str, SourcesVarargsType],
                         kwargs: kwtypes.Executable) -> build.Executable:
+        for_machine = kwargs['native']
+        m = self.environment.machines[for_machine]
+        if m.is_android() and kwargs.get('android_exe_type') == 'application':
+            holder = self.build_target(node, args, self._strip_exe_specific_kwargs(kwargs), build.SharedLibrary)
+            holder.shared_library_only = True
+            return holder
         return self.build_target(node, args, kwargs, build.Executable)
 
     @permittedKwargs(build.known_stlib_kwargs)
diff --git a/mesonbuild/interpreter/kwargs.py b/mesonbuild/interpreter/kwargs.py
index c0b74405f6f1..fb34bbbce9c8 100644
--- a/mesonbuild/interpreter/kwargs.py
+++ b/mesonbuild/interpreter/kwargs.py
@@ -393,6 +393,7 @@ class Executable(_BuildTarget):
     pie: T.Optional[bool]
     vs_module_defs: T.Optional[T.Union[str, File, build.CustomTarget, build.CustomTargetIndex]]
     win_subsystem: T.Optional[str]
+    android_exe_type: T.Optional[Literal['application', 'executable']]
 
 
 class _StaticLibMixin(TypedDict):
diff --git a/mesonbuild/interpreter/type_checking.py b/mesonbuild/interpreter/type_checking.py
index 1c9953ee4153..78938ba9cd0c 100644
--- a/mesonbuild/interpreter/type_checking.py
+++ b/mesonbuild/interpreter/type_checking.py
@@ -701,6 +701,12 @@ def _convert_darwin_versions(val: T.List[T.Union[str, int]]) -> T.Optional[T.Tup
         convertor=lambda x: x.lower() if isinstance(x, str) else None,
         validator=_validate_win_subsystem,
     ),
+    KwargInfo(
+        'android_exe_type',
+        (str, NoneType),
+        validator=in_set_validator({'application', 'executable'}),
+        since='1.8.0'
+    ),
 ]
 
 # The total list of arguments used by Executable
diff --git a/run_project_tests.py b/run_project_tests.py
index 0dc287191f21..fa7c8a6eb3dd 100755
--- a/run_project_tests.py
+++ b/run_project_tests.py
@@ -76,7 +76,7 @@ class ArgumentType(CompilerArgumentType):
         v: bool
 
 ALL_TESTS = ['cmake', 'common', 'native', 'warning-meson', 'failing-meson', 'failing-build', 'failing-test',
-             'keyval', 'platform-osx', 'platform-windows', 'platform-linux',
+             'keyval', 'platform-osx', 'platform-windows', 'platform-linux', 'platform-android',
              'java', 'C#', 'vala', 'cython', 'rust', 'd', 'objective c', 'objective c++',
              'fortran', 'swift', 'cuda', 'python3', 'python', 'fpga', 'frameworks', 'nasm', 'wasm', 'wayland',
              'format',
@@ -1123,6 +1123,8 @@ def __init__(self, category: str, subdir: str, skip: bool = False, stdout_mandat
         TestCategory('platform-osx', 'osx', not mesonlib.is_osx()),
         TestCategory('platform-windows', 'windows', not mesonlib.is_windows() and not mesonlib.is_cygwin()),
         TestCategory('platform-linux', 'linuxlike', mesonlib.is_osx() or mesonlib.is_windows()),
+        # FIXME, does not actually run in CI, change to run the test if an Android cross toolchain is detected.
+        TestCategory('platform-android', 'android', not mesonlib.is_android()),
         TestCategory('java', 'java', backend is not Backend.ninja or not have_java()),
         TestCategory('C#', 'csharp', skip_csharp(backend)),
         TestCategory('vala', 'vala', backend is not Backend.ninja or not shutil.which(os.environ.get('VALAC', 'valac'))),
diff --git a/test cases/android/1 exe_type/exe_type.c b/test cases/android/1 exe_type/exe_type.c
new file mode 100644
index 000000000000..cd9ca7db9479
--- /dev/null
+++ b/test cases/android/1 exe_type/exe_type.c	
@@ -0,0 +1,5 @@
+#include <stdio.h>
+
+int main(void) {
+    return 0;
+}
diff --git a/test cases/android/1 exe_type/meson.build b/test cases/android/1 exe_type/meson.build
new file mode 100644
index 000000000000..5b0e64a5182d
--- /dev/null
+++ b/test cases/android/1 exe_type/meson.build	
@@ -0,0 +1,15 @@
+project('android exe type', 'c')
+fs = import('fs')
+
+e = executable('executable', 'exe_type.c',
+  android_exe_type : 'executable')
+a = executable('application', 'exe_type.c',
+  android_exe_type : 'application')
+
+if fs.name(e.full_path()).contains('.')
+  error('Executable with exe_type `executable` did have expected filename')
+endif
+
+if not fs.name(a.full_path()).startswith('lib') or not fs.name(a.full_path()).endswith('.so')
+  error('Executable with exe_type `application` did not have expected filename')
+endif


Reviews:
user: login: jpakkane
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-16T09:33:37Z
user: login: sp1ritCS
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T10:45:25Z
user: login: jpakkane
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-16T10:57:25Z
user: login: dcbaker
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-04-16T16:11:27Z
user: login: sp1ritCS
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T16:15:07Z
user: login: dcbaker
body: Thanks for making those changes, with the CI turned green this looks good to me.
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-16T17:23:02Z

Review comments:
diff_hunk: @@ -1123,6 +1123,7 @@ def __init__(self, category: str, subdir: str, skip: bool = False, stdout_mandat
         TestCategory('platform-osx', 'osx', not mesonlib.is_osx()),
         TestCategory('platform-windows', 'windows', not mesonlib.is_windows() and not mesonlib.is_cygwin()),
         TestCategory('platform-linux', 'linuxlike', mesonlib.is_osx() or mesonlib.is_windows()),
+        TestCategory('platform-android', 'android', not mesonlib.is_android()),
path: run_project_tests.py
user: login: jpakkane
body: This will never actually run. `is_android` means "is the machine I'm currently running on an Android machine", which would only be true when running on an Android device and most (all?) builds for Android are done by cross compilation. I don't think we have the Android NDK installed on any CI machines either so the test could not be run in any case.
created_at: 2025-04-16T09:33:36Z
author_association: MEMBER
diff_hunk: @@ -1123,6 +1123,7 @@ def __init__(self, category: str, subdir: str, skip: bool = False, stdout_mandat
         TestCategory('platform-osx', 'osx', not mesonlib.is_osx()),
         TestCategory('platform-windows', 'windows', not mesonlib.is_windows() and not mesonlib.is_cygwin()),
         TestCategory('platform-linux', 'linuxlike', mesonlib.is_osx() or mesonlib.is_windows()),
+        TestCategory('platform-android', 'android', not mesonlib.is_android()),
path: run_project_tests.py
user: login: sp1ritCS
body: Uh, that's bad. Any suggestion on how this could be made to work?
created_at: 2025-04-16T10:45:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1123,6 +1123,7 @@ def __init__(self, category: str, subdir: str, skip: bool = False, stdout_mandat
         TestCategory('platform-osx', 'osx', not mesonlib.is_osx()),
         TestCategory('platform-windows', 'windows', not mesonlib.is_windows() and not mesonlib.is_cygwin()),
         TestCategory('platform-linux', 'linuxlike', mesonlib.is_osx() or mesonlib.is_windows()),
+        TestCategory('platform-android', 'android', not mesonlib.is_android()),
path: run_project_tests.py
user: login: jpakkane
body: Maybe just add a comment like:

    # FIXME, does not actually run in CI, change to run the test if an Android cross toolchain is detected.

created_at: 2025-04-16T10:57:25Z
author_association: MEMBER
diff_hunk: @@ -701,6 +708,11 @@ def _convert_darwin_versions(val: T.List[T.Union[str, int]]) -> T.Optional[T.Tup
         convertor=lambda x: x.lower() if isinstance(x, str) else None,
         validator=_validate_win_subsystem,
     ),
+    KwargInfo(
+        'android_exe_type',
+        (str, NoneType),
+        validator=_validate_android_exe_type,
path: mesonbuild/interpreter/type_checking.py
user: login: dcbaker
body: ```suggestion
        validator=in_set_validator({'application', 'executable'}),
```
and drop the hand rolled validator.
created_at: 2025-04-16T16:06:29Z
author_association: MEMBER
diff_hunk: @@ -393,6 +393,7 @@ class Executable(_BuildTarget):
     pie: T.Optional[bool]
     vs_module_defs: T.Optional[T.Union[str, File, build.CustomTarget, build.CustomTargetIndex]]
     win_subsystem: T.Optional[str]
+    android_exe_type: T.Optional[str]
path: mesonbuild/interpreter/kwargs.py
user: login: dcbaker
body: ```suggestion
    android_exe_type: T.Optional[Literal['application', 'executable']]
```

This can later propagate down to help catch typos 
created_at: 2025-04-16T16:08:06Z
author_association: MEMBER
diff_hunk: @@ -3433,14 +3433,17 @@ def build_target(self, node: mparser.BaseNode, args: T.Tuple[str, SourcesVarargs
         if targetclass is build.Executable:
             kwargs = T.cast('kwtypes.Executable', kwargs)
             if kwargs['gui_app'] is not None:
-                if kwargs['win_subsystem'] is not None:
+                if kwargs['win_subsystem'] is not None or kwargs['android_exe_type'] is not None:
                     raise InvalidArguments.from_node(
-                        'Executable got both "gui_app", and "win_subsystem" arguments, which are mutually exclusive',
+                        'Executable got both "gui_app", and "win_subsystem"/"android_exe_type" arguments, which are mutually exclusive',
path: mesonbuild/interpreter/interpreter.py
user: login: dcbaker
body: ```suggestion
                        'Executable got both "gui_app" and at least one of: "win_subsystem" or "android_exe_type" arguments, which are mutually exclusive',
```
created_at: 2025-04-16T16:08:39Z
author_association: MEMBER
diff_hunk: @@ -2043,6 +2051,8 @@ def post_init(self) -> None:
             else:
                 self.suffix = machine.get_exe_suffix()
         self.filename = self.name
+        if self.prefix:
+            self.filename = self.prefix + self.filename
path: mesonbuild/build.py
user: login: dcbaker
body: This seems unrelated?
created_at: 2025-04-16T16:09:14Z
author_association: MEMBER
diff_hunk: @@ -3376,8 +3376,13 @@ def get_target_type_link_args(self, target, linker):
             # If implib, and that's significant on this platform (i.e. Windows using either GCC or Visual Studio)
             if target.import_filename:
                 commands += linker.gen_import_library_args(self.get_import_filename(target))
-            if target.pie:
-                commands += linker.get_pie_link_args()
+            m = self.environment.machines[target.for_machine]
+            if m.is_android() and target.android_exe_type == 'application':
+                commands += linker.get_pic_args()
+                commands += linker.get_std_shared_module_link_args(target)
+            else:
+                if target.pie:
+                    commands += linker.get_pie_link_args()
path: mesonbuild/backend/ninjabackend.py
user: login: dcbaker
body: ```suggestion
            elif target.pie:
                commands += linker.get_pie_link_args()
```
created_at: 2025-04-16T16:10:25Z
author_association: MEMBER
diff_hunk: @@ -21,6 +21,17 @@ varargs_inherit: _build_target_base
 kwargs_inherit: _build_target_base
 
 kwargs:
+  android_exe_type:
+    type: str
+    default: "'executable'"
+    since: 1.6.0
path: docs/yaml/functions/executable.yaml
user: login: dcbaker
body: ```suggestion
    since: 1.8.0
```
created_at: 2025-04-16T16:10:39Z
author_association: MEMBER
diff_hunk: @@ -701,6 +708,11 @@ def _convert_darwin_versions(val: T.List[T.Union[str, int]]) -> T.Optional[T.Tup
         convertor=lambda x: x.lower() if isinstance(x, str) else None,
         validator=_validate_win_subsystem,
     ),
+    KwargInfo(
+        'android_exe_type',
+        (str, NoneType),
path: mesonbuild/interpreter/type_checking.py
user: login: dcbaker
body: This also needs: `since='1.8.9'`
created_at: 2025-04-16T16:11:05Z
author_association: MEMBER
diff_hunk: @@ -2043,6 +2051,8 @@ def post_init(self) -> None:
             else:
                 self.suffix = machine.get_exe_suffix()
         self.filename = self.name
+        if self.prefix:
+            self.filename = self.prefix + self.filename
path: mesonbuild/build.py
user: login: sp1ritCS
body: this was required for the 'lib' prefix 
created_at: 2025-04-16T16:15:07Z
author_association: CONTRIBUTOR

PR comments:
user: login: jpakkane
created_at: 2024-10-13T20:29:26Z
body: Could you not already do this with [the `build_target` function](https://mesonbuild.com/Reference-manual_functions.html#build_target)?
author_association: MEMBER
user: login: sp1ritCS
created_at: 2024-10-13T21:10:28Z
body: Perhaps. However my goal is to have a [builder tool](https://github.com/sp1ritCS/gtk-android-builder/blob/master/pixiewood) that you can just point at a meson project to create an apk from.

While you still have to modify your meson sources and to add the android_usecase, it seems less invasive than doing having users switch their excecutables to `build_target(target_type: host_machine.os() == android ? 'shared_module' : 'executable', ...)`, especially given that they will have to add the android glue dependency too (declaring a link_whole dependency with it and adding it does seem to work within meson, not sure why cmake didn't like it)

What about adding a `excecutable` function to the gnome module that has the same prototype as a normal executable but that creates a library linked against the glue dependency if host_machine == 'android'?
author_association: CONTRIBUTOR
user: login: jpakkane
created_at: 2025-03-05T16:15:14Z
body: Since you seem to have knowledge about this could you answer some questions I have about this?

How are Android applications actually done? FWICT it used to be the case that you could not have actual executables, instead it needed to be a shared library which you then opened from the Java side with whatever the equivalent of `dlopen` is. Can you now have actual executables? Can there be several? Can you invoke many processes within your sandbox? Is it possible (and if yes, do you intend to use) to make an Android application that is just native code without any Java/JVM?
author_association: MEMBER
user: login: sp1ritCS
created_at: 2025-03-05T17:24:14Z
body: It was always possible to have actual 'executables' on Android. Its just Linux after all and `/system/bin/` contains a bunch of them the system relies on to work.

The thing is that users usually don't interact with them directly (outside of developers using `adb shell`) and executables do not have the possibility of interacting with the Android system (unless they [use private non-stable interfaces](https://github.com/Bzi-Han/AndroidSurfaceImgui/blob/main/modules/ANativeWindowCreator.h)), it is however possible for Applications to do the pipe+fork+exec combo to execute their own executables as child process. Doing so is discouraged however, with Google increasingly limiting its possibilities ([phantom process killer](https://issuetracker.google.com/issues/205156966), [SELinux rules that enfore R^X for exec](https://issuetracker.google.com/issues/128554619)). [Termux](https://termux.dev/) however makes heavy use of fork+exec to provide a POSIX like shell experience and is relatively popular as it allows you to use full SSH/tmux/vim/... on Android.

> Is it possible (and if yes, do you intend to use) to make an Android application that is just native code without any Java/JVM?

No, you can make executables not applications. Applications are still special (Zygote) and need Java (ART) to interact with the stable OS interfaces provided.

My work of porting GTK to Android proper, needs an "Application" to work with and essentially works by having glue expose the proper JNI symbol and then [calls `main()`](https://gitlab.gnome.org/GNOME/gtk/-/blob/main/gdk/android/glue/glue.c?ref_type=heads#L117) from the actual codebase in a new thread.
author_association: CONTRIBUTOR
user: login: bruchar1
created_at: 2025-03-05T18:21:33Z
body: Why don't you simply add a user option or a property to the cross file, and use it for the target type?
author_association: MEMBER
user: login: sp1ritCS
created_at: 2025-03-05T18:49:41Z
body: > Why don't you simply add a user option or a property to the cross file, and use it for the target type?

My idea was need as little changes as possible to the meson files of existing applications and this was just my initial attempt. A better (but gtk specific solution) is proposed in #13800. It cuts down the currently needed `build_target(host_machine.system() == 'android' ? 'shared_library' : 'executable', '...', dependencies: [...] + host_machine.system() == 'android' ? dependency('gtk4-android-glue') : [], ...)` to `import('gnome').executable(..., dependencies: [...], ...)`
author_association: CONTRIBUTOR
user: login: jpakkane
created_at: 2025-03-06T15:13:31Z
body: So, basically, the actual thing that you need to get is "build this target as an executable on all other platforms, but as a shared library in Android"? Are there any other use cases that need to be covered?

If true, then this could be implemented in one of several ways:

- tell people to use `build_target` and set the target type manually
- add some sort of module, custom function, etc that does the above
- add this (or some similar) kwarg to handle the issue
- something else, what?

The kwarg in this MR is similar to how `win_subsystem` works which is good for consistency.
author_association: MEMBER
user: login: sp1ritCS
created_at: 2025-03-07T12:24:51Z
body: > Are there any other use cases that need to be covered?

There should be some mechanism that link-wholes a glue library (that provides the JNI entry function symbol) into the shared library. The idea I had originally for this MR was to have the ability to specify the dependency name in the cross file, but I didn't really look into how to implement it specifically. Obv. the alternative GNOME/GTK specific PR doesn't need that as it can just lookup the `gtk4-android-glue` dependency directly (for now).

> The kwarg in this MR is similar to how win_subsystem works which is good for consistency.

That was the idea behind it :)
author_association: CONTRIBUTOR
user: login: jpakkane
created_at: 2025-04-13T10:37:42Z
body: I'm not very fond of the name `android_usecase`. It is not very descriptive, though I have problems trying to come up with a better name (as is usually the case). `android_exe` is similarly poor.  `android_executable_type` is too long. Does `android_exe_type` sound good for people?
author_association: MEMBER
user: login: sp1ritCS
created_at: 2025-04-13T20:41:52Z
body: Sure, `android_exe_type` is fine too.

About the issue of needing to include glue code: I've taken a closer look at how QT does it, and it looks like they initialize themselves in the `JNI_OnLoad` of the libqt and the libqtandroid plugin and to launch the actual user code they require the shared library name containing the application main() to be specified in the ApplicationManifest.xml with a `<meta-data>` tag. [They then load the application shared library using dlopen/dlsym to run the main function](https://github.com/qt/qtbase/blob/ce38df4461d09793de675b6161225fc86e5168f4/src/plugins/platforms/android/androidjnimain.cpp#L381). This way no glue code is needed on the application side.

I think I should be able to implement a similar mechanism for GTK, which means the the changes of this MR would be enough to make it acceptably work. The downsides of this however would be:
- Less logical ordering: GTK is going to be responsible to be loading the application which itself is going to use GTK.
- Using dlopen/dlsym is going to be more error prone instead of letting the linker do it for you (and any issues will only be detected at runtime)
- Unlike statically linking the glue code will call main into the application, it must be made sure that the `main` symbol will actually be exported, which isn't the case by default if people have `add_project_arguments('-fvisibility=hidden', ...])`. (This should be able to be remedied by ~~modifying the cross file to add a linker script~~, meson should probably add that linker script in that case)
- It forces GTK to be a shared library (due to the required JNI_OnLoad symbol). But GTK currently only works as a shared library anyways, so this may only end up becoming a future issue. 

EDIT: I'm seemingly not able to get the symbol visible with a version script, as a) it seemingly only operates on non-hidden symbols, so it seemingly fully ignores main and b) it hides all the other symbols that were previously explicitly marked visible. Any ideas if this can somehow be fixed without requiring the main function to be annotated with attribute(visibility(default))?
author_association: CONTRIBUTOR
user: login: jpakkane
created_at: 2025-04-14T14:45:01Z
body: Assuming we merge this can the listed issues be fixed later once they actually come up without breaking bw compatibility with this change?
author_association: MEMBER
user: login: sp1ritCS
created_at: 2025-04-14T15:13:16Z
body: The only *real* issue that can be fixed is the one about the hidden main symbol (the other ones are more "technical limitations" one has to live with).

The hidden main symbol should be fixed however (as the problem will be encountered immediately due to the gtk4-demo) because adding `android_exe_type` to meson - that just makes built applications useless because they don't provide any symbols doesn't - make much sense.

I've wondered if it may be possible to generate and add with `-include` a header that declares the main() function with visibility default. The issue with that is, that is somehow has to cope with `main(void)`, `main(int argc, char **argv)` and `main(int argc, char **argv, char **envp)`.
author_association: CONTRIBUTOR
user: login: jpakkane
created_at: 2025-04-14T16:04:42Z
body: Can the change be done basically with this:

```
#ifdef __ANDROID__
__attribute__((visibility("default")))
#endif
int main(...) {
    ...
```

If yes, this seems like the simplest solution.
author_association: MEMBER
user: login: sp1ritCS
created_at: 2025-04-14T16:55:51Z
body: Sure that'd work too (realistically the \_\_ANDROID\_\_ isn't even needed), but it would require affected users to update their code.
author_association: CONTRIBUTOR
user: login: jpakkane
created_at: 2025-04-16T13:19:15Z
body: Another thing that came to mind. Would it be possible to implement this in a simpler way? Namely:

- In `func_executable` call check if we are building for Android and the exe type is set to `application`
- if yes, remove `android_exe_type` from the kwarg list and call `func_shared_library` 

This would save us from adding a lot of `if/else`s in the code as the current implementation does.
author_association: MEMBER
user: login: sp1ritCS
created_at: 2025-04-16T13:42:26Z
body: Sure, that might work too. It would however require us to strip **any** executable specific kwargs (gui_app, win_subsystem, export_dynamic?), not just android_exe_type, as shared_library would complain about it otherwise

I guess I can just hard code them for now, but the current solution with what kwargs apply to specific targets compared to all targets (and build_target) isn't particularly scalable.
author_association: CONTRIBUTOR
user: login: jpakkane
created_at: 2025-04-16T13:49:44Z
body: There should be a named method like `strip_exe_specific_kwargs` that is called. Then it would be both readable and easily fixable in the future if things change.
author_association: MEMBER
user: login: sp1ritCS
created_at: 2025-04-16T15:02:40Z
body: I'm facing a weird issue with that, and I suspect its due to annotation magic which I don't understand.

I have:

```py
def _strip_exe_specific_kwargs(self, kwargs: kwtypes.Executable) -> kwtypes._BuildTarget:
    kwargs = kwargs.copy()
    for exe_kwarg in _EXCLUSIVE_EXECUTABLE_KWS:
        del kwargs[exe_kwarg.name]
    return kwargs

@permittedKwargs(build.known_exe_kwargs)
@typed_pos_args('executable', str, varargs=SOURCES_VARARGS)
@typed_kwargs('executable', *EXECUTABLE_KWS, allow_unknown=True)
def func_executable(self, node: mparser.BaseNode,
                    args: T.Tuple[str, SourcesVarargsType],
                    kwargs: kwtypes.Executable) -> build.Executable:
    for_machine = kwargs['native']
    m = self.environment.machines[for_machine]
    if m.is_android() and kwargs.get('android_exe_type') == 'application':
        return self.func_shared_lib(node, args, self._strip_exe_specific_kwargs(kwargs))
    return self.build_target(node, args, kwargs, build.Executable)
    
[...]
    
@permittedKwargs(build.known_shlib_kwargs)
@typed_pos_args('shared_library', str, varargs=SOURCES_VARARGS)
@typed_kwargs('shared_library', *SHARED_LIB_KWS, allow_unknown=True)
def func_shared_lib(self, node: mparser.BaseNode,
                    args: T.Tuple[str, SourcesVarargsType],
                    kwargs: kwtypes.SharedLibrary) -> build.SharedLibrary:
    holder = self.build_target(node, args, kwargs, build.SharedLibrary)
    holder.shared_library_only = True
    return holder
```

but running the testcase results in the following:
```
[...]
  File "/home/florian/workspace/Android/meson/mesonbuild/interpreterbase/interpreterbase.py", line 536, in function_call
    res = func(node, func_args, kwargs)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/florian/workspace/Android/meson/mesonbuild/interpreterbase/decorators.py", line 103, in wrapped
    return f(*wrapped_args, **wrapped_kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/florian/workspace/Android/meson/mesonbuild/interpreterbase/decorators.py", line 237, in wrapper
    return f(*nargs, **wrapped_kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/florian/workspace/Android/meson/mesonbuild/interpreterbase/decorators.py", line 556, in wrapper
    return f(*wrapped_args, **wrapped_kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/florian/workspace/Android/meson/mesonbuild/interpreter/interpreter.py", line 1835, in func_executable
    return self.func_shared_lib(node, args, self._strip_exe_specific_kwargs(kwargs))
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/florian/workspace/Android/meson/mesonbuild/interpreterbase/decorators.py", line 103, in wrapped
    return f(*wrapped_args, **wrapped_kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/florian/workspace/Android/meson/mesonbuild/interpreterbase/decorators.py", line 178, in wrapper
    assert isinstance(args, list), args
           ^^^^^^^^^^^^^^^^^^^^^^
AssertionError: ('application', ['exe_type.c'])
```

any idea why?
author_association: CONTRIBUTOR
user: login: bruchar1
created_at: 2025-04-16T15:27:18Z
body: 
> any idea why?

You cannot call `func_shared_lib`. It is an interpreter function. You should call `build_target` instead.

author_association: MEMBER
user: login: dcbaker
created_at: 2025-04-16T17:22:35Z
body: that assert needs to be `None` aware `t is not None or t in {...}`

And please do use the set, the python byte compiler can do some optimization around that which makes the lookup faster.
author_association: MEMBER
user: login: sp1ritCS
created_at: 2025-04-16T17:36:45Z
body: @dcbaker I've seen that. I'm just wondering why its necessary, given that `interpreter.py` should set android_exe_wrapper if it isn't set.
author_association: CONTRIBUTOR
user: login: jpakkane
created_at: 2025-04-16T18:03:38Z
body: ```
   File "/__w/meson/meson/mesonbuild/build.py", line 1999, in __init__
    assert kwargs.get('android_exe_type') in ['application', 'executable']
AssertionError
```

If the key is not in dict, `.get` returns `None`.
author_association: MEMBER
user: login: jpakkane
created_at: 2025-04-16T19:24:31Z
body: In case this won't be ready for rc1, it can be merged after that.
author_association: MEMBER
user: login: sp1ritCS
created_at: 2025-04-16T20:24:26Z
body: > In case this won't be ready for rc1, it can be merged after that.

whats the release schedule like? Any chance I can get #13854 looked at before release? I could work around it (by running `meson subprojects update` before invoking meson) but it be nicer if I didn't and could save myself from downloading subprojects that I don't need to to my configure options / platform.
author_association: CONTRIBUTOR
user: login: eli-schwartz
created_at: 2025-04-17T16:10:09Z
body: > > In case this won't be ready for rc1, it can be merged after that.
> 
> whats the release schedule like? Any chance I can get #13854 looked at before release? I could work around it (by running `meson subprojects update` before invoking meson) but it be nicer if I didn't and could save myself from downloading subprojects that I don't need to to my configure options / platform.

Release freeze starts when rc1 is tagged and published. Roughly ~weekly release candidates until we are happy with the results and ready for final release. Only bugfixes and documentation changes allowed for the most part, new features need an explicit Release Freeze exemption from @jpakkane.

That PR you linked may be safe to backport though, even if it misses the final release.
author_association: MEMBER