Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / build / gypi_to_gn.py
1 # Copyright 2014 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4
5 """Converts a given gypi file to a python scope and writes the result to stdout.
6
7 It is assumed that the file contains a toplevel dictionary, and this script
8 will return that dictionary as a GN "scope" (see example below). This script
9 does not know anything about GYP and it will not expand variables or execute
10 conditions (it will check for the presence of a "conditions" value in the
11 dictionary and will abort if it is present). It also does not support nested
12 dictionaries.
13
14 Say your_file.gypi looked like this:
15   {
16      'sources': [ 'a.cc', 'b.cc' ],
17      'defines': [ 'ENABLE_DOOM_MELON' ],
18   }
19
20 You would call it like this:
21   gypi_values = exec_script("//build/gypi_to_gn.py",
22                             [ rebase_path("your_file.gypi") ],
23                             "scope",
24                             [ "your_file.gypi" ])
25
26 Notes:
27  - The rebase_path call converts the gypi file from being relative to the
28    current build file to being system absolute for calling the script, which
29    will have a different current directory than this file.
30
31  - The "scope" parameter tells GN to interpret the result as a series of GN
32    variable assignments.
33
34  - The last file argument to exec_script tells GN that the given file is a
35    dependency of the build so Ninja can automatically re-run GN if the file
36    changes.
37
38 Read the values into a target like this:
39   component("mycomponent") {
40     sources = gypi_values.sources
41     defines = gypi_values.defines
42   }
43
44 Sometimes your .gypi file will include paths relative to a different
45 directory than the current .gn file. In this case, you can rebase them to
46 be relative to the current directory.
47   sources = rebase_path(gypi_values.sources, ".",
48                         "//path/gypi/input/values/are/relative/to")
49
50 This script will tolerate a 'variables' in the toplevel dictionary or not. If
51 the toplevel dictionary just contains one item called 'variables', it will be
52 collapsed away and the result will be the contents of that dictinoary. Some
53 .gypi files are written with or without this, depending on how they expect to
54 be embedded into a .gyp file.
55
56 This script also has the ability to replace certain substrings in the input.
57 Generally this is used to emulate GYP variable expansion. If you passed the
58 argument "--replace=<(foo)=bar" then all instances of "<(foo)" in strings in
59 the input will be replaced with "bar":
60
61   gypi_values = exec_script("//build/gypi_to_gn.py",
62                             [ rebase_path("your_file.gypi"),
63                               "--replace=<(foo)=bar"],
64                             "scope",
65                             [ "your_file.gypi" ])
66
67 """
68
69 import gn_helpers
70 from optparse import OptionParser
71 import sys
72
73 def LoadPythonDictionary(path):
74   file_string = open(path).read()
75   try:
76     file_data = eval(file_string, {'__builtins__': None}, None)
77   except SyntaxError, e:
78     e.filename = path
79     raise
80   except Exception, e:
81     raise Exception("Unexpected error while reading %s: %s" % (path, str(e)))
82
83   assert isinstance(file_data, dict), "%s does not eval to a dictionary" % path
84   assert 'conditions' not in file_data, \
85       "The file %s has conditions in it, these aren't supported." % path
86
87   # If the contents of the root is a dictionary with exactly one kee
88   # "variables", promote the contents of that to the top level. Some .gypi
89   # files contain this and some don't depending on how they expect to be
90   # embedded in a .gyp file. We don't actually care either way so collapse it
91   # away.
92   if len(file_data) == 1 and 'variables' in file_data:
93     return file_data['variables']
94
95   return file_data
96
97
98 def ReplaceSubstrings(values, search_for, replace_with):
99   """Recursively replaces substrings in a value.
100
101   Replaces all substrings of the "search_for" with "repace_with" for all
102   strings occurring in "values". This is done by recursively iterating into
103   lists as well as the keys and values of dictionaries."""
104   if isinstance(values, str):
105     return values.replace(search_for, replace_with)
106
107   if isinstance(values, list):
108     return [ReplaceSubstrings(v, search_for, replace_with) for v in values]
109
110   if isinstance(values, dict):
111     # For dictionaries, do the search for both the key and values.
112     result = {}
113     for key, value in values.items():
114       new_key = ReplaceSubstrings(key, search_for, replace_with)
115       new_value = ReplaceSubstrings(value, search_for, replace_with)
116       result[new_key] = new_value
117     return result
118
119   # Assume everything else is unchanged.
120   return values
121
122 def main():
123   parser = OptionParser()
124   parser.add_option("-r", "--replace", action="append",
125     help="Replaces substrings. If passed a=b, replaces all substrs a with b.")
126   (options, args) = parser.parse_args()
127
128   if len(args) != 1:
129     raise Exception("Need one argument which is the .gypi file to read.")
130
131   data = LoadPythonDictionary(args[0])
132   if options.replace:
133     # Do replacements for all specified patterns.
134     for replace in options.replace:
135       split = replace.split('=')
136       # Allow "foo=" to replace with nothing.
137       if len(split) == 1:
138         split.append('')
139       assert len(split) == 2, "Replacement must be of the form 'key=value'."
140       data = ReplaceSubstrings(data, split[0], split[1])
141
142   print gn_helpers.ToGNString(data)
143
144 if __name__ == '__main__':
145   try:
146     main()
147   except Exception, e:
148     print str(e)
149     sys.exit(1)