1#!/usr/bin/python3
2
3# Copyright 2020 The Chromium Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7import argparse
8import collections
9import json
10import os
11import subprocess
12import sys
13import tempfile
14
15
16class OrderedSet(collections.OrderedDict):
17  def add(self, value):
18    self[value] = True
19
20
21def compile_module(module, sources, settings, extras, tmpdir):
22  output_file_map = {}
23  if settings.whole_module_optimization:
24    output_file_map[''] = {
25        'object': os.path.join(settings.object_dir, module + '.o'),
26        'dependencies': os.path.join(tmpdir, module + '.d'),
27    }
28  else:
29    for source in sources:
30      name, _ = os.path.splitext(os.path.basename(source))
31      output_file_map[source] = {
32          'object': os.path.join(settings.object_dir, name + '.o'),
33          'dependencies': os.path.join(tmpdir, name + '.d'),
34      }
35
36  for key in ('module_path', 'header_path', 'depfile'):
37    path = getattr(settings, key)
38    if os.path.exists(path):
39      os.unlink(path)
40    if key == 'module_path':
41      for ext in '.swiftdoc', '.swiftsourceinfo':
42        path = os.path.splitext(getattr(settings, key))[0] + ext
43        if os.path.exists(path):
44          os.unlink(path)
45    directory = os.path.dirname(path)
46    if not os.path.exists(directory):
47      os.makedirs(directory)
48
49  if not os.path.exists(settings.object_dir):
50    os.makedirs(settings.object_dir)
51
52  for key in output_file_map:
53    path = output_file_map[key]['object']
54    if os.path.exists(path):
55      os.unlink(path)
56
57  output_file_map_path = os.path.join(tmpdir, module + '.json')
58  with open(output_file_map_path, 'w') as output_file_map_file:
59    output_file_map_file.write(json.dumps(output_file_map))
60    output_file_map_file.flush()
61
62  extra_args = []
63  if settings.bridge_header:
64    extra_args.extend([
65        '-import-objc-header',
66        os.path.abspath(settings.bridge_header),
67    ])
68
69  if settings.whole_module_optimization:
70    extra_args.append('-whole-module-optimization')
71
72  if settings.target:
73    extra_args.extend([
74        '-target',
75        settings.target,
76    ])
77
78  if settings.sdk:
79    extra_args.extend([
80        '-sdk',
81        os.path.abspath(settings.sdk),
82    ])
83
84  if settings.swift_version:
85    extra_args.extend([
86        '-swift-version',
87        settings.swift_version,
88    ])
89
90  if settings.include_dirs:
91    for include_dir in settings.include_dirs:
92      extra_args.append('-I' + include_dir)
93
94  process = subprocess.Popen([
95      'swiftc',
96      '-parse-as-library',
97      '-module-name',
98      module,
99      '-emit-object',
100      '-emit-dependencies',
101      '-emit-module',
102      '-emit-module-path',
103      settings.module_path,
104      '-emit-objc-header',
105      '-emit-objc-header-path',
106      settings.header_path,
107      '-output-file-map',
108      output_file_map_path,
109  ] + extra_args + extras + sources,
110                             stdout=subprocess.PIPE,
111                             stderr=subprocess.PIPE,
112                             universal_newlines=True)
113
114  stdout, stderr = process.communicate()
115  if process.returncode:
116    sys.stdout.write(stdout)
117    sys.stderr.write(stderr)
118    sys.exit(process.returncode)
119
120  depfile_content = collections.OrderedDict()
121  for key in output_file_map:
122    for line in open(output_file_map[key]['dependencies']):
123      output, inputs = line.split(' : ', 2)
124      _, ext = os.path.splitext(output)
125      if ext == '.o':
126        key = output
127      else:
128        key = os.path.splitext(settings.module_path)[0] + ext
129      if key not in depfile_content:
130        depfile_content[key] = OrderedSet()
131      for path in inputs.split():
132        depfile_content[key].add(path)
133
134  with open(settings.depfile, 'w') as depfile:
135    for key in depfile_content:
136      if not settings.depfile_filter or key in settings.depfile_filter:
137        inputs = depfile_content[key]
138        depfile.write('%s : %s\n' % (key, ' '.join(inputs)))
139
140
141def main(args):
142  parser = argparse.ArgumentParser(add_help=False)
143  parser.add_argument('-module-name', help='name of the Swift module')
144  parser.add_argument('-include',
145                      '-I',
146                      action='append',
147                      dest='include_dirs',
148                      help='add directory to header search path')
149  parser.add_argument('sources', nargs='+', help='Swift source file to compile')
150  parser.add_argument('-whole-module-optimization',
151                      action='store_true',
152                      help='enable whole module optimization')
153  parser.add_argument('-object-dir',
154                      help='path to the generated object files directory')
155  parser.add_argument('-module-path', help='path to the generated module file')
156  parser.add_argument('-header-path', help='path to the generated header file')
157  parser.add_argument('-bridge-header',
158                      help='path to the Objective-C bridge header')
159  parser.add_argument('-depfile', help='path to the generated depfile')
160  parser.add_argument('-swift-version',
161                      help='version of Swift language to support')
162  parser.add_argument('-depfile-filter',
163                      action='append',
164                      help='limit depfile to those files')
165  parser.add_argument('-target',
166                      action='store',
167                      help='generate code for the given target <triple>')
168  parser.add_argument('-sdk', action='store', help='compile against sdk')
169
170  parsed, extras = parser.parse_known_args(args)
171  with tempfile.TemporaryDirectory() as tmpdir:
172    compile_module(parsed.module_name, parsed.sources, parsed, extras, tmpdir)
173
174
175if __name__ == '__main__':
176  sys.exit(main(sys.argv[1:]))
177