GradleでANTLRファイルのpackageが認識されない問題に対処した

## 2022/07/19 追記
[もっと単純な記述方法が判明したので最後に追加しました。](#より簡単な方法)

## 概要
パーサを実装しようとANTLRを使い始めた。
JavaのプロジェクトなのでGradleでいい感じにできないかと調べたらプラグインで対応していたので使い方を色々調べながらビルドスクリプトを書いたけど、package名のサブディレクトリはいかに置いたら`generateGrammarSource`タスクに失敗するようになった。
情報が少なかったけど、issueとか見ながらどうにか対応したので備忘録兼ねて書いておく。

## 構成
```
project-name
-- src/
  -- antlr
    -- package/
      -- PackageLexer.g4
      -- PackageParser.g4
  -- java
    -- package/
      -- Main.java
-- build.gradle
```

build.gradleは下記のように記述

```
plugins {
    id 'java'
    id 'antlr'
    id 'application'
}

group 'group'
version '1.0'
mainClassName = 'package.Main'

repositories {
    mavenCentral()
}

dependencies {
    antlr 'org.antlr:antlr4:4.9.2'
    implementation 'org.antlr:antlr4-runtime:4.9.2'
}
```

Lexer、Parserは次のように

```
lexer grammar PackageLexer;

HELLO: 'hello' ;
```

```
parser grammar PackageParser;
options { tokenVocab=PackageLexer; }

hello : HELLO ;
```

## 問題
ここでParserファイルにoptionsでLexerを指定するとビルドがコケる。
ググるとStackOverFlowで下記の記事がヒット
https://stackoverflow.com/questions/40995727/gradle-cant-find-antlr-token-file/49388412
どうも同様の問題っぽい。
対処法は文法ファイルをpackage下ではなく直に置けと

しかしJavaでルートに置くのはなんか嫌なのでもう少しまともな方法がないかと見てみたらGradleのGitHub Issueで次の報告が
https://github.com/gradle/gradle/issues/2565
package名がparrot！
これは見覚えあるぞ、Groovy3.0で実装された新しいパーサの名前だ！
ということはGroovyのビルドファイルで何かしら対応しているのではと思いApacheGroovyのリポジトリを確認
見事に解決法を見つけたので下記に記す。

## 対応方法
[ApacheGroovyのリポジトリ](https://github.com/apache/groovy)の中でANTLRが使われているのは[src/antlr](https://github.com/apache/groovy/tree/master/src/antlr)以下にLexerとParserがあった。
基本通りの配置で前述の通りルートに置かれている。
GroovyはGradle使って管理されているので、antlrのプラグインや依存関係の設定しているところを探すと[buildSrc/src/main/groovy/groovy-core.gradle](https://github.com/apache/groovy/blob/master/buildSrc/src/main/groovy/org.apache.groovy-core.gradle)がヒット

最下部を見るとやはりパーサを生成する`generateGrammarSource`タスクに対して設定がされていた。

```
tasks.named("generateGrammarSource") {
    maxHeapSize = '1g'
    outputs.cacheIf { true }
    outputDirectory = file("${buildDir}/generated/sources/antlr4")
    sourceSets.main.java.srcDirs += outputDirectory

    final PARSER_PACKAGE_NAME = 'org.apache.groovy.parser.antlr4'
    arguments += ["-visitor", "-no-listener", "-package", PARSER_PACKAGE_NAME]

    doLast {
        def parserFilePattern = 'Groovy*'
        def outputPath = generateGrammarSource.outputDirectory.canonicalPath
        def parserPackagePath = "${outputPath}/${PARSER_PACKAGE_NAME.replace('.', '/')}"
        file(parserPackagePath).mkdirs()
        copy {
            from outputPath
            into parserPackagePath
            include parserFilePattern
        }
        delete fileTree(outputPath) {
            include parserFilePattern
        }
    }
}
```

Gradleではこのように記述すると、そのタスクの実行後に`doLast`の処理が呼ばれて独自の処理を行うことができる。
上記のコードは`build/(antlrの出力ディレクトリ)/`以下にある`Groovy`から始まるファイルをpackage名以下にコピーしている。
~~かなり無理矢理な対応だけど、ANTLRやGradleの機能を使いこなしているであろうGroovyの設定でさえこうなのだから、これしか対応方法がないのだろう。~~

ということで必要な部分のみ流用して下記のように変更

まずは`src/antlr`直下に定義ファイルを移動

次にビルドスクリプトに下記を追加

```
generateGrammarSource {
    final PARSER_PACKAGE_NAME = 'package'
    arguments += [ "-package", PARSER_PACKAGE_NAME ]

    doLast {
        def parserFilePattern = 'Package*'
        def outputPath = generateGrammarSource.outputDirectory.canonicalPath
        def parserPackagePath = "${outputPath}/${PARSER_PACKAGE_NAME.replace('.', '/')}"
        file(parserPackagePath).mkdirs()
        copy {
            from outputPath
            into parserPackagePath
            include parserFilePattern
        }
        delete fileTree(outputPath) {
            include parserFilePattern
        }
    }
}
```

参考元と微妙に記述が違うけど気にしないでいいよ。
これでビルドすると問題なく動作した。

忘れないうちに記事にしようと、ちょっとまとまってない部分も多いけどとりあえずこんな感じで動かすことができたよってことで。
まだANTLR自体の使い方全然わからないのでゆっくり調べていこう。


## より簡単な方法
単純にgenerateGrammarSourceの中でoutputDirectoryに指定すればいいことがわかった。
なんで記事書いた当初この方法見つけきらなかったのだろうか……

```
generateGrammarSource {
    final PARSER_PACKAGE_NAME = 'package'
    arguments += ["-visitor", "-package", PARSER_PACKAGE_NAME]
    outputDirectory = file("${generateGrammarSource.outputDirectory}/${PARSER_PACKAGE_NAME.replace('.', '/')}")
}
```