fwcd / kotlin-debug-adapter

Kotlin/JVM debugging for any editor/IDE using the Debug Adapter Protocol
MIT License
110 stars 19 forks source link

Exception in thread "main" java.lang.NoClassDefFoundError: org/springframework/boot/SpringApplication #69

Open jmrsnt opened 1 year ago

jmrsnt commented 1 year ago

Hi!

First of all, thanks for this project!

When launching Kotlin Debugger, this error appears:

Exception in thread "main" java.lang.NoClassDefFoundError: org/springframework/boot/SpringApplication
    at com.tutorial.kotlin.KotlinApplicationKt.main(KotlinApplication.kt:13)
Caused by: java.lang.ClassNotFoundException: org.springframework.boot.SpringApplication
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
    ... 1 more

Full Log:

[INFO] main      Connected to client
[DEBUG] async1    Adding ignore pattern 'HELP.md' from D:\disk\experiments\kotlin-vscode\.gitignore
[DEBUG] async1    Adding ignore pattern '.gradle' from D:\disk\experiments\kotlin-vscode\.gitignore
[DEBUG] async1    Adding ignore pattern 'build/' from D:\disk\experiments\kotlin-vscode\.gitignore
[DEBUG] async1    Adding ignore pattern '!gradle/wrapper/gradle-wrapper.jar' from D:\disk\experiments\kotlin-vscode\.gitignore
[DEBUG] async1    Adding ignore pattern '!**/src/main/**/build/' from D:\disk\experiments\kotlin-vscode\.gitignore
[DEBUG] async1    Adding ignore pattern '!**/src/test/**/build/' from D:\disk\experiments\kotlin-vscode\.gitignore
[DEBUG] async1    Adding ignore pattern '.apt_generated' from D:\disk\experiments\kotlin-vscode\.gitignore
[DEBUG] async1    Adding ignore pattern '.classpath' from D:\disk\experiments\kotlin-vscode\.gitignore
[DEBUG] async1    Adding ignore pattern '.factorypath' from D:\disk\experiments\kotlin-vscode\.gitignore
[DEBUG] async1    Adding ignore pattern '.project' from D:\disk\experiments\kotlin-vscode\.gitignore
[DEBUG] async1    Adding ignore pattern '.settings' from D:\disk\experiments\kotlin-vscode\.gitignore
[DEBUG] async1    Adding ignore pattern '.springBeans' from D:\disk\experiments\kotlin-vscode\.gitignore
[DEBUG] async1    Adding ignore pattern '.sts4-cache' from D:\disk\experiments\kotlin-vscode\.gitignore
[DEBUG] async1    Adding ignore pattern 'bin/' from D:\disk\experiments\kotlin-vscode\.gitignore
[DEBUG] async1    Adding ignore pattern '!**/src/main/**/bin/' from D:\disk\experiments\kotlin-vscode\.gitignore
[DEBUG] async1    Adding ignore pattern '!**/src/test/**/bin/' from D:\disk\experiments\kotlin-vscode\.gitignore
[DEBUG] async1    Adding ignore pattern '.idea' from D:\disk\experiments\kotlin-vscode\.gitignore
[DEBUG] async1    Adding ignore pattern '*.iws' from D:\disk\experiments\kotlin-vscode\.gitignore
[DEBUG] async1    Adding ignore pattern '*.iml' from D:\disk\experiments\kotlin-vscode\.gitignore
[DEBUG] async1    Adding ignore pattern '*.ipr' from D:\disk\experiments\kotlin-vscode\.gitignore
[DEBUG] async1    Adding ignore pattern 'out/' from D:\disk\experiments\kotlin-vscode\.gitignore
[DEBUG] async1    Adding ignore pattern '!**/src/main/**/out/' from D:\disk\experiments\kotlin-vscode\.gitignore
[DEBUG] async1    Adding ignore pattern '!**/src/test/**/out/' from D:\disk\experiments\kotlin-vscode\.gitignore
[DEBUG] async1    Adding ignore pattern '/nbproject/private/' from D:\disk\experiments\kotlin-vscode\.gitignore
[DEBUG] async1    Adding ignore pattern '/nbbuild/' from D:\disk\experiments\kotlin-vscode\.gitignore
[DEBUG] async1    Adding ignore pattern '/dist/' from D:\disk\experiments\kotlin-vscode\.gitignore
[DEBUG] async1    Adding ignore pattern '/nbdist/' from D:\disk\experiments\kotlin-vscode\.gitignore
[DEBUG] async1    Adding ignore pattern '/.nb-gradle/' from D:\disk\experiments\kotlin-vscode\.gitignore
[DEBUG] async1    Adding ignore pattern '.vscode/' from D:\disk\experiments\kotlin-vscode\.gitignore
[DEBUG] async1    Adding ignore pattern '.git' from D:\disk\experiments\kotlin-vscode\.gitignore
[INFO] async1    Resolving dependencies for 'kotlin-vscode' through Gradle's CLI using tasks [kotlinLSPProjectDeps]...
[DEBUG] async1    Creating temporary gradle file D:\Windows\Tmp\classpath18145152464469241149.gradle
[WARN] async1    Could not resolve classpath using Gradle: ClassLoader.getSystemResourceAsStream(scriptName) must not be null
[INFO] async1    Successfully resolved kotlin-stdlib using Maven
[INFO] async1    Starting JVM debug session with main class com.tutorial.kotlin.KotlinApplicationKt
[DEBUG] async1    Launching VM
[DEBUG] async1    Finding sourcesRoots
[TRACE] async1    Updating threads
[TRACE] async1    Updating breakpoints
[DEBUG] eventBus  VM Event: VMStartEvent in thread main
[TRACE] async1    Configured debuggee listeners
[TRACE] async1    Instantiated debuggee
[DEBUG] eventBus  VM Event: ThreadStartEvent in thread main
[DEBUG] eventBus  VM Event: ThreadStartEvent in thread Notification Thread
[DEBUG] eventBus  VM Event: ThreadStartEvent in thread Common-Cleaner
Exception in thread "main" java.lang.NoClassDefFoundError: org/springframework/boot/SpringApplication
    at com.tutorial.kotlin.KotlinApplicationKt.main(KotlinApplication.kt:13)
