Gradle প্লাগ-ইন লেখা

Android Gradle প্লাগ-ইন (AGP) হল Android অ্যাপ্লিকেশনের জন্য অফিসিয়াল বিল্ড সিস্টেম। এতে বিভিন্ন ধরনের সোর্স কম্পাইল করা এবং সেগুলিকে একসাথে একটি অ্যাপ্লিকেশনে লিঙ্ক করার জন্য সহায়তা অন্তর্ভুক্ত রয়েছে যা আপনি একটি ফিজিক্যাল Android ডিভাইস বা এমুলেটরে চালাতে পারেন।

AGP-তে প্লাগ-ইনের জন্য এক্সটেনশন পয়েন্ট থাকে যা বিল্ড ইনপুট কন্ট্রোল করে এবং নতুন ধাপের মাধ্যমে এর কার্যকারিতা বাড়ায়। এই নতুন ধাপগুলি স্ট্যান্ডার্ড বিল্ড টাস্কের সাথে ইন্টিগ্রেট করা যায়। AGP-এর আগের ভার্সনে অফিসিয়াল API-কে ইন্টার্নাল ইমপ্লিমেন্টেশন থেকে আলাদা করা হয়নি। 7.0 ভার্সন থেকে শুরু করে, AGP-তে অফিসিয়াল, স্টেবল API-এর সেট আছে যার উপর আপনি ভরসা করতে পারেন।

AGP API লাইফসাইকেল

AGP, API-এর স্ট্যাটাস নির্ধারণ করতে Gradle ফিচার লাইফসাইকেল অনুসরণ করে:

  • ইন্টার্নাল: সর্বজনীন ব্যবহারের জন্য নয়
  • ইনকিউবেটিং: সর্বজনীন ব্যবহারের জন্য উপলভ্য হলেও চূড়ান্ত নয়, যার অর্থ হল চূড়ান্ত ভার্সনে সেগুলি ব্যাকওয়ার্ড কম্প্যাটিবল নাও হতে পারে
  • পাবলিক: সর্বজনীন ব্যবহারের জন্য উপলভ্য এবং স্টেবল
  • অননুমোদিত: আর কাজ করে না এবং নতুন API দিয়ে পরিবর্তন করা হয়েছে

শীঘ্রই বন্ধ করা সংক্রান্ত নীতি

পুরনো API বন্ধ করে দিয়ে এবং নতুন, স্টেবল API ও নতুন ডোমেন স্পেসিফিক ল্যাঙ্গুয়েজ (DSL) দিয়ে সেগুলি প্রতিস্থাপন করে AGP-কে আরও উন্নত করা হচ্ছে। এই বিবর্তন একাধিক AGP রিলিজ জুড়ে চলবে এবং আপনি এই সম্পর্কে আরও জানতে পারবেন AGP API/DSL মাইগ্রেশন টাইমলাইন থেকে।

এই মাইগ্রেশন বা অন্য কোনও কারণে AGP API বন্ধ করা হলে, সেগুলি বর্তমান প্রধান রিলিজে উপলভ্য থাকবে, তবে সতর্কতা জেনারেট করবে। পরবর্তী প্রধান রিলিজে AGP থেকে বন্ধ হয়ে যাওয়া API সম্পূর্ণভাবে সরিয়ে দেওয়া হবে। যেমন, AGP 7.0-এ কোনও API বন্ধ হয়ে গেলে, সেটি সেই ভার্সনে উপলভ্য থাকবে এবং সতর্কতা দেখাবে। AGP 8.0-এ সেই API আর উপলভ্য থাকবে না।

সাধারণ বিল্ড কাস্টমাইজেশনে ব্যবহৃত নতুন API-এর উদাহরণ দেখতে, Android Gradle প্লাগ-ইন রেসিপি দেখুন। এগুলি সাধারণ বিল্ড কাস্টমাইজেশনের উদাহরণ প্রদান করে। এছাড়াও, আমাদের রেফারেন্স ডকুমেন্টেশন থেকে নতুন API সম্পর্কে আরও বিবরণ পাবেন।

AGP API আর্টিফ্যাক্টের সাথে কম্পাইল করুন

কাস্টম Gradle প্লাগ-ইন বা বিল্ড লজিক (যেমন buildSrc ডিরেক্টরি, কনভেনশন প্লাগ-ইন মডিউল বা স্ট্যান্ডঅ্যালোন প্লাগ-ইন প্রোজেক্টে) লিখতে যা AGP এক্সটেন্ড করে, সম্পূর্ণ প্লাগ-ইন ইমপ্লিমেন্টেশন আর্টিফ্যাক্টের পরিবর্তে AGP-এর gradle-api আর্টিফ্যাক্টের সাথে কম্পাইল করুন।

gradle-api আর্টিফ্যাক্টের সাথে কেন কম্পাইল করবেন?

AGP দুটি প্রাথমিক আর্টিফ্যাক্ট প্রকাশ করে:

  • com.android.tools.build:gradle-api: এতে অফিসিয়াল AGP API থাকে, যার মধ্যে স্টেবল API (যেমন AndroidComponentsExtension, Variant এবং পাবলিক DSL ইন্টারফেস), ইনকিউবেটিং API (@Incubating হিসেবে চিহ্নিত) এবং বন্ধ হয়ে যাওয়া API (@Deprecated হিসেবে চিহ্নিত) অন্তর্ভুক্ত।
  • com.android.tools.build:gradle: ইন্টার্নাল ক্লাস, বিল্ড টাস্ক ও কম্পাইলার ইন্টিগ্রেশন সহ সম্পূর্ণ AGP রানটাইম ইমপ্লিমেন্টেশন রয়েছে।

