1Building with Skia Tutorial 2=========================== 3 4dsinclair@chromium.org 5 6 7This document describes the steps used to create an application that uses Skia. The assumptions are that you're using: 8 9 * [git](http://git-scm.com) 10 * [gclient](https://code.google.com/p/gclient/) 11 * [gyp](https://code.google.com/p/gyp/) 12 * [ninja](http://martine.github.io/ninja/) 13 14I'm going to describe up to the point where we can build a simple application that prints out an SkPaint. 15 16Overview 17-------- 18 19 1. Create remote repository. 20 1. Configure and sync using gclient. 21 1. Create DEPS file to pull in third party repositories. 22 1. Setup gitignore for directories pulled in from DEPS. 23 1. Configure GYP. 24 1. Setup GYP auto-run when gclient sync is executed. 25 26gclient setup 27------------- 28The first step is to setup a remote git repo, take your pick of provider. In 29my case, the repo is called UsingSkia and lives on 30[bitbucket](https://bitbucket.org). 31 32With the remote repo created, we create a .gclient configuration file. The 33gclient config command will write the file for us: 34 35 $ gclient config --name=src https://bitbucket.org/dj2/usingskia.git 36 37This will create the following: 38 39 solutions = [ 40 { "name" : "src", 41 "url" : "https://bitbucket.org/dj2/usingskia.git", 42 "deps_file" : "DEPS", 43 "managed" : True, 44 "custom_deps" : { 45 }, 46 "safesync_url": "", 47 }, 48 ] 49 cache_dir = None 50 51The name that we configured is the directory in which the repo will be checked 52out. This is done by running gclient sync. There is a bit of magic that 53gclient does around the url to determine if the repo is SVN or GIT. I've found 54the use of ssh:// and the .git on the end seem to work to get the right SCM 55type. 56 57 $ gclient sync 58 59This should execute a bunch of commands (and, in this case, may end with an 60error because the repo was empty. That seems to be fine.) When finished, you 61should have a src directory with your git repository checked out. 62 63DEPS 64---- 65 66With the repo created we can go ahead and create our src/DEPS file. The DEPS 67file is used by gclient to checkout the dependent repositories of our 68application. In this case, the Skia repository. 69 70Create a src/DEPS file with the following: 71 72~~~~ 73 74 vars = { 75 "skia_revision": "a6a8f00a3977e71dbce9da50a32c5e9a51c49285", 76 } 77 78 deps = { 79 "src/third_party/skia/": 80 "http://skia.googlecode.com/skia.git@" + Var("skia_revision"), 81 } 82 83~~~~ 84 85There are two sections to the `DEPS` file at the moment, `vars` and `deps`. 86The `vars` sections defines variables we can use later in the file with the 87`Var()` accessor. In this case, we define our root directory, a shorter name 88for any googlecode repositories and a specific revision of Skia that we're 89going to use. I've pinned to a specific version to insulate the application 90from changes in the Skia tree. This lets us know that when someone checks out 91the repo they'll be using the same version of Skia that we've built and tested 92against. 93 94The `deps` section defines our dependencies. Currently we have one dependency 95which we're going to checkout into the `src/third_party/skia` directory. 96 97Once the deps file is created, commit and push it to the remote repository. 98Once done, we can use gclient to checkout our dependencies. 99 100 $ gclient sync 101 102This should output a whole bunch of lines about files that are being added to 103your project. This may also be a good time to create a `.gitignore` file. You 104don't want to check the `third_party/skia directory` into your repository as 105it's being managed by gclient. 106 107Now, we've run into a problem. Skia itself has a `DEPS` file which defines the 108`third_party` libraries it needs to build. None of those dependencies are being 109checked out so Skia will fail to build. 110 111The way I found around that is to add a second solution to the `.gclient` 112file. This solution tells gclient about Skia and will pull in the needed 113dependencies. I edited my `.gclient` file (created by the `gclient config` 114command above) to look as follows: 115 116 solutions = [ 117 { "name" : "src", 118 "url" : "https://bitbucket.org/dj2/usingskia.git", 119 "deps_file" : "DEPS", 120 "managed" : True, 121 "custom_deps" : { 122 }, 123 "safesync_url": "", 124 }, 125 { "name" : "src/third_party/skia", 126 "url" : "http://skia.googlecode.com/skia.git@a6a8f00a3977e71dbce9da50a32c5e9a51c49285", 127 "deps_file" : "DEPS", 128 "managed" : True, 129 "custom_deps" : { 130 }, 131 "safesync_url": "", 132 }, 133 ] 134 cache_dir = None 135 136This is a little annoying at the moment since I've duplicated the repository 137revision number in the `.gclient` file. I'm hoping to find a way to do this 138through the `DEPS` file, but until then, this seems to work. 139 140With that done, re-run `gclient sync` and you should see a whole lot more 141repositories being checked out. The 142`src/third_party/skia/third_party/externals` directory should now be 143populated. 144 145GYP 146--- 147 148The final piece of infrastructure we need to set up is GYP. GYP is a build 149system generator, in this project we're going to have it build ninja 150configuration files. 151 152First, we need to add GYP to our project. We'll do that by adding a new entry 153to the deps section of the `DEPS` file. 154 155 "src/tools/gyp": 156 (Var("googlecode_url") % "gyp") + "/trunk@1700", 157 158As you can see, I'm going to put the library into `src/tools/gyp` and checkout 159revision 1700 (note, the revision used here, 1700, was the head revision at 160the time the `DEPS` file was written. You're probably safe to use the 161tip-of-tree revision in your `DEPS` file). A quick `gclient sync` and we 162should have everything checked out. 163 164In order to run GYP we'll create a wrapper script. I've called this 165`src/build/gyp_using_skia`. 166 167~~~~ 168#!/usr/bin/python 169import os 170import sys 171 172script_dir = os.path.dirname(__file__) 173using_skia_src = os.path.abspath(os.path.join(script_dir, os.pardir)) 174 175sys.path.insert(0, os.path.join(using_skia_src, 'tools', 'gyp', 'pylib')) 176import gyp 177 178if __name__ == '__main__': 179 args = sys.argv[1:] 180 181 if not os.environ.get('GYP_GENERATORS'): 182 os.environ['GYP_GENERATORS'] = 'ninja' 183 184 args.append('--check') 185 args.append('-I%s/third_party/skia/gyp/common.gypi' % using_skia_src) 186 187 args.append(os.path.join(script_dir, '..', 'using_skia.gyp')) 188 189 print 'Updating projects from gyp files...' 190 sys.stdout.flush() 191 192 sys.exit(gyp.main(args)) 193~~~~ 194 195Most of this is just setup code. The two interesting bits are: 196 197 1. `args.append('-I%s/third_party/skia/gyp/common.gypi' % using_skia_src)` 198 1. `args.append(os.path.join(script_dir, '..', 'using_skia.gyp'))` 199 200In the case of 1, we're telling GYP to include (-I) the 201`src/third_party/skia/gyp/common.gypi` file which will define necessary 202variables for Skia to compile. In the case of 2, we're telling GYP that the 203main configuration file for our application is `src/using_skia.gyp`. 204 205The `src/using_skia.gyp` file is as follows: 206 207~~~~ 208{ 209 'targets': [ 210 { 211 'configurations': { 212 'Debug': { }, 213 'Release': { } 214 }, 215 'target_name': 'using_skia', 216 'type': 'executable', 217 'dependencies': [ 218 'third_party/skia/gyp/skia_lib.gyp:skia_lib' 219 ], 220 'include_dirs': [ 221 'third_party/skia/include/config', 222 'third_party/skia/include/core', 223 ], 224 'sources': [ 225 'app/main.cpp' 226 ], 227 'ldflags': [ 228 '-lskia', '-stdlib=libc++', '-std=c++11' 229 ], 230 'cflags': [ 231 '-Werror', '-W', '-Wall', '-Wextra', '-Wno-unused-parameter', '-g', '-O0' 232 ] 233 } 234 ] 235} 236~~~~ 237 238There is a lot going on in there, I'll touch on some of the highlights. The 239`configurations` section allows us to have different build flags for our `Debug` 240and `Release` build (in this case they're the same, but I wanted to define 241them.) The `target_name` defines the name of the build target which we'll 242provide to ninja. It will also be the name of the executable that we build. 243 244The dependencies section lists our build dependencies. These will be built 245before our sources are built. In this case, we depend on the `skia_lib` target 246inside `third_party/skia/gyp/skia_lib.gyp`. 247 248The include_dirs will be added to the include path when our files are built. 249We need to reference code in the config and core directories of Skia. 250 251`sources`, `ldflags` and `cflags` should be obvious. 252 253Our application is defined in `src/app/main.cpp` as: 254 255~~~~ 256#include "SkPaint.h" 257#include "SkString.h" 258 259int main(int argc, char** argv) { 260 SkPaint paint; 261 paint.setColor(SK_ColorRED); 262 263 SkString str; 264 paint.toString(&str); 265 266 fprintf(stdout, "%s\n", str.c_str()); 267 268 return 0; 269} 270~~~~ 271 272We're just printing out an SkPaint to show that everything is linking correctly. 273 274Now, we can run: 275 276 $ ./build/gyp_using_skia 277 278And, we get an error. Turns out, Skia is looking for a `find\_mac\_sdk.py` file in 279a relative tools directory which doesn't exist. Luckily, that's easy to fix 280with another entry in our DEPS file. 281 282 "src/tools/": 283 File((Var("googlecode_url") % "skia") + "/trunk/tools/find_mac_sdk.py@" + 284 Var("skia_revision")), 285 286Here we using the `File()` function of `gclient` to specify that we're checking 287out an individual file. Running `gclient sync` should pull the necessary file 288into `src/tools`. 289 290With that, running `build/gyp\_using\_skia` should complete successfully. You 291should now have an `out/` directory with a `Debug/` and `Release/` directory inside. 292These correspond to the configurations we specified in `using\_skia.gyp`. 293 294With all that out of the way, if you run: 295 296 $ ninja -C out/Debug using_skia 297 298The build should execute and you'll end up with an `out/Debug/using\_skia` which 299when executed, prints out our SkPaint entry. 300 301Autorun GYP 302----------- 303 304One last thing, having to run `build/gyp\_using\_skia` after each sync is a bit of 305a pain. We can fix that by adding a `hooks` section to our `DEPS` file. The `hooks` 306section lets you list a set of hooks to execute after `gclient` has finished the 307sync. 308 309 hooks = [ 310 { 311 # A change to a .gyp, .gypi or to GYP itself should run the generator. 312 "name": "gyp", 313 "pattern": ".", 314 "action": ["python", "src/build/gyp_using_skia"] 315 } 316 ] 317 318Adding the above to the end of DEPS and running gclient sync should show the 319GYP files being updated at the end of the sync procedure. 320