Sunday, 20 December 2009

Using Boost Build on your own projects

While I am a fan of SCons, every now and then I like to dabble in other build systems. One that has intrigued me for some time is Boost Build (BB). You can visit the linked site to find out more about it but in a nutshell, it is a very elegant way to build C++ software.

This post will attempt to give you some steps you can use to get started using the tool on your own projects. Note that it is a bit long but if you are new to Boost Jam as I was a few weeks back, I think it might help you get started. Please feel free to ask any clarifying questions in the comments.

In the following Boost Jam is the build tool and Boost Build is the library on top of the Jam language. I use them interchangeably, and I'm sure people will give me hell for it.

Building Boost Jam


Before getting started, I suggest you build Boost Jam as follows (might as well get Boost too!). I assume you are on a Unix system because there really is no reason to use Windows anymore ;-) but you should be able to get the same results on Windows with some slight modifications.

$ wget http://downloads.sourceforge.net/project/boost/boost/1.41.0/boost_1_41_0.tar.bz2
$ tar -xjf boost_1_41_0.tar.bz2
$ pushd boost_1_41_0
$ export BOOST_ROOT=$PWD
$ pushd tools/jam/src/
$ ./build.sh
$ export PATH=$PWD/bin.macosxx86:$PATH # substitute appropriately

Now, when you type "bjam" at the command prompt, you may get the following output:

$ bjam
warning: No toolsets are configured.
warning: Configuring default toolset "gcc".
warning: If the default is wrong, your build may not work correctly.
warning: Use the "toolset=xxxxx" option to override our guess.
warning: For more configuration options, please consult
warning: http://boost.org/boost-build2/doc/html/bbv2/advanced/configuration.html

error: error: no Jamfile in current directory found, and no target references specified.

The Jam Language


One complaint about Boost Build is that we must use the Jam language. However, it's really not so bad. While I would prefer Python, the Jam language is consistent and very simple. The main things to remember (this is my mental model and may not be technically accurate):


  • Rules are the same as functions in other languages

  • Parameters to functions are separated by ":"

  • All tokens are white space separated (use quotes to embed white space)

  • Results of functions can be used by enclosing the function call in a [] pair

  • Comments start with # and go to the end of the line



Here is an extremely simple example of a rule/function (create a file called "Jamroot" in the current directory and put in the following):

rule show-list ( list-of-stuff + : sep ) #1
{
    for local l in $(list-of-stuff) #2
    {
        echo $(l) $(sep) ; #3
    }
    return "Hello, World" ;
}

echo [ show-list 1 2 3 : "|" ] ; #4


  1. This line declares a new rule called "show-list" which accepts two parameters: a list as the first parameter and a single value as the second. Note the "+" modifier on the first parameter. This indicates to the build tool that at least one parameter is expected. You can use "*" to indicate 0 or more. I believe this can also be used to indicate optional parameters

  2. This line is a for loop using a local variable. Note the variable expansion using the "$()" syntax. In this case, each iteration of the loop will expand to an element of the list in list-of-stuff.

  3. Here we call the echo rule. Note that the line is terminated by the ";" symbol. This is required!

  4. Finally, we call the new rule with a list as the first parameter and a keyword enclosed in quotes as the second parameter. We use the result of that rule and pass it to echo.


If you execute "bjam", the output looks something like:

1 |
2 |
3 |
Hello, World

Pretty boring!

Creating a new project


When Boost Jam is invoked, it looks for a file called "Jamroot" in the current directory or in one of the parents of the current directory. This is where you define project global settings. Let's do that now. Create a new file called Jamroot and include the following contents:

import toolset ;

project app
  : requirements
    <threading>multi
    <link>static
    <warnings>all
    <warnings-as-errors>on
    # Equivalent to <toolset>darwin: <architecture>x86 <toolset>darwin: <address-model>32
    [ conditional <toolset>darwin: <architecture>x86 <address-model>32 ]

  : default-build debug release
  : build-dir build
;

The requirements state that all artifacts should be built using multi-threaded libraries, built statically with all warnings and all errors. Additionally, on darwin, we only want to build 32-bit executables for the x86 architecture.

In the default build (when you type just bjam), both debug and release variants will be built and put into the build directory relative to the Jamroot.

Now, type "bjam". If you are on OSX, you may see something like the following:

$ bjam
warning: No toolsets are configured.
warning: Configuring default toolset "gcc".
warning: If the default is wrong, your build may not work correctly.
warning: Use the "toolset=xxxxx" option to override our guess.
warning: For more configuration options, please consult
warning: http://boost.org/boost-build2/doc/html/bbv2/advanced/configuration.html
...found 1 target...

