Portál AbcLinuxu, 3. května 2025 17:39
Pieter generování kódu doporučoval a používal.
5. Learn to use code generators.
I've spoken of GSL before. A general-purpose code generator is an essential tool for a serious programmer. There are a few options. Try them, choose one.
Ten Habits of a Good Programmer
Ovšem generování kódu má obvykle mezi programátory špatnou pověst. Ať vygenerovaný, či ne, kód je kód a bude obsahovat chyby. Opravovat chybu v generátoru bývá těžké. Napojit generátor na build systém je složité. V praxi se tak generování používá u magických věcí jako Google Protocol Buffers, nebo msgpack.
Problém: napojit generátor na build systém je těžké.
Update: mluvím tu o případu, kdy je kód generován v rámci all
targetu. To sebou nese nutnost dalších závislostí, takže další řádky v configure.ac, nemožnost experimentovat s vygenerovaným kódem, .... Stejně tak není možné projekt přeložit na vašem malém armu bez věcí jako gsl a zproject. Zproject na to má target code
. Ten není součástí hlavního buildu, takže takto generované projekty mají jenom smysluplné závislosti pro sestavení.
Řešení: pokud to není nezbytně nutné, nedělejte to. Člověk, který neumí změnit model a přegenerovat kód stejně není dostatečně kvalifikovaný, aby takové úpravy dělal. A kolegové programátoři budou rádi, pokud budou moci vyenerovaný kód prošpikovat print statementy pro účely ladění.
Problém: výstup je nečitelný, nedá se upravit
Řešení: používejte imatix/gsl. Nebo podobný nástroj.
GSL je skriptovací a šablonovací jazyk, který se šablony a modelu vytvoří výstupní dokument. Celé to zní složitě. Ale v zásadě se nejedná o nic jiného, než variantu šablonovacího systému jako je Jinja2 pro Python.
.output "deposits.c" // Generated by C.gsl, do not edit by hand #include <stdio.h> int main () { .for deposit printf ("amount=%d, rate=%d, years=%d\\n", $(amount), $(rate), $(years)); .endfor }Šablona zdrojového skriptu vypadá jako kód v jazyce C společně s pár předpisy gsl.
<?xml version="1.0"?> <deposits script = "C.gsl" > <deposit amount = "1000000" rate = "5" years = "20" /> <deposit amount = "500000" rate = "4" years = "10" /> <deposit amount = "2500000" rate = "6" years = "15" /> </deposits>
Vstupní model, který gsl zpracovává. Zkoušel jsem přiohnout Jinja2 na něco podobného, protože Python je přeci jenom známějším jazykem, ale gsl je ke svému účelu perfektně postaven. Asi největší výhodou celého tohoto systému je, že je rozdělen na tři navzájem nezávislé části. Skriptovací jazyk/engine gsl, model a potom samotný skript. Díky tomu se s celým systémem velmi dobře "hraje" a zkoumá, co to vlastně dělá. No a měnit šablony je obvykle triviální, protože jde o cílový text a pár značek.
Problém: zakládat projekty v C je obtížné.
Řešení: zproject
Zproject je asi největším projektem napsaný v gsl. Jedná se (především) o generátor projektů pro jazyk C (a C++). Vytvořit nový projekt je noční můra každého vývojáře v C. Existují tři způsoby
Problém je v tom, že jenom build systémem to nekončí. Člověk musí znát jak se generují sdílené knihovny, jak umět paralelní build, jak instalovat soubory, jak dělat skripty pro Travis CI, jak generovat dokumentaci, jak spouštět testy, ... A když napíše konečně všechno to Makefile.am/confiure.ac a přežije tisíce volání autoreconf
AC_PREREQ(2.61) AC_INIT([zyre],[m4_esyscmd([./version.sh zyre])],[zeromq-dev@lists.zeromq.org]) AC_CONFIG_AUX_DIR(config) AC_CONFIG_MACRO_DIR(config) AC_CONFIG_HEADERS([src/platform.h]) AM_INIT_AUTOMAKE([subdir-objects tar-ustar foreign]) m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) PV_MAJOR=`echo $PACKAGE_VERSION | cut -d . -f 1` PV_MINOR=`echo $PACKAGE_VERSION | cut -d . -f 2` PV_PATCH=`echo $PACKAGE_VERSION | cut -d . -f 3` AC_DEFINE_UNQUOTED([PACKAGE_VERSION_MAJOR],[$PV_MAJOR], [ZYRE major version]) AC_DEFINE_UNQUOTED([PACKAGE_VERSION_MINOR],[$PV_MINOR], [ZYRE minor version]) AC_DEFINE_UNQUOTED([PACKAGE_VERSION_PATCH],[$PV_PATCH], [ZYRE patchlevel]) AC_SUBST(PACKAGE_VERSION) LTVER="2:0:1" AC_SUBST(LTVER) ZYRE_ORIG_CFLAGS="${CFLAGS:-none}" AC_PROG_CC AC_PROG_CC_C99 AM_PROG_CC_C_O AC_LIBTOOL_WIN32_DLL AC_PROG_LIBTOOL AC_PROG_SED AC_PROG_AWK PKG_PROG_PKG_CONFIG
... nějaká dobrá duše bude chtít překládat pod Windows, takže nebohý programátor bude zkoumat, jak to napsat v CMake. A popravdě neexistuje žádný podstatný rozdíl mezi autotools a CMake, ani jako mezi rcs a csv.
cmake_minimum_required(VERSION 2.8) project(zyre) enable_language(C) enable_testing() set(SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) set(BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}) foreach(which MAJOR MINOR PATCH) file(STRINGS "${SOURCE_DIR}/include/zyre.h" ZYRE_blah blah string(REGEX MATCH "#define ZYRE_VERSION_${which} ([0-9_]+) blah if (NOT ZYRE_REGEX_MATCH) message(FATAL_ERROR "failed to parse ZYRE_VERSION_ blah blah endif() set(ZYRE_${which}_VERSION ${CMAKE_MATCH_1}) endforeach(which) set(ZYRE_VERSION ${ZYRE_MAJOR_VERSION}.${ZYRE_MINOR_VERSION}. blah include(CheckIncludeFile) CHECK_INCLUDE_FILE("linux/wireless.h" HAVE_LINUX_WIRELESS_H) CHECK_INCLUDE_FILE("net/if_media.h" HAVE_NET_IF_MEDIA_H) include(CheckFunctionExists) CHECK_FUNCTION_EXISTS("getifaddrs" HAVE_GETIFADDRS) CHECK_FUNCTION_EXISTS("freeifaddrs" HAVE_FREEIFADDRS) include(CheckIncludeFiles) check_include_files("sys/socket.h;net/if.h" HAVE_NET_IF_H) if (NOT HAVE_NET_IF_H) CHECK_INCLUDE_FILE("net/if.h" HAVE_NET_IF_H) endif() file(WRITE ${BINARY_DIR}/platform.h.in " #cmakedefine HAVE_LINUX_WIRELESS_H #cmakedefine HAVE_NET_IF_H #cmakedefine HAVE_NET_IF_MEDIA_H #cmakedefine HAVE_GETIFADDRS #cmakedefine HAVE_FREEIFADDRS ") configure_file(${BINARY_DIR}/platform.h.in ${BINARY_DIR}/platform.h)
Zprojekt tedy přichází s řešením, modelem.
Todo je model projektu zyre.
<project name = "zyre" description = "an open-source framework for proximity-based P2P apps" script = "zproject.gsl" email = "zeromq-dev@lists.zeromq.org" url = "http://github.com/zeromq/zyre" repository = "http://github.com/zeromq/zyre" > <include filename = "license.xml" /> <version major = "1" minor = "1" patch = "0" /> <use project = "czmq" /> <class name = "zyre" /> <class name = "zyre_event" /> <class name = "zre_msg" /> <class name = "zyre_peer" private = "1" /> <class name = "zyre_group" private = "1" /> <class name = "zyre_node" private = "1" /> <model name = "zre_msg" /> <main name = "perf_local" private = "1" /> <main name = "perf_remote" private = "1" /> <main name = "zpinger" /> <main name = "ztester_beacon" private = "1" /> <main name = "ztester_gossip" private = "1" /> <target name = "*" /> <target name = "nodejs"> <option name = "version" value = "0.0.6" /> <option name = "repo" value = "https://github.com/hintjens/zyre-nodejs" /> </target> <constant name = "ZRE_DISCOVERY_PORT" value = "5670">IANA-assigned UDP port for ZRE</constant> <constant name = "REAP_INTERVAL" value = "1000" private = "1" >Once per second</constant> </project>
Na základě modelu zprojekt vygeneruje strukturu projektu, čili adresáře doc, include a src. V doc jsou manuálové stránky generované ze speciálních sekcí ve zdrojových souborech skriptem mkman. Adresář include obsahuje veřejné API, s tím, že projekt.h je hlavním souborem projektu. A v src jsou potom zdrojové kódy, hlavičkové soubory soukromých tríd a další soubory jako pkg-config, konfigurační soubory, anebo systemd předpisy.
Všechen kód ze tříd putuje do sdílené knihovny, v tomto případě libzyre.so.1. Tím je libovolný zproject projekt i sdílenou knihovnou, pokud obsahuje alespoň jednu veřejnou třídu. Zprojekt vygeneruje zároveň i testovací funkce, jako zyre_test. Takže make check spustí testy všech tříd, a make memcheck je spustí pod valgrind, takže se rovnou otestují i případně problémy s pamětí.
V základu zprojekt generuje předpisy pro autotools a CMake. Ovšem je k dispozici hromada skriptů, které umí generovat jiné zajímavé věci.
Základ draft API byl položen ve SVatém^WPieterově zápisku The End of Software Versions. Hlavní myšlenkou je, že verze software je jako obvykle umělý konstrukt. Důležité je, které protokoly (standardy) daný program podporuje a v jaké verzi. Autoři prohlížečů to pochopili dávno. Takže dneska se nikdo neptá na aktuální verzi Firefox, nebo Chrome. Zajímavá otázka je jenom - podporuje aktuální verze standard foobar 4.0, nebo ne?
Zproject implementuje contract lifecycle tak, že pomocí konceptu draft API (a méně zajímavého deprecated) umožňuje projektům libovolně rozšiřovat a přídávat nová API s tím, že ta označená jako draft, se ve výchozím stavu nepřeloží.
Celé to potom vede k filozofii dávejte všechno do master větve, takže poslední vydání libzmq a czmq ani nepoužívají release forky, jako tomu bylo v minulosti. Viz Git Branches Considered Harmful, nebo A succesful Git branching model considered harmful
Už jen ve stručnosti, zproject obsahuje i generátory bindingů. Díky tomu, jakje CLASS striktní, tak je možné popsat model API v XML. A později vygenerovat dopovídající binfing. Zajímavostí je nástroj mkapi.py, který z C souborů vytvoří XML model.
Tiskni
Sdílej:
Problém: napojit generátor na build systém je těžké.Asi tak dva radky do makefile?
mluvím tu o případu, kdy je kód generován v rámci all targetu. To sebou nese nutnost dalších závislostí, takže další řádky v configure.ac, nemožnost experimentovat s vygenerovaným kódem, .... Stejně tak není možné projekt přeložit na vašem malém armu bez věcí jako gsl a zproject.Ze se kod generuje v ramci all targetu nevylucuje: 1) Moznost distribuovat uzivatelum baliky s predgenerovanym kodem (cimz odpadaji zavislosti a nutnost testu v configure.ac). 2) Mit navic target ktery pouze predgeneruje kod (takze je mozne ho pak pouzit pro pripraveni baliku pro uzivatele ci pro preklad na malem ARMu). 3) Nebrani vyvojarum experimentovat s vygenerovanym kodem (dokud neupdatuji puvodni zdroj, tak make nema duvod pregenerovat generovany kod).
function(cxx_detect_cflags out) set(out_array ${${out}}) foreach(flag ${ARGN}) string(REGEX REPLACE "[-=:;/.]" "_" flag_signature "${flag}") check_cxx_compiler_flag(${flag} "__CxxFlag_${flag_signature}") if(${__CxxFlag_${flag_signature}}) list(APPEND out_array "${flag}") endif() endforeach() set(${out} "${out_array}" PARENT_SCOPE) endfunction() function(cxx_detect_standard out) set(out_array) cxx_detect_cflags(out_array "-std=c++14" "-std=c++11" "-std=c++0x") # Keep only the first flag detected, which keeps the highest version supported. if(out_array) list(GET out_array 0 out_array) endif() set(out_array ${${out}} ${out_array}) set(${out} "${out_array}" PARENT_SCOPE) endfunction()Pouziti jednoduche:
MY_CFLAGS="..." cxx_detect_standard(MY_CFLAGS) cxx_detect_cglags(MY_CFLAGS "-fno-keep-static-consts")Nerikam, ze to je super reseni a ze by to neslo deklarativne, ale ... Muj nejvetsi problem s cmake je ten idiotsky jazyk, ktery nema ani 'return'.
Tyto nastroje co pouzivaji deklarativni pristup failujou uz pri prvnim netrivialnim pozadavku.
Scons a zejmena GYP jsou moje nocni mura.Trochu rozpor, ne? GYP neznam, ale se SCons, kdyz potrebujes neco netrivialniho, prejdes do normalniho proceduralniho programovani a mas po problemu. Ve srovnani s tim mi prisly make+autotools jako generator problemu.
problémy s cmake/autotools/whatever za problémy se zproject...jo. Sem ze stare skoly a proto mam radu problemu s temito 'pomuckami'. Napr. predevcirem jsem chtel rozchodit na centos5 nejnovejsi podofo paket. (protoze jsem vubec necekal, ze bude na epelu existovat startsi verze jako rpm-ko). Nejdrive jsemm si musel nainstalovat cmake28 (cmake nestacil) a pak to hlasilo, ze konfigurace se nepovedla protoze .... Takze jsem jeste zkusil centos6 a zase jine hlasky. Musim rict, ze to jsem fakt necekal, tyhle systemy musi byt preci naprosto blbovzdorne, jestlize podle navodu odsadim ty prikazy a ono to nefunguje a ani nerekne, co mam delat, tak to by bylo lepsi predat seznam tech zdojaku a clovek si to prelozi sam. Fakt hruza.
Co se týče zproject, připadá mi, že bych tím jen vyměnil problémy s cmake/autotools/whatever za problémy se zprojectJa mam pocit, ze to ma tento vyvoj: 1) Programator chce programovat, ale moc nezna make. 2) Pouzije automake, zkopiruje nekde par sablon, a ve vysledku tam ma make a automake, kterym obema moc nerozumi. 3) V dalsi fazi tam prida zproject, takze ve vysledku tam bude make, automake a zprojekt, pricemz ten programator kloudne nezna ani jedno z toho. 4) Pokud by chtel pridat nejakou neobvyklou vlastnost, tak bude muset vyresit v zproject, aby to generovalo spravne predpisy pro automake, aby ten generoval spravne predpisy pro make, takze to radsi vzda. 5) V konecnem dusledky by asi udelal lip, kdyby si rovnou precetl manual ke GNU make a napsal to primo v tom.
4) Pokud by chtel pridat nejakou neobvyklou vlastnost, tak bude muset vyresit v zproject, aby to generovalo spravne predpisy pro automake, aby ten generoval spravne predpisy pro make, takze to radsi vzda.Lepší postup je šťouchat do autotools tak dlouho, až dělají to, co potřebuji. A potom výsledek nakopírovat do zproject a nahradit konkrétní jména za
$(proměnné)
. Pak napsat make code
pro finální test skriptu a udělat pull request.
5) V konecnem dusledky by asi udelal lip, kdyby si rovnou precetl manual ke GNU make a napsal to primo v tom.Akorát při použití zproject má člověk rovnou strukturu projektu (include/, src/, doc/), viditelnost symbolů, správně vyřešené závislosti na dalších knihovnách, podporu unit testů, podporu code coverage, callgrind a valgrind, automatické generování dokumentace, verzování ABI i API, ... a to mluvím jenom o ekvivalentu GNU make předpisu. Jinak je podpora pro docker file, Travis testy, balíkářské informace pro Debian, RedHat, bindingy pro Javu, Python, Lua, Ruby, NodeJS a Qt/QML, build předpis pro Visual Studio a jiné.
Ovšem generování kódu má obvykle mezi programátory špatnou pověst. Ať vygenerovaný, či ne, kód je kód a bude obsahovat chyby. Opravovat chybu v generátoru bývá těžké. Napojit generátor na build systém je složité. V praxi se tak generování používá u magických věcí jako Google Protocol Buffers, nebo msgpack.Nad timto se musi nejeden lispar pousmat. ;-]
To dává smysl - nevšiml jsem si, že by kromě posměšků nad ostatnímy jazyky produkovali lispaři někdy něco jiného.Asi jsi malo vsimavy. Kazdopadne tech 50+ let posmesku ma neco do sebe a uz zacina sklizet ovoce. Staci se podivat, jak vypadaly mainstreamove jazyky na prelomu tisicileti, kdy for-cyklus byl pomalu nejslozitejsi konstrukt, a jak vypadaji jazyky dnes, ... ta mas samou lambdu, map, line vyhodnocovane kolekce, atd. Mozna se casem dobereme toho, ze se rozumne pouzitelna makra (generatory kodu) stanou integralni soucasti jazyka a metod vyvoje a nebude to budit takove pozdvizeni, jako kdyz nekdo z XML generuje kod v cecku.
trousením těchto osvícených mouder do diskusíTy snad delas neco jineho? Nedelas. Delas to same a dukazem toho jsou i prispevky v diskusi pod timto blogpostem.
:-/
ISSN 1214-1267, (c) 1999-2007 Stickfish s.r.o.