Skip to content

Commit

Permalink
Adding a build script (#33)
Browse files Browse the repository at this point in the history
- Added build.py (executable)
- Updated the src/coreclr/common.props file to suppress .NET Core Preview warnings
- Added a script to run src/coreclr benchmarks against CoreCLr's CoreRun
- Updated src/coreclr readme
  • Loading branch information
jorive authored May 24, 2018
1 parent 31db84b commit 9a0b211
Show file tree
Hide file tree
Showing 14 changed files with 742 additions and 0 deletions.
167 changes: 167 additions & 0 deletions scripts/build.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
#!/usr/bin/env python3

'''
Builds the CoreClr Benchmarks
'''

from subprocess import CalledProcessError
from traceback import format_exc
from typing import Tuple

import argparse
import datetime
import logging
import os
import sys

from build.common import get_logging_console_handler
from build.common import get_logging_file_handler
from build.common import get_repo_root_path
from build.common import is_supported_version
from build.common import log_start_message
from build.common import LAUNCH_TIME
from build.common import LOGGING_FORMATTER
from build.exception.FatalError import FatalError
from build.parser.TargetFrameworkAction import TargetFrameworkAction
from build.process.DotNet import DotNet
from build.runner.RunCommand import RunCommand


def generate_log_file_name() -> str:
'''Generates a unique log file name for the current script'''
log_dir = os.path.join(get_repo_root_path(), 'logs')
if not os.path.exists(log_dir):
os.makedirs(log_dir)

script_name = os.path.splitext(os.path.basename(sys.argv[0]))[0]
timestamp = datetime.datetime.fromtimestamp(LAUNCH_TIME).strftime(
"%Y%m%d%H%M%S")
log_file_name = '{}-{}-pid{}.log'.format(
timestamp, script_name, os.getpid())
return os.path.join(log_dir, log_file_name)


def init_logging(verbose: bool) -> str:
'''Initializes the loggers used by the script.'''
logging.getLogger().setLevel(logging.INFO)

log_file_name = generate_log_file_name()

for logger in ['shell', 'script']:
logging.getLogger(logger).addHandler(get_logging_console_handler(
LOGGING_FORMATTER, verbose))
logging.getLogger(logger).addHandler(get_logging_file_handler(
log_file_name, LOGGING_FORMATTER))
logging.getLogger(logger).setLevel(logging.INFO)

return log_file_name


def check_requirements(log_file: str, verbose: bool) -> None:
'''
Checks that the requirements needs to build the CoreClr benchmarks are met.
'''
logging.getLogger('script').info("Making sure dotnet exists...")
try:
cmdline = ['dotnet', '--info']
RunCommand(log_file, cmdline, verbose=verbose).run('dotnet-info')
except Exception:
raise FatalError("Cannot find dotnet.")


def process_arguments() -> Tuple[str, list, bool]:
'''
Function used to parse the command line arguments passed to this script
through the cli.
'''
parser = argparse.ArgumentParser(
description="Builds the CoreClr benchmarks.",
)
parser.add_argument(
'-c', '--configuration',
metavar='CONFIGURATION',
required=False,
default='release',
choices=['debug', 'release'],
type=str.casefold,
help='Configuration use for building the project (default "release").',
)
parser.add_argument(
'-f', '--frameworks',
metavar='FRAMEWORK',
required=False,
nargs='*',
action=TargetFrameworkAction,
default=TargetFrameworkAction.supported_target_frameworks(),
help='Target frameworks to publish for (default all).',
)
parser.add_argument(
'-v', '--verbose',
required=False,
default=False,
action='store_true',
help='Turns on verbosity (default "False")',
)

# --verbosity <LEVEL>
# ['quiet', 'minimal', 'normal', 'detailed', 'diagnostic']

args = parser.parse_args()
return (
args.configuration,
args.frameworks,
args.verbose
)


def build_coreclr(
log_file: str,
configuration: str,
frameworks: list,
verbose: bool) -> None:
'''Builds the CoreClr set of benchmarks (Code Quality).'''
working_directory = os.path.join(
get_repo_root_path(), 'src', 'coreclr', 'PerformanceHarness')
csproj_file = 'PerformanceHarness.csproj'

dotnet = DotNet(log_file, working_directory, csproj_file, verbose)
dotnet.restore()
for framework in frameworks:
dotnet.publish(configuration, framework, 'CoreClr-Benchmarks')


def main() -> int:
'''Script main entry point.'''
try:
if not is_supported_version():
raise FatalError("Unsupported python version.")

args = process_arguments()
configuration, frameworks, verbose = args
log_file = init_logging(verbose)

log_start_message('script')
check_requirements(log_file, verbose)
build_coreclr(log_file, configuration, frameworks, verbose)

return 0
except FatalError as ex:
logging.getLogger('script').error(str(ex))
except CalledProcessError as ex:
logging.getLogger('script').error(
'Command: "%s", exited with status: %s', ex.cmd, ex.returncode)
except IOError as ex:
logging.getLogger('script').error(
"I/O error (%s): %s", ex.errno, ex.strerror)
except SystemExit: # Argparse throws this exception when it exits.
pass
except Exception:
logging.getLogger('script')(
'Unexpected error: {}'.format(sys.exc_info()[0]))
logging.getLogger('script')(format_exc())
raise
return 1


if __name__ == "__main__":
sys.exit(main())
Empty file added scripts/build/__init__.py
Empty file.
88 changes: 88 additions & 0 deletions scripts/build/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
'''
Common functionality used by the .NET Performance Repository build scripts.
'''

from contextlib import contextmanager

import datetime
import logging
import os
import sys
import time


LAUNCH_TIME = time.time()
LOGGING_FORMATTER = logging.Formatter(
fmt='[%(asctime)s][%(levelname)s] %(message)s',
datefmt="%Y-%m-%d %H:%M:%S")


def is_supported_version() -> bool:
'''Checks if the script is running on the supported version (>=3.5).'''
return sys.version_info.major > 2 and sys.version_info.minor > 4


def log_start_message(name) -> None:
'''Used to log a start event message header.'''
start_msg = "Script started at {}".format(
str(datetime.datetime.fromtimestamp(LAUNCH_TIME)))
logging.getLogger(name).info('-' * len(start_msg))
logging.getLogger(name).info(start_msg)
logging.getLogger(name).info('-' * len(start_msg))


def get_script_path() -> str:
'''Gets this script directory.'''
return sys.path[0]


def get_repo_root_path() -> str:
'''Gets repository root directory.'''
return os.path.abspath(os.path.join(get_script_path(), '..'))


@contextmanager
def push_dir(path: str = None) -> None:
'''
Adds the specified location to the top of a location stack, then changes to
the specified directory.
'''
if path:
prev = os.getcwd()
try:
logging.getLogger('shell').info('pushd "%s"', path)
os.chdir(path)
yield
finally:
logging.getLogger('shell').info('popd')
os.chdir(prev)
else:
yield


def get_logging_console_handler(
fmt: logging.Formatter,
verbose: bool) -> logging.StreamHandler:
'''
Gets a logging console handler (logging.StreamHandler) based on the
specified formatter (logging.Formatter) and verbosity.
'''
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO if verbose else logging.WARNING)
console_handler.setFormatter(fmt)
return console_handler


