On 23/07/2013, at 3:50 PM, Alex Ruiz <[email protected]> wrote: > Another alternative that Xav and I were talking about later today is that, > instead of having the single-pass multi-model resolution, we can just have a > AndroidStudioModel. > > The ToolModelBuilder for this model will do exactly what Studio is doing now: > gets the IdeaProject and iterates through its children (IdeaModel), > retrieving and AndroidModel if applicable. > > The benefits for this alternative would be: > > 1. We don't have to deal with multiple model hierarchies (e.g. GradleModel, > IdeaProject which have a parent-child structure, and AndroidProject that does > not know about this relationship) > 2. We wouldn't have to wait for Gradle 1.8 to do a single-pass multi-model > resolution. We can use still use 1.6 > > I looked at the code and it looks like we can get the > ToolModelBuilderRegistry (to get the IdeaProject and AndroidProject models) > at any point as long we have a Project: > > project.getServices().get(ToolingModelBuilderRegistry.class) > > At the beginning Xav and I talked about having an IDE-agnostic model, but now > it seems that having one (AndroidStudioModel) is not a bad idea :-)
There is another variation on this approach that might work. The goodness from your approach here comes from the fact that the logic which builds the model for the IDE runs close to the source Gradle model, and can make decisions based on interrogating the model and iterating over things and so on in a performant way. What we can do is instead of having a plugin running in the build which provides this logic, we can inject the logic from the tooling API client. The client would provide an action that would be serialised across to whichever process the build is running in. The action would be given some kind of Gradle version independent API which it can use to query which projects are in the build, which models are available for each project, and so on. It can do whatever it needs to do and then return some custom model object which is serialised back to the tooling API client. The upside here is that we don't need to have the tooling API deal with all kinds of queries - eg 'get this model if it's available and then maybe fall back to this one and do this for this project but not that project'. Instead, it would offer a few conveniences (eg 'get this model for all projects in the build') and if there's anything custom, you write an action instead. So, in your case, you'd have your Android Gradle plugin expose some Android project tooling model. You'd also have a tooling API action that iterates over each project in the build, checks which models are available for each project, and requests the appropriate one(s). It can then return some object that collects all the results (could just be a list or map). We could also potentially open up the source model to the action, so that the client can opt-in to having direct (but version dependent) access to the Gradle model. > > If we decide that this is the way to go for now (we can revisit the idea of > having the single-pass multi-model resolution in Gradle later on), the only > change that we'll need is adding the method 'File getRootDir' to > GradleProject (last item in my original e-mail.) This change seems pretty > small and straightforward. Do you think it can be included in Gradle 1.7? > > Regards, > -Alex > > > On Mon, Jul 22, 2013 at 5:14 PM, Xavier Ducrohet <[email protected]> wrote: > > > > On Mon, Jul 22, 2013 at 4:34 PM, Adam Murdoch <[email protected]> > wrote: > > On 23/07/2013, at 5:53 AM, Alex Ruiz <[email protected]> wrote: > >> x >> Greetings everyone, >> >> We have, in Android Studio, a use case for retrieving multiple types of >> model, in single pass, from a Gradle project using the Tooling API. The >> following section explains the problem and the proposed solution. We >> discussed this issue and the potential solution with Adam during Gradle >> Summit back in June. Please let us know your thoughts. We plan to contribute >> this change to Gradle. > > That'd be great. > >> >> Thanks, >> -Alex >> >> Problem >> >> Currently, importing a Gradle-based Android project takes a considerable >> amount of time. The reason is that we have to make multiple calls to the >> Gradle Tooling API. For a project with N Android sub-projects, we have to >> make N+1 calls to Gradle. The reason is that an Android project may have a >> mix of Android sub-projects and plain Java ones and we cannot get all the >> Gradle models for such structure in one shot. >> >> What we are doing now is asking for the IdeaProject at the top-level >> project, then we iterate through the models of the returned IdeaProject >> asking for the corresponding AndroidProject, if it doesn’t have it, we >> assume is a Java project. >> >> Solution >> >> We are proposing is single-pass project import: a way to return all the >> models we need, from the Tooling API, in one invocation: >> >> ModelBuilder<GradleProject> builder = >> connection.models(IdeaProject.class, AndroidProject.class); >> GradleProject project = builder.get(); >> // ‘models’ will have instances of IdeaProject and AndroidProject. >> List<Object> models = project.getModels(); >> for (GradleProject child : project.getChildren) { >> // Each child will have its own models. >> models = child.getModels(); >> } > > This is a good plan. There are a couple of things I would tweak - more on > this below. > >> >> Changes to Gradle Tooling API >> >> org.gradle.tooling.ProjectConnection: >> Add method ‘ModelBuilder<GradleProject> models(Class<?>...modelTypes)’. This >> change will allow us to query multiple models type as shown above. > > `GradleProject` currently bundles together a few things about a project - its > place in the hierarchy, some identifying info, and the tasks of the project. > This would mean that for every model query, we'd need to create and configure > the tasks of each project. And this is something we'd like to avoid where > possible. I think it would be better if the API did not imply anything about > the tasks, and instead the tool can ask for them explicitly. > > One solution would be to split up GradleProject into an interface that > defines the structure and some basic information about each project, and into > a separate model (or perhaps models) that provide additional, specific > information about the project. You can then ask for this extra information as > you would for any model of the project. > > So the tasks would be part of another Model that is queried and returned as > part of GradleProject#getModels()? > > > Another potential issue here is that there isn't necessarily a 1-1 > relationship between a Gradle project and an IDE project. There are many ways > you might map a set of Gradle projects to a set of IDE projects. It just so > happens that our IDE plugins current map each Gradle project to its own IDE > project. But you might, for example, want to map the integration tests of > each Gradle project to a separate `IntTest` IDE project. We want to allow the > build some flexibility over how each Gradle project is mapped to the IDE, so > we don't really want to bake a 1-1 mapping into the tooling API. > > There are really several parallel hierarchies here - the set of Gradle > projects, the set of IDEA modules + IDEA project, and the set of Eclipse > projects. There is a relationship between a given Gradle project and IDEA > module, and a given Gradle project and Eclipse project. > > > It seems like the issue with Integration test is always going to be a Gradle > project becoming 2+ projects in the IDE and it's very specific to the > plugin(s) that are applied in a project(*). > > This means that the API of connection.models(IdeProject.class, > AndroidProject.class) is probably still the right one. It just may mean that > some GradleProject are present twice. > When you want to get the true Gradle project hierarchy, you can still do > connection.getModel(GradleProject.class). > > When calling models(), it's up to the Java plugin to return more than one > project for a given Gradle project. > > (*) I say plugins plural here because there could be a combination of plugin > that changes the returned model maybe? > > So I think there are 3 steps: > > 1. Split GradleModel in 2+ models. The current one, minus the task, plus the > model lists. And a new one for the tasks. > 2. Add the Connection.models() feature to the tooling API to get the model in > a single pass, and possible more than one model per project (e.g. the tasks + > the IDEA model for a Java project). > 3. Change the ToolingModelBuilder class to allow returning more than one > GradleProject object from a single project > > Is there a reason to return something different than GradleProject when > querying the non actual hierarchy? The data from that interface is still > relevant I think. > > > >> >> org.gradle.tooling.model.GradleProject: >> Add method ‘List<Object> getModels()’ to GradleProject. This change will >> allows us to retrieve the models available from a project. > > What would be the behaviour when a requested model is not available? It would > be missing from the list? > > I think the GradleProject would be present but its model list would be empty. > > -- > Adam Murdoch > Gradle Co-founder > http://www.gradle.org > VP of Engineering, Gradleware Inc. - Gradle Training, Support, Consulting > http://www.gradleware.com > > > > > -- Adam Murdoch Gradle Co-founder http://www.gradle.org VP of Engineering, Gradleware Inc. - Gradle Training, Support, Consulting http://www.gradleware.com
