This document describes the steps used to create an application that uses Skia. The assumptions are that you're using:
I'm going to describe up to the point where we can build a simple application that prints out an SkPaint.
The first step is to setup a remote git repo, take your pick of provider. In my case, the repo is called UsingSkia and lives on bitbucket.
With the remote repo created, we create a .gclient configuration file. The gclient config command will write the file for us:
$ gclient config --name=src https://bitbucket.org/dj2/usingskia.git
This will create the following:
solutions = [ { "name" : "src", "url" : "https://bitbucket.org/dj2/usingskia.git", "deps_file" : "DEPS", "managed" : True, "custom_deps" : { }, "safesync_url": "", }, ] cache_dir = None
The name that we configured is the directory in which the repo will be checked out. This is done by running gclient sync. There is a bit of magic that gclient does around the url to determine if the repo is SVN or GIT. I've found the use of ssh:// and the .git on the end seem to work to get the right SCM type.
$ gclient sync
This should execute a bunch of commands (and, in this case, may end with an error because the repo was empty. That seems to be fine.) When finished, you should have a src directory with your git repository checked out.
With the repo created we can go ahead and create our src/DEPS file. The DEPS file is used by gclient to checkout the dependent repositories of our application. In this case, the Skia repository.
Create a src/DEPS file with the following:
vars = { "skia_revision": "a6a8f00a3977e71dbce9da50a32c5e9a51c49285", } deps = { "src/third_party/skia/": "http://skia.googlesource.com/skia.git@" + Var("skia_revision"), }
There are two sections to the DEPS
file at the moment, vars
and deps
. The vars
sections defines variables we can use later in the file with the Var()
accessor. In this case, we define our root directory, a shorter name for any googlesource repositories and a specific revision of Skia that we're going to use. I've pinned to a specific version to insulate the application from changes in the Skia tree. This lets us know that when someone checks out the repo they'll be using the same version of Skia that we've built and tested against.
The deps
section defines our dependencies. Currently we have one dependency which we're going to checkout into the src/third_party/skia
directory.
Once the deps file is created, commit and push it to the remote repository. Once done, we can use gclient to checkout our dependencies.
$ gclient sync
This should output a whole bunch of lines about files that are being added to your project. This may also be a good time to create a .gitignore
file. You don't want to check the third_party/skia directory
into your repository as it's being managed by gclient.
Now, we've run into a problem. Skia itself has a DEPS
file which defines the third_party
libraries it needs to build. None of those dependencies are being checked out so Skia will fail to build.
The way I found around that is to add a second solution to the .gclient
file. This solution tells gclient about Skia and will pull in the needed dependencies. I edited my .gclient
file (created by the gclient config
command above) to look as follows:
solutions = [ { "name" : "src", "url" : "https://bitbucket.org/dj2/usingskia.git", "deps_file" : "DEPS", "managed" : True, "custom_deps" : { }, "safesync_url": "", }, { "name" : "src/third_party/skia", "url" : "http://skia.googlesource.com/skia.git@a6a8f00a3977e71dbce9da50a32c5e9a51c49285", "deps_file" : "DEPS", "managed" : True, "custom_deps" : { }, "safesync_url": "", }, ] cache_dir = None
This is a little annoying at the moment since I've duplicated the repository revision number in the .gclient
file. I'm hoping to find a way to do this through the DEPS
file, but until then, this seems to work.
With that done, re-run gclient sync
and you should see a whole lot more repositories being checked out. The src/third_party/skia/third_party/externals
directory should now be populated.
The final piece of infrastructure we need to set up is GYP. GYP is a build system generator, in this project we're going to have it build ninja configuration files.
First, we need to add GYP to our project. We'll do that by adding a new entry to the deps section of the DEPS
file.
"src/tools/gyp": (Var("googlesource_url") % "gyp") + "/trunk@1700",
As you can see, I'm going to put the library into src/tools/gyp
and checkout revision 1700 (note, the revision used here, 1700, was the head revision at the time the DEPS
file was written. You're probably safe to use the tip-of-tree revision in your DEPS
file). A quick gclient sync
and we should have everything checked out.
In order to run GYP we'll create a wrapper script. I've called this src/build/gyp_using_skia
.
#!/usr/bin/python import os import sys script_dir = os.path.dirname(__file__) using_skia_src = os.path.abspath(os.path.join(script_dir, os.pardir)) sys.path.insert(0, os.path.join(using_skia_src, 'tools', 'gyp', 'pylib')) import gyp if __name__ == '__main__': args = sys.argv[1:] if not os.environ.get('GYP_GENERATORS'): os.environ['GYP_GENERATORS'] = 'ninja' args.append('--check') args.append('-I%s/third_party/skia/gyp/common.gypi' % using_skia_src) args.append(os.path.join(script_dir, '..', 'using_skia.gyp')) print 'Updating projects from gyp files...' sys.stdout.flush() sys.exit(gyp.main(args))
Most of this is just setup code. The two interesting bits are:
args.append('-I%s/third_party/skia/gyp/common.gypi' % using_skia_src)
args.append(os.path.join(script_dir, '..', 'using_skia.gyp'))
In the case of 1, we're telling GYP to include (-I) the src/third_party/skia/gyp/common.gypi
file which will define necessary variables for Skia to compile. In the case of 2, we're telling GYP that the main configuration file for our application is src/using_skia.gyp
.
The src/using_skia.gyp
file is as follows:
{ 'targets': [ { 'configurations': { 'Debug': { }, 'Release': { } }, 'target_name': 'using_skia', 'type': 'executable', 'dependencies': [ 'third_party/skia/gyp/skia_lib.gyp:skia_lib' ], 'include_dirs': [ 'third_party/skia/include/config', 'third_party/skia/include/core', ], 'sources': [ 'app/main.cpp' ], 'ldflags': [ '-lskia', '-stdlib=libc++', '-std=c++11' ], 'cflags': [ '-Werror', '-W', '-Wall', '-Wextra', '-Wno-unused-parameter', '-g', '-O0' ] } ] }
There is a lot going on in there, I'll touch on some of the highlights. The configurations
section allows us to have different build flags for our Debug
and Release
build (in this case they're the same, but I wanted to define them.) The target_name
defines the name of the build target which we'll provide to ninja. It will also be the name of the executable that we build.
The dependencies section lists our build dependencies. These will be built before our sources are built. In this case, we depend on the skia_lib
target inside third_party/skia/gyp/skia_lib.gyp
.
The include_dirs will be added to the include path when our files are built. We need to reference code in the config and core directories of Skia.
sources
, ldflags
and cflags
should be obvious.
Our application is defined in src/app/main.cpp
as:
#include "SkPaint.h" #include "SkString.h" int main(int argc, char** argv) { SkPaint paint; paint.setColor(SK_ColorRED); SkString str; paint.toString(&str); fprintf(stdout, "%s\n", str.c_str()); return 0; }
We're just printing out an SkPaint to show that everything is linking correctly.
Now, we can run:
$ ./build/gyp_using_skia
And, we get an error. Turns out, Skia is looking for a find\_mac\_sdk.py
file in a relative tools directory which doesn't exist. Luckily, that's easy to fix with another entry in our DEPS file.
"src/tools/": File((Var("googlesource_url") % "skia") + "/trunk/tools/find_mac_sdk.py@" + Var("skia_revision")),
Here we using the File()
function of gclient
to specify that we're checking out an individual file. Running gclient sync
should pull the necessary file into src/tools
.
With that, running build/gyp\_using\_skia
should complete successfully. You should now have an out/
directory with a Debug/
and Release/
directory inside. These correspond to the configurations we specified in using\_skia.gyp
.
With all that out of the way, if you run:
$ ninja -C out/Debug using_skia
The build should execute and you'll end up with an out/Debug/using\_skia
which when executed, prints out our SkPaint entry.
One last thing, having to run build/gyp\_using\_skia
after each sync is a bit of a pain. We can fix that by adding a hooks
section to our DEPS
file. The hooks
section lets you list a set of hooks to execute after gclient
has finished the sync.
hooks = [ { # A change to a .gyp, .gypi or to GYP itself should run the generator. "name": "gyp", "pattern": ".", "action": ["python", "src/build/gyp_using_skia"] } ]
Adding the above to the end of DEPS and running gclient sync should show the GYP files being updated at the end of the sync procedure.