Caused by: java.lang.ClassNotFoundException: org.springframework.boot.SpringApplication
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
    ... 1 more
[DEBUG] eventBus  VM Event: ThreadDeathEvent in thread main
[DEBUG] eventBus  VM Event: ThreadStartEvent in thread DestroyJavaVM
[DEBUG] eventBus  VM Event: ThreadDeathEvent in thread DestroyJavaVM
[DEBUG] eventBus  VM Event: VMDeathEvent
[DEBUG] eventBus  VM Event: VMDeathEvent
[INFO] eventBus  Sent exit event
[INFO] async0    Exiting JDI session
[ERROR] async0    Internal error: com.sun.jdi.VMDisconnectedException: Connection closed
[ERROR] java.util.concurrent.CompletionException: com.sun.jdi.VMDisconnectedException: Connection closed
[ERROR]     at java.base/java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:315)
[ERROR]     at java.base/java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:320)
[ERROR]     at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1770)
[ERROR]     at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
[ERROR]     at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
[ERROR]     at java.base/java.lang.Thread.run(Thread.java:833)
[ERROR] Caused by: com.sun.jdi.VMDisconnectedException: Connection closed
[ERROR]     at jdk.jdi/com.sun.tools.jdi.TargetVM.send(TargetVM.java:299)
[ERROR]     at jdk.jdi/com.sun.tools.jdi.VirtualMachineImpl.sendToTarget(VirtualMachineImpl.java:1169)
[ERROR]     at jdk.jdi/com.sun.tools.jdi.PacketStream.send(PacketStream.java:77)
[ERROR]     at jdk.jdi/com.sun.tools.jdi.JDWP$VirtualMachine$AllThreads.enqueueCommand(JDWP.java:314)
[ERROR]     at jdk.jdi/com.sun.tools.jdi.JDWP$VirtualMachine$AllThreads.process(JDWP.java:305)
[ERROR]     at jdk.jdi/com.sun.tools.jdi.VMState.allThreads(VMState.java:209)
[ERROR]     at jdk.jdi/com.sun.tools.jdi.VirtualMachineImpl.allThreads(VirtualMachineImpl.java:459)
[ERROR]     at org.javacs.ktda.jdi.JDIDebuggee.updateThreads(JDIDebuggee.kt:62)
[ERROR]     at org.javacs.ktda.adapter.KotlinDebugAdapter$threads$1.invoke(KotlinDebugAdapter.kt:384)
[ERROR]     at org.javacs.ktda.adapter.KotlinDebugAdapter$threads$1.invoke(KotlinDebugAdapter.kt:383)
[ERROR]     at org.javacs.kt.util.AsyncExecutorKt$sam$java_util_function_Supplier$0.get(AsyncExecutor.kt)
[ERROR]     at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1768)
[ERROR]     ... 3 more

It seems that it is not able to retrieve the list of dependencies and write to the temporary .gradle file, I verified that the D:\Windows\Tmp\classpath18145152464469241149.gradle file is empty.

My project structure:

D:\disk\experiments\kotlin-vscode
│   build.gradle.kts
│   gradlew
│   gradlew.bat
│   HELP.md
│   settings.gradle.kts
│   .gitignore
│   
├───gradle
│   └───wrapper
│           gradle-wrapper.jar
│           gradle-wrapper.properties
│
├───src
│   ├───main
│   │   ├───kotlin
│   │   │   └───com
│   │   │       └───tutorial
│   │   │           └───kotlin
│   │   │                   KotlinApplication.kt
│   │   │
│   │   └───resources
│   │           application.properties
│   │
│   └───test
│       └───kotlin
│           └───com
│               └───tutorial
│                   └───kotlin
│                           KotlinApplicationTests.kt
│
├───.gradle
│   ├───7.6
│   │   │   gc.properties
│   │   │
│   │   ├───fileChanges
│   │   │       last-build.bin
│   │   │
│   │   ├───vcsMetadata
│   │   ├───checksums
│   │   │       checksums.lock
│   │   │
│   │   ├───fileHashes
│   │   │       fileHashes.lock
│   │   │       fileHashes.bin
│   │   │
│   │   ├───dependencies-accessors
│   │   │       dependencies-accessors.lock
│   │   │       gc.properties
│   │   │
│   │   └───executionHistory
│   │           executionHistory.lock
│   │           executionHistory.bin
│   │
│   ├───vcs-1
│   │       gc.properties
│   │
│   └───buildOutputCleanup
│           buildOutputCleanup.lock
│           cache.properties
│           outputFiles.bin
│
├───.vscode
│       launch.json
│       settings.json
│
├───bin
│   ├───main
│   │   │   application.properties
│   │   │
│   │   └───com
│   │       └───tutorial
│   │           └───kotlin
│   │                   KotlinApplication.kt
│   │
│   └───test
│       └───com
│           └───tutorial
│               └───kotlin
│                       KotlinApplicationTests.kt
│
└───build
    │   resolvedMainClassName
    │
    ├───classes
    │   └───kotlin
    │       ├───main
    │       │   ├───com
    │       │   │   └───tutorial
    │       │   │       └───kotlin
    │       │   │               KotlinApplication.class
    │       │   │               KotlinApplicationKt.class
    │       │   │
    │       │   └───META-INF
    │       │           kotlin.kotlin_module
    │       │
    │       └───test
    │           ├───com
    │           │   └───tutorial
    │           │       └───kotlin
    │           │               KotlinApplicationTests.class
    │           │
    │           └───META-INF
    │                   kotlin.kotlin_module
    │
    ├───kotlin
    │   │   kotlin001SNAPSHOTplainjar-classes.txt
    │   │
    │   ├───compileKotlin
    │   │   ├───cacheable
    │   │   │   │   last-build.bin
    │   │   │   │
    │   │   │   └───caches-jvm
    │   │   │       ├───inputs
    │   │   │       │       source-to-output.tab
    │   │   │       │       source-to-output.tab_i
    │   │   │       │       source-to-output.tab.values.at
    │   │   │       │       source-to-output.tab.keystream
    │   │   │       │       source-to-output.tab.keystream.len
    │   │   │       │       source-to-output.tab_i.len
    │   │   │       │       source-to-output.tab.len
    │   │   │       │
    │   │   │       ├───lookups
    │   │   │       │       file-to-id.tab
    │   │   │       │       file-to-id.tab_i
    │   │   │       │       id-to-file.tab
    │   │   │       │       lookups.tab
    │   │   │       │       counters.tab
    │   │   │       │       id-to-file.tab.values.at
    │   │   │       │       id-to-file.tab.keystream
    │   │   │       │       id-to-file.tab.keystream.len
    │   │   │       │       id-to-file.tab.len
    │   │   │       │       file-to-id.tab.values.at
    │   │   │       │       file-to-id.tab.keystream
    │   │   │       │       file-to-id.tab.keystream.len
    │   │   │       │       file-to-id.tab_i.len
    │   │   │       │       file-to-id.tab.len
    │   │   │       │       lookups.tab_i
    │   │   │       │       lookups.tab.values.at
    │   │   │       │       lookups.tab.keystream
    │   │   │       │       lookups.tab.keystream.len
    │   │   │       │       lookups.tab_i.len
    │   │   │       │       lookups.tab.len
    │   │   │       │
    │   │   │       └───jvm
    │   │   │           └───kotlin
    │   │   │                   source-to-classes.tab
    │   │   │                   internal-name-to-source.tab
    │   │   │                   internal-name-to-source.tab_i
    │   │   │                   class-fq-name-to-source.tab
    │   │   │                   class-fq-name-to-source.tab_i
    │   │   │                   class-attributes.tab
    │   │   │                   class-attributes.tab_i
    │   │   │                   proto.tab
    │   │   │                   proto.tab_i
    │   │   │                   package-parts.tab
    │   │   │                   package-parts.tab_i
    │   │   │                   class-attributes.tab.values.at
    │   │   │                   class-attributes.tab.keystream
    │   │   │                   class-attributes.tab.keystream.len
    │   │   │                   class-attributes.tab_i.len
    │   │   │                   class-attributes.tab.len
    │   │   │                   class-fq-name-to-source.tab.values.at
    │   │   │                   class-fq-name-to-source.tab.keystream
    │   │   │                   class-fq-name-to-source.tab.keystream.len
    │   │   │                   class-fq-name-to-source.tab_i.len
    │   │   │                   class-fq-name-to-source.tab.len
    │   │   │                   source-to-classes.tab_i
    │   │   │                   source-to-classes.tab.values.at
    │   │   │                   source-to-classes.tab.keystream
    │   │   │                   source-to-classes.tab.keystream.len
    │   │   │                   source-to-classes.tab_i.len
    │   │   │                   source-to-classes.tab.len
    │   │   │                   proto.tab.values.at
    │   │   │                   proto.tab.keystream
    │   │   │                   proto.tab.keystream.len
    │   │   │                   proto.tab_i.len
    │   │   │                   proto.tab.len
    │   │   │                   package-parts.tab.values.at
    │   │   │                   package-parts.tab.keystream
    │   │   │                   package-parts.tab.keystream.len
    │   │   │                   package-parts.tab_i.len
    │   │   │                   package-parts.tab.len
    │   │   │                   internal-name-to-source.tab.values.at
    │   │   │                   internal-name-to-source.tab.keystream
    │   │   │                   internal-name-to-source.tab.keystream.len
    │   │   │                   internal-name-to-source.tab_i.len
    │   │   │                   internal-name-to-source.tab.len
    │   │   │
    │   │   └───local-state
    │   │           build-history.bin
    │   │
    │   ├───sessions
    │   └───compileTestKotlin
    │       ├───cacheable
    │       │   │   last-build.bin
    │       │   │
    │       │   └───caches-jvm
    │       │       ├───inputs
    │       │       │       source-to-output.tab
    │       │       │       source-to-output.tab_i
    │       │       │       source-to-output.tab.values.at
    │       │       │       source-to-output.tab.keystream
    │       │       │       source-to-output.tab.keystream.len
    │       │       │       source-to-output.tab_i.len
    │       │       │       source-to-output.tab.len
    │       │       │       
    │       │       ├───lookups
    │       │       │       file-to-id.tab
    │       │       │       file-to-id.tab_i
    │       │       │       id-to-file.tab
    │       │       │       lookups.tab
    │       │       │       counters.tab
    │       │       │       id-to-file.tab.values.at
    │       │       │       id-to-file.tab.keystream
    │       │       │       id-to-file.tab.keystream.len
    │       │       │       id-to-file.tab.len
    │       │       │       file-to-id.tab.values.at
    │       │       │       file-to-id.tab.keystream
    │       │       │       file-to-id.tab.keystream.len
    │       │       │       file-to-id.tab_i.len
    │       │       │       file-to-id.tab.len
    │       │       │       lookups.tab_i
    │       │       │       lookups.tab.values.at
    │       │       │       lookups.tab.keystream
    │       │       │       lookups.tab.keystream.len
    │       │       │       lookups.tab_i.len
    │       │       │       lookups.tab.len
    │       │       │
    │       │       └───jvm
    │       │           └───kotlin
    │       │                   source-to-classes.tab
    │       │                   internal-name-to-source.tab
    │       │                   internal-name-to-source.tab_i
    │       │                   class-fq-name-to-source.tab
    │       │                   class-fq-name-to-source.tab_i
    │       │                   class-attributes.tab
    │       │                   class-attributes.tab_i
    │       │                   proto.tab
    │       │                   proto.tab_i
    │       │                   class-attributes.tab.values.at
    │       │                   class-attributes.tab.keystream
    │       │                   class-attributes.tab.keystream.len
    │       │                   class-attributes.tab_i.len
    │       │                   class-attributes.tab.len
    │       │                   class-fq-name-to-source.tab.values.at
    │       │                   class-fq-name-to-source.tab.keystream
    │       │                   class-fq-name-to-source.tab.keystream.len
    │       │                   class-fq-name-to-source.tab_i.len
    │       │                   class-fq-name-to-source.tab.len
    │       │                   source-to-classes.tab_i
    │       │                   source-to-classes.tab.values.at
    │       │                   source-to-classes.tab.keystream
    │       │                   source-to-classes.tab.keystream.len
    │       │                   source-to-classes.tab_i.len
    │       │                   source-to-classes.tab.len
    │       │                   proto.tab.values.at
    │       │                   proto.tab.keystream
    │       │                   proto.tab.keystream.len
    │       │                   proto.tab_i.len
    │       │                   proto.tab.len
    │       │                   internal-name-to-source.tab.values.at
    │       │                   internal-name-to-source.tab.keystream
    │       │                   internal-name-to-source.tab.keystream.len
    │       │                   internal-name-to-source.tab_i.len
    │       │                   internal-name-to-source.tab.len
    │       │
    │       └───local-state
    │               build-history.bin
    │
    ├───resources
    │   └───main
    │           application.properties
    │
    ├───tmp
    │   ├───jar
    │   │       MANIFEST.MF
    │   │
    │   ├───bootJar
    │   │       MANIFEST.MF
    │   │
    │   └───test
    ├───libs
    │       kotlin-0.0.1-SNAPSHOT-plain.jar
    │       kotlin-0.0.1-SNAPSHOT.jar
    │
    ├───test-results
    │   └───test
    │       │   TEST-com.tutorial.kotlin.KotlinApplicationTests.xml
    │       │
    │       └───binary
    │               output.bin
    │               output.bin.idx
    │               results.bin
    │
    └───reports
        └───tests
            └───test
                │   index.html
                │
                ├───classes
                │       com.tutorial.kotlin.KotlinApplicationTests.html
                │
                ├───packages
                │       com.tutorial.kotlin.html
                │
                ├───css
                │       base-style.css
                │       style.css
                │
                └───js
                        report.js