gradle-api আর্টিফ্যাক্টের সাথে কম্পাইল করলে বেশ কিছু গুরুত্বপূর্ণ সুবিধা পাওয়া যায়:

  • ইন্টার্নাল API এড়িয়ে চলুন: এটি নিশ্চিত করে যে আপনার প্লাগ-ইন কোড শুধুমাত্র অফিসিয়াল API অ্যাক্সেস করে। এটি ইন্টার্নাল ইমপ্লিমেন্টেশন ক্লাসের (com.android.build.gradle.internal.*) উপর দুর্ঘটনাবশত নির্ভরতা প্রতিরোধ করে যা কোনও সতর্কতা ছাড়াই AGP ভার্সন জুড়ে পরিবর্তন বা সরানো যেতে পারে।
  • দ্রুত বিল্ড এবং ছোট ফুটপ্রিন্ট: gradle-api আর্টিফ্যাক্টের সম্পূর্ণ gradle আর্টিফ্যাক্টের চেয়ে অনেক ছোট ডিপেন্ডেন্সি ট্রি আছে। এটি ডাউনলোডের সাইজ কমায়, ক্লাসপাথ পলিউশন এড়ায় এবং আপনার বিল্ড লজিকের কম্পাইলেশন দ্রুত করে।
  • রানটাইম আইসোলেশন: কম্পাইলেশনের সময় কাস্টম প্লাগ-ইনের জন্য শুধু API ডেফিনেশন প্রয়োজন হয়। রানটাইমে, সম্পূর্ণ AGP ইমপ্লিমেন্টেশন Android বিল্ডের মাধ্যমে প্রদান করা হয়, যখন Android অ্যাপ্লিকেশন বা লাইব্রেরি প্লাগ-ইন প্রয়োগ করা হয়।

নির্ভরতা যোগ করুন

আপনার প্লাগ-ইন প্রোজেক্টের বিল্ড ফাইল বা buildSrc ডিরেক্টরিতে ডিপেন্ডেন্সি হিসেবে gradle-api আর্টিফ্যাক্ট যোগ করুন। gradle-api আর্টিফ্যাক্ট Google-এর Maven রিপোজিটরিতে প্রকাশ করা হয়েছে। আর্টিফ্যাক্ট সংক্রান্ত সমস্যার সমাধান করতে, আপনার বিল্ডে google() রিপোজিটরি অন্তর্ভুক্ত আছে কিনা তা ভাল করে দেখে নিন। যেমন, settings.gradle বা settings.gradle.kts ফাইলের dependencyResolutionManagement.repositories ব্লকে অথবা আপনার প্লাগ-ইনের বিল্ড ফাইলের repositories ব্লকে অথবা buildSrc ডিরেক্টরির বিল্ড ফাইলের ব্লকে এটি কনফিগার করুন।

ভার্সন ক্যাটালগ সহ

আপনার প্রোজেক্টে ভার্সন ক্যাটালগ ব্যবহার করা হলে, gradle-api ডিপেন্ডেন্সি gradle/libs.versions.toml ফাইলে উল্লেখ করুন:

[versions]
androidGradlePlugin = "9.4.0"

[libraries]
android-gradle-plugin-api = { group = "com.android.tools.build", name = "gradle-api", version.ref = "androidGradlePlugin" }

android-gradle-plugin-api ক্যাটালগ অ্যালিয়াস টাইপ-সেফ অ্যাক্সেসর libs.android.gradle.plugin.api জেনারেট করে।

তারপর, আপনার প্লাগ-ইনের বিল্ড ফাইলের dependencies ব্লকে libs.android.gradle.plugin.api যোগ করুন:

Kotlin

dependencies {
    compileOnly(libs.android.gradle.plugin.api)
}

গ্ৰুভি

dependencies {
    compileOnly libs.android.gradle.plugin.api
}

ভার্সন ক্যাটালগ ছাড়া

আপনার প্রোজেক্টে ভার্সন ক্যাটালগ ব্যবহার না করা হলে, আপনার প্লাগ-ইনের বিল্ড ফাইলে সরাসরি ডিপেন্ডেন্সি ঘোষণা করুন:

Kotlin

dependencies {
    compileOnly("com.android.tools.build:gradle-api:9.4.0")
}

গ্ৰুভি

dependencies {
    compileOnly 'com.android.tools.build:gradle-api:9.4.0'
}

প্রকাশনার জন্য তৈরি স্ট্যান্ডঅ্যালোন প্লাগ-ইনের ডিফল্ট হিসেবে compileOnly কনফিগারেশন বিকল্প ব্যবহার করুন। কনজিউমিং প্রোজেক্ট AGP প্রয়োগ করে এবং বিল্ডস্ক্রিপ্ট ক্লাসপাথে রানটাইম ক্লাস প্রদান করে। compileOnly কনফিগারেশন ব্যবহার করলে, আপনার প্রকাশিত প্লাগ-ইন নির্দিষ্ট AGP ভার্সনে রানটাইম ডিপেন্ডেন্সি লিক করতে পারে না এবং গ্রাহকদের জন্য ভার্সন সংক্রান্ত সমস্যা এড়ানো যায়। আপনি যদি ইন্টার্নাল বিল্ড লজিক ডেভেলপ করেন—যেমন buildSrc ডিরেক্টরিতে প্লাগ-ইন বা কম্পোজিট বিল্ডে কনভেনশন প্লাগ-ইন—পরিবর্তে implementation কনফিগারেশন বিকল্প ব্যবহার করুন। এইসব প্লাগ-ইন বিল্ডের মধ্যে রান করে এবং রানটাইম ক্লাসপাথে AGP-এর প্রয়োজন হয়।

Gradle বিল্ডের প্রাথমিক তথ্য

এই গাইডে Gradle বিল্ড সিস্টেমের সব তথ্য নেই। তবে, আমাদের API-এর সাথে ইন্টিগ্রেট করতে আপনাকে সাহায্য করার জন্য প্রয়োজনীয় ন্যূনতম ধারণার সেট এতে কভার করা হয়েছে এবং আরও পড়ার জন্য মূল Gradle ডকুমেন্টেশনের লিঙ্ক দেওয়া হয়েছে।

