8 lip 2012

Continuous Delivery by Jez Humble, David Farley

I have just read book „Continuous Delivery: Reliable Software Releases through Build, Test, and Deployment Automation” by Jez Humble, David Farley. Great position for all of you, who already had problems with releasing software. Have you ever got stuck with builds, which couldn't be released because all those showstopper bugs still were discovered during manual tests? How many times you heard from your developer „It works on my machine”? How many hours your team must spend in order to make and test build for the client? 2 days of whole team work isn't enough? Just read „ Continuous Delivery...” and look how release process may be repeatable, pain less, fully automated. Book is written by experienced software engineers, who gave a lot of problems from real projects and how it were improved. During reading I reminded myself I meet similar problems in my projects. What impressed me:
  • not always the modern (and often the most expensive) tools is the best option. For example: you definitely need some version control system: but for small projects git may be to complicated. Old proven SVN still is a good choice
  • do re-factor after every developed feature. Do not leave „technological dept”. Your code should be ready to developing nest features.
  • do not change everything in your release process in one moment: first automate a build, the tests, then write deployment scripts etc
  • try to keep simple solutions: if your software consist of many components, build and release its in one deployment pipeline. You will split components into different plans in your build server when it is necessary
My recommendation for: „Continuous Delivery: Reliable Software Releases through Build, Test, and Deployment Automation” - definitely read!

28 lip 2010

"undefined reference" - jak zdiagnozować takie błędy?

Jednym z bardziej irytujących błędów jakie możemy otrzymać kompilując projekt jest błąd linkowania "undefined reference".

np.:
example.cpp: undefined reference to `boost::program_options::option_description::option_description()'

Jeśli błąd dotyczy naszego kodu, to pół biedy. Wyświetlane w komunikatach nazwy coś nam mówią. Wiemy, co ostatnio modyfikowaliśmy w kodzie.
Gorzej jeżeli linker nie może znaleźć obiektu z używanej bibioteki, w dodatku jej wywołania znajdują się w innej używanej bibliotece. Wtedy to nazwy poszukiwanych przez linker symboli niewiele nam mówią.

Poniżej znajduje się kilka porad i narzędzi użytecznych podczas analizy błędów "undefined reference".

1. Należy się upewnić, że linkujemy w projekcie bibliotekę zawierającą funkcję poszukiwaną przez linker.

W powyższym przypadku musimy sprawdzić, czy wywołanie g++ (gcc) zawiera parametr:
g++ ... -lboost_program_options

Jeżeli linkujemy odpowiednią bibliotekę a mimo to nadal mamy błąd "undefined reference", to:

2. Sprawdzamy, czy w linkowanej biblioteece faktycznie znajduje się poszukiwany symbol.
readelf -s W /usr/lib/libboost_serialization.so

Powyższe wywołanie readelf listuje wszystkie symbole jakie znajdują się w dynamicznej bibliotece.

Niestety symbole listowane przez readelf są udekorowane (ang. name mangling), a linker pokazuje nazwy symboli z języka C++ bez dekoracji.

Na szczęście istnieje w Linuxie narzędzie do zamiany dekorowanej nazwy na deklarację C++: c++filt.

Możemy użyć następującego zestawu instrukcji do zamiany dekoracji na identyfikatory C++ dla wszystkich symboli w danej bibliotece:
readelf -s -W /usr/lib/libboost_program_options.so | awk '$8 != "" { print $8; }' | c++filt

Teraz możemy odszukać symbolu w bibliotece.

Jeżeli go tam znajdziemy, to zapewne g++ podczas linkowania używa tej biblioteki w innej wersji (czyli używa innego pliku niż ten, który badaliśmy za pomocą readelf). Należy sprawdzić ścieżki podawane jako parametr -L.

Jeżeli symbolu nie znajdziemy, to prawdopodobnie do linkowania użyte zostały headery w innej wersji niż linkowana biblioteka. W takim przypadku może pomóc reinstalacja biblioteki.

24 maj 2010

Logowanie do pliku za pomocą log4cpp

Już od dawna używam log4cpp. Dopóki rozwijałem soft, to starczało mi logowanie na konsolę.
Teraz przyszedł czas na wdrożenie i trzeba przerobić plik konfiguracyjny log4cpp tak, aby logował do pliku.

Zadanie na 10 minut - tak mi się wydawało...

Ustawiłem wszystko tak jak dla log4j (fragment dotyczący appendera do pliku):
log4j.appender.fileAppender=org.apache.log4j.FileAppender
log4j.appender.File=app.log
log4j.appender.fileAppender.layout=org.apache.log4j.PatternLayout
log4j.appender.fileAppender.layout.ConversionPattern=%d %m %x %n

i niby loguje się do pliku, ale jego nazwa to: 'foobar'.

Ręce opadają... żadnej dokumentacji w log4cpp do konfiguratora properties... w dodatku nie są w pełni kompatybilni z log4j.

Na szczęście log4cpp to projekt opensource. Co najlepiej zastąpi dokumentację?
Oczywiście kod źródłowy! Wystarczy poszukać ciągu 'foobar' i już wiadomo:
prawidłowa opcja dla nazwy pliku to 'fileName':

log4j.appender.fileAppender=org.apache.log4j.FileAppender
log4j.appender.fileName=app.log
log4j.appender.fileAppender.layout=org.apache.log4j.PatternLayout
log4j.appender.fileAppender.layout.ConversionPattern=%d %m %x %n

Uff! Szkoda że nie pomyślałem o kodzie źródłowym log4cpp godzinę wcześniej :-)

1 lut 2010

boost::serialization i dziwne błędy kompilacji

Błędy kompilacji kodu używającego biblioteki boost::serialization są trudne do zrozumienia.

Jednym z nich jest błąd:
/usr/include/boost/archive/detail/iserializer.hpp:570: error: no matching function for call to ‘load_wrapper(boost::archive::text_iarchive&, const std::basic_string<char, std::char_traits<char>, std::allocator<char> >&, boost::serialization::is_wrapper<std::basic_string<char, std::char_traits<char>, std::allocator<char> > >)’

Przyczyna jest prozaiczna: serializowalne pola nie mogą być const.
Jest to oczywiste, gdy wiemy że serializacja musi utworzyć "pusty" obiekt i potem wypełnić wartościami jego pola.

Dlatego też serializowalna klasa musi mieć:
  • konstruktor bezparametrowy
  • pola modyfikowalne (bez const)

Oczywiście z komunikatu o błędzie nie możemy tego wywnioskować... takie to już są uroki bibliotek wykorzystujących template-y...

27 sty 2010

Deklaracje zapowiadające w celu przyspieszenia kompilacji projektu

W projektach pisanych w C++ lubię korzystać z biblioteki boost.
Niestety ma ona jedną uciążliwą wadę: kod wykorzystujący boosta strasznie wolno się kompiluje. Przyczyną tego jest fakt, iż boost w dużym stopniu wykorzystuje template-y i to w bardzo zaawansowany sposób. Metaprogramy to dla kompilatora c++ nie lada wyzwanie!

W projektach składających się z kilkudziesięciu plikow źródłowych czas kompilacji całości przekracza kilka minut! Nawet przy kompilacji przyrostowej z użyciem make, po zmianie jednego z headerów w projekcie kompilacja może trwać powyżej minuty!

Jedną z technik przyspieszających kompilację jest unikanie dodawania #include do własnych headerów. Jeżeli tylko można należy wstępnie zadeklarować klasę w headerze, a właściwy #include dodać dopiero do pliku cpp.

Poniżej przykład:
Piszemy klasę Database, w której chcemy skorzystać z klasy XmlDocument. Ściślej: obiekty klasy XmlDocument będą przekazywane jako parametr w metodach klasy Database.

Jak zatem powinniśmy napisać header klasy Database.hpp:
class XmlDocument;

class Database
{
    void init( XmlDocument &document );
}

A co jeśli do obiektu XmlDocument chcemy mieć dostęp nie przez referencję, ale przez smart pointer (boost::shared_ptr)?

Żeby zdefiniować smart pointer wcale nie jest potrzebny header klasy. Także tutaj wystarczy jej deklaracja zapowiadająca:
class XmlDocument;
typedef boost::shared_ptr<XmlDocument> XmlDocumentPtr;

class Database
{
    void init( XmlDocumentPtr document );
}


Dodatkowa wskazówka: aby uniknąć duplikowania kodu, dla klas z których chcę korzystać poprzez smart pointer, tworzę osobny - dodatowy header KlasaXXXPtr.hpp

Dla przykładu header taki dla klasy XmlDocument będzie wyglądał tak:
#ifndef XMLDOCUMENTPTR_HPP_
#define XMLDOCUMENTPTR_HPP_

#ifndef XMLDOCUMENT_H_
class XmlDocument;
#endif // XMLDOCUMENT_H_

#include <boost/shared_ptr.hpp>
typedef boost::shared_ptr<XmlDocument> XmlDocumentPtr;

#endif /* XMLDOCUMENTPTR_HPP_ */
Wszędzie, gdzie chcę zadeklarować smart pointera na XmlDocument po prostu wpisuję #include "XmlDocumentPtr.hpp", natomiast w pliku cpp (np. Database.cpp) muszę jeszcze dodać #include "XmlDocument.hpp".

22 sty 2010

Deklaracje zapowiadające obiektów z biblioteki xerces-c++

Staram się ograniczać liczbę #include-ów w moich headerach.

Jeśli to możliwe wstępnie deklaruję wykorzystywaną klasę, a właściwy #include tej klasy ląduje w pliku cpp. Dzięki temu projekt znacznie szybciej się kompiluje.

Przykład: w klasie SslSession chcę korzystać z obiektów Database.
SslSession.hpp wygląda tak:
class Database;
class SslSession
{
    Database &m_db;
};
SslSession.cpp wygląda tak:
#include "Database.hpp"
Niestety wstępna deklaracja obiektów z biblioteki xerces-c++ w powyższy sposób daje następujący błąd kompilacji:
namespace xercesc
{
    class xercesc::DOMNode;
}

/usr/include/xercesc/util/XercesDefs.hpp:110: error: declaration of namespace ‘xercesc’ conflicts with
../src/modules/../lib/XmlDocumentPtr.hpp:18: error: previous declaration of namespace ‘xercesc’ here
Okazuje się, że xerces-c ma mechanizm parametrycznego włączania/wyłączania namespace (w zależności od wersji xerces-c i użytego kompilatora), który nie współgra dobrze z klasycznymi deklaracjami zapowiadającymi.

Na szczęście chłopaki z apacha zapewnili inny mechanizm pozwalający na deklararowanie ich obiektów:
#include <xercesc/util/XercesDefs.hpp>

XERCES_CPP_NAMESPACE_BEGIN
class DOMNode;
XERCES_CPP_NAMESPACE_END
Teraz wszystko ładnie się kompiluje!

18 gru 2009

Zwracanie rekordu z procedury składowanej w PostgreSQL

Jeśli z procedury/funkcji składowanej w bazie danych PostgreSQL chcemy zwrócić kilka wartości, to możemy zadeklarowąć ją jako zwracającą typ RECORD, ad-hoc utworzyć rekord z wartościami i zwrócić go.
Nie jest to trudne. Niestety "dokumentaliści" PostgreSQL zapomnieli pokazać jak taką funkcję wywołać.
Poniżej przykład znaleziony na jednym z forów Posrgresa:

CREATE FUNCTION xyz() RETURNS record AS
$$
declare
 abc RECORD;
begin
 abc := (1, 2);
 return abc;
end;
$$
language plpgsql;

select a, b from xyz() as (a int, b int);


Czyli wołamy SELECTA na tym, co zwróci nasza funkcja, ale jeszcze musimy zamapować wartości z rekordu na kolumny klauzulą AS.