وقت القراءة : 12 minutes

قاعدة الكود الخاصة بك تنمو. الاعتمادات بين الوحدات تتكاثر. تصبح وثائق الهندسة قديمة قبل حتى كتابتها. وماذا لو كان بإمكان بناء Gradle الخاص بك إنشاء مخططات محدثة تلقائيًا من هيكل الكود الفعلي؟ هذا بالضبط ما يفعله خط أنابيب Graphify + PlantUML Gradle Plugin :`graphify . --no-viz`يستخرج Knowledge Graph,`./gradlew generateKnowledgeGraphDiagram`يحوّله إلى رسوم بيانية PlantUML. صفر LLM, صفر دليل, 100% حتمي.

المشكلة: المخططات دائماً متأخرة عن الكود

كل مشروع يتجاوز بضعة آلاف من الأسطر يعرف هذا المتلازمة :

  1. نرسم مخططًا معماريًا في بداية المشروع

  2. يتطور الكود، وتتغير التبعيات

  3. المخطط يصبح كذبة زخرفية

  4. لا أحد يحدّثه لأنه مزعج

  5. الوافدون الجدد يعتمدون عليه و يرتكبون أخطاء

@startuml
skinparam backgroundColor #FEFEFE
skinparam ActivityBackgroundColor #FDEDEC
skinparam ActivityDiamondBackgroundColor #FFF3CD

start

:مخطط معماري تم إنشاؤه\n(يدويًا، باليد);

:يتطور الكود\n(فئات جديدة، تبعيات جديدة);

:مخطط قديم\n(لا يعكس بعد الآن الكود);

if (Qui met à jour ?) then (Personne)
    :وثائق مضللة;
    note right: المطورون الجدد \n يستندون إليه... ويقعون بأخطاء