আমরা ধরে নিচ্ছি যে Gradle কীভাবে কাজ করে সেই সম্পর্কে আপনার প্রাথমিক জ্ঞান আছে, যার মধ্যে রয়েছে কীভাবে প্রোজেক্ট কনফিগার করতে হয়, বিল্ড ফাইল এডিট করতে হয়, প্লাগ-ইন প্রয়োগ করতে হয় এবং টাস্ক রান করতে হয়। AGP-এর সাপেক্ষে Gradle-এর প্রাথমিক বিষয়গুলি সম্পর্কে জানতে, আমরা আপনাকে আপনার বিল্ড কনফিগার করুন বিকল্পটি পর্যালোচনা করার সাজেশন দিই। Gradle প্লাগ-ইন কাস্টমাইজ করার সাধারণ ফ্রেমওয়ার্ক সম্পর্কে জানতে, কাস্টম Gradle প্লাগ-ইন ডেভেলপ করা দেখুন।

Gradle লেজি টাইপ শব্দকোষ

Gradle এমন অনেক ধরনের সুবিধা অফার করে যা "লেজি" হিসেবে কাজ করে অথবা ভারী কম্পিউটেশন বা Task বিল্ডের পরবর্তী পর্যায়ে ক্রিয়েশনকে পিছিয়ে দিতে সাহায্য করে। এই ধরনের ক্লাসগুলি অনেক Gradle এবং AGP API-এর মূল ভিত্তি। নিচের তালিকায় লেজি এক্সিকিউশনের সাথে যুক্ত প্রধান Gradle-এর ধরন এবং সেগুলির মূল পদ্ধতি অন্তর্ভুক্ত রয়েছে।

Provider<T>
T ধরনের একটি মান প্রদান করে (যেখানে "T" বলতে যেকোনও ধরন বোঝায়), যা get() ব্যবহার করে এক্সিকিউশন ফেজ চলাকালীন পড়া যায় অথবা map(), flatMap() ও zip() পদ্ধতি ব্যবহার করে নতুন Provider<S>-এ (যেখানে "S" বলতে অন্য কোনও ধরন বোঝায়) রূপান্তরিত করা যায়। মনে রাখবেন যে কনফিগারেশন ফেজ চলাকালীন get() কখনই কল করা উচিত নয়।
  • map(): ল্যাম্বডা গ্রহণ করে এবং S, Provider<S> ধরনের Provider তৈরি করে। map()-এর জন্য ল্যাম্বডা আর্গুমেন্ট T ভ্যালু নেয় এবং S ভ্যালু তৈরি করে। ল্যাম্বডা সাথে সাথে এক্সিকিউট করা হয় না; পরিবর্তে, এর এক্সিকিউশন get()-এ Provider<S>-এর ফলাফল কল করার সময় পর্যন্ত স্থগিত রাখা হয়, যার ফলে পুরো চেইনটি অলস হয়ে যায়।
  • flatMap(): এটিও একটি lambda গ্রহণ করে এবং Provider<S> তৈরি করে, কিন্তু lambda একটি ভ্যালু T নেয় এবং Provider<S> তৈরি করে (সরাসরি ভ্যালু S তৈরি করার পরিবর্তে)। কনফিগারেশনের সময় S নির্ধারণ করা না গেলে এবং আপনি শুধু Provider<S> পেলে flatMap() ব্যবহার করুন। ব্যবহারিক দিক থেকে বলতে গেলে, আপনি যদি map() ব্যবহার করেন এবং Provider<Provider<S>> ফলাফলের ধরন পান, তাহলে সম্ভবত এর পরিবর্তে আপনার flatMap() ব্যবহার করা উচিত ছিল।
  • zip(): দুটি ইনপুট Providers ইনস্ট্যান্সের ভ্যালু একত্রিত করে এমন একটি ফাংশন ব্যবহার করে গণনা করা ভ্যালু সহ একটি নতুন Provider তৈরি করতে আপনাকে দুটি Provider ইনস্ট্যান্স একত্রিত করতে দেয়।
Property<T>
Provider<T> প্রয়োগ করে, তাই এটি T ধরনের ভ্যালুও প্রদান করে। Provider<T>-এর মতো নয়, যেটি শুধু পড়া যায়, আপনি Property<T>-এর জন্য ভ্যালু সেট করতে পারবেন। এটি করার দুটি উপায় আছে:
  • T ধরনের ভ্যালু উপলভ্য হলে সরাসরি সেট করুন, যাতে বিলম্বিত গণনা করার প্রয়োজন না হয়।
  • Property<T>-এর ভ্যালুর সোর্স হিসেবে অন্য একটি Provider<T> সেট করুন। এই ক্ষেত্রে, Property.get() কল করা হলে তবেই T ভ্যালু মেটেরিয়ালাইজ করা হয়।
TaskProvider
Provider<Task> প্রয়োগ করে। TaskProvider জেনারেট করতে, tasks.register() ব্যবহার করুন এবং tasks.create() নয়, যাতে টাস্ক শুধুমাত্র প্রয়োজন হলেই লেজি ইনস্ট্যান্টিয়েশন করা হয়। Task তৈরি হওয়ার আগে আপনি flatMap() ব্যবহার করে Task-এর আউটপুট অ্যাক্সেস করতে পারবেন, যা আপনি আউটপুটকে অন্য Task ইনস্ট্যান্সের ইনপুট হিসেবে ব্যবহার করতে চাইলে উপযোগী হতে পারে।

ইনপুট ও আউটপুট সেট-আপ করার জন্য প্রোভাইডার ও তাদের ট্রান্সফর্মেশন পদ্ধতি অত্যন্ত গুরুত্বপূর্ণ। এর ফলে, সব টাস্ক আগে থেকে তৈরি করা ও ভ্যালু রিজলভ করার প্রয়োজন হয় না।

এছাড়াও, প্রোভাইডার টাস্ক ডিপেন্ডেন্সি সংক্রান্ত তথ্য বহন করে। আপনি যখন Task আউটপুট পরিবর্তন করে Provider তৈরি করেন, তখন সেই Task, Provider-এর একটি অন্তর্নিহিত নির্ভরতা হয়ে যায় এবং যখনই Provider-এর ভ্যালু সমাধান করা হয়, যেমন অন্য কোনও Task-এর যখন এটির প্রয়োজন হয়, তখন এটি তৈরি এবং রান করা হবে।

