-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Port to Android #1442
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Port to Android #1442
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,7 +30,8 @@ static const StringRef SupportedConditionalCompilationOSs[] = { | |
"iOS", | ||
"Linux", | ||
"FreeBSD", | ||
"Windows" | ||
"Windows", | ||
"Android" | ||
}; | ||
|
||
static const StringRef SupportedConditionalCompilationArches[] = { | ||
|
@@ -105,6 +106,8 @@ std::pair<bool, bool> LangOptions::setTarget(llvm::Triple triple) { | |
addPlatformConditionValue("os", "watchOS"); | ||
else if (triple.isiOS()) | ||
addPlatformConditionValue("os", "iOS"); | ||
else if (triple.isAndroid()) | ||
addPlatformConditionValue("os", "Android"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This change needs a test like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done! I added |
||
else if (triple.isOSLinux()) | ||
addPlatformConditionValue("os", "Linux"); | ||
else if (triple.isOSFreeBSD()) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2031,6 +2031,12 @@ const ToolChain *Driver::getToolChain(const ArgList &Args) const { | |
TC = new toolchains::Darwin(*this, Target); | ||
break; | ||
case llvm::Triple::Linux: | ||
if (Target.isAndroid()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that we should specialize GenericUnix into a GNUToolchain or LinuxToolchain and sink the android check into the toolchain. Android is an environment of linux, and the toolchain really is identical across the two. Creating two toolchains feels wrong. This has previously been a problem for modeling windows in clang, and it was a fair amount of effort to clean up afterwards. I think we should draw on our experiences there to improve our choices here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @gribozavr originally commented that an Android check may not be appropriate in a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @compnerd What was the problem in Clang with a hierarchy of toolchains? It seems to me that littering branches everywhere creates very fiddly code, whereas a hierarchy with proper extension points makes it clear which parts might be different. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jrose-apple As you suggested, I'm going to go ahead and create a class hierarchy here. There's a ton of duplication across
Then this pull request could add an additional subclass:
Does that seem reasonable? (Admittedly, it seems strange to have There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Totally agree. One potential wart is the fact that other parts of the codebase will reference things like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I expect There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am also agree renaming the toolchain
Cygwin uses the GNU toolchain and has Unix compatible libraries, but it is not UNIX OS. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We're using it for FreeBSD too, though. I think it's fair to say Cygwin is a Unix-like toolchain even if it's not a Unix-like target. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (Maybe it is a Unix-like target too, and then not a Unix-like OS. But that's not about the Driver.) |
||
TC = new toolchains::Android(*this, Target); | ||
} else { | ||
TC = new toolchains::GenericUnix(*this, Target); | ||
} | ||
break; | ||
case llvm::Triple::FreeBSD: | ||
TC = new toolchains::GenericUnix(*this, Target); | ||
break; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1210,11 +1210,11 @@ std::string toolchains::GenericUnix::getDefaultLinker() const { | |
} | ||
} | ||
|
||
bool toolchains::GenericUnix::shouldProvideRPathToLinker() const { | ||
return true; | ||
std::string toolchains::GenericUnix::getTargetForLinker() const { | ||
return getTriple().str(); | ||
} | ||
|
||
bool toolchains::GenericUnix::shouldSpecifyTargetTripleToLinker() const { | ||
bool toolchains::GenericUnix::shouldProvideRPathToLinker() const { | ||
return true; | ||
} | ||
|
||
|
@@ -1266,9 +1266,10 @@ toolchains::GenericUnix::constructInvocation(const LinkJobAction &job, | |
Arguments.push_back(context.Args.MakeArgString("-fuse-ld=" + Linker)); | ||
} | ||
|
||
// Explicitly pass the target to the linker | ||
if (shouldSpecifyTargetTripleToLinker()) { | ||
Arguments.push_back(context.Args.MakeArgString("--target=" + getTriple().str())); | ||
std::string Target = getTargetForLinker(); | ||
if (!Target.empty()) { | ||
Arguments.push_back("-target"); | ||
Arguments.push_back(context.Args.MakeArgString(Target)); | ||
} | ||
|
||
// Add the runtime library link path, which is platform-specific and found | ||
|
@@ -1340,13 +1341,29 @@ toolchains::GenericUnix::constructInvocation(const LinkJobAction &job, | |
return {"clang++", Arguments}; | ||
} | ||
|
||
std::string | ||
toolchains::Android::getTargetForLinker() const { | ||
// Explicitly set the linker target to "androideabi", as opposed to the | ||
// llvm::Triple representation of "armv7-none-linux-android". | ||
// This is the only ABI we currently support for Android. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just to double-check, there's no arm64/aarch64 Android Swift yet? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're correct, we only support armv7. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's actually assert that then. That way, we'll know very loudly when it changes.
or something. |
||
assert( | ||
getTriple().getArch() == llvm::Triple::arm && | ||
getTriple().getSubArch() == llvm::Triple::SubArchType::ARMSubArch_v7 && | ||
"Only armv7 targets are supported for Android"); | ||
return "armv7-none-linux-androideabi"; | ||
} | ||
|
||
bool toolchains::Android::shouldProvideRPathToLinker() const { | ||
return false; | ||
} | ||
|
||
std::string toolchains::Cygwin::getDefaultLinker() const { | ||
// Cygwin uses the default BFD linker, even on ARM. | ||
return ""; | ||
} | ||
|
||
bool toolchains::Cygwin::shouldSpecifyTargetTripleToLinker() const { | ||
return false; | ||
std::string toolchains::Cygwin::getTargetForLinker() const { | ||
return ""; | ||
} | ||
|
||
std::string toolchains::Cygwin::getPreInputObjectPath( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -53,17 +53,17 @@ class LLVM_LIBRARY_VISIBILITY GenericUnix : public ToolChain { | |
/// and to not provide a specific linker otherwise. | ||
virtual std::string getDefaultLinker() const; | ||
|
||
/// The target to be passed to the compiler invocation. By default, this | ||
/// is the target triple, but this may be overridden to accomodate some | ||
/// platforms. | ||
virtual std::string getTargetForLinker() const; | ||
|
||
/// Whether to specify a linker -rpath to the Swift runtime library path. | ||
/// -rpath is not supported on all platforms, and subclasses may override | ||
/// this method to return false on platforms that don't support it. The | ||
/// default is to return true (and so specify an -rpath). | ||
virtual bool shouldProvideRPathToLinker() const; | ||
|
||
/// Whether to explicitly specify the target triple as the linker | ||
/// '--target'. This is not desirable on all platforms, and subclasses may | ||
/// override this method to return false in those cases. | ||
virtual bool shouldSpecifyTargetTripleToLinker() const; | ||
|
||
/// Provides a path to an object that should be linked first. On platforms | ||
/// that use ELF binaries, an object that provides markers and sizes for | ||
/// metadata sections must be linked first. Platforms that do not need this | ||
|
@@ -96,11 +96,21 @@ class LLVM_LIBRARY_VISIBILITY GenericUnix : public ToolChain { | |
~GenericUnix() = default; | ||
}; | ||
|
||
class LLVM_LIBRARY_VISIBILITY Android : public GenericUnix { | ||
protected: | ||
std::string getTargetForLinker() const override; | ||
|
||
bool shouldProvideRPathToLinker() const override; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Cygwin should need to return We should be able to write platform-independent tests for these, like those in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, nice catch, thanks! (I should really get a Cygwin environment so that I can catch these myself...) Looking into adding tests now. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added tests to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, we probably want to include the negative check on Cygwin. For that you'll need to run FileCheck again on the same input but with a different prefix, something like
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Neat trick! I'll add an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, you can mix CHECK and CHECK-NOT lines, in which case the -NOT line is checking that the output doesn't appear since the previous CHECK (or the beginning of the input) and before the next CHECK (or the end of the input). |
||
public: | ||
Android(const Driver &D, const llvm::Triple &Triple) : GenericUnix(D, Triple) {} | ||
~Android() = default; | ||
}; | ||
|
||
class LLVM_LIBRARY_VISIBILITY Cygwin : public GenericUnix { | ||
protected: | ||
std::string getDefaultLinker() const override; | ||
|
||
bool shouldSpecifyTargetTripleToLinker() const override; | ||
std::string getTargetForLinker() const override; | ||
|
||
std::string getPreInputObjectPath( | ||
StringRef RuntimeLibraryPath) const override; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,7 +16,7 @@ import SwiftPrivateLibcExtras | |
|
||
#if os(OSX) || os(iOS) || os(watchOS) || os(tvOS) | ||
import Darwin | ||
#elseif os(Linux) || os(FreeBSD) | ||
#elseif os(Linux) || os(FreeBSD) || os(Android) | ||
import Glibc | ||
#endif | ||
|
||
|
@@ -1170,6 +1170,7 @@ public enum OSVersion : CustomStringConvertible { | |
case watchOSSimulator | ||
case linux | ||
case freeBSD | ||
case android | ||
|
||
public var description: String { | ||
switch self { | ||
|
@@ -1191,6 +1192,8 @@ public enum OSVersion : CustomStringConvertible { | |
return "Linux" | ||
case freeBSD: | ||
return "FreeBSD" | ||
case android: | ||
return "Android" | ||
} | ||
} | ||
} | ||
|
@@ -1225,6 +1228,8 @@ func _getOSVersion() -> OSVersion { | |
return .linux | ||
#elseif os(FreeBSD) | ||
return .freeBSD | ||
#elseif os(Android) | ||
return .android | ||
#else | ||
let productVersion = _stdlib_getSystemVersionPlistProperty("ProductVersion")! | ||
let (major, minor, bugFix) = _parseDottedVersionTriple(productVersion) | ||
|
@@ -1299,6 +1304,8 @@ public enum TestRunPredicate : CustomStringConvertible { | |
|
||
case freeBSDAny(reason: String) | ||
|
||
case androidAny(reason: String) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This change needs a test like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done! I added |
||
|
||
case objCRuntime(/*reason:*/ String) | ||
case nativeRuntime(/*reason:*/ String) | ||
|
||
|
@@ -1376,6 +1383,9 @@ public enum TestRunPredicate : CustomStringConvertible { | |
case linuxAny(reason: let reason): | ||
return "linuxAny(*, reason: \(reason))" | ||
|
||
case androidAny(reason: let reason): | ||
return "androidAny(*, reason: \(reason))" | ||
|
||
case freeBSDAny(reason: let reason): | ||
return "freeBSDAny(*, reason: \(reason))" | ||
|
||
|
@@ -1620,6 +1630,14 @@ public enum TestRunPredicate : CustomStringConvertible { | |
return false | ||
} | ||
|
||
case androidAny: | ||
switch _getRunningOSVersion() { | ||
case .android: | ||
return true | ||
default: | ||
return false | ||
} | ||
|
||
case freeBSDAny: | ||
switch _getRunningOSVersion() { | ||
case .freeBSD: | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do you handle library embedding on Android? Just curious.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I may be misunderstanding the question but if you're referring to swift's dynamic library dependencies on android, they currently need to be added to the android project by hand alongside libswiftCore.so.
Then you use java's dlib api, i.e.
System.loadLibrary("swiftCore")
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you use rpath (enabled by this if statement) so that you don't have to load them manually?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Bionic linker-loader ignores RPATH afaicr
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting. How do apps typically bundle libraries? Do they have to call
System.loadLibrary()
? Is that the recommended approach?I'm just concerned about putting in a hack that would cause issues later. For example, how would an app with a Swift main() work? It won't be able to call
loadLibrary()
before running Swift code.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's up to app developer how it will be loaded. Between other solutions one of the popular approaches it to link everything statically into
libmyapp.so
and then load it once withSystem.loadLibrary("myapp")
.Usually you can't invoke something like
main()
in native code in Android directly (there are exceptions which usually depend on rooting your phone). Then your app starts it always starts in Java code, which then can instantly load native code and call C/Swift function of interest. Developers who don't want to use any Java code for their app can useNativeActivity
as a shortcut, which comes from Android SDK and does exactly this - loads your library of choice and calls functionandroid_main
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. Thank you for the explanation. Given this platform choice, the current approach looks reasonable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, in my use case it suffices to just run
System.loadLibrary("mySwiftLibrary")
at an appropriately early point in the app's lifecycle - the runtime linker finds the rest of the shared objects provided they're also in the APK bundle. Given that Android is basically a Java-first/-only platform (except for command line tools on rooted devices), this is the "best practices" approach as far as I can tell.