Skip to content
This repository has been archived by the owner on Aug 10, 2021. It is now read-only.

Windows resources #2421

Closed
msink opened this issue Nov 26, 2018 · 5 comments
Closed

Windows resources #2421

msink opened this issue Nov 26, 2018 · 5 comments

Comments

@msink
Copy link
Contributor

msink commented Nov 26, 2018

OK, multiplatform libui artifact works.

But now yet another weird issue - when I try build windows helloword with it on clean computer (Appveyor CI) - it fails.

https://github.com/msink/hello-libui
https://ci.appveyor.com/project/msink/hello-libui/builds/20552868

plugins {
    id "org.jetbrains.kotlin.multiplatform" version "1.3.10"
}

repositories {
    mavenCentral()
    maven { url "https://dl.bintray.com/msink/kotlin-native" }
}

final def os = org.gradle.internal.os.OperatingSystem.current()

def resourcesDirectory = "$projectDir/src/nativeMain/resources"
def windowsResources = "$buildDir/resources/hello.res"

task compileWindowsResources(type: Exec) {
    onlyIf { os.isWindows() }

    def konanUserDir = System.getenv("KONAN_DATA_DIR") ?: "${System.getProperty("user.home")}/.konan"
    def windresDir = "${konanUserDir}/dependencies/msys2-mingw-w64-x86_64-gcc-7.3.0-clang-llvm-lld-6.0.1/bin"
    def rcFile = file("${resourcesDirectory}/hello.rc")

    inputs.file rcFile
    outputs.file file(windowsResources)
    commandLine "${windresDir}/windres", rcFile, '-O', 'coff', '-o', windowsResources
    environment 'PATH', "${windresDir};${System.getenv('PATH')}"
}

kotlin {
    final def nativePreset = os.isWindows() ? presets.mingwX64
            : os.isLinux()   ? presets.linuxX64
            : os.isMacOsX()  ? presets.macosX64
            : /*unknown host*/ null
    targets {
        fromPreset(nativePreset, 'native') {
            compilations.main {
                outputKinds 'executable'
                if (os.isWindows()) {
                    linkerOpts "$windowsResources -mwindows"
                }
            }
        }
    }
}

dependencies {
    nativeMainImplementation 'com.github.msink:libui:0.1.0'
}

tasks.compileKotlinNative.dependsOn compileWindowsResources
> Configure project :
Kotlin Multiplatform Projects are an experimental feature.
Download https://download.jetbrains.com/kotlin/native/builds/releases/1.0.2/windows/kotlin-native-windows-1.0.2.zip
> Configure project :
Unpack Kotlin/Native compiler (version 1.0.2)...
> Task :compileWindowsResources FAILED
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':compileWindowsResources'.
> A problem occurred starting process 'command 'C:\Users\appveyor/.konan/dependencies/msys2-mingw-w64-x86_64-gcc-7.3.0-clang-llvm-lld-6.0.1/bin/windres''
* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.
* Get more help at https://help.gradle.org
BUILD FAILED in 1m 25s
1 actionable task: 1 executed
Command exited with code 1

So here it runs windres before downloading LLVM bundle, and fails.
On my work computer it succeed.

Any way to fix this?

PS: I solved this for Appveyor by using windres from preinstalled msys2, but in general problem remains.

@msink
Copy link
Contributor Author

msink commented Jan 25, 2019

@ilmat192
Here my draft for https://youtrack.jetbrains.com/issue/KT-29311, in new Kotlin Gradle DSL:
link depends on windres, windres depends on compile, and compile will download LLVM bundle.

Is it correct, and if yes - can it be simplified?
https://github.com/msink/hello-libui/blob/master/build.gradle.kts

plugins {
    kotlin("multiplatform") version "1.3.20"
}

repositories {
    jcenter()
}

kotlin {
    sourceSets.create("nativeMain") {
        dependencies {
            implementation("com.github.msink:libui:0.1.2")
        }
    }

    val os = org.gradle.internal.os.OperatingSystem.current()!!
    val nativeTarget = when {
        os.isWindows -> mingwX64("native")
        os.isMacOsX -> macosX64("native")
        os.isLinux -> linuxX64("native")
        else -> throw Error("Unknown host")
    }
    configure(listOf(nativeTarget)) {
        binaries {
            executable(listOf(DEBUG)) {
                if (os.isWindows) {
                    windowsResources("hello.rc")
                    linkerOpts("-mwindows")
                }
            }
        }
    }
}

fun org.jetbrains.kotlin.gradle.plugin.mpp.Executable.windowsResources(rcFileName: String) {
    val suffix = "${buildType.getName().capitalize()}${target.getName().capitalize()}"
    val resourcesDir = "$projectDir/src/${target.name}Main/resources"
    val resFile = file("$buildDir/windowsResources$suffix/${rcFileName.substringBefore(".rc")}.res")

    val windres = tasks.create("compileWindowsResources$suffix}", Exec::class) {
        val konanUserDir = System.getenv("KONAN_DATA_DIR") ?: "${System.getProperty("user.home")}/.konan"
        val konanLlvmDir = "$konanUserDir/dependencies/msys2-mingw-w64-x86_64-gcc-7.3.0-clang-llvm-lld-6.0.1/bin"
        val rcFile = file("$resourcesDir/$rcFileName")

        inputs.file(rcFile)
        outputs.file(resFile)
        commandLine("cmd", "/c", "windres", rcFile, "-O", "coff", "-o", resFile)
        environment("PATH", "$konanLlvmDir;${System.getenv("PATH")}")

        dependsOn(tasks.named("compileKotlin${target.getName().capitalize()}"))
    }

    tasks.named(linkTask.name) { dependsOn(windres) }
    linkerOpts(resFile.toString())
}

