2025年6月9日月曜日

Gradle 8.10.0 への苦難の記録(1)

Android の Project を Build しなくてはならなくなりました。気が重たい。Gradle のバージョンが上がると、必ずビルドが通らなくなります。今回も鬼のように問題が噴出して対応に1週間以上の時間を取られました。過去も大抵、数日から1週間以上の時間を要しました。

 なぜ、こんなに時間を取られるかというと、gradle の api が高速に移動するのでエンドユーザがバージョン毎にapiの書き換えや対応を強要される事、gradle 自身が変更により毎回バグを混入させる事にあります。 例を挙げれば、targetSdkVersion, compileSdkVersion は deprecate ですなんてメッセージが出て、信用して build.gradle の targetSdkVersion, compileSdkVersion の行を削除すると、Sync Gradle では、targetSdkVersion だか compileSdkVersion だかが見つからないとメッセージを表示して Sync に失敗するという体たらくです。 思いつきで、何でも簡単に変更しすぎです。gradle 自身も自身の下した api や property の廃棄や変更についていけてないし、バージョン間の整合も取れないので、ビルドシステム全体として見た場合は、ぐちゃぐちゃでカオスな状況が生じています。

 あなたが、Windows のアプリを開発していて、ある日 CreateFileEx という API名が GenerateFileEx という名前に変更されました。なんてアナウンスを受けたらどうなるか、想像がつきますか? コンパイルオプション /EHsc が急に廃止されて、config.options というファイルに記述しないと動作しなくなったら、どうなるか想像がつきますか?例外を有効にするオプションがデフォルト true から false に変更されたら、どうなるか想像がつきますか?

gradle では、それが日常的に病的に起こります。数か月前までビルドが通っていたはずなのに、ビルドが壊れて動かなくなるんです。... was moved. ... wad deprecated. ... was deprecated. ... was moved. まぁ、まだ moved や deprecated を表示してくれるようになっただけでも有り難いんですけどね。以前はエラーメッセージを検索して StackOverflowを見て、deprecated だった事を知って対策してましたから。ただ、エラーメッセージを検索してネットの情報を調べないと対処方法がわからないのは、相変わらずです。

  Copilot くんにお願いして名称の変更に限定して、変更をまとめてもらいました。
私の記憶が確かならば、この表は氷山の一角です。taskを使用している場合、taskの依存関係を記述しなければならないのですが、ヒステリックにtask名が変更されて、その都度、プロジェクトの修正を余儀なくされました。ある機能のtask名は、3回以上名前が変更されました。debugは間違ってるreleaseだ、いやJarじゃないRFileだといった具合に…。ころころと名前変えられたら、プロジェクトファイルも全部書き換えないと動きません。ファイルをコピーする関数名も変更されましたし、gradleの変更頻度は病的で、まともではありません。担当者のお気持ちで担当者がその名前が気に入らなければ名前が変更されます。エンドユーザは、気まぐれで変更された名前に合わせるようプロジェクトを修正しなければなりません。毎回毎回、山のように修正を余儀なくされます。


イライラはピークに達するものの落ち着いて、次のような書き込みをする決断に至ります。
Why does gradle deprecate and remove things so often? に象徴されていると思います。
 話を戻します。

 今回ビルドするプロジェクトは、legacy な Java による Application の話であり、KotlinでもGroovyでもありません。AndroidStudioにより作成できるプロジェクトは、Kotlin と Groovy の2択でlegacyのプロジェクトという選択肢が無いのも頭の痛い問題です。KotlinでもGroovyでもないのに、プロジェクトのバージョンアップ中に kotlin や groovy といったエラーを見かけ、これらのエラーを見る度に一体何のエラーなのか意味不明で頭の痛い思いをしました。

ライブラリのビルド
 gradle-3.5-all.zip と gradle-3.5.2
 gradle-7.5-all.zip と gradle-7.4.2 
以上の組み合わせから 
 gradle-8.14.1-bin.zip と gradle-10.0.0
へと移行する形になりました。ファイル名も -all から -bin に変更されています。なんでも allはファイルサイズがデカくなるからなんだそうです。gradle-8.14.0-all.zip ってやってエラーになるから何事や?と思ったら、知らんがな。いちいち、こんな調子で時間を取られます。
ここを参照すればわかるように私も質問者と同じ感覚です。
尚、試行錯誤している間は gradle-8.14.0-bin.zip しか出ていませんでしたが、途中で8.14.1がリリースされて、まともにビルドできるようになりました。その間はライブラリの依存関係バグがあったのか、何をやってもビルドが通りませんでした。

gradle-8.x になって変更された箇所は

・AndroidManifest.xml から package="com.foo.bar" の属性が削除された事
・build.gradle(app) で android { } の 下に namespace = "com.foo.bar" の記述が必要になった事。

ビルド時にサジェストがあったので、これに関しては比較的親切な対応と言えます。

しかし、こんな変更必要ですか?案の定、この変更によって、package.R.class が出力されないバグが混入し、アプリケーションのビルド時に、package R does not exist. というエラーが発生して、apk が作成できませんでしたが、gradle-8.10.1 で修正されました。gradle に振り回されて修正しても直らない地獄を徘徊しているうちに修正されました。

今回も deprecated の嵐で、バージョンアップすると、ことごとくビルドが失敗しました。何箇所も問題が発生し、たくさんの修正が必要でした。ライブラリをまともにビルドできるまでに4日以上費やしました。この他に android API の deprecated 対応にも4日以上の時間を取られました。

まずは、JDKのバージョンの整理が必要でした。今までは、なんとなくビルドできていましたが、そうはいきませんでした。
gradle は JDK のバージョンに煩い。jdk-11 じゃないと動きません。jdk-17 じゃないと動きません。jdk-18じゃないと動きません。と、色々注文が多いです。開発環境では、Oracleがインストールされていると、oracle が javac に対して path を通していたり、Visual Studio が javac に path を通していたり、Android Studio は SDK に設定されている JDKを使用しようと毎回 javac をリセットしたり、gradle は GRADLE 専用の JDK の PATH を設定してたりします。バージョンアップ中に GRADLE_JDK_LOCAL_PATH という名前への変更が行われたので、とにかく専用のPATHが設定されているという事です。これらpathの仕様は担当者の気まぐれで変更される恐れがあります。ライブラリのビルドにはJDK-17、アプリのビルドにはJDK-11を使用しました。プロジェクト>設定>検索>gradle で、モジュール>gradle の JDKの指定と、ウィンドウズの環境変数 path を制御する事で自分は対応しました。これに関しては gradleが JDKxxでは動作しないとメッセージを表示してくれるので対応しやすいです。

いろいろありすぎて、書ききれません…続きます。

android ProgressBar が deprecated

ProgressBar が deprecated になっていた。
ダイアログを表示すると、他の操作ができないから埋込レイアウトにしとけ
というのがGoogle先生の指示なんだけど、アプリケーションのステート的には、このタスクが終了しないと、どのみち次のオペレーションはできないので、埋込にする意味がないと思う。
処理の途中でアプリを切り替えられると、onDestroyが呼ばれるので、それも困る
そんな時はやり直しですしおすし。
それにダイアログを表示していてもアプリは切り替えられる
埋め込んだところで、大差がないと思うのである。
で、いちいち埋込の画面を作成するのは、大変なのでプログレスダイアログは欲しい。
package com.foo.Helper;

import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.content.Context;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import android.widget.ProgressBar;

import com.foo.helper.R;

public class CommonProgressDialog extends DialogFragment {

	private final Context context_;

	private final String title_;

	private final String message_;

	private AlertDialog.Builder builder_;

	private AlertDialog dialog_;

	private ProgressBar progressBar_;

	@NonNull
	@Override
	public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
		dialog_ = builder_.create();
		return dialog_;
	}

	private void BuildBase(final Context context, final String title, final String message) {
		builder_ = new AlertDialog.Builder(context)
				.setView(R.layout.progress)
				.setTitle(title)
				.setCancelable(false)
				//.setCanceledOnTouchOutside(false)
				.setMessage(message);
	}

	public CommonProgressDialog(final Context context, final String title, final String message) {
		this.context_ = context;
		this.title_ = title;
		this.message_ = message;
		BuildBase(context, title, message);
	}

	/// 中止ボタンを設置する
	public void setNegative(final DialogInterface.OnClickListener l) {
		if( dialog_ == null ) return;
		dialog_.setButton(DialogInterface.BUTTON_NEGATIVE, getString(android.R.string.cancel), l);
	}

	public void setMax(int maxValue) {
		if( dialog_ == null) return;
		ProgressBar pb = dialog_.findViewById(R.id.progress_bar);
		if( pb != null ) {
			pb.setMax(maxValue);
		}
	}

	public void setProgress(int progress) {
		if( dialog_ == null) return;
		ProgressBar pb = dialog_.findViewById(R.id.progress_bar);
		if( pb != null ) {
			pb.setProgress(progress);
		}
	}


	public void show() {
		if( dialog_ == null) {
			dialog_ = builder_.create();
		}
		dialog_.show();
	}

	public void dismiss() {
		dialog_.dismiss();
		dialog_ = null;
	}

	/*
	public CommonProgressDialog(Context context) {
		super(context);
		setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
	}
	public CommonProgressDialog(Context context, int theme) {
		super(context,theme);
		setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
	}

	 */
}


progress.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:padding="20dp">
    <ProgressBar
        android:id="@+id/progress_bar"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1" />

    <TextView
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="4"
        android:gravity="center"
        android:text="Please wait! This may take a moment." />
</LinearLayout>