এখানে দুটি টাস্ক রেজিস্টার করার উদাহরণ দেওয়া হল, GitVersionTask এবং ManifestProducerTask, যেখানে Task ইনস্ট্যান্স তৈরি করার কাজ স্থগিত রাখা হয়েছে যতক্ষণ না সেগুলি সত্যিই প্রয়োজন হয়। ManifestProducerTask ইনপুট ভ্যালু Provider-এ সেট করা আছে যা GitVersionTask-এর আউটপুট থেকে পাওয়া গেছে, তাই ManifestProducerTask অন্তর্নিহিতভাবে GitVersionTask-এর উপর নির্ভর করে।

// Register a task lazily to get its TaskProvider.
val gitVersionProvider: TaskProvider =
    project.tasks.register("gitVersionProvider", GitVersionTask::class.java) {
        it.gitVersionOutputFile.set(
            File(project.buildDir, "intermediates/gitVersionProvider/output")
        )
    }

...

/**
 * Register another task in the configuration block (also executed lazily,
 * only if the task is required).
 */
val manifestProducer =
    project.tasks.register(variant.name + "ManifestProducer", ManifestProducerTask::class.java) {
        /**
         * Connect this task's input (gitInfoFile) to the output of
         * gitVersionProvider.
         */
        it.gitInfoFile.set(gitVersionProvider.flatMap(GitVersionTask::gitVersionOutputFile))
    }

এই দুটি টাস্ক শুধুমাত্র তখনই কার্যকর হবে যদি সেগুলির জন্য স্পষ্টভাবে অনুরোধ করা হয়। এটি Gradle ইনভোকেশনের অংশ হিসেবে হতে পারে, যেমন, আপনি যদি ./gradlew debugManifestProducer চালান অথবা ManifestProducerTask-এর আউটপুট অন্য কোনও টাস্কের সাথে কানেক্ট করা থাকে এবং এর ভ্যালু প্রয়োজন হয়।

আপনি কাস্টম টাস্ক লিখবেন যা ইনপুট ব্যবহার করে এবং/অথবা আউটপুট তৈরি করে, AGP সরাসরি তার নিজস্ব টাস্কে সর্বজনীন অ্যাক্সেস অফার করে না। এগুলি হল প্রয়োগের বিবরণ যা এক ভার্সন থেকে অন্য ভার্সনে পরিবর্তিত হতে পারে। পরিবর্তে, AGP ভেরিয়েন্ট API এবং এর টাস্কের আউটপুটে অ্যাক্সেস অফার করে, অথবা বিল্ড আর্টিফ্যাক্ট, যা আপনি পড়তে এবং ট্রান্সফর্ম করতে পারবেন। আরও তথ্যের জন্য এই ডকুমেন্টে Variant API, আর্টিফ্যাক্ট ও টাস্ক দেখুন।

Gradle বিল্ড ফেজ

প্রোজেক্ট তৈরি করা স্বভাবতই একটি জটিল এবং রিসোর্স-ডিমান্ডিং প্রসেস, এবং টাস্ক কনফিগারেশন এড়ানো, আপ-টু-ডেট চেক এবং কনফিগারেশন ক্যাশিং ফিচারের মতো বিভিন্ন ফিচার রয়েছে যা রিপ্রোডিউসিবল বা অপ্রয়োজনীয় কম্পিউটেশনে ব্যয় করা সময় কমাতে সাহায্য করে।

এইসব অপ্টিমাইজেশনের মধ্যে কিছু প্রয়োগ করতে, Gradle স্ক্রিপ্ট ও প্লাগ-ইনকে অবশ্যই প্রতিটি স্বতন্ত্র Gradle বিল্ড ফেজের সময় কঠোর নিয়ম মেনে চলতে হবে: ইনিশিয়ালাইজেশন, কনফিগারেশন ও এক্সিকিউশন। এই গাইডে, আমরা কনফিগারেশন ও এক্সিকিউশন ফেজের উপর ফোকাস করব। আপনি Gradle বিল্ড লাইফসাইকেল গাইড থেকে সব ফেজ সম্পর্কে আরও তথ্য পেতে পারেন।

কনফিগারেশন ফেজ

কনফিগারেশন ফেজ চলাকালীন, বিল্ডের অংশ হিসেবে সব প্রোজেক্টের বিল্ড স্ক্রিপ্ট মূল্যায়ন করা হয়, প্লাগ-ইন প্রয়োগ করা হয় এবং বিল্ড ডিপেন্ডেন্সি সমাধান করা হয়। এই ফেজটি DSL অবজেক্ট ব্যবহার করে বিল্ড কনফিগার করতে এবং টাস্ক ও তাদের ইনপুট লেজিভাবে রেজিস্টার করার জন্য ব্যবহার করা উচিত।

যেহেতু কনফিগারেশন ফেজ সবসময় চলে, কোন টাস্ক চালানোর জন্য অনুরোধ করা হয়েছে তা নির্বিশেষে, এটি হালকা রাখা এবং বিল্ড স্ক্রিপ্ট ছাড়া অন্য ইনপুটের উপর নির্ভর করে এমন কোনও কম্পিউটেশন সীমাবদ্ধ করা বিশেষভাবে গুরুত্বপূর্ণ। অর্থাৎ, আপনাকে এক্সটার্নাল প্রোগ্রাম এক্সিকিউট করতে বা নেটওয়ার্ক থেকে পড়তে হবে না অথবা এমন দীর্ঘ গণনা করতে হবে না যা সঠিক Task ইনস্ট্যান্স হিসেবে এক্সিকিউশন ফেজে স্থগিত করা যেতে পারে।

এক্সিকিউশন ফেজ

