Building Skia library on Linux with Waf

I wanted to try Skia, the 2D graphic library from Google. The problem is: there's still no mature build script for linux and the samples are not compiled at all. So I patched some sources and wrote a simple build script for Waf, which in the end will build the skia library (as shared object) and the demo executable.

What is Waf? It is a python based framework that allows you to write "configure" like scripts, without the hassle of learning autotools and m4. I will show you the basics and what it takes to write from scratch a project build script. I assume you will be using Ubuntu 9.04.

So, first off: check out the skia sources, using the following command:

svn checkout http://skia.googlecode.com/svn/trunk/ skia-read-only -r 501

The revision 501 (on which I generated my patch file) will be downloaded.

Next, download this patch: skia.patch.gz and save somewhere on your system. It contains the modifications (hacks, actually) I did to have the demo compiled. After that, enter the directory skia-read-only and issue the command

zcat /path/to/skia.patch.gz | patch -p1

The patches will be applied and the waf build script, wscript, will be added. Open it and take a look. It starts like this:

  1. #! /usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3.  
  4. ports = """
  5.  src/ports/SkDebug_stdio.cpp                                                                                                                    
  6.  src/ports/SkGlobals_global.cpp                                                                                                                      
  7.  src/ports/SkOSFile_stdio.cpp                                                                                                                      
  8.  src/ports/SkThread_pthread.cpp                                                                                                                    
  9.  src/ports/SkTime_Unix.cpp
  10.  src/ports/SkFontHost_linux.cpp
  11.  src/ports/SkFontHost_gamma_none.cpp
  12.  src/ports/SkFontHost_FreeType.cpp
  13.  src/ports/SkFontHost_FreeType_Subpixel.cpp
  14.  src/ports/SkFontHost_tables.cpp
  15.  src/ports/SkXMLParser_empty.cpp
  16. """.split()
  17.  
  18. opts = """
  19.  src/opts/SkBlitRow_opts_none.cpp                                                                                                                
  20.  src/opts/SkBitmapProcState_opts_none.cpp                                                                                                        
  21.  src/opts/SkUtils_opts_none.cpp                                                                                                                  
  22. """.split()
  23.  
  24. xml = """
  25.  src/xml/SkDOM.cpp
  26.  src/xml/SkXMLParser.cpp
  27.  src/xml/SkXMLWriter.cpp
  28. """.split()
  29.  
  30. defines = """
  31.  GL_GLEXT_PROTOTYPES
  32.  SK_SUPPORT_LCDTEXT
  33.  SK_CAN_USE_FLOAT
  34.  SK_SCALAR_IS_FLOAT
  35.  SK_DEBUG
  36.  SK_SUPPORT_UNIT
  37.  SK_BUILD_FOR_UNIX
  38. """.split()
  39.  
  40. includes = """
  41.  src/core
  42.  include/config
  43.  include/core
  44.  include/effects
  45.  include/images
  46.  include/utils
  47.  include/views
  48.  include/xml
  49.  include/animator
  50. """.split()

Well, I just defined few lists. The first three: ports, opts and xml are the files I would like to be compiled in addition to all the other files sitting in few other subdirectories. With waf several approachs are possible: you can list all of your files to compile, or you can specify just the directories and let waf pick all the sources, or you can mix and match (as I did). Or you can put a wscript in each sub dir and create a hierarchy of wscript files, each one taking care of one piece of the build. This is what I like of waf: it does not enforce you doing only in a certain way - supposedly for your own sake - but let you decide. You may make a mess but we are adults and we don't like the maven approach. But I digress... So, take a look at the second part of the script:

  1. def configure(conf):
  2.  
  3.    # check gnu compiler
  4.    conf.check_tool('g++')
  5.  
  6.    # check freetype library
  7.    conf.check_cfg(package='freetype2', atleast_version='6.3', uselib_store='freetype', args='--cflags --libs', mandatory=1)
  8.  
  9.    # check the sdl library
  10.    conf.check_cfg(package='sdl', atleast_version='1.2', uselib_store='sdl', args='--cflags --libs', mandatory=1)
  11.  
  12.    # check the opengl library
  13.    conf.check_cfg(package='gl', atleast_version='7.6', uselib_store='gl', args='--cflags --libs', mandatory=1)
  14.  
  15.    # check the png library
  16.    conf.check_cfg(package='libpng', atleast_version='1.2.37', uselib_store='png', args='--cflags --libs', mandatory=1)
  17.  
  18.    # check libgif
  19.    conf.check(lib='gif',  uselib_store='gif', mandatory=True)
  20.  
  21.    # check libgif
  22.    conf.check(lib='jpeg', uselib_store='jpeg', mandatory=True)
  23.  
  24.    # compilation flags
  25.    conf.env.CXXFLAGS = ['-g', '-w', '-msse2' ]

