Gradle Native 2.6


(Shaun) #1

Hi Daniel,

Apologies for the delay in response, and thank you for getting back to me.

I may not have explained my need clearly or I am not understanding your suggestion.

Currently within gradle we create two NativeLibrarySpecs, one representing a normal binary and another that represents a metrics version (the metrics are induced outside of gradle as part of the compiler). These binaries effectively share all the same configuration and dependencies from a gradle perspective.

At the linking stage we can choose between these two binaries dependent on whether we want metrics or not.

In order to reduce the user workload we have created some helper functions that allow us to choose when to create the special libs and also when configuring dependencies or paths etc.

It looks a little like this;

-- HelperFunctionA (name , addMetricVersion) -- 

model
{
	components
	{
		name (NativeLibrarySpec)
		{
		//initial config
		}

		if (addMetricVersion)
		{
			name+metric (NativeLibrarySpec)
			{
			//initial config
			}
		}
	}
}

-- End HelperFunctionA () --

And separately:

-- HelperFunctionB (name, dependencies) -- 

model
{
	components
	{
		name
		{
			//updateConfig based on dependencies
		}

		if ("name+metric" exists) <--- **point of problem**.
		{
			name+metric
			{
				//updateConfig based on dependencies
			}
		}
	}
}

-- End HelperFunctionB () --

In our build.gradle files we will do something equavalent to;

HelperFunctionA (libA, true)
HelperFunctionB (libA, SomeDependencyX)
HelperFunctionB (libA, SomeDependencyY)
HelperFunctionB (libA, SomeDependencyZ)

HelperFunctionA (libB, false)

model
{
    	components
    	{
    		normalExecutable (NativeExecutableSpec)
    		{
    			// set a dependency on libA
    			// set a dependency on libB
    		}
    		metricExecutable (NativeExecutableSpec)
    		{
    			// set a dependency on libA+metric
    			// set a dependency on libB
    		}
    	}
}

So the issue I was trying to solve was how to check if a BinaryComponentSpec item existed within the components closure, allowing the user to call one function that optionally updated a binary if it existed. Having tried things like model.components.contains() etc I can only find decorated named items and nothing that links back to “liba-metric” for example.

We had considered flavors previously, however as you state, you cannot mix and match and that is exactly what we are trying to do.

Any help appreciated.

Thanks,
Shaun


(Daniel Lacasse) #2

Hi Shaun,

There are several ways to achieve this it all depends on how your project is structured and what is your end goal. Generally speaking, you should consider flavors or a construct similar to that (like build properties). The downside with flavors is the lack of support for mixing flavors through the native dependencies.

You would be defining 2 flavors: one for the dependency with liba and another one without. Then you would configure the binaries for those flavors according to your need. Finally, you will be able to invoke the compilation for one or both flavor to produce the various binary’s dimensions.

Don’t hesitate to post a complete example so we can direct you better toward a full solution.

Daniel


(Shaun) #3

Apologies Daniel, it appears I somehow managed to edit my original entry (I’m guessing i had already tried to start adding more detail).

Anyway please see my post above, which has more details now.


(Daniel Lacasse) #4

Thanks Shaun for the updating your question.

As a general rule from my experience in using Gradle for building native, avoid optimizing your configuration (regarding the number of variant/component). This is counter-intuitive when coming from other native build system but let me explain a bit. It serves little purpose to conditionally create component depending on the user’s input. Instead, create them all and attach the right one together. If performance is a concern, Gradle will make the right decision to minimize the impact of all the work that need to be done. In fact, the software model is lazily evaluated and will only do the work if a component is actually required during execution.

The other general concept in Gradle is modeling. It will be easier to solve any problem by modeling what you are trying to achieve instead of simply configuring. This requires a bit more effort but will definitely be rewarding.

That been said, you can’t realistically configure all of your permutation. However, I would suggest modeling a metric registry to map component name and “metric flag”. Your HelperFunctionA would configure both versions of the component (effectively removing the addMetricVersion boolean flag. The HelperFunctionB would also configure both versions (normal and metric). Finally, when you need to choose between the normal vs metric version of a component, I would use a wrapper function that check the registry to choose what component to depend on. Here is a code example:

-- HelperFunctionA (name , addMetricVersion) --

// This is simply to avoid modifying too much your code but that registry could be updated at the same level where HelperFunctionA is called.
ext.metricRegistry.put(name, addMetricVersion)

model {
    components {
        name (NativeLibrarySpec) {
            //initial config
        }

        name+metric (NativeLibrarySpec) {
            //initial config
        }
    }
}

-- End HelperFunctionA () --


-- HelperFunctionB (name, dependencies) --

model {
    components {
        name {
            //updateConfig based on dependencies
        }

        name+metric {
            //updateConfig based on dependencies
        }
    }
}

-- End HelperFunctionB () --


// This need to be initialized first as HelperFunctionA will populate this map.
ext.metricRegistry = [:]

HelperFunctionA (libA, true)
HelperFunctionB (libA, SomeDependencyX)
HelperFunctionB (libA, SomeDependencyY)
HelperFunctionB (libA, SomeDependencyZ)

HelperFunctionA (libB, false)

// This function have access to the Project scope so it can retrive ext.metricRegistry
def choose = { String name ->
    if (ext.metricRegistry.contains(name) && ext.metricRegistry.get(name)) {
        return name + "+metric";
    }
    return name
}

model {
    components {
        autoExecutable (NativeExecutableSpec) {
            binaries {
                // set a dependency on libA
                lib library: choose("libA")

                // set a dependency on libB
                lib library: choose("libB")
            }
        }

        normalExecutable (NativeExecutableSpec) {
            binaries {
                // set a dependency on libA
                lib library: "libA"

                // set a dependency on libB
                lib library: "libB"
            }
        }

        metricExecutable (NativeExecutableSpec) {
            binaries {
                // set a dependency on libA+metric
                lib library: "libA+metric"

                // set a dependency on libB
                lib library: "libB"
            }
        }
    }
}

As you can see, the autoExecutable will choose his dependencies depending on what is in the metricRegistry. The registry is a simple Map that associate a component name with a boolean (set to true to use metric component). The function choose will consult the metricRegistry and return the right component name to automatically decide if you want the metric version or not. We assume a modeling of metric component name to be the component name with +metric appended to it. This enable us to reconstruct the name for metric component as we please. This concept can be rolled out throughout Gradle if you want to change all dependency at the same time. You can write the metric flag to Project properties in order to change them from the command line. With this solution, you still have the opportunity to force a specific dependency (normal or metric) as component normalExecutable and metricExecutable. For a full modeling, I would create a custom class for the metricRegistry to keep the registry code and choose logic in the same place. I would also ensure you have a single instance of that registry in order to support multi-project scenario.

Don’t hesitate to ask more question if you need more information,

Daniel