এক্সিকিউশন ফেজে, অনুরোধ করা টাস্ক এবং তার উপর নির্ভরশীল টাস্কগুলি এক্সিকিউট করা হয়। নির্দিষ্টভাবে, @TaskAction দিয়ে চিহ্নিত Task ক্লাস মেথড এক্সিকিউট করা হয়। টাস্ক এক্সিকিউশন চলাকালীন, আপনাকে ইনপুট (যেমন ফাইল) থেকে পড়ার এবং Provider<T>.get() কল করে লেজি প্রোভাইডারদের সমাধান করার অনুমতি দেওয়া হয়। এইভাবে লেজি প্রোভাইডার সমাধান করলে, map() বা flatMap() কলের একটি সিকোয়েন্স শুরু হয় যা প্রোভাইডারের মধ্যে থাকা টাস্ক ডিপেন্ডেন্সি তথ্য অনুসরণ করে। প্রয়োজনীয় ভ্যালু মেটেরিয়ালাইজ করার জন্য টাস্ক অলসভাবে রান করা হয়।

Variant API, আর্টিফ্যাক্ট ও টাস্ক

Variant API হল Android Gradle প্লাগ-ইনের একটি এক্সটেনশন মেকানিজম যা আপনাকে বিভিন্ন বিকল্প ম্যানিপুলেট করতে দেয়, সাধারণত বিল্ড কনফিগারেশন ফাইলে DSL ব্যবহার করে সেট করা হয়, যা Android বিল্ডকে প্রভাবিত করে। এছাড়াও, Variant API আপনাকে বিল্ডের মাধ্যমে তৈরি করা ইন্টারমিডিয়েট ও ফাইনাল আর্টিফ্যাক্ট অ্যাক্সেস করার সুবিধা দেয়, যেমন ক্লাস ফাইল, মার্জ করা ম্যানিফেস্ট বা APK/AAB ফাইল।

Android বিল্ড ফ্লো ও এক্সটেনশন পয়েন্ট

AGP-এর সাথে ইন্টার‍্যাক্ট করার সময়, সাধারণ Gradle লাইফসাইকেল কলব্যাক (যেমন afterEvaluate()) রেজিস্টার করা বা স্পষ্ট Task ডিপেন্ডেন্সি সেট-আপ করার পরিবর্তে বিশেষভাবে তৈরি এক্সটেনশন পয়েন্ট ব্যবহার করুন। AGP-এর মাধ্যমে তৈরি করা টাস্ককে ইমপ্লিমেন্টেশন বিবরণ হিসেবে বিবেচনা করা হয় এবং সর্বজনীন API হিসেবে প্রকাশ করা হয় না। আপনাকে অবশ্যই Task অবজেক্টের ইনস্ট্যান্স পাওয়ার চেষ্টা করা বা Task-এর নাম অনুমান করা এবং সরাসরি সেইসব Task অবজেক্টে কলব্যাক বা ডিপেন্ডেন্সি যোগ করা এড়িয়ে চলতে হবে।

AGP নিম্নলিখিত ধাপগুলি সম্পূর্ণ করে এর Task ইনস্ট্যান্স তৈরি এবং এক্সিকিউট করে, যা ফলস্বরূপ বিল্ড আর্টিফ্যাক্ট তৈরি করে। Variant অবজেক্ট তৈরি করার প্রধান ধাপগুলি কলব্যাক দ্বারা অনুসরণ করা হয় যা আপনাকে বিল্ডের অংশ হিসাবে তৈরি করা নির্দিষ্ট অবজেক্টে পরিবর্তন করতে দেয়। এটি মনে রাখা গুরুত্বপূর্ণ যে সব কলব্যাক কনফিগারেশন ফেজ চলাকালীন হয় (এই পৃষ্ঠায় বর্ণনা করা হয়েছে) এবং দ্রুত রান করতে হবে, পরিবর্তে এক্সিকিউশন ফেজ চলাকালীন যেকোনও জটিল কাজ সঠিক Task ইনস্ট্যান্সে পিছিয়ে দিতে হবে।

  1. DSL পার্সিং: এটি হল সেই সময় যখন বিল্ড স্ক্রিপ্ট মূল্যায়ন করা হয় এবং যখন android ব্লক থেকে Android DSL অবজেক্টের বিভিন্ন প্রপার্টি তৈরি ও সেট করা হয়। এই পর্যায়ে নিম্নলিখিত বিভাগে বর্ণিত ভেরিয়েন্ট API কলব্যাকও রেজিস্টার করা হয়।
  2. finalizeDsl(): কলব্যাক যা আপনাকে DSL অবজেক্ট পরিবর্তন করতে দেয়, কম্পোনেন্ট (ভেরিয়েন্ট) তৈরির জন্য লক করার আগে। DSL অবজেক্টে থাকা ডেটার ভিত্তিতে VariantBuilderটি অবজেক্ট তৈরি করা হয়েছে।

  3. DSL লকিং: DSL এখন লক করা আছে এবং আর কোনও পরিবর্তন করা যাবে না।

  4. beforeVariants(): এই কলব্যাক, VariantBuilder-এর মাধ্যমে, কোন কম্পোনেন্ট তৈরি করা হয়েছে এবং সেগুলির কিছু প্রপার্টিকে প্রভাবিত করতে পারে। এটি এখনও বিল্ড ফ্লো এবং তৈরি করা আর্টিফ্যাক্টগুলিতে পরিবর্তন করার অনুমতি দেয়।

  5. ভ্যারিয়েন্ট তৈরি করা: যে কম্পোনেন্ট ও আর্টিফ্যাক্টের তালিকা তৈরি করা হবে তা এখন চূড়ান্ত করা হয়েছে এবং পরিবর্তন করা যাবে না।

  6. onVariants(): এই কলব্যাকে, আপনি তৈরি করা Variant অবজেক্টে অ্যাক্সেস পাবেন এবং আপনি সেগুলির মধ্যে থাকা Property ভ্যালুর জন্য ভ্যালু বা প্রোভাইডার সেট করতে পারবেন, যা লেজি কম্পিউট করা হবে।

  7. ভ্যারিয়েন্ট লক করা: ভ্যারিয়েন্ট অবজেক্ট এখন লক করা আছে এবং আর কোনও পরিবর্তন করা সম্ভব নয়।

  8. টাস্ক তৈরি করা হয়েছে: Variant অবজেক্ট এবং সেগুলির Property ভ্যালু ব্যবহার করে Task ইনস্ট্যান্স তৈরি করা হয় যা বিল্ড পারফর্ম করার জন্য প্রয়োজন।

AGP একটি AndroidComponentsExtension নিয়ে এসেছে যা আপনাকে finalizeDsl(), beforeVariants() ও onVariants()-এর জন্য কলব্যাক রেজিস্টার করতে দেয়। এক্সটেনশনটি androidComponents ব্লকের মাধ্যমে বিল্ড স্ক্রিপ্টে উপলভ্য:

// This is used only for configuring the Android build through DSL.
android { ... }

// The androidComponents block is separate from the DSL.
androidComponents {
   finalizeDsl { extension ->
      ...
   }
}

তবে, আমাদের সাজেশন হল, android ব্লকের DSL ব্যবহার করে ডিক্লারেটিভ কনফিগারেশনের জন্য বিল্ড স্ক্রিপ্ট রাখুন এবং যেকোনও কাস্টম ইম্পারেটিভ লজিক buildSrc ডিরেক্টরিতে বা এক্সটার্নাল প্লাগ-ইনে সরিয়ে দিন। এছাড়াও, আপনার প্রোজেক্টে কীভাবে প্লাগ-ইন তৈরি করতে হয় তা জানতে, আমাদের Gradle রেসিপি GitHub রিপোজিটরিতে buildSrc স্যাম্পেল দেখতে পারেন। আপনার প্লাগ-ইন বিল্ড ফাইলে gradle-api আর্টিফ্যাক্টের সাথে কম্পাইল করে কিনা তা নিশ্চিত করুন। প্লাগ-ইন কোড থেকে কলব্যাক রেজিস্টার করার একটি উদাহরণ এখানে দেওয়া হল:

abstract class ExamplePlugin: Plugin<Project> {

    override fun apply(project: Project) {
        val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java)
        androidComponents.finalizeDsl { extension ->
            ...
        }
    }
}

আসুন, উপলভ্য কলব্যাক এবং ব্যবহারের ক্ষেত্রে ধরনগুলি আরও ঘনিষ্ঠভাবে দেখে নেওয়া যাক যেগুলির প্রতিটিতে আপনার প্লাগ-ইন কাজ করতে পারে:

finalizeDsl(callback: (DslExtensionT) -> Unit)

এই কলব্যাকে, আপনি DSL অবজেক্ট অ্যাক্সেস ও পরিবর্তন করতে পারবেন যা বিল্ড ফাইলের android ব্লক থেকে তথ্য পার্স করে তৈরি করা হয়েছে। এই DSL অবজেক্টগুলি বিল্ডের পরবর্তী পর্যায়ে ভেরিয়েন্টগুলি শুরু এবং কনফিগার করতে ব্যবহার করা হবে। যেমন, আপনি প্রোগ্রাম্যাটিক্যালি নতুন কনফিগারেশন তৈরি করতে বা প্রপার্টি ওভাররাইড করতে পারেন—তবে মনে রাখবেন যে কনফিগারেশনের সময় সমস্ত ভ্যালু সমাধান করতে হবে, তাই সেগুলি কোনও বাহ্যিক ইনপুটের উপর নির্ভর করা উচিত নয়। এই কলব্যাক এক্সিকিউট করা শেষ হওয়ার পরে, DSL অবজেক্ট আর কার্যকর থাকে না এবং আপনাকে আর সেগুলির রেফারেন্স ধরে রাখতে বা সেগুলির ভ্যালু পরিবর্তন করতে হবে না।

abstract class ExamplePlugin: Plugin<Project> {

    override fun apply(project: Project) {
        val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java)
        androidComponents.finalizeDsl { extension ->
            extension.buildTypes.create("extra").let {
                it.isJniDebuggable = true
            }
        }
    }
}

beforeVariants()

বিল্ডের এই পর্যায়ে, আপনি VariantBuilder অবজেক্টে অ্যাক্সেস পাবেন, যা তৈরি করা হবে এমন ভেরিয়েন্ট এবং তাদের প্রপার্টি নির্ধারণ করে। যেমন, আপনি প্রোগ্রাম্যাটিক্যালি নির্দিষ্ট কিছু ভেরিয়েন্ট, সেগুলির পরীক্ষা বন্ধ করতে পারেন অথবা শুধুমাত্র বেছে নেওয়া ভেরিয়েন্টের জন্য কোনও প্রপার্টির ভ্যালু (যেমন, minSdk) পরিবর্তন করতে পারেন। finalizeDsl()-এর মতো, আপনার দেওয়া সব ভ্যালু কনফিগারেশনের সময় সমাধান করতে হবে এবং এক্সটার্নাল ইনপুটের উপর নির্ভর করলে চলবে না। beforeVariants() কলব্যাক এক্সিকিউশন শেষ হয়ে গেলে VariantBuilder অবজেক্ট অবশ্যই পরিবর্তন করা যাবে না।

androidComponents {
    beforeVariants { variantBuilder ->
        variantBuilder.minSdk = 23
    }
}

beforeVariants() কলব্যাক ঐচ্ছিকভাবে একটি VariantSelector নেয়, যা আপনি androidComponentsExtension-এ selector() পদ্ধতির মাধ্যমে পেতে পারেন। আপনি কলব্যাক ইনভোকেশনে অংশগ্রহণকারী কম্পোনেন্টগুলিকে তাদের নাম, বিল্ড টাইপ বা প্রোডাক্ট ফ্লেভারের উপর ভিত্তি করে ফিল্টার করতে এটি ব্যবহার করতে পারেন।

androidComponents {
    beforeVariants(selector().withName("adfree")) { variantBuilder ->
        variantBuilder.minSdk = 23
    }
}

onVariants()

