450 lines
10 KiB
Python
Executable File
450 lines
10 KiB
Python
Executable File
#!/usr/bin/env python
|
|
|
|
# Copyright 2013 Ben Vanik. All Rights Reserved.
|
|
|
|
"""
|
|
"""
|
|
|
|
__author__ = 'ben.vanik@gmail.com (Ben Vanik)'
|
|
|
|
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
|
|
|
|
def main():
|
|
# Add self to the root search path.
|
|
sys.path.insert(0, os.path.abspath(os.path.dirname(__file__)))
|
|
|
|
# Augment path to include our fancy things.
|
|
os.environ['PATH'] += os.pathsep + os.pathsep.join([
|
|
os.path.abspath('third_party/ninja/'),
|
|
os.path.abspath('third_party/gyp/')
|
|
])
|
|
|
|
# Check python version.
|
|
if sys.version_info < (2, 7):
|
|
print 'ERROR: python 2.7+ required'
|
|
sys.exit(1)
|
|
return
|
|
|
|
# Grab all commands.
|
|
commands = discover_commands()
|
|
|
|
# Parse command name and dispatch.
|
|
try:
|
|
if len(sys.argv) < 2:
|
|
raise ValueError('No command given')
|
|
command_name = sys.argv[1]
|
|
if not commands.has_key(command_name):
|
|
raise ValueError('Command "%s" not found' % (command_name))
|
|
|
|
command = commands[command_name]
|
|
return_code = run_command(command=command,
|
|
args=sys.argv[2:],
|
|
cwd=os.getcwd())
|
|
except ValueError:
|
|
print usage(commands)
|
|
return_code = 1
|
|
except Exception as e:
|
|
#print e
|
|
raise
|
|
return_code = 1
|
|
sys.exit(return_code)
|
|
|
|
|
|
def discover_commands():
|
|
"""Looks for all commands and returns a dictionary of them.
|
|
In the future commands could be discovered on disk.
|
|
|
|
Returns:
|
|
A dictionary containing name-to-Command mappings.
|
|
"""
|
|
commands = {
|
|
'setup': SetupCommand(),
|
|
'pull': PullCommand(),
|
|
'gyp': GypCommand(),
|
|
'build': BuildCommand(),
|
|
'test': TestCommand(),
|
|
'clean': CleanCommand(),
|
|
'nuke': NukeCommand(),
|
|
}
|
|
return commands
|
|
|
|
|
|
def usage(commands):
|
|
"""Gets usage info that can be displayed to the user.
|
|
|
|
Args:
|
|
commands: A command dictionary from discover_commands.
|
|
|
|
Returns:
|
|
A string containing usage info and a command listing.
|
|
"""
|
|
s = 'xenia-build.py command [--help]\n'
|
|
s += '\n'
|
|
s += 'Commands:\n'
|
|
command_names = commands.keys()
|
|
command_names.sort()
|
|
for command_name in command_names:
|
|
s += ' %s\n' % (command_name)
|
|
command_help = commands[command_name].help_short
|
|
if command_help:
|
|
s += ' %s\n' % (command_help)
|
|
return s
|
|
|
|
|
|
def run_command(command, args, cwd):
|
|
"""Runs a command with the given context.
|
|
|
|
Args:
|
|
command: Command to run.
|
|
args: Arguments, with the app and command name stripped.
|
|
cwd: Current working directory.
|
|
|
|
Returns:
|
|
0 if the command succeeded and non-zero otherwise.
|
|
|
|
Raises:
|
|
ValueError: The command could not be found or was not specified.
|
|
"""
|
|
# TODO(benvanik): parse arguments/etc.
|
|
return command.execute(args, cwd)
|
|
|
|
|
|
def has_bin(bin):
|
|
"""Checks whether the given binary is present.
|
|
"""
|
|
for path in os.environ["PATH"].split(os.pathsep):
|
|
path = path.strip('"')
|
|
exe_file = os.path.join(path, bin)
|
|
if os.path.isfile(exe_file) and os.access(exe_file, os.X_OK):
|
|
return True
|
|
exe_file = exe_file + '.exe'
|
|
if os.path.isfile(exe_file) and os.access(exe_file, os.X_OK):
|
|
return True
|
|
return None
|
|
|
|
|
|
def shell_call(command, throw_on_error=True):
|
|
"""Executes a shell command.
|
|
|
|
Args:
|
|
command: Command to execute.
|
|
throw_on_error: Whether to throw an error or return the status code.
|
|
|
|
Returns:
|
|
If throw_on_error is False the status code of the call will be returned.
|
|
"""
|
|
if throw_on_error:
|
|
subprocess.check_call(command, shell=True)
|
|
return 0
|
|
else:
|
|
return subprocess.call(command, shell=True)
|
|
|
|
|
|
class Command(object):
|
|
"""Base type for commands.
|
|
"""
|
|
|
|
def __init__(self, name, help_short=None, help_long=None, *args, **kwargs):
|
|
"""Initializes a command.
|
|
|
|
Args:
|
|
name: The name of the command exposed to the management script.
|
|
help_short: Help text printed alongside the command when queried.
|
|
help_long: Extended help text when viewing command help.
|
|
"""
|
|
self.name = name
|
|
self.help_short = help_short
|
|
self.help_long = help_long
|
|
|
|
def execute(self, args, cwd):
|
|
"""Executes the command.
|
|
|
|
Args:
|
|
args: Arguments list.
|
|
cwd: Current working directory.
|
|
|
|
Returns:
|
|
Return code of the command.
|
|
"""
|
|
return 1
|
|
|
|
|
|
def post_update_deps(config):
|
|
"""Runs common tasks that should be executed after any deps are changed.
|
|
|
|
Args:
|
|
config: 'debug' or 'release'.
|
|
"""
|
|
pass
|
|
|
|
|
|
class SetupCommand(Command):
|
|
"""'setup' command."""
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(SetupCommand, self).__init__(
|
|
name='setup',
|
|
help_short='Setup the build environment.',
|
|
*args, **kwargs)
|
|
|
|
def execute(self, args, cwd):
|
|
print 'Setting up the build environment...'
|
|
print ''
|
|
|
|
# Setup submodules.
|
|
print '- git submodule init / update...'
|
|
shell_call('git submodule init')
|
|
shell_call('git submodule update')
|
|
print ''
|
|
|
|
# Disable core.filemode on Windows to prevent weird file mode diffs in git.
|
|
# TODO(benvanik): check cygwin test - may be wrong when using Windows python
|
|
if os.path.exists('/Cygwin.bat'):
|
|
print '- setting filemode off on cygwin...'
|
|
shell_call('git config core.filemode false')
|
|
shell_call('git submodule foreach git config core.filemode false')
|
|
print ''
|
|
|
|
# Run the ninja bootstrap to build it, if it's missing.
|
|
if (not os.path.exists('third_party/ninja/ninja') and
|
|
not os.path.exists('third_party/ninja/ninja.exe')):
|
|
print '- preparing ninja...'
|
|
# Windows needs --x64 to force building the 64-bit ninja.
|
|
extra_args = ''
|
|
if sys.platform == 'win32':
|
|
extra_args = '--x64'
|
|
shell_call('python third_party/ninja/bootstrap.py ' + extra_args)
|
|
print ''
|
|
|
|
# Binutils.
|
|
# TODO(benvanik): disable on Windows
|
|
print '- binutils...'
|
|
if sys.platform == 'win32':
|
|
print 'WARNING: ignoring binutils on Windows... don\'t change tests!'
|
|
else:
|
|
if not os.path.exists('build/binutils'):
|
|
os.makedirs('build/binutils')
|
|
os.chdir('build/binutils')
|
|
shell_call(' '.join([
|
|
'../../third_party/binutils/configure',
|
|
'--disable-debug',
|
|
'--disable-dependency-tracking',
|
|
'--disable-werror',
|
|
'--enable-interwork',
|
|
'--enable-multilib',
|
|
'--target=powerpc-none-elf',
|
|
'--with-gnu-ld',
|
|
'--with-gnu-as',
|
|
]))
|
|
shell_call('make')
|
|
os.chdir(cwd)
|
|
print ''
|
|
|
|
post_update_deps('debug')
|
|
post_update_deps('release')
|
|
|
|
print '- running gyp...'
|
|
run_all_gyps()
|
|
print ''
|
|
|
|
print 'Success!'
|
|
return 0
|
|
|
|
|
|
class PullCommand(Command):
|
|
"""'pull' command."""
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(PullCommand, self).__init__(
|
|
name='pull',
|
|
help_short='Pulls the repo and all dependencies.',
|
|
*args, **kwargs)
|
|
|
|
def execute(self, args, cwd):
|
|
print 'Pulling...'
|
|
print ''
|
|
|
|
print '- pulling self...'
|
|
shell_call('git pull')
|
|
print ''
|
|
|
|
print '- pulling dependencies...'
|
|
shell_call('git submodule update')
|
|
print ''
|
|
|
|
post_update_deps('debug')
|
|
post_update_deps('release')
|
|
|
|
print '- running gyp...'
|
|
run_all_gyps()
|
|
print ''
|
|
|
|
print 'Success!'
|
|
return 0
|
|
|
|
|
|
def run_gyp(format):
|
|
"""Runs gyp on the main project with the given format.
|
|
|
|
Args:
|
|
format: gyp -f value.
|
|
"""
|
|
shell_call(' '.join([
|
|
'gyp',
|
|
'--include=common.gypi',
|
|
'-f %s' % (format),
|
|
# Set the VS version.
|
|
# TODO(benvanik): allow user to set?
|
|
'-G msvs_version=2012e',
|
|
# Removes the out/ from ninja builds.
|
|
'-G output_dir=.',
|
|
'--depth=.',
|
|
'--generator-output=build/xenia/',
|
|
'xenia.gyp',
|
|
]))
|
|
|
|
|
|
def run_all_gyps():
|
|
"""Runs all gyp configurations.
|
|
"""
|
|
run_gyp('ninja')
|
|
if sys.platform == 'darwin':
|
|
run_gyp('xcode')
|
|
elif sys.platform == 'win32':
|
|
run_gyp('msvs')
|
|
|
|
|
|
class GypCommand(Command):
|
|
"""'gyp' command."""
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(GypCommand, self).__init__(
|
|
name='gyp',
|
|
help_short='Runs gyp to update all projects.',
|
|
*args, **kwargs)
|
|
|
|
def execute(self, args, cwd):
|
|
print 'Running gyp...'
|
|
print ''
|
|
|
|
# Update GYP.
|
|
run_all_gyps()
|
|
|
|
print 'Success!'
|
|
return 0
|
|
|
|
|
|
class BuildCommand(Command):
|
|
"""'build' command."""
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(BuildCommand, self).__init__(
|
|
name='build',
|
|
help_short='Builds the project.',
|
|
*args, **kwargs)
|
|
|
|
def execute(self, args, cwd):
|
|
# TODO(benvanik): add arguments:
|
|
# --force
|
|
debug = '--debug' in args
|
|
config = 'debug' if debug else 'release'
|
|
|
|
print 'Building %s...' % (config)
|
|
print ''
|
|
|
|
print '- running gyp for ninja...'
|
|
run_gyp('ninja')
|
|
print ''
|
|
|
|
print '- building xenia in %s...' % (config)
|
|
result = shell_call('ninja -C build/xenia/%s' % (config),
|
|
throw_on_error=False)
|
|
print ''
|
|
if result != 0:
|
|
return result
|
|
|
|
print 'Success!'
|
|
return 0
|
|
|
|
|
|
class TestCommand(Command):
|
|
"""'test' command."""
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(TestCommand, self).__init__(
|
|
name='test',
|
|
help_short='Runs all tests.',
|
|
*args, **kwargs)
|
|
|
|
def execute(self, args, cwd):
|
|
print 'Testing...'
|
|
print ''
|
|
|
|
# First run make and update all of the test files.
|
|
# TOOD(benvanik): disable on Windows
|
|
print 'Updating test files...'
|
|
result = shell_call('make -C test/codegen/')
|
|
print ''
|
|
if result != 0:
|
|
return result
|
|
|
|
# Start the test runner.
|
|
print 'Launching test runner...'
|
|
result = shell_call('bin/xenia-test')
|
|
print ''
|
|
|
|
return result
|
|
|
|
|
|
class CleanCommand(Command):
|
|
"""'clean' command."""
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(CleanCommand, self).__init__(
|
|
name='clean',
|
|
help_short='Removes intermediate files and build output.',
|
|
*args, **kwargs)
|
|
|
|
def execute(self, args, cwd):
|
|
print 'Cleaning build artifacts...'
|
|
print ''
|
|
|
|
print '- removing build/xenia/...'
|
|
if os.path.isdir('build/xenia/'):
|
|
shutil.rmtree('build/xenia/')
|
|
print ''
|
|
|
|
print 'Success!'
|
|
return 0
|
|
|
|
|
|
class NukeCommand(Command):
|
|
"""'nuke' command."""
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(NukeCommand, self).__init__(
|
|
name='nuke',
|
|
help_short='Removes all build/ output.',
|
|
*args, **kwargs)
|
|
|
|
def execute(self, args, cwd):
|
|
print 'Cleaning build artifacts...'
|
|
print ''
|
|
|
|
print '- removing build/...'
|
|
if os.path.isdir('build/'):
|
|
shutil.rmtree('build/')
|
|
print ''
|
|
|
|
print 'Success!'
|
|
return 0
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|