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.

Ludum Dare 33

Hey, everybody! This weekend, I took a part in creating a game for Ludum Dare competition. The result is called “Play”, and is made with Unity. You can check it out here.

Competition’s theme, selected by voting, was “You are monster”. We thought long and hard about different kind of gameplay and setting that would fit it, and came up with a number of really interesting ideas. Unfortunately, most of them were just too resource-heavy for a three-day competition, so we had to settle for a simpler one. Player controls Monster Under The Bed, but our monster is not evil. Quite the opposite: he wants to help his child, a small girl, to overcame her fears. The girl fears monsters and fire. We can’t do much about the first one, beyond avoiding getting into her sight, but to help her with fear of fire, we can make her play with the right toys. First, a random toy needs to be dislodged from its place on a shelf, which can be accomplished by bumping into its leg. Then, we should nudge they toy into girl’s path, so she would notice it, without being seen ourselves. To complicate the problem a bit, other, evil monsters, represented by tongues of flame, wander around the room, trying to scare the girl. If she sees too much of them, or of us, she will get really scared, and the games is over.

And that’s pretty much all. It’s a simple game, I would even say a bit too simple for my tastes, but three-day limit makes it hard to do something more complex. At least, I think, we have nice visuals. And it’s fun to play volleyball with the toy ball, even if it has nothing to do with game’s goal 🙂

Development of Return of Dr. Destructo – Following the Progress

In this post, I would like to tell a few stories about the process of development of Return of Dr. Destructo and share some views on game architecture and cross-platform development



I often use the same trick for self-motivation when working on a pet project: a file called Progress.txt, where I write daily what I have accomplished. Combined with oft-cited advice for writers, that you should add at least a line to your work every day, it does wonders. During the first, the most active period of development, I really worked on the game daily, with only a few exceptions for the holiday abroad and other things like that. Such a file makes for an interesting reading a month, or a year later. Or after the game’s release, as is the case. So, let’s scan through it together and see if there is anything interesting there. I’ll try to only pick entries that have some story behind them. Also, there will be pictures 🙂

Continue reading

Return of Dr. Destructo: some statistics

So, I’ve been promoting Return of Dr. Destructo for more than a week now, and the initial surge of interest is already beginning to fade. Time to gather some statistics!

Downloads:

Downloads is the most important part of statistics for me, because it shows how much people played my game. Let’s look at breakdowns by platform and by site:

By platform:

Windows: 95
Linux (32+64 bit): 64
MacOS X: 10

So, a total of 169 downloads. Not too bad for a relatively undistinguished free game, but nothing to write home about, either.

By source:

Direct downloads from site: 88
IndieDB: 70
GameJolt: 11

Direct downloads came from many sources where I posted links, but in terms of single best site to get players, IndieDB seems to be the best so far. Most of IndieDB downloads came on the first few days, and tapered off quickly after that, but there was a surge of about 30 downloads after the news of game release got posted in IndieDB Facebook group.

Continue reading

Return of Dr. Destructo 1.0 Final Release

After more than three years of development, the final version of Return of Dr. Destructo is here! I don’t have much else to say right now, so just watch the trailer and download builds for Windows, Linux and MacOS X.

Trailer:




Downloads:

The full set of downloads are available at game’s site, but here are some quick links!

Windows (installer) [zxstudio.org] (portable version available on site)
Linux 32-bit (zip) [zxstudio.org] (RPM and DEB available on site)
Linux 64-bit (zip) [zxstudio.org] (RPM and DEB available on site)
MacOS X 10.9+ (dmg) [zxstudio.org] (portable version available on site)

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

Weekly update. Less words, more progress.

So I missed a whole week this time. But I have an excuse, or even a pair of them! First, I’ve been working on the new menus’ UI all this time, and now it’s nearly finished, though a few rough spots remain here and there, mainly due to gamepad support being somewhat inconsistent. The new controls menu with support for gamepad and (in future) touch controls also took some time to get right.

Another excuse is, Wasteland 2 came out. And it’s good. No, it’s not astonishing, and it probably will never achieve such cult status as Fallout did, but it’s quite enjoyable once you get past the brutality of first-levels combat, and so I spend some of my free time playing it instead of coding.

Here’s a screenshot of the new menu UI. It’s quite similar to mock-ups that I posted before, no big surprises here, but it’s an actual, in-game screenshot, just to document progress I’m making.

Continue reading