else (Quelqu'un)
    :التحديث اليدوي\n(2-3 ساعات عمل);
    note right: إنها قديمة بالفعل\nقبل الطلب التالي PR
endif

stop

@enduml

السؤال ليس هل يجب أن تكون هناك مخططات؟ — الجميع يعرف أن الجواب نعم. السؤال هو :من يُحدثهم؟

الجواب: لا أحد. إلا إذا كان تلقائيًا.

الحل : خط أنابيب حتمي Knowledge Graph → PlantUML

المبدأ بسيط: بدلاً من رسم المخططات يدويًا، نقوم بهايولّد من البنية الفعلية للرمز.

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

actor Développeur
component "Graphify\n(تنصيب graphifyy باستخدام pip)" as Graphify
collections "graphify-out/graph.json\n(المخطط المعرفي)" as KGJSON
component "PlantUML Gradle Plugin\n(generateKnowledgeGraphDiagram)" as Plugin
component "KnowledgeGraphParser" as Parser
component "KnowledgeGraphRenderer" as Renderer
component "PlantumlService\n(التحقق + العرض PNG)" as PS
collections "الرسوم البيانية/knowledge-graph/\n(.puml + .png)" as Output

Développeur --> Graphify : graphify . --no-viz
Graphify --> KGJSON : استخراج البنية\nمن الكود المصدر
Développeur --> Plugin : ./gradlew generateKnowledgeGraphDiagram
Plugin --> Parser : parse(graph.json)
Parser --> Plugin : KnowledgeGraph\n(عقد + حواف + مجتمعات)
Plugin --> Renderer : render(graph, filters)
Renderer --> Plugin : كود PlantUML\nمحدد
Plugin --> PS : validateSyntax + generateImage
PS --> Output : .puml + .png

note bottom of Plugin
خط أنابيب DÉTERMINISTE
لا يوجد استدعاء LLM
نتيجة قابلة للتكرار
end note

@enduml

أمران. هذا كل شيء.

# Étape 1 : extraire le Knowledge Graph
graphify . --no-viz

# Étape 2 : générer les diagrammes PlantUML
./gradlew generateKnowledgeGraphDiagram

النتيجة؟ ملفات`.puml` et .png`في`diagrams/knowledge-graph/, مُدارة إصداراته في Git، دائمًا محدث مع الكود.

الخطوة 1: تثبيت Graphify واستخراج Knowledge Graph

التثبيت

Graphify هو أداة بايثون تحلل قاعدة الكود الخاصة بك وتبني رسمًا بيانيًا للمعرفة منظمًا:

# Méthode recommandée
uv tool install graphifyy && graphify install --platform opencode

# Alternative avec pip
pip install graphifyy && graphify install --platform opencode

ضبط الاستثناءات

إنشاء ملف`.graphifyignore`في جذر المشروع لاستبعاد الملفات التي لا تشكل جزءًا من منطق العمل :

# Secrets — JAMAIS dans le graphe
*-context.yml
*.env

# Fichiers générés
build/
.gradle/

# Tests fonctionnels
src/functionalTest/

استخراج رسم المعرفة

graphify . --no-viz

العلم`--no-viz`يتخطى إنشاء HTML (غير مفيد في خط إنتاج Gradle). النتيجة هي ملف`graphify-out/graph.json`يحتوي :

  • عقد: الفئات، والوظائف، والملفات — مع نوعها ومجتمعها

  • حافاتالعلاقات بين العقد (EXTRACTED من الكود, INFERRED بواسطة LLM)

  • مجتمعات: التجمعات التلقائية للعقد المرتبطة

مثال على الهيكل`graph.json`
{
  "nodes": [
    {"id": "0", "label": "LlmService", "file_type": "code", "community": 0},
    {"id": "1", "label": "ApiKeyPool", "file_type": "code", "community": 0},
    {"id": "2", "label": "PlantumlService", "file_type": "code", "community": 1}
  ],
  "links": [
    {"source": "1", "target": "0", "relation": "uses", "confidence": "EXTRACTED", "weight": 0.9},
    {"source": "0", "target": "2", "relation": "calls", "confidence": "INFERRED", "weight": 0.7}
  ]
}

شفرة المصدر(.kt, .java) يُحلل محليًا بواسطة tree-sitter,بدون استئناف LLM. فقط ملفات التوثيق (.adoc, .md) يحتاجون إلى استدعاء LLM للاستخراج الدلالي. لذلك`--update`على كود Kotlin يكون فوريًا تقريبًا.

الخطوة 2 : يحول المكون الإضافي Gradle رسم المعرفة إلى PlantUML

بنية الخط المتسلسل

الإضافة`com.cheroliv.plantuml`يضم مهمة`generateKnowledgeGraphDiagram`الذي يحول`graph.json`في مخططات PlantUML بطريقةتمامًا حتمي:

@startuml
skinparam backgroundColor #FEFEFE

participant "Gradle" as G
participant "GenerateKnowledgeGraphDiagramTask" as Task
participant "KnowledgeGraphParser" as Parser
participant "KnowledgeGraphRenderer" as Renderer
participant "PlantumlService" as PS

collections "graphify-out/graph.json" as JSON

G -> Task : نفّذ
Task -> JSON : قراءة
Task -> Parser : parse(graph.json)
Parser --> Task : KnowledgeGraph\n(العقد + الحواف + المجتمعات)
Task -> Renderer : render(graph, filters)
Renderer --> Task : code PlantUML\n(محدد)
Task -> PS : validateSyntax(plantumlCode)
alt syntaxe valide
    Task -> PS : generateImage → .png
else syntaxe invalide
    Task -> Task : احفظ .puml مع تحذير
end

@enduml

المكونات الداخلية

مكوّن دور

KnowledgeGraphParser

(No text provided to translate.)graph.json— يدعم 3 صيغ : جرافيف أصلي (nodes+links), الإرث (communities)، مسطّح. يحلّ المعرفات الرقمية إلى التسميات.

KnowledgeGraphRenderer

يُحوِّل بشكل حتمي واحد`KnowledgeGraph`في كود PlantUML. مجموعات حسب النوع، المجتمعات في الحزم، أسطورة تلقائية.

GenerateKnowledgeGraphDiagramTask

مهمة Gradle التي تنظّم: التحليل → العرض → التحقق → PNG. قابلة للتكوين عبر خصائص Gradle.

kgmodels.kt

نماذج البيانات :`KnowledgeGraph`, KnowledgeGraphNode, KnowledgeGraphEdge, KnowledgeGraphCommunity, EdgeType.

PlantumlService

التحقق من الصياغة + عرض PNG (يُعاد استخدامه من قبل جميع مهام البرنامج المساعد).

قواعد العرض

يُطبِّق المُحرِّك قواعد بصرية محدَّدة :

نوع الحافة التدوين PlantUML المعنى

مستخرج

-→(خط متصل, أسود)

العلاقة المستخلصة من كود المصدر (اليقين)

مستنتج

`..>`خط منقط

العلاقة المستنتجة من قبل LLM (درجة الثقة)

غامض

--x(خط أحمر منقط)

علاقة غامضة (للتحقق)

يتم عرض المجتمعات كحزم PlantUML مع لوحة ألوان تلقائية.

الخطوة 3: الاستخدام اليومي

مخطط كامل

# Générer le diagramme du Knowledge Graph complet
./gradlew generateKnowledgeGraphDiagram

المخرج :`diagrams/knowledge-graph/knowledge-graph-full.puml`+.png

تصفية حسب المجتمع

# Une seule communauté
./gradlew generateKnowledgeGraphDiagram \
  -Pplantuml.kg.community=community_0

# Limiter le nombre de noeuds (lisibilité)
./gradlew generateKnowledgeGraphDiagram \
  -Pplantuml.kg.community=community_0 \
  -Pplantuml.kg.maxNodes=15

تصفية حسب نوع الحافة

# Uniquement les relations certaines (EXTRACTED)
./gradlew generateKnowledgeGraphDiagram \
  -Pplantuml.kg.edgeTypes=EXTRACTED

# Relations certaines + inférées
./gradlew generateKnowledgeGraphDiagram \
  -Pplantuml.kg.edgeTypes=EXTRACTED,INFERRED

فلتر حسب نوع العقدة والثقة

# Uniquement les classes de code
./gradlew generateKnowledgeGraphDiagram \
  -Pplantuml.kg.nodeTypes=code

# Seuil de confiance minimum (pour les INFERRED)
./gradlew generateKnowledgeGraphDiagram \
  -Pplantuml.kg.minConfidence=0.7

مجلد الإخراج المخصص

./gradlew generateKnowledgeGraphDiagram \
  -Pplantuml.kg.outputDir=docs/architecture

المرجع الكامل للخصائص

ملكية الافتراضي الوصف

plantuml.kg.community

(جميع)

تصفية المجتمعات حسب الاسم (مطابقة جزئية)

plantuml.kg.edgeTypes

(الجميع)

أنواع الحواف مفصولة بفواصل :`EXTRACTED`, INFERRED, AMBIGUOUS

plantuml.kg.minConfidence

0.0

الحد الأدنى للثقة للحواف

plantuml.kg.maxNodes

(غير محدود)

العدد الأقصى للعقد للعرض

plantuml.kg.nodeTypes

(الجميع)

أنواع العقد مفصولة بفواصل (ex.class, code)

plantuml.kg.outputDir

diagrams/knowledge-graph

مجلد الإخراج للملفات`.puml` et .png

الحل الكامل في سير عمل Gradle

نوع سير العمل

@startuml
skinparam backgroundColor #FEFEFE
skinparam ActivityBackgroundColor #E8F4FD
skinparam ActivityDiamondBackgroundColor #FFF3CD
skinparam ActivityBorderColor #2C3E50

start

partition "EXTRACTION" {
    :تحويل إلى رسم بياني . --no-viz;
    note right: تحليل الكود المصدري\ntree-sitter (محلي، لا LLM)

    :graphify-out/graph.json;
    note right: العقد + الحواف\n+ المجتمعات
}

partition "تم إنشاؤهRATION" {
    :./gradlew generateKnowledgeGraphDiagram;
    note right: خط أنابيب محدد\nتحليل → عرض → التحقق → PNG

    if (Filtres actifs ?) then (Oui)
        :تطبيق الفلاتر\n(community, edgeTypes,\nmaxNodes, nodeTypes);
    else (Non)
        :مخطط كامل;
    endif

    :المخططات/knowledge-graph/\nknowledge-graph-full.puml\nknowledge-graph-full.png;
}

partition "INTإGRATION" {
    :git add diagrams/knowledge-graph/;
    :git commit;
    note right: مخططات مُصدّرة\nدائمًا محدثًا مع الكود
}

stop

@enduml

التكامل في دورة التطوير

يتكامل خط الأنابيب بشكل طبيعي في المراحل الرئيسية للتطوير :

@startuml
skinparam backgroundColor #FEFEFE

state "التطوير" as dev
state "استخراج\ngraphify . --no-viz" as extract
state "توليد\n./gradlew generateKnowledgeGraphDiagram" as generate
state "Commit\nالرسوم البيانية المصدّرة" as commit

[*] --> dev
dev --> extract : الكود المعدل
extract --> generate : graph.json محدث
generate --> commit : .puml + .png تم إنشاؤها
commit --> dev : الرسوم البيانية في المستودع

note right of extract
قريبًا من الفوري على الكود Kotlin (tree-sitter، لا يوجد LLM)
end note

note right of generate
مُحدَّد
لا LLM
نتيجة قابلة للتكرار
end note

@enduml

تحديث تدريجي

عندما يتغير الكود، لا نعيد بناء الرسم البياني بالكامل من الصفر :

# Mise à jour incrémentale (fichiers modifiés uniquement)
graphify . --update

# Puis régénérer les diagrammes
./gradlew generateKnowledgeGraphDiagram

--update`يستخرج مجددًا فقط الملفات المعدلة (التي تم اكتشافها بواسطة SHA256 في`graphify-out/cache/). عند العمل على كود Kotlin، يكون الأمر شبه فوري لأن tree-sitter يعمل محليًا دون استدعاء LLM.

الـ dogfooding : الإضافة توثق نفسها

الإضافة PlantUML موجودة لتحويل المطالبات إلى مخططات. يمكنه أيضًا تحويل Knowledge Graph الخاص بقاعدة الكود propre له إلى مخططات توثيقية. هذا هو dogfooding : الإضافة تستهلك خدمتها الخاصة.

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

package "خط الأنابيب العادي\n(المستخدم → المخططات)" as normal {
    [Fichier .prompt] as prompt
    [LlmService\n+ ApiKeyPool] as llm1
    [ProcessPlantumlPromptsTask] as task1
    [Diagramme PNG] as out1

    prompt --> task1
    task1 --> llm1
    llm1 --> out1
}

package "مخطط المعرفة لخط الأنابيب\n(حتمي، بدون LLM)" as kg {
    [graphify-out/graph.json] as kgjson
    [KnowledgeGraphParser] as parser
    [KnowledgeGraphRenderer] as renderer
    [PlantumlService] as ps
    [Diagramme PNG\n(documentation du plugin)] as out2

    kgjson --> parser
    parser --> renderer
    renderer --> ps
    ps --> out2
}

package "Pipeline Dogfooding\n(LLM → توثيق الإضافة)" as dogfood {
    [GraphifyPromptAdapter] as gpa
    [Fichiers .prompt\nauto-générés] as auto_prompt
    [LlmService\n+ ApiKeyPool] as llm2
    [ProcessPlantumlPromptsTask] as task2
    [Diagramme PNG\n(documentation LLM)] as out3

    kgjson --> gpa
    gpa --> auto_prompt
    auto_prompt --> task2
    task2 --> llm2
    llm2 --> out3
}

note "نفس LlmService، نفس ApiKeyPool،\nنفس PlantumlService\n— صفر تكرار" as N

@enduml

المهام Gradle المرتبطة :

مهمة LLM ? وصف

generateKnowledgeGraphDiagram

لا

حوّل`graph.json`باستخدام PlantUML (تحديدي)

generateDiagramDocs

نعم

أنشئ بعض`.prompt`من الرسم البياني، يعالجها عبر LLM (dogfooding)

# Documentation déterministe (rapide, pas de LLM)
./gradlew generateKnowledgeGraphDiagram

# Documentation LLM (plus riche, consomme des tokens)
./gradlew generateDiagramDocs

لماذا هو حتمي (ولماذا هذا مهم)

النقطة الأساسية في خط الأنابيب`generateKnowledgeGraphDiagram` : هو لا يستدعي أي LLM. التحويل`graph.json`PlantUML هي دالة نقية.

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

rectangle "Pipeline DÉTERMINISTE\n(generateKnowledgeGraphDiagram)" as det {
    [graph.json] as json
    [KnowledgeGraphParser] as parser
    [KnowledgeGraphRenderer] as renderer
    [PlantUML code] as puml

    json --> تحليلr : تحليل
    parser --> renderer : KnowledgeGraph
    renderer --> puml : عرض (دالة نقية)
}

rectangle "Pipeline LLM\n(processPlantumlPrompts)" as llm {
    [.prompt] as prompt
    [LlmService] as llmSvc
    [ChatModel] as model
    [PlantUML code] as puml2

    prompt --> llmSvc
    llmSvc --> model : API اتصل
    model --> puml2 : استجابة غير حتمية
}

note bottom of det
المدخل نفسه → المخرج نفسه
لا تأخير في الشبكة
لا تكلفة في الرموز
قابل للتكرار في CI
end note

note bottom of llm
نفس المدخل → مخرج متغير
الزمن المستغرق في الشبكة (1-5ث)
التكلفة بالتوكنات
يحتاج إلى مفتاح API
end note

@enduml

المزايا الملموسة :

ميزة التأثير

إعادة الإنتاج

نفس`graph.json`→ نفس المخطط. بالضبط. في كل مرة.

صفر تكلفة

لا استدعاء LLM = لا توكنات = لا فاتورة API.

صفر زمن الوصول

التحليل + العرض يستغرق ~100ms، وليس 1-5 ثوانٍ.

CI متوافق

لا يلزم وجود مفتاح API. لا يوجد اختبار غير مستقر بسبب استجابة LLM متغيرة.

قابل للإصدار

Le .puml`تم إنشاؤه نصًا. يمكننا ذلك`diff, المراجع في PR, إصداره في Git.

مخططات خط الأنابيب نفسه

لإتمام الحلقة، إليك مخطط الأنابيب كما لو تم إنشاؤه بواسطة الإضافة :

@startuml
skinparam backgroundColor #FEFEFE

participant "مطور" as Dev
participant "Gradle" as G
participant "GenerateKnowledgeGraphDiagramTask" as Task
participant "KnowledgeGraphParser" as Parser
participant "KnowledgeGraphRenderer" as Renderer
participant "PlantumlService" as PS
collections "graphify-out/graph.json" as JSON
collections "diagrams/knowledge-graph/" as Out

Dev -> G : ./gradlew generateKnowledgeGraphDiagram
G -> Task : نفّذ
Task -> JSON : قراءة
Task -> Parser : parse(graph.json)
Parser --> Task : KnowledgeGraph

alt filtres actifs
    Task -> Task : تطبيق الفلاتر\n(community, edgeTypes, maxNodes…)
end

Task -> Renderer : render(graph, filters)
Renderer --> Task : كود PlantUML

Task -> PS : validateSyntax()
alt valide
    Task -> PS : generateImage()
    PS --> Out : .puml + .png
else invalide
    Task -> Out : .puml (مع تحذير)
end

@enduml

التكامل في حوكمة المشروع

في مشروعنا، يتكامل هذا المسار في استراتيجية إدارة السياق EAGER/LAZY للوكيل الذكي الاصطناعي. يحل مخطط المعرفة محل وثائق الهندسة المعمارية اليدوية برسم بياني منظم، قابل للاستعلام، ويتم تحديثه تلقائيًا.

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

package "EAGER\n(دائمًا محمل)" as eager {
    [PROMPT_REPRISE.adoc\n(contexte session)] as pr
    [INDEX.adoc\n(vue d'ensemble)] as idx
    [GRAPH_REPORT.adoc\n(~50 lignes)] as gr
}

package "LAZY — استراتيجية\n(عند الطلب)" as lazy_strat {
    [Méthodologies] as meth
    [Archives sessions] as sessions
}

package "LAZY — Graphify\n(طلبات مستهدفة)" as lazy_graph {
    [graph.json] as gj
    [Queries\n(query/path/explain)] as queries
}

package "Pipeline PlantUML\n(مُولَّد آليًا)" as puml {
    [generateKnowledgeGraphDiagram] as kgTask
    [generateDiagramDocs] as ddTask
    [Diagrammes PNG] as diagrams
}

Agent --> eager : هو يقرأ في بداية الجلسة
Agent ..> lazy_strat : أَشْحِن إذا تمّ اكتشاف النوع
Agent ..> lazy_graph : إذا لزم الأمر هيكليًا
kgTask --> diagrams : حتمي
ddTask --> diagrams : من خلال LLM

note right of gr
ملخص من Knowledge Graph
God nodes + مجتمعات
استبدل ECOSYSTEM_OVERVIEW
(225 أسطر → 50 أسطر)
end note

@enduml

زواج النظامين مكمل:

  • الاستراتيجية تدير متى وكيف— الحوكمة, سير العمل, الأرشفة

  • Graphify تدير ما هو وأين— هيكل الكود، العلاقات، الاستعلامات المستهدفة

_ استراتيجية الجلسة تديرعندما et le كيف(الحوكمة, سير العمل, العتبات), Graphify تدير الما et le أين(هيكل الكود، العلاقات، الاستعلامات المستهدفة). يتعامل خط أنابيب PlantUML معبماذا(مخططات حتمية، مصدّرة، دائمًا محدثة). _

الإعداد: قائمة مراجعة في 5 دقائق

# خطوة أمر

1

تثبيت Graphify

uv tool install graphifyy

2

ضبط الاستثناءات

إنشاء`.graphifyignore`

3

استخراج مخطط المعرفة

graphify . --no-viz

4

إنشاء المخططات

./gradlew generateKnowledgeGraphDiagram

5

إيداع النتائج

git add diagrams/knowledge-graph/

# Script complet en 5 commandes
uv tool install graphifyy
cat > .graphifyignore << 'EOF'
*-context.yml
*.env
build/
.gradle/
EOF
graphify . --no-viz
./gradlew generateKnowledgeGraphDiagram
git add graphify-out/GRAPH_REPORT.adoc graphify-out/graph.json diagrams/knowledge-graph/

الفخاخ والتخفيف

فخ الوصف التخفيف

رسم بياني كثيف جدًا

مشروع كبير يولد مئات العقد غير المقروءة

استخدام`-Pplantuml.kg.maxNodes=30`و الفلترة حسب المجتمع

استثناءات واسعة جدًا

كثير من الملفات في`.graphifyignore`يقلل قيمة الرسم البياني

ابدأ باستبعاد credentials و build فقط

اتجاه الأسهم

قد تكون للحواف INFERRED اتجاه غامض

تصفية حسب`-Pplantuml.kg.edgeTypes=EXTRACTED`للعلاقات المؤكدة فقط

رسم بياني قديم

يتغير الكود لكن الرسم البياني لا يُعاد بناؤه

استخدم`graphify . --update`بشكل منتظم أو هوك git`graphify hook install`

التكلفة المحدثة

التحديثات التزايدية مجانية تقريبًا (tree-sitter local)

فقط المستندات (.adoc) يستهلكون رموز LLM

ما يحصل عليه في النهاية

@startuml
skinparam backgroundColor #FEFEFE

rectangle "قبل" as avant {
    card "مخططات مرسومة يدويًا\nدائمًا متقادمة\nلا أحد يحدّثها" as av1 #FDEDEC
}

rectangle "بعد" as apres {
    card "مخططات مُولَّدة تلقائيًا\nدائمًا محدَّثة مع الكود\nمُدارَة إصداراتها في Git\nحتمية وقابلة لإعادة الإنتاج" as ap1 #E8F8E8
}

avant --> apres : graphify . --no-viz\n+ ./gradlew generateKnowledgeGraphDiagram

@enduml

المنافع الملموسة :

ربح تفصيل

التوثيق دائمًا محدث

الرسوم البيانية تعكس الكود الحالي، وليس لقطة يدوية

صفر جهد للصيانة

يتم إعادة إنشاء المخططات عند كل build

تقليل الديون التقنية

لم يعد ضرورةً الحفاظ على المخططات يدويًا

السياق البصري للجدد

يفهم مطور جديد المعمارية من خلال النظر إلى المخططات

ضبط دقيق محتمل

الأزواج (الرسم الفرعي → الرسم البياني) هي أمثلة لتدريب الذكاء الاصطناعي

التحقق التلقائي

`PlantumlService.validateSyntax()`تحقق من كل مخطط تم إنشاؤه

إدماج سريع

5 مخططات = نظرة كاملة على الهندسة المعمارية

_ من لديه _pourquoi يستطيع أن يتحمل كل comment. __

السبب : رسومات محدثة دائمًا. الطريقة : أمرين في خط أنابيب Gradle.

روابط

مقالات ذات صلة