The reason for this is that BB guesses the toolset but on OSX we should really be using the darwin toolset. When Boost Jam starts up, it looks at ~/user-config.jam (somewhere similar on Windows) for a user configuration file. Add one with the following contents:

# ~/user-config.jam
using darwin ;

Now when you hit bjam, you should see something like:

$ bjam
...found 1 target...

Alternatively, if you don't want to pollute your file system, you can execute:

$ bjam toolset=darwin
...found 1 target...

Adding a project target


Now we will add a simple executable that links to some Boost libraries. Create a directory named "app" in your current directory and create a file in this directory named "Jamfile" with the following contents:

# app/Jamfile
exe app : [ glob *.cpp ] ;

Additionally, create a C++ file in the app directory with a trivial main function and the ".cpp" extension. Execute bjam. Nothing changed! That's because we haven't asked BJam to build our project. Try executing "bjam app". You should see something like the following:

$ bjam toolset=darwin app
...found 20 targets...
...updating 17 targets...
common.mkdir build
common.mkdir build/app
common.mkdir build/app/darwin-4.0.1
....
darwin.compile.c++ build/app/darwin-4.0.1/debug/address-model-32/architecture-x86/link-static/threading-multi/main.o
darwin.link build/app/darwin-4.0.1/debug/address-model-32/architecture-x86/link-static/threading-multi/app
common.mkdir build/app/darwin-4.0.1/release
...
darwin.compile.c++ build/app/darwin-4.0.1/release/address-model-32/architecture-x86/link-static/threading-multi/main.o
darwin.link build/app/darwin-4.0.1/release/address-model-32/architecture-x86/link-static/threading-multi/app
...updated 17 targets...

Note that both debug and release builds were created with one invocation. To restrict to one or the other, execute "bjam variant=debug" or "bjam variant=release".

Using Boost


Remember when we downloaded Boost? Now we will use it! The mechanism for using another Boost Jam project is the "use-project" rule. Add the following to your Jamroot file:

use-project /boost : ../boost_1_41_0 ;
alias boost_thread
  : /boost/thread//boost_thread
  : <warnings-as-errors>>off # bunch of warnings

Here we told the build system where the project with the id "/boost" is located. In this case, it is ../boost_1_41_0, relative to the Jamroot file. Yours might be different. Additionally, we added an alias for the Boost thread library. The main reason for this is that a single alias reduces proliferation of any special handling needed.

If you type "bjam" now, you will not be surprised that nothing is being built. Let's fix that now. Add the following to the Jamroot file:

build-project app ;

Now, whenever you invoke bjam, the app project will always be built. Invoke bjam now. You should notice that Boost thread is not being built. Again, this is not surprising. We aren't using it anywhere! Modify app/Jamfile to look like the following and execute bjam:

exe app : [ glob *.cpp ] ..//boost_thread ;

You will notice that Boost thread is being built in the Boost directory. This is not very useful as build artifacts are spread all over your disk. (Un?)Fortunately, there is a hack to making this work. Create a new file at the level of your Jamroot named "boost-build.jam". Fill it with the following contents:

# Add --build-dir to command line so that boost Jamfiles pick it up and use this directory to build.

ARGV += --build-dir=build ;

BOOST_ROOT = vendor/boost ;
BOOST_BUILD = $(BOOST_ROOT)/tools/build/v2 ;
boost-build $(BOOST_BUILD) ;

Boost Build looks for this file when building Boost (I think) so here we add the --build-dir parameter so that when building boost, it will build to our build directory. That's a lotta building ;-)

Hit "bjam" now. You should see Boost thread being built statically in the build directory now, in both debug and release variants.

Conclusion

In this post, you learned how to build Boost Jam, a little bit about the Jam language and created a simple project utilizing the Boost libraries. Next time, I will build on this post to cover making a plugin-aware C++ application (this is really quite exciting for me!) Again, if you have any question or comments, feel free to leave them below.

Sunday, 29 November 2009

Knowing when to quit

"Crossing the Chasm" and "The Dip" are both great books which talk about a distinguishable lag between when early adopters and the bulk of your target market start paying attention to you, financially anyway.

There is a problem though: a failure and such a chasm look exactly the same for a fairly long time.

I think a second problem which I'm not sure any book really addresses is that some think they are in a dip when in fact they never entered a dip to begin with. That is why you hear things like "You can't give up now, you made $200!" because they assume that every market is infinitely sized and you've just got to keep going!

I read somewhere that the average annual sales for shareware is $400. Enough of those and the payment processor is doing alright but of course the authors are not. Classic case of middle-man makes the most money in a transaction.

So if you are one of those $400/year authors and you need to spend more than 2 hours a year on maintenance, I would really think that is a good reason to quit and do something else. You're not going to miss the $400. You never entered a dip.

There is a very popular and article titled Shareware Amateurs vs Shareware Professionals. I highly recommend a read. Almost as a case-in-point, the author of that article has quit software altogether and is a fairly successful blogger. That is knowing when to quit.

If you have to quit, which I do advocate in the right circumstances, it does not mean you are giving up. You are just going to keep trying until you hit your mark. And for heaven's sake, don't think of yourself as a failure. A failure is someone who never tries.

What do you think?

Saturday, 17 October 2009

Using Boost Function with Qt, Part 2.

[Following up this post]

Johan asks:

please, please post that follow-up. I'm new to Qt and have just run into this problem. While searching the net, I think your solution seems like the best approach!

Who am I to deny a seeker of knowledge? Having used this method in a couple of projects now, I think it is fairly sound and easy to maintain. So here goes.
The main problem with the solution outlined in the earlier post was that it could not handle multiple arguments. On that same post, Ken posted his solution apparently inspired by mine. His solution also handles multiple arguments but does it by implementing qt_metacall, i.e., mimicking moc. This is probably the most scalable solution and I'll probably give it a second look when I need to do this again. The only problem might be requiring exact matches for types but I am not sure how much of an issue this is for code I have written.

To review, the problem is that we want to use function objects (Boost Bind, Boost Function, etc) as Qt slots because sometimes it is too much work to create stateful slots.

My solution looked something like this:

struct SignalHandler0 : QObject
{
private:
Q_OBJECT
public:
SignalHandler0(QObject * parent,
boost::function const & f):
QObject(parent), // parent will delete this object when destructed
m_f(f) {}

public slots:
void
handleSignal()
{
try
{
m_f();
}
catch(...)
{
// Cannot throw exceptions from signals.
ASSERT_BUG_HERE
}
}
private:
boost::function m_f;
};

bool
connect(QObject * sender, const char * signal,
boost::function const & f)
{
return QObject::connect(sender,signal,
// Note: Not a leak as sender will delete the handler when destructed
new SignalHandler0(sender,f),SLOT(handleSignal()));
}
To extend it to a single argument, we might do:
struct SignalHandler1 : QObject
{
private:
Q_OBJECT
public:
SignalHandler1(QObject * parent,
boost::function const & f):
QObject(parent), // parent will delete this object when destructed
m_f(f) {}

public slots:
void
handleSignal(QString const & a)
{
try
{
m_f(a);
}
catch(...)
{
// Cannot throw exceptions from signals.
ASSERT_BUG_HERE
}
}
private:
boost::function m_f;
};

That is, create a new class with the right signature. So you might think: well that screams for class templates! Unfortunately, Qt does not support class templates. Doh.

So basically, we write it out:

struct SignalHandler : public QObject
{
template
SignalHandler(QObject * parent,boost::function f):
QObject(parent),
m_handler(new SignalHandlerImpl(f))
{}

struct SignalHandlerBase
{
virtual ~SignalHandlerBase();
}

template
struct SignalHandlerImpl
{
SignalHandlerImpl(boost::function f):m_f(f){}
boost::function f;
}
public slots:
void handleSignal(void);
void handleSignal(QString const &);
...

private:
shared_ptr m_handler;
};

void
SignalHandler::handleSignal(void)
{
typename SignalHandlerImpl type;
type* handler = dynamic_cast;(m_handler.get());
ASSERT(handler);
handler->f();
}

void
SignalHandler::handleSignal(QString const & a)
{
typename SignalHandlerImpl type;
type* handler = dynamic_cast(m_handler.get());
ASSERT(handler);
handler->f(a);
}

You can use macros to generate these handlers. You will also need overloaded connect() functions:

template
QObject*
connect(QObject * sender,
const char * signal,
boost::function slot);

...
template<>
QObject*
connect(QObject * sender,
const char * signal,
boost::function slot);

template<>
QObject*
connect(QObject * sender,
const char * signal,
boost::function slot);
Again, you can use macros.

I prefer Ken's solution from a technical standpoint. It is a good evolution. The main advantage of his solution is that the amount of code you need to add scales linearly with the number of arguments you need to handle whereas with my solution, they scale with the number of signals and that there are no macros.

