aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar mtklein <mtklein@chromium.org>2016-07-21 12:25:45 -0700
committerGravatar Commit bot <commit-bot@chromium.org>2016-07-21 12:25:45 -0700
commit7fbfbbe8f435fde7233c78f4f2dd1efb4fdd324c (patch)
tree94d76299873e210f9f9f839ee25138a4a3dd8fe3
parent680e2e9a9eea28aa816ecdb88b4af1b4861393a0 (diff)
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
-rw-r--r--.gn2
-rw-r--r--BUILD.gn44
-rw-r--r--DEPS31
-rwxr-xr-xgn.py37
-rw-r--r--gn/BUILD.gn122
-rw-r--r--gn/BUILDCONFIG.gn69
-rw-r--r--gn/gn_helpers.py351
-rw-r--r--gn/gypi_to_gn.py191
l---------skia1
-rw-r--r--third_party/third_party.gni8
10 files changed, 771 insertions, 85 deletions
diff --git a/.gn b/.gn
index c6fefbae8d..dce427f6eb 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 c346673cfe..7ead5f7bbf 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -51,16 +51,6 @@ config("skia_private") {
# 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_configs = [
":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 @@ core_gypi = exec_script("//build/gypi_to_gn.py",
"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 @@ effects_gypi = exec_script("//build/gypi_to_gn.py",
"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 @@ gpu_gypi = exec_script("//build/gypi_to_gn.py",
"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 @@ opts_gypi = exec_script("//build/gypi_to_gn.py",
"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 @@ pdf_gypi = exec_script("//build/gypi_to_gn.py",
"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 @@ utils_gypi = exec_script("//build/gypi_to_gn.py",
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_ssse3") {
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_sse41") {
source_set("opts_avx") {
configs += skia_library_configs
- configs -= unwanted_configs
sources = opts_gypi.avx_sources
cflags = [ "-mavx" ]
@@ -165,7 +138,6 @@ source_set("opts_avx") {
component("skia") {
public_configs = [ ":skia_public" ]
configs += skia_library_configs
- configs -= unwanted_configs
deps = [
":opts_avx",
@@ -174,7 +146,7 @@ component("skia") {
"//third_party/zlib",
]
- libs = []
+ libs = [ "pthread" ]
sources = []
sources += core_gypi.sources
@@ -254,8 +226,6 @@ component("skia") {
}
executable("example") {
- configs -= unwanted_configs
-
sources = [
"cmake/example.cpp",
]
diff --git a/DEPS b/DEPS
index 3e87e0e5bb..5964f26675 100644
--- a/DEPS
+++ b/DEPS
@@ -3,9 +3,7 @@ use_relative_paths = True
# 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 @@ deps_os = {
}
}
+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 b17280a1b1..0000000000
--- 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 0000000000..547f57ebea
--- /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 0000000000..31056bda5f
--- /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 0000000000..fb94d5496c
--- /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 0000000000..08007088a8
--- /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 0000000000..945c9b46d6
--- /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 ede698c4af..d2583a9b7d 100644
--- a/third_party/third_party.gni
+++ b/third_party/third_party.gni
@@ -9,13 +9,5 @@ template("third_party") {
# 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",
- ]
}
}