Hacker News new | past | comments | ask | show | jobs | submit login
Rawdrawandroid – Build Android apps without any Java, in C and Make (github.com/cnlohr)
359 points by doodlesdev 73 days ago | hide | past | favorite | 147 comments



This is pretty great. The biggest reason I hate doing android development is the java (and to a lesser extent, kotlin) "ecosystem" is a pain. Java is a sucky language to write; Kotlin is less bad, but the whole build tooling/package management/IDE mania mess is still a hassle to use. So thanks to the author.


I can tolerate Java and Kotlin, but the tooling is just awful *.

I've stopped counting how many times I came back to an Android project after only a few months, only to have Android Studio force me to update Gradle and dependencies and whatnot, and break everything.

* Of course one could blame me for not learning the ins and outs of the build system!


Gradle/Studio rot is so bad. If you continuously keep up to date it's not so bad, but you're absolutely fucked if you want to open a project from a year ago.

Unfortunately this doesn't actually protect you from that since you still have to deal with that for both the actual "make an APK" step as well as the Java bindings for critical features.


I don't remember the details, but I was struck by the huge size of Gradle. Isn't it just a build tool? Why does it need so many updates?


> only to have Android Studio force me to update Gradle and dependencies and whatnot, and break everything

This. And people are surprised that many just wrap websites as apps on Android.


If targeting Android people should first consider if their app can work as a PWA.

Try and do it. if you run into a deal breaking issue with PWA features, then wrap it in a minimalist app shell.

Best case: everything works on Android and iOS and it's also a website.

Worst case: you will end up doing some platform specific code and dealing with the app stores like you were going to do anyway.

Average Case: everything will work on android and some things will be broken on iOS.


I shipped a PWA in the Play Store. Guess what? It still got delisted because I didn't update to the latest API level or whatever.

Unfortunately you can't just ship a PWA with no wrapper. You have to build a special wrapper app that hosts the PWA and you have to use Android Studio and Gradle and all that crap.

Updating the wrapper is surely still easier than a whole app, but it's nontrivial and it's frustrating to still have to deal with the platform BS for a pure web app that uses no platform APIs at all. More work than I wanted to deal with for a hobby project, anyway.


> Unfortunately you can't just ship a PWA with no wrapper.

You can… but it won’t be in the Play Store. Disqualifying for many, but not all.


this is what I do. are you really any more discoverable there?

I don't think so.


I got a significant number of installs and I didn't do any promotion. People also directly requested an "app" and this satisfies them a lot more than a lecture on the steps to install a PWA.


This really is a good way to go. The majority of apps can be a PWA just fine as long as you don't have to have an app in the Apple app store. If you need push notifications of the specific Apple variety or any other APIs that Apple doesn't think non-native apps should be able to call, then you'll hit some friction as well, but for the most part it's a pleasant delivery strategy.

Even now if I need a "native" app I'm looking very seriously at React Native first before rolling two totally separate apps in different languages.


I use most of the things as pages (miniflux, bluesky) and feel no need to install apps for them....


Same thing with XCode. I'd love a mobile app development pipeline that lets me never use either Android Studio or XCode. Let me drive everything with scripts and/or VSCode extensions.


Isn't flutter (especially including the flutter rust bridge), achieving that? You do need Android Studio and XCode installed on your machine but you don't have to interact with them directly.


As someone interested in flutter, when do you have to use XCode and Android Studio?


The only times I’ve had to use Xcode for my flutter app is configuring some things related to signing and distribution. There hasn’t been anything that required Android Studio except for setting up emulators.


