Gradle 9 で作成した Zip アーカイブの中身のタイムスタンプが 1980-02-01 になります

blog1.mammb.com


再現可能なビルド

Gradle 9 では、Jar、War、Ear、Zip などのアーカイブ生成が変更されました。

  • ファイルのタイムスタンプが固定化される
  • すべてのディレクトリには権限 0755 が固定設定される
  • すべてのファイルには権限 0644 が固定設定される
  • アーカイブ内のファイル順序が決定論的になる

これは、同じ入力から生成されたアーカイブがバイト単位で完全に同一となるように、reproducible archives(再現可能なアーカイブ)として生成されるようになったためです。

旧来の Gradle(3.4~)では以下のように明示的に有効化していたものが、Gradle 9 からデフォルトに変更されました。

allprojects {
  tasks.withType<AbstractArchiveTask>() {
    isPreserveFileTimestamps = false
    isReproducibleFileOrder = true
  }
}

再現可能なビルドは、サプライチェーン攻撃のリスクを軽減する目的で導入されています。 再現可能なビルドについては以下を参照してください。

https://reproducible-builds.org/


どの環境でビルドしても、生成されるアーカイブがバイト単位で同一となるように、アーカイブ内のファイル順序、タイムスタンプを固定化しています。

アーカイブ内のファイルの順序は基盤となるファイルシステムの影響を受けるため、バイト単位で同じアーカイブを再現することは困難です。ZIP、TAR、JAR、WAR、EARをソースからビルドするたびに、アーカイブ内のファイルの順序が変わる可能性があります。タイムスタンプのみが異なるファイルも、ビルドごとにアーカイブに差異をもたらします。


再現可能なビルド成果物の例

例えば、以下のような Zip タスクでアーカイブを生成した場合、

tasks.register<Zip>("distApp") {
    archiveFileName = "my-app-dist.zip"
    destinationDirectory = layout.buildDirectory.dir("dists")
    from(appClasses)
    with(webAssetsSpec)
}

my-app-dist.zip を解凍すると、中のファイルのタイムスタンプには 1980/02/01 が付与され、権限も固定化されます(プラグインで作成した Jar、War、Ear も同様)。


旧来のビルドに戻す

権限設定は git 側で管理されているし、1980/02/01 のような意味の無いタイムスタンプを付与したくない場合は以下のように指定します。

tasks.register<Zip>("distApp") {
    isPreserveFileTimestamps = true  // ファイルシステムのタイムスタンプを使用
    isReproducibleFileOrder = false  // ファイルシステムに基づくファイル順序を使用
    useFileSystemPermissions()       // ファイルシステムのアクセス権を使用
}

useFileSystemPermissions() については gradle.properties でプロジェクトグローバルに指定することもできます。

# ファイルシステムのアクセス権を使用
org.gradle.archives.use-file-system-permissions=true