Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding a build script #33

Merged
merged 6 commits into from
May 24, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Member

@DrewScoggins DrewScoggins May 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Should probably be verbosity

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was planning to add verbosity to the script for debuggability. This flag was to turn it on/off.
Thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that he has a potential verbosity level option commented out, I feel like the bool of verbose on or off makes sense as verbose, rather than verbosity. Verbosity implies levels.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the verbose setting that we pass to the publish and restore steps is always the default? Is this just for controlling the verbosity of the script?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-v is only for enable/disable the script verbosity.

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