web-dev-qa-db-fra.com

Où vont les fichiers de ressources dans un projet Gradle qui construit un module Java 9?

À partir de IDEA 2018.2.1, le IDE démarre les packages de surbrillance des erreurs "pas dans le graphique du module" à partir des dépendances qui ont été modularisées. J'ai ajouté un module-info.Java fichier à mon projet et ajouté les instructions requires requises, mais j'ai maintenant du mal à accéder aux fichiers de ressources dans mon src/main/resources répertoire.

(Pour un exemple complet, voir ce projet GitHub .)

Quand j'ai utilisé ./gradlew run ou ./gradlew installDist + le script d'encapsulation résultant, j'ai pu lire les fichiers de ressources, mais lorsque j'ai exécuté mon application à partir de l'IDE, je ne l'ai pas été.

J'ai déposé un problème avec JetBrains, et ce que j'ai appris, c'est que IDEA utilisait le chemin du module, tandis que Gradle, par défaut, utilisait le chemin de classe. En ajoutant le bloc suivant à mon build.gradle, J'ai pu obtenir Gradle pour aussi … ne pas pouvoir lire de fichiers de ressources.

run {
    inputs.property("moduleName", moduleName)
    doFirst {
        jvmArgs = [
                '--module-path', classpath.asPath,
                '--module', "$moduleName/$mainClassName"
        ]
        classpath = files()
    }
}

J'ai essayé export- le répertoire de ressources qui m'intéressait en tant que "package", et au moment de la compilation, j'ai eu un échec de construction avec:

erreur: le package est vide ou n'existe pas: mydir

L'utilisation de opens au lieu de exports a obtenu la même erreur, bien que déclassé en avertissement.

J'ai même essayé de déplacer le répertoire de ressources mydir sous src/main/Java, mais cela a produit les mêmes erreurs/avertissements et a également entraîné la non-copie des ressources dans le répertoire build.

Où les ressources sont-elles censées aller dans Java 9, et comment y accéder?


Remarque: J'ai considérablement modifié cette question après avoir poursuivi les recherches sur le problème. Dans la question initiale, j'essayais également de comprendre comment répertorier les fichiers dans un répertoire de ressources, mais au cours de l'enquête, j'ai déterminé qu'il s'agissait d'un hareng rouge - d'abord, car la lecture des répertoires de ressources ne fonctionne que lorsque la ressource est étant lu à partir d'un file:/// URL (et peut-être même pas à ce moment-là), et deuxièmement parce que les fichiers simples ne fonctionnaient pas non plus, donc clairement le problème était avec les fichiers de ressources en général et pas spécifiquement avec les répertoires.


Solution:

Par réponse de Slaw , j'ai ajouté ce qui suit à build.gradle:

// at compile time, put resources in same directories as classes
sourceSets {
  main.output.resourcesDir = main.Java.outputDir
}

// at compile time, include resources in module
compileJava {
  inputs.property("moduleName", moduleName)
  doFirst {
    options.compilerArgs = [
      '--module-path', classpath.asPath,
      '--patch-module', "$moduleName=" 
        + files(sourceSets.main.resources.srcDirs).asPath,
      '--module-version', "$moduleVersion"
    ]
    classpath = files()
  }
}

// at run time, make Gradle use the module path
run {
  inputs.property("moduleName", moduleName)
  doFirst {
    jvmArgs = [
      '--module-path', classpath.asPath,
      '--module', "$moduleName/$mainClassName"
    ]
    classpath = files()
  }
}

Note latérale: Fait intéressant, si je ne le faites pas continuez à ajouter le code de Slaw qui rend le run tâche exécutée sur le JAR, essayant de lire un répertoire de ressources InputStream dans la tâche d'exécution maintenant lance un IOException au lieu de fournir la liste des fichiers. (Contre le JAR, il obtient simplement un InputStream. Vide.)

15
David Moles

De l'épopée de Gradle concernant le support de Jigsaw, j'ai entendu parler d'un plugin qui pourrait faciliter le processus décrit ci-dessous: gradle-modules-plugin . L'épopée mentionne également d'autres plugins tels que tronçonneuse (qui est une fourchette du plugin de puzzle expérimental). Malheureusement, je n'ai encore essayé aucun d'entre eux, je ne peux donc pas commenter la façon dont ils gèrent les ressources, voire pas du tout. Je recommande d'essayer gradle-modules-plugin, il semble gérer au moins les configurations de base nécessaires pour utiliser les modules Jigsaw plutôt sans douleur. Cela dit, pour autant que je sache, il n'y a toujours pas de support de première classe pour les modules Jigsaw à partir de Gradle 5.5.1.


Dans votre prime, vous demandez une documentation officielle sur "la bonne façon" de gérer les ressources avec les modules Gradle et Jigsaw. La réponse, pour autant que je sache, est qu'il n'y a pas de "bonne voie" parce que Gradle reste (à partir de 4.10-rc-2) ne ' t avoir un support de première classe pour les modules Jigsaw. Le document le plus proche est le document Building Java 9 Modules ).

Cependant, vous mentionnez il s'agit d'accéder aux ressources à partir d'un module (c'est-à-dire pas à partir de modules externes). Cela ne devrait pas être trop difficile à résoudre avec un simple build.gradle configuration.

Par défaut, Gradle sépare les répertoires de sortie des classes et des ressources. Cela ressemble à ceci:

build/
|--classes/
|--resources/

Lorsque vous utilisez la tâche run, le classpath est la valeur de sourceSets.main.runtimeClasspath. Cette valeur inclut les deux répertoires et cela fonctionne en raison de la façon dont le chemin de classe fonctionne. Vous pouvez y penser comme le chemin de classe n'est qu'un module géant.

Cela ne fonctionne pas, cependant, lorsque vous utilisez le modulepath car techniquement les fichiers à l'intérieur de resources n'appartiennent pas au module qui est à l'intérieur de classes. Nous avons besoin d'un moyen de dire au système de modules que resources fait partie du module. Heureusement, il y a --patch-module . Cette option sera (citée de Java --help-extra):

remplacer ou étendre un module avec des classes et des ressources dans des fichiers ou répertoires JAR.

Et a le format suivant (je suppose que le ; le séparateur dépend de la plateforme):

--patch-module <module>=<file>(;<file>)*

Pour permettre à votre module d'accéder à ses propres ressources, configurez simplement votre tâche run comme ceci:

run {
    input.property('moduleName', moduleName)
    doFirst {
        jvmArgs = [
                '--module-path', classpath.asPath,
                '--patch-module', "$moduleName=" + files(sourceSets.main.output.resourcesDir).asPath,
                '--module', "$moduleName/$mainClassName"
        ]
        classpath = files()
    }
}

C'est ainsi que je l'ai fait et cela a plutôt bien fonctionné jusqu'à présent.


Mais comment autorisez-vous les modules externes à accéder aux ressources de votre module lors du lancement de l'application à partir de Gradle? Cela devient un peu plus compliqué.

Lorsque vous souhaitez autoriser les modules externes à accéder aux ressources, votre module doit opens (voir réponse de Eng.Fouad ) le package de la ressource au moins au module de lecture (cela ne s'applique qu'à encapsulé ressources). Cependant, comme vous l'avez découvert, cela entraîne des avertissements de compilation et des erreurs d'exécution.

  1. L'avertissement de compilation est dû au fait que vous essayez de opens un package qui n'existe pas selon le système du module.
    • Cela est normal car le répertoire des ressources n'est pas inclus lors de la compilation par défaut (en supposant un package de ressources uniquement).
  2. L'erreur d'exécution est due au fait que le système du module ne trouve pas le package pour lequel vous avez déclaré une directive opens pour.
    • Encore une fois, en supposant un package de ressources uniquement.
    • Cela se produit même avec le --patch-module option mentionnée ci-dessus. Je suppose que le système de modules vérifie l'intégrité avant d'appliquer le patch.

Remarque: Par "ressources uniquement", j'entends des packages qui n'ont pas de .Java/.class fichiers.

Pour corriger l'avertissement de compilation, il vous suffit d'utiliser --patch-module à nouveau dans la tâche compileJava. Cette fois, vous utiliserez les répertoires source des ressources plutôt que le répertoire de sortie.

compileJava {
    inputs.property('moduleName', moduleName)
    doFirst {
        options.compilerArgs = [
                '--module-path', classpath.asPath,
                '--patch-module', "$moduleName=" + files(sourceSets.main.resources.srcDirs).asPath,
                '--module-version', "$version"
        ]
    }
}

Pour l'erreur d'exécution, il existe deux options. La première option consiste à "fusionner" le répertoire de sortie des ressources avec le répertoire de sortie des classes.

sourceSets {
    main.output.resourcesDir = main.Java.outputDir
}

jar {
    // I've had bad experiences when "merging" output directories
    // where the jar task ends up creating duplicate entries in the JAR.
    // Use this option to combat that.
    duplicateStrategy = DuplicatesStrategy.EXCLUDE
}

La deuxième option consiste à configurer la tâche run pour exécuter le fichier JAR plutôt que le ou les répertoires éclatés. Cela fonctionne car, comme la première option, il combine les classes et les ressources au même endroit et donc les ressources font partie du module.

run {
    dependsOn += jar
    inputs.property('moduleName', moduleName)
    doFirst {
        // add JAR file and runtime classpath. The runtime classpath includes the output of the main source set
        // so we remove that to avoid having two of the same module on the modulepath
        def modulepath = files(jar.archivePath) + (sourceSets.main.runtimeClasspath - sourceSets.main.output)
        jvmArgs = [
                '--module-path', modulepath.asPath,
                '--module', "$moduleName/$mainClassName"
        ]
        classpath = files()
    }
}

Ces deux options peuvent être utilisées au lieu d'utiliser --patch-module dans la tâche run (expliqué dans la première partie de cette réponse).


En bonus, voici comment j'ai ajouté le --main-class attribut à mes JAR modulaires:

jar {
    inputs.property('mainClassName', mainClassName)
    doLast {
        exec {
            executable = 'jar'
            args = [
                    '--update', '--file', "$archivePath",
                    '--main-class', "$mainClassName"
            ]
        }
    }
}

Cela vous permet d'utiliser Java -m module.name plutôt que Java -m module.name/some.package.Main. De plus, si la tâche run est configurée pour exécuter le JAR, vous pouvez modifier:

'--module', "$moduleName/$mainClassName"

À:

'--module', "$moduleName"

P.S. S'il y a une meilleure façon de le faire, faites-le moi savoir.

13
Slaw

À côté de la réponse de @ Slaw (grâce à lui), j'ai dû ouvrir le paquet qui contient les ressources du module de l'appelant. Comme suit (moduleone.name module-info.Java):

opens io.fouad.packageone to moduletwo.name;

Sinon, ce qui suit retournerait null:

A.class.getResource("/io/fouad/packageone/logging.properties");

considérant que la classe A est dans le module moduletwo.name et le fichier logging.properties est dans le module moduleone.name.


Alternativement, moduleone.name pourrait exposer une méthode utilitaire qui renvoie la ressource:

public static URL getLoggingConfigFileAsResource()
{
    return A.class.getResource("/io/fouad/packageone/logging.properties");
}
2
Eng.Fouad