C प्रोजेक्ट्स को कैसे संरचित करें: ये सर्वोत्तम प्रथाएँ मेरे लिए काम आईं

C प्रोजेक्ट्स को संरचित करना और build tools चुनना, वास्तविक जटिलता आते ही जल्दी विवादास्पद हो जाता है। टिप्पणीकार पारंपरिक Make-आधारित लेआउट (जैसे `src/`, `include/`, और हाथ से लिखे Makefile) की तुलना Meson, CMake, और यहाँ तक कि Rust के Cargo या Go की tooling से करते हैं, और सरलता, लचीलापन, portability, तथा “zero-configuration” सुविधा के बीच trade-offs पर बहस करते हैं। साथ ही वे header और source organization, testing strategies, code generation, cross-compilation, और यह भी चर्चा करते हैं कि क्या C का fragmented tooling ecosystem उसकी ubiquity की स्वीकार्य कीमत है या नए भाषाओं को अपनाने का कारण।

समग्र प्रोजेक्ट लेआउट

  • प्रस्तावित संरचना को C++ “pitchfork” लेआउट (src/, include/, आदि) के बहुत समान माना गया।
  • कुछ लोगों का तर्क है कि आंतरिक हेडरों के लिए .c और .h को अलग टॉप-लेवल डायरेक्टरियों में बाँटना अनावश्यक है; जबकि कुछ इसे प्लेटफ़ॉर्म-विशिष्ट इम्प्लीमेंटेशन के लिए उपयोगी मानते हैं (जैसे platform_windows.c बनाम platform_linux.c)।
  • कई लोग सभी सबसिस्टम को src/ के अंदर रखने को प्राथमिकता देते हैं, और include/ को केवल सार्वजनिक/लाइब्रेरी हेडरों के लिए आरक्षित रखते हैं।
  • लाइब्रेरीज़ के लिए इंस्टॉल होने वाले हेडर बनाम आंतरिक हेडरों का अंतर महत्वपूर्ण माना जाता है।
  • कुछ लोग रिपॉज़िटरी के अंदर bin/ और lib/ को पसंद नहीं करते; वे कॉन्फ़िगर करने योग्य PREFIX (build/ या $HOME/.local) और PATH को समायोजित करने वाली env फ़ाइलों को प्राथमिकता देते हैं।

Make, CMake, और वैकल्पिक build tools

  • लचीले Makefile के लिए कई recipes सुझाई गईं: pattern rules, wildcard + patsubst, और ऑटो-जनरेटेड object lists ताकि Makefile को मैन्युअली अपडेट न करना पड़े।
  • यह स्पष्ट किया गया कि built-in %.o rules केवल तब काम करते हैं जब sources और objects एक ही डायरेक्टरी में हों; workaround में VPATH या explicit rules शामिल हैं।
  • लेख के Makefile की आलोचना की गई: यह -j के तहत नाज़ुक build-order व्यवहार पर निर्भर करता है, compile_commands.json संग्रहीत करता है, और editor/CI डायरेक्टरियों को “litter” के रूप में मिला देता है।
  • कुछ लोग छोटे प्रोजेक्ट्स के लिए बहुत minimal builds का समर्थन करते हैं (एक single all.c जो सब कुछ #include करता है); आलोचकों का कहना है कि यह sanitizers, tests, CI तक स्केल नहीं करता।
  • Make पर मतभेद हैं: कुछ इसे सरल, composable, और scalable मानते हैं; जबकि अन्य इसे Meson, Buck2, Xmake, Zig के build system आदि की तुलना में पुराना और झंझटभरा मानते हैं।
  • CMake को शक्तिशाली लेकिन असहज माना गया; फ़ाइलों को explicitly सूचीबद्ध करने बनाम globs उपयोग करने पर बहस हुई (regeneration और correctness के tradeoffs के साथ)।

टूलिंग, परीक्षण, और code generation

  • सुझाव: MinUnit, Clang-Tidy (जैसे CERT profile), Clang-Format, -Weverything, sanitizers (ASan/UBSan), और सख्त warnings (-Werror=missing-declarations, आदि)।
  • C में unit testing पर मिश्रित दृष्टिकोण थे: कुछ लोग assertions और context-sensitive procedures पर ज़ोर देते हैं; अन्य लोग भारी परीक्षण वाले C प्रोजेक्ट्स को counterexamples के रूप में पेश करते हैं।
  • कई लोगों ने implementation के साथ unit tests को co-locate करने का वर्णन किया, साथ ही *_test.c जैसी conventions, जिन्हें Make स्वतः detect कर सकता है।
  • code generators (Lua, Python, templating engines, यहाँ तक कि C स्वयं) का उपयोग करने और src/ को generators, gen/ को emitted C, तथा obj/ को build output मानने के लिए प्रबल प्रोत्साहन दिया गया।
  • हेडरों को अकेले compile करने और #pragma once बनाम include-guard tradeoff पर चर्चा हुई।

Packages, cross-compilation, और meta-debate

  • “Cargo-जैसी” सरलता की इच्छा: एक single standard tool, zero/low configuration, और consistent layout।
  • अन्य लोगों का तर्क है कि C की उम्र, portability, और विविध प्लेटफ़ॉर्म एक single standard tool को अव्यावहारिक बनाते हैं; इसलिए multiple package/build systems (Conan, vcpkg, pkg-config, vendoring) साथ-साथ मौजूद हैं।
  • Cross-compilation: zig cc और embedded-style toolchains का उल्लेख; interfaces के पीछे platform layer को अलग रखने पर ज़ोर।
  • Rust बनाम C पर लंबी meta-बहस: Rust के Cargo और Go की tooling की प्रशंसा की गई; कुछ लोग “rewrite in Rust” वाली भटकावों की शिकायत करते हैं, जबकि अन्य कहते हैं कि tooling की तुलना उचित और अपेक्षित है।