Running an external task with build variant-dependent parameters in Gradle

Since Google hates C++ developers and still haven’t finalized NDK support in Android Studio, we at Lextre call ndk-build manually, by using task( type: Exec ) in Gradle. However, we need to pass some parameters to it. For example, different build variants must use different output directories for object files, and we also need to set various flags depending on whether we are building debug or shipping configuration.

In general, it seems to be useful to know how to parametrize tasks based on chosen build variant (which is a combination of build configuration and product flavor).

The obvious solution – to create some variables in build.gradle and use them in task’s body – won’t work, because variables are computed and substituted during configuration step, while we only know the chosen build variant in build step.

The second obvious solution is to use project properties, which you can specify via -P key when running Gradle from command line. However, this is very inconvenient: we already specify build variant, which should tell Gradle everything it needs to know. Also, it would make building from Android Studio harder, if no impossible.

The problem is that the task’s configuration is immutable after the configuration step is complete. So, the solution is to have a different task for every build variant, each with its own configuration! Of course, to write them all by hand is a thing no sane person should do, especially as the number of build variants grow very fast with each added configuration or product flavor. Therefore, we need to automate task creation.

Gradle has a mechanism for that: Rules. Unfortunately, I haven’t managed to get it working. On the other hand, it looks pretty much the same as the solution I finally devised.

So, instead of Rules, I do this: Gradle allows you to iterate over the set of already-present tasks. I use this feature to find all tasks that compile a concrete build variant, and create a new task with an unique name and configuration, than make the old task depend on the new one. Without further delays, here’s the code:

tasks.withType(JavaCompile) {
        compileTask -> 
              // NDK task name
            def ndkBuildTaskName = "ndkBuild_" + compileTask.name
            
              // NDK task parameters
            def obj_dir = "../../build/ndk/";
            def debug = 0;
            def shipping = 0;
            def km = 0;
            
              // Parse build configuration. If it's an umbrella task (which includes several configurations), skip it
            if ( compileTask.name.contains("Debug") )
            {
                obj_dir += "obj_debug";
                debug = 1;
            }
            else if ( compileTask.name.contains("Release") )
                obj_dir += "obj_release";
            else if ( compileTask.name.contains("Shipping") )
            {
                obj_dir += "obj_shipping";
                shipping = 1;
            }
            else
                return;
            
              // Parse product flavor. If there isn't one, skip this task
            if ( compileTask.name.contains("Google") )
                obj_dir += "_google";
            else if ( compileTask.name.contains("Amazon") )
                obj_dir += "_amazon";
            else if ( compileTask.name.contains("Km") )
            {
                obj_dir += "_km";
                km = 1;
            }
            else
                return;
            
              // Create a new NDK task with specified name
            tasks.create( ndkBuildTaskName, Exec ) {
                  // Set parameters to be used in Android.mk
                if ( shipping == 1 )
                    environment( "SHIPPING", "-DSHIPPING" );
                if ( debug == 1 )
                    environment( "DEBUG", "1" );
                if ( km == 1 )
                    environment( "PS_RU", "-DPS_RU" );

                  // Enable maximum safe number of cores for parallel builds
                int cores = Runtime.getRuntime().availableProcessors();
                int ndkThreads = cores > 1 ? cores - 1 : 1;
                if ( project.hasProperty('NDK_THREADS') ) {
                    ndkThreads = Integer.parseInt(NDK_THREADS)
                }
                
                  // Find ndk-build command
                def ndkRun = 'ndk-build'
                if ( project.hasProperty('NDK_RUN') ) {
                    assert file(NDK_RUN).exists()
                    ndkRun = NDK_RUN
                } else if (Os.isFamily(Os.FAMILY_WINDOWS)) {
                    ndkRun += '.cmd'
                }

                  // Run ndk-build. We have our .mk files in src/main/jni, but you may have them in some other location
                  // NDK_OUT specifies directory for object files. If not specified, it will always be 'obj' 
                  // and it will break debug/release/shipping switch
                commandLine ndkRun, '--jobs', ndkThreads, '-C', file('src/main').absolutePath, "NDK_OUT=$obj_dir"
            }
            
              // Make java compilation task depend on newly created task
            compileTask.dependsOn ndkBuildTaskName
    }