build.gradle.kts:

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    id("org.springframework.boot") version "3.0.2"
    id("io.spring.dependency-management") version "1.1.0"
    kotlin("jvm") version "1.7.22"
    kotlin("plugin.spring") version "1.7.22"
}

group = "com.tutorial"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_17

repositories {
    mavenCentral()
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}

tasks.withType<KotlinCompile> {
    kotlinOptions {
        freeCompilerArgs = listOf("-Xjsr305=strict")
        jvmTarget = "17"
    }
}

tasks.withType<Test> {
    useJUnitPlatform()
}

.vscode/launch.json:

{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "kotlin",
            "request": "launch",
            "name": "Kotlin Launch",
            "projectRoot": "${workspaceFolder}",
            "mainClass": "com.tutorial.kotlin.KotlinApplicationKt",
            "logLevel": "ALL",
        },
    ]
}
MagnusMG commented 1 year ago

I seem to be having the same problem: Could not resolve classpath using Gradle: ClassLoader.getSystemResourceAsStream(scriptName) must not be null.

This happens as soon as I try to use an external library: I use the sample project for testing the debugger , and it works fine. If I add an external reference, I get the error.

[INFO] main      Connected to client
[INFO] async1    Resolving dependencies for 'kotlin-quick-start' through Gradle's CLI using tasks [kotlinLSPProjectDeps]...
[WARN] async1    Could not resolve classpath using Gradle: ClassLoader.getSystemResourceAsStream(scriptName) must not be null
[INFO] async1    Successfully resolved kotlin-stdlib using Maven
[INFO] async1    Starting JVM debug session with main class quick.start.AppKt
Exception in thread "main" java.lang.NoClassDefFoundError: se/magnusgunnarsson/kutil/DBConnectionBuilder
    at quick.start.AppKt.main(App.kt:17)
Caused by: java.lang.ClassNotFoundException: se.magnusgunnarsson.kutil.DBConnectionBuilder
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:581)
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
    ... 1 more
[INFO] eventBus  Sent exit event
[INFO] async0    Exiting JDI session

The project runs without problem when using ./gradlew run.

Here is the kotlin code:

package quick.start

import java.io.File
import se.magnusgunnarsson.kutil.DBConnectionBuilder

class App {
    val greeting: String
        get() = "Hello world."
}

fun main(args: Array<String>) {
    val conn = DBConnectionBuilder.createConnection( DBConnectionBuilder.DatabaseID.EUL_XGUMAG_TEST )
    val qry = """
        SELECT institution, year, OVERALL_SCORE_CALC AS score
            FROM RANK_ARWU_OVERALL
            WHERE country='Sweden'
            ORDER BY institution, year 
        """
    val stmt = conn.createStatement()
    val rs = stmt.executeQuery( qry )
    while ( rs.next() ){
        println( rs.getString(1) )
    }

    println(App().greeting)
}

I'm on Mac OS X 13.13.1, VSCode 1.77.3 and (to the best of my knowledge) openjdk 11.

themkat commented 1 year ago

Your projects are single module projects, right? Do you remember to actually build your projects before trying to debug? the debug adapter does not build anything for you, and depends on the files in the build-directory. Tried both a Spring and Quarkus project again just now to verify that nothing is suddenly broken, and both work fine after running a ./gradlew build first.

MagnusMG commented 1 year ago

Yes, it is single module, and yes, I build it first.