Gradle setup for Kotlin multi-module project structure - kotlin

I have a gradle project setup which looks similar to the one graphed below and I have some trouble setting up the dependencies between the different modules
┌Top Kotlin Project
│
├── Project1
│ │
│ ├── ModuleA1 (uses C1)
│ ├── ModuleA2
│ ├── ModuleA3
│ └── settings.gradle
├── ModuleB1 (uses C1)
├── ModuleC1 (library project)
└── settings.gradle
The Modules in A1,A2,A3 and B1 are basically isolated Gradle applications with their own build.gradle.kts
Module C1 is supposed to be a library module with shared code, that is used by Module A1, and Module B1.
Originally I thought I only had to declare C1 as an implementation dependency in the build.gradle.kts files of A1 and B1, like this
implementation(project(":ModuleC1"))
and include it in the settings.gradle of the Top Kotlin Project and also Project 1
include(":ModuleC1")
project(":ModuleC1").projectDir = File("../modulec1") #this line only needed in settings.gradle of Project1
However this does not work for multiple reasons.
First of all, all other projects/modules need to include all the repositories ModuleC needs, which means, whenever ModuleC changes I might need to also change the build files of other modules.
Most importantly though, all modules need the kotlin gradle plugin to build.
However if ModuleC1 includes the plugin and ModuleA1 as well, I get error that
> Plugin request for plugin already on the classpath must not include a version
If I do not include the Plugin in ModuleC1 it works for ModuleA1, but ModuleC1 cannot be built alone anymore.
The gradle.build.kts of ModuleC1 currently looks like this:
plugins {
kotlin("jvm") version "1.6.20"
`java-library`
}
group = "org.test.project"
version = "0.1"
repositories {
mavenCentral()
}
dependencies {
testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.1")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1")
}
tasks.getByName<Test>("test") {
useJUnitPlatform()
}
In conclusion I think, this setup of simply including the ModuleC1 in the other projects/modules is pretty flawed and there must be a better way to do this.
In summary, my goals are to be able to develop ModuleC1 independently but be able to use it in the other modules as if it was just another package there.

Related

How can I reuse library declarations in Gradle Kotlin DSL?

Background:
We are trying to migrate to Gradle's Kotlin DSL, primarily for the additional type safety.
Some of our library definitions are quite large (20+ exclude calls in some extreme cases) so we currently have a giant dependencies.gradle Groovy script which declares a giant map of all libraries, which can then be used like libraries.blah. I'm trying to come up with a working way to do the same in Kotlin.
Investigation:
Dependency constraints (java-platform plugin) allow centralising the version number for the dependency, but that does nothing for the exclusions, and the exclusions are most of the problem in our case. (I'm sure we will eventually use java-platform at some point too.)
The most elegant-looking solution I found was in this one blog post which suggests declaring Kotlin objects, like this:
object Libraries {
val guava = "..."
}
You can then supposedly use the object like Libraries.guava. But this doesn't actually work, at least in Gradle 6.9.1, because of an issue in Kotlin itself. (I'm assuming that it worked on some older version and got broken by a Kotlin compiler upgrade.)
The workaround specified on the ticket comes out like:
class Libraries(dependencies: DependencyHandler) {
val guava = dependencies.create(...)
}
val libraries by project.extra { Libraries(dependencies) }
This gets me closer to a working solution in that my dependencies.gradle.kts file now compiles, but I still can't find a way to use the Libraries class I've defined. If you do this:
val libraries: Libraries by project.extra
Kotlin complains that Libraries is not defined. So it's as if one build script can't even export classes for another build script to use.
More Context
The actual project structure is quite complex, and looks a bit like this:
─ repo root
├─ mainproject1
│ ├─ subproject11
│ │ └─ build.gradle.kts
│ ├─ subproject12
│ │ └─ build.gradle.kts
│ ├─ build.gradle.kts (references dependencies.gradle.kts)
│ └─ settings.gradle.kts
├─ mainproject2
│ ├─ subproject21
│ │ └─ build.gradle.kts
│ ├─ subproject22
│ │ └─ build.gradle.kts
│ ├─ build.gradle.kts (references dependencies.gradle.kts)
│ └─ settings.gradle.kts
└─ shared
└─ gradle
└─ dependencies.gradle.kts
Question:
How are other people with enormous builds with >100 dependencies solving this problem?
Workaround: use buildSrc to instantiate Libraries object.
Create the following directories structure:
.
├── buildSrc
│ ├── build.gradle.kts
│ └── src
│ └── main
│ └── kotlin
│ └── Libraries.kt
├── settings.gradle.kts
└── build.gradle.kts
buildSrc/build.gradle.kts is minimalistic:
plugins {
`kotlin-dsl`
}
repositories {
mavenCentral()
}
Libraries.kt:
object Libraries {
const val guava = "com.google.guava:guava:31.0.1-jre"
}
Usage (root build.gradle.kts):
plugins {
`java-library`
}
dependencies {
implementation(Libraries.guava)
}
repositories {
mavenCentral()
}