onVariants() কল করার সময়, AGP-এর মাধ্যমে তৈরি করা সব আর্টিফ্যাক্ট আগে থেকেই ঠিক করা থাকে, তাই আপনি আর সেগুলি বন্ধ করতে পারবেন না। তবে, আপনি Variant অবজেক্টে Property অ্যাট্রিবিউটের জন্য সেট করে, টাস্কের জন্য ব্যবহৃত কিছু ভ্যালু পরিবর্তন করতে পারবেন। কারণ Property ভ্যালু শুধুমাত্র AGP-এর টাস্ক এক্সিকিউট করার সময় রিজলভ করা হবে, আপনি নিরাপদে সেগুলিকে আপনার নিজস্ব কাস্টম টাস্ক থেকে প্রোভাইডারের সাথে কানেক্ট করতে পারবেন যা ফাইল বা নেটওয়ার্কের মতো বাহ্যিক ইনপুট থেকে রিড করা সহ যেকোনও প্রয়োজনীয় কম্পিউটেশন করবে।

// onVariants also supports VariantSelectors:
onVariants(selector().withBuildType("release")) { variant ->
    // Gather the output when we are in single mode (no multi-apk).
    val mainOutput = variant.outputs.single { it.outputType == OutputType.SINGLE }

    // Create version code generating task
    val versionCodeTask = project.tasks.register("computeVersionCodeFor${variant.name}", VersionCodeTask::class.java) {
        it.outputFile.set(project.layout.buildDirectory.file("${variant.name}/versionCode.txt"))
    }
    /**
     * Wire version code from the task output.
     * map() will create a lazy provider that:
     * 1. Runs just before the consumer(s), ensuring that the producer
     * (VersionCodeTask) has run and therefore the file is created.
     * 2. Contains task dependency information so that the consumer(s) run after
     * the producer.
     */
    mainOutput.versionCode.set(versionCodeTask.map { it.outputFile.get().asFile.readText().toInt() })
}

বিল্ডে জেনারেট করা সোর্স কন্ট্রিবিউট করুন

আপনার প্লাগ-ইন কয়েক ধরনের জেনারেট করা সোর্স কন্ট্রিবিউট করতে পারে, যেমন:

আপনি যোগ করতে পারেন এমন সোর্সের সম্পূর্ণ তালিকা দেখতে, Sources API দেখুন।

এই কোড স্নিপেট দেখায় যে কীভাবে addStaticSourceDirectory() ফাংশন ব্যবহার করে Java সোর্স সেটে ${variant.name} নামের একটি কাস্টম সোর্স ফোল্ডার যোগ করতে হয়। Android টুলচেন তারপর এই ফোল্ডার প্রসেস করে।

onVariants { variant ->
    variant.sources.java?.let { java ->
        java.addStaticSourceDirectory("custom/src/kotlin/${variant.name}")
    }
}

আরও বিবরণের জন্য addJavaSource রেসিপি দেখুন।

এই কোড স্নিপেট দেখায় যে কীভাবে কাস্টম টাস্ক থেকে জেনারেট করা Android রিসোর্স সহ ডিরেক্টরি res সোর্স সেটে যোগ করতে হয়। অন্যান্য সোর্স টাইপের জন্য প্রসেসটি একই রকম।

onVariants(selector().withBuildType("release")) { variant ->
    // Step 1. Register the task.
    val resCreationTask =
       project.tasks.register<ResCreatorTask>("create${variant.name}Res")

    // Step 2. Register the task output to the variant-generated source directory.
    variant.sources.res?.addGeneratedSourceDirectory(
       resCreationTask,
       ResCreatorTask::outputDirectory)
    }

...

// Step 3. Define the task.
abstract class ResCreatorTask: DefaultTask() {
   @get:OutputFiles
   abstract val outputDirectory: DirectoryProperty

   @TaskAction
   fun taskAction() {
      // Step 4. Generate your resources.
      ...
   }
}

আরও বিবরণের জন্য addCustomAsset রেসিপি দেখুন।

আর্টিফ্যাক্ট অ্যাক্সেস ও পরিবর্তন করুন

Variant অবজেক্টে সাধারণ প্রপার্টি পরিবর্তন করার সুবিধা দেওয়ার পাশাপাশি, AGP-তে এক্সটেনশন মেকানিজমও আছে যা আপনাকে বিল্ডের সময় তৈরি হওয়া ইন্টারমিডিয়েট ও ফাইনাল আর্টিফ্যাক্ট পড়তে বা ট্রান্সফর্ম করতে দেয়। যেমন, আপনি বিশ্লেষণ করার জন্য কাস্টম Task-এ চূড়ান্ত, মার্জ করা AndroidManifest.xml ফাইলের কন্টেন্ট পড়তে পারেন অথবা আপনার কাস্টম Task-এর মাধ্যমে তৈরি করা ম্যানিফেস্ট ফাইলের কন্টেন্ট দিয়ে এর কন্টেন্ট সম্পূর্ণভাবে পরিবর্তন করতে পারেন।

Artifact ক্লাসের জন্য রেফারেন্স ডকুমেন্টেশনে বর্তমানে কাজ করে এমন আর্টিফ্যাক্টের তালিকা আপনি খুঁজে পেতে পারেন। প্রতিটি আর্টিফ্যাক্টের ধরনের কিছু নির্দিষ্ট প্রপার্টি থাকে যা জানা থাকলে সুবিধা হয়:

কার্ডিনালিটি

Artifact-এর কার্ডিনালিটি, এটির FileSystemLocation ইনস্ট্যান্সের সংখ্যা বা আর্টিফ্যাক্ট ধরনের ফাইল বা ডিরেক্টরির সংখ্যাকে বোঝায়। আপনি কোনও আর্টিফ্যাক্টের পেরেন্ট ক্লাস চেক করে তার কার্ডিনালিটি সম্পর্কে তথ্য পেতে পারেন: একটিমাত্র FileSystemLocation সহ আর্টিফ্যাক্ট Artifact.Single-এর সাবক্লাস হবে; একাধিক FileSystemLocation ইনস্ট্যান্স সহ আর্টিফ্যাক্ট Artifact.Multiple-এর সাবক্লাস হবে।

FileSystemLocation ধরন

Artifact ফাইল বা ডিরেক্টরিকে উপস্থাপন করে কিনা তা আপনি এর প্যারামিটারাইজড FileSystemLocation টাইপ দেখে চেক করতে পারেন, যা RegularFile বা Directory হতে পারে।

