[OT] Zarządzanie konfiguracją modułów ko

Przedstawię najpierw scenariusz oraz problem:

Mamy bibliotekę modułów kodu źródłowego, które są używane w wielu projektach. Każdy z modułów ma swoje parametry konfiguracyjne, którymi można go "dostroić" dla potrzeb konkretnej aplikacji i płytki - np. moduł będący driverem do scalaka radiowego ma m.in. parametry określające do jakich pinów procesora scalak jest podpięty, moduł będący driverem do pamięci na I2C ma parametry mówiące którym interfejsem I2C mikrokontrolera należy z tą pamięcią rozmawiać, jaki jest rozmiar pamięci i który pin procesora to CS, itp.

Problem: W każdym projekcie, który korzysta z danego modułu, ustawiamy odpowiednio wszystkie parametry. Następnie, z dowolnej przyczyny, w bibliotece zmieniamy zestaw parametrów konfiguracyjnych oferowanych przez moduł. W efekcie musimy poprawić pliki konfiguracyjne w każdym z projektów, co przy rosnącej liczbie tych projektów po chwili staje się boleśnie pracochłonne.

Szukam mądrej metody radzenia sobie z tą niepotrzebną pracą. Metody aplikowalnej do kodu w C i assemblerze (choć bardzo chętnie usłyszę, jeśli w C++ da się to zrobić jakoś lepiej). Ważne, aby metoda nie powodowała skutków run-time (np. zamiast ustalać rozmiar pamięci w konfiguracji, mogę mieć zmienną ustawianą w funkcji inicjalizującej, do pinów mogę mieć callbacki włącz/wyłącz, też ustawiane przez API, a do sprzętowego modułu I2C mogę mieć dodatkową warstwę abstrakcji, ale wszystko to kosztuje pamięć lub cykle, a w przypadku niektórych parametrów jest praktycznie niewykonalne - poza tym tylko to przesuwa problem ze zmian konfiguracji, na zmianę API).

Jestem blisko rozpoczęcia pisania programu, który będzie mi zarządzał tą konfiguracją, tzn. za jednym klikiem porówna konfiguracje wszystkich zarządzanych projektów, pokaże gdzie są nieustawione parametry, gdzie są ustawione już nieistniejące parametry, gdzie wartość parametrów różni się od domyślnej, a następnie umożliwi wybranie akcji, ustawienie właściwych wartości i uaktualni zarządzane pliki konfiguracyjne. Ale trochę mam nadzieję, że istnieje jakiś inteligentny sposób, na który nie wpadłem.

ae

Reply to
Andrzej Ekiert
Loading thread data ...

W dniu 06.05.2012 13:09, Andrzej Ekiert pisze:

Na to nic nie poradzisz. Skoro biblioteka zmienia swoje parametry/interface to musisz dostosować do niej programy. W c++ od biedy można zastosować parametry domyślne albo przeciążyć funkcje. Wtedy stare programy mogą korzystać ze starego interfejsu.

A to już zwykłe makra, definy, funkcje inline, specjalizacje szablonów nie wystarczą do ukrycia fizycznego położenia pinów?

Jeśli do wszystkich modułów I2C z różnych procesorów jesteś w stanie opracować jeden interface to nie ma problemu. Dodajesz do projektu plik ze swoją obsługą I2C od danego procka i już. Moduł radiowy wykorzysta te funkcje, które dołączy linker. Zero narzutu.

Ja raczej unikami używania tej samej kopii biblioteki do różnych projektów. Dasz sobie głowę uciąć, że zmiana w bibliotece pod bieżący projekt x nie spowoduje jakiś anomalii w projekcie x-10, który pisała inna osoba? Wolę zrobić kopię biblioteki z projektu x-1 i nanieść poprawki.

Reply to
Zbych

Ja osobiście jestem na początku drogi - tj. jeszcze nie powstały te dziesiatki modulow do dziesiatek aplikacji, ale zaczynam już dostrzegać problem, stąd moje zainteresowanie tą problematyką - tak, aby zawczasu coś ustalić i pisać dalej w/g ustalonej, optymalnej metody.

Jednym z pomysłów jest wykorzystanie programów do zarządzania wersjami typu: SVN, GIT, Mercurial. Innym pomysłem, który mnie zainteresował, jest wykorzystanie linków do plików zamiast kopii plików (pod Linuxem) - w ten sposób mamy tylko jeden plik, widziany przez dowolnie wiele projektow i zmiana w nim przenosi się automatycznie na wszystkie projekty.

Najprosciej byłoby nie zmieniać parametrow konfiguracyjnych w bibliotece ;-) A może zostawić stare jako 'deprecated', a dodac do nich nowe?

Reply to
Jacek Domański

Dnia 06-05-2012 o 14:21:22 Jacek Domański snipped-for-privacy@nospamchello.pl napisał(a):

Ależ ja to wszystko mam pod Mercurialem, tyle że to tego problemu akurat nie rozwiązuje.

Chodzi o to, że dla każdego projektu mam te same parametry ustawione w inny sposób. Więc też nie rozwiązuje problemu.

No rzeczywiście :-)

ae

Reply to
Andrzej Ekiert

Dnia 06-05-2012 o 14:19:08 Zbych snipped-for-privacy@onet.pl napisał(a):

Wystarczą, ale dla każdego projektu trzeba te define'y inaczej ustawić - ta sama nazwa, inna wartość.

Przy wielu architekturach, to akurat nie mam wyjścia i muszę zrobić takiego HALa, ale narzut jest. W przypadku jednej architektury, to zamiast po prostu się odwołać do rejestru sprzętowego modułu, muszę przekazać mojemu driverowi do chipu jakąś strukturę drivera do modułu I2C, która będzie mieć np. callbacki do funkcji pośredniczących. Narzut jak diabli, choć czasem trzeba się na niego zgodzić (np. wspódzielony dostęp kilku "driverów" do jednego sprzętowego I2C).

Staram się tak modularyzować kod i dawać takie API, żeby zmiany nie wywoływały efektów ubocznych. Oczywiście przetestować to zawsze trzeba i nie dam sobie uciąć nawet paznokcia.

Wykrywasz błąd albo robisz usprawnienie w x-1 i dopiero masz poprawianie wszędzie gdzie ta kopia jest. Brrr...

ae

Reply to
Andrzej Ekiert

Zapropnuje sposób trywialny, być może akurat będzie w sam raz:

*** i2cdriver.c ***

#include "i2cconfiguration.h" #include "../lib/i2c/i2ccore.c" #include "../lib/i2c/i2chardware.c"

*** i2cdriver.h ***

#include "i2cconfiguration.h" #include "../lib/i2c/i2cinterface.h"

Plik i2cconfiguration.h jest częscią konkretnego projektu, podobnie jak i2cdriver.c.

I tyle. W i2cconfiguratiion stado #define per projekt. W plikach lib/i2c (h i c) od groma #ifdef na każdy wariant.

Powinno być najmniej intruzywne i najłatwiejsze w utrzymaniu przez programistę C. Żadnych zmian w bibliotece. Wada: kompilujesz wszystko za pierwszym razem. ALE. Dzieki make potem kompilujesz tylko zmiany, więc narzut jest jednorazowy.

Reply to
Sebastian Biały

W dniu 06.05.2012 14:55, Andrzej Ekiert pisze:

Zgadza się i zakładam, że rzeczy specyficzne dla projektu trzymasz w osobnym pliku, który leży sobie w katalogu z projektem i jest przez bibliotekę tylko includowany. Zgadza się?

Ja jak na razie na I2c wieszałem jakieś pamięci, RTC itp. badziewie. Do jego obsługi wystarczały mi 3 funkcje typu wyślij blok danych, odbierz blok danych, sprawdź gotowość. Współdzielenie zabezpieczałem mutexami. Funkcje RTC czy obsługa pamięci wprost wołały te funkcje. W innym procku dodawałem tylko inną bibliotekę do I2c. Interface zostawał ten sam. Zero narzutu.

To jest niewątpliwie wada. Ale powiedzmy sobie szczerze, ile można spieprzyć w kodzie obsługi I2C, uarta itp? A teraz weź projekt sprzed kilku lat (bo klient chce drobną poprawkę) i skompiluj go z nową biblioteką. Jaką masz gwarancję, że nie wyjdą jakieś wredne bugi związane np. z zależnościami czasowymi?

Reply to
Zbych

Dnia 06-05-2012 o 15:15:04 Sebastian Biały snipped-for-privacy@poczta.onet.pl> napisał(a):

Ale to mi w żaden sposób nie dotyka mojego problemu. Jeśli odwołam się w "../lib/i2c/i2ccore.c" do nowego parametru konfiguracyjnego C_I2C_SHMOO, to muszę go ręcznie zdefiniować w każdym i2cconfiguration.h w każdym projekcie. Jeśli zmienię nazwę i trochę funkcje parametru C_I2C_FOO na C_I2C_BAR, to znowu zmiana w każdym projekcie. Chodzi mi o sposób lub narzędzie do automatyzacji takich zmian: wykrywanie niedodanych definicji, eliminację przestarzałych definicji, itp.

Samo definiowanie konfiguracji per-projekt i jej #include w plikach biblioteki to mam rozwiązane w miarę elegancko. Boli mnie tylko potrzeba ręcznego czyszczenia konfiguracji per-projekt w wypadku zmian w opcjach oferowanych przez bibliotekę.