ESP-IDF project with multiple source files

I started my project with a simple "blink" example and used it as a template to write my code.
This example used only one source file blink.c.
Eventually, I want to a use multi source files project and can't figure out how to configure CMakeLists.txt in order to compile the project.
My CMakeLists.txt is:
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(blink)
I want to add for example init.c.
I tried different ways, but with no success.
None of idf_component_register() / register_component() worked for me.
Any idea how to correctly configure the project?
Right, the CMake project hierarchy in ESP IDF is a bit tricky. You are looking at the wrong CMakeLists.txt file. Instead of the one in root directory, open the one in blink/main/CMakeLists.txt. This file lists the source files for the "main" component, which is the one you want to use. It would look like this:
idf_component_register(SRCS "blink.c" "init.c"
INCLUDE_DIRS ".")
Make sure your init.c file is in the same directory as this CMakeLists.txt and blink.c.
I also recommend taking a look at the Espressif Build System documentation, it's quite useful.
You should edit the CMakeLists.txt located in your main folder inside your project folder. In addition, you need to put the directory that contains the header files into INCLUDE_DIRS parameter.
For example, if you have this file structure in your project (you're putting init.h inside include folder) as shown below:
blink/
├── main/
│ ├── include/
│ │ └── init.h
│ ├── blink.c
│ ├── CMakeLists.txt
│ ├── init.c
│ └── ...
├── CMakeLists.txt
└── ...
The content in your main/CMakeLists.txt should be:
idf_component_register(SRCS "blink.c" "init.c"
INCLUDE_DIRS "." "include")

Ktor app is not running inside Kotlin Multiplatform Project using IntelliJ IDEA

I'm trying to configure a Kotlin Multiplatform Project with Android and Ktor modules in it. After configuration, running the Ktor app fails with this message:
/usr/lib/jvm/java-8-openjdk-amd64/bin/java -javaagent:/home/lenqnr/intellij-idea/lib/idea_rt.jar=44331:/home/lenqnr/intellij-idea/bin -Dfile.encoding=UTF-8 -classpath /usr/lib/jvm/java-8-openjdk-amd64/jre/lib/charsets.jar:/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/ext/cldrdata.jar:/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/ext/dnsns.jar:/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/ext/icedtea-sound.jar:/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/ext/jaccess.jar:/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/ext/java-atk-wrapper.jar:/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/ext/localedata.jar:/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/ext/nashorn.jar:/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/ext/sunec.jar:/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/ext/sunjce_provider.jar:/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/ext/sunpkcs11.jar:/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/ext/zipfs.jar:/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/jce.jar:/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/jsse.jar:/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/management-agent.jar:/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/resources.jar:/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/rt.jar:/home/lenqnr/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-server-netty/1.2.2/86f06a652bf2859236835e802cd81dc884a72c61/ktor-server-netty-1.2.2.jar:/home/lenqnr/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-server-host-common/1.2.2/80e62a4dab4e116aeb50e8017838b83836764bf9/ktor-server-host-common-1.2.2.jar:/home/lenqnr/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-server-core/1.2.2/37ba2773c666ff3f9f35a6d5949b8b8c2edd6adf/ktor-server-core-1.2.2.jar:/home/lenqnr/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-http-cio-jvm/1.2.2/22659690f735fa439b4f91b83ef846e3ec7dedf1/ktor-http-cio-jvm-1.2.2.jar:/home/lenqnr/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-http-jvm/1.2.2/1381ef340b58a3287f1da884b9db7280466c67ff/ktor-http-jvm-1.2.2.jar:/home/lenqnr/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-network/1.2.2/e8b2d5f46acfc25d7e893dde14f8da41ab28b7fd/ktor-network-1.2.2.jar:/home/lenqnr/.gradle/caches/modules-2/files-2.1/io.ktor/ktor-utils-jvm/1.2.2/7c51add50945d74c07926e11acd33b66295903c8/ktor-utils-jvm-1.2.2.jar:/home/lenqnr/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk8/1.3.40/2d1d0a2f27fd060787075c69113846803fc27734/kotlin-stdlib-jdk8-1.3.40.jar:/home/lenqnr/.gradle/caches/modules-2/files-2.1/ch.qos.logback/logback-classic/1.2.1/706a8b8206ead3683ec639dd270d11fd948fbb0e/logback-classic-1.2.1.jar:/home/lenqnr/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk7/1.3.40/2995de8f68444ad47f29e7b59962ac31e6301d7e/kotlin-stdlib-jdk7-1.3.40.jar:/home/lenqnr/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-coroutines-io-jvm/0.1.10/ec98e487d8c710126001c4f3086e4a0b9c0d3179/kotlinx-coroutines-io-jvm-0.1.10.jar:/home/lenqnr/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-io-jvm/0.1.10/74f179ae134d78ad360770801807c7078e71885/kotlinx-io-jvm-0.1.10.jar:/home/lenqnr/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-coroutines-jdk8/1.2.2/71653b85af2fe6baeb5314c2863dffe7aac8068a/kotlinx-coroutines-jdk8-1.2.2.jar:/home/lenqnr/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.2.2/6ff48bdfc38a8c22e3fc37605b6a6afaed3b6dbd/kotlinx-coroutines-core-1.2.2.jar:/home/lenqnr/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-reflect/1.3.40/f2f8357e000fd80d8d799110f012b86fd4637386/kotlin-reflect-1.3.40.jar:/home/lenqnr/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.3.40/b8a521c687329303778548e2f09b0ba5b2665236/kotlin-stdlib-1.3.40.jar:/home/lenqnr/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/atomicfu/0.12.9/5cee16643956d76c52c7ef345448b0990b6e1e04/atomicfu-0.12.9.jar:/home/lenqnr/.gradle/caches/modules-2/files-2.1/org.slf4j/slf4j-api/1.7.25/da76ca59f6a57ee3102f8f9bd9cee742973efa8a/slf4j-api-1.7.25.jar:/home/lenqnr/.gradle/caches/modules-2/files-2.1/com.typesafe/config/1.3.1/2cf7a6cc79732e3bdf1647d7404279900ca63eb0/config-1.3.1.jar:/home/lenqnr/.gradle/caches/modules-2/files-2.1/io.netty/netty-codec-http2/4.1.36.Final/ed198d8a5283910b0c062a50dd28ef4688dcb2a5/netty-codec-http2-4.1.36.Final.jar:/home/lenqnr/.gradle/caches/modules-2/files-2.1/org.eclipse.jetty.alpn/alpn-api/1.1.3.v20160715/a1bf3a937f91b4c953acd13e8c9552347adc2198/alpn-api-1.1.3.v20160715.jar:/home/lenqnr/.gradle/caches/modules-2/files-2.1/io.netty/netty-transport-native-kqueue/4.1.36.Final/a2c6883a0f2e0adc15e6d764ced0ef044e2311c7/netty-transport-native-kqueue-4.1.36.Final.jar:/home/lenqnr/.gradle/caches/modules-2/files-2.1/io.netty/netty-transport-native-epoll/4.1.36.Final/5eb8b3ad0a99a16e5ae492da27544b1143e00606/netty-transport-native-epoll-4.1.36.Final.jar:/home/lenqnr/.gradle/caches/modules-2/files-2.1/ch.qos.logback/logback-core/1.2.1/378913dfc3c6c71e7e2a2853eff2c3e8ac27599/logback-core-1.2.1.jar:/home/lenqnr/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-coroutines-io/0.1.10/798b764e7f9396e63eb263a83c0385686d69f3e0/kotlinx-coroutines-io-0.1.10.jar:/home/lenqnr/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-io/0.1.10/c407b4bf931379824cf002f110df028cfe18c87c/kotlinx-io-0.1.10.jar:/home/lenqnr/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-coroutines-core-common/1.2.2/8b54928fbb813408684911eb27d6afeb23c92a4b/kotlinx-coroutines-core-common-1.2.2.jar:/home/lenqnr/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.3.40/ff8f3da514fc2877d1303d55e22d6da8156c29fb/kotlin-stdlib-common-1.3.40.jar:/home/lenqnr/.gradle/caches/modules-2/files-2.1/org.jetbrains/annotations/13.0/919f0dfe192fb4e063e7dacadee7f8bb9a2672a9/annotations-13.0.jar:/home/lenqnr/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/atomicfu-common/0.12.9/ba80bc10a440ea504b5cebd262becf9870e309c3/atomicfu-common-0.12.9.jar:/home/lenqnr/.gradle/caches/modules-2/files-2.1/io.netty/netty-codec-http/4.1.36.Final/62b73d439dbddf3c0dde092b048580139695ab46/netty-codec-http-4.1.36.Final.jar:/home/lenqnr/.gradle/caches/modules-2/files-2.1/io.netty/netty-handler/4.1.36.Final/1c38a5920a10c01b1cce4cdc964447ec76abf1b5/netty-handler-4.1.36.Final.jar:/home/lenqnr/.gradle/caches/modules-2/files-2.1/io.netty/netty-codec/4.1.36.Final/8462116d327bb3d1ec24258071f2e7345a73dbfc/netty-codec-4.1.36.Final.jar:/home/lenqnr/.gradle/caches/modules-2/files-2.1/io.netty/netty-transport-native-unix-common/4.1.36.Final/d95d7033f400f9472db9da7834c443b96cd4bab0/netty-transport-native-unix-common-4.1.36.Final.jar:/home/lenqnr/.gradle/caches/modules-2/files-2.1/io.netty/netty-transport/4.1.36.Final/8546e6be47be587acab86bbd106ca023678f07d9/netty-transport-4.1.36.Final.jar:/home/lenqnr/.gradle/caches/modules-2/files-2.1/io.netty/netty-buffer/4.1.36.Final/7f2db0921dd57df4db076229830ab09bba713aeb/netty-buffer-4.1.36.Final.jar:/home/lenqnr/.gradle/caches/modules-2/files-2.1/io.netty/netty-resolver/4.1.36.Final/e4d243fbf4e6837fa294f892bf97149e18129100/netty-resolver-4.1.36.Final.jar:/home/lenqnr/.gradle/caches/modules-2/files-2.1/io.netty/netty-common/4.1.36.Final/f6f38fde652a70ea579897edc80e52353e487ae6/netty-common-4.1.36.Final.jar ApplicationKt
Error: Could not find or load main class ApplicationKt
Process finished with exit code 1
I noticed the module's output path is not included in the -classpath option. How should I fix it?
Here's what I did:
Create new Gradle project with no additional libraries and frameworks using IntelliJ IDEA
Add new Android module
Add new Multiplatform module
Add new Ktor module
Define main method as shown in the Ktor quick start
If you follow these steps, then I'm pretty sure you'll face the same problem. I'm working on Ubuntu 18.04 just in case.
project structure:
hello
├── android
├── common
├── server
│ ├── resources
│ └── src
│ └── Application.kt
├── build.gradle
└── settings.gradle
settings.gradle in root project:
include ':android', ':common', ':server'
build.gradle in root project:
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath("com.android.tools.build:gradle:3.4.1")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.40")
}
}
allprojects {
repositories {
google()
jcenter()
}
}
It seems like a bug, not just a problem. I found a temporary workaround from JetBrains issue tracker.
In IntelliJ IDEA, Go to "Settings / Build, Execution, Deployment / Build Tools / Gradle / Runner" and check the "Delegate IDE build/run actions to Gradle" option as true and it'll work correctly.

Intellij will not recognize antlr generated source code

I am having trouble getting Intellij to recognize the generated source code from antlr4. Any reference to the generated code appears as errors, code completion doesn't work, etc.
I am using maven and the antlr4-maven-plugin to generate the code.
My code, referencing the generated code compiles and builds fine under maven.
The generated code is under /target/generated-sources/antlr4, which is what Intellij expects.
I have tried the usual fixes such as reimport maven projects, update folders, invalidate cache, etc. None of it seems to work.
Anyone seen this before?
Is there a way to point to the generated sources directly within Intellij?
The problem
target/generated-sources/antlr4 is not automatically marked as source dir, instead its direct subdir com.example is. Intellij Idea fails to detect proper package for classes inside target/generated-sources/antlr4/com.example.
The cause
The source file *.g4 is in src/main/antlr4/com.example, but it actually it should be src/main/antlr4/com/example. Note the /. You probably forgot to mark src/main/antlr4 as source dir in Idea, and when you thought you are creating package structure, you actually just created single dir called com.example.
The fix
Mark src/main/antlr4 as source dir, create proper directory structure src/main/antlr4/com/example. Rebuild.
Alternative fix
Go to Project Structure - Modules - Source Folders and find the target/generated-sources/antlr4/com.example - click Edit properties and set Package prefix to com.example.
Different but related problem here
My issue was somewhat similar to #spilymp's:
I was putting the *.g4 files directly in src/main/antlr4:
.
├── src/
| └── main/
| ├── antlr/
| | ├── Main.g4
| | └── imported.g4
| └── java/
| └── com/
| └── test/
| └── Test.java
which led to source being generated in default package. I changed the package structure to match that of java:
.
├── src/
| └── main/
| ├── antlr/
| | ├── com/
| | | └── test/
| | | └── Main.g4
| | └── imports/
| | └── imported.g4
| └── java/
| └── com/
| └── test/
| └── Test.java
(Note that imports need to go directly in src/main/antlr4/imports)
After this I just run the antlr4 goal from the maven menu ([Module] > Plugins > antlr4 > antlr4:antlr4), which generated the sources in the default location (target/generated-sources/antlr4), where they were already marked with blue, generated-sources icon by Intellij, and MainParser can now be imported!
It turns out that my fiddling with preferences caused this issue. If anyone else has this problem:
As v0rl0n mentioned, first make sure the gen folder is listed as a source. Go to Module Settings -> Modules -> Sources.
Look to the right for the blue Source Folders. Ensure your generated source folders are listed (they should have a [generated] flag on the row).
If your generated source directory is listed there, and they are still not being recognized, go to Preferences -> File Types. Look in the Ignore files and folders field. Make sure you don't have anything listed that will match for your generated sources directory.
In my case, I had added target to this list, which caused my problem, and was a real pain to find.
I had what I think may have been a similar issue. I am new to IntelliJ, so I am guessing to some extent.
Is the gen folder in your project brown (i.e. a resource) or blue (source code)?
I could not get my project to recognize generated code until I managed to get the gen folder registered as one containing source code.
Sorry to say you may need to fiddle with it - I cannot recall how I did it - but all was well after I changed this.
Steps that worked for me:
Open Maven Projects in right tab
Right click on the module that contained my antlr code
Click Generate Source and Update Folders
I had a similar problem. Intellij found my generated code in the java folder but not in a subfolder of java. I solved it by putting my grammar files (*.g4) into a package (com.test):
.
├── src/
| └── main/
| ├── antlr/
| | └── com.test/
| | └── Test.g4
| └── java/
| └── test/
| └── Test.java
└── target/
└── generated-sources/
└── antlr/
└── com.test/
├── TestBaseListener.java
└── ...
The easiest way to solve this issue is to Right-Click the gen directory and choose Mark Directory as -> Sources Root