The main advantage of my solution is that it is simpler to understand and maintain, which might be worth the extra code.

Hope that helps!

Wednesday, 19 August 2009

The best market is one with lots of money

There are always people who feel that developing plug-ins is not a profitable market. Now while developing a plug-in may not make you as rich as Bill Gates, if there is a sizable market, you will make money.

So if you are looking for whether your idea makes sense, here is a simple relation to figure it out:

UnitPrice * NumberOfQualifiedCustomers * ConversionRate >= SomeSpecificFinancialGoal

If the statement is true, you will make money. If it is not, you will fail.

I'm pretty sure that I grossly over estimated NumberOfQualifiedCustomers when creating Worklog Assistant. How did I over calculate? Simply, the number of total JIRA users who also use JIRA as a timesheet system is quite small (yeah, duh right?) I would not have known this if I did not reach market quickly.

However, I used very pessimistic values for ConversionRate so maybe that balanced it out because I still reached my financial goals.

What does this mean for me? It means V2 is going to kick some serious ass. Oh how I wish I could have people to work on this with me, wink wink :-)

Thursday, 9 July 2009

Going AFK with Bazaar

One thing I've really liked about distributed VC systems is that they handle merging really well. Of course, this is not limited to DVCSs. Subversion and Perforce have very good merging but do not support offline work very well.

I've been using Bazaar for a little while now for my app because I knew that I would need to be offline every now and then. I just had one of those periods and thought it would be a great chance to see how well AFK mode works with Bazaar.

This is more for my own future reference rather than for you :-)

laptop $ rsync -avz -e ssh sohail@desktop:/home/sohail/bzr/ ~/bzr
laptop $ bzr branch ~/bzr/code/master project
laptop $ bzr bind ~/bzr/code/master

Now I can work offline in the "project" directory and all checkins go to ~/bzr .

Upon return:

laptop $ rsync -avz -e ssh ~/bzr/ sohail@desktop:/home/sohail/bzr

NOTE TRAILING SLASH ON SOURCE DIRECTORY!!!!!1111oneone....

Note: I think the above is insane. Ideally, I'd just do something like:

laptop $ bzr checkout bzr+ssh://sohail@desktop/home/sohail/bzr bzr
laptop $ cd bzr
laptop $ bzr work-offline

Return from afk:

laptop $ bzr I'm back

Or something like that.

Monday, 8 June 2009

VanCPP June meeting