def get_logging_file_handler(
file: str,
fmt: logging.Formatter,
set_formatter: bool = True) -> logging.FileHandler:
'''
Gets a logging file handler (logging.FileHandler) based on the specified
formatter (logging.Formatter).
'''
file_handler = logging.FileHandler(file)
file_handler.setLevel(logging.INFO)
if set_formatter:
file_handler.setFormatter(fmt)
return file_handler
10 changes: 10 additions & 0 deletions scripts/build/exception/FatalError.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
'''
Contains the definition of the FatalError exception.
'''


class FatalError(Exception):
'''
Raised for various script errors regarding environment and build
requirements.
'''
Empty file.
30 changes: 30 additions & 0 deletions scripts/build/parser/TargetFrameworkAction.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'''
Contains the definition of the TargetFrameworkAction type used to parse
the .NET Cli the supported target frameworks.
'''

import argparse


class TargetFrameworkAction(argparse.Action):
'''
Used by the ArgumentParser to represent the information needed to parse the
supported .NET Core target frameworks argument from the command line.
'''

def __call__(self, parser, namespace, values, option_string=None):
if values:
wrong_choices = []
for value in values:
if value not in self.supported_target_frameworks():
wrong_choices.append(value)
if wrong_choices:
message = ', '.join(wrong_choices)
message = 'Invalid choice(s): {}'.format(message)
raise argparse.ArgumentError(self, message)
setattr(namespace, self.dest, values)

@staticmethod
def supported_target_frameworks() -> list:
'''List of supported .NET Core target frameworks.'''
return ['netcoreapp1.1', 'netcoreapp2.0', 'netcoreapp2.1', 'net461']
Empty file.
Loading

0 comments on commit 9a0b211

Please sign in to comment.