Mardy (Posts about mappero-geotagger)http://mardy.it/categories/mappero-geotagger.atom2024-02-02T20:11:02ZAlberto MardeganNikolaNew website for Mappero Geotagger, and cross-compiling stuffhttp://mardy.it/blog/2020/04/new-website-for-mappero-geotagger.html2020-04-12T12:20:55+03:002020-04-12T12:20:55+03:00Alberto Mardegan<p>Mappero Geotagger has now moved from <a href="http://www.mardy.it/mappero-geotagger/">its previous page from this
site</a> to a <a href="http://mappero.mardy.it">new, separate
website</a> built with the awesome <a href="http://getnikola.com">Nikola static website
generator</a>.</p>
<p>The main reason for this change is that I didn't have an online space where to
host the application binaries, and I wanted to experiment with a different
selling method. Now, downloads are (poorly) hidden behind a payment page,
whereas in multiple places of the website I also mention that I can provide
the application for free to whomever asks for it. While it might seem weird at
first, I do honestly believe that this will not stop people from buying it:
first of all, many people just think it's fair to pay for a software
applications, and secondly, for some people writing an e-mail and establishing
a personal contact with a stranger is actually harder than paying a small
amount of money. And in all sincerity, the majority of the income I've had so
far for Mappero Geotagger came from donations, rather than purchases; so, not
much to lose here.</p>
<h3>QBS and MXE</h3>
<p>Anyway, since this is primarily a technical blog, I want to share my
experiences with cross-building from Linux to Windows. As you might remember,
<a href="http://mardy.it/blog/2019/07/qbs-and-code-coverage-reports.html">some time ago I switched the build system of Mappero from qmake to
QBS</a>, and I haven't regretted it at
all. I've managed to build the application in Linux (of course), macOS, as a
Debian package on the <a href="https://launchpad.net/~mardy/+archive/ubuntu/mappero">Ubuntu PPA
builders</a>, on <a href="https://ci.appveyor.com/project/mardy/mappero">Windows
with AppVeyor</a> and, last but not
least, on Linux for Windows using the mingw setup provided by the <a href="http://mxe.cc">MXE
project</a>.</p>
<p>QBS worked surprisingly well also in this case, though I had to fight with a
small bug on the toolchain detection, which is hopefully going to be <a href="https://codereview.qt-project.org/c/qbs/qbs/+/296870">fixed
soon</a>. For the few of you
who are interested in achieving something similar, here's the steps I ran to
configure QBS for mingw:</p>
<div class="code"><pre class="code literal-block"><span class="w"> </span><span class="nv">MXE_BASE</span><span class="o">=</span><path-to-mxe>
<span class="w"> </span><span class="nv">MXE_TARGET</span><span class="o">=</span>x86_64-w64-mingw32.shared<span class="w"> </span><span class="c1"># 32 bit or static targets are also available</span>
<span class="w"> </span><span class="nv">MXE_PROFILE</span><span class="o">=</span><span class="s2">"mxe"</span>
<span class="w"> </span><span class="nv">QT_PROFILE</span><span class="o">=</span><span class="s2">"</span><span class="si">${</span><span class="nv">MXE_PROFILE</span><span class="si">}</span><span class="s2">-qt"</span>
<span class="w"> </span>qbs<span class="w"> </span>setup-toolchains<span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">MXE_BASE</span><span class="si">}</span><span class="s2">/usr/bin/</span><span class="si">${</span><span class="nv">MXE_TARGET</span><span class="si">}</span><span class="s2">-g++"</span><span class="w"> </span><span class="nv">$MXE_PROFILE</span>
<span class="w"> </span>qbs<span class="w"> </span>config<span class="w"> </span>profiles.<span class="nv">$MXE_PROFILE</span>.cpp.toolchainPrefix<span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">MXE_TARGET</span><span class="si">}</span><span class="s2">-"</span><span class="w"> </span><span class="c1"># temporary workaround</span>
<span class="w"> </span>qbs<span class="w"> </span>setup-qt<span class="w"> </span><span class="s2">"</span><span class="nv">$MXE_BASE</span><span class="s2">/usr/</span><span class="nv">$MXE_TARGET</span><span class="s2">/qt5/bin/qmake"</span><span class="w"> </span><span class="si">${</span><span class="nv">QT_PROFILE</span><span class="si">}</span>
<span class="w"> </span>qbs<span class="w"> </span>config<span class="w"> </span>profiles.<span class="si">${</span><span class="nv">QT_PROFILE</span><span class="si">}</span>.baseProfile<span class="w"> </span><span class="nv">$MXE_PROFILE</span>
</pre></div>
<p>Sorry for using that many environment variables ☺. After qbs is configured,
it's just a matter of running </p>
<div class="code"><pre class="code literal-block"><span class="w"> </span>qbs<span class="w"> </span>profile:<span class="nv">$QT_PROFILE</span>
</pre></div>
<p>to build the application. You will get a nice window binary and, once you
collect all the needed library dependencies, you'll be able to run it on
Windows. Or WINE ☺.</p>
<p>As part of this effort, I also had to build <a href="http://libraw.org">libraw</a>, so I
didn't miss the occasion to <a href="https://github.com/mxe/mxe/pull/2481">contribute its recipe to
MXE</a>. I'm also trying to get a change
accepted, that would make MXE <a href="https://github.com/mxe/mxe/pull/2480">support the dynamic OpenGL
selection</a> available since Qt 5.4.</p><p>Mappero Geotagger has now moved from <a href="http://www.mardy.it/mappero-geotagger/">its previous page from this
site</a> to a <a href="http://mappero.mardy.it">new, separate
website</a> built with the awesome <a href="http://getnikola.com">Nikola static website
generator</a>.</p>
<p>The main reason for this change is that I didn't have an online space where to
host the application binaries, and I wanted to experiment with a different
selling method. Now, downloads are (poorly) hidden behind a payment page,
whereas in multiple places of the website I also mention that I can provide
the application for free to whomever asks for it. While it might seem weird at
first, I do honestly believe that this will not stop people from buying it:
first of all, many people just think it's fair to pay for a software
applications, and secondly, for some people writing an e-mail and establishing
a personal contact with a stranger is actually harder than paying a small
amount of money. And in all sincerity, the majority of the income I've had so
far for Mappero Geotagger came from donations, rather than purchases; so, not
much to lose here.</p>
<h3>QBS and MXE</h3>
<p>Anyway, since this is primarily a technical blog, I want to share my
experiences with cross-building from Linux to Windows. As you might remember,
<a href="http://mardy.it/blog/2019/07/qbs-and-code-coverage-reports.html">some time ago I switched the build system of Mappero from qmake to
QBS</a>, and I haven't regretted it at
all. I've managed to build the application in Linux (of course), macOS, as a
Debian package on the <a href="https://launchpad.net/~mardy/+archive/ubuntu/mappero">Ubuntu PPA
builders</a>, on <a href="https://ci.appveyor.com/project/mardy/mappero">Windows
with AppVeyor</a> and, last but not
least, on Linux for Windows using the mingw setup provided by the <a href="http://mxe.cc">MXE
project</a>.</p>
<p>QBS worked surprisingly well also in this case, though I had to fight with a
small bug on the toolchain detection, which is hopefully going to be <a href="https://codereview.qt-project.org/c/qbs/qbs/+/296870">fixed
soon</a>. For the few of you
who are interested in achieving something similar, here's the steps I ran to
configure QBS for mingw:</p>
<div class="code"><pre class="code literal-block"><span class="w"> </span><span class="nv">MXE_BASE</span><span class="o">=</span><path-to-mxe>
<span class="w"> </span><span class="nv">MXE_TARGET</span><span class="o">=</span>x86_64-w64-mingw32.shared<span class="w"> </span><span class="c1"># 32 bit or static targets are also available</span>
<span class="w"> </span><span class="nv">MXE_PROFILE</span><span class="o">=</span><span class="s2">"mxe"</span>
<span class="w"> </span><span class="nv">QT_PROFILE</span><span class="o">=</span><span class="s2">"</span><span class="si">${</span><span class="nv">MXE_PROFILE</span><span class="si">}</span><span class="s2">-qt"</span>
<span class="w"> </span>qbs<span class="w"> </span>setup-toolchains<span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">MXE_BASE</span><span class="si">}</span><span class="s2">/usr/bin/</span><span class="si">${</span><span class="nv">MXE_TARGET</span><span class="si">}</span><span class="s2">-g++"</span><span class="w"> </span><span class="nv">$MXE_PROFILE</span>
<span class="w"> </span>qbs<span class="w"> </span>config<span class="w"> </span>profiles.<span class="nv">$MXE_PROFILE</span>.cpp.toolchainPrefix<span class="w"> </span><span class="s2">"</span><span class="si">${</span><span class="nv">MXE_TARGET</span><span class="si">}</span><span class="s2">-"</span><span class="w"> </span><span class="c1"># temporary workaround</span>
<span class="w"> </span>qbs<span class="w"> </span>setup-qt<span class="w"> </span><span class="s2">"</span><span class="nv">$MXE_BASE</span><span class="s2">/usr/</span><span class="nv">$MXE_TARGET</span><span class="s2">/qt5/bin/qmake"</span><span class="w"> </span><span class="si">${</span><span class="nv">QT_PROFILE</span><span class="si">}</span>
<span class="w"> </span>qbs<span class="w"> </span>config<span class="w"> </span>profiles.<span class="si">${</span><span class="nv">QT_PROFILE</span><span class="si">}</span>.baseProfile<span class="w"> </span><span class="nv">$MXE_PROFILE</span>
</pre></div>
<p>Sorry for using that many environment variables ☺. After qbs is configured,
it's just a matter of running </p>
<div class="code"><pre class="code literal-block"><span class="w"> </span>qbs<span class="w"> </span>profile:<span class="nv">$QT_PROFILE</span>
</pre></div>
<p>to build the application. You will get a nice window binary and, once you
collect all the needed library dependencies, you'll be able to run it on
Windows. Or WINE ☺.</p>
<p>As part of this effort, I also had to build <a href="http://libraw.org">libraw</a>, so I
didn't miss the occasion to <a href="https://github.com/mxe/mxe/pull/2481">contribute its recipe to
MXE</a>. I'm also trying to get a change
accepted, that would make MXE <a href="https://github.com/mxe/mxe/pull/2480">support the dynamic OpenGL
selection</a> available since Qt 5.4.</p>Qbs and code coverage reportshttp://mardy.it/blog/2019/07/qbs-and-code-coverage-reports.html2019-07-01T16:42:20+03:002019-07-01T16:42:20+03:00Alberto Mardegan<p>You know that I'm not an <em>early adopter</em>. That's why it was only a couple of
weeks ago when I decided to give <a href="https://doc.qt.io/qbs/">Qbs</a> a try, by using
the good old <a href="https://www.mardy.it/mappero/">Mappero</a> (and its spin-off,
<a href="https://www.mardy.it/mappero-geotagger/">Mappero Geotagger</a>) as a test bench.
Yes, I know that the Qt company is not going to maintain Qbs anymore in the
future, but the little I knew about Qbs was enough to convince me that it's a
project worth supporting. So, better late than never -- and hopefully the
community (me included) will do a good job in keeping Qbs thriving.</p>
<p>Having Mappero build with Qbs was the simplest thing ever. The only issue I met
was in building the unit tests, because I'm used to set the <code>rpath</code> on test
executables in order to make it easy to run them uninstalled, and with <code>qmake</code>
I achieved that with this:</p>
<div class="code"><pre class="code literal-block"><span class="nv">QMAKE_RPATHDIR</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$$</span><span class="o">{</span>QMAKE_LIBDIR<span class="o">}</span>
</pre></div>
<p>In turns out that with Qbs you can do it in almost the same way, but for some
reason I couldn't figure it out and I even <a href="https://bugreports.qt.io/browse/QBS-1455">reported a
bug</a> to which I got some nice
suggestions, before eventually settling on this:</p>
<div class="code"><pre class="code literal-block"><span class="kr">import</span> <span class="nx">qbs</span> <span class="mf">1.0</span>
<span class="nx">Test</span> <span class="p">{</span>
<span class="k">name:</span> <span class="s2">"path-test"</span>
<span class="k">files:</span> <span class="p">[</span>
<span class="s2">"path-test.cpp"</span><span class="p">,</span>
<span class="s2">"path-test.h"</span><span class="p">,</span>
<span class="s2">"paths.qrc"</span><span class="p">,</span>
<span class="p">]</span>
<span class="nx">Depends</span> <span class="p">{</span> <span class="k">name:</span> <span class="s2">"Mappero"</span> <span class="p">}</span>
<span class="k">cpp.rpaths:</span> <span class="nx">cpp</span><span class="p">.</span><span class="nx">libraryPaths</span> <span class="c1">// <-- this does the trick!</span>
<span class="p">}</span>
</pre></div>
<p>It's surprisingly similar to how it's done in qmake, so it's not clear even to
me why I didn't guess that immediately. Anyway, that was literally my only
problem, and you can see the whole set of Qbs files I wrote by having a look at
<a href="https://gitlab.com/mardy/mappero/commit/2a19b59f018cd0517d37eadd24d1aa1780140e4b">this
commit</a>.</p>
<p>Given how easy the migration was, I thought I should also try to add a code
coverage report; that's not something I had in my qmake build either, but it's
something I really want to have in all my newer projects.</p>
<h3>Teaching Qbs to make a code coverage report</h3>
<p>Unfortunately, my search for examples on how to have Qbs prepare a coverage
report was mostly insuccessful, but thanks to some amazing help from Christian
in the #qbs IRC channel, this was not hard to achieve. So, I hope to be of some
help myself too, by sharing how this works.</p>
<p>First of all, it must be said that Qbs doesn't know anything about code
coverage, at all. However, it's possible (and often easy) to extend Qbs by
adding your own <code>Product</code> with its own set of build rules, so here's the
<code>CoverageReport</code> item for Mappero (though, it should be general enough to be
reusable in your own project):</p>
<div class="code"><pre class="code literal-block"><span class="kr">import</span> <span class="nx">qbs</span>
<span class="nx">Product</span> <span class="p">{</span>
<span class="k">name:</span> <span class="s2">"coverage"</span>
<span class="nx">property</span> <span class="nx">string</span> <span class="k">outputDirectory:</span> <span class="s2">"coverage-html"</span>
<span class="nx">property</span> <span class="nx">stringList</span> <span class="k">extractPatterns:</span> <span class="p">[]</span>
<span class="k">builtByDefault:</span> <span class="kc">false</span>
<span class="k">files:</span> <span class="p">[</span><span class="s2">"**"</span><span class="p">]</span>
<span class="k">type:</span> <span class="p">[</span><span class="s2">"coverage.html"</span><span class="p">]</span>
<span class="nx">Depends</span> <span class="p">{</span> <span class="k">productTypes:</span> <span class="p">[</span><span class="s2">"autotest-result"</span><span class="p">]</span> <span class="p">}</span>
<span class="nx">Rule</span> <span class="p">{</span>
<span class="k">multiplex:</span> <span class="kc">true</span>
<span class="k">explicitlyDependsOnFromDependencies:</span> <span class="p">[</span><span class="s2">"autotest-result"</span><span class="p">]</span>
<span class="k">outputFileTags:</span> <span class="s2">"coverage.html"</span>
<span class="k">requiresInputs:</span> <span class="kc">false</span>
<span class="k">prepare:</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">commands</span> <span class="o">=</span> <span class="p">[]</span>
<span class="kd">var</span> <span class="nx">captureCmd</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Command</span><span class="p">(</span><span class="s2">"lcov"</span><span class="p">,</span> <span class="p">[</span>
<span class="s2">"--directory"</span><span class="p">,</span> <span class="nx">project</span><span class="p">.</span><span class="nx">sourceDirectory</span><span class="p">,</span>
<span class="s2">"--capture"</span><span class="p">,</span>
<span class="s2">"--output-file"</span><span class="p">,</span> <span class="s2">"coverage.info"</span><span class="p">,</span>
<span class="s2">"--no-checksum"</span><span class="p">,</span>
<span class="s2">"--compat-libtool"</span><span class="p">,</span>
<span class="p">]);</span>
<span class="nx">captureCmd</span><span class="p">.</span><span class="nx">description</span> <span class="o">=</span> <span class="s2">"Collecting coverage data"</span><span class="p">;</span>
<span class="nx">captureCmd</span><span class="p">.</span><span class="nx">highlight</span> <span class="o">=</span> <span class="s2">"coverage"</span><span class="p">;</span>
<span class="nx">captureCmd</span><span class="p">.</span><span class="nx">silent</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
<span class="nx">commands</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">captureCmd</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">extractArgs</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o"><</span> <span class="nx">product</span><span class="p">.</span><span class="nx">extractPatterns</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">extractArgs</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="s2">"--extract"</span><span class="p">);</span>
<span class="nx">extractArgs</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="s2">"coverage.info"</span><span class="p">);</span>
<span class="nx">extractArgs</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">product</span><span class="p">.</span><span class="nx">extractPatterns</span><span class="p">[</span><span class="nx">i</span><span class="p">]);</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">product</span><span class="p">.</span><span class="nx">extractPatterns</span><span class="p">.</span><span class="nx">length</span> <span class="o">></span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">extractArgs</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="s2">"-o"</span><span class="p">);</span>
<span class="nx">extractArgs</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="s2">"coverage.info"</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">extractCmd</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Command</span><span class="p">(</span><span class="s2">"lcov"</span><span class="p">,</span> <span class="nx">extractArgs</span><span class="p">);</span>
<span class="nx">extractCmd</span><span class="p">.</span><span class="nx">description</span> <span class="o">=</span> <span class="s2">"Extracting coverage data"</span><span class="p">;</span>
<span class="nx">extractCmd</span><span class="p">.</span><span class="nx">highlight</span> <span class="o">=</span> <span class="s2">"coverage"</span><span class="p">;</span>
<span class="nx">extractCmd</span><span class="p">.</span><span class="nx">silent</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
<span class="nx">commands</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">extractCmd</span><span class="p">);</span>
<span class="p">}</span>
<span class="kd">var</span> <span class="nx">filterCmd</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Command</span><span class="p">(</span><span class="s2">"lcov"</span><span class="p">,</span> <span class="p">[</span>
<span class="s2">"--remove"</span><span class="p">,</span> <span class="s2">"coverage.info"</span><span class="p">,</span> <span class="s1">'moc_*.cpp'</span><span class="p">,</span>
<span class="s2">"--remove"</span><span class="p">,</span> <span class="s2">"coverage.info"</span><span class="p">,</span> <span class="s1">'qrc_*.cpp'</span><span class="p">,</span>
<span class="s2">"--remove"</span><span class="p">,</span> <span class="s2">"coverage.info"</span><span class="p">,</span> <span class="s1">'*/tests/*'</span><span class="p">,</span>
<span class="s2">"-o"</span><span class="p">,</span> <span class="s2">"coverage.info"</span><span class="p">,</span>
<span class="p">]);</span>
<span class="nx">filterCmd</span><span class="p">.</span><span class="nx">description</span> <span class="o">=</span> <span class="s2">"Filtering coverage data"</span><span class="p">;</span>
<span class="nx">filterCmd</span><span class="p">.</span><span class="nx">highlight</span> <span class="o">=</span> <span class="s2">"coverage"</span><span class="p">;</span>
<span class="nx">filterCmd</span><span class="p">.</span><span class="nx">silent</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
<span class="nx">commands</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">filterCmd</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">genhtmlCmd</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Command</span><span class="p">(</span><span class="s2">"genhtml"</span><span class="p">,</span> <span class="p">[</span>
<span class="s2">"--prefix"</span><span class="p">,</span> <span class="nx">project</span><span class="p">.</span><span class="nx">sourceDirectory</span><span class="p">,</span>
<span class="s2">"--output-directory"</span><span class="p">,</span> <span class="nx">product</span><span class="p">.</span><span class="nx">outputDirectory</span><span class="p">,</span>
<span class="s2">"--title"</span><span class="p">,</span> <span class="s2">"Code coverage"</span><span class="p">,</span>
<span class="s2">"--legend"</span><span class="p">,</span>
<span class="s2">"--show-details"</span><span class="p">,</span>
<span class="s2">"coverage.info"</span><span class="p">,</span>
<span class="p">]);</span>
<span class="nx">genhtmlCmd</span><span class="p">.</span><span class="nx">description</span> <span class="o">=</span> <span class="s2">"Generate HTML coverage report"</span><span class="p">;</span>
<span class="nx">genhtmlCmd</span><span class="p">.</span><span class="nx">highlight</span> <span class="o">=</span> <span class="s2">"coverage"</span><span class="p">;</span>
<span class="nx">genhtmlCmd</span><span class="p">.</span><span class="nx">silent</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
<span class="nx">commands</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">genhtmlCmd</span><span class="p">);</span>
<span class="k">return</span> <span class="nx">commands</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>The most important thing here are the references to the <code>autotest-result</code> tag:
this is the tag used by the <code>AutotestRunner</code> Qbs item, which is responsible for
running the unit tests. Referencing its product's tag in the <code>Depends</code> item and
in the <code>explicitlyDependsOnFromDependencies</code> properties ensures that "building"
our product will cause the unit tests to run. Other needed bits are the
<code>requiresInputs: false</code> property, which means that our rule doesn't have any
required inputs, and the <code>builtByDefault: false</code> property, which says that our
coverage report should not be generated when just typing <code>qbs</code>. Instead, to run
the tests and get the code coverage report one will have to request it
explicitly, by typing</p>
<div class="code"><pre class="code literal-block">qbs -p coverage
</pre></div>
<p>The <code>prepare</code> property of the <code>Rule</code> is where the commands to generate the code
coverage report are defined. Here we can use the <code>Command</code> item to invoke
external programs, and we return a list of such items, so that the commands
will be executed in sequence. Note that here I'm using <code>lcov</code> and expecting to
find the coverage data produced by <code>gcov</code>, so this is probably not portable
outside of Linux/gcc.</p>
<p>Using the <code>CoverageReport</code> item is quite easy: you just need to declare it, and
specify which paths contain the coverage data that you are interested in
(otherwise, lcov will collect data from all object files that it find under the
build directory, which might not be what you desire):</p>
<div class="code"><pre class="code literal-block"> <span class="nx">CoverageReport</span> <span class="p">{</span>
<span class="k">condition:</span> <span class="nx">project</span><span class="p">.</span><span class="nx">enableCoverage</span>
<span class="k">extractPatterns:</span> <span class="p">[</span> <span class="s1">'*/src/*.cpp'</span><span class="p">,</span> <span class="s1">'*/lib/*.cpp'</span> <span class="p">]</span>
<span class="p">}</span>
</pre></div>
<p>There's little more than that to be done. Of course, you need to find a way to
pass the <code>--coverage</code> option to gcc when building your products, and for this I
created a small <code>buildconfig</code> module in
<code>qbs/modules/buildconfig/BuildConfig.qbs</code> which I depend on in all products
which I wish to build with coverage enabled:</p>
<div class="code"><pre class="code literal-block"><span class="kr">import</span> <span class="nx">qbs</span>
<span class="nx">Module</span> <span class="p">{</span>
<span class="k">cpp.cxxFlags:</span> <span class="nx">project</span><span class="p">.</span><span class="nx">enableCoverage</span> <span class="o">?</span> <span class="p">[</span><span class="s2">"--coverage"</span><span class="p">]</span> <span class="o">:</span> <span class="kc">undefined</span>
<span class="k">cpp.dynamicLibraries:</span> <span class="nx">project</span><span class="p">.</span><span class="nx">enableCoverage</span> <span class="o">?</span> <span class="p">[</span><span class="s2">"gcov"</span><span class="p">]</span> <span class="o">:</span> <span class="kc">undefined</span>
<span class="nx">Depends</span> <span class="p">{</span> <span class="k">name:</span> <span class="s2">"cpp"</span> <span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>If all this looks scary, you should probably have a look at the diff which
shows <a href="https://gitlab.com/mardy/mappero/commit/ee04f6a453a935db653095c45bfe57af6a0ce508">how I added code coverage reporting to
qbs</a>:
hopefully you'll find that it's not that complex, after all.</p>
<p>I hope that Qbs users will find this interesting, and possibly improving my
setup. Ideally we should try to get something like this part of Qbs itself, but
portability outside of Linux / gcc is going to be an issue.</p><p>You know that I'm not an <em>early adopter</em>. That's why it was only a couple of
weeks ago when I decided to give <a href="https://doc.qt.io/qbs/">Qbs</a> a try, by using
the good old <a href="https://www.mardy.it/mappero/">Mappero</a> (and its spin-off,
<a href="https://www.mardy.it/mappero-geotagger/">Mappero Geotagger</a>) as a test bench.
Yes, I know that the Qt company is not going to maintain Qbs anymore in the
future, but the little I knew about Qbs was enough to convince me that it's a
project worth supporting. So, better late than never -- and hopefully the
community (me included) will do a good job in keeping Qbs thriving.</p>
<p>Having Mappero build with Qbs was the simplest thing ever. The only issue I met
was in building the unit tests, because I'm used to set the <code>rpath</code> on test
executables in order to make it easy to run them uninstalled, and with <code>qmake</code>
I achieved that with this:</p>
<div class="code"><pre class="code literal-block"><span class="nv">QMAKE_RPATHDIR</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">$$</span><span class="o">{</span>QMAKE_LIBDIR<span class="o">}</span>
</pre></div>
<p>In turns out that with Qbs you can do it in almost the same way, but for some
reason I couldn't figure it out and I even <a href="https://bugreports.qt.io/browse/QBS-1455">reported a
bug</a> to which I got some nice
suggestions, before eventually settling on this:</p>
<div class="code"><pre class="code literal-block"><span class="kr">import</span> <span class="nx">qbs</span> <span class="mf">1.0</span>
<span class="nx">Test</span> <span class="p">{</span>
<span class="k">name:</span> <span class="s2">"path-test"</span>
<span class="k">files:</span> <span class="p">[</span>
<span class="s2">"path-test.cpp"</span><span class="p">,</span>
<span class="s2">"path-test.h"</span><span class="p">,</span>
<span class="s2">"paths.qrc"</span><span class="p">,</span>
<span class="p">]</span>
<span class="nx">Depends</span> <span class="p">{</span> <span class="k">name:</span> <span class="s2">"Mappero"</span> <span class="p">}</span>
<span class="k">cpp.rpaths:</span> <span class="nx">cpp</span><span class="p">.</span><span class="nx">libraryPaths</span> <span class="c1">// <-- this does the trick!</span>
<span class="p">}</span>
</pre></div>
<p>It's surprisingly similar to how it's done in qmake, so it's not clear even to
me why I didn't guess that immediately. Anyway, that was literally my only
problem, and you can see the whole set of Qbs files I wrote by having a look at
<a href="https://gitlab.com/mardy/mappero/commit/2a19b59f018cd0517d37eadd24d1aa1780140e4b">this
commit</a>.</p>
<p>Given how easy the migration was, I thought I should also try to add a code
coverage report; that's not something I had in my qmake build either, but it's
something I really want to have in all my newer projects.</p>
<h3>Teaching Qbs to make a code coverage report</h3>
<p>Unfortunately, my search for examples on how to have Qbs prepare a coverage
report was mostly insuccessful, but thanks to some amazing help from Christian
in the #qbs IRC channel, this was not hard to achieve. So, I hope to be of some
help myself too, by sharing how this works.</p>
<p>First of all, it must be said that Qbs doesn't know anything about code
coverage, at all. However, it's possible (and often easy) to extend Qbs by
adding your own <code>Product</code> with its own set of build rules, so here's the
<code>CoverageReport</code> item for Mappero (though, it should be general enough to be
reusable in your own project):</p>
<div class="code"><pre class="code literal-block"><span class="kr">import</span> <span class="nx">qbs</span>
<span class="nx">Product</span> <span class="p">{</span>
<span class="k">name:</span> <span class="s2">"coverage"</span>
<span class="nx">property</span> <span class="nx">string</span> <span class="k">outputDirectory:</span> <span class="s2">"coverage-html"</span>
<span class="nx">property</span> <span class="nx">stringList</span> <span class="k">extractPatterns:</span> <span class="p">[]</span>
<span class="k">builtByDefault:</span> <span class="kc">false</span>
<span class="k">files:</span> <span class="p">[</span><span class="s2">"**"</span><span class="p">]</span>
<span class="k">type:</span> <span class="p">[</span><span class="s2">"coverage.html"</span><span class="p">]</span>
<span class="nx">Depends</span> <span class="p">{</span> <span class="k">productTypes:</span> <span class="p">[</span><span class="s2">"autotest-result"</span><span class="p">]</span> <span class="p">}</span>
<span class="nx">Rule</span> <span class="p">{</span>
<span class="k">multiplex:</span> <span class="kc">true</span>
<span class="k">explicitlyDependsOnFromDependencies:</span> <span class="p">[</span><span class="s2">"autotest-result"</span><span class="p">]</span>
<span class="k">outputFileTags:</span> <span class="s2">"coverage.html"</span>
<span class="k">requiresInputs:</span> <span class="kc">false</span>
<span class="k">prepare:</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">commands</span> <span class="o">=</span> <span class="p">[]</span>
<span class="kd">var</span> <span class="nx">captureCmd</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Command</span><span class="p">(</span><span class="s2">"lcov"</span><span class="p">,</span> <span class="p">[</span>
<span class="s2">"--directory"</span><span class="p">,</span> <span class="nx">project</span><span class="p">.</span><span class="nx">sourceDirectory</span><span class="p">,</span>
<span class="s2">"--capture"</span><span class="p">,</span>
<span class="s2">"--output-file"</span><span class="p">,</span> <span class="s2">"coverage.info"</span><span class="p">,</span>
<span class="s2">"--no-checksum"</span><span class="p">,</span>
<span class="s2">"--compat-libtool"</span><span class="p">,</span>
<span class="p">]);</span>
<span class="nx">captureCmd</span><span class="p">.</span><span class="nx">description</span> <span class="o">=</span> <span class="s2">"Collecting coverage data"</span><span class="p">;</span>
<span class="nx">captureCmd</span><span class="p">.</span><span class="nx">highlight</span> <span class="o">=</span> <span class="s2">"coverage"</span><span class="p">;</span>
<span class="nx">captureCmd</span><span class="p">.</span><span class="nx">silent</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
<span class="nx">commands</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">captureCmd</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">extractArgs</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o"><</span> <span class="nx">product</span><span class="p">.</span><span class="nx">extractPatterns</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">extractArgs</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="s2">"--extract"</span><span class="p">);</span>
<span class="nx">extractArgs</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="s2">"coverage.info"</span><span class="p">);</span>
<span class="nx">extractArgs</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">product</span><span class="p">.</span><span class="nx">extractPatterns</span><span class="p">[</span><span class="nx">i</span><span class="p">]);</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">product</span><span class="p">.</span><span class="nx">extractPatterns</span><span class="p">.</span><span class="nx">length</span> <span class="o">></span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">extractArgs</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="s2">"-o"</span><span class="p">);</span>
<span class="nx">extractArgs</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="s2">"coverage.info"</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">extractCmd</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Command</span><span class="p">(</span><span class="s2">"lcov"</span><span class="p">,</span> <span class="nx">extractArgs</span><span class="p">);</span>
<span class="nx">extractCmd</span><span class="p">.</span><span class="nx">description</span> <span class="o">=</span> <span class="s2">"Extracting coverage data"</span><span class="p">;</span>
<span class="nx">extractCmd</span><span class="p">.</span><span class="nx">highlight</span> <span class="o">=</span> <span class="s2">"coverage"</span><span class="p">;</span>
<span class="nx">extractCmd</span><span class="p">.</span><span class="nx">silent</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
<span class="nx">commands</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">extractCmd</span><span class="p">);</span>
<span class="p">}</span>
<span class="kd">var</span> <span class="nx">filterCmd</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Command</span><span class="p">(</span><span class="s2">"lcov"</span><span class="p">,</span> <span class="p">[</span>
<span class="s2">"--remove"</span><span class="p">,</span> <span class="s2">"coverage.info"</span><span class="p">,</span> <span class="s1">'moc_*.cpp'</span><span class="p">,</span>
<span class="s2">"--remove"</span><span class="p">,</span> <span class="s2">"coverage.info"</span><span class="p">,</span> <span class="s1">'qrc_*.cpp'</span><span class="p">,</span>
<span class="s2">"--remove"</span><span class="p">,</span> <span class="s2">"coverage.info"</span><span class="p">,</span> <span class="s1">'*/tests/*'</span><span class="p">,</span>
<span class="s2">"-o"</span><span class="p">,</span> <span class="s2">"coverage.info"</span><span class="p">,</span>
<span class="p">]);</span>
<span class="nx">filterCmd</span><span class="p">.</span><span class="nx">description</span> <span class="o">=</span> <span class="s2">"Filtering coverage data"</span><span class="p">;</span>
<span class="nx">filterCmd</span><span class="p">.</span><span class="nx">highlight</span> <span class="o">=</span> <span class="s2">"coverage"</span><span class="p">;</span>
<span class="nx">filterCmd</span><span class="p">.</span><span class="nx">silent</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
<span class="nx">commands</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">filterCmd</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">genhtmlCmd</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Command</span><span class="p">(</span><span class="s2">"genhtml"</span><span class="p">,</span> <span class="p">[</span>
<span class="s2">"--prefix"</span><span class="p">,</span> <span class="nx">project</span><span class="p">.</span><span class="nx">sourceDirectory</span><span class="p">,</span>
<span class="s2">"--output-directory"</span><span class="p">,</span> <span class="nx">product</span><span class="p">.</span><span class="nx">outputDirectory</span><span class="p">,</span>
<span class="s2">"--title"</span><span class="p">,</span> <span class="s2">"Code coverage"</span><span class="p">,</span>
<span class="s2">"--legend"</span><span class="p">,</span>
<span class="s2">"--show-details"</span><span class="p">,</span>
<span class="s2">"coverage.info"</span><span class="p">,</span>
<span class="p">]);</span>
<span class="nx">genhtmlCmd</span><span class="p">.</span><span class="nx">description</span> <span class="o">=</span> <span class="s2">"Generate HTML coverage report"</span><span class="p">;</span>
<span class="nx">genhtmlCmd</span><span class="p">.</span><span class="nx">highlight</span> <span class="o">=</span> <span class="s2">"coverage"</span><span class="p">;</span>
<span class="nx">genhtmlCmd</span><span class="p">.</span><span class="nx">silent</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
<span class="nx">commands</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">genhtmlCmd</span><span class="p">);</span>
<span class="k">return</span> <span class="nx">commands</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>The most important thing here are the references to the <code>autotest-result</code> tag:
this is the tag used by the <code>AutotestRunner</code> Qbs item, which is responsible for
running the unit tests. Referencing its product's tag in the <code>Depends</code> item and
in the <code>explicitlyDependsOnFromDependencies</code> properties ensures that "building"
our product will cause the unit tests to run. Other needed bits are the
<code>requiresInputs: false</code> property, which means that our rule doesn't have any
required inputs, and the <code>builtByDefault: false</code> property, which says that our
coverage report should not be generated when just typing <code>qbs</code>. Instead, to run
the tests and get the code coverage report one will have to request it
explicitly, by typing</p>
<div class="code"><pre class="code literal-block">qbs -p coverage
</pre></div>
<p>The <code>prepare</code> property of the <code>Rule</code> is where the commands to generate the code
coverage report are defined. Here we can use the <code>Command</code> item to invoke
external programs, and we return a list of such items, so that the commands
will be executed in sequence. Note that here I'm using <code>lcov</code> and expecting to
find the coverage data produced by <code>gcov</code>, so this is probably not portable
outside of Linux/gcc.</p>
<p>Using the <code>CoverageReport</code> item is quite easy: you just need to declare it, and
specify which paths contain the coverage data that you are interested in
(otherwise, lcov will collect data from all object files that it find under the
build directory, which might not be what you desire):</p>
<div class="code"><pre class="code literal-block"> <span class="nx">CoverageReport</span> <span class="p">{</span>
<span class="k">condition:</span> <span class="nx">project</span><span class="p">.</span><span class="nx">enableCoverage</span>
<span class="k">extractPatterns:</span> <span class="p">[</span> <span class="s1">'*/src/*.cpp'</span><span class="p">,</span> <span class="s1">'*/lib/*.cpp'</span> <span class="p">]</span>
<span class="p">}</span>
</pre></div>
<p>There's little more than that to be done. Of course, you need to find a way to
pass the <code>--coverage</code> option to gcc when building your products, and for this I
created a small <code>buildconfig</code> module in
<code>qbs/modules/buildconfig/BuildConfig.qbs</code> which I depend on in all products
which I wish to build with coverage enabled:</p>
<div class="code"><pre class="code literal-block"><span class="kr">import</span> <span class="nx">qbs</span>
<span class="nx">Module</span> <span class="p">{</span>
<span class="k">cpp.cxxFlags:</span> <span class="nx">project</span><span class="p">.</span><span class="nx">enableCoverage</span> <span class="o">?</span> <span class="p">[</span><span class="s2">"--coverage"</span><span class="p">]</span> <span class="o">:</span> <span class="kc">undefined</span>
<span class="k">cpp.dynamicLibraries:</span> <span class="nx">project</span><span class="p">.</span><span class="nx">enableCoverage</span> <span class="o">?</span> <span class="p">[</span><span class="s2">"gcov"</span><span class="p">]</span> <span class="o">:</span> <span class="kc">undefined</span>
<span class="nx">Depends</span> <span class="p">{</span> <span class="k">name:</span> <span class="s2">"cpp"</span> <span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>If all this looks scary, you should probably have a look at the diff which
shows <a href="https://gitlab.com/mardy/mappero/commit/ee04f6a453a935db653095c45bfe57af6a0ce508">how I added code coverage reporting to
qbs</a>:
hopefully you'll find that it's not that complex, after all.</p>
<p>I hope that Qbs users will find this interesting, and possibly improving my
setup. Ideally we should try to get something like this part of Qbs itself, but
portability outside of Linux / gcc is going to be an issue.</p>Mappero: public source code, CLA, Qt5 porthttp://mardy.it/blog/2013/11/mappero-public-source-code-cla-qt5-port.html2013-11-03T15:55:00+04:002013-11-03T15:55:00+04:00Alberto Mardegan<div dir="ltr" style="text-align: left;" trbidi="on">
<a href="http://www.mardy.it/mappero">Mappero</a> has always been distributed under a GPL licence. However, since when I started selling <a href="http://www.mardy.it/mappero-geotagger">Mappero Geotagger</a> (which is built from the same source), I decided not to publish the source code in a public repository, but only to provide it to those who made an explicit request to obtain it.<br>
<br>
I spent some time reconsidering the matter, and I've finally decided to let the source code live in a <a href="https://gitlab.com/mardy/mappero">public repository</a>. I also setup a <a href="http://lists.mardy.it/listinfo.cgi/mappero-mardy.it">mailing list</a> for it. And indeed I welcome code contributions, however there's a small catch: a <a href="http://en.wikipedia.org/wiki/Contributor_License_Agreement">CLA</a>. While Mappero is distributed under the GPLv3 licence, <i>I request that all contributors send me an e-mail in which they give me the right to re-licence their contribution under any licence published by the Free Software Foundation</i>.<br>
<br>
Since I believe that the busiest time for my involvement with <a href="http://blog.mardy.it/2013/11/speculo-or-shared-memory-made-easy.html">speculo</a> has passed, I expect to be able to spend some more time developing Mappero. The qt5 port is more or less working, but most of the cool features are missing, so it's little more than a map viewer at the moment (Mappero Geotagger, however, is fully working under Qt5!).<br>
<center>
<iframe allowfullscreen="" frameborder="0" height="315" src="//www.youtube.com/embed/QeAT6LFrpp4" width="560"></iframe></center>
<br>
Here you can see Mappero running on an Ubuntu Touch powered Nexus 4. Pinch zooming and GPS are not yet working, but I promise they'll be there in less than a week. Also I found a nasty bug which can cause the application to crash when downloading map tiles, and I'll fix it ASAP (I'm mentioning it just so that I won't be flooded with identical bug reports now :-) ).</div><div dir="ltr" style="text-align: left;" trbidi="on">
<a href="http://www.mardy.it/mappero">Mappero</a> has always been distributed under a GPL licence. However, since when I started selling <a href="http://www.mardy.it/mappero-geotagger">Mappero Geotagger</a> (which is built from the same source), I decided not to publish the source code in a public repository, but only to provide it to those who made an explicit request to obtain it.<br>
<br>
I spent some time reconsidering the matter, and I've finally decided to let the source code live in a <a href="https://gitlab.com/mardy/mappero">public repository</a>. I also setup a <a href="http://lists.mardy.it/listinfo.cgi/mappero-mardy.it">mailing list</a> for it. And indeed I welcome code contributions, however there's a small catch: a <a href="http://en.wikipedia.org/wiki/Contributor_License_Agreement">CLA</a>. While Mappero is distributed under the GPLv3 licence, <i>I request that all contributors send me an e-mail in which they give me the right to re-licence their contribution under any licence published by the Free Software Foundation</i>.<br>
<br>
Since I believe that the busiest time for my involvement with <a href="http://blog.mardy.it/2013/11/speculo-or-shared-memory-made-easy.html">speculo</a> has passed, I expect to be able to spend some more time developing Mappero. The qt5 port is more or less working, but most of the cool features are missing, so it's little more than a map viewer at the moment (Mappero Geotagger, however, is fully working under Qt5!).<br>
<center>
<iframe allowfullscreen="" frameborder="0" height="315" src="//www.youtube.com/embed/QeAT6LFrpp4" width="560"></iframe></center>
<br>
Here you can see Mappero running on an Ubuntu Touch powered Nexus 4. Pinch zooming and GPS are not yet working, but I promise they'll be there in less than a week. Also I found a nasty bug which can cause the application to crash when downloading map tiles, and I'll fix it ASAP (I'm mentioning it just so that I won't be flooded with identical bug reports now :-) ).</div>Introducing Mappero Geotaggerhttp://mardy.it/blog/2012/08/introducing-mappero-geotagger.html2012-08-23T19:05:00+04:002012-08-23T19:05:00+04:00Alberto Mardegan<div style="float: right;"><a href="http://www.mardy.it/archivos/imagines/mappero-geotagger.png"><img src="http://www.mardy.it/archivos/imagines/mappero-geotagger.png" width="400px"></a></div>
<p>A few days ago I published the first version of <a href="http://www.mardy.it/mappero-geotagger">Mappero Geotagger</a>, an open source geotagging application for Linux, Mac OS X and Windows.</p>
<p><em>Geotagging</em> a file means assigning a geographic location to it, and it's an action typically performed on picture files, so that the geotagged photo can be shown on a map at the location where it was taken. Many photo-sharing websites, like for instance <a href="http://flickr.com">flickr</a> and <a href="http://picasa.google.com/">picasa</a>, will show your pictures on a map, as you can see for example <a href="http://www.flickr.com/photos/mardytardi/2915024236/in/set-72157607758948089">in this picture</a> of mine. Some services also allow you to view all photos taken in a certain area; for instance, here are some nice pictures taken in <a href="http://www.flickr.com/map?&fLat=60.3834&fLon=25.6776&zl=13">Porvoo</a>, Finland.</p>
<p>Mappero Geotagger is a simple graphical tool to geotag your photos. You can manually move pictures on the map to set or change their location (removing the geotag is also possible) or, if you happened to record your way with a GPS while you took the pictures, the program can automatically correlate the time of the photos with the time of the GPS signal, and therefore establish with a certain precision where the pictures where taken. Since the time of the GPS receiver and the time of your camera might differ (and usually they do, especially if you travel to a different time-zone and forget to update the time in one of your devices), Mappero Geotagger has some controls to allow you to smoothly adjust the time difference and provides you an immediate feedback as you'll see the images being laid out on the map along the GPS track. The video below should give you a better idea of how this works:</p>
<div style="text-align: center; margin: 0 auto;"><iframe width="560" height="315" src="http://www.youtube.com/embed/b1J84dISuNk?rel=0" frameborder="0" allowfullscreen></iframe><br><small>See Mappero Geotagger in action</small></div>
<p>I'm selling the application <a href="http://www.mardy.it/mappero-geotagger">from my website</a> for 15€, but if you hurry up and either <a href="http://twitter.com">tweet</a> or mention it in <a href="http://plus.google.com">Google+</a> you can have it for 5€ only — and <b>if you blog about it, you'll get it for free</b>. Detailed info on the promotion are <a href="http://www.mardy.it/mappero-geotagger#promotions">here</a></p>
<p>Incidentally, if you own a Nokia N9 or N900, the source code which you will get when purchasing Mappero Geotagger can also be compiled for these phones, into an application (still under heavy development, but already usable) which lets you record a GPX track usable with Mappero Geotagger.</p><div style="float: right;"><a href="http://www.mardy.it/archivos/imagines/mappero-geotagger.png"><img src="http://www.mardy.it/archivos/imagines/mappero-geotagger.png" width="400px"></a></div>
<p>A few days ago I published the first version of <a href="http://www.mardy.it/mappero-geotagger">Mappero Geotagger</a>, an open source geotagging application for Linux, Mac OS X and Windows.</p>
<p><em>Geotagging</em> a file means assigning a geographic location to it, and it's an action typically performed on picture files, so that the geotagged photo can be shown on a map at the location where it was taken. Many photo-sharing websites, like for instance <a href="http://flickr.com">flickr</a> and <a href="http://picasa.google.com/">picasa</a>, will show your pictures on a map, as you can see for example <a href="http://www.flickr.com/photos/mardytardi/2915024236/in/set-72157607758948089">in this picture</a> of mine. Some services also allow you to view all photos taken in a certain area; for instance, here are some nice pictures taken in <a href="http://www.flickr.com/map?&fLat=60.3834&fLon=25.6776&zl=13">Porvoo</a>, Finland.</p>
<p>Mappero Geotagger is a simple graphical tool to geotag your photos. You can manually move pictures on the map to set or change their location (removing the geotag is also possible) or, if you happened to record your way with a GPS while you took the pictures, the program can automatically correlate the time of the photos with the time of the GPS signal, and therefore establish with a certain precision where the pictures where taken. Since the time of the GPS receiver and the time of your camera might differ (and usually they do, especially if you travel to a different time-zone and forget to update the time in one of your devices), Mappero Geotagger has some controls to allow you to smoothly adjust the time difference and provides you an immediate feedback as you'll see the images being laid out on the map along the GPS track. The video below should give you a better idea of how this works:</p>
<div style="text-align: center; margin: 0 auto;"><iframe width="560" height="315" src="http://www.youtube.com/embed/b1J84dISuNk?rel=0" frameborder="0" allowfullscreen></iframe><br><small>See Mappero Geotagger in action</small></div>
<p>I'm selling the application <a href="http://www.mardy.it/mappero-geotagger">from my website</a> for 15€, but if you hurry up and either <a href="http://twitter.com">tweet</a> or mention it in <a href="http://plus.google.com">Google+</a> you can have it for 5€ only — and <b>if you blog about it, you'll get it for free</b>. Detailed info on the promotion are <a href="http://www.mardy.it/mappero-geotagger#promotions">here</a></p>
<p>Incidentally, if you own a Nokia N9 or N900, the source code which you will get when purchasing Mappero Geotagger can also be compiled for these phones, into an application (still under heavy development, but already usable) which lets you record a GPX track usable with Mappero Geotagger.</p>