মোবাইল অ্যাপের জন্য CI/CD
এই পাঠে যা শিখবেন
- মোবাইল CI/CD-এর স্টেজ ক্রম এবং ওয়েব CI/CD (FSWF L53) থেকে এর নির্দিষ্ট পার্থক্য — দ্বৈত আর্টিফ্যাক্ট
- early-stop-on-failure আচরণ একটি সত্যিকারের পাইপলাইন-রানারে বাস্তবায়ন করা
- call-count যাচাইয়ের মাধ্যমে প্রমাণ করা skip হওয়া স্টেজ সত্যিই একবারও চলে না
- সব স্টেজ সফল হওয়া এবং একটি স্টেজে ইনজেক্টেড ব্যর্থতা — দুটো পরিস্থিতির প্রকৃত ফলাফলের পার্থক্য পড়া
১ · মোবাইল CI/CD স্টেজ ক্রম
FSWF কোর্সের L53-এ
শেখানো হয়েছে কেন CI/CD পাইপলাইনের স্টেজগুলো একটি নির্দিষ্ট ক্রমে চলে এবং একটি ব্যর্থতায় বাকি স্টেজ কেন
সম্পূর্ণ এড়িয়ে যাওয়া উচিত — সেই সাধারণ নীতিটি এখানেও অপরিবর্তিত। মোবাইলের নির্দিষ্ট পার্থক্যটা
build স্টেজে — ওয়েবে সাধারণত একটি একক ডিপ্লয়যোগ্য আর্টিফ্যাক্ট (একটি কন্টেইনার ইমেজ বা বান্ডেল)
তৈরি হয়, কিন্তু মোবাইলে একই কোডবেস থেকে দুটো সম্পূর্ণ আলাদা প্ল্যাটফর্ম-নির্দিষ্ট আর্টিফ্যাক্ট
তৈরি করতে হয় — iOS-এর জন্য একটি .ipa ফাইল ও Android-এর জন্য একটি .apk ফাইল,
যাদের সাইনিং সার্টিফিকেট ও বিল্ড টুলচেইনও আলাদা।
unit_test ব্যর্থ হলে build_ipa_apk ও beta_distribute সম্পূর্ণ skip হয়ে যায় -- অর্থাৎ কোনো iOS/Android আর্টিফ্যাক্টই তৈরি হয় না, এবং টেস্টারদের কাছে কিছুই বিতরণ করা হয় না।কোড স্টাইল ও সাধারণ ভুল দ্রুত ধরে — সবচেয়ে সস্তা চেক, প্রথমে চলে।
দ্রুত ইউনিট টেস্ট স্যুট চালিয়ে বিজনেস লজিক আচরণগতভাবে সঠিক কি না যাচাই করে (L50/L51-এর ধারাবাহিকতা)।
একই কোডবেস থেকে দুটো আলাদা সাইনড আর্টিফ্যাক্ট তৈরি করে — iOS
.ipa ও Android .apk।বৈধ আর্টিফ্যাক্ট দুটো বেটা-টেস্টারদের কাছে পাঠায় (যেমন TestFlight/Play বেটা ট্র্যাক) — শেষ স্টেজ, সব আগের স্টেজ সফল হলেই এখানে পৌঁছায়।
২ · একটি সত্যিকারের পাইপলাইন রানার — early-stop ও call-count যাচাই
নিচের কোড সেলে প্রতিটি স্টেজ একটি real ফাংশন — কল হলে নিজের call_counts কাউন্টার বাড়ায়, এবং
একটি ইনজেক্ট করা fail_stages সেটে নিজের নাম থাকলে ব্যর্থ হয়। build_ipa_apk সফল
হলে এটি সত্যিই artifacts ডিকশনারিতে দুটো আর্টিফ্যাক্ট-নাম লিখে দেয় — যদি এই স্টেজ কখনো না চলে,
সেই ডিকশনারিও খালি থেকে যাবে, যা আমরা সরাসরি যাচাই করব।
STAGE_ORDER = ["lint", "unit_test", "build_ipa_apk", "beta_distribute"]
def make_stage(stage_name, fail_stages, call_counts, artifacts):
"""একটি স্টেজ ফাংশন তৈরি করে দেয়। কল হলে call_counts[stage_name] বাড়ায় --
তাই এটি কতবার কল হয়েছে তা পরে যাচাইযোগ্য। build_ipa_apk সফল হলে
সত্যিই দুটো প্ল্যাটফর্ম-নির্দিষ্ট আর্টিফ্যাক্ট artifacts-এ লিখে দেয়।"""
def stage_fn():
call_counts[stage_name] += 1
succeeded = stage_name not in fail_stages
if stage_name == "build_ipa_apk" and succeeded:
artifacts["ios"] = "app-release.ipa"
artifacts["android"] = "app-release.apk"
return succeeded
return stage_fn
def run_pipeline(fail_stages):
"""STAGE_ORDER অনুযায়ী প্রতিটি স্টেজ ক্রমানুসারে চালায়। কোনো স্টেজ ব্যর্থ হলে
সাথে সাথে থেমে যায় -- পরের স্টেজগুলোর ফাংশন একবারও কল করা হয় না।
রিটার্ন করে: (results, call_counts, failed_at, artifacts)।"""
call_counts = {name: 0 for name in STAGE_ORDER}
artifacts = {}
stages = {name: make_stage(name, fail_stages, call_counts, artifacts) for name in STAGE_ORDER}
results = {}
failed_at = None
for name in STAGE_ORDER:
if failed_at is not None:
results[name] = "SKIPPED"
continue # এই স্টেজের ফাংশন কখনো কল করা হচ্ছে না
succeeded = stages[name]()
results[name] = "SUCCESS" if succeeded else "FAILED"
if not succeeded:
failed_at = name
return results, call_counts, failed_at, artifacts
def print_report(label, fail_stages):
results, call_counts, failed_at, artifacts = run_pipeline(fail_stages)
print(f"=== {label} ===")
for name in STAGE_ORDER:
print(f" {name:16s} -> {results[name]:8s} (call_count={call_counts[name]})")
print(f" আর্টিফ্যাক্ট: iOS={artifacts.get('ios', '--')}, Android={artifacts.get('android', '--')}")
if failed_at is None:
print(" ফলাফল: পাইপলাইন সবুজ -- সব স্টেজ সফল, বেটা টেস্টারদের কাছে বিতরণ সম্পন্ন\n")
else:
print(f" ফলাফল: '{failed_at}' স্টেজে ব্যর্থতার কারণে থেমে গেছে -- পরের স্টেজগুলো কখনো চালানো হয়নি\n")
return call_counts, artifacts
# রান ১ -- সব স্টেজ পাস
print_report("রান ১ -- সব স্টেজ পাস", fail_stages=set())
# রান ২ -- 'unit_test' স্টেজে ইচ্ছাকৃতভাবে ব্যর্থতা ইনজেক্ট করা হলো
call_counts_run2, artifacts_run2 = print_report(
"রান ২ -- 'unit_test' স্টেজে ইনজেক্টেড ব্যর্থতা", fail_stages={"unit_test"}
)
# --- যাচাই: skip করা build_ipa_apk ও beta_distribute সত্যিই কখনো কল হয়নি ---
assert call_counts_run2["build_ipa_apk"] == 0, "build_ipa_apk স্টেজ ভুলবশত কল হয়ে গেছে!"
assert call_counts_run2["beta_distribute"] == 0, "beta_distribute স্টেজ ভুলবশত কল হয়ে গেছে!"
assert artifacts_run2 == {}, "কোনো আর্টিফ্যাক্ট তৈরি হওয়ার কথা ছিল না, কিন্তু artifacts dict খালি নয়!"
print(
f"যাচাই সফল: রান ২-এ build_ipa_apk এর call_count={call_counts_run2['build_ipa_apk']}, "
f"beta_distribute এর call_count={call_counts_run2['beta_distribute']} -- অর্থাৎ এই দুটো স্টেজের "
f"ফাংশন সত্যিই একবারও চালানো হয়নি, এবং কোনো iOS/Android আর্টিফ্যাক্টও তৈরি হয়নি।"
)
fail_stages খালি, তাই প্রতিটি স্টেজ কল হয়ে সফল হয় — build_ipa_apk চলার
সময় সত্যিই artifacts["ios"] ও artifacts["android"] সেট হয়ে যায়, এবং শেষ রিপোর্টে
দুটো আর্টিফ্যাক্টের নামই দেখা যায়। রান ২-এ unit_test কল হওয়ার সময় (তার call_count
১-এ যায়) এটি False রিটার্ন করে, সাথে সাথে failed_at = "unit_test" সেট হয়ে যায় —
লুপের পরের ধাপগুলোতে (build_ipa_apk, beta_distribute) if failed_at is not
None শর্ত সত্যি হওয়ায় সেই স্টেজের ফাংশন একবারও কল করা হয় না, তাই
artifacts-এ লেখার কোড লাইনটিও কখনো চলার সুযোগ পায় না — artifacts_run2 খালি dict
থেকে যায়, যা তৃতীয় assert-টি নিশ্চিত করে।
মোবাইল CI/CD-এর গঠনগত নীতি ওয়েবের মতোই — early-stop-on-failure — কিন্তু build স্টেজে দুটো
আলাদা প্ল্যাটফর্ম-নির্দিষ্ট আর্টিফ্যাক্ট (iOS .ipa, Android .apk) তৈরি করাটাই
মোবাইলের নির্দিষ্ট সংযোজন। উপরের কোডে দেখানো হয়েছে যে early-stop নিছক প্রিন্ট করা বার্তা নয় — skip হওয়া
স্টেজের ফাংশন সত্যিই কল হয় না, এবং সেই স্টেজ যা তৈরি করার কথা ছিল (এখানে: দুটো আর্টিফ্যাক্ট) তাও কখনো
তৈরি হয় না — real call-count ও real dict state দিয়ে যাচাইযোগ্য।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১
যদি build_ipa_apk-এর ভেতরের কোড ভুলবশত iOS আর্টিফ্যাক্ট তৈরির আগেই
artifacts["android"] সেট করে দিত এবং তারপর ব্যর্থ হতো, তাহলে বাস্তবে কী ঝুঁকি হতে পারত?
beta_distribute স্টেজ (যদি চলার সুযোগ পেত) হয়তো একটি অসম্পূর্ণ Android আর্টিফ্যাক্ট বেটা
টেস্টারদের কাছে পাঠিয়ে দিত, যখন iOS সংস্করণ কখনো তৈরিই হয়নি — একটি অসামঞ্জস্যপূর্ণ রিলিজ। এই কোর্সের
পাইপলাইনে যেহেতু early-stop beta_distribute-কে সম্পূর্ণ skip করে দেয়, এই ঝুঁকিটা এড়ানো
যায় — কিন্তু এটি মনে করিয়ে দেয় যে একটি স্টেজের ভেতরের আংশিক-সফল কাজ পরিষ্কারভাবে হ্যান্ডল করাও গুরুত্বপূর্ণ।
প্র ০২
যদি fail_stages = {"lint", "beta_distribute"} দিয়ে পাইপলাইন চালানো হতো, তাহলে
beta_distribute-এর call_count কত হতো, আর কেন?
0 হতো — কারণ lint নিজেই প্রথম স্টেজ, আর "lint" in fail_stages
সত্যি হওয়ায় এটি প্রথম কলেই ব্যর্থ হবে (call_count["lint"] = 1) এবং failed_at =
"lint" সেট হয়ে যাবে। এরপর unit_test, build_ipa_apk,
beta_distribute — সবগুলো "SKIPPED" হবে এবং কখনো কল হবে না — যদিও
beta_distribute নিজে fail_stages-এ ছিল, তার ফাংশন কল হওয়ারই সুযোগ পায়নি।
প্র ০৩
কেন build_ipa_apk স্টেজে iOS ও Android আর্টিফ্যাক্ট একই ফাংশনে একসাথে তৈরি করা হচ্ছে,
আলাদা build_ios/build_android স্টেজ না রেখে?
বাস্তব CI সিস্টেমে দুটো পদ্ধতিই দেখা যায় — এই পাঠে সরলতার জন্য একটি স্টেজে একত্রিত করা হয়েছে, কিন্তু বড় টিমে প্রায়ই দুটো প্ল্যাটফর্মের বিল্ড আলাদা করা হয় যাতে তারা সমান্তরালভাবে চলতে পারে (একটি iOS রানার ও একটি Android রানার একই সাথে কাজ করে, সিরিয়ালি নয়) — এটি মোট পাইপলাইন সময় কমায়। তবে মূল নীতি অপরিবর্তিত থাকে: আগের সব স্টেজ সফল না হলে কোনো বিল্ড স্টেজই শুরু হবে না।
অনুশীলন
-
চিন্তা করুন: কেন
lintসাধারণত পাইপলাইনের প্রথম স্টেজ, আরbeta_distributeসবসময় শেষ স্টেজ হয় বলে মনে হয়?lintসবচেয়ে সস্তা ও দ্রুততম চেক — কোনো কম্পাইল/বিল্ড ছাড়াই কোড স্ক্যান করা যায়, তাই এটি প্রথমে চালালে ব্যর্থতা দ্রুততম সময়ে ও কম খরচে ধরা পড়ে।beta_distributeসবচেয়ে ঝুঁকিপূর্ণ ও অপরিবর্তনীয় কাজ — একবার টেস্টারদের কাছে একটি বিল্ড পাঠানো হলে সেটি প্রত্যাহার করা কঠিন, তাই এটি তখনই চলা উচিত যখন lint, unit_test, ও build_ipa_apk — সবকটি স্টেজ নিশ্চিত করেছে বিল্ডটি বৈধ। -
পরীক্ষা করুন: উপরের কোড সেলে
fail_stages={"build_ipa_apk"}দিয়েprint_report(...)একটি নতুন কল যোগ করুন এবং দেখুনartifactsডিকশনারি খালি থাকে কি না, যদিওbuild_ipa_apkস্টেজ নিজেই একবার কল হয়েছে।হ্যাঁ,
artifactsখালি থাকবে — কারণmake_stage()-এর ভেতরif stage_name == "build_ipa_apk" and succeeded:শর্তটি সত্যি হয় শুধুমাত্র যখনsucceededসত্যি (অর্থাৎ স্টেজ ব্যর্থ না হলে)। যেহেতু এই কলে"build_ipa_apk"নিজেইfail_stages-এ আছে,succeeded = Falseহবে, তাই আর্টিফ্যাক্ট লেখার লাইনটি কখনো চলবে না — স্টেজটি কল হওয়া (call_count=1) আর স্টেজটির কাজ সম্পূর্ণ হওয়া দুটো ভিন্ন জিনিস, এটাই এই উদাহরণ দেখায়।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৭টি পাঠ অ্যাপ লাইফসাইকেল, মোবাইল UI/UX, MVVM/MVI আর্কিটেকচার, নেটিভ বনাম ক্রস-প্ল্যাটফর্ম, নেভিগেশন, স্টেট ম্যানেজমেন্ট, লোকাল স্টোরেজ, ডিভাইস ফিচার ও ডিপ্লয়মেন্ট — বাকি পাঠগুলো শীঘ্রই যুক্ত হবে।
- FSWF · CI/CD Pipelines for Full-Stack Projects সহোদর পাঠ CI/CD পাইপলাইনের সাধারণ ধারণা ও ওয়েব-প্রেক্ষাপটের বিস্তারিত এই পাঠেই তৈরি হয়েছে।
- সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, DBMS, Discrete Mathematics, System Design, Cybersecurity, Cloud Computing & DevOps, Computer Networks, Operating Systems, Computer Architecture, Programming Languages & Compiler Design, Software Engineering & Git, Theory of Computation, Engineering Economics, Full-Stack Web Frameworks ও Mobile App Development — সব এক জায়গায়।