Basic standalone GN configs.

This sketches out what a world without Chrome's GN configs would look like.

Instead of DEPSing in build/, we now host our own gypi_to_gn.py.

The symlink from skia/ to . lets us run gclient hooks when the .gclient file is in the directory above skia/ or inside skia/.  That means we don't need gn.py anymore.

BUG=skia:
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2167163002

Review-Url: https://codereview.chromium.org/2167163002
diff --git a/.gn b/.gn
index c6fefba..dce427f 100644
--- a/.gn
+++ b/.gn
@@ -1 +1 @@
-buildconfig = "//build/config/BUILDCONFIG.gn"
+buildconfig = "//gn/BUILDCONFIG.gn"
diff --git a/BUILD.gn b/BUILD.gn
index c346673..7ead5f7 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -51,16 +51,6 @@
 # Any code that's linked into Skia-the-library should use this config via += skia_library_configs.
 config("skia_library") {
   visibility = [ ":*" ]
-
-  cflags = [
-    "-Winit-self",
-    "-Wpointer-arith",
-    "-Wsign-compare",
-    "-Wvla",
-    "-fstrict-aliasing",
-  ]
-  cflags_cc = [ "-Wnon-virtual-dtor" ]
-
   defines = [ "SKIA_IMPLEMENTATION=1" ]
 }
 
@@ -70,21 +60,7 @@
   ":skia_library",
 ]
 
-if (!defined(is_fuchsia)) {
-  is_fuchsia = current_os == "fuchsia"
-}
-
-unwanted_configs = []
-
-if (!is_fuchsia) {
-  # Chrome's GN environment is mostly helpful, but a couple default configs tend to get in the way.
-  unwanted_configs += [
-    "//build/config/clang:find_bad_constructs",  # Chrome style checks.
-    "//build/config:feature_flags",  # A bunch of #defines we don't care about.
-  ]
-}
-
-core_gypi = exec_script("//build/gypi_to_gn.py",
+core_gypi = exec_script("gn/gypi_to_gn.py",
                         [
                           rebase_path("gyp/core.gypi"),
                           "--replace=<(skia_include_path)=include",
@@ -93,7 +69,7 @@
                         "scope",
                         [ "gyp/core.gypi" ])
 
-effects_gypi = exec_script("//build/gypi_to_gn.py",
+effects_gypi = exec_script("gn/gypi_to_gn.py",
                            [
                              rebase_path("gyp/effects.gypi"),
                              "--replace=<(skia_include_path)=include",
@@ -102,7 +78,7 @@
                            "scope",
                            [ "gyp/effects.gypi" ])
 
-gpu_gypi = exec_script("//build/gypi_to_gn.py",
+gpu_gypi = exec_script("gn/gypi_to_gn.py",
                        [
                          rebase_path("gyp/gpu.gypi"),
                          "--replace=<(skia_include_path)=include",
@@ -111,7 +87,7 @@
                        "scope",
                        [ "gyp/gpu.gypi" ])
 
-opts_gypi = exec_script("//build/gypi_to_gn.py",
+opts_gypi = exec_script("gn/gypi_to_gn.py",
                         [
                           rebase_path("gyp/opts.gypi"),
                           "--replace=<(skia_include_path)=include",
@@ -120,7 +96,7 @@
                         "scope",
                         [ "gyp/opts.gypi" ])
 
-pdf_gypi = exec_script("//build/gypi_to_gn.py",
+pdf_gypi = exec_script("gn/gypi_to_gn.py",
                        [
                          rebase_path("gyp/pdf.gypi"),
                          "--replace=<(skia_include_path)=include",
@@ -129,7 +105,7 @@
                        "scope",
                        [ "gyp/pdf.gypi" ])
 
-utils_gypi = exec_script("//build/gypi_to_gn.py",
+utils_gypi = exec_script("gn/gypi_to_gn.py",
                          [
                            rebase_path("gyp/utils.gypi"),
                            "--replace=<(skia_include_path)=include",
@@ -140,7 +116,6 @@
 
 source_set("opts_ssse3") {
   configs += skia_library_configs
-  configs -= unwanted_configs
 
   sources = opts_gypi.ssse3_sources
   cflags = [ "-mssse3" ]
@@ -148,7 +123,6 @@
 
 source_set("opts_sse41") {
   configs += skia_library_configs
-  configs -= unwanted_configs
 
   sources = opts_gypi.sse41_sources
   cflags = [ "-msse4.1" ]
@@ -156,7 +130,6 @@
 
 source_set("opts_avx") {
   configs += skia_library_configs
-  configs -= unwanted_configs
 
   sources = opts_gypi.avx_sources
   cflags = [ "-mavx" ]
@@ -165,7 +138,6 @@
 component("skia") {
   public_configs = [ ":skia_public" ]
   configs += skia_library_configs
-  configs -= unwanted_configs
 
   deps = [
     ":opts_avx",
@@ -174,7 +146,7 @@
     "//third_party/zlib",
   ]
 
-  libs = []
+  libs = [ "pthread" ]
 
   sources = []
   sources += core_gypi.sources
@@ -254,8 +226,6 @@
 }
 
 executable("example") {
-  configs -= unwanted_configs
-
   sources = [
     "cmake/example.cpp",
   ]
diff --git a/DEPS b/DEPS
index 3e87e0e..5964f26 100644
--- a/DEPS
+++ b/DEPS
@@ -3,9 +3,7 @@
 # Dependencies on outside packages.
 #
 deps = {
-  "build":       "https://chromium.googlesource.com/chromium/src/build.git@c3550298c508d10c6281794de126223a38359249",
   "buildtools":  "https://chromium.googlesource.com/chromium/buildtools.git@60f7f9a8b421ebf9a46041dfa2ff11c0fe59c582",
-  "tools/clang": "https://chromium.googlesource.com/chromium/src/tools/clang.git@ea64c667cd841b2c3268bd7dfd223269f3ea23ba",
 
   "common": "https://skia.googlesource.com/common.git@c282fe0b6e392b14f88d647cbd86e1a3ef5498e0",
 
@@ -66,4 +64,33 @@
   }
 }
 
+hooks = [{
+    'pattern': '.',
+    'action': ['download_from_google_storage',
+               '--quiet',
+               '--no_resume',
+               '--no_auth',
+               '--bucket', 'chromium-gn',
+               '--platform=linux*',
+               '-s', 'skia/buildtools/linux64/gn.sha1'],
+},{
+    'pattern': '.',
+    'action': ['download_from_google_storage',
+               '--quiet',
+               '--no_resume',
+               '--no_auth',
+               '--bucket', 'chromium-gn',
+               '--platform=darwin',
+               '-s', 'skia/buildtools/mac/gn.sha1'],
+},{
+    'pattern': '.',
+    'action': ['download_from_google_storage',
+               '--quiet',
+               '--no_resume',
+               '--no_auth',
+               '--bucket', 'chromium-gn',
+               '--platform=win32',
+               '-s', 'skia/buildtools/win/gn.sha1'],
+}]
+
 recursedeps = [ "common" ]
diff --git a/gn.py b/gn.py
deleted file mode 100755
index b17280a..0000000
--- a/gn.py
+++ /dev/null
@@ -1,37 +0,0 @@
-#!/usr/bin/env python
-
-# Copyright 2016 Google Inc.
-#
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import subprocess
-import sys
-
-def quiet(*cmd):
-  cmd = ' '.join(cmd).split()
-  subprocess.check_output(cmd)
-
-def loud(*cmd):
-  cmd = ' '.join(cmd).split()
-  ret = subprocess.call(cmd)
-  if ret != 0:
-    sys.exit(ret)
-
-def gn_path():
-  if 'linux' in sys.platform:
-    return 'buildtools/linux64/gn'
-  if 'darwin' in sys.platform:
-    return 'buildtools/mac/gn'
-  return 'buildtools/win/gn.exe'
-
-# Make sure we've got an up-to-date GN and Clang, and sysroot on Linux.
-quiet('download_from_google_storage',
-     '--no_resume --no_auth --bucket chromium-gn',
-     '-s ', gn_path() + '.sha1')
-quiet('python tools/clang/scripts/update.py --if-needed')
-if 'linux' in sys.platform:
-  quiet('python build/linux/sysroot_scripts/install-sysroot.py --arch=amd64')
-
-# Pass all our arguments over to the real GN binary.
-loud(gn_path(), *sys.argv[1:])
diff --git a/gn/BUILD.gn b/gn/BUILD.gn
new file mode 100644
index 0000000..547f57e
--- /dev/null
+++ b/gn/BUILD.gn
@@ -0,0 +1,122 @@
+# Copyright 2016 Google Inc.
+#
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+declare_args() {
+  ar = "ar"
+  cc = "cc"
+  cxx = "c++"
+}
+
+config("default") {
+  cflags = [
+    "-g",
+    "-fstrict-aliasing",
+    "-fPIC",
+
+    "-Werror",
+    "-Wall",
+    "-Wextra",
+    "-Winit-self",
+    "-Wpointer-arith",
+    "-Wsign-compare",
+    "-Wvla",
+
+    "-Wno-deprecated-declarations",
+    "-Wno-unused-parameter",
+  ]
+  cflags_cc = [
+    "-std=c++11",
+    "-fno-exceptions",
+    "-fno-rtti",
+    "-fno-threadsafe-statics",
+
+    "-Wnon-virtual-dtor",
+  ]
+}
+
+config("release") {
+  cflags = [ "-Os" ]
+  defines = [ "NDEBUG" ]
+}
+
+config("executable") {
+  if (is_mac) {
+    ldflags = [ "-Wl,-rpath,@loader_path/." ]
+  } else if (is_linux) {
+    ldflags = [ "-Wl,-rpath,\$ORIGIN" ]
+  }
+}
+
+toolchain("gcc_like") {
+  lib_switch = "-l"
+  lib_dir_switch = "-L"
+
+  tool("cc") {
+    depfile = "{{output}}.d"
+    command = "$cc -MMD -MF $depfile {{defines}} {{include_dirs}} {{cflags}} {{cflags_c}} -c {{source}} -o {{output}}"
+    depsformat = "gcc"
+    outputs = [
+      "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o",
+    ]
+  }
+
+  tool("cxx") {
+    depfile = "{{output}}.d"
+    command = "$cxx -MMD -MF $depfile {{defines}} {{include_dirs}} {{cflags}} {{cflags_cc}} -c {{source}} -o {{output}}"
+    depsformat = "gcc"
+    outputs = [
+      "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o",
+    ]
+  }
+
+  tool("asm") {
+    depfile = "{{output}}.d"
+    command = "$cc -MMD -MF $depfile {{defines}} {{include_dirs}} {{asmflags}} -c {{source}} -o {{output}}"
+    depsformat = "gcc"
+    outputs = [
+      "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o",
+    ]
+  }
+
+  tool("alink") {
+    command = "rm -f {{output}} && $ar rcs {{output}} {{inputs}}"
+    outputs = [
+      "{{target_out_dir}}/{{target_output_name}}{{output_extension}}",
+    ]
+    default_output_extension = ".a"
+    output_prefix = "lib"
+  }
+
+  tool("solink") {
+    soname = "{{target_output_name}}{{output_extension}}"
+
+    rpath = "-Wl,-soname,$soname"
+    if (is_mac) {
+      rpath = "-Wl,-install_name,@rpath/$soname"
+    }
+
+    command = "$cxx -shared {{ldflags}} {{inputs}} {{solibs}} {{libs}} $rpath -o {{output}}"
+    outputs = [
+      "{{root_out_dir}}/$soname",
+    ]
+    output_prefix = "lib"
+    default_output_extension = ".so"
+  }
+
+  tool("link") {
+    command = "$cxx {{ldflags}} {{inputs}} {{solibs}} {{libs}} -o {{output}}"
+    outputs = [
+      "{{root_out_dir}}/{{target_output_name}}{{output_extension}}",
+    ]
+  }
+
+  tool("stamp") {
+    command = "touch {{output}}"
+  }
+
+  tool("copy") {
+    command = "ln -f {{source}} {{output}} 2>/dev/null || (rm -rf {{output}} && cp -af {{source}} {{output}})"
+  }
+}
diff --git a/gn/BUILDCONFIG.gn b/gn/BUILDCONFIG.gn
new file mode 100644
index 0000000..31056bd
--- /dev/null
+++ b/gn/BUILDCONFIG.gn
@@ -0,0 +1,69 @@
+# Copyright 2016 Google Inc.
+#
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# It's best to keep the names and defaults of is_foo flags consistent with Chrome.
+
+declare_args() {
+  is_debug = true
+  is_component_build = false
+}
+
+# Platform detection
+if (target_os == "") {
+  target_os = host_os
+}
+if (current_os == "") {
+  current_os = target_os
+}
+
+is_android = current_os == "android"
+is_fuchsia = current_os == "fuchsia"
+is_ios = current_os == "ios"
+is_linux = current_os == "linux"
+is_mac = current_os == "mac"
+is_win = current_os == "win"
+
+is_posix = !is_win
+
+# A component is either a source_set or a shared_library.
+template("component") {
+  _component_mode = "source_set"
+  if (is_component_build) {
+    _component_mode = "shared_library"
+  }
+
+  target(_component_mode, target_name) {
+    forward_variables_from(invoker, "*")
+  }
+}
+
+# Default configs
+_default_configs = [ "//gn:default" ]
+if (!is_debug) {
+  _default_configs += [ "//gn:release" ]
+}
+
+set_defaults("executable") {
+  configs = _default_configs + [ "//gn:executable" ]
+}
+
+set_defaults("source_set") {
+  configs = _default_configs
+}
+
+set_defaults("static_library") {
+  configs = _default_configs
+}
+
+set_defaults("shared_library") {
+  configs = _default_configs
+}
+
+set_defaults("component") {
+  configs = _default_configs
+}
+
+# For now, we support GCC-like toolchains, including Clang.
+set_default_toolchain("//gn:gcc_like")
diff --git a/gn/gn_helpers.py b/gn/gn_helpers.py
new file mode 100644
index 0000000..fb94d54
--- /dev/null
+++ b/gn/gn_helpers.py
@@ -0,0 +1,351 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Helper functions useful when writing scripts that integrate with GN.
+
+The main functions are ToGNString and FromGNString which convert between
+serialized GN veriables and Python variables.
+
+To use in a random python file in the build:
+
+  import os
+  import sys
+
+  sys.path.append(os.path.join(os.path.dirname(__file__),
+                               os.pardir, os.pardir, "build"))
+  import gn_helpers
+
+Where the sequence of parameters to join is the relative path from your source
+file to the build directory."""
+
+class GNException(Exception):
+  pass
+
+
+def ToGNString(value, allow_dicts = True):
+  """Returns a stringified GN equivalent of the Python value.
+
+  allow_dicts indicates if this function will allow converting dictionaries
+  to GN scopes. This is only possible at the top level, you can't nest a
+  GN scope in a list, so this should be set to False for recursive calls."""
+  if isinstance(value, basestring):
+    if value.find('\n') >= 0:
+      raise GNException("Trying to print a string with a newline in it.")
+    return '"' + \
+        value.replace('\\', '\\\\').replace('"', '\\"').replace('$', '\\$') + \
+        '"'
+
+  if isinstance(value, unicode):
+    return ToGNString(value.encode('utf-8'))
+
+  if isinstance(value, bool):
+    if value:
+      return "true"
+    return "false"
+
+  if isinstance(value, list):
+    return '[ %s ]' % ', '.join(ToGNString(v) for v in value)
+
+  if isinstance(value, dict):
+    if not allow_dicts:
+      raise GNException("Attempting to recursively print a dictionary.")
+    result = ""
+    for key in sorted(value):
+      if not isinstance(key, basestring):
+        raise GNException("Dictionary key is not a string.")
+      result += "%s = %s\n" % (key, ToGNString(value[key], False))
+    return result
+
+  if isinstance(value, int):
+    return str(value)
+
+  raise GNException("Unsupported type when printing to GN.")
+
+
+def FromGNString(input_):
+  """Converts the input string from a GN serialized value to Python values.
+
+  For details on supported types see GNValueParser.Parse() below.
+
+  If your GN script did:
+    something = [ "file1", "file2" ]
+    args = [ "--values=$something" ]
+  The command line would look something like:
+    --values="[ \"file1\", \"file2\" ]"
+  Which when interpreted as a command line gives the value:
+    [ "file1", "file2" ]
+
+  You can parse this into a Python list using GN rules with:
+    input_values = FromGNValues(options.values)
+  Although the Python 'ast' module will parse many forms of such input, it
+  will not handle GN escaping properly, nor GN booleans. You should use this
+  function instead.
+
+
+  A NOTE ON STRING HANDLING:
+
+  If you just pass a string on the command line to your Python script, or use
+  string interpolation on a string variable, the strings will not be quoted:
+    str = "asdf"
+    args = [ str, "--value=$str" ]
+  Will yield the command line:
+    asdf --value=asdf
+  The unquoted asdf string will not be valid input to this function, which
+  accepts only quoted strings like GN scripts. In such cases, you can just use
+  the Python string literal directly.
+
+  The main use cases for this is for other types, in particular lists. When
+  using string interpolation on a list (as in the top example) the embedded
+  strings will be quoted and escaped according to GN rules so the list can be
+  re-parsed to get the same result."""
+  parser = GNValueParser(input_)
+  return parser.Parse()
+
+
+def FromGNArgs(input_):
+  """Converts a string with a bunch of gn arg assignments into a Python dict.
+
+  Given a whitespace-separated list of
+
+    <ident> = (integer | string | boolean | <list of the former>)
+
+  gn assignments, this returns a Python dict, i.e.:
+
+    FromGNArgs("foo=true\nbar=1\n") -> { 'foo': True, 'bar': 1 }.
+
+  Only simple types and lists supported; variables, structs, calls
+  and other, more complicated things are not.
+
+  This routine is meant to handle only the simple sorts of values that
+  arise in parsing --args.
+  """
+  parser = GNValueParser(input_)
+  return parser.ParseArgs()
+
+
+def UnescapeGNString(value):
+  """Given a string with GN escaping, returns the unescaped string.
+
+  Be careful not to feed with input from a Python parsing function like
+  'ast' because it will do Python unescaping, which will be incorrect when
+  fed into the GN unescaper."""
+  result = ''
+  i = 0
+  while i < len(value):
+    if value[i] == '\\':
+      if i < len(value) - 1:
+        next_char = value[i + 1]
+        if next_char in ('$', '"', '\\'):
+          # These are the escaped characters GN supports.
+          result += next_char
+          i += 1
+        else:
+          # Any other backslash is a literal.
+          result += '\\'
+    else:
+      result += value[i]
+    i += 1
+  return result
+
+
+def _IsDigitOrMinus(char):
+  return char in "-0123456789"
+
+
+class GNValueParser(object):
+  """Duplicates GN parsing of values and converts to Python types.
+
+  Normally you would use the wrapper function FromGNValue() below.
+
+  If you expect input as a specific type, you can also call one of the Parse*
+  functions directly. All functions throw GNException on invalid input. """
+  def __init__(self, string):
+    self.input = string
+    self.cur = 0
+
+  def IsDone(self):
+    return self.cur == len(self.input)
+
+  def ConsumeWhitespace(self):
+    while not self.IsDone() and self.input[self.cur] in ' \t\n':
+      self.cur += 1
+
+  def Parse(self):
+    """Converts a string representing a printed GN value to the Python type.
+
+    See additional usage notes on FromGNString above.
+
+    - GN booleans ('true', 'false') will be converted to Python booleans.
+
+    - GN numbers ('123') will be converted to Python numbers.
+
+    - GN strings (double-quoted as in '"asdf"') will be converted to Python
+      strings with GN escaping rules. GN string interpolation (embedded
+      variables preceeded by $) are not supported and will be returned as
+      literals.
+
+    - GN lists ('[1, "asdf", 3]') will be converted to Python lists.
+
+    - GN scopes ('{ ... }') are not supported."""
+    result = self._ParseAllowTrailing()
+    self.ConsumeWhitespace()
+    if not self.IsDone():
+      raise GNException("Trailing input after parsing:\n  " +
+                        self.input[self.cur:])
+    return result
+
+  def ParseArgs(self):
+    """Converts a whitespace-separated list of ident=literals to a dict.
+
+    See additional usage notes on FromGNArgs, above.
+    """
+    d = {}
+
+    self.ConsumeWhitespace()
+    while not self.IsDone():
+      ident = self._ParseIdent()
+      self.ConsumeWhitespace()
+      if self.input[self.cur] != '=':
+        raise GNException("Unexpected token: " + self.input[self.cur:])
+      self.cur += 1
+      self.ConsumeWhitespace()
+      val = self._ParseAllowTrailing()
+      self.ConsumeWhitespace()
+      d[ident] = val
+
+    return d
+
+  def _ParseAllowTrailing(self):
+    """Internal version of Parse that doesn't check for trailing stuff."""
+    self.ConsumeWhitespace()
+    if self.IsDone():
+      raise GNException("Expected input to parse.")
+
+    next_char = self.input[self.cur]
+    if next_char == '[':
+      return self.ParseList()
+    elif _IsDigitOrMinus(next_char):
+      return self.ParseNumber()
+    elif next_char == '"':
+      return self.ParseString()
+    elif self._ConstantFollows('true'):
+      return True
+    elif self._ConstantFollows('false'):
+      return False
+    else:
+      raise GNException("Unexpected token: " + self.input[self.cur:])
+
+  def _ParseIdent(self):
+    id_ = ''
+
+    next_char = self.input[self.cur]
+    if not next_char.isalpha() and not next_char=='_':
+      raise GNException("Expected an identifier: " + self.input[self.cur:])
+
+    id_ += next_char
+    self.cur += 1
+
+    next_char = self.input[self.cur]
+    while next_char.isalpha() or next_char.isdigit() or next_char=='_':
+      id_ += next_char
+      self.cur += 1
+      next_char = self.input[self.cur]
+
+    return id_
+
+  def ParseNumber(self):
+    self.ConsumeWhitespace()
+    if self.IsDone():
+      raise GNException('Expected number but got nothing.')
+
+    begin = self.cur
+
+    # The first character can include a negative sign.
+    if not self.IsDone() and _IsDigitOrMinus(self.input[self.cur]):
+      self.cur += 1
+    while not self.IsDone() and self.input[self.cur].isdigit():
+      self.cur += 1
+
+    number_string = self.input[begin:self.cur]
+    if not len(number_string) or number_string == '-':
+      raise GNException("Not a valid number.")
+    return int(number_string)
+
+  def ParseString(self):
+    self.ConsumeWhitespace()
+    if self.IsDone():
+      raise GNException('Expected string but got nothing.')
+
+    if self.input[self.cur] != '"':
+      raise GNException('Expected string beginning in a " but got:\n  ' +
+                        self.input[self.cur:])
+    self.cur += 1  # Skip over quote.
+
+    begin = self.cur
+    while not self.IsDone() and self.input[self.cur] != '"':
+      if self.input[self.cur] == '\\':
+        self.cur += 1  # Skip over the backslash.
+        if self.IsDone():
+          raise GNException("String ends in a backslash in:\n  " +
+                            self.input)
+      self.cur += 1
+
+    if self.IsDone():
+      raise GNException('Unterminated string:\n  ' + self.input[begin:])
+
+    end = self.cur
+    self.cur += 1  # Consume trailing ".
+
+    return UnescapeGNString(self.input[begin:end])
+
+  def ParseList(self):
+    self.ConsumeWhitespace()
+    if self.IsDone():
+      raise GNException('Expected list but got nothing.')
+
+    # Skip over opening '['.
+    if self.input[self.cur] != '[':
+      raise GNException("Expected [ for list but got:\n  " +
+                        self.input[self.cur:])
+    self.cur += 1
+    self.ConsumeWhitespace()
+    if self.IsDone():
+      raise GNException("Unterminated list:\n  " + self.input)
+
+    list_result = []
+    previous_had_trailing_comma = True
+    while not self.IsDone():
+      if self.input[self.cur] == ']':
+        self.cur += 1  # Skip over ']'.
+        return list_result
+
+      if not previous_had_trailing_comma:
+        raise GNException("List items not separated by comma.")
+
+      list_result += [ self._ParseAllowTrailing() ]
+      self.ConsumeWhitespace()
+      if self.IsDone():
+        break
+
+      # Consume comma if there is one.
+      previous_had_trailing_comma = self.input[self.cur] == ','
+      if previous_had_trailing_comma:
+        # Consume comma.
+        self.cur += 1
+        self.ConsumeWhitespace()
+
+    raise GNException("Unterminated list:\n  " + self.input)
+
+  def _ConstantFollows(self, constant):
+    """Returns true if the given constant follows immediately at the current
+    location in the input. If it does, the text is consumed and the function
+    returns true. Otherwise, returns false and the current position is
+    unchanged."""
+    end = self.cur + len(constant)
+    if end > len(self.input):
+      return False  # Not enough room.
+    if self.input[self.cur:end] == constant:
+      self.cur = end
+      return True
+    return False
diff --git a/gn/gypi_to_gn.py b/gn/gypi_to_gn.py
new file mode 100644
index 0000000..0800708
--- /dev/null
+++ b/gn/gypi_to_gn.py
@@ -0,0 +1,191 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Converts a given gypi file to a python scope and writes the result to stdout.
+
+USING THIS SCRIPT IN CHROMIUM
+
+Forking Python to run this script in the middle of GN is slow, especially on
+Windows, and it makes both the GYP and GN files harder to follow. You can't
+use "git grep" to find files in the GN build any more, and tracking everything
+in GYP down requires a level of indirection. Any calls will have to be removed
+and cleaned up once the GYP-to-GN transition is complete.
+
+As a result, we only use this script when the list of files is large and
+frequently-changing. In these cases, having one canonical list outweights the
+downsides.
+
+As of this writing, the GN build is basically complete. It's likely that all
+large and frequently changing targets where this is appropriate use this
+mechanism already. And since we hope to turn down the GYP build soon, the time
+horizon is also relatively short. As a result, it is likely that no additional
+uses of this script should every be added to the build. During this later part
+of the transition period, we should be focusing more and more on the absolute
+readability of the GN build.
+
+
+HOW TO USE
+
+It is assumed that the file contains a toplevel dictionary, and this script
+will return that dictionary as a GN "scope" (see example below). This script
+does not know anything about GYP and it will not expand variables or execute
+conditions.
+
+It will strip conditions blocks.
+
+A variables block at the top level will be flattened so that the variables
+appear in the root dictionary. This way they can be returned to the GN code.
+
+Say your_file.gypi looked like this:
+  {
+     'sources': [ 'a.cc', 'b.cc' ],
+     'defines': [ 'ENABLE_DOOM_MELON' ],
+  }
+
+You would call it like this:
+  gypi_values = exec_script("//build/gypi_to_gn.py",
+                            [ rebase_path("your_file.gypi") ],
+                            "scope",
+                            [ "your_file.gypi" ])
+
+Notes:
+ - The rebase_path call converts the gypi file from being relative to the
+   current build file to being system absolute for calling the script, which
+   will have a different current directory than this file.
+
+ - The "scope" parameter tells GN to interpret the result as a series of GN
+   variable assignments.
+
+ - The last file argument to exec_script tells GN that the given file is a
+   dependency of the build so Ninja can automatically re-run GN if the file
+   changes.
+
+Read the values into a target like this:
+  component("mycomponent") {
+    sources = gypi_values.sources
+    defines = gypi_values.defines
+  }
+
+Sometimes your .gypi file will include paths relative to a different
+directory than the current .gn file. In this case, you can rebase them to
+be relative to the current directory.
+  sources = rebase_path(gypi_values.sources, ".",
+                        "//path/gypi/input/values/are/relative/to")
+
+This script will tolerate a 'variables' in the toplevel dictionary or not. If
+the toplevel dictionary just contains one item called 'variables', it will be
+collapsed away and the result will be the contents of that dictinoary. Some
+.gypi files are written with or without this, depending on how they expect to
+be embedded into a .gyp file.
+
+This script also has the ability to replace certain substrings in the input.
+Generally this is used to emulate GYP variable expansion. If you passed the
+argument "--replace=<(foo)=bar" then all instances of "<(foo)" in strings in
+the input will be replaced with "bar":
+
+  gypi_values = exec_script("//build/gypi_to_gn.py",
+                            [ rebase_path("your_file.gypi"),
+                              "--replace=<(foo)=bar"],
+                            "scope",
+                            [ "your_file.gypi" ])
+
+"""
+
+import gn_helpers
+from optparse import OptionParser
+import sys
+
+def LoadPythonDictionary(path):
+  file_string = open(path).read()
+  try:
+    file_data = eval(file_string, {'__builtins__': None}, None)
+  except SyntaxError, e:
+    e.filename = path
+    raise
+  except Exception, e:
+    raise Exception("Unexpected error while reading %s: %s" % (path, str(e)))
+
+  assert isinstance(file_data, dict), "%s does not eval to a dictionary" % path
+
+  # Flatten any variables to the top level.
+  if 'variables' in file_data:
+    file_data.update(file_data['variables'])
+    del file_data['variables']
+
+  # Strip all elements that this script can't process.
+  elements_to_strip = [
+    'conditions',
+    'target_conditions',
+    'targets',
+    'includes',
+    'actions',
+  ]
+  for element in elements_to_strip:
+    if element in file_data:
+      del file_data[element]
+
+  return file_data
+
+
+def ReplaceSubstrings(values, search_for, replace_with):
+  """Recursively replaces substrings in a value.
+
+  Replaces all substrings of the "search_for" with "repace_with" for all
+  strings occurring in "values". This is done by recursively iterating into
+  lists as well as the keys and values of dictionaries."""
+  if isinstance(values, str):
+    return values.replace(search_for, replace_with)
+
+  if isinstance(values, list):
+    return [ReplaceSubstrings(v, search_for, replace_with) for v in values]
+
+  if isinstance(values, dict):
+    # For dictionaries, do the search for both the key and values.
+    result = {}
+    for key, value in values.items():
+      new_key = ReplaceSubstrings(key, search_for, replace_with)
+      new_value = ReplaceSubstrings(value, search_for, replace_with)
+      result[new_key] = new_value
+    return result
+
+  # Assume everything else is unchanged.
+  return values
+
+def main():
+  parser = OptionParser()
+  parser.add_option("-r", "--replace", action="append",
+    help="Replaces substrings. If passed a=b, replaces all substrs a with b.")
+  (options, args) = parser.parse_args()
+
+  if len(args) != 1:
+    raise Exception("Need one argument which is the .gypi file to read.")
+
+  data = LoadPythonDictionary(args[0])
+  if options.replace:
+    # Do replacements for all specified patterns.
+    for replace in options.replace:
+      split = replace.split('=')
+      # Allow "foo=" to replace with nothing.
+      if len(split) == 1:
+        split.append('')
+      assert len(split) == 2, "Replacement must be of the form 'key=value'."
+      data = ReplaceSubstrings(data, split[0], split[1])
+
+  # Sometimes .gypi files use the GYP syntax with percents at the end of the
+  # variable name (to indicate not to overwrite a previously-defined value):
+  #   'foo%': 'bar',
+  # Convert these to regular variables.
+  for key in data:
+    if len(key) > 1 and key[len(key) - 1] == '%':
+      data[key[:-1]] = data[key]
+      del data[key]
+
+  print gn_helpers.ToGNString(data)
+
+if __name__ == '__main__':
+  try:
+    main()
+  except Exception, e:
+    print str(e)
+    sys.exit(1)
diff --git a/skia b/skia
new file mode 120000
index 0000000..945c9b4
--- /dev/null
+++ b/skia
@@ -0,0 +1 @@
+.
\ No newline at end of file
diff --git a/third_party/third_party.gni b/third_party/third_party.gni
index ede698c..d2583a9 100644
--- a/third_party/third_party.gni
+++ b/third_party/third_party.gni
@@ -9,13 +9,5 @@
 
     # Warnings are just noise if we're not maintaining the code.
     cflags = [ "-w" ]
-
-    # Chrome's GN environment sets up a bunch of default configs we don't need/want here.
-    configs -= [
-      "//build/config/clang:extra_warnings",
-      "//build/config/clang:find_bad_constructs",
-      "//build/config/compiler:chromium_code",
-      "//build/config:feature_flags",
-    ]
   }
 }