In waf if you want to check for libraries, tools, headers and so, you define a function called configure. It takes one parameter, which is a sort of context constructed by waf, exposing some functions. You call them to have waf doing the checks for you. For example, at line 4, I check if the c++ compiler is installed. At line 7, I check for the freetype library writing

conf.check_cfg(package='freetype2',
               atleast_version='6.3',
               args='--cflags --libs',
               uselib_store='freetype',
               mandatory=1)


The check_cfg checks the libraries using the pkg-config tool, so basically it instructs waf to look into the PKG_CONFIG_PATH list of directories, searching in this case for freetype2.pc. Waf will check the version of the library to see if it equal or greater than 6.3. It stores the values for --cflags and --libs in the waf cache under the key 'freetype' (the key will allow you to reference the library somewhere else in the script) and, finally, waf will abort if the library is not found, because I've marked it as mandatory.

In the same way I check for other libraries at the lines 10, 13, 16. At line 19 and 22 I don't use the check_cfg but simply check because the libraries gif and jpeg are not shipped with a pkg-config file. Finally, at line 25 I just set up the compilation flags for both the skia and the executable. There are other ways to do so, and you can check the waf manual and its samples if you're curious.

For the build phase we have to define the function build. As for the configure fuction, the parameter passed by waf is the context. Now, in this function you should declare how your targets should be built. To do so you have to create a task generator. The task generator is able to generate the task (d'oh!) that will be performed by waf. In creating the task generator, you can specify everything about your target. Let's take a look

  1. def build(bld):
  2.  
  3.    # ========================================
  4.    #       Build skia library
  5.    # ========================================
  6.    obj = bld(
  7.       features = 'cxx cshlib',
  8.       target = 'skia',
  9.       defines = defines,
  10.       includes = includes,
  11.       export_incdirs = includes,
  12.       uselib = 'freetype gif jpeg gl'  )
  13.  
  14.    # add the files to the list of sources to compile
  15.    obj.find_sources_in_dirs('src/core src/effects src/utils src/images src/views src/animator src/gl')
  16.  
  17.    # skip the files we don't have to compile
  18.    obj.source.remove('src/core/SkDrawing.cpp')
  19.    obj.source.remove('src/images/SkImageDecoder_libpvjpeg.cpp')
  20.    obj.source.remove('src/images/SkImageDecoder_fpdfemb.cpp')
  21.    obj.source.remove('src/animator/SkAnimatorScript2.cpp')
  22.  
  23.    # add the other sources for the linux port
  24.    obj.source.extend(ports)                                                                                                                      
  25.    
  26.    # add the other optional sources
  27.    obj.source.extend(opts)                                                                                                                      
  28.  
  29.    # add the xml stuff
  30.    obj.source.extend(xml)

At line 6 I create the task generator using the shortcut offered by the context (in this case named bld). At line 7 I set the task generator to generate the target as shared library (cshlib) using c++ compiler to build it (cxx). At line 8 I want the library to be called "skia". At line 9 I specify the defines to use, in this case I use the list "defines" declared previously. At line 10 I set up the list of include dirs and at line 11 I instruct waf to export the same includes list. At line 12 I simply list the prerequisite libraries needed by skia. In listing the prerequisite I just use the keys chosen in the configure phase. The task generator is still not completed. At line 15 I set the generator to look into a set of directories for the sources. At line 18-21 I removed certain files from the set of sources. Finally, at line 24, 27, 30 I add other sources file to the list. Well, that's all. The generator will create a task able to compile the library.

For the demo executable, also within the build fuction, I create another task generator:

  1. # ========================================
  2.    #       Build the demo application
  3.    # ========================================
  4.    exe = bld(
  5.       features = 'cxx cprogram',
  6.       uselib = 'freetype sdl gl gif jpeg png',
  7.       uselib_local = 'skia',
  8.       defines = defines,
  9.       target = 'demo')
  10.  
  11.    exe.find_sources_in_dirs( 'samplecode')  
  12.    exe.source.append('src/utils/SDL/SkOSWindow_SDL.cpp')
  13.    exe.source.append('xcode/sampleapp_sdl/skia_sdl_main.cpp')
  14.  
  15.    # remove the samples that don't work
  16.    exe.source.remove('samplecode/SampleText.cpp')    
  17.    exe.source.remove('samplecode/SampleTypeface.cpp')  
  18.    exe.source.remove('samplecode/SampleFontScalerTest.cpp')  
  19.    exe.source.remove('samplecode/SampleGM.cpp')
  20.    exe.source.remove('samplecode/SampleTests.cpp')
  21.    exe.source.remove('samplecode/SamplePolyToPoly.cpp')
  22.    exe.source.remove('samplecode/SampleDecode.cpp')
  23.    exe.source.remove('samplecode/SampleEncode.cpp')
  24.    exe.source.remove('samplecode/SampleWarp.cpp')

It works more or less like the first generator. At line 7 I use the uselib_local instead of uselib because skia is generated by the same script and not found on the system as external dependency. Again, I add all the sources found in the samplecode directory and then I adjust the list cherry-picking and removing few more files.

Time to run the script

To run the script you need the waf engine, which is a singular file named waf you have to store in your project directory. So, let's grab waf here

http://waf.googlecode.com/files/waf-1.5.11.tar.bz2

Explode the tar somewhere and fire the ./configure script. If all goes well the waf script is generated. So, copy it into the skia-read-only directory.

Now we are ready to configure and compile skia! Enter the skia-read-only dir and issue the command

./waf configure

The project will be configured. Probably you will get some warnings due to the missing requirements. Here's the command to install them in one shot:

sudo apt-get install libgl1-mesa-dev libfreetype6-dev libsdl1.2-dev libpng12-dev libgif-dev libjpeg62-dev ttf-mscorefonts-installer

Now, again ./waf configure and this time you should get something like that:

waf configure

Time to compile skia library and the demo application. You just issue the following command

./waf

If you want, you can specify the number of parallel jobs to spawn. And you can enable the progress bar too:

./waf -j3 -p

You will see the compilation process rolling out.

waf compile

If all goes well you will find in skia-read-only/build/default the libskia.so shared library and the demo executable.

To run the demo application, first set

export LD_LIBRARY_PATH=/path/to/skia-read-only/build/default

Then run the demo. Once it started you can switch from a view to the other using the right-arrow. Hereafter some samples

demo0
demo1
demo2
demo3
demo4
demo5

Some demos don't work perfectly, like the ones that should show a picture. The pictures are not there and the paths are hardcoded. But you can hack your way around and do some experiments. Enjoy!

4 Comments so far

  1. penguin on March 10th, 2010

    Hi, Stefano

    Thanks for sharing how to build sampleapp for skia, but the skia.patch.gz file link was broken, can help to get it back? thanks again.

  2. mandy on October 21st, 2010

    Hi, Stefano
    Thanks for your share for skia.
    But when i run the demo,it will show “./demo: error while loading shared libraries: libskia.so: cannot open shared object file: No such file or directory

    Do you know what happened about this error?
    thanks.

  3. kindoblue on October 25th, 2010

    @mandy: you have to set up the LD_LIBRARY_PATH env variable

  4. Tim on December 30th, 2010

    Stefano,

    Have you tried this with the latest version - r663?

    I get a bunch of errors during the run of ./waf

    Thanks,

    Tim

Leave a reply