@ilmat192
Copy link
Contributor

Hi!

It looks ok, I have only a couple of remarks:

  1. configure can be replaced with apply: configure(listOf(nativeTarget)) { -> nativeTarget.apply {
  2. tasks.create("compileWindowsResources$suffix}", Exec::class) - typo: the closing brace.
  3. The link task should be available directly: tasks.named(linkTask.name) { dependsOn(windres) } -> linkTask.dependsOn(windres)

@msink
Copy link
Contributor Author

msink commented Jan 29, 2019

Ok, a little bit cleaner, but still ugly:

fun org.jetbrains.kotlin.gradle.plugin.mpp.Executable.windowsResources(rcFileName: String) {
    val rcFile = file("${compilation.defaultSourceSet.resources.sourceDirectories.asPath}/$rcFileName")
    val resFile = file("$buildDir/windowsResources/${target.getName()}/${buildType.getName()}/$baseName.res")

    val windresTask = tasks.create<Exec>("windres${buildType.getName().capitalize()}${target.getName().capitalize()}") {
        val konanUserDir = System.getenv("KONAN_DATA_DIR") ?: "${System.getProperty("user.home")}/.konan"
        val konanLlvmDir = "$konanUserDir/dependencies/msys2-mingw-w64-x86_64-gcc-7.3.0-clang-llvm-lld-6.0.1/bin"

        inputs.file(rcFile)
        outputs.file(resFile)
        commandLine("$konanLlvmDir/windres", rcFile, "-D_${buildType.name}", "-O", "coff", "-o", resFile)
        environment("PATH", "$konanLlvmDir;${System.getenv("PATH")}")

        dependsOn(compilation.compileKotlinTask)
    }

    linkTask.dependsOn(windresTask)
    linkerOpts(resFile.toString())
}

Well, questions, step by step:

  1. what should be windres task name, to fit with other similar tasks?
  2. where should be input and output files, again to fit with similar tasks?

@ilmat192
Copy link
Contributor

1. We have no final task naming convention so I think the naming you use is more or less OK.

But it also may make sense to include a name of the executable in the task name somehow. The binaries DSL allows creating several executables with different name prefixes:

binaries {
    // The default name prefix is empty,
    // so we have just debugExecutable and releaseExecutable here.
    // Link tasks have corresponding names, e.g. linkDebugExecutableWindows.
    executable() {
        windowsResources(...)
    }

    // An executable with a custom name prefix.
    // Executable names: fooDebugExecutable, fooReleaseExecutable.
    // Link tasks names: linkFooDebugExecutableWindows, linkFooReleaseExecutableWindows.
    executable("foo") {
        windowsResources(...)
    }
} 

Here is a naming conflict for the windres tasks.

There is no special API to get the name prefix from a binary but such a prefix may be extracted from the binary name.

Also you can use the whole binary name as a part of the task name. Binaries of the same target have unique names so it provides you with unique names for windres tasks. E.g. compileResourcesDebugExecutableWindows or windresDebugExecutableWindows.

Alternatively each compilation of any target (except common) has a processResources task. You can access it using the processResourcesTaskName compilation property. By default it's just a file copying task but it should be possible to add a doLast action to it and call windres in this action. So you'll get a compiled resources per compilation instead of getting them per binary. It may be more convenient.

If you choose this approach, note that binaries created using the binaries DSL don't use the linkerOpts property of a compilation. This property is kept only for compatibility reasons and affects only binaries declared using the compilation.outputKinds construction.

2. There is no final layout for output files in the build dir too. The resource copying task I mentioned above uses the following pattern: build/processedResources/<targetName>/<compilationName> (see sources). I think it make sense to store the compiled resources in a similar way. E.g. build/windowsResources/<targetName>/<binaryName>/ or build/windowsResources/<targetName>/<compilationName>/. Most probably our processing for Windows resources will use the processedResources directory so such an approach will not conflict with this feature when we add it.

@msink
Copy link
Contributor Author

msink commented Feb 1, 2019

Resources can be different for each executable, so running windres per compilation is wrong.
Updated:

fun org.jetbrains.kotlin.gradle.plugin.mpp.Executable.windowsResources(rcFileName: String) {
    val taskName = linkTaskName.replaceFirst("link", "windres")
    val inFile = compilation.defaultSourceSet.resources.sourceDirectories.singleFile.resolve(rcFileName)
    val outFile = buildDir.resolve("processedResources/$taskName.res")

    val windresTask = tasks.create<Exec>(taskName) {
        val konanUserDir = System.getenv("KONAN_DATA_DIR") ?: "${System.getProperty("user.home")}/.konan"
        val konanLlvmDir = "$konanUserDir/dependencies/msys2-mingw-w64-x86_64-gcc-7.3.0-clang-llvm-lld-6.0.1/bin"

        inputs.file(inFile)
        outputs.file(outFile)
        commandLine("$konanLlvmDir/windres", inFile, "-D_${buildType.name}", "-O", "coff", "-o", outFile)
        environment("PATH", "$konanLlvmDir;${System.getenv("PATH")}")

        dependsOn(compilation.compileKotlinTask)
    }

    linkTask.dependsOn(windresTask)
    linkerOpts(outFile.toString())
}

Remaining problems:

  1. inFile and konanLlvmDir still looks ugly,
    Well, maybe inFile is ok, but konanLlvmDir exposes K/N compiler internals.
  2. really *.rc file includes some other files, so input should depend on all of them.
    Or just run thask always, it is really very fast.

@msink msink changed the title Dependency on LLVM bundle Windows resources Feb 2, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants