web-dev-qa-db-ja.com

Java 9モジュールをビルドするGradleプロジェクトのリソースファイルはどこにありますか?

IDEA 2018.2.1以降、IDEは、モジュール化されていない依存関係から「モジュールグラフにない」エラー強調パッケージを開始します。module-info.Javaを追加しましたプロジェクトにファイルを追加し、必要なrequiresステートメントを追加しましたが、src/main/resourcesディレクトリのリソースファイルにアクセスできません。

(完全な例については、 このGitHubプロジェクト を参照してください。)

./gradlew runまたは./gradlew installDist +結果のラッパースクリプトを使用すると、リソースファイルを読み取ることができましたが、IDEからアプリを実行したときはできませんでした。

JetBrainsで issue を提出したところ、IDEAはモジュールパスを使用していたが、Gradleはデフォルトでクラスパスを使用していた。 build.gradleのブロックに続いて、Gradleをalso…にして、リソースファイルを読み取ることができませんでした。

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

「パッケージ」として関心のあるリソースディレクトリをexport- ingしようとしましたが、コンパイル時に次のエラーでビルドエラーが発生しました。

エラー:パッケージが空または存在しない:mydir

opensの代わりにexportsを使用すると、同じエラーが発生しましたが、警告に格下げされました。

mydirリソースディレクトリをsrc/main/Javaの下に移動してみましたが、同じエラー/警告が発生し、リソースがbuildディレクトリにコピーされませんでした。

Java 9)のリソースはどこにあると思われ、どのようにアクセスしますか?


注:問題の調査を続けた後、この質問をかなり編集しました。最初の質問では、リソースディレクトリ内のファイルを一覧表示する方法も見つけようとしていましたが、調査の過程で、それがレッドニシンであると判断しました。リソースディレクトリの読み取りは、リソースがfile:///のURLから読み取られている場合(それでもそうでない場合もあります)、2番目にプレーンファイルも機能していないため、問題はディレクトリではなく、リソースファイル全般にあることが明らかです。


ソリューション:

スローの答え ごとに、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()
  }
}

サイドノート:興味深いことに、私がしない場合は、runを作成するSlawのコードを追加し続けますファイルのリストを提供する代わりに、タスクがJARに対して実行され、実行タスクでリソースディレクトリInputStreamを読み取ろうとします IOException をスローします(JARに対しては、単に空のInputStreamを取得します。)

15
David Moles

ジグソーのサポートに関する Gradleの叙事詩 から私は以下で説明されているプロセスを容易にする可能性のあるプラグインについて学びました: gradle -modules-plugin 。エピックは chainsaw (experimental-jigsawプラグインの分岐)のような他のプラグインについても言及しています。 残念ながら、私はまだそれらのいずれも試していませんので、仮にそれがどれだけうまくリソースを処理しているかについてコメントすることはできません。 私はgradle-modules-pluginを試すことをお勧めします、それは少なくともジグソーモジュールを使用するのに必要な基本的な設定をかなり苦もなく扱うようです。そうは言っても、私が知る限り、Gradle 5.5.1の時点ではJigsawモジュールに対するファーストクラスのサポートはまだありません


バウンティでは、GradleおよびJigsawモジュールでリソースを処理するための「正しい方法」に関する公式ドキュメントをリクエストします。私の知る限り、Gradlestill(4.1​​0-rc-2以降)は「正しい方法」がないため、 tジグソーモジュールのファーストクラスのサポートがあります。あなたが得る最も近いのは Building Java 9 Modules documentです。

ただし、 メンション これは、モジュール内から(つまり、外部モジュールからではなく)リソースにアクセスすることについてです。これは、単純なbuild.gradle構成で修正するのは難しくありません。

デフォルトでは、Gradleはクラスとリソースの出力ディレクトリを分離します。次のようになります。

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

runタスクを使用する場合、classpathsourceSets.main.runtimeClasspathの値です。この値には両方のディレクトリが含まれますが、クラスパスが機能するため、これは機能します。クラスパスは単なる1つの巨大なモジュールであると考えることができます。

ただし、技術的にはresources内のファイルはclasses内のモジュールに属していないため、モジュールパスを使用する場合、これは機能しません。 resourcesがモジュールの一部であることをモジュールシステムに伝える方法が必要です。幸い、 --patch-module があります。このオプションは(Java --help-extraから引用):

jARファイルまたはディレクトリ内のクラスとリソースでモジュールをオーバーライドまたは拡張します。

また、次の形式になります(;セパレーターはプラットフォームに依存すると想定しています)。

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

モジュールが自身のリソースにアクセスできるようにするには、runタスクを次のように構成します。

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()
    }
}

これが私のやり方ですが、これまでのところかなりうまくいきました。


しかし、Gradleからアプリケーションを起動するときに、外部モジュールがモジュールのリソースにアクセスすることをどのように許可しますか?これはもう少し複雑になります。

外部モジュールがリソースにアクセスすることを許可する場合、モジュールはopensでなければなりません( Eng.Fouadの回答 を参照)少なくとも読み取りモジュールへのリソースのパッケージ(これのみ) encapsulated リソースに適用されます)。ただし、これを発見すると、コンパイル警告とランタイムエラーが発生します。

  1. コンパイルの警告は、モジュールシステムに応じて存在しないパッケージをopensしようとしているためです。
    • デフォルトではコンパイル時にリソースディレクトリが含まれないため、これは予想されることです(リソースのみのパッケージを想定)。
  2. ランタイムエラーは、モジュールシステムがopensディレクティブを宣言したパッケージを見つけられないためです。
    • ここでも、リソースのみのパッケージを想定しています。
    • これは、上記の--patch-moduleオプションでも発生します。パッチを適用する前に、モジュールシステムが何らかの整合性チェックを行うと思います。

注:「リソースのみ」とは、.Java/.classファイルがないパッケージを意味します。

コンパイル警告を修正するには、compileJavaタスク内で--patch-moduleを再度使用する必要があります。今回は、出力ディレクトリではなく、リソースのソースディレクトリを使用します。

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

実行時エラーには、いくつかのオプションがあります。最初のオプションは、リソース出力ディレクトリをクラスの出力ディレクトリと「マージ」することです。

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
}

2番目のオプションは、展開されたディレクトリではなくJARファイルを実行するようにrunタスクを構成することです。これは、最初のオプションと同様に、クラスとリソースを同じ場所に結合し、リソースがモジュールの一部であるため機能します。

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()
    }
}

これらのオプションはどちらも、runタスクで--patch-moduleを使用する代わりに使用できます(この回答の最初の部分で説明しています)。


おまけとして、これが--main-class属性をモジュラーJARに追加する方法です。

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

これにより、Java -m module.nameではなくJava -m module.name/some.package.Mainを使用できます。また、runタスクがJARを実行するように構成されている場合は、次のように変更できます。

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

に:

'--module', "$moduleName"

P.S。これを行うより良い方法がある場合はお知らせください

13
Slaw

@Slawの回答(彼に感謝)のほかに、呼び出し元のモジュールへのリソースを含むパッケージを開く必要がありました。次のように(moduleone.name module-info.Java):

opens io.fouad.packageone to moduletwo.name;

それ以外の場合、次はnullを返します。

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

クラスAはモジュールmoduletwo.nameにあり、ファイルlogging.propertiesはモジュールmoduleone.name内にあることを考慮してください。


または、moduleone.nameは、リソースを返すユーティリティメソッドを公開できます。

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