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.
6 ''' A tool to setup the NaCl build env and invoke a command such as make '''
8 __author__
= 'gwink@google.com (Georges Winkenbach)'
15 # The default sdk platform to use if the user doesn't specify one.
16 __DEFAULT_SDK_PLATFORM
= 'pepper_15'
19 __GLOBAL_HELP
= '''%prog options [command]
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.
27 # Map the string stored in |sys.platform| into a toolchain host specifier.
28 __PLATFORM_TO_HOST_MAP
= {
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
39 __HOST_TO_TOOLCHAIN_MAP
= {
40 'mac': { # Host arch variant
42 'newlib': 'mac_x86_newlib', # Mac x86-32 newlib
43 'glibc' : 'mac_x86'}, # Mac x86-32 glibc
45 'newlib': 'mac_x86_newlib', # Mac x86-64 newlib
46 'glibc' : 'mac_x86'}, # Mac x86-64 glibc
50 'newlib': 'win_x86_newlib', # Windows x86-32 newlib
51 'glibc' : 'win_x86'}, # Windows x86-32 glibc
53 'newlib': 'win_x86_newlib', # Windows x86-64 newlib
54 'glibc' : 'win_x86'}, # Windows x86-64 glibc
58 'newlib': 'linux_x86_newlib', # Windows x86-32 newlib
59 'glibc' : 'linux_x86'}, # Windows x86-32 glibc
61 'newlib': 'linux_x86_newlib', # Windows x86-64 newlib
62 'glibc' : 'linux_x86'}, # Windows x86-64 glibc
66 # Map architecture specification to the corresponding tool-name prefix.
68 __VALID_ARCH_SPECS
= {
74 __VALID_VARIANT
= ['glibc', 'newlib']
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'
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',
88 # List of env keys for build options with corresponding settings that are
89 # common to all build configurations.
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'],
97 # All the build-flags env keys in one list.
98 __ALL_ENV_KEYS
= __BUILD_TOOLS
.keys() + __BUILD_OPTIONS
.keys()
100 # Map build types to the corresponding build flags.
107 def FormatOptionList(option_list
, prefix
='', separator
=' '):
108 ''' Format a list of build-option items into a string.
110 Format a list of build-option items into a string suitable for output.
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.
118 A formatted string. An empty string if list is empty.
120 return separator
.join([prefix
+ item
for item
in option_list
])
123 def GetNaclSdkRoot():
124 ''' Produce a string with the full path to the NaCl SDK root.
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.
134 A string with the path to the NaCl SDK root.
136 if 'NACL_SDK_ROOT' in os
.environ
:
137 return os
.environ
['NACL_SDK_ROOT']
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')
144 def GetToolchainPath(options
):
145 ''' Build the path to the toolchain directory.
147 Given the host, sdk-root directory, sdk platform, architecture and library
148 variant, this function builds the path to the toolchain directory.
152 sdk_root == 'c:/cool_code/nacl_sdk'
154 lib variant == 'newlib'
155 nacl platform = 'pepper_17'
157 this function returns :
158 toolchain_path == /cool_code/nacl_sdk/pepper_17/toolchain/mac_x86_newlib
161 options: the options instances containing attributes options.host,
162 options.arch, options.lib_variant, options.sdk_root and
163 options.sdk_platform.
166 A string containing the absolute path to the base directory for the
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',
178 def ConfigureBaseEnv(merge
):
179 ''' Configure and return a new env instance with the essential options.
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
187 merge: True ==> merge build configuration with os.environ..
194 for key
, value
in os
.environ
.items():
196 # Ensure that every env key has a default definition.
197 for key
in __ALL_ENV_KEYS
:
198 env
.setdefault(key
, [])
202 def SetBuildTools(env
, tool_bin_path
, tool_prefix
, extras_flags
=''):
203 ''' Configure the build tools build flags in env.
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
211 '/cool_code/nacl_sdk/pepper_17/toolchain/mac_x86_newlib/bin/i686-nacl-gcc'
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'.
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
)
224 def SetRuntimeTools(env
, tool_runtime_path
):
225 ''' Setup the runtime tools in env.
227 Given an absolute path to the toolchain's runtime directory, setup the
228 entries for the runtime tools in env.
231 env: the env map to setup.
232 tool_runtime_path: the absolute path to the toolchain's runtime directory.
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')
240 def SetCommonBuildOptions(env
, options
):
241 ''' Set the common build options, such as CFLAGS.
243 Set the build options, such as CFLAGS that are common to all build
244 configurations, given the built type.
247 env: the env map to set.
248 build_type: one of 'debug' or 'release'.
250 # Set the build flags from __BUILD_OPTIONS.
251 for key
, val
in __BUILD_OPTIONS
.iteritems():
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'])
260 def SetupX86Env(options
):
261 ''' Generate a new env map for X86 builds.
263 Generate and return a new env map for x86-NN architecture. The NN bit
264 size is derived from options.arch.
267 options: the cmd-line options.
270 A new env map with the build configuration flags set.
272 env
= ConfigureBaseEnv(options
.merge
)
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')
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
283 if options
.arch
== 'x86-32':
284 SetBuildTools(env
, tool_bin_path
, 'i686-nacl-', extras_flags
=' -m32')
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
)
293 def dump(options
, env
, template
):
294 ''' Dump the build settings in env to stdout.
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'.
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
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']
318 if options
.pretty_print
:
319 print '\nBuild tools:'
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'])
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'])
347 def NormalizeEnv(env
):
348 ''' Returns a copy of env normalized.
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.
355 env: env map that must be normalized.
358 A copy of env with lists converted to strings.
361 for key
, value
in env
.iteritems():
362 if isinstance(value
, list):
363 norm_env
[key
] = ' '.join(value
)
365 norm_env
[key
] = value
369 def RunCommandOrShell(cmd_list
, env
):
370 ''' Run the command in cmd_list or a shell if cmd_list is empty.
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.
378 cmd_list: the command list to run.
379 env: the environment to use.
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).
386 # Normalize cmd_list by building a list of individual arguments.
388 for item
in cmd_list
:
389 new_cmd_list
+= item
.split()
390 cmd_list
= new_cmd_list
392 # Build a shell command.
394 if sys
.platform
== 'win32':
397 cmd_list
= ['/bin/bash', '-s']
398 return subprocess
.call(cmd_list
, env
=NormalizeEnv(env
), shell
=new_shell
)
401 def GenerateBuildSettings(options
, args
):
402 ''' Generate the build settings and dump them or invoke a command.
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
409 options: cmd-line options.
410 args: unconsumed cmd-line arguments.
413 0 in case of success or a command result code otherwise.
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
)
420 env
= SetupX86Env(options
)
422 dump(options
, env
, template
=options
.format_template
)
425 return RunCommandOrShell(args
, env
)
429 ''' Do main stuff, mainly.
431 parser
= optparse
.OptionParser(usage
=__GLOBAL_HELP
)
433 '-a', '--arch', dest
='arch',
434 choices
=['x86-32', 'x86-64'],
436 help='The target architecture; one of x86-32 or x86-64. '
437 '[default = %default.]')
439 '-A', '--no_ppapi', dest
='no_ppapi',
442 help='Do not add -lppapi to the link settings.')
444 '-d', '--dump', dest
='dump',
447 help='Dump the build settings to stdout')
449 '-D', '--pretty_print', dest
='pretty_print',
452 help='Print section headers when dumping to stdout')
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.']")
459 '-n', '--no_merge', dest
='merge',
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.')
466 '-p', '--platform', dest
='sdk_platform',
467 default
=__DEFAULT_SDK_PLATFORM
,
468 help='The SDK platform to use; e.g. pepper_16. [default = %default.]')
470 '-t', '--build_type', dest
='build_type',
471 choices
=__BUILD_TYPES
.keys(),
473 help='The desired build type; one of debug or release.'
474 ' [default = %default.]')
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.]')
481 (options
, args
) = parser
.parse_args(argv
)
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
)
488 return GenerateBuildSettings(options
, args
)
491 if __name__
== '__main__':
492 sys
.exit(main(sys
.argv
[1:]))