ae

Reply to
Andrzej Ekiert

Dnia 06-05-2012 o 15:23:06 Zbych snipped-for-privacy@onet.pl napisał(a):

Tak.

Masz driver do RTC w bibliotece. Procesor ma 2 moduły sprzętowe I2C1 i I2C2. Musisz przekazać driverowi RTC informację, funkcje dotyczące którego modułu ma wywołać: odbierz_blok_danych_z_I2C1() czy odbierz_blok_danych_z_I2C2(). Albo to robisz na poziomie #define, albo definiując "driver" do I2C i przekazując modułowi I2C handle do tego drivera (jest narzut). Ja chcę na poziomie #define, ale pragnę sobie usprawnić zarządzanie takimi #define.

Spieprzyć zawsze można. Poza tym moduł może być czymś bardziej złożonym. Np. implementacją protokołu sieciowego.

Gwarancji nie mam, choć moja ciągła dbałość o to, by kawałek kodu, w którym zależności czasowe występują, nie mógł być zakłócony przez inne niezwiązane z nim moduły wykonywane równolegle, daje mi spore szanse. Generalnie uważam, że zysk ze współdzielonych bibliotek znacznie przewyższa koszty.

ae

Reply to
Andrzej Ekiert

#include "../lib/i2c/defaultconfiguration.h" #include "i2cconfiguration.h"

To powinno zadzialać jak gdyby dziedziczenie parametrów. Możesz też uzyć #ifndef FOO, #define FOO DEFAULR_FOO.

Ewentualnie, znacznie bezpieczniej, #ifndef FOO, #error "FOO not set"

Najlepiej, gdybys tego nie robił w ogóle. takie narzędzie jest niebezpieczne. Wydaje mi się, że najbezpieczniej jest zdać się na kompilator. Czyli raz na jakiś czas budujesz wszystkie swoje żywe projekty w całości i poprawiasz tam gdzie padła kompilacja.

Podobnie do tego pomysłu działa konfigurator opcji kompilacji linuxa (kernela). Możesz sobie wyobrazić że tak jest ich dużo i że pojawia się niedostrzegana wcześniej warstwa - zależności. W dodatku są ustalane ręcznie. Np. sterownik do foobar można kompilowac tylko wtedy gdy jest scsi itp. Takie zalezności są cieżkie w utrzymaniu bo nie wynikają wprost z kodu tylko z jakieś metawiedzy poza.

Tego nie unikniesz w przypadku ogólnym. Jesli i2cdriver_init przyjmie 2 parametry a nie jeden to i tak musisz zmienić *wszystkie* projekty. Wielu programistów C wpada tutaj na genialny pomysł uzycia makr albo domyslnych parametrów, ale do ślepa uliczka. Tak czy inaczej refaktoring bibliteki generuje zmiany po stronie klientów.

Jesli masz klienta martwego, ale mimo to chcesz utrzymać kompilację, to czasami wystarczy kod forkować, czyli #include "../lib/i2c/v2/i2ccode.c". Nie jest to eleganckie, ale w przypadku embedded pozwala projekt zamrozić. Problemem jest backportowanie poprawek.

Ten sposob jest uzywany też na linuxie, wystarczy zobaczyć katalog /lib żeby zauważyć wiele róznych wersji bibliotek. Głównie dla supportu starych klientów.

Reply to
Sebastian Biały

Ja właśnie rzeźbię powolutku coś takiego w C++, tylko zamiast callbacków traits przekazywane do szablonów, żeby nie było żadnego narzutu w runtime.

Kompilator odwala całkiem niezłą robotę z funkcjami inline, np

HW::uart<0>::send_char(buf[i]); zamienia na pojedynczy mov do rejestru.

Reply to
Michoo

W dniu 06.05.2012 15:44, Andrzej Ekiert pisze:

No to dużo więcej już nie wymyślisz. Co najwyżej możesz w przypadku dokładania nowych stałych/parametrów napisać:

#ifndef XYZ #define XYZ 12345 #endif

Wtedy biblioteka ze starym programem nie będzie krzyczała, że nie ma zdefiniowanych parametrów.

No to użyj tego define. I tak jak podałem wyżej możesz zrobić domyślną definicję dla starych programów.

Takie rzeczy jak protokół to już dla mnie warstwa wyższa :-) i tu nie mam obaw o współdzielenie.

Reply to
Zbych

Dnia 06-05-2012 o 15:49:54 Sebastian Biały snipped-for-privacy@poczta.onet.pl> napisał(a):