যেসব অপারেশন কাজ করে

প্রতিটি Artifact ক্লাস নিম্নলিখিত ইন্টারফেসের যেকোনও একটি প্রয়োগ করতে পারে, যাতে বোঝা যায় কোন অপারেশন এটি সাপোর্ট করে:

  • Transformable: Task-এ ইনপুট হিসেবে Artifact ব্যবহার করার অনুমতি দেয় যা এটির উপর ইচ্ছামতো ট্রান্সফর্মেশন করে এবং Artifact-এর নতুন ভার্সন আউটপুট হিসেবে দেখায়।
  • Appendable: শুধুমাত্র সেইসব আর্টিফ্যাক্টের ক্ষেত্রে প্রযোজ্য যেগুলি Artifact.Multiple-এর সাবক্লাস। এর অর্থ হল, Artifact-এ নতুন আইটেম যোগ করা যেতে পারে, অর্থাৎ, একটি কাস্টম Task এই Artifact ধরনের নতুন ইনস্ট্যান্স তৈরি করতে পারে যা আগে থেকে থাকা তালিকায় যোগ করা হবে।
  • Replaceable: শুধুমাত্র সেইসব আর্টিফ্যাক্টের ক্ষেত্রে প্রযোজ্য যেগুলি Artifact.Single-এর সাবক্লাস। একটি প্রতিস্থাপনযোগ্য Artifact-কে সম্পূর্ণ নতুন ইনস্ট্যান্স দিয়ে প্রতিস্থাপন করা যেতে পারে, যা Task-এর আউটপুট হিসেবে তৈরি হয়।

আর্টিফ্যাক্ট-মডিফাই করার তিনটি অপারেশন ছাড়াও, প্রতিটি আর্টিফ্যাক্ট একটি get() (বা getAll()) অপারেশন সাপোর্ট করে, যা আর্টিফ্যাক্টের ফাইনাল ভার্সন সহ একটি Provider রিটার্ন করে (এটির উপর সব অপারেশন শেষ হওয়ার পরে)।

একাধিক প্লাগ-ইন onVariants() কলব্যাক থেকে পাইপলাইনে আর্টিফ্যাক্টের উপর যেকোনও সংখ্যক অপারেশন যোগ করতে পারে এবং AGP নিশ্চিত করবে যে সেগুলি সঠিকভাবে চেইন করা হয়েছে যাতে সব টাস্ক সঠিক সময়ে রান করে এবং আর্টিফ্যাক্ট সঠিকভাবে তৈরি ও আপডেট হয়। এর অর্থ হল, কোনও অপারেশন যখন কোনও আউটপুট যোগ, প্রতিস্থাপন বা রূপান্তর করে পরিবর্তন করে, তখন পরবর্তী অপারেশন এই আর্টিফ্যাক্টগুলির আপডেট করা ভার্সন ইনপুট হিসেবে দেখবে এবং এইভাবে চলতে থাকবে।

অপারেশন রেজিস্টার করার এন্ট্রি পয়েন্ট হল Artifacts ক্লাস। নিম্নলিখিত কোড স্নিপেট থেকে আপনি জানতে পারবেন যে কীভাবে onVariants() কলব্যাকে Variant অবজেক্টের প্রপার্টি থেকে Artifacts-এর ইনস্ট্যান্স অ্যাক্সেস করতে পারবেন।

তারপর আপনি নিজের কাস্টম TaskProvider পাস করতে পারবেন যাতে একটি TaskBasedOperation অবজেক্ট (১) পাওয়া যায় এবং wiredWith* পদ্ধতির (২) মধ্যে কোনও একটি ব্যবহার করে এর ইনপুট ও আউটপুট কানেক্ট করতে এটি ব্যবহার করতে পারবেন।

আপনি যে Artifact ট্রান্সফর্ম করতে চান, তার দ্বারা প্রয়োগ করা কার্ডিনালিটি ও FileSystemLocation-এর ধরনের উপর নির্ভর করে আপনাকে সঠিক পদ্ধতি বেছে নিতে হবে।

এবং শেষে, আপনি Artifact টাইপকে এমন একটি পদ্ধতিতে পাস করেন যা রিটার্নে পাওয়া *OperationRequest অবজেক্টে বেছে নেওয়া অপারেশনকে দেখায়, যেমন, toAppendTo(), toTransform() , অথবা toCreate() (3).

androidComponents.onVariants { variant ->
    val manifestUpdater = // Custom task that will be used for the transform.
            project.tasks.register(variant.name + "ManifestUpdater", ManifestTransformerTask::class.java) {
                it.gitInfoFile.set(gitVersionProvider.flatMap(GitVersionTask::gitVersionOutputFile))
            }
    // (1) Register the TaskProvider w.
    val variant.artifacts.use(manifestUpdater)
         // (2) Connect the input and output files.
        .wiredWithFiles(
            ManifestTransformerTask::mergedManifest,
            ManifestTransformerTask::updatedManifest)
        // (3) Indicate the artifact and operation type.
        .toTransform(SingleArtifact.MERGED_MANIFEST)
}

এই উদাহরণে, MERGED_MANIFEST হল একটি SingleArtifact এবং এটি হল একটি RegularFile। সেই কারণে, আমাদের wiredWithFiles পদ্ধতি ব্যবহার করতে হবে, যা ইনপুটের জন্য একটি RegularFileProperty রেফারেন্স এবং আউটপুটের জন্য একটি RegularFileProperty গ্রহণ করে। TaskBasedOperation ক্লাসে অন্যান্য wiredWith* পদ্ধতি আছে যা Artifact কার্ডিনালিটি এবং FileSystemLocation ধরনের অন্যান্য কম্বিনেশনের জন্য কাজ করবে।

AGP এক্সটেন্ড করার ব্যাপারে আরও জানতে, Gradle বিল্ড সিস্টেম ম্যানুয়াল থেকে নিম্নলিখিত বিভাগগুলি পড়ার সাজেশন আমরা দিই: