On 29/08/2012, at 6:58 AM, Luke Daley wrote:

> 
> 
> 
> 
> On 28/08/2012, at 9:23 PM, Adam Murdoch <[email protected]> wrote:
> 
>> 
>> On 29/08/2012, at 12:02 AM, Luke Daley wrote:
>> 
>>> 
>>> On 28/08/2012, at 3:20 AM, Adam Murdoch wrote:
>>> 
>>>> Hi,
>>>> 
>>>> One thing that the new build comparison/migration stuff needs to do is run 
>>>> a Gradle build and then assemble a description of what the build produced, 
>>>> in order to compare it.
>>>> 
>>>> We're currently using the tooling API for this, but it's a little awkward. 
>>>> First we run the build using a BuildLauncher. Then we ask for the 
>>>> ProjectOutcomes model. One problem with this is that it's not entirely 
>>>> accurate, as the model builder can only guess what the build would have 
>>>> done. Another problem is that it's potentially quite slow, as we have to 
>>>> configure the build model twice.
>>>> 
>>>> Both these problems would be addressed if we had some way to run the build 
>>>> and assemble the model in one operation. We have a few options about how 
>>>> we might model this.
>>>> 
>>>> Here are some use cases we want to aim for (for the following, 'build 
>>>> model' means the version-specific Gradle model):
>>>> 
>>>> * Request the Eclipse model: configure the build model, apply the eclipse 
>>>> plugin, assemble the Eclipse model.
>>>> * Request the project tasks model: configure the build model, assemble the 
>>>> project tasks model.
>>>> * Run a build from the IDE: run the selected tasks, assemble a build 
>>>> result model (successful/failed).
>>>> * Run a build for comparison: run the selected tasks, assemble the 
>>>> outcomes model from the DAG (outcomes model extends build result model 
>>>> above).
>>>> * Run a build from an integration test: inject classpath from test 
>>>> process, run the selected tasks, assemble a test build result model.
>>>> * Configure a build from an integration test: inject classpath from test 
>>>> process, configure the model, make some assertions, assemble a test build 
>>>> result model.
>>>> * Newer consumer fills out missing pieces of model provided by older 
>>>> provider: inject classpath from consumer process, invoke client provided 
>>>> action around the existing behaviour, client action decorates the result.
>>>> * Create a new Gradle project from the IDE: configure the build model, 
>>>> apply the build-initialize plugin, run some tasks, assemble a build result 
>>>> model.
>>>> * Tooling API client builds its own model: inject classpath from client 
>>>> process, invoke a client provided action, serialise result back. This 
>>>> allows, for example, an IDE to opt in to being able to ask any question of 
>>>> the Gradle model, but in a version specific way.
>>>> 
>>>> What we want to sort out for the 1.2 release is the minimum set of 
>>>> consumer <-> provider protocol changes we can make, to later allow us to 
>>>> evolve towards supporting these use cases. Clearly, we don't want all this 
>>>> stuff for the 1.2 release. 
>>>> 
>>>> Something else to consider is how notifications might be delivered to the 
>>>> client. Here are some use cases:
>>>> 
>>>> * IDE is notified when a change to the Eclipse model is made (either by a 
>>>> local change or a change in the set of available dependencies).
>>>> * IDE is notified when an updated version of a dependency is available.
>>>> * For the Gradle 'keep up-to-date' use case, the client is notified when a 
>>>> change to the inputs of the target output is made.
>>>> 
>>>> Another thing to consider here is support for end-of-life for various 
>>>> (consumer, producer) combinations.
>>>> 
>>>> There's a lot of stuff here. I think it pretty much comes down to a single 
>>>> operation on the consumer <-> provider connection: build request comes in, 
>>>> and build result comes out.
>>>> 
>>>> The build request would specify (most of this stuff is optional):
>>>> - Client provided logging settings: log level, stdin/stdout/stderr and 
>>>> progress listener, etc.
>>>> - Build environment: Java home, JVM args, daemon configuration, Gradle 
>>>> user home, etc.
>>>> - Build parameters: project dir, command-line args, etc.
>>>> - A set of tasks to run. Need to distinguish between 'don't run any 
>>>> tasks', 'run the default tasks', and 'run these tasks'.
>>>> - A client provided action to run. This is probably a classpath, and a 
>>>> serialised action of some kind. Doesn't matter exactly what.
>>>> - A listener to be notified when the requested model changes.
>>>> 
>>>> The build result would return:
>>>> - The failures, if any (the failure might be 'this request is no longer 
>>>> supported').
>>>> - The model of type T.
>>>> - whether the request is deprecated, and why.
>>>> - Perhaps some additional diagnostics.
>>>> 
>>>> So, given that we only want a subset of the above for 1.2, we need to come 
>>>> up with a strategy for evolving. The current strategy is probably 
>>>> sufficient. We currently have something like this:
>>>> 
>>>> <T> T getTheModel(Class<T> type, BuildOperationParametersVersion1 
>>>> operationParameters);
>>>> 
>>>> The provider dynamically inspects the operationParameters instance. So, 
>>>> for example, if it has a getStandardOutput() method, then the provider 
>>>> assumes that it should use this to get the OutputStream to write the 
>>>> build's standard output to.
>>>> 
>>>> This means that an old provider will ignore the build request features 
>>>> that it does not understand. To deal with this, the consumer queries the 
>>>> provider version, and uses this to decide whether the provider supports a 
>>>> given feature (the consumer knows which version a given feature was added).
>>>> 
>>>> To implement this, I think we want to add a new interface, detangled from 
>>>> the old interfaces, perhaps something like:
>>>> 
>>>> interface ProviderConnection extends InternalProtocolInterface {
>>>>    <T> BuildResult<T> build(Class<T> type, BuildRequest request)
>>>> }
>>>> 
>>>> On the provider side, DefaultConnection would implement both the old and 
>>>> new interface. On the consumer side, AdaptedConnection would prefer the 
>>>> new interface over the old interface.
>>>> 
>>>> For BuildResult and BuildRequest, we could go entirely dynamic, so that 
>>>> these interfaces have (almost) no methods. Or we could go static with 
>>>> method for the stuff we need now and dynamic for new stuff. I'm tempted to 
>>>> go dynamic.
>>> 
>>> There also may be a case for providing parameters to the model builder. 
>>> 
>>> At the moment, we are hardcoding that the only artifacts that we care about 
>>> are on the “archives” configuration. This is hardcoded in the provider. I 
>>> think it would be reasonable that one day we would want to give the user 
>>> control over this so they can deal with multiple configurations.
>>> 
>>> Even if we decide we can hardcode “archives” forever, it seems not 
>>> unreasonable that you might want to provide a spec to the model builder to 
>>> influence it. This seems similar to executing tests and injecting a 
>>> classpath (or providing anything from client to provider really).
>> 
>> Possibly. Do you have some examples?
> 
> Not beyond "here is the extra class path I want you to run with" at this time.

I think the strategy we have for growing the tooling API can deal with adding 
something like this when we need to.


--
Adam Murdoch
Gradle Co-founder
http://www.gradle.org
VP of Engineering, Gradleware Inc. - Gradle Training, Support, Consulting
http://www.gradleware.com

Reply via email to