Mam to dość podobnie zrobione. Samo dziedziczenie defaultu to nie jest najlepszy pomysł, bo nowo dodany parametr może przejść niezauważony. Więc albo dziedziczę całą konfigurację modułu, albo wszystkie parametry muszą być w projekcie przedefiniowane.

To już w zasadzie mam wbudowane: zawsze kompiluję z -Wundef i zamiast #ifdef do włączania opcji używam "#if C_FOOBAR == ENABLED". Mam wtedy warning gdy coś nie jest ustawione.

Skłaniam się ku użyciu narzędzia, żeby zamiast otwierać 20 plików (a za parę lat może 100?) i wszędzie robić 'paste' nowego parametru, móc kliknąć "update". Oczywiście kompilacja potem i tak jest nieodzowna (nie mówiąc o teście).

Nawet zaglądałem mu w źródła, czy by się nie dało czegoś wykorzystać, ale trochę mnie odrzuciło. Poza tym w menuconfig jednak niezbyt dobrze widać nowe parametry, a usunięte po prostu po cichu znikają (jeśli się nic nie zmieniło od ostatniego razu, jak kompilowałem kernel).

Miałem po prostu nadzieję, że kogoś już to uwierało i jakieś narzędzie istnieje. No nic, napiszę sobie sam.

ae

Reply to
Andrzej Ekiert

W dniu 06.05.2012 16:10, Andrzej Ekiert pisze:

Przy okazji tematu nasuwa się pytanie: "Jak długo należy utrzymywać stare projekty w stanie zaktualizowanym" Czy po prostu nie umrą one śmiercią naturalną i zatrzymają się na jakimś etapie ich rozwoju, a łatwiej będzie stworzyć nowy projekt, na nowy procesor, w/g nowych bibliotek, itp....

Reply to
Jacek Domański

A jakie będą tego zalety w stosunku do wersji z define?

Reply to
Zbych

Jesli to stare projekty to po prostu zamrażasz implementacje bibliotek i ich nie rozwijasz. Ile równolegle utrzymujesz żywych projektów? Kilka?

Zamrozić biblitekę można też pisząc adapter do nowszej wersji. Niestety to generuje czasem spory kod i potrafi też szlag trafić.

Ostatecznie po prostu porzucaj kod starych projektów poprzez usuwanie go z repozytorium. Zawsze się możesz wycofać jak-by-co. W embedded to jak-by-co nie występuje za czesto.

Zerknij jeszcze na konfigurator eCos.

IMHO zamieniasz siekierkę na kijek. Now narzedzie nie usunie problemów starego. Dlaje będziesz walczył z poprawianiem kodu, z zastanawianiem się jaki podać parametr w miejsce dwoch poprzednich itp. Zawsze będzie ywmagana ingerencja w kod. Tak czy inaczej.

Reply to
Sebastian Biały

Dnia 06-05-2012 o 16:42:22 Sebastian Biały snipped-for-privacy@poczta.onet.pl> napisał(a):

Mam trochę specyficzną sytuację: np. do projektu referencyjnego modemu PLC mam kilka programów demo, oprogramowanie własnych urządzeń, kilka programów pod różnych klientów oraz aplikacje testowe. W sumie kilkanaście programów do konfigurowania w ramach jednego żywego projektu. Wszystko musi być ciągle gotowe do wypuszczenia kodu na zewnątrz.

Kiedyś go nawet używałem. Faktycznie, zobaczę.

ae

Reply to
Andrzej Ekiert

Że nie muszę kopiować kodu dla uart 1. No i środowisko mi ładniej koloruje funkcje wpisane jako funkcje a nie jako połamane define.

Btw - jak będę miał dość czasu, żeby posprzątać kod to wrzucę go na jakieś sourceforge i każdy będzie mógł ocenić.

Reply to
Michoo

Dnia 06-05-2012 o 15:54:48 Michoo <michoo snipped-for-privacy@vp.pl napisał(a):

Możliwość robienia czegoś takiego mnie mocno przekonuje do C++.

ae

Reply to
Andrzej Ekiert

W dniu 2012-05-06 16:10, Andrzej Ekiert pisze:

Chyba miało być odwrotnie...

Ciągle nie rozumiem na czym polega Twój problem... Skoro pojawia się nowy parametr modułu bibliotecznego, to mamy dwie opcje: albo musi on być obligatoryjne określony przez użytkownika biblioteki (wtedy zdajemy się na kompilator i błąd klasy undefined), albo nowy parametr ma jakąś wartość domyślną (stałą lub jakoś ewaluowaną) i użytkownik ewentualnie może przedefiniować ten parametr.

Czy coś ponad to?

pzdr mk

Reply to
mk

ElectronDepot website is not affiliated with any of the manufacturers or service providers discussed here. All logos and trade names are the property of their respective owners.