Gradle প্লাগইন লিখুন

অ্যান্ড্রয়েড গ্রেডল প্লাগইন (AGP) হলো অ্যান্ড্রয়েড অ্যাপ্লিকেশনগুলির জন্য অফিসিয়াল বিল্ড সিস্টেম। এটি বিভিন্ন ধরণের সোর্স কম্পাইল করার এবং সেগুলিকে একত্রিত করে একটি অ্যাপ্লিকেশন তৈরি করার সুবিধা প্রদান করে, যা আপনি একটি বাস্তব অ্যান্ড্রয়েড ডিভাইস বা এমুলেটরে চালাতে পারেন।

AGP-তে প্লাগইনের জন্য এক্সটেনশন পয়েন্ট রয়েছে, যা বিল্ড ইনপুট নিয়ন্ত্রণ করে এবং স্ট্যান্ডার্ড বিল্ড টাস্কের সাথে একীভূত করা যায় এমন নতুন ধাপের মাধ্যমে এর কার্যকারিতা প্রসারিত করে। AGP-এর পূর্ববর্তী সংস্করণগুলিতে অভ্যন্তরীণ বাস্তবায়ন থেকে স্পষ্টভাবে পৃথক কোনো অফিসিয়াল API ছিল না। সংস্করণ ৭.০ থেকে শুরু করে, AGP-তে একগুচ্ছ অফিসিয়াল ও স্থিতিশীল API রয়েছে, যার উপর আপনি নির্ভর করতে পারেন।

এজিপি এপিআই জীবনচক্র

AGP তার API-গুলোর অবস্থা নির্ধারণ করতে Gradle ফিচার লাইফসাইকেল অনুসরণ করে:

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

অবচয় নীতি

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

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

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

গ্রেডল বিল্ডের মৌলিক বিষয়

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

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

গ্রেডল লেজি টাইপস শব্দকোষ

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

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

টাস্কের ইনপুট ও আউটপুট একটি ‘লেজি’ পদ্ধতিতে সেট আপ করার জন্য প্রোভাইডার এবং তাদের রূপান্তর পদ্ধতিগুলো অপরিহার্য; অর্থাৎ, আগে থেকে সমস্ত টাস্ক তৈরি করা এবং মানগুলো সমাধান করার প্রয়োজন ছাড়াই এটি করা যায়।

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

এখানে GitVersionTask এবং ManifestProducerTask দুটি টাস্ক রেজিস্টার করার একটি উদাহরণ দেওয়া হলো, যেখানে Task ইনস্ট্যান্সগুলোর তৈরি হওয়াকে ঠিক প্রয়োজন না হওয়া পর্যন্ত স্থগিত রাখা হয়েছে। ManifestProducerTask এর ইনপুট ভ্যালুটি GitVersionTask এর আউটপুট থেকে প্রাপ্ত একটি Provider এ সেট করা হয়, তাই 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))
    }

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

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

গ্রেডল বিল্ড পর্যায়

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

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

কনফিগারেশন পর্যায়

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

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

বাস্তবায়ন পর্যায়

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

ভ্যারিয়েন্ট এপিআই, আর্টিফ্যাক্টস এবং টাস্ক

ভ্যারিয়েন্ট এপিআই হলো অ্যান্ড্রয়েড গ্রেডল প্লাগইনের একটি এক্সটেনশন মেকানিজম, যা আপনাকে অ্যান্ড্রয়েড বিল্ডকে প্রভাবিত করে এমন বিভিন্ন অপশন ম্যানিপুলেট করার সুযোগ দেয়। এই অপশনগুলো সাধারণত বিল্ড কনফিগারেশন ফাইলে ডিএসএল (DSL) ব্যবহার করে সেট করা হয়। ভ্যারিয়েন্ট এপিআই আপনাকে বিল্ড দ্বারা তৈরি হওয়া অন্তর্বর্তী এবং চূড়ান্ত আর্টিফ্যাক্ট, যেমন ক্লাস ফাইল, মার্জড ম্যানিফেস্ট বা APK/AAB ফাইলগুলোতেও অ্যাক্সেস দেয়।

অ্যান্ড্রয়েড বিল্ড ফ্লো এবং এক্সটেনশন পয়েন্ট

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

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

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

  3. ডিএসএল লকিং : ডিএসএল এখন লক করা হয়েছে এবং এতে আর কোনো পরিবর্তন করা সম্ভব নয়।

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

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

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

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

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

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 ->
      ...
   }
}

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

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)

এই কলব্যাকে, আপনি বিল্ড ফাইলের android ব্লক থেকে তথ্য পার্স করে তৈরি করা ডিএসএল (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() ফাংশন ব্যবহার করে জাভা সোর্স সেটে ${variant.name} নামের একটি কাস্টম সোর্স ফোল্ডার যোগ করতে হয়। এরপর অ্যান্ড্রয়েড টুলচেইন এই ফোল্ডারটিকে প্রসেস করে।

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

আরও বিস্তারিত জানতে addJavaSource প্রণালীটি দেখুন।

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

আর্টিফ্যাক্ট পরিবর্তনকারী তিনটি অপারেশন ছাড়াও, প্রতিটি আর্টিফ্যাক্ট একটি get() (বা getAll() ) অপারেশন সমর্থন করে, যা আর্টিফ্যাক্টটির চূড়ান্ত সংস্করণসহ একটি Provider ফেরত দেয় (এর উপর সমস্ত অপারেশন সম্পন্ন হওয়ার পর)।

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

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

তারপরে আপনি একটি TaskBasedOperation অবজেক্ট (1) পেতে আপনার কাস্টম TaskProvider পাস করতে পারেন, এবং wiredWith* পদ্ধতিগুলির (2) একটি ব্যবহার করে এর ইনপুট এবং আউটপুট সংযোগ করতে এটি ব্যবহার করতে পারেন।

আপনাকে ঠিক কোন পদ্ধতিটি বেছে নিতে হবে, তা নির্ভর করে আপনি যে Artifact রূপান্তর করতে চান তার কার্ডিনালিটি এবং FileSystemLocation টাইপের উপর।

এবং অবশেষে, আপনি ফেরত পাওয়া *OperationRequest অবজেক্টের উপর নির্বাচিত অপারেশনটির প্রতিনিধিত্বকারী একটি মেথডে Artifact টাইপটি পাস করেন, উদাহরণস্বরূপ, 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 বিল্ড সিস্টেম ম্যানুয়ালের নিম্নলিখিত বিভাগগুলি পড়ার পরামর্শ দিই: