summaryrefslogtreecommitdiffstats
path: root/native_client_sdk/src/build_tools/build_projects.py
blob: f1a76c23686687c97db0e02fc936c17fbfea722f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
#!/usr/bin/env python
# Copyright (c) 2013 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.

import argparse
import multiprocessing
import os
import posixpath
import sys
import urllib2

import buildbot_common
import build_version
import generate_make
import parse_dsc

from build_paths import SDK_SRC_DIR, OUT_DIR, SDK_RESOURCE_DIR
from build_paths import GSTORE
from generate_index import LandingPage

sys.path.append(os.path.join(SDK_SRC_DIR, 'tools'))
import getos


MAKE = 'nacl_sdk/make_3.99.90-26-gf80222c/make.exe'
LIB_DICT = {
  'linux': [],
  'mac': [],
  'win': ['x86_32']
}
VALID_TOOLCHAINS = [
  'newlib',
  'clang-newlib',
  'glibc',
  'pnacl',
  'win',
  'linux',
  'mac',
]

# Global verbosity setting.
# If set to True (normally via a command line arg) then build_projects will
# add V=1 to all calls to 'make'
verbose = False


def Trace(msg):
  if verbose:
    sys.stderr.write(str(msg) + '\n')


def CopyFilesFromTo(filelist, srcdir, dstdir):
  for filename in filelist:
    srcpath = os.path.join(srcdir, filename)
    dstpath = os.path.join(dstdir, filename)
    buildbot_common.CopyFile(srcpath, dstpath)


def UpdateHelpers(pepperdir, clobber=False):
  tools_dir = os.path.join(pepperdir, 'tools')
  if not os.path.exists(tools_dir):
    buildbot_common.ErrorExit('SDK tools dir is missing: %s' % tools_dir)

  exampledir = os.path.join(pepperdir, 'examples')
  if clobber:
    buildbot_common.RemoveDir(exampledir)
  buildbot_common.MakeDir(exampledir)

  # Copy files for individual build and landing page
  files = ['favicon.ico', 'httpd.cmd', 'index.css', 'index.js',
      'button_close.png', 'button_close_hover.png']
  CopyFilesFromTo(files, SDK_RESOURCE_DIR, exampledir)

  # Copy tools scripts and make includes
  buildbot_common.CopyDir(os.path.join(SDK_SRC_DIR, 'tools', '*.py'),
      tools_dir)
  buildbot_common.CopyDir(os.path.join(SDK_SRC_DIR, 'tools', '*.mk'),
      tools_dir)

  # Copy tools/lib scripts
  tools_lib_dir = os.path.join(pepperdir, 'tools', 'lib')
  buildbot_common.MakeDir(tools_lib_dir)
  buildbot_common.CopyDir(os.path.join(SDK_SRC_DIR, 'tools', 'lib', '*.py'),
      tools_lib_dir)

  # On Windows add a prebuilt make
  if getos.GetPlatform() == 'win':
    buildbot_common.BuildStep('Add MAKE')
    make_url = posixpath.join(GSTORE, MAKE)
    make_exe = os.path.join(tools_dir, 'make.exe')
    with open(make_exe, 'wb') as f:
      f.write(urllib2.urlopen(make_url).read())


def ValidateToolchains(toolchains):
  invalid_toolchains = set(toolchains) - set(VALID_TOOLCHAINS)
  if invalid_toolchains:
    buildbot_common.ErrorExit('Invalid toolchain(s): %s' % (
        ', '.join(invalid_toolchains)))


def GetDeps(projects):
  out = {}

  # Build list of all project names
  localtargets = [proj['NAME'] for proj in projects]

  # For each project
  for proj in projects:
    deplist = []
    # generate a list of dependencies
    for targ in proj.get('TARGETS', []):
      deplist.extend(targ.get('DEPS', []) + targ.get('LIBS', []))

    # and add dependencies to targets built in this subtree
    localdeps = [dep for dep in deplist if dep in localtargets]
    if localdeps:
      out[proj['NAME']] = localdeps

  return out


def UpdateProjects(pepperdir, project_tree, toolchains,
                   clobber=False, configs=None, first_toolchain=False):
  if configs is None:
    configs = ['Debug', 'Release']
  if not os.path.exists(os.path.join(pepperdir, 'tools')):
    buildbot_common.ErrorExit('Examples depend on missing tools.')
  if not os.path.exists(os.path.join(pepperdir, 'toolchain')):
    buildbot_common.ErrorExit('Examples depend on missing toolchains.')

  ValidateToolchains(toolchains)

  # Create the library output directories
  libdir = os.path.join(pepperdir, 'lib')
  platform = getos.GetPlatform()
  for config in configs:
    for arch in LIB_DICT[platform]:
      dirpath = os.path.join(libdir, '%s_%s_host' % (platform, arch), config)
      if clobber:
        buildbot_common.RemoveDir(dirpath)
      buildbot_common.MakeDir(dirpath)

  landing_page = None
  for branch, projects in project_tree.iteritems():
    dirpath = os.path.join(pepperdir, branch)
    if clobber:
      buildbot_common.RemoveDir(dirpath)
    buildbot_common.MakeDir(dirpath)
    targets = [desc['NAME'] for desc in projects]
    deps = GetDeps(projects)

    # Generate master make for this branch of projects
    generate_make.GenerateMasterMakefile(pepperdir,
                                         os.path.join(pepperdir, branch),
                                         targets, deps)

    if branch.startswith('examples') and not landing_page:
      landing_page = LandingPage()

    # Generate individual projects
    for desc in projects:
      srcroot = os.path.dirname(desc['FILEPATH'])
      generate_make.ProcessProject(pepperdir, srcroot, pepperdir, desc,
                                   toolchains, configs=configs,
                                   first_toolchain=first_toolchain)

      if branch.startswith('examples'):
        landing_page.AddDesc(desc)

  if landing_page:
    # Generate the landing page text file.
    index_html = os.path.join(pepperdir, 'examples', 'index.html')
    index_template = os.path.join(SDK_RESOURCE_DIR, 'index.html.template')
    with open(index_html, 'w') as fh:
      out = landing_page.GeneratePage(index_template)
      fh.write(out)

  # Generate top Make for examples
  targets = ['api', 'demo', 'getting_started', 'tutorial']
  targets = [x for x in targets if 'examples/'+x in project_tree]
  branch_name = 'examples'
  generate_make.GenerateMasterMakefile(pepperdir,
                                       os.path.join(pepperdir, branch_name),
                                       targets, {})


def BuildProjectsBranch(pepperdir, branch, deps, clean, config, args=None):
  make_dir = os.path.join(pepperdir, branch)
  print "\nMake: " + make_dir

  if getos.GetPlatform() == 'win':
    # We need to modify the environment to build host on Windows.
    make = os.path.join(make_dir, 'make.bat')
  else:
    make = 'make'

  env = None
  if os.environ.get('USE_GOMA') == '1':
    env = dict(os.environ)
    env['NACL_COMPILER_PREFIX'] = 'gomacc'
    # Add -m32 to the CFLAGS when building using i686-nacl-gcc
    # otherwise goma won't recognise it as different to the x86_64
    # build.
    env['X86_32_CFLAGS'] = '-m32'
    env['X86_32_CXXFLAGS'] = '-m32'
    jobs = '50'
  else:
    jobs = str(multiprocessing.cpu_count())

  make_cmd = [make, '-j', jobs]

  make_cmd.append('CONFIG='+config)
  if not deps:
    make_cmd.append('IGNORE_DEPS=1')

  if verbose:
    make_cmd.append('V=1')

  if args:
    make_cmd += args
  else:
    make_cmd.append('TOOLCHAIN=all')

  buildbot_common.Run(make_cmd, cwd=make_dir, env=env)
  if clean:
    # Clean to remove temporary files but keep the built
    buildbot_common.Run(make_cmd + ['clean'], cwd=make_dir, env=env)


def BuildProjects(pepperdir, project_tree, deps=True,
                  clean=False, config='Debug'):
  # Make sure we build libraries (which live in 'src') before
  # any of the examples.
  build_first = [p for p in project_tree if p != 'src']
  build_second = [p for p in project_tree if p == 'src']

  for branch in build_first + build_second:
    BuildProjectsBranch(pepperdir, branch, deps, clean, config)


def main(args):
  parser = argparse.ArgumentParser(description=__doc__)
  parser.add_argument('-c', '--clobber',
      help='Clobber project directories before copying new files',
      action='store_true', default=False)
  parser.add_argument('-b', '--build',
      help='Build the projects. Otherwise the projects are only copied.',
      action='store_true')
  parser.add_argument('--config',
      help='Choose configuration to build (Debug or Release).  Builds both '
           'by default')
  parser.add_argument('-x', '--experimental',
      help='Build experimental projects', action='store_true')
  parser.add_argument('-t', '--toolchain',
      help='Build using toolchain. Can be passed more than once.',
      action='append', default=[])
  parser.add_argument('-d', '--dest',
      help='Select which build destinations (project types) are valid.',
      action='append')
  parser.add_argument('projects', nargs='*',
      help='Select which projects to build.')
  parser.add_argument('-v', '--verbose', action='store_true')

  # To setup bash completion for this command first install optcomplete
  # and then add this line to your .bashrc:
  #  complete -F _optcomplete build_projects.py
  try:
    import optcomplete
    optcomplete.autocomplete(parser)
  except ImportError:
    pass

  options = parser.parse_args(args)

  global verbose
  if options.verbose:
    verbose = True

  buildbot_common.verbose = verbose

  if 'NACL_SDK_ROOT' in os.environ:
    # We don't want the currently configured NACL_SDK_ROOT to have any effect
    # on the build.
    del os.environ['NACL_SDK_ROOT']

  pepper_ver = str(int(build_version.ChromeMajorVersion()))
  pepperdir = os.path.join(OUT_DIR, 'pepper_' + pepper_ver)

  if not options.toolchain:
    # Order matters here: the default toolchain for an example's Makefile will
    # be the first toolchain in this list that is available in the example.
    # e.g. If an example supports newlib and glibc, then the default will be
    # newlib.
    options.toolchain = ['pnacl', 'newlib', 'glibc', 'host', 'clang-newlib']

  if 'host' in options.toolchain:
    options.toolchain.remove('host')
    options.toolchain.append(getos.GetPlatform())
    Trace('Adding platform: ' + getos.GetPlatform())

  ValidateToolchains(options.toolchain)

  filters = {}
  if options.toolchain:
    filters['TOOLS'] = options.toolchain
    Trace('Filter by toolchain: ' + str(options.toolchain))
  if not options.experimental:
    filters['EXPERIMENTAL'] = False
  if options.dest:
    filters['DEST'] = options.dest
    Trace('Filter by type: ' + str(options.dest))
  if options.projects:
    filters['NAME'] = options.projects
    Trace('Filter by name: ' + str(options.projects))

  try:
    project_tree = parse_dsc.LoadProjectTree(SDK_SRC_DIR, include=filters)
  except parse_dsc.ValidationError as e:
    buildbot_common.ErrorExit(str(e))

  if verbose:
    parse_dsc.PrintProjectTree(project_tree)

  UpdateHelpers(pepperdir, clobber=options.clobber)
  UpdateProjects(pepperdir, project_tree, options.toolchain,
                 clobber=options.clobber)

  if options.build:
    if options.config:
      configs = [options.config]
    else:
      configs = ['Debug', 'Release']
    for config in configs:
      BuildProjects(pepperdir, project_tree, config=config, deps=False)

  return 0


if __name__ == '__main__':
  script_name = os.path.basename(sys.argv[0])
  try:
    sys.exit(main(sys.argv[1:]))
  except parse_dsc.ValidationError as e:
    buildbot_common.ErrorExit('%s: %s' % (script_name, e))
  except KeyboardInterrupt:
    buildbot_common.ErrorExit('%s: interrupted' % script_name)