If you are in Vancouver and interested in programming (who isn't?!) you might want to make your way to the VanCPP meeting in June. The announcement is below:

Our June 2009 meeting will be held on Thursday, June 18. Please note the new venue.

Topic: Concurrent Programming in the D Programming Language

Presented by Walter Bright

Abstract: Many-core concurrent programming offers exciting and compelling advantages. The single core, single thread programming model is assumed by imperative programming languages. This model offers sequential consistency as its fundamental characteristic.Because many-core systems use layered cache memory systems,sequential consistency is not guaranteed among threads.Because imperative programming languages allow implicit sharing of data between threads, many misguided idioms and optimizations are possible that erroneously assume sequential consistency.One example of this is the double checked locking optimization.The pernicious nature of these sorts of bugs is they defy programmers' natural intuition about how programs behave,they are not statically detectable, and there is no way to reliably test a program to rule out the existence of such bugs.A program may appear to work, but have problems appear years later, fail when ported to a different platform, and such problems may be extremely hard to reproduce and track down.In essence, the correctness of the program relies entirely on the expertise and care of the programmer.This is not an acceptable situation for developers of programs that require high reliability.

The D programming language is an imperative programming language with an innovative type system that prevents implicit sharing and also fosters a complete, integrated pure functional subset. It is possible to statically verify that D programs do not have sequential consistency bugs. The double checked locking optimization bug is not possible. Type support for shared data and immutable data, as well as pure functions, means that mutating data interactions between threads can occur only under carefully controlled conditions.This dramatically reduces the problem space for concurrency bugs from the whole of the source code to a small subset of it, making it a much more tractable problem.

Speaker bio: Walter Bright graduated from Caltech in 1979 with a degree in mechanical engineering. He worked for Boeing for three years on the development of the 757 stabilizer trim system. He then switched to writing software, in particular compilers, and has been writing them ever since.

The meeting will be held at:
Workspace
21 Water Street
Vancouver, BC
V6B 1A

Regards,

Vladan Vidakovic

Thursday, 7 May 2009

Using C++0x lambda to replace Boost Bind in C++03 code

[Note: This post looks ugly under Internet Explorer. If you want to tell me how to fix my CSS, that would be much appreciated]
[Note: This post was written using the Intel C++ compiler with -std=c++0x.]

As some of you know, I work on Worklog Assistant which is written using C++/Qt.

One thing that I've made very good use of is a slightly modified version of the code in an earlier post titled "Using Boost Bind and Boost Function with Qt".

As an example, consider the following code from the above app which creates a popup menu to toggle the visibility of table columns. This is done by creating a "toggle" action for each column header that... toggles the visibility. It might help to think how this action would be created in plain Qt (hint: it would be painful.) It would certainly not have the locality it does now.

void
showColumnHeaderContextMenu(ssci::CustomTableView * self,const QPoint & pos)
{
QMenu columns(ssci::CustomTableView::tr("Visible Columns"));
columns.setIcon(QIcon(QString::fromLatin1(":/ui/icons/grid.png")));

QHeaderView * headers(self->horizontalHeader());

std::vector<std::pair<QString,int> > sorted_columns;
getSortedColumns(self->model(),headers,sorted_columns);

for(int ii = 0; ii < headers->count(); ++ii)
{
QAction * action =
new QAction(sorted_columns[ii].first,
&columns);

bool is_hidden = headers->isSectionHidden(sorted_columns[ii].second);

action->setCheckable(true);
action->setChecked(!is_hidden);

using boost::bind;
using boost::function;

function<void()> toggle =
bind(&QHeaderView::setSectionHidden,headers,
sorted_columns[ii].second,!is_hidden);

ssci::connect(action,SIGNAL(triggered()),
toggle);

columns.addAction(action);
}

QMenu menu;
menu.addMenu(&columns);

menu.exec(self->mapToGlobal(pos));
}


Of particular interest is the code in the for loop which sets up the actions:
    function<void()> toggle =
bind(&QHeaderView::setSectionHidden,headers,
sorted_columns[ii].second,!is_hidden);

ssci::connect(action,SIGNAL(triggered()),
toggle);



This creates a function object on the fly which hides the selected column using code from the above linked post. To do this in plain Qt is a gigantic pain. LibQxt has a solution as well but mine is much better!

Anyway, the point is that you might find a lot of code using bind and function in an app like this. Binding member function pointers, member data pointers and function pointers is fairly normal and is really the only way to maintain sanity and reduce boiler plate. Nested binds are also used where appropriate.

Boost Bind is a way to create closures, or at least as close as you can get in C++ to true closures. Therefore it is natural to try and replace some uses of Boost Bind with C++0x lambda. There is good coverage of how C++0x lambda works here so I won't repeat the same information. However, I will cover the cases of bind I could convert and (horrifically) the cases I couldn't!

Using automatic variables



Consider the following code:

const int LICENSING_TAB_INDEX = 1;

ssci::connect(lblImportNewLicense,SIGNAL(linkActivated(QString const &)),
boost::function<void()>(
boost::bind(&QTabWidget::setCurrentIndex,
tabWidget,
LICENSING_TAB_INDEX)
)
);



The above function call sets things up so that when a link is clicked in the interface, the LICENSING_TAB_INDEX tab is selected in the configuration.

One thing Boost Bind does is that it stores all bound arguments by value. That means &QTabWidget::setCurrentIndex, tabWidget and LICENSING_TAB_INDEX are all stored by value. C++0x lambda calls this capturing. This can be done using implicit capture (by parsing the lambda body) or by you. By default, C++0x lambda captures variables by reference. It is potentially buggy to capture function-local variables by reference automatically. Therefore, the above code translated to C++0x lambda is:
[=]()
{ tabWidget->setCurrentIndex(LICENSING_TAB_INDEX); }

The first element of a lambda-introducer (the []) can be a capture default which can be one of = or &. The capture default tells the compiler how to capture those variables that are implicitly captured. The assignment is supposed to make you think of copy assignment and the & is supposed to make you think of reference to. So in the above case, to get the exact same behaviour as the bind, we need to intentionally copy all variables by value.

Great! Not so bad :-)

A simpler example... Or is it?



Consider the following code:

ssci::connect(clearSelectedRole,SIGNAL(clicked()),
boost::function<void()>(
boost::bind(
&QComboBox::setCurrentIndex,
projectRoles,
-1)
)
);


This code sets things up so that when the clearSelectedRole button is clicked, the corresponding QComboBox has an invalid index.

The C++0x version of this is really straightforward:

ssci::connect(clearSelectedRole,SIGNAL(clicked()),
boost::function<void()>(
[&](){projectRoles->setCurrentIndex(-1);}
)
);

In this case, the pointer object (not the value) projectRoles is captured by reference. This is not an issue because the pointer object is guaranteed to outlive the closure. Now you know why closures and garbage collection go together :-)

Verbosity or Why I wish C++0x used polymorphic lambdas



Consider the following code:

static
void
updateThreadSetup(QObject * parent,ssci::MainWindow * self);

m_checkForUpdatesThread(boost::bind(updateThreadSetup,_1,&parent))


The bound function in this case is an object that calls the function updateThreadSetup with a to-be-determined value for the first parameter and a fixed value for the second parameter.

The C++0x version of this is horrible:
m_checkForUpdatesThread([&parent](QObject * obj){updateThreadSetup(obj,&parent);});


First, since parent is a local variable (was passed into the function), you can't implicitly capture it. So you have to explicitly capture it. In this case, we want to capture it by reference, hence [&parent] in the lambda-introducer. Then, since this thread setup function is passed in a QObject* we have to tell the compiler to accept this argument. Apparently it isn't smart enough. Ask someone why it is this way, you'll hear some hand waving about the callable concept. Whatever.

That is some ugly code.

Anyway...

What you cannot convert without making your code super ugly



Consider the following simple code:
vector<int> d = {1,2,3,4,5};
vector<function<int()>> funcs;

for(vector<int>::iterator it = d.begin(), end = d.end();
it != end;
++it)
{
funcs.push_back(boost::bind(add5,*it));
}


This creates a vector of function objects that presumably add 5 to their bound argument and return the result.

Here is the equivalent in C++0x lambda:

vector<int> d = {1,2,3,4,5};
vector<function<int()>> funcs;

for(vector<int>::iterator it = d.begin(), end = d.end();
it != end;
++it)
{
int &i = *it; // WTF?
funcs.push_back([i](){add5(i);});//THIS IS REDUNDANT. REDUNDANT.
}


Naively, one might have done:

funcs.push_back([it](){add5(*it);});

in the loop. But this is a bug waiting to happen. Add the following line right after the for loop:
d.push_back(0)

Now all those iterators that are captured by value can be invalidated! YAY! The only way to avoid this issue is to manually extract the value referenced by the iterator and pass that into the closure which is what I did in my translation.

Conclusion



This exercise showed me that C++0x lambda is of some interest to me. I'd really like to get rid of the verbosity (I know, too late!) A couple of things would make this the perfect lambda for me:

  • Polymorphic lambdas (or at least let me specify auto for the arguments)

  • An easy way to capture computed values (the *it bug above)



I don't know how these problems would be solved, or whether they could be but until they are, I think there is still a future for function binding ala Boost Bind which is fine by me!

Monday, 4 May 2009

BoostCon 2009

BoostCon starts tomorrow.

I went to the first and second Boost conferences. The first as an attendee, the second as a speaker. I was blown away by the quality of the talks, particularly the author's corner series of talks. I think that if the organizers are smart about it, this can be one of the best conferences. For me, it is already on par with conferences like SD West for quality and relevance.

I am extremely disappointed that I could not make it this year due to my schedule being way too busy. I hope that some of you reading this have been able to schedule better than me and go to BoostCon.

If you are attending, please ask at least one very difficult on-topic question in each of your talks for me.

Also, ask Joel de Guzman about Boost.GUI. Apparently he's got something cooking (you didn't hear it from me!)

Thursday, 19 March 2009

A seemingly complete Qt API for CL

Just came across this library called CommonQT.

It is more complete than the API I developed earlier. It is also using the KDE Smoke libraries which make it damn easy to create the API on the fly.

Very exciting.

Sunday, 15 February 2009

Latest NVIDIA drivers, Ubuntu Edgy

NVIDIA drivers not loading after the latest Ubuntu update? Low resolution mode?

They used to work didn't they?

Before you start uninstalling Ubuntu packages and installing the official NVIDIA drivers, throwing your hands up and generally having a bad day, try this:

(as root): echo nvidia >> /etc/modules

Reboot. Should work now.

For some reason, you need to explicitly load the module with the latest nvidia packages (I'm using nvidia-glx-new)

Wednesday, 14 January 2009

Qt goes LGPL!

http://www.qtsoftware.com/about/news/lgpl-license-option-added-to-qt

Now that's awesome.