I develop an app with flutter on Mac OS. I open xcode when creating a new project to setup signing. I never installed Android studio since I use an Android device for testing and just use the command line tools and SDK (setting this up without android studio was a bit of pain and probably not worth it but still it proves that it's possible).

All development work takes place in VS code, including the incredibly incredibly convenient and performant hot code reload, as well as the final step of "flutter build ios"/"flutter build aab"

It is an experience with very few pain points* and I find it to be very enjoyable.

*except for the random Cocoa Pods error every few months which can be solved by random helpless googling and then deleting the podfile.lock


If only Firefox OS had taken off...


XCode is awful but JetBrains makes the best IDEs. There are people that dislike all IDEs which I disagree with but understand.

VSCode isn't bad but disliking Android Studio just feels like you started with it and didn't want to learn a different tool.


Jetbrains makes great stuff, I only wish their remote development story was better. You basically have to run a full headless instance on the remote and since it's all java it requires way more ram resources than it should.


Try give a look at kraken: https://github.com/sal0max/kraken

It's a native Android app (Kotlin) which isn't developed using Android Studio. Gradle only.


Give ReactNative + Expo a try


As an Android dev who use Android Studio for daily work, I confirm updating Gradle may break your projects, well depends on the libraries you used.

In other cases, updating Gradle won't give any problem. You can build the APK fine. Debugging the build system is annoying :D


> I've stopped counting how many times I came back to an Android project after only a few months, only to have Android Studio force me to update Gradle and dependencies and whatnot, and break everything.

This used to be 5-6 years back. These days it is very stable.


No, it's not. This year I still had to upgrade all my projects (I do this yearly) and they still broke in a myriad of different ways. Most of them fortunately and perhaps ironically the solution was one Google search away.

I actually _like_ Java, so it's not that I hate Android, but frankly, I have no idea what it is even doing downloading and generating gigabytes of crap with inscrutable huge log files. Most of my applications' Java/GUI parts are no more complex than a hello world -- many times I find it easier to just rewrite them from scratch than even try "upgrading" them using Android Studio.

The Android tooling is basically a textbook definition of oversized enterprisey software. I really wish there was just a frigging (resource) compiler, linker and maybe packer that I could invoke from the command line in simple steps.


> The Android tooling is basically a textbook definition of oversized enterprisey software. I really wish there was just a frigging (resource) compiler, linker and maybe packer that I could invoke from the command line in simple steps.

Yeah, I feel the same. There are CLI approaches that I have encapsulated into Makefile ( https://ashishb.net/all/use-makefile-for-android) and GitHub Actions (https://github.com/ashishb/gabo/blob/master/src/gabo/interna...) but they do require time to set up.


This is why folks should stick with Maven and live a happy, generally drama-free life.

As a Gradle veteran, I've written in detail about this previously:

https://news.ycombinator.com/item?id=38875936

TL;DR: Gradle is too powerful (which is fun and seems valuable at first!) with too many footguns. Combine this with the mandatory update migrations and you're literally signing up for future pain compared to using a lower change-rate build tool like Maven. Do you really need to run arbitrary commands to build your app? If so, take a deep gaze in the mirror and reflect on what you're doing with your life.


Gradle was created by folks that didn't learn the Ant lesson, and only matters thanks to Android.

Had it not been for Android replacing Ant with Gradle, and most folks wouldn't even remember Gradle.


With all due respect, that’s a blatant misunderstanding of gradle (though gradle’s documentation not being clear enough, giving to the spread of this misunderstanding is a fair criticism).

Gradle is not too powerful, it builds a build graph based on the .gradle file, and the building itself happens imperatively — but the actual execution is static, based on that fixed build graph. This configuration is cached and subsequent executions will all refer to this static graph to know what requires rebuilding.

This is the minimum required complexity/generality for a build system that is not “hardcoded” for a given task (e.g. to a degree maven, but cargo, go’s build tool etc all can only compile their respective languages on their own. “Plugins” can let them do more, but at that point plugins have to re-develop all the functionality of a build system - caching and parallelization), and as I mentioned there really are only a handful of tools capable of that.

As for valid criticisms of gradle - it’s API historically experiencing constant breakages, used to be dependent on JDK version (and lagged behind for a long time due to Groovy), which is more or less fixed now with toolchains, and the model not being clear enough for people not wanting to dive deep. E.g. I can count on a single hand how many people knows that doFirst/doLast should be used for the actual task itself, anything else will happen at the config build time which is not what you want.


Saying Gradle is the minimum possible for a build tool because it generates a dependency graph is a bit of a stretch. Gradle is essentially a custom groovy/kotlin build script dsl in addition to a dependency manager. I just want the latter; leave the former to existing tools. Maven and plug-ins can do the same without learning a new (and brittle) dsl. Worst case in maven you have to write your own plugin, but then you just use their documented plug-in API (import org.apache.maven.plugin) like any other JVM project.


Gradle rot really is a pain, at least for Android projects (haven't used it in e.g. serverside stuff).

Proguard is fun too, with how it'll carve out or break chunks of functional code if you don't spell out precisely what shouldn't get deleted/obfuscated.


Gradle is pretty sane, from my backend dev experience. But you indeed can get in a situation when dependencies become slightly incompatible after an attempt to upgrade, and you have to fix your code or juggle version requirements, much like with npm or pip.


Eh... "all hail Gradle" :/

Each time I have to touch it, it results in problems... Oh, you updated JDK? Gradle won't work. There is new Gradle? Tough luck it will break your build..

And with Maven it can update and the build keeps working just fine.

To that end I mostly ignore prompt "update gradle!" in Android Studio to avoid any issues...


Maven (and pretty much every other build tool with the exception of bazel) is not capable enough for such a complex build as is required for android, though. E.g. maven can often “lose” that something requires a rebuild, and only clean build will produce the correct artifacts. Gradle can always keeps track of tasks correctly.


Maven (and pretty much every other build tool with the exception of bazel) is not capable enough for such a complex build as is required for android, though.

Maven is virtually infinite extensibility via plugins. The difference is that gradle is easier to "hack" and put the build logic in your configuration...

> E.g. maven can often “lose” that something requires a rebuild, and only clean build will produce the correct artifacts.

This is not build shortcoming but rather detection what requires rebuild and what not but in the worst case scenario it rebuilds everything…

> Gradle can always keeps track of tasks correctly.

LOL, from my experience that is not true.

Also, if you have multiple projects using different gradle version this results in multiple daemons running in the background. Besides gradle builds as as slow as maven...


> Maven is virtually infinite extensibility via plugins

Losing the only task of a build tool, caching and parallelization.

No, the problem is that maven doesn’t rebuild everything. It is actually quite common in code bases that I had to run clean build instead of build to actually recompile everything that changed. I guess maven just doesn’t have an accurate build graph?

> LOL, from my experience that is not true

In what way is it not true?

Also, gradle is demonstrably faster, especially on projects that can be parallelized. Obviously a hello world will be dominated by javac and build tool startup speed so it’s not relevant.

And multiple daemons — you can disable them, and they will go away if unused.


> Losing the only task of a build tool, caching and parallelization.

You are aware that Maven does offer paralle build, right?

> I guess maven just doesn’t have an accurate build graph?

You guess wrong? then

>> LOL, from my experience that is not true

> In what way is it not true?

Consistency of gradle build. You yourself stated just a paragraph above that sometimes you have to run "clean" xD

> Also, gradle is demonstrably faster, especially on projects that can be parallelized. Obviously a hello world will be dominated by javac and build tool startup speed so it’s not relevant.

Again, learn the tool. Maven does offer parallel builds and is comparably fast…

> And multiple daemons — you can disable them, and they will go away if unused.

"but you are turning off the only feature of a build!!!"

Also - I had a setup open with gradle/kotlin-multiplatform project (shell with "gradlew" and Android Studio) and magically it fails to build with "Unresolved reference: kotlinx" even though I haven't done anything to the project and no ammount of running "gradlew clean" solves it... what a lovely tool....


> Consistency of gradle build. You yourself stated just a paragraph above that sometimes you have to run "clean" xD

That was in reference to maven..

Also, feel free to show me any benchmark showing maven being as fast as gradle. https://gradle.org/gradle-vs-maven-performance/


> Also, feel free to show me any benchmark showing maven being as fast as gradle. https://gradle.org/gradle-vs-maven-performance/

I tried and * commons took more than 2,5 minutes to build (they show 70s?) * tried their tests (medium project, https://github.com/gradle/performance-comparisons/tree/maste...) and maven compiled it in 4s and usual "clean install" took 14s but they don't enable paralel test run); then I tried running "gradle build" and... got an error:

``` FAILURE: Build failed with an exception.

* Where: Build file '/Users/wojtek/dev/tmps/performance-comparisons/single-medium-project/build.gradle' line: 16

* What went wrong: A problem occurred evaluating root project 'single-medium-project'. > Could not find method compile() for arguments [commons-lang:commons-lang:2.5] on object of type org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler. ```

It's so nice that even gradle team themselves can keep up with their crap xD but of course it's probably due to convoluted setup... again - with maven you just clone and build and be done with it, with gradle you have to "stand on your head" to figure how to make any progress... I updated the build.gradle (according to https://docs.gradle.org/current/userguide/declaring_dependen...) and `gradle build` finished in 19s...

So your beloved gradle is 25% slower and can't even be compatible with itself...

I took a look at "large-multimodule" to have something more challenging but: they setup each project individually and for some convoluted reason configure maven compiler with `<source>1.5</source>` (and JDK21 complains) and same goes for gradle modules with broken `dependencies`. I adjusted it but then it turned out that the sources can't even compile (due to wrong arguments in tests) ffs...


Android team marketing.

Almost no other build tool has had so many conference talks regarding performance issues, and dealing with its gotchas as Gradle.


I am primarily a backend developer, so nope. Also, give me any build tool that would be similarly capable. I only know of Bazel.


Maven is more as capable for anyone that doesn't suffer from XML allergy, and doesn't require a background daemon to actually make it run at reasonable speed.


Correctness is the number 1 priority, and maven is simply not always doing what it should, unless you do a clean build.

I am absolutely not bothered by XML and alia (btw, there are maven frontends in json/yaml, etc for those who are), and throwing out valid criticism over some straw man is not really productive.


I’m still learning about the tools and their pros and cons. Can you provide an example of a case where Maven would not do what it should? Are there particular bug reports or bug reproduction repos that you’ve seen?

Context: I worked on a Spring app many years ago and am picking Java back up.


Here is a case: https://stackoverflow.com/a/4662537

But I have run into issues with multi-module projects and the like, and it can be very frustrating to realize that you are trying out some corrupt version. Nonetheless, maven is also a cool and stable tool, but you are better off adding a clean step from time to time.


As if Gradle was any better in "correctness" given without its daemon, it is a slower Ant for folks with XML allergy.


> The biggest reason I hate doing android development is the java (and to a lesser extent, kotlin) "ecosystem" is a pain.

Wait till you want to build anything reasonable.

All libraries for Android are in Java and/or Kotlin. If you have to write all of them in C then best of luck doing anything meaningful.

What kind of libraries? Anything related to SSO. Anything related to Material design. Anything related to any ancillary services that you want to integrate with e.g. Google services.


Looking at the Flappy Bird post earlier I noted the structure of the repo to be simultaneously elegantly structured and yet still more complex than I would prefer.

The structure is (using [dirname] notation for directories)

    [Repo Root]
        ...git and github files, READMEs...
        [Flappy Bird]
           ... project files, keystore, build.bat ...
           [App]
             [build] 
                 ... I'm ok with tools making a mess here as long as outputs is tidy ...
                 [outputs/apk]    
                    ..the actual .apk
             [src]
                [main]  this seems a little off,  main contains libs and resources 
                   Android_Manifest.xml     Yep ok, I'll rant about that later, that's its own thing
                   [assets]   all good
                   [res] Wait what?  that's like a synonym for assets, (but for a different layer) 
                   [libs] Hang on, these are .so files.  built libs should be in the build dir
                   [jni] This looks like what src or src/main should have had inside it
 
So ignoring the outer layer for the README etc. which isn't needed for an Android app, Just encapsulation for presentation. Arguably the same applies for the next layer too. I feel like the contents of App could be placed in here at no loss.

I'd Shuffle it around to make [Flappy Bird] contain [main] and [build]. move [libs] to [build/libs] rename [jni] to [src].

If you had an architecture like that then the one-true-build-system should be a command line tool that you can point at [main] and it does what [Flappy bird]/build.bat does only with auto detection of the installed tools, optional config file overrides, optional commandline overrides.

AndroidManifest is another issue entirely I'd like something that invisibly converted something sane to XML and I'd never see it again, but I'll grin and bear it. A decent validator and maybe I'd like a standalone AndroidManifest editor that knew what everything did and could provide appropriate boilerplate.

After typing this up (mostly as organizing my thoughts), I actually think you could have a relatively painless way to make Android Apps.


The `src` folder is structured that way because the Android NDK demands it. They're not just chosen by convention, but because the build tool searches for those files and subdirectories.


Yeah, that feeds back to the tooling issues that the post I was replying to was talking about.

Ideally tools should support, not dictate. For instance, if you were building a simple source only app with no assets, a good tool should be able to be pointed at, say, a main.c file in the directory and be able to use the contents of the directory containing main.c for other required files and work with no special extra configuration. Supporting a structure to keep things clean is great, Imposing one(whether you use the features it offers or not) is poor.

I might take a look at the NDK and see if it can be circumvented.


Whenever I need to touch XCode or Android Studio, I'm reminded how lucky web devs are now that almost everything has converged around Vite (death stare at NextJS). Everything Just Works(tm). Simple plugin system to integrate anything.

The few times there wasn't a plugin to do what I needed, I've managed to roll a custom one pretty easily.

When Vite breaks you're completely screwed though. Find a different way to do it or wait until a patch comes out. Vite internals are nigh impossible to fix on your own.


I sincerely Vite would just align with actual web standards rather than throwing in bits and pieces of their own nonsense. It’s precisely why it tends to break in seemingly unfixable ways


Would you mind elaborating?


"I can do anything I want. It's just bits. You don't own me." Fair enough! x-D


"This is computer science. There aren't restrictions." - It's literally what many people forget when they let themselves constraint by frameworks and claim this and that isn't possible. Very refreshing to see how liberating it is if you go one level deeper.


"We can cause any problem by introducing an extra level of indirection." ;)


cnlohr is in his own league, this guy is bordering on genius.

Dissatisfied with the state of the vendor SDK for CH32V003 microcontrollers (ultra-cheap RISC-V MCUs), he created his own [1], which is a pleasure to use. He also has a header-only RISC-V emulator that runs Linux (and Doom!) [2], and hacked an ESP32 to emit valid LORA frames with clever use of aliasing [3].

[1] https://github.com/cnlohr/ch32v003fun

[2] https://github.com/cnlohr/mini-rv32ima

[3] https://www.youtube.com/watch?v=eIdHBDSQHyw


to be clear, this is only really useful for applications that present their ui through opengl and do not interact with the rest of the android system very much.

the ndk is meant for writing little bits of c to speed things up in a classic java android application.

this is a pretty cool hack that allows for opengl apps to be written in straight c that run full screen and have limited access to things like keyboards, adc inputs or usb.

it does not include a reimplementation of the android frameworks in c and the ndk provides limited access for ndk code to interact with them.

the main use case seems to be supporting a program for live audio reactive visuals based on extraction of chroma.


> this is a pretty cool hack that allows for opengl apps to be written in straight c that run full screen and have limited access to things like keyboards, adc inputs or usb.

It's not even a hack. Android NDK added that way back in like gingerbread, it's just a NativeActivity. https://developer.android.com/ndk/samples/sample_na

This is just a small framework on top of that. It's not doing anything "new"


it is, however, cross platform for a handful of embedded targets of which android is one of.


To be fair this is the way to not be platform dependent and more people should pursue it.

I'm doing a desktop app meant to be as cross compatible as possible and the only reasonable way to do it (without sending yourself into build config hell) is with opengl.


How do you make it accessible?


I read that last bit of your comment before I opened the link, and thought to myself, that's something Cnlohr would write.

And it is! Hah.


Looks like you still have to start by installing Android Studio, which seems excessive. Is there a way to just download an Android SDK?

Looking briefly at the makefile, I think they might have avoided Gradle, though it calls other tools written in Java.

I'd love to see a way to build a Flutter app without Gradle.


> curl -k "https://dl.google.com/android/repository/commandlinetools-li..." -o commandlinetools-linux.zip

unzip somewhere, set path variable

> yes | sdkmanager --licenses && sdkmanager "platform-tools" && sdkmanager "ndk-bundle" && sdkmanager "build-tools;33.0.0" "platforms;android-33"


This is better indeed, but from recollection last time I tried this.. I think that still requires a full OpenJDK installation and I believe those `sdkmanager` commands install multiple GB of tools sadly.


Yes to both OpenJDK and large size.


Not sure how useful this is but here's a gist that runs through the process without needing Android Studio: https://gist.github.com/jonnybrooks/13c6cbae832e7d1662e6c143...


It's possible to build without gradle by calling the sdk tools for bundling and signing directly (for instance from make or cmake), but Android Studio is at least useful for debugging the resulting apk (AS also works as "standalone debugger" for debugging an apk built elsewhere).


> Is there a way to just download an Android SDK?

Yes, you can just grab the zip files. For example like this: https://www.hanshq.net/command-line-android.html#sdk


>I'd love to see a way to build a Flutter app without Gradle.

You generally don't need to touch gradle at all with flutter. Is your issue about having to install gradle onto your machine, even if you don't use it directly?


Yes, it’s mostly hidden, but it runs Gradle behind the scenes and that’s okay as long as nothing goes wrong.

(I used to work on it, or at least tried to before giving up in frustration.)


>I used to work on it, or at least tried to before giving up in frustration.

Out of interest, what blockers did you encounter using Flutter? For me it was much better than native android in terms of development, but it could depend on the usecase.


C is neat but something like python may be simpler to work with for most programmers. I wonder if anyone here has experience with Kivy and KivyMD libraries for python.

The code is simple and self explanatory.

class RectangleFlatButton(TouchRippleBehavior, Button): primary_color = get_color_from_hex("#EB8933")

    def on_touch_down(self, touch):
        collide_point = self.collide_point(touch.x, touch.y)
        if collide_point:
            touch.grab(self)
            self.ripple_show(touch)
            return True
        return False

    def on_touch_up(self, touch):
        if touch.grab_current is self:
            touch.ungrab(self)
            self.ripple_fade()
            return True
        return False

class MainApp(App): def build(self): screen = Builder.load_string(KV) screen.add_widget( RectangleFlatButton( text="Hello, World", pos_hint={"center_x": 0.5, "center_y": 0.5}, size_hint=(None, None), size=(dp(110), dp(35)), ripple_color=(0.8, 0.8, 0.8, 0.5), ) ) return screen

MainApp().run()


I built a simple kivy app a few years. Working with kivy and testing on my (linux) dev machine was great. Even side loading to test on my phone using the kivy launcher wasn't too bad. The pain point was that building APKs that the play store would accept was an ordeal and then when they change requirements you have to hope the kivy folks update their build tools in time to recompile with a new version of the NDK before google delists your app.


Thank you. Wasn't aware of that.


Given that it's a thin wrapper around GL and friends, could one have an alternate implementation of these calls and do most of the dev work natively on the desktop? And not muck around with devices or emulators except for some final / edge case testing?

I just remember working with Libgdx back in the days. A game framework made for Android. It however was cleverly designed in that you could just run it on desktop by changing which implementations were used for drawing/sounds/assets. Very nice to work with, could just recompile the app in seconds and test, even hot swap, compared to the other way of building an apk, installing, launching etc which took minutes per iteration.


Present tense, LibGDX is still around, even though it isn't Unity or Godot like cool.

https://libgdx.com/


Yeah, sorry, the past tense was supposed to be my usage of it, which was around 2012-2015. I was quite active, maintaining lots of the documentation, writing guides on how to do stuff. Specifically how to get it running with IntelliJ, as android sdk at the time was eclipse integrated.

Also made a simple project on github to show how to do a "loading screen", aka not just freeze while it loads but have a progress bar and interactivity. Somehow it still gets random stars a decade later.


You know its funny all the Gradle sucks comments here....I use Flutter which behind the scenes uses Gradle builds for android targets....no Gradle problems whatsoever...maybe operator touching and changing Gradle was the feature and the problem??


May be someone with deep pockets like Elon must get Linux to work on mobile. I know there's efforts going on but seems slow progress. That will break the back of the duopoly and also make things like this so much easier.


Getting Linux running is not the problem. All android phones have a working Linux kernel, and could probably be made to run some kind of Linux based software if you can get through the bootloader hurdles. The issues so far with open source phone stacks are largely around getting all the hardware talking with open source software, battery life, mobile appropriate UIs and finding ways to bring the apps people expect on board.


Already been done https://ubuntu-touch.io/en_GB/ I ran my main phone on this for a little while and it was definitely an interesting experience


(I assume by "Linux" you mean a distribution like Arch/Debian/Fedora/Ubuntu/… since, as the sibling says, Android also runs Linux.)

> That will break the back of the duopoly and also make things like this so much easier.

…and so much less secure by giving every application access to everything in my home dir. No, thank you.


Restricting app filesystem access is easily doable with Flatpak, Firejail, systemd-run, etc. Hardware support and UX are much bigger problems.


AFAIK the sandbox on Android is quite a bit stronger than your run-of-the-mill container (starting with every app running under a different UID[0], apps having very restricted access to the screen buffer[1], et cetera).

But it's not really about sandbox security and file system access. The issue is that the more walls you erect and the tighter you make your sandbox, the harder you will make it for apps to access system resources for legitimate purposes. This is where Android shines as it provides a huge, fine-grained API for apps to access system resources while still making sure they only access what they're allowed to access. On Linux we don't even have that fine-grained an API, let alone the same level of fine-grained control. You either grant an application access to your bluetooth/wifi/… device or you don't.

What's more, all Android apps and their UI have been built around this permission-based system these days. Compare this to Linux where applications still assume they have access to everything by default and you have to work very hard to confine an application while making sure it's still usable and the user is not thrown off by weird error messages.

In other words: The strength of Android (and iOS) is that they built in a tight & fine-grained capability-based system from the get-go and also solved the UI challenges that went along with that. These days, they can count on an entire ecosystem of applications that expect to live (and are usable) in that sandbox.

Regular Linux distributions are lightyears away from that. Personally, I really hope SpectrumOS[2] will make a dent here.

[0]: https://source.android.com/docs/security/app-sandbox

[1]: https://source.android.com/docs/core/permissions/restricted-...

[2]: https://spectrum-os.org/


I'm not tech enough to challenge you on that but in practice most apps demand far more than they need and users generally click yes. If I don't I get annoying reminders every time and feature restrictions. Like my app that comes with smart watch. At some point I guess most will say take it can stop bothering.

I don't follow what you mean by "give or don't" isn't that how Android works? Like I give file system or drive access to some app so it can pick pictures. Do I know what else it does with that permission?! Just have to trust.


> users generally click yes

I agree that's an issue but I would say it's a separate one. At least in Android these days you don't have to agree to everything upfront (during installation) but you can choose the permissions you want to grant one by one later during runtime. E.g. when the app wants to access the camera, it asks you for access right there and then and you can also only give it one-time access. That's already been a big UX and security improvement and I'm sure this can be improved even further.

> Like I give file system or drive access to some app so it can pick pictures.

In past versions of Android yes. Nowadays, however, in order for the app to access select photos it no longer needs the general file system access permission. The file picker has become a system component, and by picking photos you grant the app access to the selected files and the selected files only. Of course if the app is, say, a photo gallery app, then it will probably still need access to your entire camera folder (or even the entire file system), not just individual files. In such cases, I at least will probably review the app a bit more carefully.

> Do I know what else it does with that permission?! Just have to trust.

That's always the case when you grant permissions to someone or something. At least, the more fine-grained your permission system is, the more control you have, and the easier it will be for you to build that trust.

In my case I usually disable internet access[0] for apps by default, so that even if they need access to, say, my entire file system, they won't be able to send my files anywhere or do anything shady. Only for select apps that I've reviewed carefully I might also enable internet access.

Either way, I hope we can agree that Android gives you a lot more control over what you allow your apps to do than Linux. In Linux I have to blindly trust all apps at once. It's all or nothing.

[0]: I use GrapheneOS. In regular Android the internet permission is no longer exposed to the user, I think.


Maybe PinePhone or Librem5 are a good start.


There should be a lot more high quality, super lightweight, non bloated apps.


Unfortunately it’s easier to write a useful app (reliable, bug free, with useful features, etc) using more bloated tech. That translates to the cost.

In other words: lightweight, useful, cheap - pick two.


I remember when I was developping a cross platform C++/OpenGL ES engine that worked on Android, iOS, Linux, Web… so satisfying


Now we only need to embed Lua into this to write the high-level logic, and we may have a winner for stuff that does not need a lot of accessibility support. Like, say, games, or media players. Easy to link C libraries that do performance-critical stuff, or writhe your own C code.

(Then gradually rewrite the core in Zig.)


Love2D has Android support. It's surprisingly easy to setup: https://github.com/love2d/love-android


...I of course would rather embed Janet [1], but I realize what is going to have an easier time gaining popularity %) Also, Lua has Löve [2] which could be immediately usable, among other things.

[1]: https://janet-lang.org/

[2]: https://www.love2d.org/


thanks for the link to janet! i hadn't heard of it before. a lot of lua energy even if it's a lot bigger


Feel free to ask questions in the Janet Zulip (link at the bottom of the Janet website's front page) if there's anything you need help with!


Defold is another great open source Lua-based cross-platform game engine, and has a healthy ecosystem.

https://defold.com/


SDL can be used to provide something similar to what you describe.

https://github.com/libsdl-org/SDL/blob/main/docs/README-andr...


This should be integrated into DevkitPro! Would love to see the relatively lightweight msys2 environment building APKs. I know WSL is better but I'm hesitant to use platforms Microsoft is in the process of embracing and extending.


Honestly the whole java/kotlin tooling is the worst to pick for mobile dev, and KEEP it after so many other great languages and tools that are out there. I don’t why google didn’t offer at least Go as a native alternative for android dev.


Because adding Go as an official language would be a monumental amount of work for vanishingly little benefit. Remember Android itself is half written in Java, so are you doing JNI calls for everything from Go? That's not fast nor "native". Are you rewriting the framework in Go? That's a crazy amount of effort.

And all for what? To satisfy a hobby itch that has no practical benefit? The problem of app developers is almost never the language. Hell, look at how popular web apps are even though JavaScript is the worst language in any sort of widespread usage. The platform is what gets people excited (or frustrated), not a language.


Because Go only exists due to a trio that doesn't like C++, has fought against Java 20 years ago and lost (Inferno and Limbo), and is pretty much the atenthesis of the feature rich capabilities of Java, Kotlin and C++, the official Android languages.

They hit jackpot with Kubernetes and Docker taking off after their pivot to Go.


That'd require some form of collaborative behavior across internal organizations, and real planning! </s> </disgruntled_xoogler>

I wake up every morning and thank God that Flutter exists. I can target Android without dealing with building on years and years of sloppy work.

Sunk-cost fallacy x politics leads to this never being fixed. I don't think Google can fix it, unless hardware fails entirely. There's been years-long politics that culminated in the hardware org swallowing all the software orgs, and each step along the way involved killing off anything different.


>I don't think Google can fix it

Can they eventually throw away Android and replace it with Fuchsia? In the reporting about Fuchsia that I read ages ago, it sounded like it was intended to be an Android replacement but, looking into it again just now, it seems more like an embedded OS for other non-smartphone hardware -- maybe with some (aspirational?) claims of utility on smartphones and tablets.


Fuchsia has components for running APKs (Android Runner) and Linux binaries (Starnix), but that probably isn't what you meant.

The problem with replacing a UI toolkit - any toolkit - is that any change to the toolkit requires modification of all software, including third-party software. Typically, when an OS wants to provide a new toolkit, they wrap the existing toolkit in new code. For example, on macOS, UIKit wraps AppKit, and on all Apple platforms SwiftUI is a wrapper around AppKit and UIKit (depending on platform). On Windows, every UI toolkit ultimately is creating "windows" as they are understood by USER[0], which creates corresponding objects in CSRSS and/or the NT kernel, which can then be used to draw on or attach to a GPU. The lowest level UI abstraction either OS provides is the objects supported by their oldest toolkit, and the lowest level programming language you can write apps in is whatever can call it.

Linux is a bit different, because it inherits its windowing model from X11. X shipped with no default toolkit and a stable window server protocol that apps could program against directly, in an era where most GUI OSes[1] didn't have 'servers' or 'protocols'. You populated resource files and called the relevant function calls to make things happen, and those function calls became sacrosanct. Even Windows NT couldn't escape this; it still used USER despite USER being years older than NT.

The best you can do is shim the library - write something more lower level than the old junk and then rewrite the old library in terms of the new one. This is what Xwayland does to make X apps work on Wayland; and it's what Apple did (mostly) with Carbon to give a transition path to Mac OS 8/9 apps on OS X. Google could, say, ship a new Android toolkit that doesn't use Java bindings, and then make Android's Java toolkit a shim to the new native toolkit. However, this still means you have to keep the shim around forever, at least unless you want to start having flag dates and cut-offs. For context, Apple didn't kill Carbon until macOS 10.15 Catalina, and if they hadn't refused to ship Carbon on 64-bit Intel, it probably would still be in macOS today.

[0] An interesting consequence of this is that disabling "legacy input" in games turns off the ability to move the application window since all that code is intimately coupled to every app that has to open a top-level (i.e. not a widget) window.

[1] At the time that would be XEROX Star, the Lisa, and the Macintosh

[2] This is also why Apple will never, ever ship an iPad that can run macOS software in any capacity. Even if they were forced to allow root access and everything else macOS can do. The entire point of the iPad is to force software developers to rewrite their apps for touch, and I suspect their original intent was for the Macintosh to go away like the Apple ][ did.


Correct, tl;dr roadkill. I don't mean to be disrespectful, someone anonymous picked a bitter fight about this once. But to your point, its clear there was a larger context that Fuchsia was born from, and the ambitions and commitment to it are greatly different than they were at some previous juncture.


Google even didn't consider supporting Go as an official flutter language.


I was interested but then saw JDK/Gradle. One of the reasons I avoid android development.


Rawdraw operates on everything from an ESP8266, to RaspberryPi, Windows Linux and now, even Android. Write code once, use it everywhere.

That "everything" contains a pretty big gap.


Memory corruption, now on Android!

Just gonna leave this here: https://safecpp.org/draft.html#the-call-for-memory-safety

P.S. Was discussed on HN recently: https://news.ycombinator.com/item?id=41528124

P.P.S. The author has a great YT channel with awesome embedded systems projects: https://www.youtube.com/playlist?list=PLDRymMFQl3Nktk_pjlUP_...


Any effort to make android more accessible to a C ABI with a simple toolchain can probably also serve as an FFI to other languages.


Zig also much better at memory as far as I can tell. Only reason I found out about it was some random twitch clip I can't remember, but parts of zig seem to be more readable and easier to learn than rust. Granted I will not stop loving the C language even as someone who has only begun to learn C programming but I do recognise the need for secure software.

Check out this blog on zig memory safety: https://www.scattered-thoughts.net/writing/how-safe-is-zig/

(Not my blog, just a blog I found that discusses zig)

Zig is not at 1.0 yet so its a bit behind rust from what iv seen online. One I manage to get C under my belt and get good at writing los level stuff in that language, I might attempt to learn rust or zig.


Yes, I think Zig is the most suitable language (of Zig, Rust, Go, ...) to replace C in various (systems-related) contexts including embedded/RTOS.


If works with C why not Rust


Interfacing with C from Rust is harder, and Rust has fewer cross-compile targets.

I'd expect Zig to be an easier deal here, with its zero-impedance interface with C.


> Interfacing with C from Rust is harder

Sure, harder, but not really all that difficult. Run bindgen over the NDK headers and see what happens. Sure, for ergonomics you'll want to write safe Rust wrappers. After that's done once, it's done.

> Rust has fewer cross-compile targets.

Not really an issue here. Rust supports all the targets Android runs on. Natively, even:

    $ rustup target list | grep android
    aarch64-linux-android
    arm-linux-androideabi
    armv7-linux-androideabi
    i686-linux-android
    thumbv7neon-linux-androideabi
    x86_64-linux-android


In my experience, C <-> Rust works quite well, except when the C API relies too much on macros.

C++ <-> Rust is much more annoying.


Make? Jesus Christ people are still using that? It's like people don't realize that other languages have been created in the last 20 years


So which other actual alternative should we use for Android development besides Java and webapps.


I am not a fan of Java, but going from Java to C is not an improvement.


Going from Java to C is a good step along the way towards supporting any language capable of calling C functions.


We have WASM/WASI now which is almost certainly going to become the new gold standard of cross language interop.


It depends purely on what you’re making.

It would be silly to write low-level software in Java, or high-level software (with exceptions for software which would require a low-level language) in C.


> It would be silly to write low-level software in Java

Depends on what kind of low-level stuff.


If you're going to nit-pick then at least do us the favor of writing something interesting, like a case where Java was a surprisingly good choice or some interesting libraries.

Yes, of course there are exceptions to every rule. In general, as much as I love the language, Java is not always going to be the best choice.


No, it doesn't.

Java is a piece of crap with JVMs imposing an 8-24 byte overhead per object, which destroys caching, memory footprint, and any chance of low-level optimization. Not only is the overhead bad, it's implementation-dependent, i.e. unpredictable. That, along with lacking SIMD, imposing stupid decisions like bounds checks on all array accesses, making arrays boxed (int[] is not an array of ints, sorry), or using exceptions for control flow, which is the wrong way to use exceptions, is a just part of the big laundry list of why Java is a piece of obsolete crap that nobody serious about low-level hardware programming uses. Java SE/ME is a fucking joke, as is every book I have read about "high performance Java". People who promote Java for low-level hardware programming, real-time systems, and the like, just have absolutely no fucking idea what they're talking about and they get their garbage published only because of the low standards of publishing nowadays. They do get the "Java Champ" badge from Oracle, though. Noobs award other noobs badges, as it turns out.


> JVMs imposing an 8-24 byte overhead per object

You want project Valhalla, or more specifically Value Objects, which are in preview [0]. If you really want the highest performance related to cache accesses, you want to arrange things as SoA (struct of arrays) anyway, which has approx 0 object overhead and doesn't require Value Objects.

> Not only is the overhead bad, it's implementation-dependent, i.e. unpredictable.

This is silly. This happens with all other languages, and Java will give you better results on average. Use a different compiler or runtime for your C code, or run it on a different processor architecture, and have fun tuning your code.

> That, along with lacking SIMD

The Vector API [1] is available in preview.

> imposing stupid decisions like bounds checks on all array accesses

It's widely understood that doing the opposite has been perhaps one of the largest mistakes in the history of the industry. JITs elide access checks when safe, and it's easier for them to do with high performance code that doesn't bounce around in memory and thrash cache.

> making arrays boxed (int[] is not an array of ints, sorry)

What does this even mean? An int[] is a linear array of 32-bit int values in memory.

> or using exceptions for control flow, which is the wrong way to use exceptions

Are we now just talking about a small slice of people who write bad code?

> is a just part of the big laundry list of why Java is a piece of obsolete crap

Ah, ok. Have a fine day.

0) https://openjdk.org/jeps/401

1) https://openjdk.org/jeps/460


I appreciate your response. My original post was a rant for sure, but let me reply to your comments.

> You want project Valhalla, or more specifically Value Objects, which are in preview [0]. If you really want the highest performance related to cache accesses, you want to arrange things as SoA (struct of arrays) anyway, which has approx 0 object overhead and doesn't require Value Objects.

SoA requires unboxed arrays, otherwise it defeats the purpose. I may have been wrong about int[] being boxed (it seems to be unboxed in most implementations), but the fact that ints still auto-promote to Integer in certain circumstances is just ridiculous for any kind of low-level programming.

It's also hilarious that Value Objects are still in preview. Even C# has had structs (value semantics) since forever.

> This is silly. This happens with all other languages, and Java will give you better results on average. Use a different compiler or runtime for your C code, or run it on a different processor architecture, and have fun tuning your code.

This is false. structs in C or C++ don't have a language-imposed overhead. They may have different sizes depending on the alignment requirements of the platform.

> The Vector API [1] is available in preview.

Also hilarious. It's 2024.

> It's widely understood that doing the opposite has been perhaps one of the largest mistakes in the history of the industry.

Checking every single array access in a tight loop, for example, is stupid. It's the naive, dumb, and inefficient way of doing it (i.e., the Java way.) The right way to do it is to enforce up front that the index may never be out of bounds. Ada helps with this by having user-defined ranges and integer types, for example.

> What does this even mean? An int[] is a linear array of 32-bit int values in memory.

Yes, I think I botched that one, but see also my comment above.

> Are we now just talking about a small slice of people who write bad code?

No, I am talking about Java.

> Ah, ok. Have a fine day.

Oh, and how does Java do low-level systems programming without unsigned integer types, lol? Oh, yeah, you need to cast to/from the next largest integer representation so that you can encode all of the positive values. And you better hope you don't need more than the native size, because then you get fuck-boxed into Integer (see my comment above.) Fucking hilarious, y'all.

Also, you don't even have a guarantee that a variable is allocated on the stack even when the dumbest compiler can tell it's bound by function scope. You need to hope that the JIT will do it, and from my past experience in Android (which this post is about), it doesn't. This is why, for example, if you look at the math library of LWJGL [1], all functions are defined to mutate one of the arguments, they never allocate a new vector to return. This is just completely stupid because mathematical vectors should have value semantics and the compiler optimize the fuck out of them. (Don't get me wrong, I love LWJGL and the guys. This is a criticism of the language, not the library; they are doing the best they can given the language's stupid decisions.)

[1] https://github.com/JOML-CI/JOML/blob/main/src/main/java/org/...


> Vector API

In all fairness, simd<T> in C++ and portable-simd in Rust are both in preview. Not that there is a lack of alternate options there but still.

The languages that do have these APIs stable are Swift, C#, Mojo, Zig and Julia if I’m not mistaken. Stability means different things for some of these as Mojo and Zig are young languages.


C++ compilers today already auto-vectorize simple loops, even without manual unrolling. For manual SIMD, I agree a built-in type would be best to abstract over the 10 different HW APIs.


This is true, but it is also very brittle and cannot kick in where compiler cannot assert that e.g. source and destination do not alias, where it might change semantics or where there might be side-effects that the compiler cannot hoist out of the loop body on your behalf.

Which is why most code that really cares about optimal HW utilization is vectorized explicitly - it's problematic to assert against potential regressions between compiler versions and addressing them is just as brittle.


FWIW, I spend a lot of my time writing low-level code, but more for desktop/server machines and less-so for mobile.

> SoA requires unboxed arrays, otherwise it defeats the purpose. I may have been wrong about int[] being boxed

Yeah, that's wrong. Primitive arrays aren't "boxed". An int[] is not an Integer[], and no boxing is performed unless you ask for it. SoA is fine in Java.

> the fact that ints still auto-promote to Integer in certain circumstances

It's pretty easy to avoid boxing if you don't want it. They are separate types after all. Even so, Android lint has an inspection for it, and Intellij (presumably Android Studio too?) has an inspection you can turn on to detect it [1]. If you're running afoul of autoboxing, it's probably mostly in collections-based code and you'll want something like fastutil [2].

> It's also hilarious that Value Objects are still in preview.

Agreed, it's been a long time coming, but they've been doing a good job of releasing features that don't need deprecating five years later.

> This is false. structs in C or C++ don't have a language-imposed overhead. They may have different sizes depending on the alignment requirements of the platform.

Sorry, I misunderstood your comment. You were talking about different overheads on different platforms, and I read that as general overhead. (For example, if you run the exact same x86_64 binary on an Intel and AMD processor from the same generation, you can get drastically different results. Heck, you can get drastically different results within the same family from the same manufacturer for a half-dozen reasons). In any case, I wouldn't lose sleep over the varying size of object headers on different platforms. In the cases it matters, you should be avoiding them altogether.

> Also hilarious. It's 2024.

I don't know when Valhalla will land, but the Vector API is far smaller in scope. It's on its 8th incubator, and seems likely to land soon. You could give it a try.

> Checking every single array access in a tight loop, for example, is stupid.

This is a red herring in most situations. Your array accesses did get elided, you need to rewrite your code so it's easier for them to get elided, or the checks didn't have a performance impact. Also, you dev for mobile in Ada?

> No, I am talking about Java.

You must not be, because there are no exceptions used for control flow in code I write or review.

> Oh, and how does Java do low-level systems programming without unsigned integer types, lol?

It's annoying. You don't cast, you promote through a mask, e.g. val & 0xFF; That said, jOOU [3] looks interesting.

> And you better hope you don't need more than the native size, because then you get fuck-boxed into Integer

I don't understand this. You aren't using more than "native size" on any platform in any language without some sort of emulated Big* type.

> Also, you don't even have a guarantee that a variable is allocated on the stack even when the dumbest compiler can tell it's bound by function scope.

Implementations typically have both escape analysis and a nursery generation for this. This works well in a desktop/server environment. I can't speak to ART. Ultimately, Valhalla will solve this for constrained implementations.

> I love LWJGL

I'm knee-deep in GDX and LWJGL, right now, but again not in a mobile context. I used to hang out in the Java gaming forums with the folks who originated LWJGL (e.g. cas).

> This is a criticism of the language, not the library

I think your criticisms are mostly with ART. I agree there are a handful of language changes that would make things easier for ART.

1) https://www.jetbrains.com/help/inspectopedia/boxing.html#loc... 2) https://github.com/vigna/fastutil 3) https://github.com/jOOQ/jOOU


> Yeah, that's wrong. Primitive arrays aren't "boxed". An int[] is not an Integer[], and no boxing is performed unless you ask for it. SoA is fine in Java.

Yes, I botched that one, agreed.

> It's pretty easy to avoid boxing if you don't want it. They are separate types after all. Even so, Android lint has an inspection for it, and Intellij (presumably Android Studio too?) has an inspection you can turn on to detect it [1]. If you're running afoul of autoboxing, it's probably mostly in collections-based code and you'll want something like fastutil [2].

Autoboxing is a contradiction of "It's pretty easy to avoid boxing". It's specifically egregious for any place where you need performance guarantees.

> I don't understand this. You aren't using more than "native size" on any platform in any language without some sort of emulated Big* type.

Because you don't have unsigned integers (or arithmetic). So, for example, if you're playing with a 16-bit unsigned value, then you need a 32-bit signed integer to hold the 16 bits you care about (and you can kind of pretend you have 16-bit unsigned arithmetic.) But if you need a native-sized unsigned integer, you're out of luck.

> Implementations typically have both escape analysis and a nursery generation for this. This works well in a desktop/server environment.

Not on Google's VM the last time I tried. Shit went on the heap and the "concurrent" GC would then stall the entire application for ~1s every ~10s.

> I'm knee-deep in GDX and LWJGL, right now, but again not in a mobile context. I used to hang out in the Java gaming forums with the folks who originated LWJGL (e.g. cas).

I used to be on #LWJGL on Freenode, but it seems the guys moved to Discord or something.

By the way, we haven't talked about how horrible the JNI is, which requires you to wrap every C function so that you can pass around the JNIEnv handle and to manually marshal data structures to/from, like you're writing fucking Lua bindings or something. The right way to do FFI is the Haskell way, where you just call the function and get compiler support for auto-marshalling data structures (and, if you want, you can get your Haskell program to build and statically link the C library from source; no need to compile separately using another build tool and then dynamically load a shared lib.) Even C# does it better than Java. So in the use case where you have optimized C code and the rest of the application in Java, even the bridge itself is a pain in the ass to write.


I feel so out of place when interviewing for firmware/embedded roles and the interviewers turn out to be java snobs. I honestly still have a hard time comprehending the situation; how does it come to this? Thanks for your insight


It's really sad. I think Java is a rather ill-designed and boring language, not even worth learning for intellectual purposes, but it does have a mature ecosystem around databases, web services and stuff, and it has its use cases. Beyond that, it really beats me where this attitude comes from. I attribute it to living in your bubble and not exploring what the guys over on the other side of the fence are doing. This is why one should learn as many programming languages (and paradigms) as possible, even if you don't end up using half of them. It's like when those guys come out with a new "minimalist" CSS framework, and the bitch downloads a 2MB payload just to bootstrap your "hello world".


C++ or Rust? Or Zig? Or D? Kinda pretty much anything else tbh?


So what you are suggesting seems to be, rewrite android_native_app_glue.c in (say) Zig and compile to the .so? Good, but it would nice to see a proof of concept. Also to see how extensive the work has to be: I am not checking all the tree, but android_native_app_glue has includes itself:

  #include <android/configuration.h>
  #include <android/looper.h>
  #include <android/native_activity.h>


Quite a few game engines work and don't use Make.


Game engines that we can readily use to code for Android outside the Java and web frameworks? Which ones exactly?


At least Unreal, Unity and Godot but most likely more target Android and iOS as well.


Unreal and Unity both target iOS and Android and are not "java" or "web frameworks"


Obviously the complete and utter crap that gradle/maven are is much better, right?


Yes. Definitely. Ignore your blind rage of Gradle/Maven for a second and realize that they’re literally next iterations of Make.


It's not blind rage. I've used them, they're over-engineered crap, like most things Java, especially the abominations made at Apache land. You can't say Make is over-engineered; lacking, maybe, but it does what it's meant to do and not much of anything else.


> I've used them, they're over-engineered crap

Compared to what? And in what way? And then for you to manually recreate it anyway? Like when you need NodeJs, typescript, Turborepo, webpack, babel and a whole list of tools and then you still need scripts to build and deploy?

Yes Maven and Gradle has its problems, but which tool is exactly better? They all just have their pros and cons. Which tool / framework in Go allows you to update a single dependency instead of the 100 that you might need and generates OpenAPI docs for you, etc etc?


Make can be as low level or high level as one wants. One simply runs shell commands with it mostly, except for in-built functions and using other shells. This means it all depends on the tooling one wants to use for a project. If it is a C project then probably some C compiler. If it is Python then probably some venv and Python. And so on. So it is very flexible. Only doesn't have a great syntax.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: