OLD | NEW |
| (Empty) |
1 #!/usr/bin/env python | |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
3 # Use of this source code is governed by a BSD-style license that can be | |
4 # found in the LICENSE file. | |
5 | |
6 ''' A tool to setup the NaCl build env and invoke a command such as make ''' | |
7 | |
8 __author__ = 'gwink@google.com (Georges Winkenbach)' | |
9 | |
10 import optparse | |
11 import os | |
12 import subprocess | |
13 import sys | |
14 | |
15 # The default sdk platform to use if the user doesn't specify one. | |
16 __DEFAULT_SDK_PLATFORM = 'pepper_15' | |
17 | |
18 # Usage info. | |
19 __GLOBAL_HELP = '''%prog options [command] | |
20 | |
21 set-nacl-env is a utility that sets up the environment required to build | |
22 NaCl modules and invokes an optional command in a shell. If no command | |
23 is specified, set-nacl-env spawns a new shell instead. Optionally, the user | |
24 can request that the settings are printed to stdout. | |
25 ''' | |
26 | |
27 # Map the string stored in |sys.platform| into a toolchain host specifier. | |
28 __PLATFORM_TO_HOST_MAP = { | |
29 'win32': 'windows', | |
30 'cygwin': 'windows', | |
31 'linux2': 'linux', | |
32 'darwin': 'mac', | |
33 } | |
34 | |
35 # Map key triplet of (host, arch, variant) keys to the corresponding subdir in | |
36 # the toolchain path. For instance (mac, x86-32, newlib) maps to mac_x86_newlib. | |
37 # Note to NaCl eng.: this map is duplicated in nack_utils.py; you must keep them | |
38 # synched. | |
39 __HOST_TO_TOOLCHAIN_MAP = { | |
40 'mac': { # Host arch variant | |
41 'x86-32': { | |
42 'newlib': 'mac_x86_newlib', # Mac x86-32 newlib | |
43 'glibc' : 'mac_x86'}, # Mac x86-32 glibc | |
44 'x86-64': { | |
45 'newlib': 'mac_x86_newlib', # Mac x86-64 newlib | |
46 'glibc' : 'mac_x86'}, # Mac x86-64 glibc | |
47 }, | |
48 'windows': { | |
49 'x86-32': { | |
50 'newlib': 'win_x86_newlib', # Windows x86-32 newlib | |
51 'glibc' : 'win_x86'}, # Windows x86-32 glibc | |
52 'x86-64': { | |
53 'newlib': 'win_x86_newlib', # Windows x86-64 newlib | |
54 'glibc' : 'win_x86'}, # Windows x86-64 glibc | |
55 }, | |
56 'linux': { | |
57 'x86-32': { | |
58 'newlib': 'linux_x86_newlib', # Windows x86-32 newlib | |
59 'glibc' : 'linux_x86'}, # Windows x86-32 glibc | |
60 'x86-64': { | |
61 'newlib': 'linux_x86_newlib', # Windows x86-64 newlib | |
62 'glibc' : 'linux_x86'}, # Windows x86-64 glibc | |
63 }, | |
64 } | |
65 | |
66 # Map architecture specification to the corresponding tool-name prefix. | |
67 # @private | |
68 __VALID_ARCH_SPECS = { | |
69 'x86-32': 'i686', | |
70 'x86-64': 'x86_64', | |
71 } | |
72 | |
73 # Valid lib variants. | |
74 __VALID_VARIANT = ['glibc', 'newlib'] | |
75 | |
76 # Lists of env keys for build tools. Note: Each matching value is actually a | |
77 # format template with fields for 'prefix' such as 'i686-nacl-' and 'extras' | |
78 # such as ' -m64'. | |
79 __BUILD_TOOLS = { | |
80 'CC': '{prefix}gcc{extras}', | |
81 'CXX': '{prefix}g++{extras}', | |
82 'AR': '{prefix}ar{extras}', | |
83 'LINK': '{prefix}g++{extras}', | |
84 'STRIP': '{prefix}strip', | |
85 'RANLIB': '{prefix}ranlib', | |
86 } | |
87 | |
88 # List of env keys for build options with corresponding settings that are | |
89 # common to all build configurations. | |
90 __BUILD_OPTIONS = { | |
91 'CFLAGS': ['-std=gnu99', '-Wall', '-Wswitch-enum', '-g'], | |
92 'CXXFLAGS': ['-std=gnu++98', '-Wswitch-enum', '-g', '-pthread'], | |
93 'CPPFLAGS': ['-D_GNU_SOURCE=1', '-D__STDC_FORMAT_MACROS=1'], | |
94 'LDFLAGS': [], | |
95 } | |
96 | |
97 # All the build-flags env keys in one list. | |
98 __ALL_ENV_KEYS = __BUILD_TOOLS.keys() + __BUILD_OPTIONS.keys() | |
99 | |
100 # Map build types to the corresponding build flags. | |
101 __BUILD_TYPES = { | |
102 'debug': ['-O0'], | |
103 'release': ['-O3'], | |
104 } | |
105 | |
106 | |
107 def FormatOptionList(option_list, prefix='', separator=' '): | |
108 ''' Format a list of build-option items into a string. | |
109 | |
110 Format a list of build-option items into a string suitable for output. | |
111 | |
112 Args: | |
113 prefix: a prefix string to prepend to each list item. For instance, | |
114 prefix='-D' with item='__DEBUG' generates '-D__DEBUG'. | |
115 separator: a separator string to insert between items. | |
116 | |
117 Returns: | |
118 A formatted string. An empty string if list is empty. | |
119 ''' | |
120 return separator.join([prefix + item for item in option_list]) | |
121 | |
122 | |
123 def GetNaclSdkRoot(): | |
124 ''' Produce a string with the full path to the NaCl SDK root. | |
125 | |
126 The path to nacl-sdk root is derived from one of two sources. First, if | |
127 NACL_SDK_ROOT is defined in env it is assumed to contain the desired sdk | |
128 root. That makes it possible for this tool to run from any location. If | |
129 NACL_SDK_ROOT is not defined or is empty, the sdk root is taken as the | |
130 parent directory to this script's file. This works well when this script | |
131 is ran from the sdk_tools directory in the standard SDK installation. | |
132 | |
133 Returns: | |
134 A string with the path to the NaCl SDK root. | |
135 ''' | |
136 if 'NACL_SDK_ROOT' in os.environ: | |
137 return os.environ['NACL_SDK_ROOT'] | |
138 else: | |
139 SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) | |
140 SRC_DIR = os.path.dirname(os.path.dirname(os.path.dirname(SCRIPT_DIR))) | |
141 return os.path.join(SRC_DIR, 'native_client') | |
142 | |
143 | |
144 def GetToolchainPath(options): | |
145 ''' Build the path to the toolchain directory. | |
146 | |
147 Given the host, sdk-root directory, sdk platform, architecture and library | |
148 variant, this function builds the path to the toolchain directory. | |
149 | |
150 Examples: | |
151 For | |
152 sdk_root == 'c:/cool_code/nacl_sdk' | |
153 arch == 'x86-32' | |
154 lib variant == 'newlib' | |
155 nacl platform = 'pepper_17' | |
156 host == 'mac' | |
157 this function returns : | |
158 toolchain_path == /cool_code/nacl_sdk/pepper_17/toolchain/mac_x86_newlib | |
159 | |
160 Args: | |
161 options: the options instances containing attributes options.host, | |
162 options.arch, options.lib_variant, options.sdk_root and | |
163 options.sdk_platform. | |
164 | |
165 Returns: | |
166 A string containing the absolute path to the base directory for the | |
167 toolchain. | |
168 ''' | |
169 host = options.host | |
170 arch = options.arch | |
171 variant = options.lib_variant | |
172 toolchain_dir = __HOST_TO_TOOLCHAIN_MAP[host][arch][variant] | |
173 base_dir = os.path.abspath(options.sdk_root) | |
174 return os.path.join(base_dir, options.sdk_platform, 'toolchain', | |
175 toolchain_dir) | |
176 | |
177 | |
178 def ConfigureBaseEnv(merge): | |
179 ''' Configure and return a new env instance with the essential options. | |
180 | |
181 Create and return a new env instance with the base configuration. That env | |
182 contains at least an empty entry for each key defined in __ALL_ENV_KEYS. | |
183 However, if merge is True, a copy of the current os.environ is used to seed | |
184 env. | |
185 | |
186 Argument: | |
187 merge: True ==> merge build configuration with os.environ.. | |
188 | |
189 Returns: | |
190 A base env map. | |
191 ''' | |
192 env = {} | |
193 if merge: | |
194 for key, value in os.environ.items(): | |
195 env[key] = [value] | |
196 # Ensure that every env key has a default definition. | |
197 for key in __ALL_ENV_KEYS: | |
198 env.setdefault(key, []) | |
199 return env | |
200 | |
201 | |
202 def SetBuildTools(env, tool_bin_path, tool_prefix, extras_flags=''): | |
203 ''' Configure the build tools build flags in env. | |
204 | |
205 Given the absolute path to the toolchain's bin directory, tool_prefix and | |
206 optional extra_flags build flags, set the entries for the build tools | |
207 in env. For instance, using the sample path from GetToolchainPath above and | |
208 tool_prefix = 'i686-nacl-' we would get | |
209 | |
210 env['CC'] = | |
211 '/cool_code/nacl_sdk/pepper_17/toolchain/mac_x86_newlib/bin/i686-nacl-gcc' | |
212 | |
213 Args: | |
214 env: the env map to setup. | |
215 tool_bin_path: the absolute path to the toolchain's bin directory. | |
216 tool_prefix: a string with the tool's prefix, such as 'i686-nacl-'. | |
217 extra_flags: optional extra flags, such as ' -m64'. | |
218 ''' | |
219 for key, val in __BUILD_TOOLS.iteritems(): | |
220 tool_name = val.format(prefix=tool_prefix, extras=extras_flags) | |
221 env[key] = os.path.join(tool_bin_path, tool_name) | |
222 | |
223 | |
224 def SetRuntimeTools(env, tool_runtime_path): | |
225 ''' Setup the runtime tools in env. | |
226 | |
227 Given an absolute path to the toolchain's runtime directory, setup the | |
228 entries for the runtime tools in env. | |
229 | |
230 Args: | |
231 env: the env map to setup. | |
232 tool_runtime_path: the absolute path to the toolchain's runtime directory. | |
233 ''' | |
234 env['NACL_IRT_CORE32'] = os.path.join(tool_runtime_path, | |
235 'irt_core_x86_32.nexe') | |
236 env['NACL_IRT_CORE64'] = os.path.join(tool_runtime_path, | |
237 'irt_core_x86_64.nexe') | |
238 | |
239 | |
240 def SetCommonBuildOptions(env, options): | |
241 ''' Set the common build options, such as CFLAGS. | |
242 | |
243 Set the build options, such as CFLAGS that are common to all build | |
244 configurations, given the built type. | |
245 | |
246 Args: | |
247 env: the env map to set. | |
248 build_type: one of 'debug' or 'release'. | |
249 ''' | |
250 # Set the build flags from __BUILD_OPTIONS. | |
251 for key, val in __BUILD_OPTIONS.iteritems(): | |
252 env[key].extend(val) | |
253 # Add the build-type specific flags. | |
254 env['CFLAGS'].extend(__BUILD_TYPES[options.build_type]) | |
255 env['CXXFLAGS'].extend(__BUILD_TYPES[options.build_type]) | |
256 if not options.no_ppapi: | |
257 env['LDFLAGS'].extend(['-lppapi']) | |
258 | |
259 | |
260 def SetupX86Env(options): | |
261 ''' Generate a new env map for X86 builds. | |
262 | |
263 Generate and return a new env map for x86-NN architecture. The NN bit | |
264 size is derived from options.arch. | |
265 | |
266 Argument: | |
267 options: the cmd-line options. | |
268 | |
269 Returns: | |
270 A new env map with the build configuration flags set. | |
271 ''' | |
272 env = ConfigureBaseEnv(options.merge) | |
273 | |
274 # Where to find tools and libraries within the toolchain directory. | |
275 tool_bin_path = os.path.join(options.toolchain_path, 'bin') | |
276 tool_runtime_path = os.path.join(options.toolchain_path, 'runtime') | |
277 | |
278 # Store the bin paths into env. This isn't really part of the build | |
279 # environment. But it's nice to have there for reference. | |
280 env['NACL_TOOL_BIN_PATH'] = tool_bin_path | |
281 env['NACL_TOOL_RUNTIME_PATH'] = tool_runtime_path | |
282 | |
283 if options.arch == 'x86-32': | |
284 SetBuildTools(env, tool_bin_path, 'i686-nacl-', extras_flags=' -m32') | |
285 else: | |
286 assert(options.arch == 'x86-64') | |
287 SetBuildTools(env, tool_bin_path, 'x86_64-nacl-', extras_flags=' -m64') | |
288 SetRuntimeTools(env, tool_runtime_path) | |
289 SetCommonBuildOptions(env, options) | |
290 return env | |
291 | |
292 | |
293 def dump(options, env, template): | |
294 ''' Dump the build settings in env to stdout. | |
295 | |
296 Args: | |
297 options: the cmd-line options, used to output the target buid configuartion. | |
298 env: the env map with the build flags. | |
299 template: a fiormatting template used to format options output. It must | |
300 contain format fields 'option' and 'value'. | |
301 ''' | |
302 if options.pretty_print: | |
303 print '\nConfiguration:' | |
304 print '-------------' | |
305 print ' Host = %s' % options.host | |
306 print ' NaCl SDK root = %s' % options.sdk_root | |
307 print ' SDK platform = %s' % options.sdk_platform | |
308 print ' Target architecture = %s' % options.arch | |
309 print ' Lib variant = %s' % options.lib_variant | |
310 | |
311 if options.pretty_print: | |
312 print '\nNaCl toolchain paths:' | |
313 print '-------------------------' | |
314 print ' toolchain = %s' % options.toolchain_path | |
315 print ' toolchain bin = %s' % env['NACL_TOOL_BIN_PATH'] | |
316 print ' toolchain runtime = %s' % env['NACL_TOOL_RUNTIME_PATH'] | |
317 | |
318 if options.pretty_print: | |
319 print '\nBuild tools:' | |
320 print '-----------' | |
321 print template.format(option='CC', value=env['CC']) | |
322 print template.format(option='CXX', value=env['CXX']) | |
323 print template.format(option='AR', value=env['AR']) | |
324 print template.format(option='LINK', value=env['LINK']) | |
325 print template.format(option='STRIP', value=env['STRIP']) | |
326 print template.format(option='RANLIB', value=env['RANLIB']) | |
327 | |
328 if options.pretty_print: | |
329 print '\nBuild settings:' | |
330 print '--------------' | |
331 print template.format(option='CFLAGS', | |
332 value=FormatOptionList(option_list=env['CFLAGS'])) | |
333 print template.format(option='CXXFLAGS', | |
334 value=FormatOptionList(option_list=env['CXXFLAGS'])) | |
335 print template.format(option='LDFLAGS', | |
336 value=FormatOptionList(option_list=env['LDFLAGS'])) | |
337 print template.format(option='CPPFLAGS', | |
338 value=FormatOptionList(option_list=env['CPPFLAGS'])) | |
339 if options.pretty_print: | |
340 print '\nRuntime tools:' | |
341 print '-------------' | |
342 print template.format(option='NACL_IRT_CORE32', value=env['NACL_IRT_CORE32']) | |
343 print template.format(option='NACL_IRT_CORE64', value=env['NACL_IRT_CORE64']) | |
344 print '' | |
345 | |
346 | |
347 def NormalizeEnv(env): | |
348 ''' Returns a copy of env normalized. | |
349 | |
350 Internally, this script uses lists to keep track of build settings in env. | |
351 This function converts these list to space-separated strings of items, | |
352 suitable for use as a subprocess env. | |
353 | |
354 Argument: | |
355 env: env map that must be normalized. | |
356 | |
357 Returns: | |
358 A copy of env with lists converted to strings. | |
359 ''' | |
360 norm_env = {} | |
361 for key, value in env.iteritems(): | |
362 if isinstance(value, list): | |
363 norm_env[key] = ' '.join(value) | |
364 else: | |
365 norm_env[key] = value | |
366 return norm_env | |
367 | |
368 | |
369 def RunCommandOrShell(cmd_list, env): | |
370 ''' Run the command in cmd_list or a shell if cmd_list is empty. | |
371 | |
372 Run the command in cmd_list using a normalized copy of env. For instance, | |
373 cmd_list might contain the items ['make', 'application'], which would | |
374 cause command 'make application' to run in the current directory. If cmd_list | |
375 is empty, this function will spawn a new sbushell instead. | |
376 | |
377 Args: | |
378 cmd_list: the command list to run. | |
379 env: the environment to use. | |
380 ''' | |
381 # If cmd_list is empty, set it up to spawn a shell instead. If cmd_list | |
382 # isn't empty, it will run in the current shell (for security and so that the | |
383 # user can see the output). | |
384 new_shell = False | |
385 if cmd_list: | |
386 # Normalize cmd_list by building a list of individual arguments. | |
387 new_cmd_list = [] | |
388 for item in cmd_list: | |
389 new_cmd_list += item.split() | |
390 cmd_list = new_cmd_list | |
391 else: | |
392 # Build a shell command. | |
393 new_shell = True | |
394 if sys.platform == 'win32': | |
395 cmd_list = ['cmd'] | |
396 else: | |
397 cmd_list = ['/bin/bash', '-s'] | |
398 return subprocess.call(cmd_list, env=NormalizeEnv(env), shell=new_shell) | |
399 | |
400 | |
401 def GenerateBuildSettings(options, args): | |
402 ''' Generate the build settings and dump them or invoke a command. | |
403 | |
404 Given the cmd-line options and remaining cmd-line arguments, generate the | |
405 required build settings and either dump them to stdout or invoke a shell | |
406 command. | |
407 | |
408 Args: | |
409 options: cmd-line options. | |
410 args: unconsumed cmd-line arguments. | |
411 | |
412 Returns: | |
413 0 in case of success or a command result code otherwise. | |
414 ''' | |
415 # A few generated options, which we store in options for convenience. | |
416 options.host = __PLATFORM_TO_HOST_MAP[sys.platform] | |
417 options.sdk_root = GetNaclSdkRoot() | |
418 options.toolchain_path = GetToolchainPath(options) | |
419 | |
420 env = SetupX86Env(options) | |
421 if options.dump: | |
422 dump(options, env, template=options.format_template) | |
423 return 0 | |
424 else: | |
425 return RunCommandOrShell(args, env) | |
426 | |
427 | |
428 def main(argv): | |
429 ''' Do main stuff, mainly. | |
430 ''' | |
431 parser = optparse.OptionParser(usage=__GLOBAL_HELP) | |
432 parser.add_option( | |
433 '-a', '--arch', dest='arch', | |
434 choices=['x86-32', 'x86-64'], | |
435 default='x86-64', | |
436 help='The target architecture; one of x86-32 or x86-64. ' | |
437 '[default = %default.]') | |
438 parser.add_option( | |
439 '-A', '--no_ppapi', dest='no_ppapi', | |
440 default=False, | |
441 action='store_true', | |
442 help='Do not add -lppapi to the link settings.') | |
443 parser.add_option( | |
444 '-d', '--dump', dest='dump', | |
445 default=False, | |
446 action='store_true', | |
447 help='Dump the build settings to stdout') | |
448 parser.add_option( | |
449 '-D', '--pretty_print', dest='pretty_print', | |
450 default=False, | |
451 action='store_true', | |
452 help='Print section headers when dumping to stdout') | |
453 parser.add_option( | |
454 '-f', '--format_template', dest='format_template', | |
455 default=' {option}={value}', | |
456 help="The formatting template used to output (option, value) pairs." | |
457 "[default='%default.']") | |
458 parser.add_option( | |
459 '-n', '--no_merge', dest='merge', | |
460 default=True, | |
461 action='store_false', | |
462 help='Do not merge the build options with current environment. By default' | |
463 ' %prog merges the build flags with the current environment vars.' | |
464 ' This option turns that off.') | |
465 parser.add_option( | |
466 '-p', '--platform', dest='sdk_platform', | |
467 default=__DEFAULT_SDK_PLATFORM, | |
468 help='The SDK platform to use; e.g. pepper_16. [default = %default.]') | |
469 parser.add_option( | |
470 '-t', '--build_type', dest='build_type', | |
471 choices=__BUILD_TYPES.keys(), | |
472 default='debug', | |
473 help='The desired build type; one of debug or release.' | |
474 ' [default = %default.]') | |
475 parser.add_option( | |
476 '-v', '--variant', dest='lib_variant', | |
477 choices=['glibc', 'newlib'], default='newlib', | |
478 help='The lib variant to use; one of glibc or newlib. ' | |
479 '[default = %default.]') | |
480 | |
481 (options, args) = parser.parse_args(argv) | |
482 | |
483 # Verify that we're running on a supported host. | |
484 if sys.platform not in __PLATFORM_TO_HOST_MAP: | |
485 sys.stderr.write('Platform %s is not supported.' % sys.platform) | |
486 return 1 | |
487 | |
488 return GenerateBuildSettings(options, args) | |
489 | |
490 | |
491 if __name__ == '__main__': | |
492 sys.exit(main(sys.argv[1:])) | |
OLD | NEW |