I’ll be the first to admit that this might not be the most elegant solution, but still, it works and it’s flexible enough. If you know a better way to solve this problem, I urge you to share it in comments, because I searched Google in vain for one before, and haven’t found anything.

Integrating Google Breakpad

That programs have bugs is a given in modern times. We all make mistakes, and even if we don’t, we use somebody else’s code, which may contain errors. When your program crashes on user’s computer, you should know about it, and that’s where Crash Reporting comes in. We all have seen it in Windows, MacOS X and even in KDE applications. But what if you want to add such capability to your own project?

As long as you’re only using mobile platforms like iOS, Android or Windows Phone, you have a rich choice of 3rd-party APIs which allows generation, collection and processing of crash reports. iOS has TestFlight. For a cross-platform applications, you may use HockeyApp SDK or CApptain. But on desktop, your choice is limited.

Actually, I was unable to find any free alternatives to Google’s Breakpad. The good news, however, is that Breakpad is a well-tested piece of code (it is used in Mozilla Firefox and some other major software products) and it’s certainly cross-platform. In fact, not only does it support desktop, but also mobile platforms!

However, its integration is far from straightforward.

Continue reading

Reboot.

This blog has suffered a catastrophic failure due to my error in managing my web server. All posts are gone, and there are no backups. So, I’m starting from scratch. I’ll be keeping current theme for now, and I’ll try to write more often. My friends say this site is where projects come do die, and I want to change that 🙂

As for old posts, some of them were just a little bit popular and had incoming links from other sites. I’ll try to restore a few of those under old URLs from drafts I have on my desktop’s HDD, but everything related to Razgriz’s OpenHorizon project is gone (beyond downloads). Razgriz himself is no longer going to work on OpenHorizon because of lack of interest.

As for me, I still hope to release Return of Dr. Destructo some time in the future, and I also maybe have a new project going, but I’ll wait a bit before posting any details.

If you have any particular questions about content of old posts or state of various old projects, feel free to ask, and I’ll do my best to answer.

Update: Razgriz took upon himself to re-create his posts about OpenHorizon. He might be back to working on it.

Progress report

Screen-Shot-2014-04-15-at-4.45.21I’m glad to share with you my current progress. As you can see, I’ve loaded landscape (with hacks that I’ll fix later), loaded animations and added basic post-processing – color correction and FXAA. I also wrote linear approximation to AH’s flight model, it works pretty well, but another approach is required to get exact formulas. What’s next? Clouds, perhaps.

Continue reading

Textures

texturesInstead of working on a flight model, I’ve researched locations’ format and texture assignment in AH a bit. Looks trashy without proper shaders and post-processing, but it’s an another step on a long way. I wish I had more free time…

 

A friend of mine (I don’t know if game files’ modification is legal or not) also found another approach in flight model’s research – to modify plane’s parameters, run the game and see, how it affects plane’s performance. By the way, Assault Horizon doesn’t recognize my PS3 controller on PC and I can’t complete the first mission with a keyboard, lol. Thus I shall start with F-22′s params.
I hardly believe that AH can recognize my save files from PS3

Map loaded

Hurray! The title speaks for itself.map01I have learned how to load map files. Not everything, but enough for testing. Finally, I’m able to work on flight model! In fact, I don’t know how to do it. I shall start with simple manoeuvres and find relations between plane’s flight model parameters and speed/acceleration. I hope it’s possible to read AH’s memory at runtime and dump values to collect reference data. Memory address of each value can be found with ArtMoney but it’s not an easy way, so I’ll start with visual development of a simple model based on common assumptions and see where it goes.map02