xref: /aosp_15_r20/external/libchrome/build/gypi_to_gn.py (revision 635a864187cb8b6c713ff48b7e790a6b21769273)
1*635a8641SAndroid Build Coastguard Worker# Copyright 2014 The Chromium Authors. All rights reserved.
2*635a8641SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be
3*635a8641SAndroid Build Coastguard Worker# found in the LICENSE file.
4*635a8641SAndroid Build Coastguard Worker
5*635a8641SAndroid Build Coastguard Worker"""Converts a given gypi file to a python scope and writes the result to stdout.
6*635a8641SAndroid Build Coastguard Worker
7*635a8641SAndroid Build Coastguard WorkerUSING THIS SCRIPT IN CHROMIUM
8*635a8641SAndroid Build Coastguard Worker
9*635a8641SAndroid Build Coastguard WorkerForking Python to run this script in the middle of GN is slow, especially on
10*635a8641SAndroid Build Coastguard WorkerWindows, and it makes both the GYP and GN files harder to follow. You can't
11*635a8641SAndroid Build Coastguard Workeruse "git grep" to find files in the GN build any more, and tracking everything
12*635a8641SAndroid Build Coastguard Workerin GYP down requires a level of indirection. Any calls will have to be removed
13*635a8641SAndroid Build Coastguard Workerand cleaned up once the GYP-to-GN transition is complete.
14*635a8641SAndroid Build Coastguard Worker
15*635a8641SAndroid Build Coastguard WorkerAs a result, we only use this script when the list of files is large and
16*635a8641SAndroid Build Coastguard Workerfrequently-changing. In these cases, having one canonical list outweights the
17*635a8641SAndroid Build Coastguard Workerdownsides.
18*635a8641SAndroid Build Coastguard Worker
19*635a8641SAndroid Build Coastguard WorkerAs of this writing, the GN build is basically complete. It's likely that all
20*635a8641SAndroid Build Coastguard Workerlarge and frequently changing targets where this is appropriate use this
21*635a8641SAndroid Build Coastguard Workermechanism already. And since we hope to turn down the GYP build soon, the time
22*635a8641SAndroid Build Coastguard Workerhorizon is also relatively short. As a result, it is likely that no additional
23*635a8641SAndroid Build Coastguard Workeruses of this script should every be added to the build. During this later part
24*635a8641SAndroid Build Coastguard Workerof the transition period, we should be focusing more and more on the absolute
25*635a8641SAndroid Build Coastguard Workerreadability of the GN build.
26*635a8641SAndroid Build Coastguard Worker
27*635a8641SAndroid Build Coastguard Worker
28*635a8641SAndroid Build Coastguard WorkerHOW TO USE
29*635a8641SAndroid Build Coastguard Worker
30*635a8641SAndroid Build Coastguard WorkerIt is assumed that the file contains a toplevel dictionary, and this script
31*635a8641SAndroid Build Coastguard Workerwill return that dictionary as a GN "scope" (see example below). This script
32*635a8641SAndroid Build Coastguard Workerdoes not know anything about GYP and it will not expand variables or execute
33*635a8641SAndroid Build Coastguard Workerconditions.
34*635a8641SAndroid Build Coastguard Worker
35*635a8641SAndroid Build Coastguard WorkerIt will strip conditions blocks.
36*635a8641SAndroid Build Coastguard Worker
37*635a8641SAndroid Build Coastguard WorkerA variables block at the top level will be flattened so that the variables
38*635a8641SAndroid Build Coastguard Workerappear in the root dictionary. This way they can be returned to the GN code.
39*635a8641SAndroid Build Coastguard Worker
40*635a8641SAndroid Build Coastguard WorkerSay your_file.gypi looked like this:
41*635a8641SAndroid Build Coastguard Worker  {
42*635a8641SAndroid Build Coastguard Worker     'sources': [ 'a.cc', 'b.cc' ],
43*635a8641SAndroid Build Coastguard Worker     'defines': [ 'ENABLE_DOOM_MELON' ],
44*635a8641SAndroid Build Coastguard Worker  }
45*635a8641SAndroid Build Coastguard Worker
46*635a8641SAndroid Build Coastguard WorkerYou would call it like this:
47*635a8641SAndroid Build Coastguard Worker  gypi_values = exec_script("//build/gypi_to_gn.py",
48*635a8641SAndroid Build Coastguard Worker                            [ rebase_path("your_file.gypi") ],
49*635a8641SAndroid Build Coastguard Worker                            "scope",
50*635a8641SAndroid Build Coastguard Worker                            [ "your_file.gypi" ])
51*635a8641SAndroid Build Coastguard Worker
52*635a8641SAndroid Build Coastguard WorkerNotes:
53*635a8641SAndroid Build Coastguard Worker - The rebase_path call converts the gypi file from being relative to the
54*635a8641SAndroid Build Coastguard Worker   current build file to being system absolute for calling the script, which
55*635a8641SAndroid Build Coastguard Worker   will have a different current directory than this file.
56*635a8641SAndroid Build Coastguard Worker
57*635a8641SAndroid Build Coastguard Worker - The "scope" parameter tells GN to interpret the result as a series of GN
58*635a8641SAndroid Build Coastguard Worker   variable assignments.
59*635a8641SAndroid Build Coastguard Worker
60*635a8641SAndroid Build Coastguard Worker - The last file argument to exec_script tells GN that the given file is a
61*635a8641SAndroid Build Coastguard Worker   dependency of the build so Ninja can automatically re-run GN if the file
62*635a8641SAndroid Build Coastguard Worker   changes.
63*635a8641SAndroid Build Coastguard Worker
64*635a8641SAndroid Build Coastguard WorkerRead the values into a target like this:
65*635a8641SAndroid Build Coastguard Worker  component("mycomponent") {
66*635a8641SAndroid Build Coastguard Worker    sources = gypi_values.sources
67*635a8641SAndroid Build Coastguard Worker    defines = gypi_values.defines
68*635a8641SAndroid Build Coastguard Worker  }
69*635a8641SAndroid Build Coastguard Worker
70*635a8641SAndroid Build Coastguard WorkerSometimes your .gypi file will include paths relative to a different
71*635a8641SAndroid Build Coastguard Workerdirectory than the current .gn file. In this case, you can rebase them to
72*635a8641SAndroid Build Coastguard Workerbe relative to the current directory.
73*635a8641SAndroid Build Coastguard Worker  sources = rebase_path(gypi_values.sources, ".",
74*635a8641SAndroid Build Coastguard Worker                        "//path/gypi/input/values/are/relative/to")
75*635a8641SAndroid Build Coastguard Worker
76*635a8641SAndroid Build Coastguard WorkerThis script will tolerate a 'variables' in the toplevel dictionary or not. If
77*635a8641SAndroid Build Coastguard Workerthe toplevel dictionary just contains one item called 'variables', it will be
78*635a8641SAndroid Build Coastguard Workercollapsed away and the result will be the contents of that dictinoary. Some
79*635a8641SAndroid Build Coastguard Worker.gypi files are written with or without this, depending on how they expect to
80*635a8641SAndroid Build Coastguard Workerbe embedded into a .gyp file.
81*635a8641SAndroid Build Coastguard Worker
82*635a8641SAndroid Build Coastguard WorkerThis script also has the ability to replace certain substrings in the input.
83*635a8641SAndroid Build Coastguard WorkerGenerally this is used to emulate GYP variable expansion. If you passed the
84*635a8641SAndroid Build Coastguard Workerargument "--replace=<(foo)=bar" then all instances of "<(foo)" in strings in
85*635a8641SAndroid Build Coastguard Workerthe input will be replaced with "bar":
86*635a8641SAndroid Build Coastguard Worker
87*635a8641SAndroid Build Coastguard Worker  gypi_values = exec_script("//build/gypi_to_gn.py",
88*635a8641SAndroid Build Coastguard Worker                            [ rebase_path("your_file.gypi"),
89*635a8641SAndroid Build Coastguard Worker                              "--replace=<(foo)=bar"],
90*635a8641SAndroid Build Coastguard Worker                            "scope",
91*635a8641SAndroid Build Coastguard Worker                            [ "your_file.gypi" ])
92*635a8641SAndroid Build Coastguard Worker
93*635a8641SAndroid Build Coastguard Worker"""
94*635a8641SAndroid Build Coastguard Worker
95*635a8641SAndroid Build Coastguard Workerimport gn_helpers
96*635a8641SAndroid Build Coastguard Workerfrom optparse import OptionParser
97*635a8641SAndroid Build Coastguard Workerimport sys
98*635a8641SAndroid Build Coastguard Worker
99*635a8641SAndroid Build Coastguard Workerdef LoadPythonDictionary(path):
100*635a8641SAndroid Build Coastguard Worker  file_string = open(path).read()
101*635a8641SAndroid Build Coastguard Worker  try:
102*635a8641SAndroid Build Coastguard Worker    file_data = eval(file_string, {'__builtins__': None}, None)
103*635a8641SAndroid Build Coastguard Worker  except SyntaxError as e:
104*635a8641SAndroid Build Coastguard Worker    e.filename = path
105*635a8641SAndroid Build Coastguard Worker    raise
106*635a8641SAndroid Build Coastguard Worker  except Exception as e:
107*635a8641SAndroid Build Coastguard Worker    raise Exception("Unexpected error while reading %s: %s" % (path, str(e)))
108*635a8641SAndroid Build Coastguard Worker
109*635a8641SAndroid Build Coastguard Worker  assert isinstance(file_data, dict), "%s does not eval to a dictionary" % path
110*635a8641SAndroid Build Coastguard Worker
111*635a8641SAndroid Build Coastguard Worker  # Flatten any variables to the top level.
112*635a8641SAndroid Build Coastguard Worker  if 'variables' in file_data:
113*635a8641SAndroid Build Coastguard Worker    file_data.update(file_data['variables'])
114*635a8641SAndroid Build Coastguard Worker    del file_data['variables']
115*635a8641SAndroid Build Coastguard Worker
116*635a8641SAndroid Build Coastguard Worker  # Strip all elements that this script can't process.
117*635a8641SAndroid Build Coastguard Worker  elements_to_strip = [
118*635a8641SAndroid Build Coastguard Worker    'conditions',
119*635a8641SAndroid Build Coastguard Worker    'target_conditions',
120*635a8641SAndroid Build Coastguard Worker    'target_defaults',
121*635a8641SAndroid Build Coastguard Worker    'targets',
122*635a8641SAndroid Build Coastguard Worker    'includes',
123*635a8641SAndroid Build Coastguard Worker    'actions',
124*635a8641SAndroid Build Coastguard Worker  ]
125*635a8641SAndroid Build Coastguard Worker  for element in elements_to_strip:
126*635a8641SAndroid Build Coastguard Worker    if element in file_data:
127*635a8641SAndroid Build Coastguard Worker      del file_data[element]
128*635a8641SAndroid Build Coastguard Worker
129*635a8641SAndroid Build Coastguard Worker  return file_data
130*635a8641SAndroid Build Coastguard Worker
131*635a8641SAndroid Build Coastguard Worker
132*635a8641SAndroid Build Coastguard Workerdef ReplaceSubstrings(values, search_for, replace_with):
133*635a8641SAndroid Build Coastguard Worker  """Recursively replaces substrings in a value.
134*635a8641SAndroid Build Coastguard Worker
135*635a8641SAndroid Build Coastguard Worker  Replaces all substrings of the "search_for" with "repace_with" for all
136*635a8641SAndroid Build Coastguard Worker  strings occurring in "values". This is done by recursively iterating into
137*635a8641SAndroid Build Coastguard Worker  lists as well as the keys and values of dictionaries."""
138*635a8641SAndroid Build Coastguard Worker  if isinstance(values, str):
139*635a8641SAndroid Build Coastguard Worker    return values.replace(search_for, replace_with)
140*635a8641SAndroid Build Coastguard Worker
141*635a8641SAndroid Build Coastguard Worker  if isinstance(values, list):
142*635a8641SAndroid Build Coastguard Worker    return [ReplaceSubstrings(v, search_for, replace_with) for v in values]
143*635a8641SAndroid Build Coastguard Worker
144*635a8641SAndroid Build Coastguard Worker  if isinstance(values, dict):
145*635a8641SAndroid Build Coastguard Worker    # For dictionaries, do the search for both the key and values.
146*635a8641SAndroid Build Coastguard Worker    result = {}
147*635a8641SAndroid Build Coastguard Worker    for key, value in values.items():
148*635a8641SAndroid Build Coastguard Worker      new_key = ReplaceSubstrings(key, search_for, replace_with)
149*635a8641SAndroid Build Coastguard Worker      new_value = ReplaceSubstrings(value, search_for, replace_with)
150*635a8641SAndroid Build Coastguard Worker      result[new_key] = new_value
151*635a8641SAndroid Build Coastguard Worker    return result
152*635a8641SAndroid Build Coastguard Worker
153*635a8641SAndroid Build Coastguard Worker  # Assume everything else is unchanged.
154*635a8641SAndroid Build Coastguard Worker  return values
155*635a8641SAndroid Build Coastguard Worker
156*635a8641SAndroid Build Coastguard Workerdef main():
157*635a8641SAndroid Build Coastguard Worker  parser = OptionParser()
158*635a8641SAndroid Build Coastguard Worker  parser.add_option("-r", "--replace", action="append",
159*635a8641SAndroid Build Coastguard Worker    help="Replaces substrings. If passed a=b, replaces all substrs a with b.")
160*635a8641SAndroid Build Coastguard Worker  (options, args) = parser.parse_args()
161*635a8641SAndroid Build Coastguard Worker
162*635a8641SAndroid Build Coastguard Worker  if len(args) != 1:
163*635a8641SAndroid Build Coastguard Worker    raise Exception("Need one argument which is the .gypi file to read.")
164*635a8641SAndroid Build Coastguard Worker
165*635a8641SAndroid Build Coastguard Worker  data = LoadPythonDictionary(args[0])
166*635a8641SAndroid Build Coastguard Worker  if options.replace:
167*635a8641SAndroid Build Coastguard Worker    # Do replacements for all specified patterns.
168*635a8641SAndroid Build Coastguard Worker    for replace in options.replace:
169*635a8641SAndroid Build Coastguard Worker      split = replace.split('=')
170*635a8641SAndroid Build Coastguard Worker      # Allow "foo=" to replace with nothing.
171*635a8641SAndroid Build Coastguard Worker      if len(split) == 1:
172*635a8641SAndroid Build Coastguard Worker        split.append('')
173*635a8641SAndroid Build Coastguard Worker      assert len(split) == 2, "Replacement must be of the form 'key=value'."
174*635a8641SAndroid Build Coastguard Worker      data = ReplaceSubstrings(data, split[0], split[1])
175*635a8641SAndroid Build Coastguard Worker
176*635a8641SAndroid Build Coastguard Worker  # Sometimes .gypi files use the GYP syntax with percents at the end of the
177*635a8641SAndroid Build Coastguard Worker  # variable name (to indicate not to overwrite a previously-defined value):
178*635a8641SAndroid Build Coastguard Worker  #   'foo%': 'bar',
179*635a8641SAndroid Build Coastguard Worker  # Convert these to regular variables.
180*635a8641SAndroid Build Coastguard Worker  for key in data:
181*635a8641SAndroid Build Coastguard Worker    if len(key) > 1 and key[len(key) - 1] == '%':
182*635a8641SAndroid Build Coastguard Worker      data[key[:-1]] = data[key]
183*635a8641SAndroid Build Coastguard Worker      del data[key]
184*635a8641SAndroid Build Coastguard Worker
185*635a8641SAndroid Build Coastguard Worker  print(gn_helpers.ToGNString(data))
186*635a8641SAndroid Build Coastguard Worker
187*635a8641SAndroid Build Coastguard Workerif __name__ == '__main__':
188*635a8641SAndroid Build Coastguard Worker  try:
189*635a8641SAndroid Build Coastguard Worker    main()
190*635a8641SAndroid Build Coastguard Worker  except Exception as e:
191*635a8641SAndroid Build Coastguard Worker    print(str(e))
192*635a8641SAndroid Build Coastguard Worker    sys.exit(1)
193