From 7fbfbbe8f435fde7233c78f4f2dd1efb4fdd324c Mon Sep 17 00:00:00 2001 From: mtklein Date: Thu, 21 Jul 2016 12:25:45 -0700 Subject: 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 --- .gn | 2 +- BUILD.gn | 44 +----- DEPS | 31 +++- gn.py | 37 ----- gn/BUILD.gn | 122 +++++++++++++++ gn/BUILDCONFIG.gn | 69 +++++++++ gn/gn_helpers.py | 351 ++++++++++++++++++++++++++++++++++++++++++++ gn/gypi_to_gn.py | 191 ++++++++++++++++++++++++ skia | 1 + third_party/third_party.gni | 8 - 10 files changed, 771 insertions(+), 85 deletions(-) delete mode 100755 gn.py create mode 100644 gn/BUILD.gn create mode 100644 gn/BUILDCONFIG.gn create mode 100644 gn/gn_helpers.py create mode 100644 gn/gypi_to_gn.py create mode 120000 skia 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 + + = (integer | string | boolean | ) + + 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", - ] } } -- cgit v1.2.3