In Android entry point is ANativeActivity_onCreate and there is no such thing as binary executable, everything is shared library. So I have code something like this:
How are you executing the Swift code? Are you using the Swift Package Manager and an executable target (swift run --flag value), using swiftc, the Run button, or something else?
I use swift swift build to get libApplication.so shared library. Then pack all Swift runtime shared libraries and mine libApplication.so to .apk. On Android there is no such thing as binary executable, it runs your libApplication.so defined in AndroidManifest.xml by calling ANativeActivity_onCreate function in libApplication.so. So entry point is ANativeActivity_onCreate not main. I created C ANativeActivity_onCreate where I call Swift main. I pass arguments to main by my own as terminal would do if I ran it with swift run, but CommandLine.arguments is empty.
For example iOS executable uses CommandLine.arguments[0] to get actual executable path, I wanted to make something similar on Android.
How swift-argument-parser would help me if it uses CommandLine.arguments which is empty.
If I am understanding you correctly, you created something else that calls your Swift's main.
If that is true, my guess is the CommandLine.arguments is empty because nothing was passed to it since you didn't use Swift directly to do anything (or didn't pass the arguments to the process correctly), and called the main function from a different process. You need to invoke Swift somehow (or get the arguments some other way from your other process), like using an executable target via the Swift Package Manager and execute swift run <arguments> or by using swift-argument-parser.
Without knowing the exact setup I, and others, can barely help. Do you have a repo with a demo of what you're trying to get working? Why are you working with .so files? Are you trying to do Swift x Android interoperability or using Swift Embedded?
I've been developing some projects using the Swift Package Manager utilizing the CommandLine.arguments and swift-argument-parser for different workflows without any problems.
swift run and swift-argument-parser is not a thing here at all.
swift run do not needed because swift run just finds your binary executable in your .build folder and runs like you can do. Just run .build/<debug/release>/<target name> <arguments> and you will get same thing as swift run <arguments> do.
swift-argument-parser is not related here because it is just sugar wrapper around CommandLine.arguments from standard library.
Why are you working with .so files?
Because Android can run only .so libraries by finding symbol ANativeActivity_onCreate in it. Android can run binary executable only from shell. I run Swift from app archive .apk by calling main manualy.
The question is why CommandLine.arguments is empty when I call main and it's arguments manually?
Let's forget about Android and shared library here is my any platform and binary executable example: swift run -Xswiftc -enable-experimental-feature -Xswiftc Extern -Xlinker -e -Xlinker _reset
I'm looking at the standard library code now. I found that CommandLine is populated with the values from this C++ function getUnsafeArgvArgc:
// NOTE: forward declare this rather than including crt_externs.h as not all
// SDKs provide it
extern "C" char ***_NSGetArgv(void);
extern "C" int *_NSGetArgc(void);
static char **swift::getUnsafeArgvArgc(int *outArgLen) {
*outArgLen = *_NSGetArgc();
return *_NSGetArgv();
}
A code search on github finds no references to _NSGetArgc outside of this file so I'm at a bit of a loss now. It's possible that the Android SDK implements it like
int *_NSGetArgc(void) {
static int argc = 0;
return &argc;
}
The implementation you're looking at here is guarded by #if defined(__APPLE__) (i.e. only used on Darwin platforms). There are other platform implementations below — I would assume Android uses the Linux implementation, but it's possible this fails to correctly grab the args at runtime.
According to Rust, this is a non-standard extension of glibc.
But I tested the C++ version on Android, it works.
I also tested the Swift version on Ubuntu.
I tried compiling Swift codes for Android but wasn't familiar with how to do it (and failed), but it should work fine.
If this is something we're not handling correctly in the runtime on Android, we can add an Android-specific section of the C++ implementation that does the right thing.
Would be good to get a GitHub issue filed for this. @Finagolfin, any thoughts on this issue?
If you want succeed in compiling Swift for Android just check @Finagolfin’s github. He made an amazing job and provided cool sdk. With this sdk you can build Swift for Android with only one line.
You can make SwiftPM executableTarget and build it with sdk. Copy result binary and c++ runtime to device/emulator temperate folder adb push <binary> libc++_shared.so /data/local/tmp and run it with adb shell && cd /data/local/tmp && ./<binary>.
For myself I created script, that compiles SwiftPM directly to .apk, may be one day I would release it publicly.
Everything is correct, there is just no such thing as argc, argv and main entry point on Android Native Activity (full Swift apk without single line of Java).
If run Swift as terminal program, I believe everything would work fine.
This can happen if you are calling setenv() or putenv() from a constructor function or from a static C++ initialiser or similar. Doing that is crazy, so you probably shouldn't be doing it — there was an issue with OpenMPI where they were attempting to set ZES_ENABLE_SYSMAN from a constructor because of the behaviour of an Intel driver, which you can fall foul of if you're trying to use that package or something that depends on it (OpenCV, for instance). If you're seeing that, the OpenMPI project actually has a fix for it, but as a workaround you can make sure you always explicitly set ZES_ENABLE_SYSMAN=1 before running your program.
This is an unfortunate consequence of the way we grab the arguments on Linux; prior to the current code, we did it by attempting to parse them from /proc/self/cmdline, but that is itself problematic… First, due to an OS bug, Docker on macOS was causing programs to get the wrong data when doing that, and we needed to ship a fix without waiting for an OS release. Second, the data there can be overwritten, in which case we'll also get the wrong results. In order to fix the former, we changed approach to grab the data from the area the kernel writes it into, but this only works if the environment pointer hasn't changed by the time we get to look at it. In practice, this is true unless you mess with the environment early in program start-up, which is a reasonable trade-off because that's a super odd thing to be doing.
Thanks for pinging me: I saw this post title but not the tag, so I didn't think it had anything to do with Android and hadn't bothered reading it.
@purpln, I'm still unclear on why you want to run an executable from an Android apk. As you note, cross-compiling Swift executables using my Android SDK bundle and pushing them to Android using adb or the Termux CLI app works fine today. I don't see a reason to run executables as part of a non-Termux Android apk though, which may be why a few features like this don't work right now.
If you want to run Swift or any compiled code really in an Android apk, the way to do that is to build a native shared library. See the C/C++ examples in the official Android NDK samples, which are called from an Android apk through JNI. There is some support for running Android executables too, probably primarily for legacy codebases that may only have binary executables lying around, but it's not expected to run executables as part of Android apks.
With your sdk I have successfully got fully working Android apk without any Java, JNI, gradle, Android Studio and other things. I am iOS dev I don't know what all this things are:). In my shell scripts I use only sdkmanager, ndk and adb.
You wrote about official Android examples to call native code from Java environment. I know about them and this is not my goal. I am using another approach. I use not well-known method called Android Native Activity.
How Native Activity works:
I have !dynamic! SwiftPM with two targets Application and NativeAppGlue;
Application - target with Swift @main entry point;
NativeAppGlue - C target with android_main function and google's android_native_app_glue code;
In C android_main I call Swift main;
After swift build --swift-sdk I get libApplication.so;
I pack all Swift runtime and my dynamic library as lib/<arch>/lib<name>.so;
In AndroidManifest.xml for activity android.app.NativeActivity I've put android.app.lib_name key with Application value (SwiftPM Target name where I have Swift @main entry point and android_native_app_glue) and mark this activity android.intent.action.MAIN and android.intent.category.LAUNCHER;
How Android launches such applications:
In AndroidManifest it finds android.app.lib_name;
With this key finds required library in lib/<arch>/lib<name>.so;
In this library it searches for ANativeActivity_onCreate symbol and calls it;
In ANativeActivity_onCreate calls function android_app_create -> android_app_entry -> android_main -> main;
So that's how I get fully native app running. There is no such thing as binary executable in Android apk environment. That's why argc and argv are empty. I wanted to fill them by myself.
Why I have chose this route? - I wanted to unify entry point and made truly Swift only (with some C) cross-platform app for Darwin, Linux and Android.
Now I am in process of trying putting pixels into the window.
Sure, I'm familiar with it, have built small Java-free or mostly Java-free Android apps with that before. Take a look at the pure C++ sample app in the NDK samples repo I linked you earlier, which uses a native activity alone, ie zero Java.
I don't understand this part: you want to create a Swift command-line executable that someone would invoke from a terminal, possibly with command-line arguments, that launches a GUI window? That's obviously possible on linux, and maybe macOS too, so you want to reuse that code on Android and launch it how, from Termux?
On Android, I think you'd be best off forgetting about the command-line if you're launching a GUI app, ie simply add a different Android-specific entry point like the android_main() we discussed above and use some Android GUI controls instead to set whatever options you're setting with command-line arguments on linux.
Haven't seen that before, thanks. I was mostly googling ANativeActivity_onCreate on GitHub rather then android_main as in example google provides.
I used same code above in this topic trying to express myself and what I mean.
I have 4 targets in my SwiftPM:
Application - @main entry point that calls run() function and imports Android, Linux, Apple targets depending on #os();
Android - with android_native_app_glue, ndk and android_main, where I made Android specific function run() with ALooper_pollOnce runloop;
Linux - Linux specific function run() with X11 runloop;
Apple - Apple specific function run() with UIApplicationMain;
That's how I made 3 platform specific runloop and I hope will get window with simple triangle on all of them. I plan to use Application target for unified code that's why I need unified entry point.
I want simple pure Swift (C) cross-platform GUI app, not command-line app. Why then I talk about CommandLine.arguments? - Because it's like unspoken rule for first argument to be application path even for non command-line apps. Just wanted to make same thing for Android.
OK, but in your Swift Application target, can't you just use android_main() as your entry point for Android, and only use @main for the other platforms? I think that should work, unless SwiftPM interferes in some way.
OK, you should've explained all this in the beginning, as it didn't made any sense to ask for command-line arguments for an Android GUI app.
I guess the next question is why you want the application path alone? I believe there are other C APIs to get binary paths, and it doesn't really matter if CommandLine.arguments doesn't work for Android GUI apps.
Swift Testing needs to extract the path to the test executable on various platforms and we have platform-specific code to do so. Android poses a special challenge because it's not entirely clear what the executable should be (although for our purposes we assume it's whatever's at /proc/self/exe on Linux.)
This is a useful feature to have distinct from the full set of command-line arguments. It's worth a GitHub issue to add it to the stdlib.