How to publish models vs. full project artifacts separately?

How could ivy support publishing artifacts of projects in multiple phases?
Suppose we had project A and B. A depends on B's models while B depends on A's models. (Usually the circular dependence isn't that direct, but the example serves. Our projects are relatively loosely coupled, sending messages to each other via the models) The models themselves don't depend on anything, so I can easy build those artifacts. However, while I can build moduleA-models.jar, I cannot build moduleA.jar until I get moduleB-models.jar. (And.. of course, visa-versa with module B.)
So I'm thinking a 2 phase publishing effort. I'm doing exactly that. I have an ant target that builds the models and then publishes the 'model' ivy conf. I run through all the projects building/publishing the models. I then go back and start building the rest of the project code. Note that 'going back and building the rest of the project code' implies a new publishing call... this time with all the artifacts, not just the model artifact.
However ivy is.. mildly unhappy with it. For example, it sometimes sees module A's 'published' ivy.xml with just the model jar, and then might find out later there's an updated ivy.xml for module A that has model and non-model jars in it. By and large I can get around that with 'changing="true"' dependency flag.
However, lately even that just fails for me and ivy is trying to build projects out of order and thus failing. Also I occasionally get into trouble about a missing version of a project (due again to the fact that it's seeing two different versions of a project's ivy.xml within the same build cycle).
So what's the recommended approach here? Separate ivy projects (in the same file structure) perhaps?
Why don't you structure your project to have a common module that builds and publishes the jars containing the message model classes?
├── build.xml
├── common
│   ├── build.xml
│   ├── ivy.xml
│   └── src
| ..
├── module1
│   ├── build.xml
│   ├── ivy.xml
│   └── src
| ..
└── module2
├── build.xml
├── ivy.xml
└── src
..
Each module can then have a dependency on these common dependencies:
<dependency org="myproj.common" name="module1-model" rev="1.0"/>
The root build file can use the buildlist task to determine the module build order based on the ivy file dependencies.
<target name="determine-build-order">
<ivy:buildlist reference="build-path">
<fileset dir="." includes="modules/**/build.xml"/>
</ivy:buildlist>
</target>
<target name="build" depends="determine-build-order">
<subant target="build" buildpathref="build-path" />
</target>